From 9b63fb40786020303a890a0bcf1fa8fa614746c1 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 2 Sep 2024 04:20:04 -0400 Subject: [PATCH 01/64] Fix map anger race condition Was making angry maps never actually get picked. Dang. --- src/d_netcmd.c | 29 +++++++++++++++++++---------- src/d_netcmd.h | 2 +- src/k_vote.c | 20 ++++++++++++-------- src/k_vote.h | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e926cf520..c524d325e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2376,13 +2376,12 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted) SendNetXCmdForPlayer(sendPlayer, XD_MODIFYVOTE, buf, p - buf); } -void D_PickVote(void) +void D_PickVote(SINT8 angry_map) { - char buf[2]; + char buf[3]; char* p = buf; SINT8 temppicks[VOTE_TOTAL]; SINT8 templevels[VOTE_TOTAL]; - SINT8 votecompare = VOTE_NOT_PICKED; UINT8 numvotes = 0, key = 0; INT32 i; @@ -2393,16 +2392,23 @@ void D_PickVote(void) continue; } + if (i == VOTE_SPECIAL && angry_map != VOTE_NOT_PICKED) + { + // Anger map is going to change because of + // the vote ending. We need to account for this + // here because a net command would not be ready + // in time for this code. + temppicks[numvotes] = i; + templevels[numvotes] = angry_map; + numvotes++; + continue; + } + if (g_votes[i] != VOTE_NOT_PICKED) { temppicks[numvotes] = i; templevels[numvotes] = g_votes[i]; numvotes++; - - if (votecompare == VOTE_NOT_PICKED) - { - votecompare = g_votes[i]; - } } } @@ -2411,14 +2417,16 @@ void D_PickVote(void) key = M_RandomKey(numvotes); WRITESINT8(p, temppicks[key]); WRITESINT8(p, templevels[key]); + WRITESINT8(p, angry_map); } else { WRITESINT8(p, VOTE_NOT_PICKED); WRITESINT8(p, 0); + WRITESINT8(p, VOTE_NOT_PICKED); } - SendNetXCmd(XD_PICKVOTE, &buf, 2); + SendNetXCmd(XD_PICKVOTE, &buf, 3); } static char * @@ -5859,6 +5867,7 @@ static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum) { SINT8 pick = READSINT8(*cp); SINT8 level = READSINT8(*cp); + SINT8 anger = READSINT8(*cp); if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -5868,7 +5877,7 @@ static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum) return; } - Y_SetupVoteFinish(pick, level); + Y_SetupVoteFinish(pick, level, anger); } static void Got_ScheduleTaskcmd(const UINT8 **cp, INT32 playernum) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5a1a79255..f6d863aac 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -249,7 +249,7 @@ void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function any void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); void D_SetupVote(INT16 newgametype); void D_ModifyClientVote(UINT8 player, SINT8 voted); -void D_PickVote(void); +void D_PickVote(SINT8 angry_map); void ObjectPlace_OnChange(void); void P_SetPlayerSpectator(INT32 playernum); boolean IsPlayerAdmin(INT32 playernum); diff --git a/src/k_vote.c b/src/k_vote.c index ca6bb4c79..9f3ba2c32 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -1352,7 +1352,7 @@ static void Y_TickVoteRoulette(void) } } -static void Y_TryMapAngerVote(void) +static SINT8 Y_TryMapAngerVote(void) { SINT8 angryMaps[VOTE_NUM_LEVELS] = { -1 }; size_t angryMapsCount = 0; @@ -1381,7 +1381,7 @@ static void Y_TryMapAngerVote(void) if (numPlayers < 3) { // Don't handle map anger if there's not enough players. - return; + return VOTE_NOT_PICKED; } for (i = 0; i < VOTE_NUM_LEVELS; i++) @@ -1409,12 +1409,12 @@ static void Y_TryMapAngerVote(void) if (angryMapsCount == 0) { - return; + return VOTE_NOT_PICKED; } // Set the special vote to a random angry map. pick = M_RandomKey(angryMapsCount); - D_ModifyClientVote(UINT8_MAX, angryMaps[pick]); + return angryMaps[pick]; } static void Y_TickVoteSelection(void) @@ -1542,8 +1542,7 @@ static void Y_TickVoteSelection(void) if (server) { - Y_TryMapAngerVote(); - D_PickVote(); + D_PickVote( Y_TryMapAngerVote() ); } } } @@ -1593,7 +1592,7 @@ void Y_VoteTicker(void) if (server && g_pickedVote != VOTE_NOT_PICKED && g_votes[g_pickedVote] == VOTE_NOT_PICKED) // Uh oh! The person who got picked left! Recalculate, quick! { - D_PickVote(); + D_PickVote( VOTE_NOT_PICKED ); } if (vote.tic == 0) @@ -1849,13 +1848,18 @@ enum VOTE_END_NORMAL, }; -void Y_SetupVoteFinish(SINT8 pick, SINT8 level) +void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger) { if (vote.loaded == false) { return; } + if (anger != VOTE_NOT_PICKED) + { + Y_SetPlayersVote(VOTE_SPECIAL, anger); + } + if (pick == VOTE_NOT_PICKED || level == VOTE_NOT_PICKED) // No other votes? We gotta get out of here, then! { Y_EndVote(); diff --git a/src/k_vote.h b/src/k_vote.h index 652eb865a..f78310a92 100644 --- a/src/k_vote.h +++ b/src/k_vote.h @@ -31,7 +31,7 @@ void Y_VoteDrawer(void); void Y_VoteTicker(void); void Y_StartVote(void); void Y_EndVote(void); -void Y_SetupVoteFinish(SINT8 pick, SINT8 level); +void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger); #ifdef __cplusplus } // extern "C" From d2b2178143f68c5d891ecd7f28416b7cac97e4e4 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 20:56:42 -0700 Subject: [PATCH 02/64] WIP: Duel ruleset --- src/d_player.h | 2 ++ src/k_hud.cpp | 9 +++++++++ src/k_kart.c | 18 ++++++++++++++++++ src/lua_playerlib.c | 4 ++++ src/p_saveg.c | 2 ++ src/p_spec.c | 4 +++- 6 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index 7f3a55587..6d6a7b8bb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -926,6 +926,8 @@ struct player_t INT32 cheatchecknum; // The number of the last cheatcheck you hit INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp + INT16 duelscore; + UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue UINT8 checkskip; // Skipping checkpoints? Oh no no no diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 1a6bde3b4..1c8ece97a 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2997,6 +2997,15 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. + if (inDuel) + { + UINT8 flashflag = (leveltime % 2 && abs(stplyr->duelscore >= 2)) ? V_TRANSLUCENT : 0; + if (stplyr->duelscore >= 0) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_BLUEMAP|flashflag, va("+%d", stplyr->duelscore)); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_REDMAP|flashflag, va("%d", stplyr->duelscore)); + } + if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 9139deab6..f3b2a672c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4144,6 +4144,24 @@ void K_CheckpointCrossAward(player_t *player) { player->exp += K_GetExpAdjustment(player); K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); + + // Update Duel scoring. + if (inDuel && player->position == 1) + { + player->duelscore += 1; + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && &players[i] != player) + players[i].duelscore -= 1; + } + + if (player->duelscore == 3) + { + P_DoPlayerExit(player, 0); + P_DoAllPlayersExit(PF_NOCONTEST, 0); + } + } + } boolean K_Overdrive(player_t *player) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index a7937fbfc..59796ac60 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -675,6 +675,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->checkskip); else if (fastcmp(field,"cheatchecknum")) lua_pushinteger(L, plr->cheatchecknum); + else if (fastcmp(field,"duelscore")) + lua_pushinteger(L, plr->duelscore); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1246,6 +1248,8 @@ static int player_set(lua_State *L) plr->checkskip = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"cheatchecknum")) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"duelscore")) + plr->duelscore = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_saveg.c b/src/p_saveg.c index 9a642cc25..4a697e3eb 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -284,6 +284,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].lapPoints); WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); + WRITEINT16(save->p, players[i].duelscore); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -955,6 +956,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].lapPoints = READUINT32(save->p); players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); + players[i].duelscore = READINT16(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index 220954e82..bd1bcd579 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1991,8 +1991,10 @@ static void K_HandleLapIncrement(player_t *player) player->latestlap = player->laps; } + boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + // finished race exit setup - if (player->laps > numlaps) + if (player->laps > numlaps && !specialduelexit) { pflags_t applyflags = 0; if (specialstageinfo.valid == true) From 4f1a49c0fb76ba7219ba0e6a945577651e8c16d1 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 21:52:46 -0700 Subject: [PATCH 03/64] WIP: Duel infinite laps in circuit --- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_hud.cpp | 10 +++++++--- src/k_kart.c | 3 ++- src/p_saveg.c | 2 ++ src/p_spec.c | 5 +++++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index b880a4c22..628ff22be 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -868,6 +868,7 @@ extern SINT8 spbplace; extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; +extern UINT8 extralaps; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) extern boolean legitimateexit; diff --git a/src/g_game.c b/src/g_game.c index 0158f6e6b..56991ea59 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -319,6 +319,7 @@ SINT8 spbplace; // SPB exists, give the person behind better items boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 +UINT8 extralaps; // Duel extensions! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures. diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 1c8ece97a..8918e3233 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2999,11 +2999,15 @@ static void K_drawKartLaps(void) if (inDuel) { - UINT8 flashflag = (leveltime % 2 && abs(stplyr->duelscore >= 2)) ? V_TRANSLUCENT : 0; + UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; + if (leveltime % 2) + if (abs(stplyr->duelscore) >= 2) + flashflag = V_YELLOWMAP; + if (stplyr->duelscore >= 0) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_BLUEMAP|flashflag, va("+%d", stplyr->duelscore)); + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_REDMAP|flashflag, va("%d", stplyr->duelscore)); + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); } if (numlaps != 1) diff --git a/src/k_kart.c b/src/k_kart.c index f3b2a672c..1fa198003 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -145,6 +145,7 @@ void K_TimerReset(void) numbulbs = 1; inDuel = rainbowstartavailable = false; linecrossed = 0; + extralaps = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; } @@ -10684,7 +10685,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; + const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection + extralaps; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 4a697e3eb..1b697bd66 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6679,6 +6679,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITESINT8(save->p, spbplace); WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); + WRITEUINT8(save->p, extralaps); WRITEUINT32(save->p, introtime); WRITEUINT32(save->p, starttime); @@ -6884,6 +6885,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) spbplace = READSINT8(save->p); rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); + extralaps = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); starttime = READUINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index bd1bcd579..875d08072 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1993,6 +1993,11 @@ static void K_HandleLapIncrement(player_t *player) boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + if (specialduelexit) + { + extralaps += 1; + } + // finished race exit setup if (player->laps > numlaps && !specialduelexit) { From 075b09e5374a497914c3065c0f3a927209a3fcf0 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 22:05:33 -0700 Subject: [PATCH 04/64] Duel less rewarding First Blood --- src/p_spec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_spec.c b/src/p_spec.c index 875d08072..de2547392 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2055,7 +2055,7 @@ static void K_HandleLapIncrement(player_t *player) K_SpawnDriftBoostExplosion(player, 4); K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, 50, player->mo); + K_SpawnAmps(player, (inDuel) ? 20 : 50, player->mo); rainbowstartavailable = false; } From 1d83dddab8f561b8d6275d9a1e77df6ef0b022c9 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 3 Sep 2024 01:08:59 -0700 Subject: [PATCH 05/64] WIP: Timer Duel --- src/d_player.h | 1 + src/g_game.c | 12 +++++++++++ src/k_hud.cpp | 50 +++++++++++++++++++++++++++++---------------- src/k_kart.c | 47 +++++++++++++++++++++++++++++++++++------- src/k_kart.h | 6 ++++++ src/lua_playerlib.c | 4 ++++ src/p_inter.c | 2 ++ src/p_saveg.c | 2 ++ 8 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 6d6a7b8bb..d3facfed7 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -927,6 +927,7 @@ struct player_t INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp INT16 duelscore; + tic_t dueltimer; UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue diff --git a/src/g_game.c b/src/g_game.c index 56991ea59..6b2ff35fe 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2179,6 +2179,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 lastsafecheatcheck; UINT16 bigwaypointgap; + INT16 duelscore; + tic_t dueltimer; + roundconditions_t roundconditions; boolean saveroundconditions; @@ -2334,6 +2337,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bigwaypointgap = 0; tallyactive = false; + + dueltimer = 15*TICRATE; + duelscore = 0; } else { @@ -2384,6 +2390,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) lastsafecheatcheck = players[player].lastsafecheatcheck; bigwaypointgap = players[player].bigwaypointgap; + duelscore = players[player].duelscore; + dueltimer = players[player].dueltimer; + tallyactive = players[player].tally.active; if (tallyactive) { @@ -2521,6 +2530,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->griefValue = griefValue; p->griefStrikes = griefStrikes; + p->dueltimer = dueltimer; + p->duelscore = duelscore; + memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 8918e3233..b8272f3b0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2628,11 +2628,38 @@ void PositionFacesInfo::draw_1p() ; else if (gametyperules & GTR_CIRCUIT) { - INT32 pos = players[rankplayer[i]].position; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); + if (inDuel) + { + INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; + + colormap = NULL; + + if (K_PlayerLosingDuel(&players[rankplayer[i]]) || players[rankplayer[i]].dueltimer == 0) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, (players[rankplayer[i]].dueltimer == 0) ? SKINCOLOR_CRIMSON : SKINCOLOR_TANGERINE, GTC_CACHE); + flags |= V_STRINGDANCE; + } + + V_DrawStringScaled( + (FACE_X - 5) * FRACUNIT, + (Y + 10) * FRACUNIT, + FRACUNIT, + FRACUNIT, + FRACUNIT, + flags, + colormap, + PINGF_FONT, + va("%02d.%02d", players[rankplayer[i]].dueltimer/TICRATE, (players[rankplayer[i]].dueltimer%TICRATE)*100/TICRATE) + ); + } + else + { + INT32 pos = players[rankplayer[i]].position; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); + } } else if (gametyperules & GTR_POINTLIMIT) { @@ -2997,19 +3024,6 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. - if (inDuel) - { - UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; - if (leveltime % 2) - if (abs(stplyr->duelscore) >= 2) - flashflag = V_YELLOWMAP; - - if (stplyr->duelscore >= 0) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); - else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); - } - if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 1fa198003..6d3fa23c6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4147,19 +4147,23 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel && player->position == 1) + if (inDuel) { player->duelscore += 1; + + if (player->position == 1) + { + tic_t effectiveleveltime = min(leveltime, DUELTIMER_NOBONUS); + player->dueltimer += Easing_Linear(effectiveleveltime*FRACUNIT/DUELTIMER_NOBONUS, DUELTIMER_BONUS, 0); + player->dueltimer = min(player->dueltimer, DUELTIMER_MAX); + } + for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) + { players[i].duelscore -= 1; - } - - if (player->duelscore == 3) - { - P_DoPlayerExit(player, 0); - P_DoAllPlayersExit(PF_NOCONTEST, 0); + } } } @@ -7616,6 +7620,19 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds) return i; } +boolean K_PlayerLosingDuel(player_t *player) +{ + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && &players[i] != player) + { + if (players[i].duelscore > player->duelscore) + return true; + } + } + return false; +} + mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); @@ -9385,6 +9402,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } + if (inDuel && K_PlayerLosingDuel(player)) + { + if (player->dueltimer) + { + player->dueltimer--; + if (!(player->dueltimer % 4) && P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_s3k55); + + if (player->dueltimer == 0) + { + P_DoTimeOver(player); + P_DoAllPlayersExit(0, false); + } + } + } + if (!player->invincibilitytimer) player->invincibilityextensions = 0; diff --git a/src/k_kart.h b/src/k_kart.h index bf73ffcf2..38e80016e 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -88,6 +88,11 @@ Make sure this matches the actual number of states #define AUTORESPAWN_TIME (10*TICRATE) #define AUTORESPAWN_THRESHOLD (7*TICRATE) +#define DUELTIMER_START (10*TICRATE) +#define DUELTIMER_BONUS (2*TICRATE) +#define DUELTIMER_MAX (3*DUELTIMER_START/2) +#define DUELTIMER_NOBONUS (60*TICRATE) + angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); boolean K_IsDuelItem(mobjtype_t type); @@ -203,6 +208,7 @@ void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); +boolean K_PlayerLosingDuel(player_t *player); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); mobj_t *K_FlingPaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 59796ac60..c0bf2df4b 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -677,6 +677,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->cheatchecknum); else if (fastcmp(field,"duelscore")) lua_pushinteger(L, plr->duelscore); + else if (fastcmp(field,"dueltimer")) + lua_pushinteger(L, plr->dueltimer); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1250,6 +1252,8 @@ static int player_set(lua_State *L) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"duelscore")) plr->duelscore = (INT16)luaL_checkinteger(L, 3); + else if (fastcmp(field,"dueltimer")) + plr->dueltimer = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_inter.c b/src/p_inter.c index 8464cf0b9..7b5c69b57 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1544,6 +1544,8 @@ boolean P_CheckRacers(void) const boolean griefed = (spectateGriefed > 0); boolean eliminateLast = (!K_CanChangeRules(true) || (cv_karteliminatelast.value != 0)); + if (inDuel) + eliminateLast = false; boolean allHumansDone = true; //boolean allBotsDone = true; diff --git a/src/p_saveg.c b/src/p_saveg.c index 1b697bd66..711d6632d 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -285,6 +285,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); WRITEINT16(save->p, players[i].duelscore); + WRITEUINT32(save->p, players[i].dueltimer); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -957,6 +958,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); players[i].duelscore = READINT16(save->p); + players[i].dueltimer = READUINT32(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); From 141a37c48a6c2196f70b9774f9ff7f552c44e63d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 3 Sep 2024 07:34:11 -0400 Subject: [PATCH 06/64] Duel voting Instead of voting for the level you want and the pick is decided by RNG ... now you take turns picking the maps you *don't* want, and the last one remaining gets picked. The previous loser gets to strike two stages and goes first, while the previous winner only gets to strike one. Very incomplete visuals, very janky. --- src/d_netcmd.c | 39 ++-- src/doomstat.h | 9 +- src/g_game.c | 3 +- src/k_vote.c | 575 +++++++++++++++++++++++++++++++++++++++++++++--- src/k_vote.h | 4 +- src/p_saveg.c | 2 + src/y_inter.cpp | 45 ++-- src/y_inter.h | 7 +- 8 files changed, 616 insertions(+), 68 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c524d325e..d743ed3a2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2339,11 +2339,11 @@ void D_SetupVote(INT16 newgametype) void D_ModifyClientVote(UINT8 player, SINT8 voted) { - char buf[2]; + char buf[3]; char *p = buf; - UINT8 sendPlayer = consoleplayer; + UINT8 sendPlayer = 0; - if (player == UINT8_MAX) + if (player >= MAXPLAYERS) { // Special game vote (map anger, duel) if (!server) @@ -2352,16 +2352,16 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted) } } - if (player == UINT8_MAX) - { - // special vote - WRITEUINT8(p, UINT8_MAX); - } - else - { - INT32 i = 0; - WRITEUINT8(p, player); + // Context value -- if context has changed, then discard vote update. + // This is to prevent votes being registered from different vote types. + // Currently used for Duel vs Normal votes. + WRITEUINT8(p, Y_VoteContext()); + WRITEUINT8(p, player); + + if (player <= MAXPLAYERS) + { + INT32 i; for (i = 0; i <= splitscreen; i++) { if (g_localplayers[i] == player) @@ -5819,17 +5819,24 @@ static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum) static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum) { + UINT8 context = READUINT8(*cp); UINT8 targetID = READUINT8(*cp); SINT8 vote = READSINT8(*cp); - if (targetID == UINT8_MAX) + if (context != Y_VoteContext()) { - if (playernum != serverplayer) // server-only special vote + // Silently discard. Server changed the + // vote type as we were sending our vote. + return; + } + + if (targetID >= MAXPLAYERS) + { + // only the server is allowed to send these + if (playernum != serverplayer) { goto fail; } - - targetID = VOTE_SPECIAL; } else if (playeringame[targetID] == true && players[targetID].bot == true) { diff --git a/src/doomstat.h b/src/doomstat.h index b880a4c22..ce0d1c976 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -873,11 +873,18 @@ extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines extern boolean legitimateexit; extern boolean comebackshowninfo; +#define VOTE_NUM_LEVELS (4) +#define VOTE_NOT_PICKED (-1) #define VOTE_SPECIAL (MAXPLAYERS) #define VOTE_TOTAL (MAXPLAYERS+1) -extern UINT16 g_voteLevels[4][2]; + +#define VOTE_TIMEOUT_LOSER (MAXPLAYERS+1) // not a real vote ID +#define VOTE_TIMEOUT_WINNER (MAXPLAYERS+2) // ditto + +extern UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; extern SINT8 g_votes[VOTE_TOTAL]; extern SINT8 g_pickedVote; +extern boolean g_votes_striked[VOTE_NUM_LEVELS]; // =========================== // Internal parameters, fixed. diff --git a/src/g_game.c b/src/g_game.c index 0158f6e6b..371498d23 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -305,9 +305,10 @@ boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? // Voting system -UINT16 g_voteLevels[4][2]; // Levels that were rolled by the host +UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; // Levels that were rolled by the host SINT8 g_votes[VOTE_TOTAL]; // Each player's vote SINT8 g_pickedVote; // What vote the host rolls +boolean g_votes_striked[VOTE_NUM_LEVELS]; // Which levels were striked from votes? // Server-sided, synched variables tic_t wantedcalcdelay; // Time before it recalculates WANTED diff --git a/src/k_vote.c b/src/k_vote.c index 9f3ba2c32..39d0fc36f 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -109,6 +109,7 @@ // 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 STRIKE_DELAY_TIME (TICRATE/3) #define MAP_ANGER_MAX (2) @@ -179,12 +180,21 @@ typedef struct { INT32 timer; INT32 tic, endtic; - INT32 selectFinalize, pickFinalize; + INT32 selectFinalize, pickFinalize, strikeFinalize; boolean notYetPicked; boolean loaded; SINT8 deferredLevel; y_vote_player players[MAXSPLITSCREENPLAYERS]; y_vote_roulette roulette; + + // If both of these players are valid, + // and they're the only players in the server, + // then we want stage striking! + player_t *strike_loser; + player_t *strike_winner; + boolean strike_turn; + boolean strike_time_out; + boolean stage_striking; } y_vote_data; // Voting level drawing @@ -209,6 +219,8 @@ typedef struct patch_t *ruby_icon; fixed_t ruby_height; + patch_t *strike_icon; + patch_t *bg_planet[PLANET_FRAMES]; patch_t *bg_checker; patch_t *bg_levelText; @@ -230,13 +242,28 @@ typedef struct static y_vote_data vote = {0}; static y_vote_draw vote_draw = {0}; +static boolean Y_VoteIDIsSpecial(const UINT8 playerId) +{ + switch (playerId) + { + case VOTE_SPECIAL: + case VOTE_TIMEOUT_LOSER: + case VOTE_TIMEOUT_WINNER: + { + // Special vote spot, always allow + return true; + } + default: + { + return false; + } + } +} + boolean Y_PlayerIDCanVote(const UINT8 playerId) { - player_t *player = NULL; - - if (playerId == VOTE_SPECIAL) + if (Y_VoteIDIsSpecial(playerId) == true) { - // Special vote spot, always allow return true; } @@ -245,7 +272,7 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId) return false; } - player = &players[playerId]; + const player_t *player = &players[playerId]; if (player->spectator == true) { return false; @@ -260,8 +287,48 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId) return true; } +static boolean Y_IsPlayersTurn(const UINT8 playerId) +{ + if (Y_VoteIDIsSpecial(playerId) == true) + { + return true; + } + + if (vote.stage_striking == false) + { + // Not stage striking -- we can always vote. + return true; + } + + // Is it our turn to strike a stage? + const player_t *player = &players[playerId]; + if (vote.strike_turn == true) + { + return (player == vote.strike_winner); + } + else + { + return (player == vote.strike_loser); + } +} + +static boolean Y_PlayerIDCanVoteRightNow(const UINT8 playerId) +{ + if (Y_IsPlayersTurn(playerId) == false) + { + return false; + } + + return Y_PlayerIDCanVote(playerId); +} + static boolean Y_PlayerCanSelect(const UINT8 localId) { + if (localId > splitscreen) + { + return false; + } + const UINT8 p = g_localplayers[localId]; if (g_pickedVote != VOTE_NOT_PICKED) @@ -280,7 +347,7 @@ static boolean Y_PlayerCanSelect(const UINT8 localId) return false; } - return Y_PlayerIDCanVote(p); + return Y_PlayerIDCanVoteRightNow(p); } static void Y_SortPile(void) @@ -381,21 +448,82 @@ static void Y_SortPile(void) } } -void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote) +void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) { - y_vote_pile *const pile = &vote.roulette.pile[playerId]; - y_vote_catcher *const catcher = &pile->catcher; + INT32 i; if (gamestate != GS_VOTING) { return; } + UINT8 playerId = inputPlayerId; + + // Manually overwrite these players for timed out votes. + // Loser/winner is encoded in the vote ID to prevent race + // race conditions with real votes causing problems. + if (inputPlayerId == VOTE_TIMEOUT_LOSER) + { + playerId = (vote.strike_loser - players); + } + else if (inputPlayerId == VOTE_TIMEOUT_WINNER) + { + playerId = (vote.strike_winner - players); + } + if (newVote < 0 || newVote >= VOTE_NUM_LEVELS) { newVote = VOTE_NOT_PICKED; } + if (playerId < MAXPLAYERS) + { + if (Y_PlayerIDCanVoteRightNow(playerId) == false) + { + // Not your turn, dude! + return; + } + + if (vote.stage_striking == true) + { + UINT8 num_striked = 0; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + num_striked++; + } + } + + if (newVote != VOTE_NOT_PICKED + && num_striked < VOTE_NUM_LEVELS-1 + && g_votes_striked[newVote] == false) + { + // Strike a stage, instead of voting. + g_votes_striked[newVote] = true; + + // Change turn. + vote.strike_turn = !vote.strike_turn; + + // Reset variables. + vote.timer = cv_votetime.value * TICRATE; + for (i = 0; i <= splitscreen; i++) + { + vote.players[i].sentTimeOutVote = false; + vote.players[i].delay = NEWTICRATE/7; + } + vote.strike_time_out = false; + + // TODO: striking animation + } + + return; + } + } + + y_vote_pile *const pile = &vote.roulette.pile[playerId]; + y_vote_catcher *const catcher = &pile->catcher; + g_votes[playerId] = newVote; Y_SortPile(); @@ -468,29 +596,48 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy); + boolean striked = false; + if (playerID < 0) + { + striked = g_votes_striked[v]; + } + V_DrawFill( fx - dupx, fy - dupy, fw + (dupx << 1), fh + (dupy << 1), 0|flags|V_NOSCALESTART ); - K_DrawMapThumbnail( - x, y, - width, flags | ((encore == true) ? V_FLIP : 0), - g_voteLevels[v][0], - (dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL) - ); - - if (encore == true) + if (striked == true) { - const fixed_t rubyScale = width / 72; + const fixed_t strikeScale = width / 32; V_DrawFixedPatch( - center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale), - rubyScale, flags, - vote_draw.ruby_icon, + center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2), + strikeScale, flags, + vote_draw.strike_icon, NULL ); } + else + { + K_DrawMapThumbnail( + x, y, + width, flags | ((encore == true) ? V_FLIP : 0), + g_voteLevels[v][0], + (dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL) + ); + + if (encore == true) + { + const fixed_t rubyScale = width / 72; + V_DrawFixedPatch( + center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale), + rubyScale, flags, + vote_draw.ruby_icon, + NULL + ); + } + } if (dim == true) { @@ -506,7 +653,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt { const INT32 whiteSq = 16 * dupx; - if (playerID < MAXPLAYERS) + if (playerID < MAXPLAYERS) // Player vote { UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE); patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK]; @@ -518,7 +665,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt playerPatch, playerMap ); } - else + else if (vote.stage_striking == false) // Angry map { const fixed_t iconHeight = (14 << FRACBITS); const fixed_t iconWidth = (iconHeight * 320) / 200; @@ -813,7 +960,7 @@ static void Y_DrawVoteSelection(fixed_t offset) continue; } - if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVote(p) == false) + if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVoteRightNow(p) == false) { continue; } @@ -1033,7 +1180,7 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) { Y_FinalizeVote(level); - if (netgame && P_IsPartyPlayer(&players[pick])) + if (netgame && pick < MAXPLAYERS && P_IsPartyPlayer(&players[pick])) { S_StartSound(NULL, sfx_yeeeah); // yeeeah! } @@ -1043,8 +1190,30 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) } } +static void Y_PlayerSendStrike(const UINT8 localPlayer) +{ + y_vote_player *const player = &vote.players[localPlayer]; + y_vote_catcher *const catcher = &player->catcher; + + if (g_votes_striked[player->selection] == true) + { + // TODO: "Can't select" animation + return; + } + + D_ModifyClientVote(g_localplayers[localPlayer], player->selection); + catcher->action = CATCHER_NA; + catcher->delay = 5; +} + static void Y_PlayerSendVote(const UINT8 localPlayer) { + if (vote.stage_striking == true) + { + Y_PlayerSendStrike(localPlayer); + return; + } + y_vote_player *const player = &vote.players[localPlayer]; y_vote_catcher *const catcher = &player->catcher; @@ -1171,7 +1340,11 @@ static void Y_TickPlayerCatcher(const UINT8 localPlayer) { if (catcher->x == catcher->destX && catcher->y == catcher->destY) { - D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection); + if (vote.stage_striking == false) + { + D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection); + } + catcher->action = CATCHER_NA; catcher->delay = 5; S_StopSoundByNum(sfx_kc37); @@ -1417,6 +1590,200 @@ static SINT8 Y_TryMapAngerVote(void) return angryMaps[pick]; } +static void Y_ExitStageStrike(void) +{ + INT32 i; + + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + g_votes_striked[i] = false; + } + + vote.stage_striking = false; + vote.timer = cv_votetime.value * TICRATE; + + vote.strike_loser = NULL; + vote.strike_winner = NULL; + vote.strike_turn = false; + vote.strike_time_out = false; +} + +static boolean Y_CheckStageStrikeStatus(void) +{ + INT32 i; + UINT8 num_voters = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (Y_PlayerIDCanVote(i) == false) + { + continue; + } + + num_voters++; + if (num_voters > 2) + { + break; + } + } + + if (num_voters != 2) + { + // Someone joined or left. Stage striking is broken! + return false; + } + + if (vote.strike_loser == NULL || Y_PlayerIDCanVote(vote.strike_loser - players) == false) + { + // Loser is invalidated! + return false; + } + + if (vote.strike_winner == NULL || Y_PlayerIDCanVote(vote.strike_winner - players) == false) + { + // Winner is invalidated! + return false; + } + + // Looks good, we can tick stage striking. + return true; +} + +static void Y_TickVoteStageStrike(void) +{ + INT32 i; + + if (Y_CheckStageStrikeStatus() == false) + { + Y_ExitStageStrike(); + return; + } + + SINT8 the_only_level = VOTE_NOT_PICKED; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + continue; + } + + if (the_only_level != VOTE_NOT_PICKED) + { + // More than 1 valid level. + // Unset and stop iterating. + the_only_level = VOTE_NOT_PICKED; + break; + } + + the_only_level = i; + } + + if (the_only_level != VOTE_NOT_PICKED) + { + vote.timer = 0; + vote.strikeFinalize = STRIKE_DELAY_TIME; + + if (vote.selectFinalize < SELECT_DELAY_TIME) + { + if (vote.selectFinalize == 0) + { + for (i = 0; i <= splitscreen; i++) + { + UINT8 p = g_localplayers[i]; + + if (p != (vote.strike_loser - players) + && p != (vote.strike_winner - players)) + { + continue; + } + + y_vote_player *const player = &vote.players[i]; + y_vote_catcher *const catcher = &player->catcher; + + player->selection = the_only_level; + catcher->action = CATCHER_FG_LOWER; + + catcher->x = catcher->destX = SELECTION_X + (SELECTION_SPACING_W * player->selection); + catcher->y = CATCHER_OFFSCREEN; + catcher->destY = SELECTION_Y - SELECTION_HOP; + catcher->spr = 0; + catcher->level = VOTE_NOT_PICKED; + + S_StartSound(NULL, sfx_kc37); + } + } + + vote.selectFinalize++; + } + + if (vote.selectFinalize >= SELECT_DELAY_TIME) + { + if (vote.pickFinalize < PICK_DELAY_TIME) + { + vote.pickFinalize++; + } + else if (vote.endtic == -1) + { + vote.notYetPicked = false; /* don't pick vote twice */ + + if (server) + { + D_PickVote( the_only_level ); + } + } + } + } + else + { + if (vote.timer == 0) + { + if (vote.strikeFinalize < STRIKE_DELAY_TIME) + { + vote.strikeFinalize++; + } + } + else + { + vote.strikeFinalize = 0; + } + + if (vote.strikeFinalize >= STRIKE_DELAY_TIME) + { + // We didn't get their timeout strike net command. + // Maybe they hacked their exe, or connection was + // interrupted, or some other issue. + + // Let's just strike a random stage for them. + if (server && vote.strike_time_out == false) + { + INT32 rng = M_RandomKey(VOTE_NUM_LEVELS); + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == false) + { + break; + } + + rng++; + if (rng >= VOTE_NUM_LEVELS) + { + rng = 0; + } + } + + D_ModifyClientVote((vote.strike_turn == true) ? VOTE_TIMEOUT_WINNER : VOTE_TIMEOUT_LOSER, rng); + } + + vote.strike_time_out = true; + } + else if (vote.timer > 0) + { + vote.timer--; + vote.selectFinalize = 0; + vote.pickFinalize = 0; + } + } +} + static void Y_TickVoteSelection(void) { boolean everyone_voted = true;/* the default condition */ @@ -1446,6 +1813,22 @@ static void Y_TickVoteSelection(void) // Time's up, send our vote ASAP. if (vote.players[i].sentTimeOutVote == false) { + // Move off of striked stages for the timeout vote. + INT32 j; + for (j = 0; j < VOTE_NUM_LEVELS; j++) + { + if (g_votes_striked[vote.players[i].selection] == false) + { + break; + } + + vote.players[i].selection++; + if (vote.players[i].selection >= VOTE_NUM_LEVELS) + { + vote.players[i].selection = 0; + } + } + Y_PlayerSendVote(i); vote.players[i].sentTimeOutVote = true; vote.players[i].delay = NEWTICRATE/7; @@ -1497,9 +1880,9 @@ static void Y_TickVoteSelection(void) continue; } - if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED) + if (players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) { - if (( M_RandomFixed() % 100 ) == 0) + if (server && ( M_RandomFixed() % 100 ) == 0) { // bots vote randomly D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS)); @@ -1512,6 +1895,13 @@ static void Y_TickVoteSelection(void) } } + if (vote.stage_striking == true) + { + // Use the same selection logic, otherwise use separate ending logic. + Y_TickVoteStageStrike(); + return; + } + if (everyone_voted == true) { vote.timer = 0; @@ -1632,6 +2022,7 @@ static void Y_InitVoteDrawing(void) INT32 i = 0, j = 0; vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC); + vote_draw.strike_icon = W_CachePatchName("K_NOBLNS", PU_STATIC); for (i = 0; i < PLANET_FRAMES; i++) { @@ -1719,6 +2110,103 @@ static void Y_InitVoteDrawing(void) vote_draw.selectTransition = FRACUNIT; } +static boolean Y_DetermineStageStrike(void) +{ + player_t *a = NULL; + player_t *b = NULL; + + UINT8 num_voters = 0; + + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (Y_PlayerIDCanVote(i) == false) + { + continue; + } + + num_voters++; + + // Just set the pointers for now, sort them later. + if (a == NULL) + { + a = &players[i]; + } + else if (b == NULL) + { + b = &players[i]; + } + else + { + // Too many players + return false; + } + } + + if (num_voters != 2 || a == NULL || b == NULL) + { + // Requires exactly 2 of them. + return false; + } + + UINT32 score_a = 0; + UINT32 score_b = 0; + + intertype_t scoring_type = Y_GetIntermissionType(); + switch (scoring_type) + { + case int_time: + { + score_a = UINT32_MAX - a->realtime; + score_b = UINT32_MAX - b->realtime; + break; + } + case int_score: + { + score_a = a->score; + score_b = b->realtime; + break; + } + default: + { + // Invalid, exit now. + return false; + } + } + + if (a->pflags & PF_NOCONTEST) + { + score_a = 0; + } + + if (b->pflags & PF_NOCONTEST) + { + score_b = 0; + } + + if (score_a == score_b) + { + // TODO: should be a coin flip, but how + // should the RNG for this be handled? + score_a++; + } + + if (score_a > score_b) + { + vote.strike_loser = b; + vote.strike_winner = a; + } + else + { + vote.strike_loser = a; + vote.strike_winner = b; + } + + vote.stage_striking = true; + return true; +} + void Y_StartVote(void) { INT32 i = 0; @@ -1765,6 +2253,13 @@ void Y_StartVote(void) catcher->player = i; } + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + g_votes_striked[i] = false; + } + + Y_DetermineStageStrike(); + Y_InitVoteDrawing(); vote.loaded = true; @@ -1786,6 +2281,7 @@ static void Y_UnloadVoteData(void) } UNLOAD(vote_draw.ruby_icon); + UNLOAD(vote_draw.strike_icon); for (i = 0; i < PLANET_FRAMES; i++) { @@ -1924,4 +2420,25 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger) vote.timer = -1; vote.selectFinalize = SELECT_DELAY_TIME; vote.pickFinalize = PICK_DELAY_TIME; + vote.strikeFinalize = STRIKE_DELAY_TIME; +} + +// +// Y_VoteContext +// + +enum +{ + VOTE_CTX_NORMAL = 0, + VOTE_CTX_DUEL, +}; + +UINT8 Y_VoteContext(void) +{ + if (vote.stage_striking == true) + { + return VOTE_CTX_DUEL; + } + + return VOTE_CTX_NORMAL; } diff --git a/src/k_vote.h b/src/k_vote.h index f78310a92..0f35ffb37 100644 --- a/src/k_vote.h +++ b/src/k_vote.h @@ -19,9 +19,6 @@ extern "C" { #endif -#define VOTE_NUM_LEVELS (4) -#define VOTE_NOT_PICKED (-1) - #define VOTE_MOD_ENCORE (0x01) boolean Y_PlayerIDCanVote(const UINT8 playerId); @@ -32,6 +29,7 @@ void Y_VoteTicker(void); void Y_StartVote(void); void Y_EndVote(void); void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger); +UINT8 Y_VoteContext(void); #ifdef __cplusplus } // extern "C" diff --git a/src/p_saveg.c b/src/p_saveg.c index 9a642cc25..bef12df23 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6586,6 +6586,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) { WRITEUINT16(save->p, g_voteLevels[i][0]); WRITEUINT16(save->p, g_voteLevels[i][1]); + WRITEUINT8(save->p, g_votes_striked[i]); } for (i = 0; i < VOTE_TOTAL; i++) @@ -6795,6 +6796,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) { g_voteLevels[i][0] = READUINT16(save->p); g_voteLevels[i][1] = READUINT16(save->p); + g_votes_striked[i] = (boolean)READUINT8(save->p); } for (i = 0; i < VOTE_TOTAL; i++) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 935303fbf..465df3e38 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1976,6 +1976,35 @@ boolean Y_ShouldDoIntermission(void) return true; } +// +// Y_GetIntermissionType +// +// Returns the intermission type from the current gametype. +// +intertype_t Y_GetIntermissionType(void) +{ + intertype_t ret = static_cast(gametypes[gametype]->intermission); + + if (ret == int_scoreortimeattack) + { + UINT8 i = 0, nump = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + nump++; + } + + ret = (nump < 2 ? int_time : int_score); + } + + return ret; +} + // // Y_DetermineIntermissionType // @@ -1990,21 +2019,7 @@ void Y_DetermineIntermissionType(void) return; } - // set initially - intertype = static_cast(gametypes[gametype]->intermission); - - // special cases - if (intertype == int_scoreortimeattack) - { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - intertype = (nump < 2 ? int_time : int_score); - } + intertype = Y_GetIntermissionType(); } static UINT8 Y_PlayersBestPossiblePosition(player_t *const player) diff --git a/src/y_inter.h b/src/y_inter.h index 51665f734..0f3687db4 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -61,9 +61,6 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree void Y_StartIntermission(void); void Y_EndIntermission(void); -boolean Y_ShouldDoIntermission(void); -void Y_DetermineIntermissionType(void); - void Y_PlayIntermissionMusic(void); typedef enum @@ -76,6 +73,10 @@ typedef enum extern intertype_t intertype; +boolean Y_ShouldDoIntermission(void); +intertype_t Y_GetIntermissionType(void); +void Y_DetermineIntermissionType(void); + #ifdef __cplusplus } // extern "C" #endif From e21cf463eadd5e8be0109e734225ba89488cb9d0 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 3 Sep 2024 20:56:36 -0400 Subject: [PATCH 07/64] Duel vote: More conveyance - Show X over selection, to make it more obvious when it's rip snortin' time - "Waiting for [player]..." when it's not your turn - Halve vote timer in Duels (maybe make a separate cvar?) - Fix bots sometimes striking stages out realllllly slowly --- src/d_netcmd.c | 8 ++- src/k_vote.c | 138 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d743ed3a2..939fa84c1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5833,23 +5833,21 @@ static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum) if (targetID >= MAXPLAYERS) { // only the server is allowed to send these - if (playernum != serverplayer) + if (playernum != serverplayer) { goto fail; } } else if (playeringame[targetID] == true && players[targetID].bot == true) { - if (targetID >= MAXPLAYERS - || playernum != serverplayer) + if (playernum != serverplayer) { goto fail; } } else { - if (targetID >= MAXPLAYERS - || playernode[targetID] != playernode[playernum]) + if (playernode[targetID] != playernode[playernum]) { goto fail; } diff --git a/src/k_vote.c b/src/k_vote.c index 39d0fc36f..8c17cfedb 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -242,6 +242,32 @@ typedef struct static y_vote_data vote = {0}; static y_vote_draw vote_draw = {0}; +static void Y_SetVoteTimer(void) +{ + vote.timer = cv_votetime.value * TICRATE; + + if (vote.stage_striking == true) + { + vote.timer /= 2; + } +} + +static UINT8 Y_CountStriked(void) +{ + INT32 i; + + UINT8 num_striked = 0; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + num_striked++; + } + } + + return num_striked; +} + static boolean Y_VoteIDIsSpecial(const UINT8 playerId) { switch (playerId) @@ -486,18 +512,9 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) if (vote.stage_striking == true) { - UINT8 num_striked = 0; - for (i = 0; i < VOTE_NUM_LEVELS; i++) - { - if (g_votes_striked[i] == true) - { - num_striked++; - } - } - if (newVote != VOTE_NOT_PICKED - && num_striked < VOTE_NUM_LEVELS-1 - && g_votes_striked[newVote] == false) + && g_votes_striked[newVote] == false + && Y_CountStriked() < VOTE_NUM_LEVELS-1) { // Strike a stage, instead of voting. g_votes_striked[newVote] = true; @@ -506,7 +523,7 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) vote.strike_turn = !vote.strike_turn; // Reset variables. - vote.timer = cv_votetime.value * TICRATE; + Y_SetVoteTimer(); for (i = 0; i <= splitscreen; i++) { vote.players[i].sentTimeOutVote = false; @@ -544,12 +561,12 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) if (vote.timer == -1) { // Someone has voted, so start the timer now. - vote.timer = cv_votetime.value * TICRATE; + Y_SetVoteTimer(); } #endif } -static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID) +static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID, boolean from_selection) { const boolean encore = vote_draw.levels[v].encore; const fixed_t height = (width * BASEVIDHEIGHT) / BASEVIDWIDTH; @@ -597,7 +614,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy); boolean striked = false; - if (playerID < 0) + if (from_selection == true) { striked = g_votes_striked[v]; } @@ -637,6 +654,22 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt NULL ); } + + if (vote.stage_striking == true + && from_selection == true + && dim == false) + { + if (Y_CountStriked() < VOTE_NUM_LEVELS-1) + { + const fixed_t strikeScale = width / 32; + V_DrawFixedPatch( + center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2), + strikeScale, flags /*| V_TRANSLUCENT*/, + vote_draw.strike_icon, + NULL + ); + } + } } if (dim == true) @@ -765,7 +798,7 @@ static void Y_DrawCatcher(y_vote_catcher *catcher) baseX, catcher->y, ((catcher->small == true) ? PILE_WIDTH : SELECTION_WIDTH), 0, catcher->level, false, - catcher->player + catcher->player, false ); } @@ -1028,12 +1061,41 @@ static void Y_DrawVoteSelection(fixed_t offset) x, y - vote_draw.levels[i].hop, SELECTION_WIDTH, 0, i, (selected == false), - -1 + -1, true ); x += SELECTION_SPACING_W; } + if (vote.stage_striking == true && Y_CountStriked() < VOTE_NUM_LEVELS-1) + { + UINT8 current_strike_player = ( + (vote.strike_turn == true) + ? (vote.strike_winner - players) + : (vote.strike_loser - players) + ); + + for (i = 0; i <= splitscreen; i++) + { + if (g_localplayers[i] == current_strike_player) + { + break; + } + } + + if (i > splitscreen) + { + const char *wait_str = va("Waiting for %s...", player_names[current_strike_player]); + + V_DrawThinString( + BASEVIDWIDTH / 2 - (V_ThinStringWidth(wait_str, 0) / 2), + 180, + 0, + wait_str + ); + } + } + // // Draw our catchers // @@ -1091,7 +1153,7 @@ static void Y_DrawVotePile(void) PILE_WIDTH, 0, g_votes[i], (i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED), - i + i, false ); } @@ -1594,18 +1656,19 @@ static void Y_ExitStageStrike(void) { INT32 i; + vote.stage_striking = false; + for (i = 0; i < VOTE_NUM_LEVELS; i++) { g_votes_striked[i] = false; } - vote.stage_striking = false; - vote.timer = cv_votetime.value * TICRATE; - vote.strike_loser = NULL; vote.strike_winner = NULL; vote.strike_turn = false; vote.strike_time_out = false; + + Y_SetVoteTimer(); } static boolean Y_CheckStageStrikeStatus(void) @@ -1880,12 +1943,27 @@ static void Y_TickVoteSelection(void) continue; } - if (players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) + if (server && players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) { - if (server && ( M_RandomFixed() % 100 ) == 0) + if (( M_RandomFixed() % 100 ) == 0) { // bots vote randomly - D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS)); + INT32 rng = M_RandomKey(VOTE_NUM_LEVELS); + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == false) + { + break; + } + + rng++; + if (rng >= VOTE_NUM_LEVELS) + { + rng = 0; + } + } + + D_ModifyClientVote(i, rng); } } @@ -2220,12 +2298,6 @@ void Y_StartVote(void) vote.tic = vote.endtic = -1; -#ifdef VOTE_TIME_WAIT_FOR_VOTE - vote.timer = -1; // Timer is not set until the first vote is added -#else - vote.timer = cv_votetime.value * TICRATE; -#endif - g_pickedVote = VOTE_NOT_PICKED; vote.notYetPicked = true; @@ -2260,6 +2332,12 @@ void Y_StartVote(void) Y_DetermineStageStrike(); +#ifdef VOTE_TIME_WAIT_FOR_VOTE + vote.timer = -1; // Timer is not set until the first vote is added +#else + Y_SetVoteTimer(); +#endif + Y_InitVoteDrawing(); vote.loaded = true; From 2f164cf1b84e480cc90a3e23b5ea3103caeec88e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 3 Sep 2024 05:11:00 -0700 Subject: [PATCH 08/64] Revert "WIP: Timer Duel" This reverts commit 1d83dddab8f561b8d6275d9a1e77df6ef0b022c9. --- src/d_player.h | 1 - src/g_game.c | 12 ----------- src/k_hud.cpp | 50 ++++++++++++++++----------------------------- src/k_kart.c | 47 +++++++----------------------------------- src/k_kart.h | 6 ------ src/lua_playerlib.c | 4 ---- src/p_inter.c | 2 -- src/p_saveg.c | 2 -- 8 files changed, 25 insertions(+), 99 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index d3facfed7..6d6a7b8bb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -927,7 +927,6 @@ struct player_t INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp INT16 duelscore; - tic_t dueltimer; UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue diff --git a/src/g_game.c b/src/g_game.c index ea8f78712..dfcc624f1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2180,9 +2180,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 lastsafecheatcheck; UINT16 bigwaypointgap; - INT16 duelscore; - tic_t dueltimer; - roundconditions_t roundconditions; boolean saveroundconditions; @@ -2338,9 +2335,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bigwaypointgap = 0; tallyactive = false; - - dueltimer = 15*TICRATE; - duelscore = 0; } else { @@ -2391,9 +2385,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) lastsafecheatcheck = players[player].lastsafecheatcheck; bigwaypointgap = players[player].bigwaypointgap; - duelscore = players[player].duelscore; - dueltimer = players[player].dueltimer; - tallyactive = players[player].tally.active; if (tallyactive) { @@ -2531,9 +2522,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->griefValue = griefValue; p->griefStrikes = griefStrikes; - p->dueltimer = dueltimer; - p->duelscore = duelscore; - memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index b8272f3b0..8918e3233 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2628,38 +2628,11 @@ void PositionFacesInfo::draw_1p() ; else if (gametyperules & GTR_CIRCUIT) { - if (inDuel) - { - INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; - - colormap = NULL; - - if (K_PlayerLosingDuel(&players[rankplayer[i]]) || players[rankplayer[i]].dueltimer == 0) - { - colormap = R_GetTranslationColormap(TC_RAINBOW, (players[rankplayer[i]].dueltimer == 0) ? SKINCOLOR_CRIMSON : SKINCOLOR_TANGERINE, GTC_CACHE); - flags |= V_STRINGDANCE; - } - - V_DrawStringScaled( - (FACE_X - 5) * FRACUNIT, - (Y + 10) * FRACUNIT, - FRACUNIT, - FRACUNIT, - FRACUNIT, - flags, - colormap, - PINGF_FONT, - va("%02d.%02d", players[rankplayer[i]].dueltimer/TICRATE, (players[rankplayer[i]].dueltimer%TICRATE)*100/TICRATE) - ); - } - else - { - INT32 pos = players[rankplayer[i]].position; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); - } + INT32 pos = players[rankplayer[i]].position; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); } else if (gametyperules & GTR_POINTLIMIT) { @@ -3024,6 +2997,19 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. + if (inDuel) + { + UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; + if (leveltime % 2) + if (abs(stplyr->duelscore) >= 2) + flashflag = V_YELLOWMAP; + + if (stplyr->duelscore >= 0) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); + } + if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 6d3fa23c6..1fa198003 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4147,23 +4147,19 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel) + if (inDuel && player->position == 1) { player->duelscore += 1; - - if (player->position == 1) - { - tic_t effectiveleveltime = min(leveltime, DUELTIMER_NOBONUS); - player->dueltimer += Easing_Linear(effectiveleveltime*FRACUNIT/DUELTIMER_NOBONUS, DUELTIMER_BONUS, 0); - player->dueltimer = min(player->dueltimer, DUELTIMER_MAX); - } - for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) - { players[i].duelscore -= 1; - } + } + + if (player->duelscore == 3) + { + P_DoPlayerExit(player, 0); + P_DoAllPlayersExit(PF_NOCONTEST, 0); } } @@ -7620,19 +7616,6 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds) return i; } -boolean K_PlayerLosingDuel(player_t *player) -{ - for (UINT8 i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && &players[i] != player) - { - if (players[i].duelscore > player->duelscore) - return true; - } - } - return false; -} - mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); @@ -9402,22 +9385,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } - if (inDuel && K_PlayerLosingDuel(player)) - { - if (player->dueltimer) - { - player->dueltimer--; - if (!(player->dueltimer % 4) && P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_s3k55); - - if (player->dueltimer == 0) - { - P_DoTimeOver(player); - P_DoAllPlayersExit(0, false); - } - } - } - if (!player->invincibilitytimer) player->invincibilityextensions = 0; diff --git a/src/k_kart.h b/src/k_kart.h index 38e80016e..bf73ffcf2 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -88,11 +88,6 @@ Make sure this matches the actual number of states #define AUTORESPAWN_TIME (10*TICRATE) #define AUTORESPAWN_THRESHOLD (7*TICRATE) -#define DUELTIMER_START (10*TICRATE) -#define DUELTIMER_BONUS (2*TICRATE) -#define DUELTIMER_MAX (3*DUELTIMER_START/2) -#define DUELTIMER_NOBONUS (60*TICRATE) - angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); boolean K_IsDuelItem(mobjtype_t type); @@ -208,7 +203,6 @@ void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); -boolean K_PlayerLosingDuel(player_t *player); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); mobj_t *K_FlingPaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index c0bf2df4b..59796ac60 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -677,8 +677,6 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->cheatchecknum); else if (fastcmp(field,"duelscore")) lua_pushinteger(L, plr->duelscore); - else if (fastcmp(field,"dueltimer")) - lua_pushinteger(L, plr->dueltimer); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1252,8 +1250,6 @@ static int player_set(lua_State *L) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"duelscore")) plr->duelscore = (INT16)luaL_checkinteger(L, 3); - else if (fastcmp(field,"dueltimer")) - plr->dueltimer = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_inter.c b/src/p_inter.c index 7b5c69b57..8464cf0b9 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1544,8 +1544,6 @@ boolean P_CheckRacers(void) const boolean griefed = (spectateGriefed > 0); boolean eliminateLast = (!K_CanChangeRules(true) || (cv_karteliminatelast.value != 0)); - if (inDuel) - eliminateLast = false; boolean allHumansDone = true; //boolean allBotsDone = true; diff --git a/src/p_saveg.c b/src/p_saveg.c index dbf40c043..d1999a03e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -285,7 +285,6 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); WRITEINT16(save->p, players[i].duelscore); - WRITEUINT32(save->p, players[i].dueltimer); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -958,7 +957,6 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); players[i].duelscore = READINT16(save->p); - players[i].dueltimer = READUINT32(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); From c4fc86b6685903973eda994f1903d6615db232c7 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 5 Sep 2024 00:36:24 -0700 Subject: [PATCH 09/64] Margin Boost --- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_kart.c | 14 +++++++++++++- src/k_kart.h | 2 ++ src/p_saveg.c | 2 ++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/doomstat.h b/src/doomstat.h index dd0e8bc68..9dc8f4034 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -869,6 +869,7 @@ extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; extern UINT8 extralaps; +extern UINT8 overtimecheckpoints; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) extern boolean legitimateexit; diff --git a/src/g_game.c b/src/g_game.c index dfcc624f1..226ea6d67 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -321,6 +321,7 @@ boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start w tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 UINT8 extralaps; // Duel extensions! +UINT8 overtimecheckpoints; // Duel overtime speedups! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures. diff --git a/src/k_kart.c b/src/k_kart.c index 1fa198003..27969e032 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -146,6 +146,7 @@ void K_TimerReset(void) inDuel = rainbowstartavailable = false; linecrossed = 0; extralaps = 0; + overtimecheckpoints = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; } @@ -466,7 +467,10 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) if (cv_4thgear.value && !netgame && (!demo.playback || !demo.netgame) && !modeattacking) value = 3; - return ((13 + (3*value)) << FRACBITS) / 16; + fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; + fixed_t duel = overtimecheckpoints*(1<position == 1) { player->duelscore += 1; + + if (leveltime > DUELOVERTIME) + { + overtimecheckpoints++; + K_AddMessage(va("MARGIN BOOST x%d", overtimecheckpoints), true, false); + S_StartSound(NULL, sfx_gsha6); + } + for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) diff --git a/src/k_kart.h b/src/k_kart.h index bf73ffcf2..3e4995dd2 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -74,6 +74,8 @@ Make sure this matches the actual number of states #define RINGVOLUMEREGEN 1 #define RINGTRANSPARENCYREGEN 3 +#define DUELOVERTIME (3*60*TICRATE) + #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) #define MAXTOPACCEL (12*FRACUNIT) diff --git a/src/p_saveg.c b/src/p_saveg.c index d1999a03e..a4679ea8d 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6681,6 +6681,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); WRITEUINT8(save->p, extralaps); + WRITEUINT8(save->p, overtimecheckpoints); WRITEUINT32(save->p, introtime); WRITEUINT32(save->p, starttime); @@ -6888,6 +6889,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); extralaps = (boolean)READUINT8(save->p); + overtimecheckpoints = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); starttime = READUINT32(save->p); From bf2b31a83364eb346b92bc275cd30074a9440b9d Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 5 Sep 2024 01:21:07 -0700 Subject: [PATCH 10/64] Slower margin boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 27969e032..8a7fcfab9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -468,7 +468,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Thu, 5 Sep 2024 01:26:14 -0700 Subject: [PATCH 11/64] Even slower Margin Boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8a7fcfab9..82d41dc86 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -468,7 +468,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Fri, 6 Sep 2024 20:58:06 -0700 Subject: [PATCH 12/64] Duel: cleanup, finish refactor, endcam, position fixes --- src/doomstat.h | 1 - src/g_game.c | 1 - src/k_hud.cpp | 4 +-- src/k_kart.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++--- src/k_kart.h | 3 ++ src/k_tally.cpp | 3 ++ src/p_saveg.c | 2 -- src/p_spec.c | 19 ++++++------- src/p_user.c | 2 +- 9 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index 9dc8f4034..a31a8dc9b 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -868,7 +868,6 @@ extern SINT8 spbplace; extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; -extern UINT8 extralaps; extern UINT8 overtimecheckpoints; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) diff --git a/src/g_game.c b/src/g_game.c index 226ea6d67..af8105920 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -320,7 +320,6 @@ SINT8 spbplace; // SPB exists, give the person behind better items boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 -UINT8 extralaps; // Duel extensions! UINT8 overtimecheckpoints; // Duel overtime speedups! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 8918e3233..30cc7bc48 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2997,7 +2997,7 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. - if (inDuel) + if (K_InRaceDuel()) { UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; if (leveltime % 2) @@ -3018,7 +3018,7 @@ static void K_drawKartLaps(void) bump = 40; } - if (numlaps != 1) + if (numlaps != 1 && !K_InRaceDuel()) { if (r_splitscreen > 1) { diff --git a/src/k_kart.c b/src/k_kart.c index 82d41dc86..43b430e8a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -119,6 +119,27 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt) return !!(mt->thing_args[0]); } +boolean K_InRaceDuel(void) +{ + return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); +} + +player_t *K_DuelOpponent(player_t *player) +{ + if (!K_InRaceDuel()) + return player; // ???? + else + { + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && player - players != i) + return &players[i]; + } + } + + return player; // ???????????? +} + static void K_SpawnDuelOnlyItems(void) { mapthing_t *mt = NULL; @@ -145,7 +166,6 @@ void K_TimerReset(void) numbulbs = 1; inDuel = rainbowstartavailable = false; linecrossed = 0; - extralaps = 0; overtimecheckpoints = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; @@ -275,6 +295,9 @@ void K_TimerInit(void) introtime = (108) + 5; // 108 for rotation, + 5 for white fade numbulbs += (numPlayers-2); // Extra POSITION!! time } + + if (K_InRaceDuel()) + numlaps = 200; } } @@ -4151,7 +4174,7 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel && player->position == 1) + if (K_InRaceDuel() && player->position == 1) { player->duelscore += 1; @@ -4168,10 +4191,48 @@ void K_CheckpointCrossAward(player_t *player) players[i].duelscore -= 1; } - if (player->duelscore == 3) + if (player->duelscore == DUELWINNINGSCORE) { + S_StartSound(NULL, sfx_s3k6a); P_DoPlayerExit(player, 0); P_DoAllPlayersExit(PF_NOCONTEST, 0); + + player_t *opp = K_DuelOpponent(player); + opp->position = 2; + player->position = 1; + + if (opp->distancetofinish - player->distancetofinish < 128) + { + K_StartRoundWinCamera( + player->mo, + player->angleturn + ANGLE_180, + 400*mapobjectscale, + 6*TICRATE, + FRACUNIT/16 + ); + } + else + { + K_StartRoundWinCamera( + opp->mo, + opp->angleturn + ANGLE_180, + 400*mapobjectscale, + 6*TICRATE, + FRACUNIT/16 + ); + } + + } + else + { + // Doing this here because duel exit is a weird path, and we don't want to transform for endcam. + UINT32 skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[(player-players)]].flags + : skins[player->skin].flags; + if (skinflags & SF_IRONMAN) + { + SetRandomFakePlayerSkin(player, true, false); + } } } @@ -10697,7 +10758,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection + extralaps; + const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } @@ -11707,6 +11768,11 @@ void K_KartUpdatePosition(player_t *player) realplayers++; } } + else if (K_InRaceDuel() && player->exiting) + { + // Positions directly set in K_CheckpointCrossAward, don't touch. + return; + } else { for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/k_kart.h b/src/k_kart.h index 3e4995dd2..3b0b10a5b 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -75,6 +75,7 @@ Make sure this matches the actual number of states #define RINGTRANSPARENCYREGEN 3 #define DUELOVERTIME (3*60*TICRATE) +#define DUELWINNINGSCORE (1) #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) @@ -94,6 +95,8 @@ angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t boolean K_IsDuelItem(mobjtype_t type); boolean K_DuelItemAlwaysSpawns(mapthing_t *mt); +boolean K_InRaceDuel(void); +player_t *K_DuelOpponent(player_t *player); void K_TimerReset(void); void K_TimerInit(void); diff --git a/src/k_tally.cpp b/src/k_tally.cpp index b095ff202..3b49c352f 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -46,6 +46,9 @@ boolean level_tally_t::UseBonuses(void) return false; } + if (K_InRaceDuel()) + return false; + // No bonuses / ranking in FREE PLAY or Time Attack return (grandprixinfo.gp == true || K_TimeAttackRules() == false); } diff --git a/src/p_saveg.c b/src/p_saveg.c index a4679ea8d..2183821c2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6680,7 +6680,6 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITESINT8(save->p, spbplace); WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); - WRITEUINT8(save->p, extralaps); WRITEUINT8(save->p, overtimecheckpoints); WRITEUINT32(save->p, introtime); @@ -6888,7 +6887,6 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) spbplace = READSINT8(save->p); rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); - extralaps = (boolean)READUINT8(save->p); overtimecheckpoints = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index de2547392..0712f049f 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -52,6 +52,7 @@ #include "m_easing.h" #include "music.h" #include "k_battle.h" // battleprisons +#include "k_endcam.h" // K_EndCameraIsFreezing() // Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog #include @@ -1993,11 +1994,6 @@ static void K_HandleLapIncrement(player_t *player) boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); - if (specialduelexit) - { - extralaps += 1; - } - // finished race exit setup if (player->laps > numlaps && !specialduelexit) { @@ -2025,7 +2021,8 @@ static void K_HandleLapIncrement(player_t *player) : skins[player->skin].flags; if (skinflags & SF_IRONMAN) { - SetRandomFakePlayerSkin(player, true, false); + if (!K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. + SetRandomFakePlayerSkin(player, true, false); } // Always trust waypoints entering the first lap. @@ -2055,7 +2052,7 @@ static void K_HandleLapIncrement(player_t *player) K_SpawnDriftBoostExplosion(player, 4); K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, (inDuel) ? 20 : 50, player->mo); + K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 50, player->mo); rainbowstartavailable = false; } @@ -2077,7 +2074,9 @@ static void K_HandleLapIncrement(player_t *player) } else if (P_IsDisplayPlayer(player)) { - if (numlaps > 1 && player->laps == numlaps) // final lap + if (K_InRaceDuel()) + S_StartSound(NULL, sfx_s221); + else if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap S_StartSound(NULL, sfx_s221); @@ -2090,7 +2089,7 @@ static void K_HandleLapIncrement(player_t *player) } else { - if ((player->laps > numlaps) && (player->position == 1)) + if ((player->laps > numlaps) && (player->position == 1) && (!K_InRaceDuel())) { // opponent finished S_StartSound(NULL, sfx_s253); @@ -4724,7 +4723,7 @@ void P_SetupSignExit(player_t *player, boolean tie) return; // SRB2Kart: FINALLY, add in an alternative if no place is found - if (player->mo && !P_MobjWasRemoved(player->mo)) + if (player->mo && !P_MobjWasRemoved(player->mo) && !K_EndCameraIsFreezing()) { thing = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->floorz, MT_SIGN); thing->angle = bestAngle; diff --git a/src/p_user.c b/src/p_user.c index c39b105bd..fc19292a6 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1368,7 +1368,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) { UINT8 i; - const boolean dofinishsound = (musiccountdown == 0); + const boolean dofinishsound = (musiccountdown == 0) && (!K_InRaceDuel()); if (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_SPECIAL From bb51c2df4d2fde0bc3933905dbfae40a94086486 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 6 Sep 2024 21:45:05 -0700 Subject: [PATCH 13/64] Fix lap display in duel --- src/k_hud.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 30cc7bc48..88088915e 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3010,7 +3010,9 @@ static void K_drawKartLaps(void) V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); } - if (numlaps != 1) + boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel()); + + if (drawinglaps) { if (r_splitscreen > 1) bump = 27; @@ -3018,7 +3020,7 @@ static void K_drawKartLaps(void) bump = 40; } - if (numlaps != 1 && !K_InRaceDuel()) + if (drawinglaps) { if (r_splitscreen > 1) { From bdd32cd08f4b84ce880b70e62a850a2da0c67383 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 00:08:38 -0700 Subject: [PATCH 14/64] Tighten "close finish" ending camera --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 43b430e8a..91dd91fa1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4201,7 +4201,7 @@ void K_CheckpointCrossAward(player_t *player) opp->position = 2; player->position = 1; - if (opp->distancetofinish - player->distancetofinish < 128) + if (opp->distancetofinish - player->distancetofinish < 200) { K_StartRoundWinCamera( player->mo, From abc0187b7e07ec20135fb2d5ded7dcbe6888e266 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 01:07:27 -0700 Subject: [PATCH 15/64] Oops, make duels actually score correctly --- src/k_kart.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.h b/src/k_kart.h index 3b0b10a5b..faabd9704 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -75,7 +75,7 @@ Make sure this matches the actual number of states #define RINGTRANSPARENCYREGEN 3 #define DUELOVERTIME (3*60*TICRATE) -#define DUELWINNINGSCORE (1) +#define DUELWINNINGSCORE (3) #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) From 1660d9c143ea50f971e2fdadbfa53229c7725157 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 18:57:35 -0700 Subject: [PATCH 16/64] Yet slower Margin Boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 91dd91fa1..2c008048c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -491,7 +491,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Mon, 9 Sep 2024 02:09:30 -0700 Subject: [PATCH 17/64] WIP --- src/k_kart.c | 31 ++++++++++++++++++++++--------- src/p_spec.c | 6 ++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 727b0cca7..217a2aae9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -297,7 +297,7 @@ void K_TimerInit(void) } if (K_InRaceDuel()) - numlaps = 200; + numlaps = 1; } } @@ -10764,15 +10764,28 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) } Z_Free(pathtofinish.array); - // distancetofinish is currently a flat distance to the finish line, but in order to be fully - // correct we need to add to it the length of the entire circuit multiplied by the number of laps - // left after this one. This will give us the total distance to the finish line, and allow item - // distance calculation to work easily - const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; - if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) + if (K_InRaceDuel() && player->position == 1) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; - player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + // As far as we're concerned, the race starts and ends with our position. + // Don't care about laps at all! + } + else + { + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; + if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) + { + UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; + if (K_InRaceDuel()) + { + player_t *opp = K_DuelOpponent(player); + numfulllapsleft = opp->laps - player->laps; + } + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + } } } } diff --git a/src/p_spec.c b/src/p_spec.c index 0712f049f..f9091c145 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1992,10 +1992,8 @@ static void K_HandleLapIncrement(player_t *player) player->latestlap = player->laps; } - boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); - // finished race exit setup - if (player->laps > numlaps && !specialduelexit) + if (player->laps > numlaps && !K_InRaceDuel()) { pflags_t applyflags = 0; if (specialstageinfo.valid == true) @@ -2021,7 +2019,7 @@ static void K_HandleLapIncrement(player_t *player) : skins[player->skin].flags; if (skinflags & SF_IRONMAN) { - if (!K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. + if ((player->laps == 1 && lapisfresh) || !K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. SetRandomFakePlayerSkin(player, true, false); } From 6e51815df3896174a97c5233fbb7749ff4c8307f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:08:58 -0700 Subject: [PATCH 18/64] Race Duel: only endcam on close finishes --- src/k_kart.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 217a2aae9..3b877bf6d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4211,16 +4211,6 @@ void K_CheckpointCrossAward(player_t *player) FRACUNIT/16 ); } - else - { - K_StartRoundWinCamera( - opp->mo, - opp->angleturn + ANGLE_180, - 400*mapobjectscale, - 6*TICRATE, - FRACUNIT/16 - ); - } } else From fc56b1c802c3d0781c60ac3762336a4cb52cb27d Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:09:40 -0700 Subject: [PATCH 19/64] Race Duel: prettier Margin Boost text --- src/k_kart.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 3b877bf6d..5d3548b4b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4181,7 +4181,10 @@ void K_CheckpointCrossAward(player_t *player) if (leveltime > DUELOVERTIME) { overtimecheckpoints++; - K_AddMessage(va("MARGIN BOOST x%d", overtimecheckpoints), true, false); + if (overtimecheckpoints > 1) + K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false); + else + K_AddMessage(va("Margin Boost!", overtimecheckpoints), true, false); S_StartSound(NULL, sfx_gsha6); } From 2a42d664f0f71d61f7ce7f04f02b76300e4cdedb Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:11:47 -0700 Subject: [PATCH 20/64] Special Stage is never Race Duel --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 5d3548b4b..31b5eb956 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -121,7 +121,7 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt) boolean K_InRaceDuel(void) { - return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) && !specialstageinfo.valid; } player_t *K_DuelOpponent(player_t *player) From 62e8b435aad2aad142d3fb3d95e08dc9cb9a70ea Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:31:48 -0700 Subject: [PATCH 21/64] Race Duel: endcam fixup --- src/k_kart.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 31b5eb956..264846a0a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4184,7 +4184,7 @@ void K_CheckpointCrossAward(player_t *player) if (overtimecheckpoints > 1) K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false); else - K_AddMessage(va("Margin Boost!", overtimecheckpoints), true, false); + K_AddMessage("Margin Boost!", true, false); S_StartSound(NULL, sfx_gsha6); } @@ -4196,15 +4196,11 @@ void K_CheckpointCrossAward(player_t *player) if (player->duelscore == DUELWINNINGSCORE) { - S_StartSound(NULL, sfx_s3k6a); - P_DoPlayerExit(player, 0); - P_DoAllPlayersExit(PF_NOCONTEST, 0); - player_t *opp = K_DuelOpponent(player); opp->position = 2; player->position = 1; - if (opp->distancetofinish - player->distancetofinish < 200) + if (opp->distancetofinish - player->distancetofinish < 200) // Setting player.exiting changes distance reporting, check these first! { K_StartRoundWinCamera( player->mo, @@ -4215,6 +4211,9 @@ void K_CheckpointCrossAward(player_t *player) ); } + S_StartSound(NULL, sfx_s3k6a); + P_DoPlayerExit(player, 0); + P_DoAllPlayersExit(PF_NOCONTEST, 0); } else { @@ -9491,6 +9490,14 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->amppickup--; } + // ACHTUNG TEMPORARY FUCKUP. + // Disable skip protection in Race Duel because of distance jumps in infinite-lap contexts. + // This shouldn't exist at all in release 2.4, so this is probably fine, right...? + if (K_InRaceDuel()) + { + player->pflags |= PF_TRUSTWAYPOINTS; + } + // Don't tick down while in damage state. // There may be some maps where the timer activates for From a0fce69e11218f38509fb67473509c40c02a6966 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 16 May 2025 20:33:51 -0400 Subject: [PATCH 22/64] Disallow drift exit help on D0 --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index abd2138e4..aff233330 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2345,7 +2345,7 @@ static void P_UpdatePlayerAngle(player_t *player) // This is the hardest case for the turn solver, because your handling properties on // client side are very different than your handling properties on server side—at least, // until your drift status makes the full round-trip and is reflected in your gamestate. - if (player->drift && abs(player->drift) < 5) + if (player->drift && abs(player->drift) < 5 && player->cmd.latency) { steeringRight = KART_FULLTURN; steeringLeft = -KART_FULLTURN; From 9a23bde4356196c3e7cbf01ebd847fc09151d1f9 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 May 2025 18:48:21 -0400 Subject: [PATCH 23/64] Don't increase karted time when faulted --- src/p_user.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/p_user.c b/src/p_user.c index 9e811dc01..0b7325695 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4238,6 +4238,7 @@ void P_PlayerThink(player_t *player) // Track airtime if (P_IsObjectOnGround(player->mo) + && !(player->pflags & PF_FAULT) && !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same. { player->lastairtime = player->airtime; From 2940b45a22243d2d1903aed70fe8288c2084c7fd Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 May 2025 18:52:41 -0400 Subject: [PATCH 24/64] No map command in GP --- src/d_netcmd.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 054504912..aa7711d7f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2635,6 +2635,9 @@ static void Command_Map_f(void) } } + if (grandprixinfo.gp) + ischeating = true; + if (ischeating && !usingcheats) { CONS_Printf(M_GetText("Cheats must be enabled.\n")); From c3005b2a6a7482e9ecb1326a2628587bce3cc6aa Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 May 2025 10:59:04 -0400 Subject: [PATCH 25/64] Move Overdrive boost power into standard OD --- src/k_kart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 0371c1134..8d7f33877 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3564,7 +3564,7 @@ static void K_GetKartBoostPower(player_t *player) Easing_InCubic( player->overdrivepower, 0, - 5*FRACUNIT/10 + 65*FRACUNIT/100 ), Easing_InSine( player->overdrivepower, @@ -3640,7 +3640,7 @@ static void K_GetKartBoostPower(player_t *player) { fixed_t ringboost_base = FRACUNIT/4; if (player->overdrive) - ringboost_base += FRACUNIT/2; + ringboost_base += FRACUNIT/4; // This one's a little special: we add extra top speed per tic of ringboost stored up, to allow for Ring Box to really rocket away. // (We compensate when decrementing ringboost to avoid runaway exponential scaling hell.) fixed_t rb = FixedDiv(player->ringboost * FRACUNIT, max(FRACUNIT, K_RingDurationBoost(player))); From 077a3750b086c1852c7ec7bffb7488759019a43c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 May 2025 11:39:39 -0400 Subject: [PATCH 26/64] More drawpickups exclusions for snapshotmaps --- src/r_things.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/r_things.cpp b/src/r_things.cpp index 6fca80e5f..a1e59cf88 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -3797,6 +3797,12 @@ boolean R_ThingVisible (mobj_t *thing) case MT_BATTLECAPSULE_PIECE: case MT_SPRAYCAN: case MT_PLAYER: + case MT_LANDMINE: + case MT_SSMINE: + case MT_SSMINE_SHIELD: + case MT_CHECKPOINT_END: + case MT_SIGNSPARKLE: + case MT_THOK: // checkpoint parts return false; default: From 14c115fa363166456dd3b97d5bc0039fca565110 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 May 2025 13:57:08 -0400 Subject: [PATCH 27/64] Do something useful lmao --- src/p_user.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index 0b7325695..0e498e4f8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4238,7 +4238,6 @@ void P_PlayerThink(player_t *player) // Track airtime if (P_IsObjectOnGround(player->mo) - && !(player->pflags & PF_FAULT) && !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same. { player->lastairtime = player->airtime; @@ -4249,6 +4248,12 @@ void P_PlayerThink(player_t *player) player->airtime++; } + if ((player->pflags & PF_FAULT) || (player->pflags & PF_VOID)) + { + player->lastairtime = 0; + player->airtime = 0; + } + cmd = &player->cmd; // SRB2kart From d236620adf6f9425d0bbbb87f366ea85c9fff87c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 03:03:08 -0400 Subject: [PATCH 28/64] Fix cantact with duel Hyudoro crashing in GP bonus (trivial) --- src/objects/hyudoro.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/objects/hyudoro.c b/src/objects/hyudoro.c index f1e0f440e..c43317928 100644 --- a/src/objects/hyudoro.c +++ b/src/objects/hyudoro.c @@ -599,7 +599,8 @@ hyudoro_patrol_hit_player P_SetTarget(&hyudoro_target(hyu), master); - K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher); + if (master && !P_MobjWasRemoved(master)) + K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher); if (center) P_RemoveMobj(center); From cb0bff86ac0d6804b103ad2763d2196e30850228 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 13:52:24 -0400 Subject: [PATCH 29/64] Players are never scamming in GTR_SPHERES --- src/k_kart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 0371c1134..613468100 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -446,6 +446,9 @@ boolean K_IsPlayerScamming(player_t *player) if (!M_NotFreePlay()) return false; + if (gametyperules & GTR_SPHERES) + return false; + // "Why 8?" Consistency // "Why 2000?" Vibes return (K_GetItemRouletteDistance(player, 8) < SCAMDIST); From 94c6ed448dcc206825381e42f5cbd852388af08c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 14:17:48 -0400 Subject: [PATCH 30/64] Start egg roulette when transferring to player with egg roulette --- src/k_kart.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 0371c1134..5ecf2cef4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -15262,8 +15262,17 @@ void K_EggmanTransfer(player_t *source, player_t *victim) if (victim->eggmanexplode) return; + boolean prank = false; + + if (victim->itemRoulette.eggman) + { + K_StopRoulette(&source->itemRoulette); + prank = true; // Give the transferring player the victim's eggbox roulette?! + } + K_AddHitLag(victim->mo, 5, false); K_DropItems(victim); + victim->eggmanexplode = 6*TICRATE; victim->eggmanblame = (source - players); K_StopRoulette(&victim->itemRoulette); @@ -15272,9 +15281,20 @@ void K_EggmanTransfer(player_t *source, player_t *victim) S_StartSound(NULL, sfx_itrole); K_AddHitLag(source->mo, 5, false); - source->eggmanexplode = 0; - source->eggmanblame = -1; - K_StopRoulette(&source->itemRoulette); + + if (prank) + { + source->eggmanexplode = 0; + source->eggmanblame = (victim - players); + K_StartEggmanRoulette(source); + S_StartSound(source->mo, sfx_s223); + } + else + { + source->eggmanexplode = 0; + source->eggmanblame = -1; + K_StopRoulette(&source->itemRoulette); + } source->eggmanTransferDelay = 25; victim->eggmanTransferDelay = 15; From 27eaa82bd3c93b9fb2cafbe02141cbc421b0af67 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 14:28:13 -0400 Subject: [PATCH 31/64] Never top shake in Special --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 0371c1134..51710aa52 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12146,7 +12146,8 @@ void K_KartUpdatePosition(player_t *player) /* except in FREE PLAY */ if (player->curshield == KSHIELD_TOP && (gametyperules & GTR_CIRCUIT) && - realplayers > 1) + realplayers > 1 && + !specialstageinfo.valid) { /* grace period so you don't fall off INSTANTLY */ if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount. From 99c5ebc109c4db742dcc57533ae91485802bdd20 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 14:33:53 -0400 Subject: [PATCH 32/64] Use sliptide super handling while flamedashing --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 0371c1134..4d67fea0e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11393,7 +11393,7 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue) // If you're sliptiding, don't interact with handling boosts. // You need turning power proportional to your speed, no matter what! fixed_t topspeed = K_GetKartSpeed(player, false, false); - if (K_Sliptiding(player)) + if (K_Sliptiding(player) || player->flamedash) { fixed_t sliptide_handle; From f46a2f83b4ed2962a116bfabff0e33436fa8b89c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 20:00:35 -0400 Subject: [PATCH 33/64] Check GTR_CIRCUIT for scams --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 613468100..672ae21bd 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -446,7 +446,7 @@ boolean K_IsPlayerScamming(player_t *player) if (!M_NotFreePlay()) return false; - if (gametyperules & GTR_SPHERES) + if (!(gametyperules & GTR_CIRCUIT)) return false; // "Why 8?" Consistency From 0e515f8688f50c672429448703f09e68defc235c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 20:02:50 -0400 Subject: [PATCH 34/64] Check map command for rulechange --- src/d_netcmd.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index aa7711d7f..618fc405b 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2529,7 +2529,7 @@ static void Command_Map_f(void) newforcespecialstage = COM_CheckParm("-forcespecialstage"); usingcheats = CV_CheatsEnabled(); - ischeating = (!(netgame || multiplayer)) || (!newresetplayers); + ischeating = (!(netgame || multiplayer)) || (!newresetplayers) || (!K_CanChangeRules(false)); if (!( first_option = COM_FirstOption() )) first_option = COM_Argc(); @@ -2635,9 +2635,6 @@ static void Command_Map_f(void) } } - if (grandprixinfo.gp) - ischeating = true; - if (ischeating && !usingcheats) { CONS_Printf(M_GetText("Cheats must be enabled.\n")); From ae0e872fa5c90dc38269e4caab84de0af14f317b Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 23 May 2025 20:06:16 -0400 Subject: [PATCH 35/64] Don't top shake in Cooperative either --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 51710aa52..3a45e01cf 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12147,7 +12147,8 @@ void K_KartUpdatePosition(player_t *player) if (player->curshield == KSHIELD_TOP && (gametyperules & GTR_CIRCUIT) && realplayers > 1 && - !specialstageinfo.valid) + !specialstageinfo.valid + && !K_Cooperative()) { /* grace period so you don't fall off INSTANTLY */ if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount. From a1a53d4e906e2a2d6b6787e0d8a136b6092132c8 Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Sat, 24 May 2025 15:52:54 -0400 Subject: [PATCH 36/64] Small uptune 65% -> 75% --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8d7f33877..c753e3cc9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3564,7 +3564,7 @@ static void K_GetKartBoostPower(player_t *player) Easing_InCubic( player->overdrivepower, 0, - 65*FRACUNIT/100 + 75*FRACUNIT/100 ), Easing_InSine( player->overdrivepower, From 0fc8077375ca466ac6b3463ef45b02136e460f2a Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 17:35:51 -0400 Subject: [PATCH 37/64] Duel uses internal 99 laps --- src/k_kart.c | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 94f5e9cef..5bde7b66c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -297,7 +297,7 @@ void K_TimerInit(void) } if (K_InRaceDuel()) - numlaps = 1; + numlaps = 99; } } @@ -9780,14 +9780,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->amppickup--; } - // ACHTUNG TEMPORARY FUCKUP. - // Disable skip protection in Race Duel because of distance jumps in infinite-lap contexts. - // This shouldn't exist at all in release 2.4, so this is probably fine, right...? - if (K_InRaceDuel()) - { - player->pflags |= PF_TRUSTWAYPOINTS; - } - // Don't tick down while in damage state. // There may be some maps where the timer activates for @@ -11081,28 +11073,15 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) } Z_Free(pathtofinish.array); - if (K_InRaceDuel() && player->position == 1) + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; + if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - // As far as we're concerned, the race starts and ends with our position. - // Don't care about laps at all! - } - else - { - // distancetofinish is currently a flat distance to the finish line, but in order to be fully - // correct we need to add to it the length of the entire circuit multiplied by the number of laps - // left after this one. This will give us the total distance to the finish line, and allow item - // distance calculation to work easily - const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; - if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) - { - UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; - if (K_InRaceDuel()) - { - player_t *opp = K_DuelOpponent(player); - numfulllapsleft = opp->laps - player->laps; - } - player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); - } + UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } } From 63659b126b7b499361197b6c94e365c37f25d102 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 20:23:08 -0400 Subject: [PATCH 38/64] Duel HUD --- src/k_hud.cpp | 190 +++++++++++++++++++++++++++++++++++++++++++++----- src/k_kart.c | 8 +-- 2 files changed, 175 insertions(+), 23 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index d0e6da19c..c2aa68999 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -235,6 +235,13 @@ static patch_t *kp_team_underlay[2][2]; static patch_t *kp_team_minihead; static patch_t *kp_team_you; +static patch_t *kp_duel_foe; +static patch_t *kp_duel_you; +static patch_t *kp_duel_sticker; +static patch_t *kp_duel_under; +static patch_t *kp_duel_over; +static patch_t *kp_duel_margin[6]; + patch_t *kp_autoroulette; patch_t *kp_autoring; @@ -1061,6 +1068,16 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_team_underlay[1][1], "TEAM4UR"); HU_UpdatePatch(&kp_team_minihead, "TEAM4H"); HU_UpdatePatch(&kp_team_you, "TEAM_YOU"); + + HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE"); + HU_UpdatePatch(&kp_duel_sticker, "DUEL_S"); + HU_UpdatePatch(&kp_duel_under, "DUEL_B"); + HU_UpdatePatch(&kp_duel_over, "DUEL_B2"); + HU_UpdatePatch(&kp_duel_you, "DUEL_YOU"); + for (i = 0; i < 6; i++) + { + HU_UpdatePatch(&kp_duel_margin[i], "DUELMB0%d", i); + } } // For the item toggle menu @@ -2970,6 +2987,9 @@ static boolean K_drawKartPositionFaces(void) if (!LUA_HudEnabled(hud_minirankings)) return false; // Don't proceed but still return true for free play above if HUD is disabled. + if (K_InRaceDuel()) + return false; + switch (r_splitscreen) { case 0: @@ -3229,11 +3249,155 @@ INT32 K_GetTransFlagFromFixed(fixed_t value) } } +static tic_t duel_lastleveltime = 0; +static INT32 youheight = 0; + +static void K_drawKartDuelScores(void) +{ + if (!K_InRaceDuel()) + return; + + using srb2::Draw; + + player_t *foe = K_DuelOpponent(stplyr); + + INT32 basex = 0; + INT32 basey = 40; + INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN; + + // score bars, here barheight is the size of bars at tied score + INT32 barx = 8; + INT32 bary = 61; + INT32 barheight = 48; + INT32 barwidth = 6; + + // portraits + INT32 foex = 16; + INT32 foey = 21; + INT32 youx = 16; + INT32 youy = 85; + + // scores + INT32 foescorex = 16; + INT32 foescorey = 38; + INT32 youscorex = 16; + INT32 youscorey = 69; + + Draw::Font scorefont = Draw::Font::kThinTimer; + + UINT8 ri = 6; + INT32 youfill = skincolors[stplyr->skincolor].ramp[ri]; + INT32 foefill = skincolors[foe->skincolor].ramp[ri]; + + INT32 margin = std::min(overtimecheckpoints, (UINT8)5); // Absolutely what the fuck kind of cast. + + V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker); + + INT32 scoredelta = stplyr->duelscore - foe->duelscore; + INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins... + INT32 savemargin = 3 + ((leveltime/2)%2); // ...minus a little bit. + + INT32 targetyouheight = barheight*abs(clutchscore+scoredelta)/clutchscore; + + if (targetyouheight == 0) + { + targetyouheight = savemargin; + } + else if (targetyouheight >= 2*barheight) + { + targetyouheight = 2*barheight - savemargin; + } + + if (leveltime != duel_lastleveltime) + { + if (targetyouheight > youheight) + youheight++; + else if (targetyouheight < youheight) + youheight--; + } + duel_lastleveltime = leveltime; + + INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight + + V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags); + V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight, youfill|flags); + + V_DrawScaledPatch(basex, basey, flags, kp_duel_under); + V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over); + V_DrawScaledPatch(basex, basey, flags, kp_duel_foe); + V_DrawScaledPatch(basex, basey, flags, kp_duel_you); + + Draw foenum = Draw(basex+foescorex, basey+foescorey).flags(flags).font(scorefont).align(Draw::Align::kLeft); + Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(Draw::Align::kLeft); + + if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value)) + { + if (foe->duelscore > stplyr->duelscore) + foenum = foenum.colorize(SKINCOLOR_GOLD); + else + younum = younum.colorize(SKINCOLOR_GOLD); + } + + foenum.text("{:01}", foe->duelscore%10); + younum.text("{:01}", stplyr->duelscore%10); + + // minirankings shamelessly copypasted because i know that shit works already + // and SURELY we will never need to use this somewhere else, right? + + UINT8 workingskin; + UINT8 *colormap; + INT32 xoff, yoff, flipflag, skinflags; + + for (UINT8 draw = 0; draw < 2; draw++) + { + UINT8 drawme = draw ? (stplyr - players) : (foe - players); + UINT8 drawx = basex + (draw ? youx : foex); + UINT8 drawy = basey + (draw ? youy : foey); + + if (!playeringame[drawme] || players[drawme].spectator) + continue; + + if (!players[drawme].mo || P_MobjWasRemoved(players[drawme].mo)) + continue; + + skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[drawme]].flags + : skins[players[drawme].skin].flags; + + // Flip SF_IRONMAN portraits, but only if they're transformed + if (skinflags & SF_IRONMAN + && !(players[drawme].charflags & SF_IRONMAN) ) + { + flipflag = V_FLIP|V_VFLIP; // blonic flip + xoff = yoff = 16; + } else + { + flipflag = 0; + xoff = yoff = 0; + } + + if ((skin_t*)players[drawme].mo->skin) + workingskin = (skin_t*)players[drawme].mo->skin - skins; + else + workingskin = players[drawme].skin; + + colormap = R_GetTranslationColormap(workingskin, static_cast(players[drawme].mo->color), GTC_CACHE); + if (players[drawme].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast(players[drawme].mo->color), GTC_CACHE); + else + colormap = R_GetTranslationColormap(workingskin, static_cast(players[drawme].mo->color), GTC_CACHE); + + V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap); + } + + V_DrawScaledPatch(basex, basey, flags, kp_duel_margin[margin]); +} + static INT32 easedallyscore = 0; static tic_t scorechangecooldown = 0; // Mildly ugly. Don't want to export this to khud when it's so nicely handled here, // but HUD hooks run at variable timing based on your actual framerate. -static tic_t lastleveltime = 0; +static tic_t teams_lastleveltime = 0; static void K_drawKartTeamScores(void) { @@ -3360,7 +3524,7 @@ static void K_drawKartTeamScores(void) } else { - if (lastleveltime != leveltime) // Timing consistency + if (teams_lastleveltime != leveltime) // Timing consistency { INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score? @@ -3387,7 +3551,7 @@ static void K_drawKartTeamScores(void) enemyscore = totalscore - allyscore; } - lastleveltime = leveltime; + teams_lastleveltime = leveltime; fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT); // fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT); @@ -3543,19 +3707,6 @@ static boolean K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. - if (K_InRaceDuel()) - { - UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; - if (leveltime % 2) - if (abs(stplyr->duelscore) >= 2) - flashflag = V_YELLOWMAP; - - if (stplyr->duelscore >= 0) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); - else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); - } - boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel() && displayEXP != UINT16_MAX); if (drawinglaps) @@ -7266,9 +7417,14 @@ void K_drawKartHUD(void) K_drawKartTeamScores(); } + if (K_InRaceDuel()) + { + K_drawKartDuelScores(); + } + if (LUA_HudEnabled(hud_gametypeinfo)) { - if (gametyperules & GTR_CIRCUIT) + if (gametyperules & GTR_CIRCUIT && !K_InRaceDuel()) { K_drawKartLaps(); gametypeinfoshown = true; diff --git a/src/k_kart.c b/src/k_kart.c index 5bde7b66c..afde50887 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4294,13 +4294,9 @@ void K_CheckpointCrossAward(player_t *player) S_StartSound(NULL, sfx_gsha6); } - for (UINT8 i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && &players[i] != player) - players[i].duelscore -= 1; - } + player_t *opp = K_DuelOpponent(player); - if (player->duelscore == DUELWINNINGSCORE) + if (player->duelscore - opp->duelscore == DUELWINNINGSCORE) { player_t *opp = K_DuelOpponent(player); opp->position = 2; From 3d39ab52251893e4c2b3a9db10624d256e24c9fd Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 21:19:33 -0400 Subject: [PATCH 39/64] more Duel --- src/cvars.cpp | 2 ++ src/d_netcmd.h | 1 + src/k_bot.cpp | 5 +++++ src/k_hud.cpp | 7 ++++--- src/k_kart.c | 3 +-- src/k_kart.h | 4 ++-- src/menus/options-gameplay-1.c | 11 +++++++++++ 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 2ea673fdc..0dccdcdb6 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -783,6 +783,8 @@ consvar_t cv_timelimit = UnsavedNetVar("timelimit", "Default").min_max(1, 30*60, consvar_t cv_votetime = UnsavedNetVar("votetime", "20").min_max(10, 3600); +consvar_t cv_dueltimelimit = UnsavedNetVar("dueltimelimit", "180").min_max(0, 3600); +consvar_t cv_duelscorelimit = UnsavedNetVar("duelscorelimit", "3").min_max(1, 9); // // Online cheats - synced in netgames. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index bff6fe264..50b5339d7 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -52,6 +52,7 @@ extern consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_pointlimit; extern consvar_t cv_timelimit; +extern consvar_t cv_dueltimelimit, cv_duelscorelimit; extern consvar_t cv_numlaps; extern UINT32 timelimitintics, extratimeintics, secretextratime; extern UINT32 g_pointlimit; diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 041a0be38..d1874eae1 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -2067,6 +2067,11 @@ void K_UpdateBotGameplayVars(player_t *player) if (cv_levelskull.value) player->botvars.difficulty = MAXBOTDIFFICULTY; + if (K_InRaceDuel()) + player->botvars.rival = true; + else if (grandprixinfo.gp != true) + player->botvars.rival = false; + player->botvars.rubberband = K_UpdateRubberband(player); player->botvars.turnconfirm += player->cmd.bot.turnconfirm; diff --git a/src/k_hud.cpp b/src/k_hud.cpp index c2aa68999..e9821e8f6 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3310,10 +3310,11 @@ static void K_drawKartDuelScores(void) if (leveltime != duel_lastleveltime) { + INT32 slide = std::max(1, abs(targetyouheight - youheight)/3); if (targetyouheight > youheight) - youheight++; + youheight += slide; else if (targetyouheight < youheight) - youheight--; + youheight -= slide; } duel_lastleveltime = leveltime; @@ -5028,7 +5029,7 @@ playertagtype_t K_WhichPlayerTag(player_t *p) } else if (p->bot) { - if (p->botvars.rival == true || cv_levelskull.value) + if ((p->botvars.rival == true || cv_levelskull.value) && (!K_InRaceDuel())) { return PLAYERTAG_RIVAL; } diff --git a/src/k_kart.c b/src/k_kart.c index afde50887..9177d36c4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4284,7 +4284,7 @@ void K_CheckpointCrossAward(player_t *player) { player->duelscore += 1; - if (leveltime > DUELOVERTIME) + if (leveltime > (tic_t)(TICRATE*DUELOVERTIME)) { overtimecheckpoints++; if (overtimecheckpoints > 1) @@ -4298,7 +4298,6 @@ void K_CheckpointCrossAward(player_t *player) if (player->duelscore - opp->duelscore == DUELWINNINGSCORE) { - player_t *opp = K_DuelOpponent(player); opp->position = 2; player->position = 1; diff --git a/src/k_kart.h b/src/k_kart.h index 1b817eb53..2b9ed82a9 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -80,8 +80,8 @@ Make sure this matches the actual number of states #define RINGVOLUMEREGEN 1 #define RINGTRANSPARENCYREGEN 3 -#define DUELOVERTIME (3*60*TICRATE) -#define DUELWINNINGSCORE (3) +#define DUELOVERTIME (cv_dueltimelimit.value) +#define DUELWINNINGSCORE (cv_duelscorelimit.value) #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) diff --git a/src/menus/options-gameplay-1.c b/src/menus/options-gameplay-1.c index 11398a6f4..7cf67ee3f 100644 --- a/src/menus/options-gameplay-1.c +++ b/src/menus/options-gameplay-1.c @@ -52,6 +52,17 @@ menuitem_t OPTIONS_Gameplay[] = NULL, {.cvar = &cv_kartbumpers}, 0, 0}, + + {IT_HEADER, "Duel...", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Duel Time Limit", "How long it takes for Margin Boost to kick in (seconds).", + NULL, {.cvar = &cv_dueltimelimit}, 0, 0}, + + {IT_STRING | IT_CVAR, "Duel Score Limit", "How many points a player must be ahead to win a Duel.", + NULL, {.cvar = &cv_duelscorelimit}, 0, 0}, + + {IT_SPACE | IT_DYBIGSPACE, NULL, NULL, NULL, {NULL}, 0, 0}, From 1b308d02787f32d26b607cae3fdec96f73c3f588 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 21:26:35 -0400 Subject: [PATCH 40/64] Perfect Round + SIGL Duel support --- src/m_cond.c | 5 ++++- src/p_mobj.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 5659205ee..d5901902e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1858,7 +1858,10 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && (gamespeed != KARTSPEED_EASY) && (player->tally.active == true) && (player->tally.totalExp > 0) // Only true if not Time Attack - && (player->tally.exp >= player->tally.totalExp)); + && ( + (player->tally.exp >= player->tally.totalExp) + || (K_InRaceDuel() && player->duelscore == DUELWINNINGSCORE) + ); case UCRP_FINISHALLPRISONS: return (battleprisons && !(player->pflags & PF_NOCONTEST) diff --git a/src/p_mobj.c b/src/p_mobj.c index d6782f179..9aacf8009 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9110,7 +9110,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj) && (gamespeed != KARTSPEED_EASY) && (newplayer->tally.active == true) && (newplayer->tally.totalExp > 0) // Only true if not Time Attack - && (newplayer->tally.exp >= newplayer->tally.totalExp) + && ( + (newplayer->tally.exp >= newplayer->tally.totalExp) || + (K_InRaceDuel() && newplayer->duelscore = DUELWINNINGSCORE) + ) ) { UINT8 pnum = (newplayer-players); From 79e4af5b8e7d0232978ab991f3f9358036e741d5 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 21:34:46 -0400 Subject: [PATCH 41/64] oops I'm a dipshit --- src/m_cond.c | 2 +- src/p_mobj.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index d5901902e..026dace10 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1861,7 +1861,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && ( (player->tally.exp >= player->tally.totalExp) || (K_InRaceDuel() && player->duelscore == DUELWINNINGSCORE) - ); + )); case UCRP_FINISHALLPRISONS: return (battleprisons && !(player->pflags & PF_NOCONTEST) diff --git a/src/p_mobj.c b/src/p_mobj.c index 9aacf8009..717cc6cca 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9112,7 +9112,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) && (newplayer->tally.totalExp > 0) // Only true if not Time Attack && ( (newplayer->tally.exp >= newplayer->tally.totalExp) || - (K_InRaceDuel() && newplayer->duelscore = DUELWINNINGSCORE) + (K_InRaceDuel() && newplayer->duelscore == DUELWINNINGSCORE) ) ) { From 05936d8b76af343b2f4945096cbf5184517d8740 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 01:29:37 -0400 Subject: [PATCH 42/64] Don't vibrate clutch scores all the time --- src/k_hud.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index e9821e8f6..0650e7729 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3295,7 +3295,13 @@ static void K_drawKartDuelScores(void) INT32 scoredelta = stplyr->duelscore - foe->duelscore; INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins... - INT32 savemargin = 3 + ((leveltime/2)%2); // ...minus a little bit. + INT32 savemargin = 3; // ...minus a little bit. + + if (leveltime/(TICRATE/2) % 2) + savemargin += ((leveltime/2)%2); + + if (clutchscore == 0) + clutchscore = 1; // Fuck it, just don't crash INT32 targetyouheight = barheight*abs(clutchscore+scoredelta)/clutchscore; From b9818d52e4723ae62c85429719399722bd0903c5 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 01:57:02 -0400 Subject: [PATCH 43/64] Duel sprint lockout, duel overtime darken --- src/g_game.c | 6 ++++++ src/k_kart.c | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index 24e4bd663..779bf2024 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3870,6 +3870,12 @@ tryAgain: continue; } + if (numPlayers == 2 && gametype == GT_RACE && ((mapheaderinfo[i]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE)) + { + // Duel doesn't support sprints. + continue; + } + // Only care about restrictions if the host is a listen server. if (!dedicated) { diff --git a/src/k_kart.c b/src/k_kart.c index 9177d36c4..b7188dd94 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4288,9 +4288,20 @@ void K_CheckpointCrossAward(player_t *player) { overtimecheckpoints++; if (overtimecheckpoints > 1) + { K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false); + } else + { K_AddMessage("Margin Boost!", true, false); + g_darkness.start = leveltime; + g_darkness.end = INT32_MAX; + for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + g_darkness.value[i] = FRACUNIT; + } + } + S_StartSound(NULL, sfx_gsha6); } From a434e760c5d307ff38629465eb9161178f502ee9 Mon Sep 17 00:00:00 2001 From: Lach Date: Sun, 25 May 2025 16:28:31 +1000 Subject: [PATCH 44/64] Add rank mode label to intermission tally screens (EXP/MOBIUMS) --- src/y_inter.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ src/y_inter.h | 1 + 2 files changed, 95 insertions(+) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index b0c0f64c1..4c4956551 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -951,6 +951,18 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) i--; } while (true); + + if (standings->rankingsmode) + { + if (standings->isduel) + { + Y_DrawRankMode(BASEVIDWIDTH / 2 + xoffset, BASEVIDHEIGHT - 19, true); + } + else + { + Y_DrawRankMode(x + 122, returny - yspacing + 7, false); + } + } } // @@ -1627,6 +1639,88 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree } } +// +// Y_DrawRankMode +// +// Draws EXP or MOBIUMS label depending on context. +// x and y designate the coordinates of the most bottom-right pixel to draw from (because it is the left extent and patch heights that vary), +// or the bottom-center if center is true. +// +void Y_DrawRankMode(INT32 x, INT32 y, boolean center) +{ + boolean useMobiums = (powertype != PWRLV_DISABLED); + INT32 textWidth, middleLeftEdge, middleRightEdge, middleWidth; + + char text[8]; + char iconPatchName[8]; + UINT8 iconWidth; // the graphic paddings are inconsistent... + UINT8 *iconColormap; + UINT8 *stickerColormap; + + patch_t *iconPatch; + patch_t *stickerTail = static_cast(W_CachePatchName("INT_STK1", PU_CACHE)); + patch_t *stickerMiddle = static_cast(W_CachePatchName("INT_STK2", PU_CACHE)); + patch_t *stickerHead = center ? stickerTail : static_cast(W_CachePatchName("INT_STK3", PU_CACHE)); + UINT32 stickerHeadFlags = 0; + UINT8 stickerHeadOffset = 0; + + if (useMobiums) + { + snprintf(text, sizeof text, "MOBIUMS"); + snprintf(iconPatchName, sizeof iconPatchName, "K_STMOB"); + iconWidth = 22; + iconColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast(SKINCOLOR_NONE), GTC_CACHE); + stickerColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast(SKINCOLOR_TEA), GTC_CACHE); + } + else + { + snprintf(text, sizeof text, "EXP"); + snprintf(iconPatchName, sizeof iconPatchName, "K_STEXP"); + iconWidth = 16; + iconColormap = R_GetTranslationColormap(TC_RAINBOW, static_cast(SKINCOLOR_MUSTARD), GTC_CACHE); + stickerColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast(SKINCOLOR_MUSTARD), GTC_CACHE); + } + + iconPatch = static_cast(W_CachePatchName(iconPatchName, PU_CACHE)); + textWidth = (INT32)V_ThinStringWidth(text, 0); + middleLeftEdge = x - iconWidth - textWidth - 8; + middleRightEdge = x - stickerHead->width; + middleWidth = middleRightEdge - middleLeftEdge; + + if (center) + { + // flip the right-hand sticker tail and keep it left-aligned + stickerHeadFlags |= V_FLIP; + stickerHeadOffset += stickerHead->width; + + // sliiightly extend the right side of the sticker + middleWidth += 2; + middleRightEdge += 2; + + // shift all components to the right so that our x coordinates are center-aligned + #define CENTER_SHIFT (stickerHead->width + middleWidth / 2) + x += CENTER_SHIFT; + middleLeftEdge += CENTER_SHIFT; + middleRightEdge += CENTER_SHIFT; + #undef CENTER_SHIFT + } + + // draw sticker + V_DrawMappedPatch(middleRightEdge + stickerHeadOffset, y - stickerHead->height, stickerHeadFlags, stickerHead, stickerColormap); + V_DrawStretchyFixedPatch( + middleLeftEdge << FRACBITS, + (y - stickerMiddle->height) << FRACBITS, + (middleWidth << FRACBITS) / stickerMiddle->width + 1, + FRACUNIT, + 0, stickerMiddle, stickerColormap + ); + V_DrawMappedPatch(middleLeftEdge - stickerTail->width, y - stickerTail->height, 0, stickerTail, stickerColormap); + + // draw icon and text + V_DrawMappedPatch(x - iconPatch->width - 6, y - iconPatch->height + 4, 0, iconPatch, iconColormap); + V_DrawThinString(middleLeftEdge - 1, y - 9, 0, text); +} + void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const char *headerstring, boolean showroundnum, boolean small) { const INT32 v_width = (small ? BASEVIDWIDTH/2 : BASEVIDWIDTH); diff --git a/src/y_inter.h b/src/y_inter.h index 572b4a6d1..de0fe7179 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -56,6 +56,7 @@ void Y_Ticker(void); void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset); void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen); void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen); +void Y_DrawRankMode(INT32 x, INT32 y, boolean center); void Y_StartIntermission(void); void Y_MidIntermission(void); From 938000cd7eb2adb37acfcfa3e248afd1e190bd9f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 03:01:18 -0400 Subject: [PATCH 45/64] Longer, more fair duels --- src/cvars.cpp | 2 +- src/p_spec.c | 40 ++++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 0dccdcdb6..6ed5b04c6 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -784,7 +784,7 @@ consvar_t cv_timelimit = UnsavedNetVar("timelimit", "Default").min_max(1, 30*60, consvar_t cv_votetime = UnsavedNetVar("votetime", "20").min_max(10, 3600); consvar_t cv_dueltimelimit = UnsavedNetVar("dueltimelimit", "180").min_max(0, 3600); -consvar_t cv_duelscorelimit = UnsavedNetVar("duelscorelimit", "3").min_max(1, 9); +consvar_t cv_duelscorelimit = UnsavedNetVar("duelscorelimit", "4").min_max(1, 9); // // Online cheats - synced in netgames. diff --git a/src/p_spec.c b/src/p_spec.c index 074819acb..fc91dfad4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2066,24 +2066,32 @@ static void K_HandleLapIncrement(player_t *player) if (rainbowstartavailable == true && player->mo->hitlag == 0) { - S_StartSound(player->mo, sfx_s23c); - player->startboost = 125; - - K_SpawnDriftBoostExplosion(player, 4); - K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo); - - if (g_teamplay) + if (K_InRaceDuel()) { - for (UINT8 j = 0; i < MAXPLAYERS; i++) + K_SpawnDriftElectricSparks(player, player->skincolor, false); + K_SpawnAmps(player, 20, player->mo); + } + else + { + S_StartSound(player->mo, sfx_s23c); + player->startboost = 125; + + K_SpawnDriftBoostExplosion(player, 4); + K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); + K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo); + + if (g_teamplay) { - if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo)) - continue; - if (!G_SameTeam(player, &players[j])) - continue; - if (player == &players[j]) - continue; - K_SpawnAmps(&players[j], 10, player->mo); + for (UINT8 j = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo)) + continue; + if (!G_SameTeam(player, &players[j])) + continue; + if (player == &players[j]) + continue; + K_SpawnAmps(&players[j], 10, player->mo); + } } } From d0a8d4df79440aa462294322b15743a66cc7ae02 Mon Sep 17 00:00:00 2001 From: Lach Date: Sun, 25 May 2025 17:05:34 +1000 Subject: [PATCH 46/64] Change "usepwrlv" cvar name to "mobiums" --- src/cvars.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 2ea673fdc..98df32ddb 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -728,7 +728,7 @@ consvar_t cv_kartspeed = UnsavedNetVar("gamespeed", "Auto Gear").values(kartspee consvar_t cv_teamplay = UnsavedNetVar("teamplay", "Off").on_off(); -consvar_t cv_kartusepwrlv = UnsavedNetVar("usepwrlv", "Yes").yes_no(); +consvar_t cv_kartusepwrlv = UnsavedNetVar("mobiums", "Yes").yes_no(); void LiveStudioAudience_OnChange(void); #ifdef DEVELOP From 0fc22e5fbde51a87fe7c3c2352108fbc1434fe95 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 25 May 2025 00:58:50 -0700 Subject: [PATCH 47/64] Preserve spectatewait between maps - Add spectatewait to G_PlayerReborn - This was making shuffeloser not work - Testing setup - `-server -splitscreen 4 +shuffleloser 1 +maxplayers 2 +numlaps 0 +debugstart 0 +inttime 0 +advancemap random` - Spectate two players, go to map --- src/g_game.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index 03af98262..a75134f5b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2262,6 +2262,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 kickstartaccel; INT32 checkpointId; boolean enteredGame; + tic_t spectatewait; UINT8 lastsafelap; UINT8 lastsafecheatcheck; UINT16 bigwaypointgap; @@ -2551,6 +2552,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) checkpointId = players[player].checkpointId; enteredGame = players[player].enteredGame; + spectatewait = players[player].spectatewait; p = &players[player]; memset(p, 0, sizeof (*p)); @@ -2624,6 +2626,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->karthud[khud_fault] = khudfault; p->kickstartaccel = kickstartaccel; p->checkpointId = checkpointId; + p->spectatewait = spectatewait; p->ringvolume = 255; p->ringtransparency = 255; From 212603838d6193e17394d5102803f5f3841617ab Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Sun, 25 May 2025 04:48:48 -0400 Subject: [PATCH 48/64] "use pwr.lv" into "use mobiums" The only other case I can think of, I'd like to leave "PWR" on the master server --- src/menus/options-server-1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/options-server-1.c b/src/menus/options-server-1.c index 877d0e223..a6640a6e6 100644 --- a/src/menus/options-server-1.c +++ b/src/menus/options-server-1.c @@ -39,7 +39,7 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "CPU Level", "Bots can fill unused slots. How strong should they be?", NULL, {.cvar = &cv_kartbot}, 0, 0}, - {IT_STRING | IT_CVAR, "Use PWR.LV", "Should players should be rated on their performance?", + {IT_STRING | IT_CVAR, "Use Mobiums", "Should players should be rated on their performance?", NULL, {.cvar = &cv_kartusepwrlv}, 0, 0}, {IT_STRING | IT_CVAR, "Antigrief Timer (seconds)", "How long can players stop progressing before they're removed?", From 3297933a09eda6f973e977fd2a72f414f8a5d084 Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Sun, 25 May 2025 05:35:15 -0400 Subject: [PATCH 49/64] Instawhip disjoint buff 95 -> 108 as a little treat --- src/info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/info.c b/src/info.c index c33e6b943..43563d151 100644 --- a/src/info.c +++ b/src/info.c @@ -13585,8 +13585,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_None, // deathsound 0, // speed - 95*FRACUNIT, // radius - 95*FRACUNIT, // height + 108*FRACUNIT, // radius + 80*FRACUNIT, // height 0, // display offset 100, // mass 0, // damage From 37f73562f85fa39dc5b514f84ea12779f29348d1 Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Sun, 25 May 2025 13:54:32 -0400 Subject: [PATCH 50/64] Whip height reduced 80 -> 50, to be right above the player. Keeps Prison Break sensible --- src/info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info.c b/src/info.c index 43563d151..59c252880 100644 --- a/src/info.c +++ b/src/info.c @@ -13586,7 +13586,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_None, // deathsound 0, // speed 108*FRACUNIT, // radius - 80*FRACUNIT, // height + 50*FRACUNIT, // height 0, // display offset 100, // mass 0, // damage From e9f4cf58fe10f96a8270e1f8efabebc82c351fae Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 15:22:23 -0400 Subject: [PATCH 51/64] SUPER UNTESTED DUEL PWR --- src/k_kart.c | 1 + src/k_pwrlv.c | 43 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index b7188dd94..aa1ab78c6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4283,6 +4283,7 @@ void K_CheckpointCrossAward(player_t *player) if (K_InRaceDuel() && player->position == 1) { player->duelscore += 1; + K_UpdatePowerLevels(player, player->laps, false); if (leveltime > (tic_t)(TICRATE*DUELOVERTIME)) { diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 6291d3a21..da04b3981 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -19,6 +19,7 @@ #include "k_grandprix.h" #include "k_profiles.h" #include "k_serverstats.h" +#include "k_kart.h" // K_InRaceDuel // Client-sided calculations done for Power Levels. // This is done so that clients will never be able to hack someone else's score over the server. @@ -225,6 +226,8 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) CONS_Debug(DBG_PWRLV, "%s's gametype score: %d\n", player_names[playerNum], yourScore); CONS_Debug(DBG_PWRLV, "========\n"); + + boolean dueling = K_InRaceDuel(); for (i = 0; i < MAXPLAYERS; i++) { UINT16 theirScore = 0; @@ -295,11 +298,18 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) } } - if (exitBonus == false) + if (dueling && !forfeit) { INT16 prevInc = inc; - inc /= max(numlaps-1, 1); + // Long duels mean players were closer. Less PWR changes hands when there's a lot of back-and-forth. + INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore; + INT32 divisor = DUELWINNINGSCORE; + + if (winnerscore > DUELWINNINGSCORE) // Opponent scored at least one point. + divisor += 2*(winnerscore - DUELWINNINGSCORE); + + inc /= divisor; if (inc == 0) { @@ -313,7 +323,30 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) } } - CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc); + CONS_Debug(DBG_PWRLV, "DUELING: Reduced (%d / %d = %d)\n", prevInc, divisor, inc); + } + else + { + if (exitBonus == false) + { + INT16 prevInc = inc; + + inc /= max(numlaps-1, 1); + + if (inc == 0) + { + if (prevInc > 0) + { + inc = 1; + } + else if (prevInc < 0) + { + inc = -1; + } + } + + CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc); + } } CONS_Debug(DBG_PWRLV, "========\n"); @@ -346,6 +379,10 @@ void K_UpdatePowerLevelsFinalize(player_t *player, boolean onForfeit) INT16 lapsLeft = 0; UINT8 i; + // No remaining laps in Duel. + if (K_InRaceDuel()) + return; + lapsLeft = (numlaps - player->latestlap) + 1; if (lapsLeft <= 0) From 2a03651e21b0e8f977497d10be241729db8af864 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 16:46:58 -0400 Subject: [PATCH 52/64] More PWR exchange, sounds --- src/k_kart.c | 24 ++++++++++++++++++++++++ src/k_pwrlv.c | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index aa1ab78c6..8978b1903 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4307,6 +4307,30 @@ void K_CheckpointCrossAward(player_t *player) } player_t *opp = K_DuelOpponent(player); + boolean clutch = (player->duelscore - opp->duelscore == (DUELWINNINGSCORE-1)); + boolean win = (player->duelscore - opp->duelscore == DUELWINNINGSCORE); + + if (!win) + { + for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + player_t *check = &players[displayplayers[i]]; + if (check == player) + { + S_StartSound(NULL, sfx_mbs45); + if (clutch) + S_StartSoundAtVolume(NULL, sfx_s3k9c, 170); + } + + else if (check == opp) + { + S_StartSound(NULL, sfx_s3k96); + if (clutch) + S_StartSound(NULL, sfx_s3kbes); + } + + } + } if (player->duelscore - opp->duelscore == DUELWINNINGSCORE) { diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index da04b3981..3571c5258 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -304,10 +304,10 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) // Long duels mean players were closer. Less PWR changes hands when there's a lot of back-and-forth. INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore; - INT32 divisor = DUELWINNINGSCORE; + INT32 divisor = 1; if (winnerscore > DUELWINNINGSCORE) // Opponent scored at least one point. - divisor += 2*(winnerscore - DUELWINNINGSCORE); + divisor += (winnerscore - DUELWINNINGSCORE); inc /= divisor; From 40029e5af67a02ec217db923ae865d602028f3af Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 16:52:43 -0400 Subject: [PATCH 53/64] Don't truncate duel scores in HUD --- src/k_hud.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 0650e7729..aa963501b 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3345,8 +3345,8 @@ static void K_drawKartDuelScores(void) younum = younum.colorize(SKINCOLOR_GOLD); } - foenum.text("{:01}", foe->duelscore%10); - younum.text("{:01}", stplyr->duelscore%10); + foenum.text("{}", foe->duelscore) + younum.text("{}", stplyr->duelscore); // minirankings shamelessly copypasted because i know that shit works already // and SURELY we will never need to use this somewhere else, right? From c085f0098f6d56bd192d722c6656ec2dd6ec1744 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 16:53:31 -0400 Subject: [PATCH 54/64] Oops --- src/k_hud.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index aa963501b..9719424a1 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3345,7 +3345,7 @@ static void K_drawKartDuelScores(void) younum = younum.colorize(SKINCOLOR_GOLD); } - foenum.text("{}", foe->duelscore) + foenum.text("{}", foe->duelscore); younum.text("{}", stplyr->duelscore); // minirankings shamelessly copypasted because i know that shit works already From 2975592a4d5bdcc1e0e470458fc0800c1e0e9df4 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 17:12:25 -0400 Subject: [PATCH 55/64] Sound revision --- src/k_kart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8978b1903..e44162d61 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4324,9 +4324,9 @@ void K_CheckpointCrossAward(player_t *player) else if (check == opp) { - S_StartSound(NULL, sfx_s3k96); + S_StartSound(NULL, sfx_mbs60); if (clutch) - S_StartSound(NULL, sfx_s3kbes); + S_StartSoundAtVolume(NULL, sfx_kc4b, 150); } } From 2898c5d60c02689388d48a1affb705b9330cc556 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 17:04:05 -0400 Subject: [PATCH 56/64] Warnings for checkpoints missing associated lines --- src/objects/checkpoint.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 36e1eec84..1ab035865 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -495,7 +495,21 @@ struct CheckpointManager else // Checkpoint isn't in the list, find any associated tagged lines and make the pair { if (chk->linetag()) - lines_.try_emplace(chk->linetag(), tagged_lines(chk->linetag())); + { + auto lines = tagged_lines(chk->linetag()); + if (lines.empty()) + { + CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has linetag %d, but no lines found. Please ensure all checkpoints have associated lines.\n", chk->spawnpoint - mapthings, chk->linetag()); + } + else + { + lines_.try_emplace(chk->linetag(), lines); + } + } + else + { + CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has no linetag. Please ensure all checkpoint things have a linetag.\n", chk->spawnpoint - mapthings, chk->spawnpoint->type); + } list_.push_front(chk); count_ += 1; // Mobjlist can't have a count on it, so we keep it here } From 0cdd4d1b07daaf0fcdb638abb333c59450dbb675 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 17:08:22 -0400 Subject: [PATCH 57/64] Increase intermission tally speed --- src/y_inter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 4c4956551..095484090 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -2175,7 +2175,7 @@ void Y_Ticker(void) // Basic bitch points if (data.increase[data.num[q]]) { - if (--data.increase[data.num[q]]) + if (std::max(0,data.increase[data.num[q]]-3)) kaching = false; } } From 9c61edbeec1b726d31df4135259e3681e514dbff Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 17:09:16 -0400 Subject: [PATCH 58/64] End of round grade adjustments, reduces weight of position on grade --- src/k_tally.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/k_tally.cpp b/src/k_tally.cpp index 877e39dd1..a73319f95 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -218,7 +218,7 @@ INT32 level_tally_t::CalculateGrade(void) } } - const INT32 positionWeight = (position > 0 && numPlayers > 2) ? 50 : 0; + const INT32 positionWeight = (position > 0 && numPlayers > 2) ? 20 : 0; const INT32 total = positionWeight + bonusWeights[0] + bonusWeights[1]; INT32 ours = 0; @@ -242,10 +242,7 @@ INT32 level_tally_t::CalculateGrade(void) } case TALLY_BONUS_EXP: { - // Use a special curve for this. - // Low Exp amounts are guaranteed, higher than half is where skill expression starts - // Magic numbers here are to reduce the range from 50-125 to 0-75 and compare with a max of 58, 85% of which is 49.3, which should put an even 100 or higher exp at A rank - const fixed_t frac = std::min(FRACUNIT, ((exp-50) * FRACUNIT) / std::max(1, static_cast(totalExp-42))); + const fixed_t frac = std::min(FRACUNIT, ((exp-15) * FRACUNIT) / std::max(1, static_cast(totalExp))); ours += Easing_Linear(frac, 0, bonusWeights[i]); break; } From 083cc4a44f878cd4ac792e0b0ba2b63482c8b1f1 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 17:12:54 -0400 Subject: [PATCH 59/64] Exp rework. Uses the entire valid range of gradingfactor to map to exp instead of clamping some range off. --- src/doomdef.h | 2 +- src/k_grandprix.cpp | 25 -------------------- src/k_hud.cpp | 4 +--- src/k_kart.c | 56 ++++++++++++++++++++++++++++++++++++++++----- src/k_kart.h | 4 ++-- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/doomdef.h b/src/doomdef.h index cef04507f..b62f44472 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -745,7 +745,7 @@ extern int #define MAXAMPSCALINGDIST 18000 // Exp -#define MINEXP 50 // The min value target +#define MINEXP 25 // The min value target #define TARGETEXP 100 // The target value needed for A rank #define MAXEXP 125 // The max value displayed by the hud and in the tally screen and GP results screen diff --git a/src/k_grandprix.cpp b/src/k_grandprix.cpp index d4b96a257..24a197f39 100644 --- a/src/k_grandprix.cpp +++ b/src/k_grandprix.cpp @@ -67,31 +67,6 @@ INT16 K_CalculateGPRankPoints(UINT16 exp, UINT8 position, UINT8 numplayers) points = exp; - // Give bonus to high-ranking players, depending on player count - // This rounds out the point gain when you get 1st every race, - // and gives bots able to catch up in points if a player gets an early lead. - // The maximum points you can get in a cup is: ((number of players - 1) + (max extra points)) * (number of races) - // 8P: (7 + 5) * 5 = 60 maximum points - // 12P: (11 + 5) * 5 = 80 maximum points - // 16P: (15 + 5) * 5 = 100 maximum points - switch (numplayers) - { - case 0: case 1: case 2: // 1v1 - break; // No bonus needed. - case 3: case 4: // 3-4P - if (position == 1) { points += 5; } // 1st gets +1 extra point - break; - case 5: case 6: // 5-6P - if (position == 1) { points += 10; } // 1st gets +3 extra points - // else if (position == 2) { points += 4; } // 2nd gets +1 extra point - break; - default: // Normal matches - if (position == 1) { points += 10; } // 1st gets +5 extra points - // else if (position == 2) { points += 5; } // 2nd gets +3 extra points - // else if (position == 3) { points += 2; } // 3rd gets +1 extra point - break; - } - // somehow underflowed? if (points < 0) { diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 616861b37..b4120c199 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -6715,10 +6715,8 @@ static void K_DrawGPRankDebugger(void) V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT, va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION)); - V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT, - va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints)); V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT, - va("LAPS: %d / %d", grandprixinfo.rank.exp, grandprixinfo.rank.totalExp)); + va("EXP: %d / %d", grandprixinfo.rank.exp, grandprixinfo.rank.totalExp)); V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT, va("CONTINUES: %d", grandprixinfo.rank.continuesUsed)); V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT, diff --git a/src/k_kart.c b/src/k_kart.c index 4e2f0ef35..855214220 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4242,7 +4242,7 @@ void K_CheckpointCrossAward(player_t *player) if (gametype != GT_RACE) return; - player->gradingfactor += K_GetGradingMultAdjustment(player); + player->gradingfactor += K_GetGradingFactorAdjustment(player); player->gradingpointnum++; player->exp = K_GetEXP(player); //CONS_Printf("player: %s factor: %.2f exp: %d\n", player_names[player-players], FIXED_TO_FLOAT(player->gradingfactor), player->exp); @@ -15527,7 +15527,7 @@ boolean K_PlayerCanUseItem(player_t *player) return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime); } -fixed_t K_GetGradingMultAdjustment(player_t *player) +fixed_t K_GetGradingFactorAdjustment(player_t *player) { fixed_t power = 3*FRACUNIT/100; // adjust to change overall xp volatility fixed_t stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain @@ -15581,13 +15581,57 @@ fixed_t K_GetGradingMultAdjustment(player_t *player) return result; } +fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max) +{ + // Create a dummy player structure for the theoretical last-place player + player_t dummy_player; + memset(&dummy_player, 0, sizeof(player_t)); + dummy_player.gradingfactor = FRACUNIT; // Start at 1.0 + + if (G_GametypeHasTeams()) + { + const UINT8 orange_count = G_CountTeam(TEAM_ORANGE); + const UINT8 blue_count = G_CountTeam(TEAM_BLUE); + if (orange_count <= blue_count) + { + dummy_player.team = TEAM_ORANGE; + } + else + { + dummy_player.team = TEAM_BLUE; + } + dummy_player.position = max ? 0 : D_NumPlayersInRace() + 1; // Ensures that all enemy players are counted, and our dummy won't overlap + } + else + { + dummy_player.position = max ? 1 : D_NumPlayersInRace(); + } + + // Apply the adjustment for each grading point + for (UINT32 i = 0; i < gradingpointnum; i++) + { + dummy_player.gradingfactor += K_GetGradingFactorAdjustment(&dummy_player); + } + return dummy_player.gradingfactor; +} + UINT16 K_GetEXP(player_t *player) { UINT32 numgradingpoints = K_GetNumGradingPoints(); - // target is where you should be if you're doing good and at a 1.0 mult - fixed_t clampedmult = max(FRACUNIT/2, min(FRACUNIT*5/4, player->gradingfactor)); // clamp between 0.5 and 1.25 - fixed_t targetexp = (TARGETEXP*player->gradingpointnum/max(1,numgradingpoints))<>FRACBITS; + UINT16 targetminexp = (MINEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a last place player should be at this stage of the race + UINT16 targetexp = (MAXEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a 1.0 factor should be at this stage of the race + fixed_t factormin = K_GetGradingFactorMinMax(player->gradingpointnum, false); + fixed_t factormax = K_GetGradingFactorMinMax(player->gradingpointnum, true); + fixed_t clampedfactor = max(factormin, min(factormax, player->gradingfactor)); + fixed_t range = factormax - factormin; + fixed_t normalizedfactor = FixedDiv(clampedfactor - factormin, range); + fixed_t easedexp = Easing_Linear(normalizedfactor, targetminexp, targetexp); + // fixed_t easedexp = Easing_Linear(normalizedfactor, MINEXP*FRACUNIT, MAXEXP*FRACUNIT); + UINT16 exp = easedexp; + // CONS_Printf("Player %s numgradingpoints=%d targetminexp=%d targetexp=%d factormin=%.2f factormax=%.2f clampedfactor=%.2f normalizedfactor=%.2f easedexp=%d\n", + // player_names[player - players], numgradingpoints, targetminexp, targetexp, FIXED_TO_FLOAT(factormin), FIXED_TO_FLOAT(factormax), + // FIXED_TO_FLOAT(clampedfactor), FIXED_TO_FLOAT(normalizedfactor), easedexp); + // UINT16 exp = (player->gradingfactor*100)>>FRACBITS; return exp; } diff --git a/src/k_kart.h b/src/k_kart.h index cd5dee152..a0fd8a1b9 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -310,8 +310,8 @@ boolean K_ThunderDome(void); boolean K_PlayerCanUseItem(player_t *player); -fixed_t K_GetGradingMultAdjustment(player_t *player); - +fixed_t K_GetGradingFactorAdjustment(player_t *player); +fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max); UINT16 K_GetEXP(player_t *player); UINT32 K_GetNumGradingPoints(void); From d079783e338b5b45d58aa93b4e536c5b531a7478 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 17:17:03 -0400 Subject: [PATCH 60/64] Various suspicions --- src/k_pwrlv.c | 2 +- src/p_spec.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 3571c5258..96c52aa28 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -298,7 +298,7 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) } } - if (dueling && !forfeit) + if (dueling) { INT16 prevInc = inc; diff --git a/src/p_spec.c b/src/p_spec.c index fc91dfad4..6b648bf1e 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2151,7 +2151,8 @@ static void K_HandleLapIncrement(player_t *player) player->laptime[LAP_CUR] = 0; // Update power levels for this lap. - K_UpdatePowerLevels(player, player->laps, false); + if (!K_InRaceDuel) // we do this in K_CheckpointCrossAward instead + K_UpdatePowerLevels(player, player->laps, false); K_CheckpointCrossAward(player); From 4b88ea04fd6427e973289e4a7ef9ead8ab97c05f Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 17:29:59 -0400 Subject: [PATCH 61/64] FIx warnings to not appear in tutorial --- src/objects/checkpoint.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 1ab035865..83b7ed0bf 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -497,7 +497,7 @@ struct CheckpointManager if (chk->linetag()) { auto lines = tagged_lines(chk->linetag()); - if (lines.empty()) + if (lines.empty() && gametype != GT_TUTORIAL) { CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has linetag %d, but no lines found. Please ensure all checkpoints have associated lines.\n", chk->spawnpoint - mapthings, chk->linetag()); } @@ -508,7 +508,10 @@ struct CheckpointManager } else { - CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has no linetag. Please ensure all checkpoint things have a linetag.\n", chk->spawnpoint - mapthings, chk->spawnpoint->type); + if (gametype != GT_TUTORIAL) + { + CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has no linetag. Please ensure all checkpoint things have a linetag.\n", chk->spawnpoint - mapthings); + } } list_.push_front(chk); count_ += 1; // Mobjlist can't have a count on it, so we keep it here From 10a694cc0b0a9733ea1a19598629e6ef356024a4 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 18:13:49 -0400 Subject: [PATCH 62/64] Disallow interacting with checkpoints after you finish --- src/objects/checkpoint.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 83b7ed0bf..97bba1cd5 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -584,6 +584,11 @@ void Obj_CheckpointThink(mobj_t* end) void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) { + if (player->exiting) // can't cross checkpoints when exiting + { + return; + } + LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius); auto it = std::find_if( From 066a8046147ad88c24d25b29bf3c70b8c9dca353 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 18:19:05 -0400 Subject: [PATCH 63/64] Fuck it, I'll rewrite parts of PWR later --- src/k_kart.c | 2 +- src/k_pwrlv.c | 24 ++++++++++++++---------- src/p_spec.c | 3 +-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index e44162d61..ff87ed2b7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4283,7 +4283,6 @@ void K_CheckpointCrossAward(player_t *player) if (K_InRaceDuel() && player->position == 1) { player->duelscore += 1; - K_UpdatePowerLevels(player, player->laps, false); if (leveltime > (tic_t)(TICRATE*DUELOVERTIME)) { @@ -11159,6 +11158,7 @@ static void K_UpdatePlayerWaypoints(player_t *const player) player->respawn.state == RESPAWNST_NONE && // Respawning should be a full reset. old_currentwaypoint != NULL && // So should touching the first waypoint ever. player->laps != 0 && // POSITION rooms may have unorthodox waypoints to guide bots. + player->exiting == 0 && // What the fuck? Why do duels antiskip the bot? !(player->pflags & PF_TRUSTWAYPOINTS)) // Special exception. { extern consvar_t cv_debuglapcheat; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 96c52aa28..62c257952 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -214,6 +214,10 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) CONS_Debug(DBG_PWRLV, "========\n"); yourPower = clientpowerlevels[playerNum][powerType]; + + if (K_InRaceDuel()) + yourPower += clientPowerAdd[playerNum]; + if (yourPower == 0) { // Guests don't record power level changes. @@ -257,6 +261,9 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) CONS_Debug(DBG_PWRLV, "%s VS %s:\n", player_names[playerNum], player_names[i]); theirPower = clientpowerlevels[i][powerType]; + if (K_InRaceDuel()) + theirPower += clientPowerAdd[i]; + if (theirPower == 0) { // No power level (splitscreen guests, bots) @@ -302,14 +309,9 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) { INT16 prevInc = inc; - // Long duels mean players were closer. Less PWR changes hands when there's a lot of back-and-forth. - INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore; - INT32 divisor = 1; - - if (winnerscore > DUELWINNINGSCORE) // Opponent scored at least one point. - divisor += (winnerscore - DUELWINNINGSCORE); - - inc /= divisor; + // INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore; + INT32 multiplier = 2; + inc *= multiplier; if (inc == 0) { @@ -323,7 +325,9 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) } } - CONS_Debug(DBG_PWRLV, "DUELING: Reduced (%d / %d = %d)\n", prevInc, divisor, inc); + // CONS_Printf("%s PWR UPDATE: %d\n", player_names[player - players], inc); + + CONS_Debug(DBG_PWRLV, "DUELING: Boosted (%d * %d = %d)\n", prevInc, multiplier, inc); } else { @@ -431,7 +435,7 @@ INT16 K_FinalPowerIncrement(player_t *player, INT16 yourPower, INT16 baseInc) if (inc <= 0) { - if (player->position == 1 && numPlayers > 1) + if (player->position == 1 && numPlayers > 1 && !(K_InRaceDuel())) { // Won the whole match? // Get at least one point. diff --git a/src/p_spec.c b/src/p_spec.c index 6b648bf1e..fc91dfad4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2151,8 +2151,7 @@ static void K_HandleLapIncrement(player_t *player) player->laptime[LAP_CUR] = 0; // Update power levels for this lap. - if (!K_InRaceDuel) // we do this in K_CheckpointCrossAward instead - K_UpdatePowerLevels(player, player->laps, false); + K_UpdatePowerLevels(player, player->laps, false); K_CheckpointCrossAward(player); From 6e49378bc857a344c5d4edbe03ca16dbc03eb203 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 25 May 2025 18:30:40 -0400 Subject: [PATCH 64/64] Fix botched tally rate increase --- src/y_inter.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 095484090..3857b37a3 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -2175,7 +2175,9 @@ void Y_Ticker(void) // Basic bitch points if (data.increase[data.num[q]]) { - if (std::max(0,data.increase[data.num[q]]-3)) + data.increase[data.num[q]] = std::max(data.increase[data.num[q]] - 3, 0); + + if (data.increase[data.num[q]] != 0) kaching = false; } }