Map anger

- Maps build anger every time a map isn't selected by anyone.
- If a map is ignored for 4 votes in a row, then on the 5th vote it shows up it will be angry enough to vote for itself when everyone else finishes voting.
- Once it gives its funny vote, or it gets played, it will calm down again.
- 13P+ vote icons are implemented; it's just a basic circle though cuz lazy.
- Made the roulette finish even faster.
- Bots can vote again but now behind a debug cvar.
This commit is contained in:
Sally Coolatta 2023-04-10 02:08:48 -04:00
parent 95540888ce
commit 9c4ace6fbc
8 changed files with 255 additions and 64 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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;
// ===========================

View file

@ -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++;
}

View file

@ -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);

View file

@ -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)
{

View file

@ -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);
}

View file

@ -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;
}