diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 87219af7f..76758aa41 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -468,6 +468,8 @@ consvar_t cv_reducevfx = CVAR_INIT ("reducevfx", "No", CV_SAVE, CV_YesNo, NULL); static CV_PossibleValue_t votetime_cons_t[] = {{10, "MIN"}, {3600, "MAX"}, {0, NULL}}; consvar_t cv_votetime = CVAR_INIT ("votetime", "20", CV_NETVAR, votetime_cons_t, NULL); +consvar_t cv_botscanvote = CVAR_INIT ("botscanvote", "No", CV_CHEAT, CV_YesNo, NULL); + consvar_t cv_gravity = CVAR_INIT ("gravity", "0.8", CV_CHEAT|CV_FLOAT|CV_CALL, NULL, Gravity_OnChange); // change DEFAULT_GRAVITY if you change this consvar_t cv_soundtest = CVAR_INIT ("soundtest", "0", CV_CALL, NULL, SoundTest_OnChange); @@ -2645,29 +2647,52 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted) { char buf[2]; char *p = buf; + UINT8 sendPlayer = consoleplayer; - if (player >= MAXSPLITSCREENPLAYERS) + if (player == UINT8_MAX) { - return; + // Special game vote (map anger, duel) + if (!server) + { + return; + } + } + + if (player == UINT8_MAX) + { + // special vote + WRITEUINT8(p, UINT8_MAX); + } + else + { + INT32 i = 0; + WRITEUINT8(p, player); + + for (i = 0; i <= splitscreen; i++) + { + if (g_localplayers[i] == player) + { + sendPlayer = i; + } + } } - WRITEUINT8(p, g_localplayers[player]); WRITESINT8(p, voted); - SendNetXCmdForPlayer(player, XD_MODIFYVOTE, buf, p - buf); + SendNetXCmdForPlayer(sendPlayer, XD_MODIFYVOTE, buf, p - buf); } void D_PickVote(void) { char buf[2]; char* p = buf; - SINT8 temppicks[MAXPLAYERS]; - SINT8 templevels[MAXPLAYERS]; + SINT8 temppicks[VOTE_TOTAL]; + SINT8 templevels[VOTE_TOTAL]; SINT8 votecompare = VOTE_NOT_PICKED; UINT8 numvotes = 0, key = 0; INT32 i; - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { if (Y_PlayerIDCanVote(i) == false) { @@ -5434,23 +5459,45 @@ static void Got_ModifyVotecmd(UINT8 **cp, INT32 playernum) UINT8 targetID = READUINT8(*cp); SINT8 vote = READSINT8(*cp); - if (targetID >= MAXPLAYERS - || playernode[targetID] != playernode[playernum]) + if (targetID == UINT8_MAX) { - CONS_Alert(CONS_WARNING, - M_GetText ("Illegal modify vote command received from %s\n"), - player_names[playernum] - ); - - if (server) + if (playernum != serverplayer) // server-only special vote { - SendKick(playernum, KICK_MSG_CON_FAIL); + goto fail; } - return; + targetID = VOTE_SPECIAL; + } + else if (playeringame[targetID] == true && players[targetID].bot == true) + { + if (targetID >= MAXPLAYERS + || playernum != serverplayer) + { + goto fail; + } + } + else + { + if (targetID >= MAXPLAYERS + || playernode[targetID] != playernode[playernum]) + { + goto fail; + } } Y_SetPlayersVote(targetID, vote); + return; + +fail: + CONS_Alert(CONS_WARNING, + M_GetText ("Illegal modify vote command received from %s\n"), + player_names[playernum] + ); + + if (server) + { + SendKick(playernum, KICK_MSG_CON_FAIL); + } } static void Got_PickVotecmd(UINT8 **cp, INT32 playernum) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index d4c7d82a4..8cab1e10f 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -90,6 +90,7 @@ extern consvar_t cv_karteliminatelast; extern consvar_t cv_kartusepwrlv; extern consvar_t cv_votetime; +extern consvar_t cv_botscanvote; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, cv_kartdebughuddrop; extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; diff --git a/src/doomstat.h b/src/doomstat.h index 80d7f3635..e044ed0cf 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -411,6 +411,7 @@ struct mapheader_t cupheader_t *cup; ///< Cached cup size_t justPlayed; ///< Prevent this map from showing up in votes if it was recently picked. + size_t anger; ///< No one picked this map... it's mad now. // Titlecard information char lvlttl[22]; ///< Level name without "Zone". (21 character limit instead of 32, 21 characters can display on screen max anyway) @@ -734,8 +735,10 @@ extern boolean legitimateexit; extern boolean comebackshowninfo; extern tic_t curlap, bestlap; +#define VOTE_SPECIAL (MAXPLAYERS) +#define VOTE_TOTAL (MAXPLAYERS+1) extern INT16 g_voteLevels[4][2]; -extern SINT8 g_votes[MAXPLAYERS]; +extern SINT8 g_votes[VOTE_TOTAL]; extern SINT8 g_pickedVote; // =========================== diff --git a/src/g_game.c b/src/g_game.c index 1627c996d..e9168df51 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -299,7 +299,7 @@ boolean franticitems; // Frantic items currently enabled? // Voting system INT16 g_voteLevels[4][2]; // Levels that were rolled by the host -SINT8 g_votes[MAXPLAYERS]; // Each player's vote +SINT8 g_votes[VOTE_TOTAL]; // Each player's vote SINT8 g_pickedVote; // What vote the host rolls // Server-sided, synched variables @@ -3687,14 +3687,33 @@ static INT32 TOLMaps(UINT8 pgametype) // Find all the maps that are ok for (i = 0; i < nummapheaders; i++) { - if (!mapheaderinfo[i]) + if (mapheaderinfo[i] == NULL) + { continue; + } + if (mapheaderinfo[i]->lumpnum == LUMPERROR) + { continue; - if (!(mapheaderinfo[i]->typeoflevel & tolflag)) + } + + if ((mapheaderinfo[i]->typeoflevel & tolflag) == 0) + { continue; - if (mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) // Don't include Map Hell + } + + if (mapheaderinfo[i]->menuflags & LF2_HIDEINMENU) + { + // Don't include hidden continue; + } + + if (M_MapLocked(i + 1)) + { + // Don't include locked + continue; + } + num++; } diff --git a/src/k_kart.c b/src/k_kart.c index c06b15bd7..89d02517c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -342,6 +342,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_karteliminatelast); CV_RegisterVar(&cv_kartusepwrlv); CV_RegisterVar(&cv_votetime); + CV_RegisterVar(&cv_botscanvote); CV_RegisterVar(&cv_kartdebugitem); CV_RegisterVar(&cv_kartdebugamount); diff --git a/src/k_vote.c b/src/k_vote.c index 8db929bb6..5e8f78b2d 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -96,11 +96,14 @@ #define PILE_SPACING_W (PILE_WIDTH + PILE_SPACE) #define PILE_SPACING_H (PILE_HEIGHT + PILE_SPACE) +#define LOTS_OF_VOTES_X (120*FRACUNIT) +#define LOTS_OF_VOTES_Y (80*FRACUNIT) + // Give time for the animations to finish before finalizing the vote stages. #define SELECT_DELAY_TIME (TICRATE*4) #define PICK_DELAY_TIME (TICRATE/2) -//#define TEST_VOTES (11) +#define MAP_ANGER_MAX (VOTE_NUM_LEVELS) // Catcher data enum @@ -155,7 +158,7 @@ typedef struct // Voting roulette variables. typedef struct { - y_vote_pile pile[MAXPLAYERS]; + y_vote_pile pile[VOTE_TOTAL]; UINT8 anim; UINT8 tics; UINT32 offset; @@ -211,17 +214,29 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId) { player_t *player = NULL; + if (playerId == VOTE_SPECIAL) + { + // Special vote spot, always allow + return true; + } + if (playerId >= MAXPLAYERS || playeringame[playerId] == false) { return false; } player = &players[playerId]; - if (player->spectator == true || player->bot == true) + if (player->spectator == true) { return false; } + if (player->bot == true && cv_botscanvote.value == 0) + { + // Bots may only vote if the server allows it + return false; + } + return true; } @@ -231,10 +246,7 @@ static void Y_SortPile(void) UINT8 votesLeft = 0; INT32 i; -#ifdef TEST_VOTES - numVotes = TEST_VOTES; -#else - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { if (g_votes[i] == VOTE_NOT_PICKED) { @@ -243,7 +255,6 @@ static void Y_SortPile(void) numVotes++; } -#endif if (numVotes == 0) { @@ -252,16 +263,14 @@ static void Y_SortPile(void) votesLeft = numVotes; - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { y_vote_pile *const pile = &vote.roulette.pile[i]; -#ifndef TEST_VOTES if (g_votes[i] == VOTE_NOT_PICKED) { continue; } -#endif // Just center it for now. pile->destX = BASEVIDWIDTH << FRACBITS >> 1; @@ -313,7 +322,9 @@ static void Y_SortPile(void) } else { - // TODO: 13+ votes + angle_t a = ANGLE_90 + (ANGLE_MAX / numVotes) * (votesLeft - 1); + pile->destX += FixedMul(LOTS_OF_VOTES_X, FINECOSINE(a >> ANGLETOFINESHIFT)); + pile->destY += FixedMul(LOTS_OF_VOTES_Y, -FINESINE(a >> ANGLETOFINESHIFT)); } votesLeft--; @@ -448,20 +459,51 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt if (playerID >= 0) { + const INT32 whiteSq = 16 * dupx; + if (playerID < MAXPLAYERS) { UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE); patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK]; + V_DrawFixedPatch( - x + width - (playerPatch->width * FRACUNIT) + FRACUNIT - 1, - y + height - (playerPatch->height * FRACUNIT) + FRACUNIT, - FRACUNIT, flags, + (fx + fw - whiteSq + dupx) * FRACUNIT, + (fy + fh - whiteSq + dupy) * FRACUNIT, + FRACUNIT, flags|V_NOSCALESTART, playerPatch, playerMap ); } else { - ; // angry level goes here + const fixed_t iconHeight = (14 << FRACBITS); + const fixed_t iconWidth = (iconHeight * 320) / 200; + + V_DrawFill( + fx + fw - whiteSq + dupx, + fy + fh - whiteSq + dupy, + whiteSq, + whiteSq, + 0|flags|V_NOSCALESTART + ); + + V_SetClipRect( + fx + fw - whiteSq + (2 * dupx), + fy + fh - whiteSq + (2 * dupy), + whiteSq - (2 * dupx), + whiteSq - (2 * dupy), + flags|V_NOSCALESTART + ); + + K_DrawMapThumbnail( + ((fx + fw - whiteSq + (2 * dupx)) * FRACUNIT) - (iconWidth - iconHeight), + (fy + fh - whiteSq + (2 * dupy)) * FRACUNIT, + iconWidth, + flags | V_NOSCALESTART | ((encore == true) ? V_FLIP : 0), + g_voteLevels[v][0], + NULL + ); + + V_ClearClipRect(); } } } @@ -707,7 +749,7 @@ static void Y_DrawVotePile(void) { INT32 i; - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { y_vote_pile *const pile = &vote.roulette.pile[i]; y_vote_catcher *const catcher = &pile->catcher; @@ -717,27 +759,21 @@ static void Y_DrawVotePile(void) continue; } -#ifndef TEST_VOTES if (g_votes[i] == VOTE_NOT_PICKED) { continue; } -#endif Y_DrawVoteThumbnail( pile->x, pile->y, PILE_WIDTH, 0, -#ifdef TEST_VOTES - 0, -#else g_votes[i], -#endif (i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED), i ); } - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { Y_DrawCatcher(&vote.roulette.pile[i].catcher); } @@ -949,7 +985,7 @@ static void Y_TickPlayerCatcher(const UINT8 localPlayer) { if (catcher->x == catcher->destX && catcher->y == catcher->destY) { - D_ModifyClientVote(localPlayer, vote.players[localPlayer].selection); + D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection); catcher->action = CATCHER_NA; S_StopSoundByNum(sfx_kc37); } @@ -1023,13 +1059,11 @@ static void Y_TickPlayerPile(const UINT8 playerId) fixed_t movedX = 0; fixed_t movedY = 0; -#ifndef TEST_VOTES if (g_votes[playerId] == VOTE_NOT_PICKED) { catcher->action = CATCHER_NA; return; } -#endif movedX = (pile->destX - pile->x) / 2; movedY = (pile->destY - pile->y) / 2; @@ -1058,10 +1092,10 @@ static void Y_TickVoteRoulette(void) if (vote.endtic == -1) { - UINT8 tempvotes[MAXPLAYERS]; + UINT8 tempvotes[VOTE_TOTAL]; UINT8 numvotes = 0; - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { if (g_votes[i] == VOTE_NOT_PICKED) { @@ -1086,7 +1120,7 @@ static void Y_TickVoteRoulette(void) else { vote.roulette.offset++; - vote.roulette.tics = min(6, 9 * vote.roulette.offset / 40); + vote.roulette.tics = min(5, 7 * vote.roulette.offset / 40); S_StartSound(NULL, sfx_kc39); } @@ -1095,7 +1129,7 @@ static void Y_TickVoteRoulette(void) vote.roulette.anim = tempvotes[((g_pickedVote + vote.roulette.offset) % numvotes)]; } - if (vote.roulette.offset > 30) + if (vote.roulette.offset > 20) { if (vote.roulette.endOffset == 0) { @@ -1107,7 +1141,7 @@ static void Y_TickVoteRoulette(void) { vote.roulette.endOffset = vote.roulette.offset + i; - if (M_RandomChance(FRACUNIT/32)) // Let it cheat occasionally~ + if (M_RandomChance(FRACUNIT/4)) // Let it cheat occasionally~ { vote.roulette.endOffset++; } @@ -1153,6 +1187,74 @@ static boolean Y_PlayerCanSelect(const UINT8 localId) return Y_PlayerIDCanVote(p); } +static void Y_TryMapAngerVote(void) +{ + SINT8 angryMaps[VOTE_NUM_LEVELS] = { -1 }; + size_t angryMapsCount = 0; + + boolean mapVoted[VOTE_NUM_LEVELS] = { false }; + INT32 pick = 0; + + INT32 numPlayers = 0; + INT32 i = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (Y_PlayerIDCanVote(i) == false) + { + continue; + } + + numPlayers++; + + if (g_votes[i] != VOTE_NOT_PICKED) + { + mapVoted[ g_votes[i] ] = true; + } + } + + if (numPlayers < 3) + { + // Don't handle map anger if there's not enough players. + return; + } + + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + const INT16 mapID = g_voteLevels[i][0]; + + if (mapVoted[i] == true) + { + // Someone voted for us, no need to be angry anymore :) + mapheaderinfo[ mapID ]->anger = 0; + } + else + { + // Increment map anger for maps that weren't picked by a single soul. + mapheaderinfo[ mapID ]->anger++; + + if (mapheaderinfo[ mapID ]->anger > MAP_ANGER_MAX) + { + // If they are angry enough, then it can vote for itself! + angryMaps[ angryMapsCount ] = i; + angryMapsCount++; + } + } + } + + if (angryMapsCount == 0) + { + return; + } + + // Set the special vote to a random angry map. + pick = M_RandomKey(angryMapsCount); + D_ModifyClientVote(UINT8_MAX, angryMaps[pick]); + + // Make it not angry anymore. + mapheaderinfo[ g_voteLevels[ angryMaps[pick] ][0] ]->anger = 0; +} + static void Y_TickVoteSelection(void) { boolean everyone_voted = true;/* the default condition */ @@ -1233,10 +1335,18 @@ static void Y_TickVoteSelection(void) continue; } + if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED) + { + if (( M_RandomFixed() % 100 ) == 0) + { + // bots vote randomly + D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS)); + } + } + if (g_votes[i] == VOTE_NOT_PICKED) { everyone_voted = false; - break; } } @@ -1270,6 +1380,7 @@ static void Y_TickVoteSelection(void) if (server) { + Y_TryMapAngerVote(); D_PickVote(); } } @@ -1306,11 +1417,15 @@ void Y_VoteTicker(void) return; } - for (i = 0; i < MAXPLAYERS; i++) // Correct votes as early as possible, before they're processed by the game at all + // Correct invalid votes as early as possible, + // before they're processed by the rest of the ticker + for (i = 0; i < MAXPLAYERS; i++) { if (Y_PlayerIDCanVote(i) == false) { - g_votes[i] = VOTE_NOT_PICKED; // Spectators are the lower class, and have effectively no voice in the government. Democracy sucks. + // Spectators are the lower class, and have + // effectively no voice in the government. Democracy sucks. + g_votes[i] = VOTE_NOT_PICKED; } } @@ -1341,7 +1456,7 @@ void Y_VoteTicker(void) Y_SortPile(); - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { Y_TickPlayerPile(i); } @@ -1449,7 +1564,7 @@ void Y_StartVote(void) catcher->player = -1; } - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { y_vote_pile *const pile = &vote.roulette.pile[i]; y_vote_catcher *const catcher = &pile->catcher; @@ -1556,7 +1671,7 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level) vote.roulette.syncTime = 0; - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { if (g_votes[i] == VOTE_NOT_PICKED) { diff --git a/src/p_saveg.c b/src/p_saveg.c index 05c55e0ea..081eceffe 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4997,14 +4997,16 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEINT16(save->p, lastmap); WRITEUINT16(save->p, bossdisabled); - for (i = 0; i < 4; i++) + for (i = 0; i < VOTE_NUM_LEVELS; i++) { WRITEINT16(save->p, g_voteLevels[i][0]); WRITEINT16(save->p, g_voteLevels[i][1]); } - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) + { WRITESINT8(save->p, g_votes[i]); + } WRITESINT8(save->p, g_pickedVote); @@ -5169,13 +5171,13 @@ static inline boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) lastmap = READINT16(save->p); bossdisabled = READUINT16(save->p); - for (i = 0; i < 4; i++) + for (i = 0; i < VOTE_NUM_LEVELS; i++) { g_voteLevels[i][0] = READINT16(save->p); g_voteLevels[i][1] = READINT16(save->p); } - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < VOTE_TOTAL; i++) { g_votes[i] = READSINT8(save->p); } diff --git a/src/p_setup.c b/src/p_setup.c index 6de8c86b1..232be6670 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -429,6 +429,9 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) Z_Free(mapheaderinfo[num]->mainrecord); mapheaderinfo[num]->mainrecord = NULL; + mapheaderinfo[num]->justPlayed = 0; + mapheaderinfo[num]->anger = 0; + mapheaderinfo[num]->customopts = NULL; mapheaderinfo[num]->numCustomOptions = 0; }