From 9b63fb40786020303a890a0bcf1fa8fa614746c1 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 2 Sep 2024 04:20:04 -0400 Subject: [PATCH 001/107] 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 002/107] 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 003/107] 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 004/107] 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 005/107] 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 006/107] 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 007/107] 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 008/107] 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 009/107] 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 010/107] 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 011/107] 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 012/107] 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 013/107] 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 014/107] 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 015/107] 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 016/107] 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 017/107] 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 018/107] 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 019/107] 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 020/107] 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 021/107] 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 022/107] 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 023/107] 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 024/107] 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 025/107] 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 026/107] 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 027/107] 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 028/107] 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 029/107] 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 030/107] 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 031/107] 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 032/107] 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 033/107] 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 034/107] 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 035/107] 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 036/107] 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 037/107] 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 038/107] 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 039/107] 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 040/107] 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 041/107] 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 042/107] 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 043/107] 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 044/107] 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 045/107] 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 046/107] 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 047/107] 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 048/107] "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 049/107] 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 050/107] 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 051/107] 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 052/107] 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 053/107] 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 054/107] 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 055/107] 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 056/107] 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 057/107] 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 058/107] 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 059/107] 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 060/107] 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 061/107] 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 062/107] 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 063/107] 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 064/107] 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; } } From da6a0ae48becfd123396ce65cf4a465d869c2fe9 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 25 May 2025 21:47:31 -0700 Subject: [PATCH 065/107] srb2::Vector: fix copy assignment not clearing contents of vector - Test - debugwaypoints - very easily observable, all labels are the same without this fix - ACS text would sometimes be reversed without this fix, not as easily observable --- src/core/vector.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/vector.hpp b/src/core/vector.hpp index e6d66f91f..ec26334ea 100644 --- a/src/core/vector.hpp +++ b/src/core/vector.hpp @@ -149,6 +149,7 @@ public: Vector& operator=(const Vector& rhs) { + clear(); for (auto itr = rhs.begin(); itr != rhs.end(); itr++) { push_back(*itr); From 5e7cce9047e774e2b3ec93c7fce329b6f1528ef1 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 May 2025 15:22:30 +0100 Subject: [PATCH 066/107] CallFunc_SetThingProperty: Get next mobj in TID chain at start of loop In cases where state/property set can cause instant deletion, definitely interrupts FindMobjFromTID iteration after one step and potentially uses after free Also adds comment warnings to this effect near ways to find P_FindMobjFromTID, and updates P_ProcessSpecial even though we could probably stand to rip it out now --- src/acs/call-funcs.cpp | 8 ++++---- src/p_local.h | 3 +++ src/p_mobj.c | 2 ++ src/p_spec.c | 7 ++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 47502cb0f..8e598bef8 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -3589,13 +3589,15 @@ bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A INT32 value = 0; tag = argV[0]; - mobj = P_FindMobjFromTID(tag, mobj, info->mo); + mobj_t *next = P_FindMobjFromTID(tag, mobj, info->mo); property = argV[1]; value = argV[2]; - while (mobj != NULL) + while ((mobj = next) != NULL) { + // First in case of deletion. (Can't check for value == S_NULL because of A_ calls, etc) + next = P_FindMobjFromTID(tag, mobj, info->mo); #define PROP_READONLY(x, y) \ case x: \ @@ -3830,8 +3832,6 @@ bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A } } - mobj = P_FindMobjFromTID(tag, mobj, info->mo); - #undef PROP_FLAGS #undef PROP_SCALE #undef PROP_MOBJ diff --git a/src/p_local.h b/src/p_local.h index 505ccd8e0..af4f722bf 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -612,6 +612,9 @@ void P_InitTIDHash(void); void P_AddThingTID(mobj_t *mo); void P_RemoveThingTID(mobj_t *mo); void P_SetThingTID(mobj_t *mo, mtag_t tid); + +// This function cannot be safely called after *i is removed! +// Please call at start of loops if *i is to be mutated mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator); void P_DeleteMobjStringArgs(mobj_t *mobj); diff --git a/src/p_mobj.c b/src/p_mobj.c index 717cc6cca..98d377637 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -15417,6 +15417,8 @@ void P_SetThingTID(mobj_t *mo, mtag_t tid) // // P_FindMobjFromTID // Mobj tag search function. +// This function cannot be safely called after *i is removed! +// Please call at start of loops if *i is to be mutated // mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator) { diff --git a/src/p_spec.c b/src/p_spec.c index fc91dfad4..eb30cc265 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -3275,8 +3275,13 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha return false; } - while ((targetThing = P_FindMobjFromTID(args[1], targetThing, mo)) != NULL) + mobj_t *next = P_FindMobjFromTID(args[1], targetThing, mo); + + while ((targetThing = next) != NULL) { + // First in case of deletion. (Can't check for state == S_NULL because of A_ calls, etc) + next = P_FindMobjFromTID(args[1], targetThing, mo); + if (targetThing->player != NULL) { continue; From 5ec9d4491566ad43d696adb4afb327d3cd04236f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 26 May 2025 15:59:23 -0400 Subject: [PATCH 067/107] Margin Boost refinements --- src/k_hud.cpp | 22 +++++++++++++++++++--- src/k_kart.c | 10 +++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 0fe61fafa..56875b8d7 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3289,8 +3289,6 @@ static void K_drawKartDuelScores(void) 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; @@ -3397,7 +3395,25 @@ static void K_drawKartDuelScores(void) V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap); } - V_DrawScaledPatch(basex, basey, flags, kp_duel_margin[margin]); + UINT8 MARGINLEVELS = 6; + INT32 marginx = 0; + INT32 marginoffset = 6; + + INT32 margin = 1 + overtimecheckpoints; + // margin = ((leveltime/10)%25)+1; // debug + + INT32 margindigits = 1 + (margin-1)/MARGINLEVELS; + + marginx -= (margindigits-1) * (marginoffset/2); + + while (margindigits) + { + V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[std::min(margin-1, MARGINLEVELS-1)]); + + margindigits--; + margin -= MARGINLEVELS; + marginx += marginoffset; + } } static INT32 easedallyscore = 0; diff --git a/src/k_kart.c b/src/k_kart.c index d3a183097..2fb84c986 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -497,7 +497,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1<drift != 0 && P_IsObjectOnGround(player->mo)) { if (G_CompatLevel(0x000A)) From 29234d8f20f0450c153e1711c1a83f27e99d3aa7 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 26 May 2025 14:45:55 -0700 Subject: [PATCH 068/107] Add A_GenericBumper, hardcoded generic version of some bumper object scripts --- src/deh_tables.c | 1 + src/info.h | 2 ++ src/p_enemy.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ src/p_map.c | 14 ++++++++++++- 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 2d92aac06..e115287c8 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -297,6 +297,7 @@ actionpointer_t actionpointers[] = {{A_MakeSSCandle}, "A_MAKESSCANDLE"}, {{A_HologramRandomTranslucency}, "A_HOLOGRAMRANDOMTRANSLUCENCY"}, {{A_SSChainShatter}, "A_SSCHAINSHATTER"}, + {{A_GenericBumper}, "A_GENERICBUMPER"}, {{NULL}, "NONE"}, diff --git a/src/info.h b/src/info.h index d7495e852..2be47f164 100644 --- a/src/info.h +++ b/src/info.h @@ -289,6 +289,7 @@ enum actionnum A_MAKESSCANDLE, A_HOLOGRAMRANDOMTRANSLUCENCY, A_SSCHAINSHATTER, + A_GENERICBUMPER, NUMACTIONS }; @@ -557,6 +558,7 @@ void A_BlendEyePuyoHack(); void A_MakeSSCandle(); void A_HologramRandomTranslucency(); void A_SSChainShatter(); +void A_GenericBumper(); extern boolean actionsoverridden[NUMACTIONS]; diff --git a/src/p_enemy.c b/src/p_enemy.c index de957c0c4..3006b7369 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -321,6 +321,7 @@ void A_BlendEyePuyoHack(mobj_t *actor); void A_MakeSSCandle(mobj_t *actor); void A_HologramRandomTranslucency(mobj_t *actor); void A_SSChainShatter(mobj_t *actor); +void A_GenericBumper(mobj_t *actor); //for p_enemy.c @@ -12668,3 +12669,54 @@ void A_SSChainShatter(mobj_t* actor) actor->fuse = 1; } + +// var1 = If -1, triggered by collision event +// var2 = Strength value +// +// mobjinfo dependencies: +// - deathsound - bumper noise +// - seestate - bumper flashing state +// +void A_GenericBumper(mobj_t* actor) +{ + if (var1 != -1) + return; + + mobj_t *other = actor->target; + + if (!other) + return; + + // This code was ported from Lua + // Original was Balloon Park's bumpers? + INT32 hang = R_PointToAngle2( + actor->x, actor->y, + other->x, other->y + ); + + INT32 vang = 0; + + if (!P_IsObjectOnGround(other)) + { + vang = R_PointToAngle2( + FixedHypot(actor->x, actor->y), actor->z + (actor->height / 2), + FixedHypot(other->x, other->y), other->z + (other->height / 2) + ); + } + + INT32 baseStrength = abs(astate->var2); + fixed_t strength = (baseStrength * actor->scale) / 2; + + other->momx = FixedMul(FixedMul(strength, FCOS(hang)), abs(FCOS(vang))); + other->momy = FixedMul(FixedMul(strength, FSIN(hang)), abs(FCOS(vang))); + other->momz = FixedMul(strength, FSIN(vang)); + + if (other->player) + K_SetTireGrease(other->player, max(other->player->tiregrease, 2*TICRATE)); + + if (actor->state != &states[actor->info->seestate]) + { + S_StartSound(actor, actor->info->deathsound); + P_SetMobjState(actor, actor->info->seestate); + } +} diff --git a/src/p_map.c b/src/p_map.c index bdec595d7..07090967f 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1552,7 +1552,19 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) if (!K_PuntCollide(thing, g_tm.thing)) { - K_KartSolidBounce(g_tm.thing, thing); + state_t *st = &states[thing->info->spawnstate]; + + if (st->action.acp1 == A_GenericBumper) + { + P_SetTarget(&thing->target, g_tm.thing); + + var1 = -1; + var2 = 0; + astate = st; + st->action.acp1(thing); + } + else + K_KartSolidBounce(g_tm.thing, thing); } return BMIT_CONTINUE; } From 465387c69e2966698485f8860070a75ef12106a3 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 26 May 2025 14:56:44 -0700 Subject: [PATCH 069/107] MT_SEALEDSTAR_BUMPER: use A_GenericBumper --- src/info.c | 4 +-- src/k_objects.h | 1 - src/objects/sealed-star.c | 52 --------------------------------------- src/p_inter.c | 4 --- 4 files changed, 2 insertions(+), 59 deletions(-) diff --git a/src/info.c b/src/info.c index 59c252880..d017dda40 100644 --- a/src/info.c +++ b/src/info.c @@ -3640,7 +3640,7 @@ state_t states[NUMSTATES] = {SPR_S_SP, FF_ANIMATE|FF_SEMIBRIGHT, -1, {NULL}, 3, 2, S_NULL}, // S_SLSTMACE // MT_SEALEDSTAR_BUMPER - {SPR_SBMP, 0|FF_FULLBRIGHT, -1, {NULL}, 2, 8, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPER + {SPR_SBMP, 0|FF_FULLBRIGHT, -1, {A_GenericBumper}, 0, 56, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPER {SPR_SBMP, 1|FF_ANIMATE|FF_FULLBRIGHT, 8, {NULL}, 1, 2, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPERHIT // MT_SSCHAIN_SPAWNER @@ -22233,7 +22233,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 0, // mass 0, // damage sfx_None, // activesound - MF_SPECIAL|MF_NOGRAVITY, // flags + MF_NOGRAVITY|MF_SOLID, // flags S_NULL // raisestate }, { // MT_SSCHAIN_SPAWNER diff --git a/src/k_objects.h b/src/k_objects.h index cbdb189a7..c6cc64db9 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -411,7 +411,6 @@ void Obj_SSGobletMobjThink(mobj_t* mo); void Obj_SSLampMapThingSpawn(mobj_t* mo, mapthing_t* mt); void Obj_SSWindowMapThingSpawn(mobj_t* mo, mapthing_t* mt); void Obj_SLSTMaceMobjThink(mobj_t* mo); -void Obj_SSBumperTouchSpecial(mobj_t* special, mobj_t* toucher); void Obj_SSBumperMobjSpawn(mobj_t* mo); void Obj_SSChainMobjThink(mobj_t* mo); void Obj_SSGachaTargetMobjSpawn(mobj_t* mo); diff --git a/src/objects/sealed-star.c b/src/objects/sealed-star.c index ccf136800..2ee16a6fd 100644 --- a/src/objects/sealed-star.c +++ b/src/objects/sealed-star.c @@ -542,58 +542,6 @@ void Obj_SLSTMaceMobjThink(mobj_t* mo) } } -#define BUMPER_STRENGTH (56) - -void Obj_SSBumperTouchSpecial(mobj_t* special, mobj_t* toucher) -{ - angle_t hang; - angle_t vang; - fixed_t str; - int i; - - hang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y); - vang = 0; - - if (P_IsObjectOnGround(toucher) == false) - { - vang = R_PointToAngle2( - FixedHypot(special->x, special->y), special->z + (special->height >> 1), - FixedHypot(toucher->x, toucher->y), toucher->z + (toucher->height >> 1) - ); - } - - str = (BUMPER_STRENGTH * special->scale) >> 1; - - toucher->momx = FixedMul(FixedMul(str, FCOS(hang)), abs(FCOS(vang))); - toucher->momy = FixedMul(FixedMul(str, FSIN(hang)), abs(FCOS(vang))); - toucher->momz = FixedMul(str, FSIN(vang)); - - if (toucher->player) - { - if (toucher->player->tiregrease == 0) - { - for (i = 0; i < 2; i++) - { - mobj_t *grease = P_SpawnMobjFromMobj(toucher, 0, 0, 0, MT_TIREGREASE); - P_SetTarget(&grease->target, toucher); - grease->angle = toucher->angle; - grease->extravalue1 = i; - } - } - - if (toucher->player->tiregrease < 2*TICRATE) // greasetics - { - toucher->player->tiregrease = 2*TICRATE; - } - } - - if (special->state != &states[special->info->seestate]) - { - S_StartSound(special, special->info->deathsound); - P_SetMobjState(special, special->info->seestate); - } -} - void Obj_SSBumperMobjSpawn(mobj_t* mo) { mo->shadowscale = FRACUNIT; diff --git a/src/p_inter.c b/src/p_inter.c index 2df953dec..ff2b90ed1 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1083,10 +1083,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_TrickBalloonTouchSpecial(special, toucher); return; - case MT_SEALEDSTAR_BUMPER: - Obj_SSBumperTouchSpecial(special, toucher); - return; - case MT_PULLUPHOOK: Obj_PulleyHookTouch(special, toucher); return; From 0ae5ca334135eb1876c39213787c1e543e36ec14 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 03:38:04 -0400 Subject: [PATCH 070/107] Fix weird inconsistent initialization of cangrabitems maybe??? I took a shower and I want to go to bed --- src/g_game.c | 11 ++--------- src/k_kart.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index df5595b99..6498d417a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2352,8 +2352,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bot = players[player].bot; botdifficulty = players[player].botvars.difficulty; - cangrabitems = players[player].cangrabitems; - botdiffincrease = players[player].botvars.diffincrease; botrival = players[player].botvars.rival; @@ -2439,13 +2437,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) tallyactive = false; cangrabitems = 0; - if (gametyperules & GTR_SPHERES - || gametyperules & GTR_CATCHER - || G_TimeAttackStart() - || gametype == GT_TUTORIAL - || !M_NotFreePlay() - || K_GetNumWaypoints() == 0) - cangrabitems = EARLY_ITEM_FLICKER; } else { @@ -2501,6 +2492,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) { tally = players[player].tally; } + + cangrabitems = players[player].cangrabitems; } spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry); diff --git a/src/k_kart.c b/src/k_kart.c index 2fb84c986..566bcf17e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9781,6 +9781,21 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } + // The precise ordering of start-of-level made me want to cut my head off, + // so let's try this instead. Whatever! + if (leveltime < introtime) + { + if ((gametyperules & GTR_SPHERES) + || (gametyperules & GTR_CATCHER) + || G_TimeAttackStart() + || (gametype == GT_TUTORIAL) + || !M_NotFreePlay() + || (K_GetNumWaypoints() == 0)) + player->cangrabitems = EARLY_ITEM_FLICKER; + else + player->cangrabitems = 0; + } + if (player->cangrabitems && player->cangrabitems <= EARLY_ITEM_FLICKER) player->cangrabitems++; From 9dfdacb7c569bded57c46a34f71833716d5b0ff4 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 15:00:32 -0400 Subject: [PATCH 071/107] Always update match stats even in PWRLV_DISABLED --- src/y_inter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 6d07a8aea..b2159285a 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -2544,9 +2544,10 @@ void Y_StartIntermission(void) } K_CashInPowerLevels(); - SV_BumpMatchStats(); } + SV_BumpMatchStats(); + if (!timer) { Y_EndIntermission(); From e0ec684539d4a5e380840dcc30a42c7fe97f3aa1 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 15:36:11 -0400 Subject: [PATCH 072/107] Scale flame shield to player when ticking --- src/objects/flame-shield.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objects/flame-shield.cpp b/src/objects/flame-shield.cpp index bed96c97a..df8d5b255 100644 --- a/src/objects/flame-shield.cpp +++ b/src/objects/flame-shield.cpp @@ -60,6 +60,7 @@ struct Visual : Mobj } move_origin(shield()->pos()); + scale(5 * shield()->follow()->scale() / 4); renderflags = (renderflags & ~RF_DONTDRAW) | (shield()->state()->num() == S_INVISIBLE ? 0 : RF_DONTDRAW); From 2ba7310a1000775d19a04c93d993b18be879f048 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 16:16:44 -0400 Subject: [PATCH 073/107] EAT SHIT --- 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 566bcf17e..c453f6558 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9783,7 +9783,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // The precise ordering of start-of-level made me want to cut my head off, // so let's try this instead. Whatever! - if (leveltime < introtime) + if (leveltime <= introtime) { if ((gametyperules & GTR_SPHERES) || (gametyperules & GTR_CATCHER) From 38e4fcf911a413084d385576ac0a84533f6c470c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 16:25:33 -0400 Subject: [PATCH 074/107] Fix lightning while I'm here --- src/objects/lightning-shield.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objects/lightning-shield.cpp b/src/objects/lightning-shield.cpp index e08e4529c..3c80e1b70 100644 --- a/src/objects/lightning-shield.cpp +++ b/src/objects/lightning-shield.cpp @@ -55,6 +55,7 @@ struct Visual : Mobj } move_origin(shield()->pos()); + scale(5 * shield()->follow()->scale() / 4); dispoffset = state()->num() == S_THNB1 ? -1 : 1; return true; From 628d9cd1271e7e2fd0d5e119782ebda64da2fbbb Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 20:46:37 -0400 Subject: [PATCH 075/107] Height check Lightning Shield attack --- src/k_collide.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 4e1a3451b..0cc1d2cc9 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -777,6 +777,12 @@ static inline BlockItReturn_t PIT_LightningShieldAttack(mobj_t *thing) return BMIT_CONTINUE; } + // see if it went over / under + if (lightningSource->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (lightningSource->z + lightningSource->height < thing->z) + return BMIT_CONTINUE; // underneath + #if 0 if (P_CheckSight(lightningSource, thing) == false) { From ea7a7ea7e6fa440499616d59b6d8c2f23d17c019 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 20:54:45 -0400 Subject: [PATCH 076/107] Allow viewing enemy team in replays --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index df5595b99..e5d96a138 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1556,7 +1556,7 @@ boolean G_CouldView(INT32 playernum) return false; // SRB2Kart: we have no team-based modes, YET... - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() && !demo.playback) { if (players[consoleplayer].spectator == false && player->team != players[consoleplayer].team) return false; From a333806da4efca654f4bf5a0e97338d620b6cbc0 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 21:27:55 -0400 Subject: [PATCH 077/107] Fix 4P accessibility icons (close enough) --- src/k_hud.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 56875b8d7..c134279fa 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3718,6 +3718,11 @@ static void K_drawKartTeamScores(void) */ } +static boolean K_DrawingLaps() +{ + return (numlaps != 1 && !K_InRaceDuel() && (UINT16)stplyr->exp != UINT16_MAX); +} + static boolean K_drawKartLaps(void) { INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; @@ -4157,7 +4162,16 @@ static void K_drawKartAccessibilityIcons(boolean gametypeinfoshown, INT32 fx) } else { - fx = LAPS_X+44; + fx = LAPS_X + 33; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fx -= 14; + + if (K_DrawingLaps()) + { + fx += 30; + } + fy = LAPS_Y; if (R_GetViewNumber() & 1) // If we are not P1 or P3... { From 7ea9945769238ac406839881aa82dcf08b3e4650 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 21:39:25 -0400 Subject: [PATCH 078/107] Cubic Lightning --- src/k_collide.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 0cc1d2cc9..4abe3f269 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -778,9 +778,9 @@ static inline BlockItReturn_t PIT_LightningShieldAttack(mobj_t *thing) } // see if it went over / under - if (lightningSource->z > thing->z + thing->height) + if (lightningSource->z - lightningDist > thing->z + thing->height) return BMIT_CONTINUE; // overhead - if (lightningSource->z + lightningSource->height < thing->z) + if (lightningSource->z + lightningSource->height + lightningDist < thing->z) return BMIT_CONTINUE; // underneath #if 0 From 045d561e4b3fce8f796c348950083fdf5f3c9a4a Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 27 May 2025 23:52:26 -0400 Subject: [PATCH 079/107] Fix zero division in unusual teams situations --- src/k_hud.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 56875b8d7..976d74c00 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3496,6 +3496,9 @@ static void K_drawKartTeamScores(void) UINT16 enemyscore = g_teamscores[enemies]; UINT16 totalscore = allyscore + enemyscore; + if (totalscore == 0) + return; + using srb2::Draw; srb2::Draw::Font scorefont = Draw::Font::kTimer; @@ -3551,7 +3554,7 @@ static void K_drawKartTeamScores(void) { INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score? - if (scorechangecooldown == 0) + if (scorechangecooldown == 0 && delta) { if (allyscore > easedallyscore) { From e75ffd0707156201c0cfc3d1727d7c41da4d2c41 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 28 May 2025 15:20:06 -0400 Subject: [PATCH 080/107] Save team assignments in demo (DEMO BREAKER) --- src/g_demo.cpp | 6 ++++++ src/g_game.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 88828fee6..e60e1e759 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -2368,6 +2368,8 @@ void G_BeginRecording(void) WRITEUINT8(demobuf.p, player->skin); WRITEUINT8(demobuf.p, player->lastfakeskin); + WRITEUINT8(demobuf.p, player->team); + // Color demobuf.p += copy_fixed_buf(demobuf.p, skincolors[player->skincolor].name, g_buffer_sizes.color_name); @@ -3500,6 +3502,8 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) demo.currentskinid[p] = 0; lastfakeskin[p] = READUINT8(demobuf.p); + players[p].team = READUINT8(demobuf.p); + // Color demobuf.p += copy_fixed_buf(color, demobuf.p, g_buffer_sizes.color_name); for (i = 0; i < numskincolors; i++) @@ -3783,6 +3787,8 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) ghskin = &skins[skinlist[i].mapping]; p++; // lastfakeskin + p++; // team + // Color p += copy_fixed_buf(color, p, ghostsizes.color_name); diff --git a/src/g_game.c b/src/g_game.c index 2287ea3fa..1dedf3833 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5419,6 +5419,8 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr players[i].xtralife = 0; players[i].totalring = 0; players[i].score = 0; + if (roundqueue.position == 0) // Don't unassign teams in tournament play + players[i].team = TEAM_UNASSIGNED; } if (resetplayer || !(gametyperules & GTR_CHECKPOINTS && map == gamemap)) From e6de2e73d808efeae09a29f0d997d940021c0724 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 28 May 2025 15:30:25 -0400 Subject: [PATCH 081/107] I'M TIRED OF YOU --- 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 c453f6558..fc6a42ae2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9783,7 +9783,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // The precise ordering of start-of-level made me want to cut my head off, // so let's try this instead. Whatever! - if (leveltime <= introtime) + if (leveltime <= starttime || player->gradingpointnum == 0) { if ((gametyperules & GTR_SPHERES) || (gametyperules & GTR_CATCHER) From 5d4dc6e29713d6e83b1fbee68f377b416ed13c05 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 28 May 2025 17:20:05 -0400 Subject: [PATCH 082/107] Don't draw screen edge arrows for pickmeups --- src/k_hud_track.cpp | 68 +++++++++++++++------------------------------ src/k_kart.c | 23 +++++++++++++++ src/k_kart.h | 2 ++ 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index 49abd8a5b..0ac07e641 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -143,6 +143,8 @@ struct TargetTracking return false; default: + if (K_IsPickMeUpItem(mobj->type)) + return false; return true; } } @@ -277,28 +279,17 @@ private: {{6, 2, {kp_spraycantarget_far[1]}, V_ADD}}, // 4P }}, }; - - case MT_JAWZ: - case MT_JAWZ_SHIELD: - case MT_ORBINAUT: - case MT_ORBINAUT_SHIELD: - case MT_DROPTARGET: - case MT_DROPTARGET_SHIELD: - case MT_LANDMINE: - case MT_BANANA: - case MT_BANANA_SHIELD: - case MT_GACHABOM: - case MT_EGGMANITEM: - case MT_EGGMANITEM_SHIELD: - case MT_BUBBLESHIELDTRAP: - return { - { // Near - {2, TICRATE/2, {kp_pickmeup}, 0}, // 1P - {{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P - }, - }; - default: + if (K_IsPickMeUpItem(mobj->type)) + { + return { + { // Near + {2, TICRATE/2, {kp_pickmeup}, 0}, // 1P + {{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P + }, + }; + } + return { { // Near {8, 2, {kp_capsuletarget_near[0]}}, // 1P @@ -902,32 +893,17 @@ void K_drawTargetHUD(const vector3_t* origin, player_t* player) if (tracking) { fixed_t itemOffset = 36*mobj->scale; - switch (mobj->type) + + if (K_IsPickMeUpItem(mobj->type)) { - case MT_JAWZ: - case MT_JAWZ_SHIELD: - case MT_ORBINAUT: - case MT_ORBINAUT_SHIELD: - case MT_DROPTARGET: - case MT_DROPTARGET_SHIELD: - case MT_LANDMINE: - case MT_BANANA: - case MT_BANANA_SHIELD: - case MT_GACHABOM: - case MT_BUBBLESHIELDTRAP: - case MT_EGGMANITEM: - case MT_EGGMANITEM_SHIELD: - if (stplyr->mo->eflags & MFE_VERTICALFLIP) - { - pos.z -= itemOffset; - } - else - { - pos.z += itemOffset; - } - break; - default: - break; + if (stplyr->mo->eflags & MFE_VERTICALFLIP) + { + pos.z -= itemOffset; + } + else + { + pos.z += itemOffset; + } } K_ObjectTracking(&tr.result, &pos, false); diff --git a/src/k_kart.c b/src/k_kart.c index c453f6558..dc22b0694 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -15795,6 +15795,29 @@ void K_BotHitPenalty(player_t *player) } } +boolean K_IsPickMeUpItem(mobjtype_t type) +{ + switch (type) + { + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + case MT_BUBBLESHIELDTRAP: + return true; + default: + return false; + } +} + static boolean K_PickUp(player_t *player, mobj_t *picked) { SINT8 type = -1; diff --git a/src/k_kart.h b/src/k_kart.h index 5c36fcd26..762ab997a 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -325,6 +325,8 @@ boolean K_LegacyRingboost(player_t *player); void K_BotHitPenalty(player_t *player); +boolean K_IsPickMeUpItem(mobjtype_t type); + boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2); fixed_t K_TeamComebackMultiplier(player_t *player); From 4bcf0e29243b544f5cb3480e9762a0e09597b72f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 29 May 2025 11:57:44 -0400 Subject: [PATCH 083/107] Snap MessageFeed --- src/k_hud.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 229438d18..700fdb3f4 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -7146,35 +7146,39 @@ static void K_DrawMessageFeed(void) text.font(Draw::Font::kMenu); - UINT8 x = BASEVIDWIDTH/2; - UINT8 y = 10; + UINT32 vw = vid.width / vid.dupx; + UINT32 vh = vid.height / vid.dupy; + + UINT32 x = vw / 2; + UINT32 y = 10; + SINT8 shift = 0; if (r_splitscreen >= 2) { text.font(Draw::Font::kThin); shift = -2; - x = BASEVIDWIDTH/4; + x = vw/4; y = 5; if (i % 2) - x += BASEVIDWIDTH/2; + x += vw / 2; if (i >= 2) - y += BASEVIDHEIGHT / 2; + y += vh / 2; } else if (r_splitscreen >= 1) { y = 5; if (i >= 1) - y += BASEVIDHEIGHT / 2; + y += vh / 2; } UINT16 sw = text.width(); - K_DrawSticker(x - sw/2, y, sw, 0, true); - Draw(x, y+shift).align(Draw::Align::kCenter).text(text); + K_DrawSticker(x - sw/2, y, sw, V_SNAPTOTOP|V_SNAPTOLEFT, true); + Draw(x, y+shift).align(Draw::Align::kCenter).flags(V_SNAPTOTOP|V_SNAPTOLEFT).text(text); } } From 0aa4c3f4725be14d6eb2b50eacb988bea1aa86be Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 29 May 2025 13:47:58 -0400 Subject: [PATCH 084/107] WIP - Puyo-like margin icon stacking --- src/k_hud.cpp | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 229438d18..9f06e69ea 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3395,25 +3395,47 @@ static void K_drawKartDuelScores(void) V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap); } - UINT8 MARGINLEVELS = 6; - INT32 marginx = 0; - INT32 marginoffset = 6; + #define MARGINLEVELS (6) - INT32 margin = 1 + overtimecheckpoints; - // margin = ((leveltime/10)%25)+1; // debug + INT32 marginvalues[MARGINLEVELS] = {1, 5, 7, 9, 11, 13}; - INT32 margindigits = 1 + (margin-1)/MARGINLEVELS; + INT32 margindigits[20]; + memset(margindigits, -1, sizeof(margindigits)); - marginx -= (margindigits-1) * (marginoffset/2); + INT32 nummargindigits = 0; - while (margindigits) + INT32 margin = overtimecheckpoints; + margin = ((leveltime/20)%50)+1; // debug + + if (margin == 0) + return; + + while (margin) { - V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[std::min(margin-1, MARGINLEVELS-1)]); - - margindigits--; - margin -= MARGINLEVELS; - marginx += marginoffset; + UINT32 significant_margin = 0; + for (UINT8 i = MARGINLEVELS-1; i >= 0; i--) + { + if (margin >= marginvalues[i]) + { + significant_margin = i; + break; + } + } + margindigits[nummargindigits] = significant_margin; + nummargindigits++; + margin -= marginvalues[significant_margin]; } + + INT32 marginoffset = 6; + INT32 marginx = ((nummargindigits-1) * marginoffset)/2; + + for (INT32 i = nummargindigits - 1; i >= 0; i--) + { + V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[margindigits[i]]); + marginx -= marginoffset; + } + + #undef MARGINLEVELS } static INT32 easedallyscore = 0; From 55a8a5307736077259fd1adece405a77a6bfa59f Mon Sep 17 00:00:00 2001 From: Eidolon Date: Thu, 29 May 2025 15:18:58 -0500 Subject: [PATCH 085/107] Turn on and off microphone explicitly On most devices these days, there is a mandatory microphone indicator when an input device is being used. Moreover, on macOS and some Linux distros, the user will be prompted to grant permission to the game for microphone access. To ensure we're playing nicely with these expectations, instead of just leaving the device on at all times on first use, close and reopen the device as sound input is needed. --- src/d_clisrv.c | 13 +++++++++---- src/menus/options-voice.cpp | 22 ++++++++++++++++++++-- src/sdl/new_sound.cpp | 17 ++++++----------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 33e9dceaa..def543d63 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -281,7 +281,7 @@ shouldsign_t ShouldSignChallenge(uint8_t *message) if ((max(now, then) - min(now, then)) > 60*15) return SIGN_BADTIME; - // ____ _____ ___ ____ _ + // ____ _____ ___ ____ _ // / ___|_ _/ _ \| _ \| | // \___ \ | || | | | |_) | | // ___) || || |_| | __/|_| @@ -2436,6 +2436,11 @@ static void CL_ConnectToServer(void) joinedIP[0] = '\0'; // And empty this for good measure regardless of whether or not we actually used it. + // Enable sound input/microphone in netgames, activating the microphone device. + if (netgame) + { + S_SoundInputSetEnabled(true); + } } static void Command_connect(void) @@ -2502,6 +2507,8 @@ static void Command_connect(void) { CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n")); D_CloseConnection(); + + S_SoundInputSetEnabled(false); return; } } @@ -3659,6 +3666,7 @@ void D_QuitNetGame(void) K_ClearClientPowerLevels(); G_ObliterateParties(); K_ResetMidVote(); + S_SoundInputSetEnabled(false); DEBFILE("===========================================================================\n" " Log finish\n" @@ -7376,9 +7384,6 @@ void NetVoiceUpdate(void) return; } - // This necessarily runs every frame, not every tic - S_SoundInputSetEnabled(true); - UINT32 bytes_dequed = 0; do { diff --git a/src/menus/options-voice.cpp b/src/menus/options-voice.cpp index 8f02fad5f..7f3a8ba00 100644 --- a/src/menus/options-voice.cpp +++ b/src/menus/options-voice.cpp @@ -72,6 +72,24 @@ static boolean input_routine(INT32) return false; } +static void init_routine(void) +{ + if (!netgame) + { + S_SoundInputSetEnabled(true); + } +} + +static boolean quit_routine(void) +{ + if (!netgame) + { + S_SoundInputSetEnabled(false); + } + + return true; +} + menu_t OPTIONS_VoiceDef = { sizeof (OPTIONS_Voice) / sizeof (menuitem_t), &OPTIONS_MainDef, @@ -85,7 +103,7 @@ menu_t OPTIONS_VoiceDef = { draw_routine, M_DrawOptionsCogs, tick_routine, - NULL, - NULL, + init_routine, + quit_routine, input_routine, }; diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp index 3ded71879..1e1d93797 100644 --- a/src/sdl/new_sound.cpp +++ b/src/sdl/new_sound.cpp @@ -198,7 +198,6 @@ static void (*music_fade_callback)(); static SDL_AudioDeviceID g_device_id; static SDL_AudioDeviceID g_input_device_id; -static boolean g_input_device_paused; void* I_GetSfx(sfxinfo_t* sfx) { @@ -999,7 +998,7 @@ void I_UpdateAudioRecorder(void) boolean I_SoundInputIsEnabled(void) { - return g_input_device_id != 0 && !g_input_device_paused; + return g_input_device_id != 0; } boolean I_SoundInputSetEnabled(boolean enabled) @@ -1023,21 +1022,17 @@ boolean I_SoundInputSetEnabled(boolean enabled) CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError()); return false; } - g_input_device_paused = true; - } - - if (enabled && g_input_device_paused) - { SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE); - g_input_device_paused = false; } - else if (!enabled && !g_input_device_paused) + else if (g_input_device_id != 0 && !enabled) { SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE); SDL_ClearQueuedAudio(g_input_device_id); - g_input_device_paused = true; + SDL_CloseAudioDevice(g_input_device_id); + g_input_device_id = 0; } - return !g_input_device_paused; + + return enabled; } UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len) From 610599e7d157fdcffc4e74b74412c2e749036e1d Mon Sep 17 00:00:00 2001 From: Eidolon Date: Thu, 29 May 2025 15:31:24 -0500 Subject: [PATCH 086/107] Don't try to open microphone when sound is disabled --- src/sdl/new_sound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp index 1e1d93797..1fa9deea2 100644 --- a/src/sdl/new_sound.cpp +++ b/src/sdl/new_sound.cpp @@ -1005,7 +1005,7 @@ boolean I_SoundInputSetEnabled(boolean enabled) { if (g_input_device_id == 0 && enabled) { - if (SDL_GetNumAudioDevices(true) == 0) + if (!sound_started || SDL_GetNumAudioDevices(true) == 0) { return false; } From 9c07bd3ca84fa4d7a1ec062df69c39cd67944968 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 29 May 2025 19:11:32 -0400 Subject: [PATCH 087/107] Margin Boost UI psychosis --- src/k_hud.cpp | 152 ++++++++++++++++++++++++++++++++++++++++--------- src/k_hud.h | 2 + src/m_random.h | 2 + src/p_user.c | 5 +- 4 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 9f06e69ea..35e0bb0dd 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -240,7 +240,7 @@ 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]; +static patch_t *kp_duel_margin[24]; patch_t *kp_autoroulette; patch_t *kp_autoring; @@ -1074,9 +1074,13 @@ void K_LoadKartHUDGraphics(void) 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++) + + sprintf(buffer, "DUELMBxx"); + for (i = 0; i < MARGINLEVELS; i++) { - HU_UpdatePatch(&kp_duel_margin[i], "DUELMB0%d", i); + buffer[6] = '0'+(i/10); + buffer[7] = '0'+(i%10); + HU_UpdatePatch(&kp_duel_margin[i], "%s", buffer); } } @@ -3250,6 +3254,8 @@ INT32 K_GetTransFlagFromFixed(fixed_t value) } static tic_t duel_lastleveltime = 0; +static INT32 duel_marginanim = 0; +static INT32 duel_lastmargin = 0; static INT32 youheight = 0; static void K_drawKartDuelScores(void) @@ -3320,7 +3326,6 @@ static void K_drawKartDuelScores(void) else if (targetyouheight < youheight) youheight -= slide; } - duel_lastleveltime = leveltime; INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight @@ -3395,47 +3400,138 @@ static void K_drawKartDuelScores(void) V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap); } - #define MARGINLEVELS (6) + // Dogshit. Should have just figured out how to do log base 5 in C++. + // However, I think this works anyway. + // I did my best to comment this but the algorithm is honestly just bad and hard to + // reason about. Please don't try to maintain this, just yell at me if it needs any + // adjustments. -Tyron 2025-05-29 - INT32 marginvalues[MARGINLEVELS] = {1, 5, 7, 9, 11, 13}; + // DESIGN INTENT: Create realistic-looking Puyo garbage stacks, while using the + // leading garbage symbol as an indicator of the current Margin Boost value. - INT32 margindigits[20]; + INT32 rawmargin = overtimecheckpoints; // The actual Margin Boost value. + INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol? + // rawmargin = (leveltime/10)%(3*boostspersymbol); + + if (duel_lastleveltime != leveltime) // Trigger the "slide" animation when rawmargin changes. + { + duel_marginanim = std::min(duel_marginanim + 1, 100); // not magic just arbitrary + if (duel_lastmargin != rawmargin) + { + duel_marginanim = 0; + duel_lastmargin = rawmargin; + } + } + + duel_lastleveltime = leveltime; + + // CONS_Printf("=== RAWMARGIN %d\n", rawmargin); + + if (rawmargin == 0) + return; + + rawmargin--; // Start at 0, idiot + + // We're invoking the RNG to get a slightly chaotic symbol distribution, + // but we're a HUD hook, so we need to keep the results of the call consistent. + P_SetRandSeed(PR_NUISANCE, 69 + rawmargin); + + INT32 highsymbol = rawmargin/boostspersymbol + 1; // Highest symbol that should appear. + INT32 symbolsperupgrade = 5; // What is each symbol worth relative to each other? Like, 5 Stars = 1 Moon, etc. + + // Okay, so we would LOVE to do this in a way that isn't a big clusterfuck, like just + // doing rawmargin^3 and then subtracting powers of 5 out of that. Unfortunately, UINT64 + // is too small for the values that feel intuitively right here, so we have to do some of + // the math on a limited set of symbols, then shift up. This is the concept of "symbol + // headroom" that's in use here. + // + // (Note that Puyo~n uses a super inconsistent symbol table, probably to avoid this problem, + // but we're assholes and want things to feel logically consistent I guess? + // I dunno. I sort of feel like I should have just directly used the Puyo~n garbage table and + // avoided most of this, LOL) + + INT32 symbolheadroom = 5; // Maximum # symbols we can "step down". + INT32 frac = rawmargin % boostspersymbol; // Used in intermediate calculations. + INT32 minsymbol = std::max(1, highsymbol - symbolheadroom); // The lowest symbol that should appear. + INT32 symbolheadroominuse = highsymbol - minsymbol; // The # of symbols we are stepping down. + INT32 minscore = std::pow(symbolsperupgrade, symbolheadroominuse+1); + INT32 maxscore = std::pow(symbolsperupgrade, symbolheadroominuse+2) - 1; + + // CONS_Printf("min %d max %d\n", minscore, maxscore); + + // We show the player successive combos with the same leading symbol, but we + // waht them to feel intuitively like they're increasing each time. + // Maxscore and minscore have been mapped to the correct power-of-N, so any + // point we pick between them will lead with the correct symbol once we adjust + // for symbol headroom. Pick a point that's appropriate for how "far" into the + // current symbol we are. + fixed_t lobound = FRACUNIT * frac / boostspersymbol; + fixed_t hibound = FRACUNIT * (frac+1) / boostspersymbol; + fixed_t roll = P_RandomRange(PR_NUISANCE, lobound, hibound); + + INT32 margin = Easing_Linear(roll, minscore, maxscore); // The score we're trying to draw a garbage stack for. + + INT32 margindigits[5]; memset(margindigits, -1, sizeof(margindigits)); INT32 nummargindigits = 0; - INT32 margin = overtimecheckpoints; - margin = ((leveltime/20)%50)+1; // debug + // CONS_Printf("margin %d min %d max %d roll %d shiu %d ms %d\n", margin, minscore, maxscore, roll, symbolheadroominuse, minsymbol); - if (margin == 0) - return; - - while (margin) + if (rawmargin/boostspersymbol >= (MARGINLEVELS-1)) { - UINT32 significant_margin = 0; - for (UINT8 i = MARGINLEVELS-1; i >= 0; i--) + // Capped out. Show 5 Chaos. + nummargindigits = 5; + for(UINT8 i = 0; i < nummargindigits; i++) { - if (margin >= marginvalues[i]) - { - significant_margin = i; - break; - } + margindigits[i] = MARGINLEVELS-1; + } + } + else + { + // Subtract powers of N from our chosen score to create a decent-enough-looking + // garbage stack, then queue up the right patches to be drawn, shifting all the math + // up by "minsymbol"—remember, once maxsymbol goes above symbolheadroom, we are doing + // a low-precision version of the math that ignores low enough symbols. + while (margin > 0) + { + INT32 significant_margin = 0; + for (UINT8 i = symbolheadroominuse+1; i >= 0; i--) + { + INT32 test = std::pow(symbolsperupgrade, i); + // CONS_Printf("testing %d (%d)\n", i, test); + if (margin >= test) + { + significant_margin = i; + break; + } + } + + INT32 index = significant_margin; + + margindigits[nummargindigits] = index + minsymbol - 1; + // CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]); + + nummargindigits++; + + // CONS_Printf("margin was %d ", margin); + margin -= std::pow(symbolsperupgrade, index); + // CONS_Printf("is %d\n", margin); + + if (nummargindigits >= 3 + frac) + break; } - margindigits[nummargindigits] = significant_margin; - nummargindigits++; - margin -= marginvalues[significant_margin]; } - INT32 marginoffset = 6; - INT32 marginx = ((nummargindigits-1) * marginoffset)/2; + INT32 marginspacing = std::min(6, duel_marginanim); + INT32 marginx = ((nummargindigits-1) * marginspacing)/2; for (INT32 i = nummargindigits - 1; i >= 0; i--) { + // CONS_Printf("draw %d - %d\n", i, margindigits[i]); V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[margindigits[i]]); - marginx -= marginoffset; + marginx -= marginspacing; } - - #undef MARGINLEVELS } static INT32 easedallyscore = 0; diff --git a/src/k_hud.h b/src/k_hud.h index cd93a192b..038be77f2 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -25,6 +25,8 @@ extern "C" { #define POS_DELAY_TIME 10 +#define MARGINLEVELS 24 + extern INT32 MINI_X, MINI_Y; struct trackingResult_t diff --git a/src/m_random.h b/src/m_random.h index edc59ee66..dcfeeca3e 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -92,6 +92,8 @@ typedef enum PR_ITEM_SPAWNER = PROLDDEMO, // Battle mode item spawners PR_TEAMS, // Teamplay shuffling + PR_NUISANCE, // Margin Boost HUD + PRNUMSYNCED, PR_INTERPHUDRANDOM = PRNUMSYNCED, // Interpolation-accomodating HUD randomisation diff --git a/src/p_user.c b/src/p_user.c index c5c9e6207..3b7119c30 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3763,7 +3763,6 @@ boolean P_SpectatorJoinGame(player_t *player) } player->spectator = false; player->pflags &= ~PF_WANTSTOJOIN; - player->spectatewait = 0; player->team = TEAM_UNASSIGNED; // We will auto-assign later. player->playerstate = PST_REBORN; player->enteredGame = true; @@ -3778,6 +3777,10 @@ boolean P_SpectatorJoinGame(player_t *player) // a surprise tool that will help us later... text = va("\x82*%s entered the game.", player_names[player-players]); + if (P_IsMachineLocalPlayer(player) && player->spectatewait > TICRATE) + S_StartSound(NULL, sfx_s3ka9); + player->spectatewait = 0; + HU_AddChatText(text, false); return true; // no more player->mo, cannot continue. } From 136cb20cb6e2ef6470c12da2ed59e2adeb53b0fa Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 May 2025 15:57:59 -0400 Subject: [PATCH 088/107] Whip collects items --- src/k_collide.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 4abe3f269..37cfa616e 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -1059,11 +1059,13 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) shield->extravalue1 = 1; } - if (P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL)) + if (!K_TryPickMeUp(attackerPlayer->mo, victim)) { - K_AddHitLag(attacker, attackerHitlag, false); - shield->hitlag = attacker->hitlag; + P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL); } + + K_AddHitLag(attacker, attackerHitlag, false); + shield->hitlag = attacker->hitlag; } return false; } From cdc49d755b5ec443678fe761be604dbf4079e223 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 May 2025 16:21:56 -0400 Subject: [PATCH 089/107] Update K_TryPickMeUp with allowHostile boolean --- src/k_collide.cpp | 12 ++++++------ src/k_kart.c | 4 ++-- src/k_kart.h | 2 +- src/objects/orbinaut.c | 2 +- src/p_inter.c | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 37cfa616e..28e544d8b 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -75,7 +75,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (t1->type == MT_BALLHOGBOOM && t2->type == MT_BALLHOGBOOM) return true; // Ballhogs don't collide with eachother - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (t2->player) @@ -178,7 +178,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX)) @@ -434,7 +434,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (t2->player) @@ -544,7 +544,7 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) if (t2->player && (t2->player->hyudorotimer || t2->player->justbumped)) return true; - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (draggeddroptarget && P_MobjWasRemoved(draggeddroptarget)) @@ -1059,7 +1059,7 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) shield->extravalue1 = 1; } - if (!K_TryPickMeUp(attackerPlayer->mo, victim)) + if (!K_TryPickMeUp(attackerPlayer->mo, victim, true)) { P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL); } @@ -1076,7 +1076,7 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (t2->player) diff --git a/src/k_kart.c b/src/k_kart.c index d50b4c31a..856ea65d4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -15908,7 +15908,7 @@ static boolean K_PickUp(player_t *player, mobj_t *picked) } // ACHTUNG this destroys items when returning true, make sure to bail out -boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2) +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2, boolean allowHostile) { if (!m1 || P_MobjWasRemoved(m1)) return false; @@ -15943,7 +15943,7 @@ boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2) if (inflictor->target->player && G_SameTeam(inflictor->target->player, victim->player)) allied = true; - if (!allied) + if (!allied && !allowHostile) return false; // CONS_Printf("target check passed\n"); diff --git a/src/k_kart.h b/src/k_kart.h index 762ab997a..721b5763f 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -327,7 +327,7 @@ void K_BotHitPenalty(player_t *player); boolean K_IsPickMeUpItem(mobjtype_t type); -boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2); +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2, boolean allowHostile); fixed_t K_TeamComebackMultiplier(player_t *player); diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 501bc0e2d..9767f49f1 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -190,7 +190,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } - if (K_TryPickMeUp(t1, t2)) + if (K_TryPickMeUp(t1, t2, false)) return true; if (t1->type == MT_GARDENTOP) diff --git a/src/p_inter.c b/src/p_inter.c index ff2b90ed1..c7b370f04 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -667,7 +667,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!player->mo || player->spectator) return; - if (K_TryPickMeUp(special, toucher)) + if (K_TryPickMeUp(special, toucher, false)) return; // attach to player! From b9f80f902b176a8aa6117b15198f60fa339a6dea Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 9 Nov 2024 20:44:40 -0600 Subject: [PATCH 090/107] Allow drop target to be picked up by whipping and a bit of cleanup --- src/k_collide.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 28e544d8b..77cf778c8 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -1042,7 +1042,14 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) } else if (victim->type == MT_DROPTARGET || victim->type == MT_DROPTARGET_SHIELD) { - K_DropTargetCollide(victim, shield); + if (K_TryPickMeUp(attacker, victim, true)) + { + shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too + } + else + { + K_DropTargetCollide(victim, shield); + } return true; } else @@ -1059,13 +1066,16 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) shield->extravalue1 = 1; } - if (!K_TryPickMeUp(attackerPlayer->mo, victim, true)) + if (K_TryPickMeUp(attacker, victim, true)) + { + shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too + } + else { P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL); + K_AddHitLag(attacker, attackerHitlag, false); + shield->hitlag = attacker->hitlag; } - - K_AddHitLag(attacker, attackerHitlag, false); - shield->hitlag = attacker->hitlag; } return false; } From 9e0510d674b85fe66888fbb92d81cef2804b9414 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 May 2025 23:34:46 +0100 Subject: [PATCH 091/107] Improve Demo end handing - Demos/Ghosts that end before ticking once are now correctly ignored. (Resolves KartKrew/RingRacers#168) - There was code for discovering it on read! It was just placed slightly too early, probably due to the conversion for netreplays! I'm very mad! - As a preventative measure, demos *recorded* before ticking will simply not save in the first place. - This was also a frustratingly easy fix for the amount of headache it's caused us. - Reduced the amount of copypasted boilerplate by simplifying the places where DEMOMARKER can be written (and therefore read). - Previously, like half the write functions tried to guess their own output size and potentially end the demo at any point. - At best, this will grant us a few tics of reprireve for large netgames and MAYBE a handful of seconds for time attack, The Mode In Which The Aim Is To Go Fast. - Instead, double the size of the deadspace buffer extension and just check to see if we've crossed into that territory. --- src/g_demo.cpp | 194 +++++++++++++++++++++---------------------------- src/g_demo.h | 2 + src/p_tick.c | 37 +++++++--- 3 files changed, 111 insertions(+), 122 deletions(-) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index e60e1e759..8b5121931 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -270,6 +270,26 @@ static ticcmd_t oldcmd[MAXPLAYERS]; static mobj_t oldghost[MAXPLAYERS]; +boolean G_ConsiderEndingDemoWrite(void) +{ + // chill, we reserved extra memory so it's + // "safe" to have written a bit past the end + if (demobuf.p < demobuf.end) + return false; + + G_CheckDemoStatus(); + return true; +} + +boolean G_ConsiderEndingDemoRead(void) +{ + if (*demobuf.p != DEMOMARKER) + return false; + + G_CheckDemoStatus(); + return true; +} + void G_ReadDemoExtraData(void) { INT32 p, extradata, i; @@ -460,13 +480,6 @@ void G_ReadDemoExtraData(void) p = READUINT8(demobuf.p); } - - if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER) - { - // end of demo data stream - G_CheckDemoStatus(); - return; - } } void G_WriteDemoExtraData(void) @@ -623,13 +636,6 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) } G_CopyTiccmd(cmd, &oldcmd[playernum], 1); - - if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER) - { - // end of demo data stream - G_CheckDemoStatus(); - return; - } } void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) @@ -739,14 +745,6 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) WRITEUINT16(botziptic_p, botziptic); } - - // attention here for the ticcmd size! - // latest demos with mouse aiming byte in ticcmd - if (!(demoflags & DF_GHOST) && ziptic_p > demobuf.end - 9) - { - G_CheckDemoStatus(); // no more space - return; - } } void G_GhostAddFlip(INT32 playernum) @@ -794,8 +792,11 @@ void G_GhostAddHit(INT32 playernum, mobj_t *victim) void G_WriteAllGhostTics(void) { - boolean toobig = false; INT32 i, counter = leveltime; + + if (!demobuf.p || !(demoflags & DF_GHOST)) + return; // No ghost data to write. + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -811,22 +812,9 @@ void G_WriteAllGhostTics(void) WRITEUINT8(demobuf.p, i); G_WriteGhostTic(players[i].mo, i); - - // attention here for the ticcmd size! - // latest demos with mouse aiming byte in ticcmd - if (demobuf.p >= demobuf.end - (13 + 9 + 9)) - { - toobig = true; - break; - } } + WRITEUINT8(demobuf.p, 0xFF); - - if (toobig) - { - G_CheckDemoStatus(); // no more space - return; - } } void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) @@ -835,11 +823,6 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) UINT8 *ziptic_p; UINT32 i; - if (!demobuf.p) - return; - if (!(demoflags & DF_GHOST)) - return; // No ghost data to write. - ziptic_p = demobuf.p++; // the ziptic, written at the end of this function #define MAXMOM (0xFFFF<<8) @@ -1061,7 +1044,7 @@ void G_ConsAllGhostTics(void) { UINT8 p; - if (!demobuf.p || !demo.deferstart) + if (!demobuf.p || !(demoflags & DF_GHOST) || !demo.deferstart) return; p = READUINT8(demobuf.p); @@ -1071,13 +1054,6 @@ void G_ConsAllGhostTics(void) G_ConsGhostTic(p); p = READUINT8(demobuf.p); } - - if (*demobuf.p == DEMOMARKER) - { - // end of demo data stream - G_CheckDemoStatus(); - return; - } } // Uses ghost data to do consistency checks on your position. @@ -1089,9 +1065,6 @@ void G_ConsGhostTic(INT32 playernum) mobj_t *testmo; UINT32 syncleeway; - if (!(demoflags & DF_GHOST)) - return; // No ghost data to use. - testmo = players[playernum].mo; // Grab ghost data. @@ -1282,13 +1255,6 @@ void G_ConsGhostTic(INT32 playernum) } } } - - if (*demobuf.p == DEMOMARKER) - { - // end of demo data stream - G_CheckDemoStatus(); - return; - } } void G_GhostTicker(void) @@ -1309,11 +1275,31 @@ void G_GhostTicker(void) continue; readghosttic: +#define follow g->mo->tracer // Skip normal demo data. ziptic = READUINT8(g->p); xziptic = 0; + // Demo ends after ghost data. + if (ziptic == DEMOMARKER) + { +fadeghost: + g->mo->momx = g->mo->momy = g->mo->momz = 0; + g->mo->fuse = TICRATE; + if (follow) + { + follow->fuse = TICRATE; + } + + g->done = true; + if (p) + { + p->next = g->next; + } + continue; + } + while (ziptic != DW_END) // Get rid of extradata stuff { if (ziptic < MAXPLAYERS) @@ -1414,9 +1400,11 @@ readghosttic: // Grab ghost data. ziptic = READUINT8(g->p); + if (ziptic == DEMOMARKER) // Had to end early for some reason + goto fadeghost; if (ziptic == 0xFF) goto skippedghosttic; // Didn't write ghost info this frame - else if (ziptic != 0) + if (ziptic != 0) I_Error("Ghost is not a record attack ghost ZIPTIC"); //@TODO lmao don't blow up like this ziptic = READUINT8(g->p); @@ -1530,7 +1518,6 @@ readghosttic: g->mo->renderflags &= ~RF_DONTDRAW; } -#define follow g->mo->tracer if (ziptic & GZT_FOLLOW) { // Even more... UINT8 followtic = READUINT8(g->p); @@ -1620,28 +1607,6 @@ skippedghosttic: if (READUINT8(g->p) != 0xFF) // Make sure there isn't other ghost data here. I_Error("Ghost is not a record attack ghost GHOSTEND"); //@TODO lmao don't blow up like this - // Demo ends after ghost data. - if (*g->p == DEMOMARKER) - { - g->mo->momx = g->mo->momy = g->mo->momz = 0; -#if 0 // freeze frame (maybe more useful for time attackers) (2024-03-11: you leave it behind anyway!) - g->mo->colorized = true; - g->mo->fuse = 10*TICRATE; - if (follow) - follow->colorized = true; -#else // dissapearing act - g->mo->fuse = TICRATE; - if (follow) - follow->fuse = TICRATE; -#endif - g->done = true; - if (p) - { - p->next = g->next; - } - continue; - } - // If the timer started, skip ahead until the ghost starts too. if (starttime <= leveltime && !g->linecrossed && G_TimeAttackStart()) goto readghosttic; @@ -1885,7 +1850,7 @@ void G_RecordDemo(const char *name) functions will check if they overran the buffer, but it should be safe enough because they'll think there's less memory than there actually is and stop early. */ - const size_t deadspace = 1024; + const size_t deadspace = 2048; I_Assert(demobuf.size > deadspace); demobuf.size -= deadspace; demobuf.end -= deadspace; @@ -3363,22 +3328,6 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) // Load "mapmusrng" used for altmusic selection mapmusrng = READUINT8(demobuf.p); - // Sigh ... it's an empty demo. - if (*demobuf.p == DEMOMARKER) - { - snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - M_StartMessage("Demo Playback", msg, NULL, MM_NOTHING, NULL, "Return to Menu"); - Z_Free(demo.skinlist); - demo.skinlist = NULL; - Z_Free(pdemoname); - Z_Free(demobuf.buffer); - demo.playback = false; - return; - } - - Z_Free(pdemoname); - memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); memset(&ghostext,0,sizeof(ghostext)); @@ -3607,6 +3556,22 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) players[p].lastfakeskin = lastfakeskin[p]; } + // Sigh ... it's an empty demo. + if (*demobuf.p == DEMOMARKER) + { + snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage("Demo Playback", msg, NULL, MM_NOTHING, NULL, "Return to Menu"); + Z_Free(demo.skinlist); + demo.skinlist = NULL; + Z_Free(pdemoname); + Z_Free(demobuf.buffer); + demo.playback = false; + return; + } + + Z_Free(pdemoname); + demo.deferstart = true; CV_StealthSetValue(&cv_playbackspeed, 1); @@ -3756,14 +3721,6 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) p++; // mapmusrng - if (*p == DEMOMARKER) - { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), defdemoname); - Z_Free(skinlist); - P_SaveBufferFree(buffer); - return; - } - p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once // any invalidating flags? @@ -3811,6 +3768,14 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) return; } + if (*p == DEMOMARKER) + { + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), defdemoname); + Z_Free(skinlist); + P_SaveBufferFree(buffer); + return; + } + gh = static_cast(Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL)); gh->sizes = ghostsizes; @@ -4173,7 +4138,7 @@ boolean G_CheckDemoStatus(void) // Keep the demo open and don't boot to intermission // YET, pause demo playback. if (!demo.waitingfortally && modeattacking && exitcountdown) - demo.waitingfortally = true; + ; else if (!demo.attract) G_FinishExitLevel(); else @@ -4191,6 +4156,8 @@ boolean G_CheckDemoStatus(void) D_SetDeferredStartTitle(true); } + demo.waitingfortally = true; // if we've returned early for some reason... + return true; } @@ -4235,6 +4202,13 @@ void G_SaveDemo(void) if (currentMenu == &TitleEntryDef) M_ClearMenus(true); + if (!leveltime) + { + // Why would you save if nothing has been recorded + G_ResetDemoRecording(); + return; + } + // Ensure extrainfo pointer is always available, even if no info is present. if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) { diff --git a/src/g_demo.h b/src/g_demo.h index e3c4c74ff..2a0a0d170 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -172,6 +172,8 @@ extern UINT8 demo_writerng; boolean G_CompatLevel(UINT16 level); // Record/playback tics +boolean G_ConsiderEndingDemoRead(void); +boolean G_ConsiderEndingDemoWrite(void); void G_ReadDemoExtraData(void); void G_WriteDemoExtraData(void); void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum); diff --git a/src/p_tick.c b/src/p_tick.c index 29cf6d62c..a29c01033 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -762,17 +762,23 @@ void P_Ticker(boolean run) if (demo.recording) { - G_WriteDemoExtraData(); - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - G_WriteDemoTiccmd(&players[i].cmd, i); + if (!G_ConsiderEndingDemoWrite()) + { + G_WriteDemoExtraData(); + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + G_WriteDemoTiccmd(&players[i].cmd, i); + } } if (demo.playback && !demo.waitingfortally) { - G_ReadDemoExtraData(); - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - G_ReadDemoTiccmd(&players[i].cmd, i); + if (!G_ConsiderEndingDemoRead()) + { + G_ReadDemoExtraData(); + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + G_ReadDemoTiccmd(&players[i].cmd, i); + } } LUA_ResetTicTimers(); @@ -1168,14 +1174,21 @@ void P_Ticker(boolean run) if (demo.recording) { - G_WriteAllGhostTics(); - if (cv_recordmultiplayerdemos.value && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) G_CheckDemoTitleEntry(); + + if (!G_ConsiderEndingDemoWrite()) + { + G_WriteAllGhostTics(); + } } - else if (demo.playback && !demo.waitingfortally) // Use Ghost data for consistency checks. + else if (demo.playback && !demo.waitingfortally) { - G_ConsAllGhostTics(); + if (!G_ConsiderEndingDemoRead()) + { + // Use Ghost data for consistency checks. + G_ConsAllGhostTics(); + } } if (modeattacking) From 09e23e6e18abec1710357973d2df123659e1334c Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 00:22:19 +0100 Subject: [PATCH 092/107] Rip out all the rewinding code It was all messy and none of it is called in release builds because it was so crashy in testing --- src/d_clisrv.c | 60 --------- src/d_clisrv.h | 15 --- src/d_main.cpp | 3 - src/g_demo.cpp | 210 ----------------------------- src/g_demo.h | 6 - src/g_game.c | 10 +- src/k_menudraw.c | 2 +- src/menus/transient/pause-replay.c | 41 +----- src/p_setup.cpp | 4 +- src/p_tick.c | 13 +- src/typedef.h | 1 - 11 files changed, 8 insertions(+), 357 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 33e9dceaa..73c8ce525 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -7555,66 +7555,6 @@ tic_t GetLag(INT32 node) return gametic - nettics[node]; } -#define REWIND_POINT_INTERVAL 4*TICRATE + 16 -rewind_t *rewindhead; - -void CL_ClearRewinds(void) -{ - rewind_t *head; - while ((head = rewindhead)) - { - rewindhead = rewindhead->next; - free(head); - } -} - -rewind_t *CL_SaveRewindPoint(size_t demopos) -{ - savebuffer_t save = {0}; - rewind_t *rewind; - - if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime) - return NULL; - - rewind = (rewind_t *)malloc(sizeof (rewind_t)); - if (!rewind) - return NULL; - - P_SaveBufferFromExisting(&save, rewind->savebuffer, NETSAVEGAMESIZE); - P_SaveNetGame(&save, false); - - rewind->leveltime = leveltime; - rewind->next = rewindhead; - rewind->demopos = demopos; - rewindhead = rewind; - - return rewind; -} - -rewind_t *CL_RewindToTime(tic_t time) -{ - savebuffer_t save = {0}; - rewind_t *rewind; - - while (rewindhead && rewindhead->leveltime > time) - { - rewind = rewindhead->next; - free(rewindhead); - rewindhead = rewind; - } - - if (!rewindhead) - return NULL; - - P_SaveBufferFromExisting(&save, rewindhead->savebuffer, NETSAVEGAMESIZE); - P_LoadNetGame(&save, false); - - wipegamestate = gamestate; // No fading back in! - timeinmap = leveltime; - - return rewindhead; -} - void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest) { #ifdef NOMD5 diff --git a/src/d_clisrv.h b/src/d_clisrv.h index de606acb6..eb3861c55 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -712,21 +712,6 @@ extern boolean hu_stopped; // SRB2Kart // -struct rewind_t { - UINT8 savebuffer[NETSAVEGAMESIZE]; - tic_t leveltime; - size_t demopos; - - ticcmd_t oldcmd[MAXPLAYERS]; - mobj_t oldghost[MAXPLAYERS]; - - rewind_t *next; -}; - -void CL_ClearRewinds(void); -rewind_t *CL_SaveRewindPoint(size_t demopos); -rewind_t *CL_RewindToTime(tic_t time); - void HandleSigfail(const char *string); void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message); diff --git a/src/d_main.cpp b/src/d_main.cpp index 7afb32df7..71f5f74d4 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -742,9 +742,6 @@ static bool D_Display(bool world) if (forcerefresh && G_GamestateUsesLevel() == false) V_SetPalette(0); - if (demo.rewinding) - V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSET); - // vid size change is now finished if it was on... vid.recalc = 0; diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 8b5121931..9e6ecf1e2 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -295,17 +295,6 @@ void G_ReadDemoExtraData(void) INT32 p, extradata, i; char name[64]; static_assert(sizeof name >= std::max({MAXPLAYERNAME+1u, SKINNAMESIZE+1u, MAXCOLORNAME+1u})); - - if (leveltime > starttime) - { - rewind_t *rewind = CL_SaveRewindPoint(demobuf.p - demobuf.buffer); - if (rewind) - { - memcpy(rewind->oldcmd, oldcmd, sizeof (oldcmd)); - memcpy(rewind->oldghost, oldghost, sizeof (oldghost)); - } - } - memset(name, '\0', sizeof name); p = READUINT8(demobuf.p); @@ -1616,203 +1605,6 @@ skippedghosttic: } } -// Demo rewinding functions -typedef struct rewindinfo_s { - tic_t leveltime; - - struct { - boolean ingame; - player_t player; - mobj_t mobj; - } playerinfo[MAXPLAYERS]; - - struct rewindinfo_s *prev; -} rewindinfo_t; - -static tic_t currentrewindnum; -static rewindinfo_t *rewindhead = NULL; // Reverse chronological order - -void G_InitDemoRewind(void) -{ - CL_ClearRewinds(); - - while (rewindhead) - { - rewindinfo_t *p = rewindhead->prev; - Z_Free(rewindhead); - rewindhead = p; - } - - currentrewindnum = 0; -} - -void G_StoreRewindInfo(void) -{ - static UINT8 timetolog = 8; - rewindinfo_t *info; - size_t i; - - if (timetolog-- > 0) - return; - timetolog = 8; - - info = static_cast(Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL)); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - info->playerinfo[i].ingame = false; - continue; - } - - info->playerinfo[i].ingame = true; - memcpy(&info->playerinfo[i].player, &players[i], sizeof(player_t)); - if (players[i].mo) - memcpy(&info->playerinfo[i].mobj, players[i].mo, sizeof(mobj_t)); - } - - info->leveltime = leveltime; - info->prev = rewindhead; - rewindhead = info; -} - -void G_PreviewRewind(tic_t previewtime) -{ - SINT8 i; - //size_t j; - fixed_t tweenvalue = 0; - rewindinfo_t *info = rewindhead, *next_info = rewindhead; - - if (!info) - return; - - while (info->leveltime > previewtime && info->prev) - { - next_info = info; - info = info->prev; - } - if (info != next_info) - tweenvalue = FixedDiv(previewtime - info->leveltime, next_info->leveltime - info->leveltime); - - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - if (info->playerinfo[i].player.mo) - { - //@TODO spawn temp object to act as a player display - } - - continue; - } - - if (!info->playerinfo[i].ingame || !info->playerinfo[i].player.mo) - { - if (players[i].mo) - players[i].mo->renderflags |= RF_DONTDRAW; - - continue; - } - - if (!players[i].mo) - continue; //@TODO spawn temp object to act as a player display - - players[i].mo->renderflags &= ~RF_DONTDRAW; - - P_UnsetThingPosition(players[i].mo); -#define TWEEN(pr) info->playerinfo[i].mobj.pr + FixedMul((INT32) (next_info->playerinfo[i].mobj.pr - info->playerinfo[i].mobj.pr), tweenvalue) - players[i].mo->x = TWEEN(x); - players[i].mo->y = TWEEN(y); - players[i].mo->z = TWEEN(z); - players[i].mo->angle = TWEEN(angle); -#undef TWEEN - P_SetThingPosition(players[i].mo); - - players[i].drawangle = info->playerinfo[i].player.drawangle + FixedMul((INT32) (next_info->playerinfo[i].player.drawangle - info->playerinfo[i].player.drawangle), tweenvalue); - - players[i].mo->sprite = info->playerinfo[i].mobj.sprite; - players[i].mo->sprite2 = info->playerinfo[i].mobj.sprite2; - players[i].mo->frame = info->playerinfo[i].mobj.frame; - - players[i].mo->hitlag = info->playerinfo[i].mobj.hitlag; - - players[i].realtime = info->playerinfo[i].player.realtime; - // Genuinely CANNOT be fucked. I can redo lua and I can redo netsaves but I draw the line at this abysmal hack. - /*for (j = 0; j < NUMKARTSTUFF; j++) - players[i].kartstuff[j] = info->playerinfo[i].player.kartstuff[j];*/ - } - - for (i = splitscreen; i >= 0; i--) - P_ResetCamera(&players[displayplayers[i]], &camera[i]); -} - -void G_ConfirmRewind(tic_t rewindtime) -{ - SINT8 i; - tic_t j; - boolean oldmenuactive = menuactive, oldsounddisabled = sound_disabled; - - INT32 olddp1 = displayplayers[0], olddp2 = displayplayers[1], olddp3 = displayplayers[2], olddp4 = displayplayers[3]; - UINT8 oldss = splitscreen; - - menuactive = false; // Prevent loops - - CV_StealthSetValue(&cv_renderview, 0); - - if (rewindtime <= starttime) - { - demo.rewinding = true; // this doesn't APPEAR to cause any misery, and it allows us to prevent running all the wipes again - G_DoPlayDemo(NULL); // Restart the current demo - } - else - { - rewind_t *rewind; - sound_disabled = true; // Prevent sound spam - demo.rewinding = true; - - rewind = CL_RewindToTime(rewindtime); - - if (rewind) - { - demobuf.p = demobuf.buffer + rewind->demopos; - memcpy(oldcmd, rewind->oldcmd, sizeof (oldcmd)); - memcpy(oldghost, rewind->oldghost, sizeof (oldghost)); - paused = false; - } - else - { - demo.rewinding = true; - G_DoPlayDemo(NULL); // Restart the current demo - } - } - - for (j = 0; j < rewindtime && leveltime < rewindtime; j++) - { - G_Ticker((j % NEWTICRATERATIO) == 0); - } - - demo.rewinding = false; - menuactive = oldmenuactive; // Bring the menu back up - sound_disabled = oldsounddisabled; // Re-enable SFX - - wipegamestate = gamestate; // No fading back in! - - COM_BufInsertText("renderview on\n"); - - splitscreen = oldss; - displayplayers[0] = olddp1; - displayplayers[1] = olddp2; - displayplayers[2] = olddp3; - displayplayers[3] = olddp4; - R_ExecuteSetViewSize(); - G_ResetViews(); - - for (i = splitscreen; i >= 0; i--) - P_ResetCamera(&players[displayplayers[i]], &camera[i]); -} - // // G_RecordDemo // @@ -2964,8 +2756,6 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) boolean skiperrors = true; #endif - G_InitDemoRewind(); - gtname[MAXGAMETYPELENGTH-1] = '\0'; if (deflumpnum != LUMPERROR) diff --git a/src/g_demo.h b/src/g_demo.h index 2a0a0d170..d2e19617f 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -84,7 +84,6 @@ struct demovars_s { boolean recording, playback, timing; UINT16 version; // Current file format of the demo being played UINT8 attract; // Attract demo can be cancelled by any key - boolean rewinding; // Rewind in progress boolean loadfiles, ignorefiles; // Demo file loading options boolean quitafterplaying; // quit after playing a demo from cmdline @@ -188,11 +187,6 @@ void G_ConsAllGhostTics(void); void G_ConsGhostTic(INT32 playernum); void G_GhostTicker(void); -void G_InitDemoRewind(void); -void G_StoreRewindInfo(void); -void G_PreviewRewind(tic_t previewtime); -void G_ConfirmRewind(tic_t rewindtime); - struct DemoBufferSizes { size_t player_name; diff --git a/src/g_game.c b/src/g_game.c index 1dedf3833..f4e2d3feb 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1247,7 +1247,7 @@ void G_StartTitleCard(void) // The title card has been disabled for this map. // Oh well. - if (demo.rewinding || !G_IsTitleCardAvailable()) + if (!G_IsTitleCardAvailable()) { WipeStageTitle = false; return; @@ -1439,13 +1439,7 @@ boolean G_Responder(event_t *ev) { paused = !paused; - if (demo.rewinding) - { - G_ConfirmRewind(leveltime); - paused = true; - S_PauseAudio(); - } - else if (paused) + if (paused) S_PauseAudio(); else S_ResumeAudio(); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 61853ece5..a06a6fec1 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -6485,7 +6485,7 @@ void M_DrawPlaybackMenu(void) else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); - if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding)) + if ((i == playback_fastforward && cv_playbackspeed.value > 1)) V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE)); else V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap); diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 864344d91..7a63e65d1 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -151,7 +151,7 @@ static void M_PlaybackTick(void) playback_last_menu_interaction_leveltime = leveltime - 6*TICRATE; // Toggle items - if (paused && !demo.rewinding) + if (paused) { PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_DISABLED; PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING; @@ -204,34 +204,9 @@ void M_SetPlaybackMenuPointer(void) void M_PlaybackRewind(INT32 choice) { -#if 0 - static tic_t lastconfirmtime; - - (void)choice; - - if (!demo.rewinding) - { - if (paused) - { - G_ConfirmRewind(leveltime-1); - paused = true; - S_PauseAudio(); - } - else - demo.rewinding = paused = true; - } - else if (lastconfirmtime + TICRATE/2 < I_GetTime()) - { - lastconfirmtime = I_GetTime(); - G_ConfirmRewind(leveltime); - } - - CV_SetValue(&cv_playbackspeed, 1); -#else (void)choice; G_DoPlayDemo(NULL); // Restart the current demo M_ClearMenus(true); -#endif } void M_PlaybackPause(INT32 choice) @@ -240,13 +215,7 @@ void M_PlaybackPause(INT32 choice) paused = !paused; - if (demo.rewinding) - { - G_ConfirmRewind(leveltime); - paused = true; - S_PauseAudio(); - } - else if (paused) + if (paused) S_PauseAudio(); else S_ResumeAudio(); @@ -258,12 +227,6 @@ void M_PlaybackFastForward(INT32 choice) { (void)choice; - if (demo.rewinding) - { - G_ConfirmRewind(leveltime); - paused = false; - S_ResumeAudio(); - } CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1); } diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 3ad9d6a16..d6ceb5c19 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8474,7 +8474,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) wipegamestate = gamestate; // Don't fade if reloading the gamestate // Encore mode fade to pink to white // This is handled BEFORE sounds are stopped. - else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE && !demo.rewinding) + else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE) { if (rendermode != render_none) { @@ -8545,7 +8545,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Let's fade to white here // But only if we didn't do the encore startup wipe - if (!demo.rewinding && !reloadinggamestate) + if (!reloadinggamestate) { int wipetype = wipe_level_toblack; diff --git a/src/p_tick.c b/src/p_tick.c index a29c01033..206cdaf4c 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -739,15 +739,7 @@ void P_Ticker(boolean run) // Check for pause or menu up in single player if (paused || P_AutoPause()) { - if (demo.rewinding && leveltime > 0) - { - leveltime = (leveltime-1) & ~3; - if (timeinmap > 0) - timeinmap = (timeinmap-1) & ~3; - G_PreviewRewind(leveltime); - } - else - P_RunChaseCameras(); // special case: allow freecam to MOVE during pause! + P_RunChaseCameras(); // special case: allow freecam to MOVE during pause! return; } @@ -1251,9 +1243,6 @@ void P_Ticker(boolean run) P_MapEnd(); - if (demo.playback) - G_StoreRewindInfo(); - for (i = 0; i < MAXPLAYERS; i++) { G_CopyTiccmd(&players[i].oldcmd, &players[i].cmd, 1); diff --git a/src/typedef.h b/src/typedef.h index 03ecea75d..3dfa9b8c0 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -76,7 +76,6 @@ TYPEDEF (plrconfig); TYPEDEF (filesneededconfig_pak); TYPEDEF (doomdata_t); TYPEDEF (serverelem_t); -TYPEDEF (rewind_t); TYPEDEF (clientkey_pak); TYPEDEF (serverchallenge_pak); TYPEDEF (challengeall_pak); From e4907227fc4f6e51c22ca0cf203be5470ccd4bf4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 01:12:18 +0100 Subject: [PATCH 093/107] New simpler rewind code + re-enable in UI - Built off g_fast_forward to utilise a lot of existing structure - Only does steps of 5 seconds and therefore isn't precise, but that's better than having to rewatch/fastforward through the entire thing - No single frame step back or general in-motion rewind preview, but that was all overkill --- src/d_main.cpp | 2 +- src/g_demo.cpp | 3 +- src/g_demo.h | 8 +++ src/g_game.c | 10 +++- src/k_menu.h | 2 +- src/menus/transient/pause-replay.c | 89 ++++++++++++++++++++---------- src/p_setup.cpp | 21 ++++--- src/p_tick.c | 2 +- src/st_stuff.c | 2 +- 9 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 71f5f74d4..d660cf3a7 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -933,7 +933,7 @@ void D_SRB2Loop(void) realtics = entertic - oldentertics; oldentertics = entertic; - if (demo.playback && gamestate == GS_LEVEL) + if (demo.playback && gamestate == GS_LEVEL && demo.simplerewind == DEMO_REWIND_OFF) { // Nicer place to put this. realtics = realtics * cv_playbackspeed.value; diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 9e6ecf1e2..05a881ace 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -3364,7 +3364,8 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) demo.deferstart = true; - CV_StealthSetValue(&cv_playbackspeed, 1); + if (demo.simplerewind == DEMO_REWIND_OFF) + CV_StealthSetValue(&cv_playbackspeed, 1); } void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) diff --git a/src/g_demo.h b/src/g_demo.h index d2e19617f..4ce05c2a3 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -84,6 +84,7 @@ struct demovars_s { boolean recording, playback, timing; UINT16 version; // Current file format of the demo being played UINT8 attract; // Attract demo can be cancelled by any key + UINT8 simplerewind; boolean loadfiles, ignorefiles; // Demo file loading options boolean quitafterplaying; // quit after playing a demo from cmdline @@ -246,6 +247,13 @@ typedef enum DEMO_ATTRACT_CREDITS } demoAttractMode_t; +typedef enum +{ + DEMO_REWIND_OFF = 0, + DEMO_REWIND_RESUME, + DEMO_REWIND_PAUSE +} demoRewindMode_t; + void G_SyncDemoParty(INT32 rem, INT32 newsplitscreen); #ifdef __cplusplus diff --git a/src/g_game.c b/src/g_game.c index f4e2d3feb..8f05f30ab 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1247,7 +1247,7 @@ void G_StartTitleCard(void) // The title card has been disabled for this map. // Oh well. - if (!G_IsTitleCardAvailable()) + if (demo.simplerewind || !G_IsTitleCardAvailable()) { WipeStageTitle = false; return; @@ -2144,6 +2144,14 @@ void G_Ticker(boolean run) if (g_fast_forward == 0) { + // Not "rewinding" anymore. + if (demo.simplerewind == DEMO_REWIND_PAUSE) + { + paused = true; + S_PauseAudio(); + } + demo.simplerewind = DEMO_REWIND_OFF; + // Next fast-forward is unlimited. g_fast_forward_clock_stop = INFTICS; } diff --git a/src/k_menu.h b/src/k_menu.h index c9564c484..1a4dc3066 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -593,10 +593,10 @@ extern menu_t PAUSE_PlaybackMenuDef; typedef enum { playback_hide, + playback_restart, playback_rewind, playback_pause, playback_fastforward, - playback_backframe, playback_resume, playback_advanceframe, playback_viewcount, diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 7a63e65d1..ef5d1eb1e 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -39,21 +39,21 @@ menuitem_t PAUSE_PlaybackMenu[] = {IT_CALL | IT_STRING, "Hide Menu", NULL, "M_PHIDE", {.routine = M_SelectableClearMenus}, 0, 0}, {IT_CALL | IT_STRING, "Restart", NULL, "M_PRSTRT", {.routine = M_PlaybackRewind}, 20, 0}, - {IT_CALL | IT_STRING, "Pause", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 36, 0}, - {IT_CALL | IT_STRING, "Fast-Forward", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 52, 0}, - {IT_CALL | IT_STRING, "Restart", NULL, "M_PRSTRT", {.routine = M_PlaybackRewind}, 20, 0}, - {IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 36, 0}, - {IT_CALL | IT_STRING, "Advance Frame", NULL, "M_PFADV", {.routine = M_PlaybackAdvance}, 52, 0}, + {IT_CALL | IT_STRING, "Rewind 5 seconds", NULL, "M_PREW", {.routine = M_PlaybackRewind}, 36, 0}, + {IT_CALL | IT_STRING, "Pause", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 52, 0}, + {IT_CALL | IT_STRING, "Fast-Forward", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 68, 0}, + {IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 52, 0}, + {IT_CALL | IT_STRING, "Advance Frame", NULL, "M_PFADV", {.routine = M_PlaybackAdvance}, 68, 0}, - {IT_ARROWS | IT_STRING, "View Count", NULL, "M_PVIEWS", {.routine = M_PlaybackSetViews}, 72, 0}, - {IT_ARROWS | IT_STRING, "Viewpoint", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 88, 0}, - {IT_ARROWS | IT_STRING, "Viewpoint 2", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 104, 0}, - {IT_ARROWS | IT_STRING, "Viewpoint 3", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 120, 0}, - {IT_ARROWS | IT_STRING, "Viewpoint 4", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 136, 0}, + {IT_ARROWS | IT_STRING, "View Count", NULL, "M_PVIEWS", {.routine = M_PlaybackSetViews}, 88, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 104, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 2", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 120, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 3", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 136, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 4", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 152, 0}, - {IT_CALL | IT_STRING, "Toggle Director", NULL, "UN_IC11A", {.routine = M_PlaybackToggleDirector}, 156, 0}, - {IT_CALL | IT_STRING, "Toggle Free Camera", NULL, "M_PVIEWS", {.routine = M_PlaybackToggleFreecam}, 172, 0}, - {IT_CALL | IT_STRING, "Stop Playback", NULL, "M_PEXIT", {.routine = M_PlaybackQuit}, 188, 0}, + {IT_CALL | IT_STRING, "Toggle Director", NULL, "UN_IC11A", {.routine = M_PlaybackToggleDirector}, 172, 0}, + {IT_CALL | IT_STRING, "Toggle Free Camera", NULL, "M_PVIEWS", {.routine = M_PlaybackToggleFreecam}, 188, 0}, + {IT_CALL | IT_STRING, "Stop Playback", NULL, "M_PEXIT", {.routine = M_PlaybackQuit}, 204, 0}, }; menu_t PAUSE_PlaybackMenuDef = { @@ -153,19 +153,19 @@ static void M_PlaybackTick(void) // Toggle items if (paused) { - PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_DISABLED; - PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING; + PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = IT_DISABLED; + PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = IT_CALL|IT_STRING; - if (itemOn >= playback_rewind && itemOn <= playback_fastforward) - itemOn += playback_backframe - playback_rewind; + if (itemOn >= playback_pause && itemOn <= playback_fastforward) + itemOn += playback_resume - playback_pause; } else { - PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_CALL|IT_STRING; - PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_DISABLED; + PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = IT_CALL|IT_STRING; + PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = IT_DISABLED; - if (itemOn >= playback_backframe && itemOn <= playback_advanceframe) - itemOn -= playback_backframe - playback_rewind; + if (itemOn >= playback_resume && itemOn <= playback_advanceframe) + itemOn -= playback_resume - playback_pause; } if (modeattacking) @@ -173,10 +173,10 @@ static void M_PlaybackTick(void) for (i = playback_viewcount; i <= playback_director; i++) PAUSE_PlaybackMenu[i].status = IT_DISABLED; - PAUSE_PlaybackMenu[playback_freecam].mvar1 = 72; - PAUSE_PlaybackMenu[playback_quit].mvar1 = 88; + PAUSE_PlaybackMenu[playback_freecam].mvar1 = 88; + PAUSE_PlaybackMenu[playback_quit].mvar1 = 104; - currentMenu->x = BASEVIDWIDTH/2 - 52; + currentMenu->x = BASEVIDWIDTH/2 - 60; } else { @@ -189,11 +189,11 @@ static void M_PlaybackTick(void) for (i = r_splitscreen+1; i < 4; i++) PAUSE_PlaybackMenu[playback_view1+i].status = IT_DISABLED; - PAUSE_PlaybackMenu[playback_freecam].mvar1 = 172; - PAUSE_PlaybackMenu[playback_quit].mvar1 = 188; + PAUSE_PlaybackMenu[playback_freecam].mvar1 = 188; + PAUSE_PlaybackMenu[playback_quit].mvar1 = 204; - //currentMenu->x = BASEVIDWIDTH/2 - 94; - currentMenu->x = BASEVIDWIDTH/2 - 96; + //currentMenu->x = BASEVIDWIDTH/2 - 102; + currentMenu->x = BASEVIDWIDTH/2 - 104; } } @@ -204,9 +204,38 @@ void M_SetPlaybackMenuPointer(void) void M_PlaybackRewind(INT32 choice) { - (void)choice; + const tic_t curleveltime = leveltime; + + if (choice == playback_rewind) + { + demo.simplerewind = (paused ? DEMO_REWIND_PAUSE : DEMO_REWIND_RESUME); + menuactive = false; + } + G_DoPlayDemo(NULL); // Restart the current demo - M_ClearMenus(true); + + if (demo.simplerewind) + { + if (curleveltime > 5*TICRATE) + { + g_fast_forward = curleveltime - (5 * TICRATE); + g_fast_forward_clock_stop = INFTICS; //I_GetTime() + 2 * TICRATE; -- maybe? + } + else + { + if (demo.simplerewind == DEMO_REWIND_PAUSE) + { + paused = true; + S_PauseAudio(); + } + demo.simplerewind = DEMO_REWIND_OFF; + } + menuactive = true; + } + else + { + M_ClearMenus(true); + } } void M_PlaybackPause(INT32 choice) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index d6ceb5c19..8dd1b1e93 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8474,7 +8474,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) wipegamestate = gamestate; // Don't fade if reloading the gamestate // Encore mode fade to pink to white // This is handled BEFORE sounds are stopped. - else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE) + else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE && !demo.simplerewind) { if (rendermode != render_none) { @@ -8545,7 +8545,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Let's fade to white here // But only if we didn't do the encore startup wipe - if (!reloadinggamestate) + if (demo.attract || demo.simplerewind) + { + // Leave the music alone! We're already playing what we want! + // Pull from RNG even though music will never change + // To silence playback has desynced warning + P_Random(PR_MUSICSELECT); + } + else if (!reloadinggamestate) { int wipetype = wipe_level_toblack; @@ -8558,15 +8565,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)); #endif - if (demo.attract) - { - ; // Leave the music alone! We're already playing what we want! - - // Pull from RNG even though music will never change - // To silence playback has desynced warning - P_Random(PR_MUSICSELECT); - } - else if (K_PodiumSequence()) + if (K_PodiumSequence()) { // mapmusrng is set by local player position in K_ResetCeremony P_LoadLevelMusic(); diff --git a/src/p_tick.c b/src/p_tick.c index 206cdaf4c..bb1013312 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -1076,7 +1076,7 @@ void P_Ticker(boolean run) } } - if (g_fast_forward == 0) + if (g_fast_forward == 0 || demo.simplerewind) { timeinmap++; } diff --git a/src/st_stuff.c b/src/st_stuff.c index fef6481dc..e8196e57e 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -788,7 +788,7 @@ patch_t *ST_getRoundPicture(boolean small) // void ST_runTitleCard(void) { - boolean run = !(paused || P_AutoPause() || g_fast_forward > 0); + boolean run = !(paused || P_AutoPause() || (g_fast_forward > 0 && demo.simplerewind == DEMO_REWIND_OFF)); INT32 auxticker; boolean doroundicon = (ST_getRoundPicture(false) != NULL); From 860b888460992d73767a17d969b5289c64ce8005 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 23:12:39 +0100 Subject: [PATCH 094/107] P_FreeLevelState A chunk of P_LoadLevel, extracted. Calling it in D_ClearState should clear up some of the straggling pointers that might cause Laz' reported issue. With any luck, it should also significantly reduce the amount of memory the menu uses. --- src/d_main.cpp | 3 +- src/p_setup.cpp | 77 ++++++++++++++++++++++++++----------------------- src/p_setup.h | 1 + 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index d660cf3a7..e36e8f805 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1245,7 +1245,8 @@ void D_ClearState(void) if (gamedata && gamedata->deferredsave) G_SaveGameData(); - K_UnsetDialogue(); + P_FreeLevelState(); + P_InvalidateThinkersWithoutInit(); G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 8dd1b1e93..8d8b6e55a 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8388,6 +8388,46 @@ void P_LoadLevelMusic(void) Music_ResetLevelVolume(); } +void P_FreeLevelState(void) +{ + if (numsectors) + { + F_EndTextPrompt(false, true); + K_UnsetDialogue(); + + ACS_InvalidateMapScope(); + LUA_InvalidateLevel(); + + Obj_ClearCheckpoints(); + + sector_t *ss; + for (ss = sectors; sectors+numsectors != ss; ss++) + { + Z_Free(ss->attached); + Z_Free(ss->attachedsolid); + } + + // This is the simplest guard against double frees. + // No valid map has zero sectors. Or, come to think + // of it, less than two in general! ~toast 310525 + numsectors = 0; + } + + // Clear pointers that would be left dangling by the purge + R_FlushTranslationColormapCache(); + +#ifdef HWRENDER + // Free GPU textures before freeing patches. + if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED)) + HWR_ClearAllTextures(); +#endif + + G_FreeGhosts(); // ghosts are allocated with PU_LEVEL + Patch_FreeTag(PU_PATCH_LOWPRIORITY); + Patch_FreeTag(PU_PATCH_ROTATED); + Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); +} + /** Loads a level from a lump or external wad. * * \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot. @@ -8401,7 +8441,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // 99% of the things already did, so. // Map header should always be in place at this point INT32 i, ranspecialwipe = 0; - sector_t *ss; virtlump_t *encoreLump = NULL; levelloading = true; @@ -8645,41 +8684,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } } - /* - if (!titlemapinaction) - wipegamestate = GS_LEVEL; - */ - - // Close text prompt before freeing the old level - F_EndTextPrompt(false, true); - - K_UnsetDialogue(); - - ACS_InvalidateMapScope(); - - LUA_InvalidateLevel(); - - Obj_ClearCheckpoints(); - - for (ss = sectors; sectors+numsectors != ss; ss++) - { - Z_Free(ss->attached); - Z_Free(ss->attachedsolid); - } - - // Clear pointers that would be left dangling by the purge - R_FlushTranslationColormapCache(); - -#ifdef HWRENDER - // Free GPU textures before freeing patches. - if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED)) - HWR_ClearAllTextures(); -#endif - - G_FreeGhosts(); // ghosts are allocated with PU_LEVEL - Patch_FreeTag(PU_PATCH_LOWPRIORITY); - Patch_FreeTag(PU_PATCH_ROTATED); - Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + P_FreeLevelState(); R_InitializeLevelInterpolators(); diff --git a/src/p_setup.h b/src/p_setup.h index b16db6bb5..0430cdfb5 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -107,6 +107,7 @@ extern mapthing_t *mapthings; void P_SetupLevelSky(const char *skytexname, boolean global); void P_RespawnThings(void); +void P_FreeLevelState(void); void P_ResetLevelMusic(void); boolean P_UseContinuousLevelMusic(void); void P_LoadLevelMusic(void); From 71e1179030aea0271793ddc420a0db67d85c8f6c Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 14:57:06 +0100 Subject: [PATCH 095/107] M_MenuToLevelPreamble: Seriously clean up the process of starting a course from the menu A lot of messy, copypasted boilerplate has been bundled together into a single function. Programmers can now fire up a Match Race, the most basic type of gameplay, from menu code in only four steps (other modes take a little more attention): - **M_MenuToLevelPreamble(UINT8 - splitscreen players #, boolean - false to extend wipe/true for short wipe)** - set restoremenu - D_MapChange(...) - M_ClearMenus(...) Includes the following fixes: - Encore no longer has over-long wipes when started from menu, only standard-length - "Boss Intro" and Encore start-of-round sounds will always play, even if no Title Card is drawn - No long wipe when restarting a Time Attack run - Auto Encore and Auto Gamespeed are no longer accidentially forced if you've manually changed them to Off and Gear 2 before starting netgame --- src/g_game.c | 40 +++++--- src/k_menu.h | 1 + src/menus/play-local-race-time-attack.c | 40 ++++---- src/menus/transient/cup-select.c | 25 +---- src/menus/transient/level-select.c | 120 ++++++++++++------------ src/p_setup.cpp | 2 +- src/st_stuff.c | 2 + 7 files changed, 109 insertions(+), 121 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 8f05f30ab..2ae89c184 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1245,26 +1245,36 @@ void G_StartTitleCard(void) // prepare status bar ST_startTitleCard(); // <-- always must be called to init some variables - // The title card has been disabled for this map. - // Oh well. - if (demo.simplerewind || !G_IsTitleCardAvailable()) - { - WipeStageTitle = false; + if (demo.simplerewind) return; + + sfxenum_t kstart = 0; + + if (K_CheckBossIntro() == true) + { + kstart = sfx_ssa021; } + else if (encoremode) + { + kstart = sfx_ruby2; + } + + if (kstart) + { + // Play the guaranteed alt sounds + S_StartSound(NULL, kstart); + } + + if (!G_IsTitleCardAvailable()) + return; // start the title card WipeStageTitle = (gamestate == GS_LEVEL); - // play the sound - if (WipeStageTitle) + if (WipeStageTitle && !kstart) { - sfxenum_t kstart = sfx_kstart; - if (K_CheckBossIntro() == true) - kstart = sfx_ssa021; - else if (encoremode == true) - kstart = sfx_ruby2; - S_StartSound(NULL, kstart); + // Play the standard titlecard sound + S_StartSound(NULL, sfx_kstart); } } @@ -5379,7 +5389,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr S_ResumeAudio(); } - prevencoremode = ((!Playing()) ? false : encoremode); + prevencoremode = encoremode; encoremode = pencoremode; legitimateexit = false; // SRB2Kart @@ -5831,7 +5841,7 @@ boolean G_GetExitGameFlag(void) // Same deal with retrying. void G_SetRetryFlag(void) { - if (retrying == false) + if (retrying == false && grandprixinfo.gp) { grandprixinfo.rank.continuesUsed++; } diff --git a/src/k_menu.h b/src/k_menu.h index 1a4dc3066..fbf8594b8 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -944,6 +944,7 @@ void M_CupSelectTick(void); void M_LevelSelectHandler(INT32 choice); void M_LevelSelectTick(void); +void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe); void M_LevelSelected(INT16 add, boolean menuupdate); boolean M_LevelSelectCupSwitch(boolean next, boolean skipones); diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index b682cfe07..7ac7343bf 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -614,34 +614,19 @@ void M_StartTimeAttack(INT32 choice) { modeattacking |= ATTACKING_SPB; } + + if (gamestate == GS_MENU) + { + encoremode = true; // guarantees short wipe + } + modeprefix = "spb-"; } // DON'T SOFTLOCK CON_ToggleOff(); - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - - splitscreen = 0; - SplitScreen_OnChange(); - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - S_StopMusicCredit(); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, false); + M_MenuToLevelPreamble(0, (gamestate != GS_MENU || cv_dummyspbattack.value == 1)); gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder); @@ -659,8 +644,17 @@ void M_StartTimeAttack(INT32 choice) restoreMenu = &PLAY_TimeAttackDef; + D_MapChange( + levellist.choosemap+1, + levellist.newgametype, + (cv_dummyspbattack.value == 1), + true, + 1, + false, + false + ); + M_ClearMenus(true); - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_dummyspbattack.value == 1), 1, 1, false, false); G_UpdateTimeStickerMedals(levellist.choosemap, true); } diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index 8fa830094..f8c5266f3 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -57,26 +57,7 @@ static void M_StartCup(UINT8 entry) entry = UINT8_MAX; } - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - S_StopMusicCredit(); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } + M_MenuToLevelPreamble(ssplayers, false); if (entry == UINT8_MAX) { @@ -135,10 +116,6 @@ static void M_StartCup(UINT8 entry) } } - paused = false; - - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - M_ClearMenus(true); restoreMenu = &PLAY_CupSelectDef; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index a0c57ae8c..5745c82b9 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -657,6 +657,11 @@ void M_LevelSelectInit(INT32 choice) case 0: levellist.levelsearch.grandprix = false; levellist.levelsearch.timeattack = false; + + CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); + CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); + CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto Gear" : cv_dummykartspeed.string); + break; case 1: levellist.levelsearch.grandprix = false; @@ -689,6 +694,40 @@ void M_LevelSelectInit(INT32 choice) } } +void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe) +{ + cht_debug = 0; + + if (demo.playback) + G_StopDemo(); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + paused = false; + + S_StopMusicCredit(); + + if (!nowipe) + { + S_StartSound(NULL, sfx_s3k63); + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + } + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); +} + void M_LevelSelected(INT16 add, boolean menuupdate) { UINT8 i = 0; @@ -733,70 +772,35 @@ void M_LevelSelected(INT16 add, boolean menuupdate) { if (gamestate == GS_MENU) { - UINT8 ssplayers = levellist.levelsearch.tutorial ? 0 : cv_splitplayers.value-1; - - netgame = false; multiplayer = true; - strlcpy(connectedservername, cv_servername.string, MAXSERVERNAME); + M_MenuToLevelPreamble( + (levellist.levelsearch.tutorial + ? 0 + : cv_splitplayers.value-1 + ), + ( + (cv_kartencore.value == 1) + && (gametypes[levellist.newgametype]->rules & GTR_ENCORE) + ) + ); - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - - /*if (levellist.choosemap == 0) - levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - S_StopMusicCredit(); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - - if (!levellist.netgame) - CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); - - CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); - CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto Gear" : cv_dummykartspeed.string); - - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - - if (levellist.netgame == true) - { - restoreMenu = &PLAY_MP_OptSelectDef; - } - else /*if (!M_GameTrulyStarted() || - levellist.levelsearch.tutorial)*/ - { - restoreMenu = currentMenu; - } + restoreMenu = (levellist.netgame) + ? &PLAY_MP_OptSelectDef + : currentMenu; restorelevellist = levellist; } - else - { - // directly do the map change - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } + + D_MapChange( + levellist.choosemap+1, + levellist.newgametype, + (cv_kartencore.value == 1), + true, + 1, + false, + false + ); M_ClearMenus(true); } diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 8d8b6e55a..2d11cc8d5 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8513,7 +8513,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) wipegamestate = gamestate; // Don't fade if reloading the gamestate // Encore mode fade to pink to white // This is handled BEFORE sounds are stopped. - else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE && !demo.simplerewind) + else if (encoremode && !prevencoremode && !demo.simplerewind) { if (rendermode != render_none) { diff --git a/src/st_stuff.c b/src/st_stuff.c index e8196e57e..44703e839 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -737,6 +737,8 @@ void ST_startTitleCard(void) lt_ticker = lt_exitticker = lt_lasttic = 0; lt_endtime = 4*TICRATE; // + (10*NEWTICRATERATIO); lt_fade = 0; + + WipeStageTitle = false; } // From 57cdb4fcb50a73069210ea54b6622a5567b4b9ff Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 15:03:46 +0100 Subject: [PATCH 096/107] Time Attack respawn button: Specify hold behaviour Now that TA wipes are their intended length, holding Respawn during Time Attack for more than a single frame repeatedly restarts the run. This is not ideal, and frankly wasteful of CPU. Here is the specified replacement for this case: - Holding gc_respawn will hold on a black(/white) screen before mapload. - Allows for taking a conscious breather (or opportunity to curse) in the middle of long Time Attack sessions. - Ticcmdbuilder will not interpret gc_respawn into BT_ constants in Time Attack contexts at all. - Fixes the occasionally visible E-Brake when coming out of this breather state. --- src/g_build_ticcmd.cpp | 5 ++++- src/k_menufunc.c | 2 +- src/p_setup.cpp | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index b8d45e3dc..f32c1f509 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -404,7 +404,10 @@ class TiccmdBuilder map(gc_item, BT_ATTACK); // fire map(gc_lookback, BT_LOOKBACK); // rear view - map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn + if (!modeattacking) + { + map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn + } map(gc_vote, BT_VOTE); // mp general function button // lua buttons a thru c diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 0e3b1385d..3631441b0 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -466,7 +466,7 @@ boolean M_Responder(event_t *ev) if (Playing() && !demo.playback) { // Quick Retry (Y in modeattacking) - if (modeattacking && G_PlayerInputDown(0, gc_y, splitscreen + 1) == true) + if (modeattacking && G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true) { M_TryAgain(0); return true; diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 2d11cc8d5..27e808c7c 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8681,6 +8681,38 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); + + // Hold respawn to keep waiting until you're ready + if (G_IsModeAttackRetrying() && !demo.playback) + { + nowtime = lastwipetic; + while (G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true) + { + while (!((nowtime = I_GetTime()) - lastwipetic)) + { + I_Sleep(cv_sleep.value); + I_UpdateTime(); + } \ + + I_OsPolling(); + G_ResetAllDeviceResponding(); + + for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) + { + HandleGamepadDeviceEvents(&events[eventtail]); + G_MapEventsToControls(&events[eventtail]); + } + + lastwipetic = nowtime; + if (moviemode && rendermode == render_opengl) + M_LegacySaveFrame(); + else if (moviemode && rendermode == render_soft) + I_CaptureVideoFrame(); + NetKeepAlive(); + } + + //wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE); + } } } From 5bfbfccdaebf96e5a339611ad496ec5d23b62442 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 22:17:23 +0100 Subject: [PATCH 097/107] M_LevelSelectInit: Catch potentially invalid skin dereference for Hivolt message, just in case --- src/menus/transient/level-select.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 5745c82b9..78709305c 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -681,10 +681,14 @@ void M_LevelSelectInit(INT32 choice) gt = menugametype; } - if (levellist.levelsearch.timeattack && gt == GT_RACE && skins[R_SkinAvailableEx(cv_skin[0].string, false)].flags & SF_HIVOLT) + if (levellist.levelsearch.timeattack && gt == GT_RACE) { - M_StartMessage("A long-forgotten power...", "You are using a \x82prototype engine\x80.\nRecords will not be saved.", NULL, MM_NOTHING, NULL, NULL); - S_StartSound(NULL, sfx_s3k81); + const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false); + if (skinid >= 0 && (skins[skinid].flags & SF_HIVOLT)) + { + M_StartMessage("A long-forgotten power...", "You are using a \x82prototype engine\x80.\nRecords will not be saved.", NULL, MM_NOTHING, NULL, NULL); + S_StartSound(NULL, sfx_s3k81); + } } if (!M_LevelListFromGametype(gt)) From 830cfe76da28877be4be273a3883800c8e9543d3 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 May 2025 22:25:52 +0100 Subject: [PATCH 098/107] Round Queue UI on Level Select - Only works in Match Race and Online level selects - Shows in-progress state on Pause and Cup Grid, but no direct influence available on those screens - Z to queue current highlighted map - Pending until you press A, then sent to server - When done online, tries to send one at a time to avoid overloading connection - C to clear - Removes one from pending first - If you're out of pending maps, prompts whether you want to clear server's queue --- src/d_main.cpp | 1 + src/d_netcmd.c | 2 +- src/d_netcmd.h | 1 + src/g_game.c | 1 + src/g_game.h | 9 + src/k_hud.cpp | 40 +++++ src/k_hud.h | 1 + src/k_menu.h | 2 + src/k_menudraw.c | 85 ++++----- src/k_vote.c | 24 +-- src/menus/main-goner.cpp | 1 + src/menus/play-online-host.c | 1 + src/menus/transient/level-select.c | 273 ++++++++++++++++++++++++++++- src/y_inter.cpp | 134 +++++++++++--- src/y_inter.h | 2 +- 15 files changed, 487 insertions(+), 90 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index e36e8f805..98590c30a 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1213,6 +1213,7 @@ void D_ClearState(void) // Reset GP and roundqueue memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); memset(&roundqueue, 0, sizeof(struct roundqueue)); + memset(&menuqueue, 0, sizeof(struct menuqueue)); // empty some other semi-important state maptol = 0; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 56c1eeef9..2be94c176 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2984,7 +2984,7 @@ static void Command_RestartLevel(void) D_MapChange(gamemap, g_lastgametype, newencore, false, 0, false, false); } -static void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode) +void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode) { UINT8 flags = 0; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 50b5339d7..7391d0921 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -206,6 +206,7 @@ size_t WeaponPref_Parse(const UINT8 *p, INT32 playernum); void D_SendPlayerConfig(UINT8 n); void Command_ExitGame_f(void); void Command_Retry_f(void); +void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode); boolean G_GamestateUsesExitLevel(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); diff --git a/src/g_game.c b/src/g_game.c index 2ae89c184..a081e437b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4106,6 +4106,7 @@ doremove: // Next map apparatus struct roundqueue roundqueue; +struct menuqueue menuqueue; void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted) { diff --git a/src/g_game.h b/src/g_game.h index 14ee6494a..7996a8e61 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -75,6 +75,15 @@ extern struct roundqueue roundentry_t entries[ROUNDQUEUE_MAX]; // Entries in the round queue } roundqueue; +extern struct menuqueue +{ + // Degenerate version of roundqueue exclusively for menu use. + UINT8 size; + UINT8 sending; + UINT8 anchor; + roundentry_t entries[ROUNDQUEUE_MAX]; +} menuqueue; + void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted); void G_MapIntoRoundQueue(UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted); void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 91bd054f1..bba3d1d9b 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -1536,6 +1536,46 @@ void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, p ); } +void K_DrawMapAsFace(INT32 x, INT32 y, UINT32 flags, UINT16 map, const UINT8 *colormap) +{ + const fixed_t iconHeight = (14 << FRACBITS); + const fixed_t iconWidth = (iconHeight * 320) / 200; + INT32 unit = 1; + fixed_t mul = FRACUNIT; + if (flags & V_NOSCALESTART) + { + unit = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy); + mul = 1; + } + + V_DrawFill( + x, + y, + 16 * unit, + 16 * unit, + (flags & ~V_FLIP) + ); + + V_SetClipRect( + (x + unit) * mul, + (y + unit) * mul, + (14 * unit) * mul, + (14 * unit) * mul, + (flags & ~V_FLIP) + ); + + K_DrawMapThumbnail( + ((x + unit) * FRACUNIT) - (iconWidth - iconHeight)/2, + ((y + unit) * FRACUNIT), + iconWidth, + flags, + map, + colormap + ); + + V_ClearClipRect(); +} + // see also K_DrawNameTagItemSpy static void K_drawKartItem(void) { diff --git a/src/k_hud.h b/src/k_hud.h index 038be77f2..6a991bddf 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -60,6 +60,7 @@ void K_drawKart4PTimestamp(void); void K_drawEmeraldWin(boolean overlay); void K_DrawMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, UINT16 map, const UINT8 *colormap); void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, patch_t *patch, const UINT8 *colormap); +void K_DrawMapAsFace(INT32 x, INT32 y, UINT32 flags, UINT16 map, const UINT8 *colormap); void K_drawTargetHUD(const vector3_t *origin, player_t *player); void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean pressed); void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic); diff --git a/src/k_menu.h b/src/k_menu.h index fbf8594b8..28db4810e 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -923,6 +923,7 @@ typedef struct levellist_s { UINT8 guessgt; levelsearch_t levelsearch; boolean netgame; // Start the game in an actual server + boolean canqueue; menu_t *backMenu; } levellist_t; @@ -944,6 +945,7 @@ void M_CupSelectTick(void); void M_LevelSelectHandler(INT32 choice); void M_LevelSelectTick(void); +INT16 M_LevelFromScrolledList(INT16 add); void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe); void M_LevelSelected(INT16 add, boolean menuupdate); boolean M_LevelSelectCupSwitch(boolean next, boolean skipones); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a06a6fec1..0c02b15ba 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -755,33 +755,40 @@ static void M_DrawMenuTyping(void) } -// Largely replaced by boxed drawing mode in K_DrawGameControl and rich text -/* -static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 worky, boolean push, boolean rightaligned) +static void M_DrawPauseRoundQueue(INT16 offset, boolean canqueue) { - INT32 buttonwidth = V_StringWidth(text, 0) + 2; + y_data_t standings; + memset(&standings, 0, sizeof (standings)); - if (rightaligned) + if (gamestate == GS_MENU) { - (*workx) -= buttonwidth; - } - - if (push) - { - worky += 2; + standings.mainplayer = MAXPLAYERS; } else { - V_DrawFill((*workx)-1, worky+10, buttonwidth, 2, 24); + standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer); } - V_DrawFill((*workx)-1, worky, buttonwidth, 10, 16); - V_DrawString( - (*workx), worky + 1, - 0, text - ); + // See also G_GetNextMap, Y_CalculateMatchData + if ( + canqueue == false + && grandprixinfo.gp == true + && netgame == false // TODO netgame Special Mode support + && grandprixinfo.gamespeed >= KARTSPEED_NORMAL + && roundqueue.size > 1 + && roundqueue.entries[roundqueue.size - 1].rankrestricted == true + && ( + gamedata->everseenspecial == true + || roundqueue.position == roundqueue.size + ) + ) + { + // Additional cases in which it should always be shown. + standings.showrank = true; + } + + Y_RoundQueueDrawer(&standings, offset, false, false, canqueue); } -*/ // Draw the message popup submenu void M_DrawMenuMessage(void) @@ -991,6 +998,16 @@ void M_Drawer(void) // Draw message overlay when needed M_DrawMenuMessage(); + + if ( + ( + currentMenu == &PLAY_LevelSelectDef + || currentMenu == &PLAY_CupSelectDef + ) && levellist.canqueue + ) + { + M_DrawPauseRoundQueue(0, true); + } } if (menuwipe) @@ -6288,30 +6305,11 @@ void M_DrawPause(void) V_DrawCenteredLSTitleLowString(220 + offset*2, 103, mainflags, word2); } + const boolean rulescheck = (K_CanChangeRules(false) && (server || IsPlayerAdmin(consoleplayer))); + boolean drawqueue = (rulescheck && (menuqueue.size > 0)); + if (gamestate != GS_INTERMISSION && roundqueue.size > 0) { - y_data_t standings; - memset(&standings, 0, sizeof (standings)); - - standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer); - - // See also G_GetNextMap, Y_CalculateMatchData - if ( - grandprixinfo.gp == true - && netgame == false // TODO netgame Special Mode support - && grandprixinfo.gamespeed >= KARTSPEED_NORMAL - && roundqueue.size > 1 - && roundqueue.entries[roundqueue.size - 1].rankrestricted == true - && ( - gamedata->everseenspecial == true - || roundqueue.position == roundqueue.size - ) - ) - { - // Additional cases in which it should always be shown. - standings.showrank = true; - } - if (roundqueue.position > 0 && roundqueue.position <= roundqueue.size) { patch_t *smallroundpatch = ST_getRoundPicture(true); @@ -6328,7 +6326,7 @@ void M_DrawPause(void) V_DrawCenteredMenuString(24, 167 + offset/2, V_YELLOWMAP, M_GetGameplayMode()); - Y_RoundQueueDrawer(&standings, offset/2, false, false); + drawqueue = true; } else if (gametype == GT_TUTORIAL) { @@ -6347,6 +6345,11 @@ void M_DrawPause(void) { V_DrawMenuString(4, 188 + offset/2, V_YELLOWMAP, M_GetGameplayMode()); } + + if (drawqueue) + { + M_DrawPauseRoundQueue(offset/2, rulescheck); + } } void M_DrawKickHandler(void) diff --git a/src/k_vote.c b/src/k_vote.c index 419d0feec..e5eeb5c63 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -700,35 +700,13 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt } else if (vote.stage_striking == false) // Angry map { - const fixed_t iconHeight = (14 << FRACBITS); - const fixed_t iconWidth = (iconHeight * 320) / 200; - - V_DrawFill( + K_DrawMapAsFace( fx + fw - whiteSq + dupx, fy + fh - whiteSq + dupy, - whiteSq, - whiteSq, - 0|flags|V_NOSCALESTART - ); - - V_SetClipRect( - fx + fw - whiteSq + (2 * dupx), - fy + fh - whiteSq + (2 * dupy), - whiteSq - (2 * dupx), - whiteSq - (2 * dupy), - flags|V_NOSCALESTART - ); - - K_DrawMapThumbnail( - ((fx + fw - whiteSq + (2 * dupx)) * FRACUNIT) - (iconWidth - iconHeight), - (fy + fh - whiteSq + (2 * dupy)) * FRACUNIT, - iconWidth, flags | V_NOSCALESTART | ((encore == true) ? V_FLIP : 0), g_voteLevels[v][0], NULL ); - - V_ClearClipRect(); } } } diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 5539ed5a2..75b41d172 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -1272,6 +1272,7 @@ void M_GonerTutorial(INT32 choice) // Please also see M_LevelSelectInit as called in extras-1.c levellist.netgame = false; + levellist.canqueue = false; levellist.levelsearch.checklocked = true; levellist.levelsearch.grandprix = false; levellist.levelsearch.timeattack = false; diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index a52f04822..ccc3214c2 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -172,6 +172,7 @@ void M_MPSetupNetgameMapSelect(INT32 choice) // Yep, we'll be starting a netgame. levellist.netgame = true; + levellist.canqueue = true; // Make sure we reset those levellist.levelsearch.timeattack = false; levellist.levelsearch.checklocked = true; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 78709305c..34069e02b 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -21,6 +21,7 @@ #include "../../v_video.h" #include "../../g_game.h" // G_GetBackupCupData #include "../../p_saveg.h" // cupsavedata +#include "../../d_netcmd.h" // Handle_MapQueueSend cupheader_t dummy_lostandfound; @@ -657,6 +658,7 @@ void M_LevelSelectInit(INT32 choice) case 0: levellist.levelsearch.grandprix = false; levellist.levelsearch.timeattack = false; + levellist.canqueue = true; CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); @@ -666,10 +668,12 @@ void M_LevelSelectInit(INT32 choice) case 1: levellist.levelsearch.grandprix = false; levellist.levelsearch.timeattack = true; + levellist.canqueue = false; break; case 2: levellist.levelsearch.grandprix = true; levellist.levelsearch.timeattack = false; + levellist.canqueue = false; break; default: CONS_Alert(CONS_WARNING, "Bad level select init\n"); @@ -732,7 +736,7 @@ void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe) SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); } -void M_LevelSelected(INT16 add, boolean menuupdate) +INT16 M_LevelFromScrolledList(INT16 add) { UINT8 i = 0; INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); @@ -749,6 +753,13 @@ void M_LevelSelected(INT16 add, boolean menuupdate) add--; } + return map; +} + +void M_LevelSelected(INT16 add, boolean menuupdate) +{ + INT16 map = M_LevelFromScrolledList(add); + if (map >= nummapheaders) { // This shouldn't happen @@ -810,6 +821,82 @@ void M_LevelSelected(INT16 add, boolean menuupdate) } } +static void M_MenuQueueStopSend(INT32 ch) +{ + (void)ch; + + memset(&menuqueue, 0, sizeof(struct menuqueue)); +} + +static void M_MenuQueueSelectedLocal(void) +{ + UINT8 i = 0; + + for (; i < menuqueue.size; i++) + { + G_MapIntoRoundQueue( + menuqueue.entries[i].mapnum, + menuqueue.entries[i].gametype, + menuqueue.entries[i].encore, + false + ); + } + + roundqueue.netcommunicate = true; + + if (gamestate == GS_MENU) + { + levellist.choosemap = roundqueue.entries[0].mapnum; + + multiplayer = true; + + M_MenuToLevelPreamble( + cv_splitplayers.value-1, + ( + roundqueue.entries[0].encore + && (gametypes[roundqueue.entries[0].gametype]->rules & GTR_ENCORE) + ) + ); + + restoreMenu = (levellist.netgame) + ? &PLAY_MP_OptSelectDef + : currentMenu; + + restorelevellist = levellist; + + roundqueue.position = roundqueue.roundnum = 1; + + D_MapChange( + roundqueue.entries[0].mapnum + 1, + roundqueue.entries[0].gametype, + roundqueue.entries[0].encore, + true, + 1, + false, + roundqueue.entries[0].rankrestricted + ); + + M_MenuQueueStopSend(MA_NONE); + + M_ClearMenus(true); + } + else + { + if (gamestate == GS_LEVEL && roundqueue.position == 0) + { + menuqueue.sending = UINT8_MAX; + } + else + { + S_StartSound(NULL, sfx_chchng); + + M_MenuQueueStopSend(MA_NONE); + + M_ClearMenus(true); + } + } +} + boolean M_LevelSelectCupSwitch(boolean next, boolean skipones) { levelsearch_t templevelsearch = levellist.levelsearch; @@ -891,6 +978,44 @@ boolean M_LevelSelectCupSwitch(boolean next, boolean skipones) } } +static void M_MenuQueueResponse(INT32 ch) +{ + M_ClearMenus(false); + + if (ch != MA_YES) + return; + + if (!(server || (IsPlayerAdmin(consoleplayer)))) + return; + + if (!(gamestate == GS_LEVEL && roundqueue.position == 0)) + return; + + SendNetXCmd(XD_EXITLEVEL, NULL, 0); +} + + +static void M_ClearQueueResponse(INT32 ch) +{ + if (ch != MA_YES) + return; + + if (!(server || (IsPlayerAdmin(consoleplayer)))) + return; + + S_StartSound(NULL, sfx_slip); + + if (netgame) + { + if (roundqueue.size) + { + Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false); + } + return; + } + + memset(&roundqueue, 0, sizeof(struct roundqueue)); +} void M_LevelSelectHandler(INT32 choice) { const UINT8 pid = 0; @@ -902,6 +1027,11 @@ void M_LevelSelectHandler(INT32 choice) return; } + if (menuqueue.sending) + { + return; + } + if (menucmd[pid].dpad_ud > 0) { levellist.cursor++; @@ -934,10 +1064,96 @@ void M_LevelSelectHandler(INT32 choice) M_LevelSelectScrollDest(); - if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) + if (M_MenuConfirmPressed(pid)) { + // Starting immediately OR importing queue + M_SetMenuDelay(pid); - M_LevelSelected(levellist.cursor, true); + + while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX) + menuqueue.size--; + + if (!levellist.canqueue || !menuqueue.size) + { + M_LevelSelected(levellist.cursor, true); + } + else if (netgame) + { + menuqueue.anchor = roundqueue.size; + menuqueue.sending = 1; + + M_StartMessage("Queueing Rounds", + va(M_GetText( + "Attempting to send %d Round%s...\n" + "\n" + "If this is taking longer than you\n" + "expect, exit out of this message.\n" + ), menuqueue.size, (menuqueue.size == 1 ? "" : "s") + ), &M_MenuQueueStopSend, MM_NOTHING, + NULL, + "This is taking too long..." + ); + } + else + { + M_MenuQueueSelectedLocal(); + } + } + else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z)) + { + // Adding to queue + + INT16 map = NEXTMAP_INVALID; + + M_SetMenuDelay(pid); + + if ((roundqueue.size + menuqueue.size) < ROUNDQUEUE_MAX) + { + map = M_LevelFromScrolledList(levellist.cursor); + } + + if (map < nummapheaders) + { + memset(menuqueue.entries+menuqueue.size, 0, sizeof(roundentry_t)); + menuqueue.entries[menuqueue.size].mapnum = map; + menuqueue.entries[menuqueue.size].gametype = levellist.newgametype; + menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1); + + menuqueue.size++; + + S_StartSound(NULL, sfx_s3k4a); + } + else + { + S_StartSound(NULL, sfx_s3kb2); + } + } + else if (levellist.canqueue && M_MenuExtraPressed(pid)) + { + while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX) + menuqueue.size--; + + if (menuqueue.size) + { + S_StartSound(NULL, sfx_shldls); + menuqueue.size--; + } + else if (roundqueue.size) + { + M_StartMessage("Queue Clearing", + va(M_GetText( + "There %s %d Round%s of play queued.\n" + "\n" + "Do you want to empty the queue?\n" + ), + (roundqueue.size == 1 ? "is" : "are"), + roundqueue.size, + (roundqueue.size == 1 ? "" : "s") + ), &M_ClearQueueResponse, MM_YESNO, + "Time to start fresh", + "Not right now" + ); + } } else if (M_MenuBackPressed(pid)) { @@ -952,4 +1168,55 @@ void M_LevelSelectHandler(INT32 choice) void M_LevelSelectTick(void) { + if (!menuqueue.sending) + return; + + if ((menuqueue.sending <= menuqueue.size) // Sending + && (roundqueue.size >= menuqueue.anchor)) // Didn't get it wiped + { + if (!netgame) + return; + + const UINT8 idcount = (roundqueue.size - menuqueue.anchor); + + if (idcount == menuqueue.sending-1) + { + Handle_MapQueueSend( + menuqueue.entries[idcount].mapnum, + menuqueue.entries[idcount].gametype, + menuqueue.entries[idcount].encore + ); + } + if (idcount >= menuqueue.sending-1) + { + menuqueue.sending++; + } + return; + } + + if (menuqueue.size) + { + S_StartSound(NULL, sfx_chchng); + + if (roundqueue.position) + { + M_StopMessage(MA_NONE); + } + else + { + M_StartMessage("Round Queue", + va(M_GetText( + "You just queued %d Round%s of play.\n" + "\n" + "Do you want to skip the current\n" + "course and start them immediately?\n" + ), menuqueue.size, (menuqueue.size == 1 ? "" : "s") + ), &M_MenuQueueResponse, MM_YESNO, + "Let's get going!", + "Not right now" + ); + } + } + + M_MenuQueueStopSend(MA_NONE); } diff --git a/src/y_inter.cpp b/src/y_inter.cpp index b2159285a..d2c509424 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -971,11 +971,15 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) // Handles drawing the bottom-of-screen progression. // Currently requires intermission y_data for animation only. // -void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen) +void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen, boolean adminmode) { if (roundqueue.size == 0) { - return; + if (!adminmode + || menuqueue.size == 0) + { + return; + } } // The following is functionally a hack. @@ -1041,6 +1045,10 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, prize_dot[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMRK4", PU_PATCH)); prize_dot[BPP_DONE] = static_cast(W_CachePatchName("R_RRMRK6", PU_PATCH)); + patch_t *rpmark[2]; + rpmark[0] = static_cast(W_CachePatchName("R_RPMARK", PU_PATCH)); + rpmark[1] = static_cast(W_CachePatchName("R_R2MARK", PU_PATCH)); + UINT8 *colormap = NULL, *oppositemap = NULL; fixed_t playerx = 0, playery = 0; UINT8 pskin = MAXSKINS; @@ -1079,10 +1087,37 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, upwa = true; } - workingqueuesize--; + if (!adminmode) + { + workingqueuesize--; + } } - INT32 widthofroundqueue = 24*(workingqueuesize - 1); + INT32 widthofroundqueue, totalsteps; + + INT32 menusendoffset = 0; + if (menuqueue.sending) + { + if (menuqueue.sending > menuqueue.size) + { + menusendoffset = menuqueue.size; + } + else + { + menusendoffset = menuqueue.sending-1; + } + } + + if (adminmode) + { + totalsteps = std::min(workingqueuesize + (menuqueue.size - menusendoffset), ROUNDQUEUE_MAX); + } + else + { + totalsteps = workingqueuesize; + } + + widthofroundqueue = (totalsteps - 1) * 24; INT32 x = (BASEVIDWIDTH - widthofroundqueue) / 2; INT32 y, basey = 167 + offset; @@ -1091,7 +1126,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, // The following block handles horizontal easing of the // progression bar on the last non-rankrestricted round. - if (standings->showrank == true) + if (!adminmode && standings->showrank == true) { fixed_t percentslide = 0; SINT8 deferxoffs = 0; @@ -1151,12 +1186,22 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, V_DrawMappedPatch(xiter, basey, baseflags, queuebg_upwa, greymap); } + // Draw to left side of screen while (xiter > -bufferspace) { xiter -= 24; V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap); } + // Draw to right side of screen + xiter = x + widthofroundqueue; + while (xiter < BASEVIDWIDTH + bufferspace) + { + xiter += 24; + V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap); + } + + // Actually queued maps for (i = 0; i < workingqueuesize; i++) { // Draw the background, and grab the appropriate line, to the right of the dot @@ -1186,7 +1231,13 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, } else { - V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap); + V_DrawMappedPatch( + x, + basey, + baseflags, + ((workingqueuesize == totalsteps) ? queuebg_flat : queuebg_upwa), + greymap + ); } } @@ -1321,17 +1372,9 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, } else { - // No more line! Fill in background to right edge of screen - xiter = x; - while (xiter < BASEVIDWIDTH + bufferspace) - { - xiter += 24; - V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap); - } - // Handle special entry on the end // (has to be drawn before the semifinal dot due to overlap) - if (standings->showrank == true) + if (!adminmode && standings->showrank == true) { const fixed_t x2 = x + spacetospecial; @@ -1342,7 +1385,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, } else if ( doanimations == true - && roundqueue.position == roundqueue.size-1 + && roundqueue.position == workingqueuesize && timer - interpoffs <= 2*TICRATE ) { @@ -1522,13 +1565,62 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, x += 24; } + totalsteps -= i; + + // Maps in the progress of being queued on the menu + if (adminmode && totalsteps) + { + for (i = menusendoffset; i < (totalsteps + menusendoffset); i++) + { + upwa ^= true; + if (upwa == false) + { + y = basey + 4; + + V_DrawMappedPatch(x, basey, baseflags, queuebg_down, greymap); + } + else + { + y = basey + 12; + + if (i+1 != menuqueue.size) // no more line? + { + V_DrawMappedPatch(x, basey, baseflags, queuebg_upwa, greymap); + } + else + { + V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap); + } + } + + V_DrawMappedPatch( + x - 8, y, + baseflags, + level_dot[BPP_AHEAD], + NULL + ); + + V_DrawMappedPatch( + x - 10, y - 14, + baseflags, + rpmark[0], + NULL + ); + + K_DrawMapAsFace( + x - 9, y - 13, + (baseflags|((menuqueue.entries[i].encore) ? V_FLIP : 0)), + menuqueue.entries[i].mapnum, + NULL + ); + + x += 24; + } + } + // Draw the player position through the round queue! if (playery != 0) { - patch_t *rpmark[2]; - rpmark[0] = static_cast(W_CachePatchName("R_RPMARK", PU_PATCH)); - rpmark[1] = static_cast(W_CachePatchName("R_R2MARK", PU_PATCH)); - // Change alignment playerx -= (10 * FRACUNIT); playery -= (14 * FRACUNIT); @@ -1934,7 +2026,7 @@ skiptallydrawer: goto finalcounter; // Returns early if there's no roundqueue entries to draw - Y_RoundQueueDrawer(&data, 0, true, false); + Y_RoundQueueDrawer(&data, 0, true, false, false); if (netgame) { diff --git a/src/y_inter.h b/src/y_inter.h index 0a92758ee..b98c130d3 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -54,7 +54,7 @@ void Y_Ticker(void); // Specific sub-drawers void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset); -void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen); +void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen, boolean adminmode); void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen); void Y_DrawRankMode(INT32 x, INT32 y, boolean center); From d9faf64463184c900a0c58a914fd6dcda79f54b5 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 1 Jun 2025 01:08:55 +0100 Subject: [PATCH 099/107] Don't show the "start queue?" message outside of level play --- src/menus/transient/level-select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 34069e02b..30c39ef75 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -1198,7 +1198,7 @@ void M_LevelSelectTick(void) { S_StartSound(NULL, sfx_chchng); - if (roundqueue.position) + if (roundqueue.position || gamestate != GS_LEVEL) { M_StopMessage(MA_NONE); } From a37114b6d2beeb88ad7fba4a3af97f551f35b200 Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Sun, 1 Jun 2025 05:25:57 -0400 Subject: [PATCH 100/107] Buff sneaker, invinc, and flameshield-burst Sneaker (85% -> 100% boost buff), invinc at back (10secs -> 15secs), flameshield burst (50 -> 80 thrust) --- src/k_kart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 856ea65d4..c56874192 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3526,7 +3526,7 @@ static void K_GetKartBoostPower(player_t *player) UINT8 i; for (i = 0; i < player->numsneakers; i++) { - ADDBOOST(FRACUNIT*85/100, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 50% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 100% top speed, + 800% acceleration, +50% handling } } @@ -13841,7 +13841,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); UINT32 behindScaled = behind * TICRATE / 4500; - behindScaled = min(behindScaled, 10*TICRATE); + behindScaled = min(behindScaled, 15*TICRATE); K_DoInvincibility(player, max(7u * TICRATE + behindScaled, player->invincibilitytimer + 5u*TICRATE)); @@ -14394,7 +14394,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { P_Thrust( player->mo, player->mo->angle, - FixedMul((50*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed)) + FixedMul((80*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed)) ); player->wavedashboost += TICRATE; From 9893bf999c1836f3a12bc5d20d572aa194ffced0 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 1 Jun 2025 14:26:30 -0400 Subject: [PATCH 101/107] fix math --- src/k_kart.c | 4 ++-- src/p_link.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 856ea65d4..449dbf726 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4165,8 +4165,8 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact) if (amps == 0) return; - UINT32 itemdistance = max(0, min( FRACUNIT, K_GetItemRouletteDistance(player, D_NumPlayersInRace()))); // cap this to FRACUNIT, so it doesn't wrap when turning it into fixed_t - fixed_t itemdistmult = FRACUNIT + max( 0, min(FixedMul(FixedDiv(itemdistance<kartspeed) - (9-player->kartweight)) / 10); // Debug print for scaledamps calculation // CONS_Printf("K_SpawnAmps: player=%s, amps=%d, kartspeed=%d, kartweight=%d, itemdistance=%d, itemdistmult=%0.2f, statscaledamps=%d, distscaledamps=%d\n", diff --git a/src/p_link.cpp b/src/p_link.cpp index bbb2773de..8393ea176 100644 --- a/src/p_link.cpp +++ b/src/p_link.cpp @@ -21,6 +21,7 @@ svg_rocks + using link = mobj_t*; using each_ref = std::initializer_list>; From e8140ae3883f3618aa423407d2d8492b2fb78749 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Jun 2025 12:02:58 -0400 Subject: [PATCH 102/107] Relink player to kartitems --- src/lua_mobjlib.c | 8 ++++++++ src/p_mobj.c | 15 +++++++++++++++ src/p_mobj.h | 2 ++ src/p_saveg.cpp | 11 +++++++++++ 4 files changed, 36 insertions(+) diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 158909815..dd80daf66 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -111,6 +111,7 @@ enum mobj_e { mobj_reappear, mobj_punt_ref, mobj_owner, + mobj_relinkplayer, }; static const char *const mobj_opt[] = { @@ -201,6 +202,7 @@ static const char *const mobj_opt[] = { "reappear", "punt_ref", "owner", + "relinkplayer", NULL}; #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field]) @@ -519,6 +521,9 @@ static int mobj_get(lua_State *L) } LUA_PushUserdata(L, mo->owner, META_MOBJ); break; + case mobj_relinkplayer: + lua_pushinteger(L, mo->relinkplayer); + break; default: // extra custom variables in Lua memory lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS); I_Assert(lua_istable(L, -1)); @@ -934,6 +939,9 @@ static int mobj_set(lua_State *L) P_SetTarget(&mo->owner, owner); } break; + case mobj_relinkplayer: + mo->relinkplayer = luaL_checkinteger(L, 3); + break; default: lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS); I_Assert(lua_istable(L, -1)); diff --git a/src/p_mobj.c b/src/p_mobj.c index 98d377637..0040ccad9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10353,9 +10353,24 @@ void P_MobjThinker(mobj_t *mobj) I_Assert(mobj != NULL); I_Assert(!P_MobjWasRemoved(mobj)); + if (P_IsKartItem(mobj->type) && mobj->target && !P_MobjWasRemoved(mobj->target)) + { + player_t *link = mobj->target->player; + if (link && playeringame[link-players] && !link->spectator) + mobj->relinkplayer = (link-players) + 1; + } + // Remove dead target/tracer. if (mobj->target && P_MobjWasRemoved(mobj->target)) + { P_SetTarget(&mobj->target, NULL); + if (P_IsKartItem(mobj->type) && mobj->relinkplayer && mobj->relinkplayer <= MAXPLAYERS) + { + player_t *relink = &players[mobj->relinkplayer-1]; + if (playeringame[relink-players] && !relink->spectator && relink->mo && !P_MobjWasRemoved(relink->mo)) + P_SetTarget(&mobj->target, relink->mo); + } + } if (mobj->tracer && P_MobjWasRemoved(mobj->tracer)) P_SetTarget(&mobj->tracer, NULL); if (mobj->hnext && P_MobjWasRemoved(mobj->hnext)) diff --git a/src/p_mobj.h b/src/p_mobj.h index 08b41922e..353011ddf 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -450,6 +450,8 @@ struct mobj_t INT32 po_movecount; // Polyobject carrying (NOT savegame, NOT Lua) + UINT8 relinkplayer; // reassociate kartitem target when it dies. ACHTUNG 1-INDEXED + // WARNING: New fields must be added separately to savegame and Lua. }; diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 7fc84d0f8..6e39c8174 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -2999,6 +2999,7 @@ typedef enum MD3_REAPPEAR = 1<<1, MD3_PUNT_REF = 1<<2, MD3_OWNER = 1<<3, + MD3_RELINK_PLAYER = 1<<4, } mobj_diff3_t; typedef enum @@ -3322,6 +3323,8 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 diff3 |= MD3_PUNT_REF; if (mobj->owner) diff3 |= MD3_OWNER; + if (mobj->relinkplayer) + diff3 |= MD3_RELINK_PLAYER; if (diff3 != 0) diff2 |= MD2_MORE; @@ -3612,6 +3615,10 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 { WRITEUINT32(save->p, mobj->owner->mobjnum); } + if (diff3 & MD3_RELINK_PLAYER) + { + WRITEUINT8(save->p, mobj->relinkplayer); + } WRITEUINT32(save->p, mobj->mobjnum); } @@ -4925,6 +4932,10 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { mobj->owner = (mobj_t *)(size_t)READUINT32(save->p); } + if (diff3 & MD3_OWNER) + { + mobj->relinkplayer = READUINT8(save->p); + } // link tid set earlier P_AddThingTID(mobj); From 6c790348b37bef0f3712ab8c62557ef8638f39d3 Mon Sep 17 00:00:00 2001 From: eebrozgi Date: Mon, 2 Jun 2025 20:17:46 +0300 Subject: [PATCH 103/107] Rocket sneaker boost type separated from normal sneaker monkey copypaste --- src/d_player.h | 2 ++ src/k_collide.cpp | 3 +- src/k_kart.c | 80 ++++++++++++++++++++++++++++++------------- src/objects/crate.cpp | 2 +- src/objects/rocks.cpp | 2 +- src/objects/ufo.c | 4 ++- src/p_inter.c | 1 + src/p_mobj.c | 2 +- src/p_saveg.cpp | 3 ++ src/p_tick.c | 4 +-- src/p_user.c | 2 +- 11 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 0c3501227..f9ccf8a6c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -828,6 +828,8 @@ struct player_t UINT8 numsneakers; // Number of stacked sneaker effects UINT16 panelsneakertimer; UINT8 numpanelsneakers; + UINT16 weaksneakertimer; + UINT8 numweaksneakers; UINT8 floorboost; // (0 to 3) - Prevents Sneaker sounds for a brief duration when triggered by a floor panel INT16 growshrinktimer; // > 0 = Big, < 0 = small diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 77cf778c8..d42be35d0 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -973,6 +973,7 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) attackerPlayer->spindashboost = 0; attackerPlayer->sneakertimer = 0; attackerPlayer->panelsneakertimer = 0; + attackerPlayer->weaksneakertimer = 0; attackerPlayer->instaWhipCharge = 0; attackerPlayer->flashing = 0; @@ -1211,7 +1212,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { auto shouldSteal = [](mobj_t *t1, mobj_t *t2) { - return ((t1->player->sneakertimer > 0 || t1->player->panelsneakertimer > 0) + return ((t1->player->sneakertimer > 0 || t1->player->panelsneakertimer > 0 || t1->player->weaksneakertimer > 0) && !P_PlayerInPain(t1->player) && (t1->player->flashing == 0)); }; diff --git a/src/k_kart.c b/src/k_kart.c index 8ce6ba3f8..8c742d069 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3012,7 +3012,7 @@ void K_MomentumToFacing(player_t *player) boolean K_ApplyOffroad(const player_t *player) { - if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer || player->panelsneakertimer) + if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer || player->panelsneakertimer|| player->weaksneakertimer) return false; if (K_IsRidingFloatingTop(player)) return false; @@ -3021,7 +3021,7 @@ boolean K_ApplyOffroad(const player_t *player) boolean K_SlopeResistance(const player_t *player) { - if (player->invincibilitytimer || player->sneakertimer || player->panelsneakertimer || player->tiregrease || player->flamedash) + if (player->invincibilitytimer || player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->tiregrease || player->flamedash) return true; if (player->curshield == KSHIELD_TOP) return true; @@ -3162,6 +3162,7 @@ boolean K_WaterRun(mobj_t *mobj) if (mobj->player->invincibilitytimer || mobj->player->sneakertimer || mobj->player->panelsneakertimer + || mobj->player->weaksneakertimer || mobj->player->tiregrease || mobj->player->flamedash || mobj->player->speed > minspeed) @@ -3526,19 +3527,29 @@ static void K_GetKartBoostPower(player_t *player) UINT8 i; for (i = 0; i < player->numsneakers; i++) { - ADDBOOST(FRACUNIT, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 100% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 100% top speed, + 800% acceleration, +50%(???) handling } } - if (player->panelsneakertimer) // Sneaker + if (player->panelsneakertimer) // Sneaker panel { UINT8 i; for (i = 0; i < player->numpanelsneakers; i++) { - ADDBOOST(FRACUNIT/2, 8*FRACUNIT, HANDLESCALING); // + 50% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT/2, 8*FRACUNIT, HANDLESCALING); // + 50% top speed, + 800% acceleration, +50%(???) handling } } + if (player->weaksneakertimer) // Rocket sneaker boost + { + UINT8 i; + for (i = 0; i < player->numweaksneakers; i++) + { + ADDBOOST((FRACUNIT*85)/100, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 85% top speed, + 800% acceleration, +50%(???) handling + } + } + //NOTE: The various sneaker booth strengths are also defined in K_DoSneaker()! + if (player->invincibilitytimer) // Invincibility { // S-Monitor: no extra % @@ -4002,7 +4013,7 @@ SINT8 K_GetForwardMove(const player_t *player) return 0; } - if (player->sneakertimer || player->panelsneakertimer || player->spindashboost + if (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->spindashboost || (((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) && (leveltime < TICRATE/2))) { return MAXPLMOVE; @@ -5065,7 +5076,7 @@ static boolean K_IsLosingWavedash(player_t *player) if (!K_Sliptiding(player) && player->wavedash < MIN_WAVEDASH_CHARGE) return true; if (!K_Sliptiding(player) && player->drift == 0 - && P_IsObjectOnGround(player->mo) && player->sneakertimer == 0 && player->panelsneakertimer == 0 + && P_IsObjectOnGround(player->mo) && player->sneakertimer == 0 && player->panelsneakertimer == 0 && player->weaksneakertimer == 0 && player->driftboost == 0) return true; return false; @@ -7204,8 +7215,14 @@ void K_DoSneaker(player_t *player, INT32 type) fixed_t intendedboost = FRACUNIT/2; - // If you've already got an item sneaker type boost, panel sneakers will instead turn into item sneaker boosts - if (player->numsneakers && type == 0) + // If you've already got an rocket sneaker type boost, panel sneakers will instead turn into rocket sneaker boosts + if (player->numweaksneakers && type == 0) + { + type = 2; + } + + // If you've already got an item sneaker type boost, other sneakers will instead turn into item sneaker boosts + if (player->numsneakers && type != 1) { type = 1; } @@ -7216,10 +7233,13 @@ void K_DoSneaker(player_t *player, INT32 type) intendedboost = FRACUNIT/2; break; case 1: // Single item sneaker - case 2: // Rocket sneaker + intendedboost = 100*FRACUNIT/100; + break; + case 2: // Rocket sneaker (aka. weaksneaker) intendedboost = 85*FRACUNIT/100; break; } + //NOTE: The various sneaker booth strengths are also defined in K_GetKartBoostPower()! if (player->roundconditions.touched_sneakerpanel == false && !(player->exiting || (player->pflags & PF_NOCONTEST)) @@ -7235,7 +7255,7 @@ void K_DoSneaker(player_t *player, INT32 type) const sfxenum_t smallsfx = sfx_cdfm40; sfxenum_t sfx = normalsfx; - if (player->numsneakers || player->numpanelsneakers) + if (player->numsneakers || player->numpanelsneakers || player->numweaksneakers) { // Use a less annoying sound when stacking sneakers. sfx = smallsfx; @@ -7251,17 +7271,19 @@ void K_DoSneaker(player_t *player, INT32 type) switch (type) { - case 0: + case 0: // Panel sneaker player->numpanelsneakers++; break; - case 1: - case 2: + case 1: // Single item sneaker player->numsneakers++; break; + case 2: // Rocket sneaker (aka. weaksneaker) + player->numweaksneakers++; + break; } } - if (player->sneakertimer == 0 && player->panelsneakertimer == 0) + if (player->sneakertimer == 0 && player->panelsneakertimer == 0 && player->weaksneakertimer == 0) { if (type == 2) { @@ -7293,16 +7315,16 @@ void K_DoSneaker(player_t *player, INT32 type) switch (type) { - case 0: + case 0: // Panel sneaker player->panelsneakertimer = sneakertime; player->overshield += 1; break; - case 1: + case 1: // Single item sneaker player->sneakertimer = sneakertime; player->overshield += TICRATE/2; break; - case 2: - player->sneakertimer = 3*sneakertime/4; + case 2: // Rocket sneaker (aka. weaksneaker) + player->weaksneakertimer = 3*sneakertime/4; player->overshield += TICRATE/2; break; } @@ -7438,7 +7460,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) } } - if (mo->player->sneakertimer || mo->player->panelsneakertimer || mo->player->invincibilitytimer) + if (mo->player->sneakertimer || mo->player->panelsneakertimer || mo->player->weaksneakertimer || mo->player->invincibilitytimer) { thrust = FixedMul(thrust, (3*FRACUNIT)/2); } @@ -9198,7 +9220,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->speed > 0) { // Speed lines - if (player->sneakertimer || player->panelsneakertimer || player->ringboost + if (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->ringboost || player->driftboost || player->startboost || player->eggmanexplode || player->trickboost || player->gateBoost || player->wavedashboost) @@ -9439,7 +9461,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (((P_IsObjectOnGround(player->mo) || ( player->spinouttype & KSPIN_AIRTIMER )) && (!player->sneakertimer) - && (!player->panelsneakertimer)) + && (!player->panelsneakertimer) + && (!player->weaksneakertimer)) || (player->respawn.state != RESPAWNST_NONE && player->spinouttimer > 1 && (leveltime & 1))) @@ -9635,6 +9658,16 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } + if (player->weaksneakertimer) + { + player->weaksneakertimer--; + + if (player->weaksneakertimer <= 0) + { + player->numweaksneakers = 0; + } + } + if (player->trickboost) player->trickboost--; @@ -9654,7 +9687,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->counterdash) player->counterdash--; - if ((player->sneakertimer || player->panelsneakertimer) && player->wipeoutslow > 0 && player->wipeoutslow < wipeoutslowtime+1) + if ((player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer) && player->wipeoutslow > 0 && player->wipeoutslow < wipeoutslowtime+1) player->wipeoutslow = wipeoutslowtime+1; if (player->floorboost > 0) @@ -9958,6 +9991,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->tiregrease = 0; player->sneakertimer = 0; player->panelsneakertimer = 0; + player->weaksneakertimer = 0; player->spindashboost = 0; player->flashing = TICRATE/2; player->ringboost = 0; diff --git a/src/objects/crate.cpp b/src/objects/crate.cpp index bfea4d900..224880d14 100644 --- a/src/objects/crate.cpp +++ b/src/objects/crate.cpp @@ -110,7 +110,7 @@ struct Side : Graphic struct Toucher : Mobj { - bool boosting() const { return player && (player->sneakertimer || player->panelsneakertimer || K_PlayerCanPunt(player)); } + bool boosting() const { return player && (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || K_PlayerCanPunt(player)); } }; struct AnyBox : Graphic diff --git a/src/objects/rocks.cpp b/src/objects/rocks.cpp index 8bf400838..1ed75110d 100644 --- a/src/objects/rocks.cpp +++ b/src/objects/rocks.cpp @@ -96,7 +96,7 @@ private: { const player_t* p = toucher->player; - if (p->sneakertimer || p->panelsneakertimer || p->invincibilitytimer || p->growshrinktimer > 0 || p->hyudorotimer) + if (p->sneakertimer || p->panelsneakertimer || p->weaksneakertimer || p->invincibilitytimer || p->growshrinktimer > 0 || p->hyudorotimer) { return; } diff --git a/src/objects/ufo.c b/src/objects/ufo.c index f753874c3..8bdce7962 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -895,7 +895,7 @@ static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType) { // Players deal damage relative to how many sneakers they used. targetdamaging = UFOD_BOOST; - ret = 15 * max(1, inflictor->player->numsneakers + inflictor->player->numpanelsneakers); + ret = 15 * max(1, inflictor->player->numsneakers + inflictor->player->numpanelsneakers + inflictor->player->numweaksneakers); break; } case MT_SPECIAL_UFO: @@ -1056,6 +1056,8 @@ void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other) other->player->numsneakers = 0; other->player->panelsneakertimer = 0; other->player->numpanelsneakers = 0; + other->player->weaksneakertimer = 0; + other->player->numweaksneakers = 0; // Copied from Obj_OrbinautThrown const ffloor_t *rover = P_IsObjectFlipped(other) ? other->ceilingrover : other->floorrover; diff --git a/src/p_inter.c b/src/p_inter.c index c7b370f04..0a74ab9c4 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -3288,6 +3288,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->sneakertimer = player->numsneakers = 0; player->panelsneakertimer = player->numpanelsneakers = 0; + player->weaksneakertimer = player->numweaksneakers = 0; player->driftboost = player->strongdriftboost = 0; player->gateBoost = 0; player->fastfall = 0; diff --git a/src/p_mobj.c b/src/p_mobj.c index 98d377637..cfa0b4e0d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7989,7 +7989,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (p) { - UINT16 timer = max(p->sneakertimer, p->panelsneakertimer); + UINT16 timer = max(max(p->sneakertimer, p->panelsneakertimer), p->weaksneakertimer); if (timer > mobj->movecount) P_SetMobjState(mobj, S_BOOSTFLAME); mobj->movecount = timer; diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 7fc84d0f8..c971ad20e 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -550,6 +550,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].numsneakers); WRITEUINT16(save->p, players[i].panelsneakertimer); WRITEUINT8(save->p, players[i].numpanelsneakers); + WRITEUINT16(save->p, players[i].weaksneakertimer); + WRITEUINT8(save->p, players[i].numweaksneakers); WRITEUINT8(save->p, players[i].floorboost); @@ -1196,6 +1198,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].numsneakers = READUINT8(save->p); players[i].panelsneakertimer = READUINT16(save->p); players[i].numpanelsneakers = READUINT8(save->p); + players[i].numweaksneakers = READUINT8(save->p); players[i].floorboost = READUINT8(save->p); players[i].growshrinktimer = READINT16(save->p); diff --git a/src/p_tick.c b/src/p_tick.c index 29cf6d62c..0ae7e3de7 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -599,9 +599,9 @@ static inline void P_DeviceRumbleTick(void) { low = high = 65536 / 2; } - else if (player->sneakertimer > (sneakertime-(TICRATE/2)) || player->panelsneakertimer > (sneakertime-(TICRATE/2))) + else if (player->sneakertimer > (sneakertime-(TICRATE/2)) || player->panelsneakertimer > (sneakertime-(TICRATE/2)) || player->weaksneakertimer > (sneakertime-(TICRATE/2))) { - low = high = 65536 / (3+player->numsneakers+player->numpanelsneakers); + low = high = 65536 / (3+player->numsneakers+player->numpanelsneakers+player->numweaksneakers); } else if (((player->boostpower < FRACUNIT) || (player->stairjank > 8)) && P_IsObjectOnGround(player->mo) && player->speed != 0) diff --git a/src/p_user.c b/src/p_user.c index 3b7119c30..5895b6cbc 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2618,7 +2618,7 @@ void P_MovePlayer(player_t *player) //////////////////////////// // SRB2kart - Drifting smoke and fire - if ((player->sneakertimer || player->panelsneakertimer || player->flamedash) + if ((player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->flamedash) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); From 60db6dac3ebe9d6f15fb50c372e30de6568f23e5 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Jun 2025 13:27:20 -0400 Subject: [PATCH 104/107] Expose sneaker typing stuff to Lua --- src/lua_playerlib.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 6b3c1032f..a71b3214d 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -498,6 +498,14 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->sneakertimer); else if (fastcmp(field,"numsneakers")) lua_pushinteger(L, plr->numsneakers); + else if (fastcmp(field,"panelsneakertimer")) + lua_pushinteger(L, plr->panelsneakertimer); + else if (fastcmp(field,"numpanelsneakers")) + lua_pushinteger(L, plr->numpanelsneakers); + else if (fastcmp(field,"weaksneakertimer")) + lua_pushinteger(L, plr->weaksneakertimer); + else if (fastcmp(field,"numweaksneakers")) + lua_pushinteger(L, plr->numweaksneakers); else if (fastcmp(field,"floorboost")) lua_pushinteger(L, plr->floorboost); else if (fastcmp(field,"growshrinktimer")) From 5bd70c13bd3e9da9a827e4532d10c24ddd2d5a53 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Jun 2025 13:56:47 -0400 Subject: [PATCH 105/107] Fix tripwire sound riding garden top --- src/k_kart.c | 14 ++++++++------ src/k_kart.h | 3 +++ src/p_mobj.c | 8 +++++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8ce6ba3f8..93d17f6b6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9042,6 +9042,7 @@ static void K_UpdateTripwire(player_t *player) boolean goodSpeed = (player->speed >= speedThreshold); boolean boostExists = (player->tripwireLeniency > 0); // can't be checked later because of subtractions... tripwirepass_t triplevel = K_TripwirePassConditions(player); + boolean mightplaysound = false; if (triplevel != TRIPWIRE_NONE) { @@ -9050,12 +9051,7 @@ static void K_UpdateTripwire(player_t *player) mobj_t *front = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST); mobj_t *back = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST); - if (P_IsDisplayPlayer(player)) - { - S_StartSound(player->mo, sfx_s3k40); - S_StopSoundByID(player->mo, sfx_gshaf); - } - + mightplaysound = true; P_SetTarget(&front->target, player->mo); P_SetTarget(&back->target, player->mo); @@ -9074,6 +9070,12 @@ static void K_UpdateTripwire(player_t *player) if (triplevel != TRIPWIRE_CONSUME) player->tripwireLeniency = max(player->tripwireLeniency, TRIPWIRETIME); + + if (P_IsDisplayPlayer(player) && player->tripwireLeniency && mightplaysound) + { + S_StartSound(player->mo, TRIPWIRE_OK_SOUND); + S_StopSoundByID(player->mo, TRIPWIRE_NG_SOUND); + } } // TRIPWIRE_CONSUME is only applied in very specific cases (currently, riding Garden Top) diff --git a/src/k_kart.h b/src/k_kart.h index 721b5763f..738513358 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -64,6 +64,9 @@ Make sure this matches the actual number of states #define EARLY_ITEM_FLICKER (NUMTRANSMAPS) +#define TRIPWIRE_OK_SOUND (sfx_s3k40) +#define TRIPWIRE_NG_SOUND (sfx_gshaf) + // 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron #define STUMBLE_STEEP_VAL (ANG60 + ANG20) #define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20) diff --git a/src/p_mobj.c b/src/p_mobj.c index 98d377637..d539f6c88 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7864,10 +7864,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (!mobj->target || !mobj->target->health || !mobj->target->player || !mobj->target->player->tripwireLeniency) { - if (mobj->target && mobj->target->player && P_IsDisplayPlayer(mobj->target->player)) + if (mobj->target && mobj->target->player + && P_IsDisplayPlayer(mobj->target->player) + && !(mobj->target->player->curshield == KSHIELD_TOP)) { - S_StopSoundByID(mobj->target, sfx_s3k40); - S_StartSound(mobj->target, sfx_gshaf); + S_StopSoundByID(mobj->target, TRIPWIRE_OK_SOUND); + S_StartSound(mobj->target, TRIPWIRE_NG_SOUND); } P_RemoveMobj(mobj); From 09dda82aff04a6cb2171913ad81f735515835f55 Mon Sep 17 00:00:00 2001 From: eebrozgi Date: Mon, 2 Jun 2025 22:24:19 +0300 Subject: [PATCH 106/107] Cap overshield stacking from sneaker boosts Previously the overshield you got from sneaker boosts could stack up to nonsensical amounts of overshield. This revises the behavior as follows: - Sneakers give you 25 tics of overshield, but will never stack - Rocket sneakers give you 1/2 second of overshield, but will never stack - Sneaker panels extend your existing overshield for 1/3 seconds, but cap at 1 full second --- src/k_kart.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index deac181f9..64cea1ec2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7317,15 +7317,17 @@ void K_DoSneaker(player_t *player, INT32 type) { case 0: // Panel sneaker player->panelsneakertimer = sneakertime; - player->overshield += 1; + if (player->overshield > 0) { + player->overshield = min( player->overshield + TICRATE/3, max( TICRATE, player->overshield )); + } break; case 1: // Single item sneaker player->sneakertimer = sneakertime; - player->overshield += TICRATE/2; + player->overshield = max( player->overshield, 25 ); break; case 2: // Rocket sneaker (aka. weaksneaker) player->weaksneakertimer = 3*sneakertime/4; - player->overshield += TICRATE/2; + player->overshield = max( player->overshield, TICRATE/2 ); break; } From 7157d80d00569fbd3641cec409da594a55557ef0 Mon Sep 17 00:00:00 2001 From: eebrozgi Date: Tue, 3 Jun 2025 01:58:12 +0300 Subject: [PATCH 107/107] More tripwire leniency after invinc + fixed regression in rocketsneaker boost type separation What it says on the tin. Shoutouts to Ashnal for figuring out the regression before anyone got hit by it! --- src/k_kart.c | 5 +++++ src/p_saveg.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 64cea1ec2..60f743c81 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9816,6 +9816,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; if (player->invincibilitytimer && K_IsPlayerScamming(player)) player->invincibilitytimer--; + + // Extra tripwire leniency for the end of invincibility + if (player->invincibilitytimer <= 0) { + player->tripwireLeniency = max( player->tripwireLeniency, TICRATE ); + } } // The precise ordering of start-of-level made me want to cut my head off, diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index b3029e0fa..2c7b05fc9 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -1198,6 +1198,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].numsneakers = READUINT8(save->p); players[i].panelsneakertimer = READUINT16(save->p); players[i].numpanelsneakers = READUINT8(save->p); + players[i].weaksneakertimer = READUINT16(save->p); players[i].numweaksneakers = READUINT8(save->p); players[i].floorboost = READUINT8(save->p);