From 9b63fb40786020303a890a0bcf1fa8fa614746c1 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 2 Sep 2024 04:20:04 -0400 Subject: [PATCH 01/36] Fix map anger race condition Was making angry maps never actually get picked. Dang. --- src/d_netcmd.c | 29 +++++++++++++++++++---------- src/d_netcmd.h | 2 +- src/k_vote.c | 20 ++++++++++++-------- src/k_vote.h | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e926cf520..c524d325e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2376,13 +2376,12 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted) SendNetXCmdForPlayer(sendPlayer, XD_MODIFYVOTE, buf, p - buf); } -void D_PickVote(void) +void D_PickVote(SINT8 angry_map) { - char buf[2]; + char buf[3]; char* p = buf; SINT8 temppicks[VOTE_TOTAL]; SINT8 templevels[VOTE_TOTAL]; - SINT8 votecompare = VOTE_NOT_PICKED; UINT8 numvotes = 0, key = 0; INT32 i; @@ -2393,16 +2392,23 @@ void D_PickVote(void) continue; } + if (i == VOTE_SPECIAL && angry_map != VOTE_NOT_PICKED) + { + // Anger map is going to change because of + // the vote ending. We need to account for this + // here because a net command would not be ready + // in time for this code. + temppicks[numvotes] = i; + templevels[numvotes] = angry_map; + numvotes++; + continue; + } + if (g_votes[i] != VOTE_NOT_PICKED) { temppicks[numvotes] = i; templevels[numvotes] = g_votes[i]; numvotes++; - - if (votecompare == VOTE_NOT_PICKED) - { - votecompare = g_votes[i]; - } } } @@ -2411,14 +2417,16 @@ void D_PickVote(void) key = M_RandomKey(numvotes); WRITESINT8(p, temppicks[key]); WRITESINT8(p, templevels[key]); + WRITESINT8(p, angry_map); } else { WRITESINT8(p, VOTE_NOT_PICKED); WRITESINT8(p, 0); + WRITESINT8(p, VOTE_NOT_PICKED); } - SendNetXCmd(XD_PICKVOTE, &buf, 2); + SendNetXCmd(XD_PICKVOTE, &buf, 3); } static char * @@ -5859,6 +5867,7 @@ static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum) { SINT8 pick = READSINT8(*cp); SINT8 level = READSINT8(*cp); + SINT8 anger = READSINT8(*cp); if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -5868,7 +5877,7 @@ static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum) return; } - Y_SetupVoteFinish(pick, level); + Y_SetupVoteFinish(pick, level, anger); } static void Got_ScheduleTaskcmd(const UINT8 **cp, INT32 playernum) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5a1a79255..f6d863aac 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -249,7 +249,7 @@ void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function any void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); void D_SetupVote(INT16 newgametype); void D_ModifyClientVote(UINT8 player, SINT8 voted); -void D_PickVote(void); +void D_PickVote(SINT8 angry_map); void ObjectPlace_OnChange(void); void P_SetPlayerSpectator(INT32 playernum); boolean IsPlayerAdmin(INT32 playernum); diff --git a/src/k_vote.c b/src/k_vote.c index ca6bb4c79..9f3ba2c32 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -1352,7 +1352,7 @@ static void Y_TickVoteRoulette(void) } } -static void Y_TryMapAngerVote(void) +static SINT8 Y_TryMapAngerVote(void) { SINT8 angryMaps[VOTE_NUM_LEVELS] = { -1 }; size_t angryMapsCount = 0; @@ -1381,7 +1381,7 @@ static void Y_TryMapAngerVote(void) if (numPlayers < 3) { // Don't handle map anger if there's not enough players. - return; + return VOTE_NOT_PICKED; } for (i = 0; i < VOTE_NUM_LEVELS; i++) @@ -1409,12 +1409,12 @@ static void Y_TryMapAngerVote(void) if (angryMapsCount == 0) { - return; + return VOTE_NOT_PICKED; } // Set the special vote to a random angry map. pick = M_RandomKey(angryMapsCount); - D_ModifyClientVote(UINT8_MAX, angryMaps[pick]); + return angryMaps[pick]; } static void Y_TickVoteSelection(void) @@ -1542,8 +1542,7 @@ static void Y_TickVoteSelection(void) if (server) { - Y_TryMapAngerVote(); - D_PickVote(); + D_PickVote( Y_TryMapAngerVote() ); } } } @@ -1593,7 +1592,7 @@ void Y_VoteTicker(void) if (server && g_pickedVote != VOTE_NOT_PICKED && g_votes[g_pickedVote] == VOTE_NOT_PICKED) // Uh oh! The person who got picked left! Recalculate, quick! { - D_PickVote(); + D_PickVote( VOTE_NOT_PICKED ); } if (vote.tic == 0) @@ -1849,13 +1848,18 @@ enum VOTE_END_NORMAL, }; -void Y_SetupVoteFinish(SINT8 pick, SINT8 level) +void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger) { if (vote.loaded == false) { return; } + if (anger != VOTE_NOT_PICKED) + { + Y_SetPlayersVote(VOTE_SPECIAL, anger); + } + if (pick == VOTE_NOT_PICKED || level == VOTE_NOT_PICKED) // No other votes? We gotta get out of here, then! { Y_EndVote(); diff --git a/src/k_vote.h b/src/k_vote.h index 652eb865a..f78310a92 100644 --- a/src/k_vote.h +++ b/src/k_vote.h @@ -31,7 +31,7 @@ void Y_VoteDrawer(void); void Y_VoteTicker(void); void Y_StartVote(void); void Y_EndVote(void); -void Y_SetupVoteFinish(SINT8 pick, SINT8 level); +void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger); #ifdef __cplusplus } // extern "C" From d2b2178143f68c5d891ecd7f28416b7cac97e4e4 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 20:56:42 -0700 Subject: [PATCH 02/36] WIP: Duel ruleset --- src/d_player.h | 2 ++ src/k_hud.cpp | 9 +++++++++ src/k_kart.c | 18 ++++++++++++++++++ src/lua_playerlib.c | 4 ++++ src/p_saveg.c | 2 ++ src/p_spec.c | 4 +++- 6 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index 7f3a55587..6d6a7b8bb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -926,6 +926,8 @@ struct player_t INT32 cheatchecknum; // The number of the last cheatcheck you hit INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp + INT16 duelscore; + UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue UINT8 checkskip; // Skipping checkpoints? Oh no no no diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 1a6bde3b4..1c8ece97a 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2997,6 +2997,15 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. + if (inDuel) + { + UINT8 flashflag = (leveltime % 2 && abs(stplyr->duelscore >= 2)) ? V_TRANSLUCENT : 0; + if (stplyr->duelscore >= 0) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_BLUEMAP|flashflag, va("+%d", stplyr->duelscore)); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_REDMAP|flashflag, va("%d", stplyr->duelscore)); + } + if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 9139deab6..f3b2a672c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4144,6 +4144,24 @@ void K_CheckpointCrossAward(player_t *player) { player->exp += K_GetExpAdjustment(player); K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); + + // Update Duel scoring. + if (inDuel && player->position == 1) + { + player->duelscore += 1; + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && &players[i] != player) + players[i].duelscore -= 1; + } + + if (player->duelscore == 3) + { + P_DoPlayerExit(player, 0); + P_DoAllPlayersExit(PF_NOCONTEST, 0); + } + } + } boolean K_Overdrive(player_t *player) diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index a7937fbfc..59796ac60 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -675,6 +675,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->checkskip); else if (fastcmp(field,"cheatchecknum")) lua_pushinteger(L, plr->cheatchecknum); + else if (fastcmp(field,"duelscore")) + lua_pushinteger(L, plr->duelscore); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1246,6 +1248,8 @@ static int player_set(lua_State *L) plr->checkskip = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"cheatchecknum")) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"duelscore")) + plr->duelscore = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_saveg.c b/src/p_saveg.c index 9a642cc25..4a697e3eb 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -284,6 +284,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].lapPoints); WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); + WRITEINT16(save->p, players[i].duelscore); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -955,6 +956,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].lapPoints = READUINT32(save->p); players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); + players[i].duelscore = READINT16(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index 220954e82..bd1bcd579 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1991,8 +1991,10 @@ static void K_HandleLapIncrement(player_t *player) player->latestlap = player->laps; } + boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + // finished race exit setup - if (player->laps > numlaps) + if (player->laps > numlaps && !specialduelexit) { pflags_t applyflags = 0; if (specialstageinfo.valid == true) From 4f1a49c0fb76ba7219ba0e6a945577651e8c16d1 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 21:52:46 -0700 Subject: [PATCH 03/36] WIP: Duel infinite laps in circuit --- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_hud.cpp | 10 +++++++--- src/k_kart.c | 3 ++- src/p_saveg.c | 2 ++ src/p_spec.c | 5 +++++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index b880a4c22..628ff22be 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -868,6 +868,7 @@ extern SINT8 spbplace; extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; +extern UINT8 extralaps; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) extern boolean legitimateexit; diff --git a/src/g_game.c b/src/g_game.c index 0158f6e6b..56991ea59 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -319,6 +319,7 @@ SINT8 spbplace; // SPB exists, give the person behind better items boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 +UINT8 extralaps; // Duel extensions! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures. diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 1c8ece97a..8918e3233 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2999,11 +2999,15 @@ static void K_drawKartLaps(void) if (inDuel) { - UINT8 flashflag = (leveltime % 2 && abs(stplyr->duelscore >= 2)) ? V_TRANSLUCENT : 0; + UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; + if (leveltime % 2) + if (abs(stplyr->duelscore) >= 2) + flashflag = V_YELLOWMAP; + if (stplyr->duelscore >= 0) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_BLUEMAP|flashflag, va("+%d", stplyr->duelscore)); + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, V_REDMAP|flashflag, va("%d", stplyr->duelscore)); + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); } if (numlaps != 1) diff --git a/src/k_kart.c b/src/k_kart.c index f3b2a672c..1fa198003 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -145,6 +145,7 @@ void K_TimerReset(void) numbulbs = 1; inDuel = rainbowstartavailable = false; linecrossed = 0; + extralaps = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; } @@ -10684,7 +10685,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; + const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection + extralaps; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 4a697e3eb..1b697bd66 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6679,6 +6679,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITESINT8(save->p, spbplace); WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); + WRITEUINT8(save->p, extralaps); WRITEUINT32(save->p, introtime); WRITEUINT32(save->p, starttime); @@ -6884,6 +6885,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) spbplace = READSINT8(save->p); rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); + extralaps = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); starttime = READUINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index bd1bcd579..875d08072 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1993,6 +1993,11 @@ static void K_HandleLapIncrement(player_t *player) boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + if (specialduelexit) + { + extralaps += 1; + } + // finished race exit setup if (player->laps > numlaps && !specialduelexit) { From 075b09e5374a497914c3065c0f3a927209a3fcf0 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 2 Sep 2024 22:05:33 -0700 Subject: [PATCH 04/36] Duel less rewarding First Blood --- src/p_spec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_spec.c b/src/p_spec.c index 875d08072..de2547392 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2055,7 +2055,7 @@ static void K_HandleLapIncrement(player_t *player) K_SpawnDriftBoostExplosion(player, 4); K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, 50, player->mo); + K_SpawnAmps(player, (inDuel) ? 20 : 50, player->mo); rainbowstartavailable = false; } From 1d83dddab8f561b8d6275d9a1e77df6ef0b022c9 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 3 Sep 2024 01:08:59 -0700 Subject: [PATCH 05/36] WIP: Timer Duel --- src/d_player.h | 1 + src/g_game.c | 12 +++++++++++ src/k_hud.cpp | 50 +++++++++++++++++++++++++++++---------------- src/k_kart.c | 47 +++++++++++++++++++++++++++++++++++------- src/k_kart.h | 6 ++++++ src/lua_playerlib.c | 4 ++++ src/p_inter.c | 2 ++ src/p_saveg.c | 2 ++ 8 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 6d6a7b8bb..d3facfed7 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -927,6 +927,7 @@ struct player_t INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp INT16 duelscore; + tic_t dueltimer; UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue diff --git a/src/g_game.c b/src/g_game.c index 56991ea59..6b2ff35fe 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2179,6 +2179,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 lastsafecheatcheck; UINT16 bigwaypointgap; + INT16 duelscore; + tic_t dueltimer; + roundconditions_t roundconditions; boolean saveroundconditions; @@ -2334,6 +2337,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bigwaypointgap = 0; tallyactive = false; + + dueltimer = 15*TICRATE; + duelscore = 0; } else { @@ -2384,6 +2390,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) lastsafecheatcheck = players[player].lastsafecheatcheck; bigwaypointgap = players[player].bigwaypointgap; + duelscore = players[player].duelscore; + dueltimer = players[player].dueltimer; + tallyactive = players[player].tally.active; if (tallyactive) { @@ -2521,6 +2530,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->griefValue = griefValue; p->griefStrikes = griefStrikes; + p->dueltimer = dueltimer; + p->duelscore = duelscore; + memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 8918e3233..b8272f3b0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2628,11 +2628,38 @@ void PositionFacesInfo::draw_1p() ; else if (gametyperules & GTR_CIRCUIT) { - INT32 pos = players[rankplayer[i]].position; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); + if (inDuel) + { + INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; + + colormap = NULL; + + if (K_PlayerLosingDuel(&players[rankplayer[i]]) || players[rankplayer[i]].dueltimer == 0) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, (players[rankplayer[i]].dueltimer == 0) ? SKINCOLOR_CRIMSON : SKINCOLOR_TANGERINE, GTC_CACHE); + flags |= V_STRINGDANCE; + } + + V_DrawStringScaled( + (FACE_X - 5) * FRACUNIT, + (Y + 10) * FRACUNIT, + FRACUNIT, + FRACUNIT, + FRACUNIT, + flags, + colormap, + PINGF_FONT, + va("%02d.%02d", players[rankplayer[i]].dueltimer/TICRATE, (players[rankplayer[i]].dueltimer%TICRATE)*100/TICRATE) + ); + } + else + { + INT32 pos = players[rankplayer[i]].position; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); + } } else if (gametyperules & GTR_POINTLIMIT) { @@ -2997,19 +3024,6 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. - if (inDuel) - { - UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; - if (leveltime % 2) - if (abs(stplyr->duelscore) >= 2) - flashflag = V_YELLOWMAP; - - if (stplyr->duelscore >= 0) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); - else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); - } - if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 1fa198003..6d3fa23c6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4147,19 +4147,23 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel && player->position == 1) + if (inDuel) { player->duelscore += 1; + + if (player->position == 1) + { + tic_t effectiveleveltime = min(leveltime, DUELTIMER_NOBONUS); + player->dueltimer += Easing_Linear(effectiveleveltime*FRACUNIT/DUELTIMER_NOBONUS, DUELTIMER_BONUS, 0); + player->dueltimer = min(player->dueltimer, DUELTIMER_MAX); + } + for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) + { players[i].duelscore -= 1; - } - - if (player->duelscore == 3) - { - P_DoPlayerExit(player, 0); - P_DoAllPlayersExit(PF_NOCONTEST, 0); + } } } @@ -7616,6 +7620,19 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds) return i; } +boolean K_PlayerLosingDuel(player_t *player) +{ + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && &players[i] != player) + { + if (players[i].duelscore > player->duelscore) + return true; + } + } + return false; +} + mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); @@ -9385,6 +9402,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } + if (inDuel && K_PlayerLosingDuel(player)) + { + if (player->dueltimer) + { + player->dueltimer--; + if (!(player->dueltimer % 4) && P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_s3k55); + + if (player->dueltimer == 0) + { + P_DoTimeOver(player); + P_DoAllPlayersExit(0, false); + } + } + } + if (!player->invincibilitytimer) player->invincibilityextensions = 0; diff --git a/src/k_kart.h b/src/k_kart.h index bf73ffcf2..38e80016e 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -88,6 +88,11 @@ Make sure this matches the actual number of states #define AUTORESPAWN_TIME (10*TICRATE) #define AUTORESPAWN_THRESHOLD (7*TICRATE) +#define DUELTIMER_START (10*TICRATE) +#define DUELTIMER_BONUS (2*TICRATE) +#define DUELTIMER_MAX (3*DUELTIMER_START/2) +#define DUELTIMER_NOBONUS (60*TICRATE) + angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); boolean K_IsDuelItem(mobjtype_t type); @@ -203,6 +208,7 @@ void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); +boolean K_PlayerLosingDuel(player_t *player); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); mobj_t *K_FlingPaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 59796ac60..c0bf2df4b 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -677,6 +677,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->cheatchecknum); else if (fastcmp(field,"duelscore")) lua_pushinteger(L, plr->duelscore); + else if (fastcmp(field,"dueltimer")) + lua_pushinteger(L, plr->dueltimer); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1250,6 +1252,8 @@ static int player_set(lua_State *L) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"duelscore")) plr->duelscore = (INT16)luaL_checkinteger(L, 3); + else if (fastcmp(field,"dueltimer")) + plr->dueltimer = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_inter.c b/src/p_inter.c index 8464cf0b9..7b5c69b57 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1544,6 +1544,8 @@ boolean P_CheckRacers(void) const boolean griefed = (spectateGriefed > 0); boolean eliminateLast = (!K_CanChangeRules(true) || (cv_karteliminatelast.value != 0)); + if (inDuel) + eliminateLast = false; boolean allHumansDone = true; //boolean allBotsDone = true; diff --git a/src/p_saveg.c b/src/p_saveg.c index 1b697bd66..711d6632d 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -285,6 +285,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); WRITEINT16(save->p, players[i].duelscore); + WRITEUINT32(save->p, players[i].dueltimer); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -957,6 +958,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); players[i].duelscore = READINT16(save->p); + players[i].dueltimer = READUINT32(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); From 141a37c48a6c2196f70b9774f9ff7f552c44e63d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 3 Sep 2024 07:34:11 -0400 Subject: [PATCH 06/36] Duel voting Instead of voting for the level you want and the pick is decided by RNG ... now you take turns picking the maps you *don't* want, and the last one remaining gets picked. The previous loser gets to strike two stages and goes first, while the previous winner only gets to strike one. Very incomplete visuals, very janky. --- src/d_netcmd.c | 39 ++-- src/doomstat.h | 9 +- src/g_game.c | 3 +- src/k_vote.c | 575 +++++++++++++++++++++++++++++++++++++++++++++--- src/k_vote.h | 4 +- src/p_saveg.c | 2 + src/y_inter.cpp | 45 ++-- src/y_inter.h | 7 +- 8 files changed, 616 insertions(+), 68 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c524d325e..d743ed3a2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2339,11 +2339,11 @@ void D_SetupVote(INT16 newgametype) void D_ModifyClientVote(UINT8 player, SINT8 voted) { - char buf[2]; + char buf[3]; char *p = buf; - UINT8 sendPlayer = consoleplayer; + UINT8 sendPlayer = 0; - if (player == UINT8_MAX) + if (player >= MAXPLAYERS) { // Special game vote (map anger, duel) if (!server) @@ -2352,16 +2352,16 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted) } } - if (player == UINT8_MAX) - { - // special vote - WRITEUINT8(p, UINT8_MAX); - } - else - { - INT32 i = 0; - WRITEUINT8(p, player); + // Context value -- if context has changed, then discard vote update. + // This is to prevent votes being registered from different vote types. + // Currently used for Duel vs Normal votes. + WRITEUINT8(p, Y_VoteContext()); + WRITEUINT8(p, player); + + if (player <= MAXPLAYERS) + { + INT32 i; for (i = 0; i <= splitscreen; i++) { if (g_localplayers[i] == player) @@ -5819,17 +5819,24 @@ static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum) static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum) { + UINT8 context = READUINT8(*cp); UINT8 targetID = READUINT8(*cp); SINT8 vote = READSINT8(*cp); - if (targetID == UINT8_MAX) + if (context != Y_VoteContext()) { - if (playernum != serverplayer) // server-only special vote + // Silently discard. Server changed the + // vote type as we were sending our vote. + return; + } + + if (targetID >= MAXPLAYERS) + { + // only the server is allowed to send these + if (playernum != serverplayer) { goto fail; } - - targetID = VOTE_SPECIAL; } else if (playeringame[targetID] == true && players[targetID].bot == true) { diff --git a/src/doomstat.h b/src/doomstat.h index b880a4c22..ce0d1c976 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -873,11 +873,18 @@ extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines extern boolean legitimateexit; extern boolean comebackshowninfo; +#define VOTE_NUM_LEVELS (4) +#define VOTE_NOT_PICKED (-1) #define VOTE_SPECIAL (MAXPLAYERS) #define VOTE_TOTAL (MAXPLAYERS+1) -extern UINT16 g_voteLevels[4][2]; + +#define VOTE_TIMEOUT_LOSER (MAXPLAYERS+1) // not a real vote ID +#define VOTE_TIMEOUT_WINNER (MAXPLAYERS+2) // ditto + +extern UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; extern SINT8 g_votes[VOTE_TOTAL]; extern SINT8 g_pickedVote; +extern boolean g_votes_striked[VOTE_NUM_LEVELS]; // =========================== // Internal parameters, fixed. diff --git a/src/g_game.c b/src/g_game.c index 0158f6e6b..371498d23 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -305,9 +305,10 @@ boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? // Voting system -UINT16 g_voteLevels[4][2]; // Levels that were rolled by the host +UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; // Levels that were rolled by the host SINT8 g_votes[VOTE_TOTAL]; // Each player's vote SINT8 g_pickedVote; // What vote the host rolls +boolean g_votes_striked[VOTE_NUM_LEVELS]; // Which levels were striked from votes? // Server-sided, synched variables tic_t wantedcalcdelay; // Time before it recalculates WANTED diff --git a/src/k_vote.c b/src/k_vote.c index 9f3ba2c32..39d0fc36f 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -109,6 +109,7 @@ // Give time for the animations to finish before finalizing the vote stages. #define SELECT_DELAY_TIME (TICRATE*4) #define PICK_DELAY_TIME (TICRATE/2) +#define STRIKE_DELAY_TIME (TICRATE/3) #define MAP_ANGER_MAX (2) @@ -179,12 +180,21 @@ typedef struct { INT32 timer; INT32 tic, endtic; - INT32 selectFinalize, pickFinalize; + INT32 selectFinalize, pickFinalize, strikeFinalize; boolean notYetPicked; boolean loaded; SINT8 deferredLevel; y_vote_player players[MAXSPLITSCREENPLAYERS]; y_vote_roulette roulette; + + // If both of these players are valid, + // and they're the only players in the server, + // then we want stage striking! + player_t *strike_loser; + player_t *strike_winner; + boolean strike_turn; + boolean strike_time_out; + boolean stage_striking; } y_vote_data; // Voting level drawing @@ -209,6 +219,8 @@ typedef struct patch_t *ruby_icon; fixed_t ruby_height; + patch_t *strike_icon; + patch_t *bg_planet[PLANET_FRAMES]; patch_t *bg_checker; patch_t *bg_levelText; @@ -230,13 +242,28 @@ typedef struct static y_vote_data vote = {0}; static y_vote_draw vote_draw = {0}; +static boolean Y_VoteIDIsSpecial(const UINT8 playerId) +{ + switch (playerId) + { + case VOTE_SPECIAL: + case VOTE_TIMEOUT_LOSER: + case VOTE_TIMEOUT_WINNER: + { + // Special vote spot, always allow + return true; + } + default: + { + return false; + } + } +} + boolean Y_PlayerIDCanVote(const UINT8 playerId) { - player_t *player = NULL; - - if (playerId == VOTE_SPECIAL) + if (Y_VoteIDIsSpecial(playerId) == true) { - // Special vote spot, always allow return true; } @@ -245,7 +272,7 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId) return false; } - player = &players[playerId]; + const player_t *player = &players[playerId]; if (player->spectator == true) { return false; @@ -260,8 +287,48 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId) return true; } +static boolean Y_IsPlayersTurn(const UINT8 playerId) +{ + if (Y_VoteIDIsSpecial(playerId) == true) + { + return true; + } + + if (vote.stage_striking == false) + { + // Not stage striking -- we can always vote. + return true; + } + + // Is it our turn to strike a stage? + const player_t *player = &players[playerId]; + if (vote.strike_turn == true) + { + return (player == vote.strike_winner); + } + else + { + return (player == vote.strike_loser); + } +} + +static boolean Y_PlayerIDCanVoteRightNow(const UINT8 playerId) +{ + if (Y_IsPlayersTurn(playerId) == false) + { + return false; + } + + return Y_PlayerIDCanVote(playerId); +} + static boolean Y_PlayerCanSelect(const UINT8 localId) { + if (localId > splitscreen) + { + return false; + } + const UINT8 p = g_localplayers[localId]; if (g_pickedVote != VOTE_NOT_PICKED) @@ -280,7 +347,7 @@ static boolean Y_PlayerCanSelect(const UINT8 localId) return false; } - return Y_PlayerIDCanVote(p); + return Y_PlayerIDCanVoteRightNow(p); } static void Y_SortPile(void) @@ -381,21 +448,82 @@ static void Y_SortPile(void) } } -void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote) +void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) { - y_vote_pile *const pile = &vote.roulette.pile[playerId]; - y_vote_catcher *const catcher = &pile->catcher; + INT32 i; if (gamestate != GS_VOTING) { return; } + UINT8 playerId = inputPlayerId; + + // Manually overwrite these players for timed out votes. + // Loser/winner is encoded in the vote ID to prevent race + // race conditions with real votes causing problems. + if (inputPlayerId == VOTE_TIMEOUT_LOSER) + { + playerId = (vote.strike_loser - players); + } + else if (inputPlayerId == VOTE_TIMEOUT_WINNER) + { + playerId = (vote.strike_winner - players); + } + if (newVote < 0 || newVote >= VOTE_NUM_LEVELS) { newVote = VOTE_NOT_PICKED; } + if (playerId < MAXPLAYERS) + { + if (Y_PlayerIDCanVoteRightNow(playerId) == false) + { + // Not your turn, dude! + return; + } + + if (vote.stage_striking == true) + { + UINT8 num_striked = 0; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + num_striked++; + } + } + + if (newVote != VOTE_NOT_PICKED + && num_striked < VOTE_NUM_LEVELS-1 + && g_votes_striked[newVote] == false) + { + // Strike a stage, instead of voting. + g_votes_striked[newVote] = true; + + // Change turn. + vote.strike_turn = !vote.strike_turn; + + // Reset variables. + vote.timer = cv_votetime.value * TICRATE; + for (i = 0; i <= splitscreen; i++) + { + vote.players[i].sentTimeOutVote = false; + vote.players[i].delay = NEWTICRATE/7; + } + vote.strike_time_out = false; + + // TODO: striking animation + } + + return; + } + } + + y_vote_pile *const pile = &vote.roulette.pile[playerId]; + y_vote_catcher *const catcher = &pile->catcher; + g_votes[playerId] = newVote; Y_SortPile(); @@ -468,29 +596,48 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy); + boolean striked = false; + if (playerID < 0) + { + striked = g_votes_striked[v]; + } + V_DrawFill( fx - dupx, fy - dupy, fw + (dupx << 1), fh + (dupy << 1), 0|flags|V_NOSCALESTART ); - K_DrawMapThumbnail( - x, y, - width, flags | ((encore == true) ? V_FLIP : 0), - g_voteLevels[v][0], - (dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL) - ); - - if (encore == true) + if (striked == true) { - const fixed_t rubyScale = width / 72; + const fixed_t strikeScale = width / 32; V_DrawFixedPatch( - center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale), - rubyScale, flags, - vote_draw.ruby_icon, + center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2), + strikeScale, flags, + vote_draw.strike_icon, NULL ); } + else + { + K_DrawMapThumbnail( + x, y, + width, flags | ((encore == true) ? V_FLIP : 0), + g_voteLevels[v][0], + (dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL) + ); + + if (encore == true) + { + const fixed_t rubyScale = width / 72; + V_DrawFixedPatch( + center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale), + rubyScale, flags, + vote_draw.ruby_icon, + NULL + ); + } + } if (dim == true) { @@ -506,7 +653,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt { const INT32 whiteSq = 16 * dupx; - if (playerID < MAXPLAYERS) + if (playerID < MAXPLAYERS) // Player vote { UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE); patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK]; @@ -518,7 +665,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt playerPatch, playerMap ); } - else + else if (vote.stage_striking == false) // Angry map { const fixed_t iconHeight = (14 << FRACBITS); const fixed_t iconWidth = (iconHeight * 320) / 200; @@ -813,7 +960,7 @@ static void Y_DrawVoteSelection(fixed_t offset) continue; } - if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVote(p) == false) + if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVoteRightNow(p) == false) { continue; } @@ -1033,7 +1180,7 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) { Y_FinalizeVote(level); - if (netgame && P_IsPartyPlayer(&players[pick])) + if (netgame && pick < MAXPLAYERS && P_IsPartyPlayer(&players[pick])) { S_StartSound(NULL, sfx_yeeeah); // yeeeah! } @@ -1043,8 +1190,30 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) } } +static void Y_PlayerSendStrike(const UINT8 localPlayer) +{ + y_vote_player *const player = &vote.players[localPlayer]; + y_vote_catcher *const catcher = &player->catcher; + + if (g_votes_striked[player->selection] == true) + { + // TODO: "Can't select" animation + return; + } + + D_ModifyClientVote(g_localplayers[localPlayer], player->selection); + catcher->action = CATCHER_NA; + catcher->delay = 5; +} + static void Y_PlayerSendVote(const UINT8 localPlayer) { + if (vote.stage_striking == true) + { + Y_PlayerSendStrike(localPlayer); + return; + } + y_vote_player *const player = &vote.players[localPlayer]; y_vote_catcher *const catcher = &player->catcher; @@ -1171,7 +1340,11 @@ static void Y_TickPlayerCatcher(const UINT8 localPlayer) { if (catcher->x == catcher->destX && catcher->y == catcher->destY) { - D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection); + if (vote.stage_striking == false) + { + D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection); + } + catcher->action = CATCHER_NA; catcher->delay = 5; S_StopSoundByNum(sfx_kc37); @@ -1417,6 +1590,200 @@ static SINT8 Y_TryMapAngerVote(void) return angryMaps[pick]; } +static void Y_ExitStageStrike(void) +{ + INT32 i; + + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + g_votes_striked[i] = false; + } + + vote.stage_striking = false; + vote.timer = cv_votetime.value * TICRATE; + + vote.strike_loser = NULL; + vote.strike_winner = NULL; + vote.strike_turn = false; + vote.strike_time_out = false; +} + +static boolean Y_CheckStageStrikeStatus(void) +{ + INT32 i; + UINT8 num_voters = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (Y_PlayerIDCanVote(i) == false) + { + continue; + } + + num_voters++; + if (num_voters > 2) + { + break; + } + } + + if (num_voters != 2) + { + // Someone joined or left. Stage striking is broken! + return false; + } + + if (vote.strike_loser == NULL || Y_PlayerIDCanVote(vote.strike_loser - players) == false) + { + // Loser is invalidated! + return false; + } + + if (vote.strike_winner == NULL || Y_PlayerIDCanVote(vote.strike_winner - players) == false) + { + // Winner is invalidated! + return false; + } + + // Looks good, we can tick stage striking. + return true; +} + +static void Y_TickVoteStageStrike(void) +{ + INT32 i; + + if (Y_CheckStageStrikeStatus() == false) + { + Y_ExitStageStrike(); + return; + } + + SINT8 the_only_level = VOTE_NOT_PICKED; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + continue; + } + + if (the_only_level != VOTE_NOT_PICKED) + { + // More than 1 valid level. + // Unset and stop iterating. + the_only_level = VOTE_NOT_PICKED; + break; + } + + the_only_level = i; + } + + if (the_only_level != VOTE_NOT_PICKED) + { + vote.timer = 0; + vote.strikeFinalize = STRIKE_DELAY_TIME; + + if (vote.selectFinalize < SELECT_DELAY_TIME) + { + if (vote.selectFinalize == 0) + { + for (i = 0; i <= splitscreen; i++) + { + UINT8 p = g_localplayers[i]; + + if (p != (vote.strike_loser - players) + && p != (vote.strike_winner - players)) + { + continue; + } + + y_vote_player *const player = &vote.players[i]; + y_vote_catcher *const catcher = &player->catcher; + + player->selection = the_only_level; + catcher->action = CATCHER_FG_LOWER; + + catcher->x = catcher->destX = SELECTION_X + (SELECTION_SPACING_W * player->selection); + catcher->y = CATCHER_OFFSCREEN; + catcher->destY = SELECTION_Y - SELECTION_HOP; + catcher->spr = 0; + catcher->level = VOTE_NOT_PICKED; + + S_StartSound(NULL, sfx_kc37); + } + } + + vote.selectFinalize++; + } + + if (vote.selectFinalize >= SELECT_DELAY_TIME) + { + if (vote.pickFinalize < PICK_DELAY_TIME) + { + vote.pickFinalize++; + } + else if (vote.endtic == -1) + { + vote.notYetPicked = false; /* don't pick vote twice */ + + if (server) + { + D_PickVote( the_only_level ); + } + } + } + } + else + { + if (vote.timer == 0) + { + if (vote.strikeFinalize < STRIKE_DELAY_TIME) + { + vote.strikeFinalize++; + } + } + else + { + vote.strikeFinalize = 0; + } + + if (vote.strikeFinalize >= STRIKE_DELAY_TIME) + { + // We didn't get their timeout strike net command. + // Maybe they hacked their exe, or connection was + // interrupted, or some other issue. + + // Let's just strike a random stage for them. + if (server && vote.strike_time_out == false) + { + INT32 rng = M_RandomKey(VOTE_NUM_LEVELS); + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == false) + { + break; + } + + rng++; + if (rng >= VOTE_NUM_LEVELS) + { + rng = 0; + } + } + + D_ModifyClientVote((vote.strike_turn == true) ? VOTE_TIMEOUT_WINNER : VOTE_TIMEOUT_LOSER, rng); + } + + vote.strike_time_out = true; + } + else if (vote.timer > 0) + { + vote.timer--; + vote.selectFinalize = 0; + vote.pickFinalize = 0; + } + } +} + static void Y_TickVoteSelection(void) { boolean everyone_voted = true;/* the default condition */ @@ -1446,6 +1813,22 @@ static void Y_TickVoteSelection(void) // Time's up, send our vote ASAP. if (vote.players[i].sentTimeOutVote == false) { + // Move off of striked stages for the timeout vote. + INT32 j; + for (j = 0; j < VOTE_NUM_LEVELS; j++) + { + if (g_votes_striked[vote.players[i].selection] == false) + { + break; + } + + vote.players[i].selection++; + if (vote.players[i].selection >= VOTE_NUM_LEVELS) + { + vote.players[i].selection = 0; + } + } + Y_PlayerSendVote(i); vote.players[i].sentTimeOutVote = true; vote.players[i].delay = NEWTICRATE/7; @@ -1497,9 +1880,9 @@ static void Y_TickVoteSelection(void) continue; } - if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED) + if (players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) { - if (( M_RandomFixed() % 100 ) == 0) + if (server && ( M_RandomFixed() % 100 ) == 0) { // bots vote randomly D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS)); @@ -1512,6 +1895,13 @@ static void Y_TickVoteSelection(void) } } + if (vote.stage_striking == true) + { + // Use the same selection logic, otherwise use separate ending logic. + Y_TickVoteStageStrike(); + return; + } + if (everyone_voted == true) { vote.timer = 0; @@ -1632,6 +2022,7 @@ static void Y_InitVoteDrawing(void) INT32 i = 0, j = 0; vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC); + vote_draw.strike_icon = W_CachePatchName("K_NOBLNS", PU_STATIC); for (i = 0; i < PLANET_FRAMES; i++) { @@ -1719,6 +2110,103 @@ static void Y_InitVoteDrawing(void) vote_draw.selectTransition = FRACUNIT; } +static boolean Y_DetermineStageStrike(void) +{ + player_t *a = NULL; + player_t *b = NULL; + + UINT8 num_voters = 0; + + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (Y_PlayerIDCanVote(i) == false) + { + continue; + } + + num_voters++; + + // Just set the pointers for now, sort them later. + if (a == NULL) + { + a = &players[i]; + } + else if (b == NULL) + { + b = &players[i]; + } + else + { + // Too many players + return false; + } + } + + if (num_voters != 2 || a == NULL || b == NULL) + { + // Requires exactly 2 of them. + return false; + } + + UINT32 score_a = 0; + UINT32 score_b = 0; + + intertype_t scoring_type = Y_GetIntermissionType(); + switch (scoring_type) + { + case int_time: + { + score_a = UINT32_MAX - a->realtime; + score_b = UINT32_MAX - b->realtime; + break; + } + case int_score: + { + score_a = a->score; + score_b = b->realtime; + break; + } + default: + { + // Invalid, exit now. + return false; + } + } + + if (a->pflags & PF_NOCONTEST) + { + score_a = 0; + } + + if (b->pflags & PF_NOCONTEST) + { + score_b = 0; + } + + if (score_a == score_b) + { + // TODO: should be a coin flip, but how + // should the RNG for this be handled? + score_a++; + } + + if (score_a > score_b) + { + vote.strike_loser = b; + vote.strike_winner = a; + } + else + { + vote.strike_loser = a; + vote.strike_winner = b; + } + + vote.stage_striking = true; + return true; +} + void Y_StartVote(void) { INT32 i = 0; @@ -1765,6 +2253,13 @@ void Y_StartVote(void) catcher->player = i; } + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + g_votes_striked[i] = false; + } + + Y_DetermineStageStrike(); + Y_InitVoteDrawing(); vote.loaded = true; @@ -1786,6 +2281,7 @@ static void Y_UnloadVoteData(void) } UNLOAD(vote_draw.ruby_icon); + UNLOAD(vote_draw.strike_icon); for (i = 0; i < PLANET_FRAMES; i++) { @@ -1924,4 +2420,25 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger) vote.timer = -1; vote.selectFinalize = SELECT_DELAY_TIME; vote.pickFinalize = PICK_DELAY_TIME; + vote.strikeFinalize = STRIKE_DELAY_TIME; +} + +// +// Y_VoteContext +// + +enum +{ + VOTE_CTX_NORMAL = 0, + VOTE_CTX_DUEL, +}; + +UINT8 Y_VoteContext(void) +{ + if (vote.stage_striking == true) + { + return VOTE_CTX_DUEL; + } + + return VOTE_CTX_NORMAL; } diff --git a/src/k_vote.h b/src/k_vote.h index f78310a92..0f35ffb37 100644 --- a/src/k_vote.h +++ b/src/k_vote.h @@ -19,9 +19,6 @@ extern "C" { #endif -#define VOTE_NUM_LEVELS (4) -#define VOTE_NOT_PICKED (-1) - #define VOTE_MOD_ENCORE (0x01) boolean Y_PlayerIDCanVote(const UINT8 playerId); @@ -32,6 +29,7 @@ void Y_VoteTicker(void); void Y_StartVote(void); void Y_EndVote(void); void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger); +UINT8 Y_VoteContext(void); #ifdef __cplusplus } // extern "C" diff --git a/src/p_saveg.c b/src/p_saveg.c index 9a642cc25..bef12df23 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6586,6 +6586,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) { WRITEUINT16(save->p, g_voteLevels[i][0]); WRITEUINT16(save->p, g_voteLevels[i][1]); + WRITEUINT8(save->p, g_votes_striked[i]); } for (i = 0; i < VOTE_TOTAL; i++) @@ -6795,6 +6796,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) { g_voteLevels[i][0] = READUINT16(save->p); g_voteLevels[i][1] = READUINT16(save->p); + g_votes_striked[i] = (boolean)READUINT8(save->p); } for (i = 0; i < VOTE_TOTAL; i++) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 935303fbf..465df3e38 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1976,6 +1976,35 @@ boolean Y_ShouldDoIntermission(void) return true; } +// +// Y_GetIntermissionType +// +// Returns the intermission type from the current gametype. +// +intertype_t Y_GetIntermissionType(void) +{ + intertype_t ret = static_cast(gametypes[gametype]->intermission); + + if (ret == int_scoreortimeattack) + { + UINT8 i = 0, nump = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + nump++; + } + + ret = (nump < 2 ? int_time : int_score); + } + + return ret; +} + // // Y_DetermineIntermissionType // @@ -1990,21 +2019,7 @@ void Y_DetermineIntermissionType(void) return; } - // set initially - intertype = static_cast(gametypes[gametype]->intermission); - - // special cases - if (intertype == int_scoreortimeattack) - { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - intertype = (nump < 2 ? int_time : int_score); - } + intertype = Y_GetIntermissionType(); } static UINT8 Y_PlayersBestPossiblePosition(player_t *const player) diff --git a/src/y_inter.h b/src/y_inter.h index 51665f734..0f3687db4 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -61,9 +61,6 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree void Y_StartIntermission(void); void Y_EndIntermission(void); -boolean Y_ShouldDoIntermission(void); -void Y_DetermineIntermissionType(void); - void Y_PlayIntermissionMusic(void); typedef enum @@ -76,6 +73,10 @@ typedef enum extern intertype_t intertype; +boolean Y_ShouldDoIntermission(void); +intertype_t Y_GetIntermissionType(void); +void Y_DetermineIntermissionType(void); + #ifdef __cplusplus } // extern "C" #endif From e21cf463eadd5e8be0109e734225ba89488cb9d0 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Tue, 3 Sep 2024 20:56:36 -0400 Subject: [PATCH 07/36] Duel vote: More conveyance - Show X over selection, to make it more obvious when it's rip snortin' time - "Waiting for [player]..." when it's not your turn - Halve vote timer in Duels (maybe make a separate cvar?) - Fix bots sometimes striking stages out realllllly slowly --- src/d_netcmd.c | 8 ++- src/k_vote.c | 138 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d743ed3a2..939fa84c1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5833,23 +5833,21 @@ static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum) if (targetID >= MAXPLAYERS) { // only the server is allowed to send these - if (playernum != serverplayer) + if (playernum != serverplayer) { goto fail; } } else if (playeringame[targetID] == true && players[targetID].bot == true) { - if (targetID >= MAXPLAYERS - || playernum != serverplayer) + if (playernum != serverplayer) { goto fail; } } else { - if (targetID >= MAXPLAYERS - || playernode[targetID] != playernode[playernum]) + if (playernode[targetID] != playernode[playernum]) { goto fail; } diff --git a/src/k_vote.c b/src/k_vote.c index 39d0fc36f..8c17cfedb 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -242,6 +242,32 @@ typedef struct static y_vote_data vote = {0}; static y_vote_draw vote_draw = {0}; +static void Y_SetVoteTimer(void) +{ + vote.timer = cv_votetime.value * TICRATE; + + if (vote.stage_striking == true) + { + vote.timer /= 2; + } +} + +static UINT8 Y_CountStriked(void) +{ + INT32 i; + + UINT8 num_striked = 0; + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == true) + { + num_striked++; + } + } + + return num_striked; +} + static boolean Y_VoteIDIsSpecial(const UINT8 playerId) { switch (playerId) @@ -486,18 +512,9 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) if (vote.stage_striking == true) { - UINT8 num_striked = 0; - for (i = 0; i < VOTE_NUM_LEVELS; i++) - { - if (g_votes_striked[i] == true) - { - num_striked++; - } - } - if (newVote != VOTE_NOT_PICKED - && num_striked < VOTE_NUM_LEVELS-1 - && g_votes_striked[newVote] == false) + && g_votes_striked[newVote] == false + && Y_CountStriked() < VOTE_NUM_LEVELS-1) { // Strike a stage, instead of voting. g_votes_striked[newVote] = true; @@ -506,7 +523,7 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) vote.strike_turn = !vote.strike_turn; // Reset variables. - vote.timer = cv_votetime.value * TICRATE; + Y_SetVoteTimer(); for (i = 0; i <= splitscreen; i++) { vote.players[i].sentTimeOutVote = false; @@ -544,12 +561,12 @@ void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote) if (vote.timer == -1) { // Someone has voted, so start the timer now. - vote.timer = cv_votetime.value * TICRATE; + Y_SetVoteTimer(); } #endif } -static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID) +static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID, boolean from_selection) { const boolean encore = vote_draw.levels[v].encore; const fixed_t height = (width * BASEVIDHEIGHT) / BASEVIDWIDTH; @@ -597,7 +614,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy); boolean striked = false; - if (playerID < 0) + if (from_selection == true) { striked = g_votes_striked[v]; } @@ -637,6 +654,22 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt NULL ); } + + if (vote.stage_striking == true + && from_selection == true + && dim == false) + { + if (Y_CountStriked() < VOTE_NUM_LEVELS-1) + { + const fixed_t strikeScale = width / 32; + V_DrawFixedPatch( + center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2), + strikeScale, flags /*| V_TRANSLUCENT*/, + vote_draw.strike_icon, + NULL + ); + } + } } if (dim == true) @@ -765,7 +798,7 @@ static void Y_DrawCatcher(y_vote_catcher *catcher) baseX, catcher->y, ((catcher->small == true) ? PILE_WIDTH : SELECTION_WIDTH), 0, catcher->level, false, - catcher->player + catcher->player, false ); } @@ -1028,12 +1061,41 @@ static void Y_DrawVoteSelection(fixed_t offset) x, y - vote_draw.levels[i].hop, SELECTION_WIDTH, 0, i, (selected == false), - -1 + -1, true ); x += SELECTION_SPACING_W; } + if (vote.stage_striking == true && Y_CountStriked() < VOTE_NUM_LEVELS-1) + { + UINT8 current_strike_player = ( + (vote.strike_turn == true) + ? (vote.strike_winner - players) + : (vote.strike_loser - players) + ); + + for (i = 0; i <= splitscreen; i++) + { + if (g_localplayers[i] == current_strike_player) + { + break; + } + } + + if (i > splitscreen) + { + const char *wait_str = va("Waiting for %s...", player_names[current_strike_player]); + + V_DrawThinString( + BASEVIDWIDTH / 2 - (V_ThinStringWidth(wait_str, 0) / 2), + 180, + 0, + wait_str + ); + } + } + // // Draw our catchers // @@ -1091,7 +1153,7 @@ static void Y_DrawVotePile(void) PILE_WIDTH, 0, g_votes[i], (i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED), - i + i, false ); } @@ -1594,18 +1656,19 @@ static void Y_ExitStageStrike(void) { INT32 i; + vote.stage_striking = false; + for (i = 0; i < VOTE_NUM_LEVELS; i++) { g_votes_striked[i] = false; } - vote.stage_striking = false; - vote.timer = cv_votetime.value * TICRATE; - vote.strike_loser = NULL; vote.strike_winner = NULL; vote.strike_turn = false; vote.strike_time_out = false; + + Y_SetVoteTimer(); } static boolean Y_CheckStageStrikeStatus(void) @@ -1880,12 +1943,27 @@ static void Y_TickVoteSelection(void) continue; } - if (players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) + if (server && players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED) { - if (server && ( M_RandomFixed() % 100 ) == 0) + if (( M_RandomFixed() % 100 ) == 0) { // bots vote randomly - D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS)); + INT32 rng = M_RandomKey(VOTE_NUM_LEVELS); + for (i = 0; i < VOTE_NUM_LEVELS; i++) + { + if (g_votes_striked[i] == false) + { + break; + } + + rng++; + if (rng >= VOTE_NUM_LEVELS) + { + rng = 0; + } + } + + D_ModifyClientVote(i, rng); } } @@ -2220,12 +2298,6 @@ void Y_StartVote(void) vote.tic = vote.endtic = -1; -#ifdef VOTE_TIME_WAIT_FOR_VOTE - vote.timer = -1; // Timer is not set until the first vote is added -#else - vote.timer = cv_votetime.value * TICRATE; -#endif - g_pickedVote = VOTE_NOT_PICKED; vote.notYetPicked = true; @@ -2260,6 +2332,12 @@ void Y_StartVote(void) Y_DetermineStageStrike(); +#ifdef VOTE_TIME_WAIT_FOR_VOTE + vote.timer = -1; // Timer is not set until the first vote is added +#else + Y_SetVoteTimer(); +#endif + Y_InitVoteDrawing(); vote.loaded = true; From 2f164cf1b84e480cc90a3e23b5ea3103caeec88e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 3 Sep 2024 05:11:00 -0700 Subject: [PATCH 08/36] Revert "WIP: Timer Duel" This reverts commit 1d83dddab8f561b8d6275d9a1e77df6ef0b022c9. --- src/d_player.h | 1 - src/g_game.c | 12 ----------- src/k_hud.cpp | 50 ++++++++++++++++----------------------------- src/k_kart.c | 47 +++++++----------------------------------- src/k_kart.h | 6 ------ src/lua_playerlib.c | 4 ---- src/p_inter.c | 2 -- src/p_saveg.c | 2 -- 8 files changed, 25 insertions(+), 99 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index d3facfed7..6d6a7b8bb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -927,7 +927,6 @@ struct player_t INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp INT16 duelscore; - tic_t dueltimer; UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue diff --git a/src/g_game.c b/src/g_game.c index ea8f78712..dfcc624f1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2180,9 +2180,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 lastsafecheatcheck; UINT16 bigwaypointgap; - INT16 duelscore; - tic_t dueltimer; - roundconditions_t roundconditions; boolean saveroundconditions; @@ -2338,9 +2335,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bigwaypointgap = 0; tallyactive = false; - - dueltimer = 15*TICRATE; - duelscore = 0; } else { @@ -2391,9 +2385,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) lastsafecheatcheck = players[player].lastsafecheatcheck; bigwaypointgap = players[player].bigwaypointgap; - duelscore = players[player].duelscore; - dueltimer = players[player].dueltimer; - tallyactive = players[player].tally.active; if (tallyactive) { @@ -2531,9 +2522,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->griefValue = griefValue; p->griefStrikes = griefStrikes; - p->dueltimer = dueltimer; - p->duelscore = duelscore; - memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index b8272f3b0..8918e3233 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2628,38 +2628,11 @@ void PositionFacesInfo::draw_1p() ; else if (gametyperules & GTR_CIRCUIT) { - if (inDuel) - { - INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; - - colormap = NULL; - - if (K_PlayerLosingDuel(&players[rankplayer[i]]) || players[rankplayer[i]].dueltimer == 0) - { - colormap = R_GetTranslationColormap(TC_RAINBOW, (players[rankplayer[i]].dueltimer == 0) ? SKINCOLOR_CRIMSON : SKINCOLOR_TANGERINE, GTC_CACHE); - flags |= V_STRINGDANCE; - } - - V_DrawStringScaled( - (FACE_X - 5) * FRACUNIT, - (Y + 10) * FRACUNIT, - FRACUNIT, - FRACUNIT, - FRACUNIT, - flags, - colormap, - PINGF_FONT, - va("%02d.%02d", players[rankplayer[i]].dueltimer/TICRATE, (players[rankplayer[i]].dueltimer%TICRATE)*100/TICRATE) - ); - } - else - { - INT32 pos = players[rankplayer[i]].position; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); - } + INT32 pos = players[rankplayer[i]].position; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); } else if (gametyperules & GTR_POINTLIMIT) { @@ -3024,6 +2997,19 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. + if (inDuel) + { + UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; + if (leveltime % 2) + if (abs(stplyr->duelscore) >= 2) + flashflag = V_YELLOWMAP; + + if (stplyr->duelscore >= 0) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("+%d", stplyr->duelscore)); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); + } + if (numlaps != 1) { if (r_splitscreen > 1) diff --git a/src/k_kart.c b/src/k_kart.c index 6d3fa23c6..1fa198003 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4147,23 +4147,19 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel) + if (inDuel && player->position == 1) { player->duelscore += 1; - - if (player->position == 1) - { - tic_t effectiveleveltime = min(leveltime, DUELTIMER_NOBONUS); - player->dueltimer += Easing_Linear(effectiveleveltime*FRACUNIT/DUELTIMER_NOBONUS, DUELTIMER_BONUS, 0); - player->dueltimer = min(player->dueltimer, DUELTIMER_MAX); - } - for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) - { players[i].duelscore -= 1; - } + } + + if (player->duelscore == 3) + { + P_DoPlayerExit(player, 0); + P_DoAllPlayersExit(PF_NOCONTEST, 0); } } @@ -7620,19 +7616,6 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds) return i; } -boolean K_PlayerLosingDuel(player_t *player) -{ - for (UINT8 i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && &players[i] != player) - { - if (players[i].duelscore > player->duelscore) - return true; - } - } - return false; -} - mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); @@ -9402,22 +9385,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } - if (inDuel && K_PlayerLosingDuel(player)) - { - if (player->dueltimer) - { - player->dueltimer--; - if (!(player->dueltimer % 4) && P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_s3k55); - - if (player->dueltimer == 0) - { - P_DoTimeOver(player); - P_DoAllPlayersExit(0, false); - } - } - } - if (!player->invincibilitytimer) player->invincibilityextensions = 0; diff --git a/src/k_kart.h b/src/k_kart.h index 38e80016e..bf73ffcf2 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -88,11 +88,6 @@ Make sure this matches the actual number of states #define AUTORESPAWN_TIME (10*TICRATE) #define AUTORESPAWN_THRESHOLD (7*TICRATE) -#define DUELTIMER_START (10*TICRATE) -#define DUELTIMER_BONUS (2*TICRATE) -#define DUELTIMER_MAX (3*DUELTIMER_START/2) -#define DUELTIMER_NOBONUS (60*TICRATE) - angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); boolean K_IsDuelItem(mobjtype_t type); @@ -208,7 +203,6 @@ void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); -boolean K_PlayerLosingDuel(player_t *player); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); mobj_t *K_FlingPaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index c0bf2df4b..59796ac60 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -677,8 +677,6 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->cheatchecknum); else if (fastcmp(field,"duelscore")) lua_pushinteger(L, plr->duelscore); - else if (fastcmp(field,"dueltimer")) - lua_pushinteger(L, plr->dueltimer); else if (fastcmp(field,"lastsidehit")) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) @@ -1252,8 +1250,6 @@ static int player_set(lua_State *L) plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"duelscore")) plr->duelscore = (INT16)luaL_checkinteger(L, 3); - else if (fastcmp(field,"dueltimer")) - plr->dueltimer = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastsidehit")) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) diff --git a/src/p_inter.c b/src/p_inter.c index 7b5c69b57..8464cf0b9 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1544,8 +1544,6 @@ boolean P_CheckRacers(void) const boolean griefed = (spectateGriefed > 0); boolean eliminateLast = (!K_CanChangeRules(true) || (cv_karteliminatelast.value != 0)); - if (inDuel) - eliminateLast = false; boolean allHumansDone = true; //boolean allBotsDone = true; diff --git a/src/p_saveg.c b/src/p_saveg.c index dbf40c043..d1999a03e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -285,7 +285,6 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEINT32(save->p, players[i].exp); WRITEUINT16(save->p, players[i].gradingpointnum); WRITEINT16(save->p, players[i].duelscore); - WRITEUINT32(save->p, players[i].dueltimer); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -958,7 +957,6 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].exp = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); players[i].duelscore = READINT16(save->p); - players[i].dueltimer = READUINT32(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); From c4fc86b6685903973eda994f1903d6615db232c7 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 5 Sep 2024 00:36:24 -0700 Subject: [PATCH 09/36] Margin Boost --- src/doomstat.h | 1 + src/g_game.c | 1 + src/k_kart.c | 14 +++++++++++++- src/k_kart.h | 2 ++ src/p_saveg.c | 2 ++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/doomstat.h b/src/doomstat.h index dd0e8bc68..9dc8f4034 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -869,6 +869,7 @@ extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; extern UINT8 extralaps; +extern UINT8 overtimecheckpoints; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) extern boolean legitimateexit; diff --git a/src/g_game.c b/src/g_game.c index dfcc624f1..226ea6d67 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -321,6 +321,7 @@ boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start w tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 UINT8 extralaps; // Duel extensions! +UINT8 overtimecheckpoints; // Duel overtime speedups! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures. diff --git a/src/k_kart.c b/src/k_kart.c index 1fa198003..27969e032 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -146,6 +146,7 @@ void K_TimerReset(void) inDuel = rainbowstartavailable = false; linecrossed = 0; extralaps = 0; + overtimecheckpoints = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; } @@ -466,7 +467,10 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) if (cv_4thgear.value && !netgame && (!demo.playback || !demo.netgame) && !modeattacking) value = 3; - return ((13 + (3*value)) << FRACBITS) / 16; + fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; + fixed_t duel = overtimecheckpoints*(1<position == 1) { player->duelscore += 1; + + if (leveltime > DUELOVERTIME) + { + overtimecheckpoints++; + K_AddMessage(va("MARGIN BOOST x%d", overtimecheckpoints), true, false); + S_StartSound(NULL, sfx_gsha6); + } + for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && &players[i] != player) diff --git a/src/k_kart.h b/src/k_kart.h index bf73ffcf2..3e4995dd2 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -74,6 +74,8 @@ Make sure this matches the actual number of states #define RINGVOLUMEREGEN 1 #define RINGTRANSPARENCYREGEN 3 +#define DUELOVERTIME (3*60*TICRATE) + #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) #define MAXTOPACCEL (12*FRACUNIT) diff --git a/src/p_saveg.c b/src/p_saveg.c index d1999a03e..a4679ea8d 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6681,6 +6681,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); WRITEUINT8(save->p, extralaps); + WRITEUINT8(save->p, overtimecheckpoints); WRITEUINT32(save->p, introtime); WRITEUINT32(save->p, starttime); @@ -6888,6 +6889,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); extralaps = (boolean)READUINT8(save->p); + overtimecheckpoints = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); starttime = READUINT32(save->p); From bf2b31a83364eb346b92bc275cd30074a9440b9d Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 5 Sep 2024 01:21:07 -0700 Subject: [PATCH 10/36] Slower margin boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 27969e032..8a7fcfab9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -468,7 +468,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Thu, 5 Sep 2024 01:26:14 -0700 Subject: [PATCH 11/36] Even slower Margin Boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8a7fcfab9..82d41dc86 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -468,7 +468,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Fri, 6 Sep 2024 20:58:06 -0700 Subject: [PATCH 12/36] Duel: cleanup, finish refactor, endcam, position fixes --- src/doomstat.h | 1 - src/g_game.c | 1 - src/k_hud.cpp | 4 +-- src/k_kart.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++--- src/k_kart.h | 3 ++ src/k_tally.cpp | 3 ++ src/p_saveg.c | 2 -- src/p_spec.c | 19 ++++++------- src/p_user.c | 2 +- 9 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index 9dc8f4034..a31a8dc9b 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -868,7 +868,6 @@ extern SINT8 spbplace; extern boolean rainbowstartavailable; extern tic_t linecrossed; extern boolean inDuel; -extern UINT8 extralaps; extern UINT8 overtimecheckpoints; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) diff --git a/src/g_game.c b/src/g_game.c index 226ea6d67..af8105920 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -320,7 +320,6 @@ SINT8 spbplace; // SPB exists, give the person behind better items boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten tic_t linecrossed; // For Time Attack boolean inDuel; // Boolean, keeps track of if it is a 1v1 -UINT8 extralaps; // Duel extensions! UINT8 overtimecheckpoints; // Duel overtime speedups! // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 8918e3233..30cc7bc48 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -2997,7 +2997,7 @@ static void K_drawKartLaps(void) // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. - if (inDuel) + if (K_InRaceDuel()) { UINT32 flashflag = (stplyr->duelscore >= 0) ? V_BLUEMAP : V_REDMAP; if (leveltime % 2) @@ -3018,7 +3018,7 @@ static void K_drawKartLaps(void) bump = 40; } - if (numlaps != 1) + if (numlaps != 1 && !K_InRaceDuel()) { if (r_splitscreen > 1) { diff --git a/src/k_kart.c b/src/k_kart.c index 82d41dc86..43b430e8a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -119,6 +119,27 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt) return !!(mt->thing_args[0]); } +boolean K_InRaceDuel(void) +{ + return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); +} + +player_t *K_DuelOpponent(player_t *player) +{ + if (!K_InRaceDuel()) + return player; // ???? + else + { + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && player - players != i) + return &players[i]; + } + } + + return player; // ???????????? +} + static void K_SpawnDuelOnlyItems(void) { mapthing_t *mt = NULL; @@ -145,7 +166,6 @@ void K_TimerReset(void) numbulbs = 1; inDuel = rainbowstartavailable = false; linecrossed = 0; - extralaps = 0; overtimecheckpoints = 0; timelimitintics = extratimeintics = secretextratime = 0; g_pointlimit = 0; @@ -275,6 +295,9 @@ void K_TimerInit(void) introtime = (108) + 5; // 108 for rotation, + 5 for white fade numbulbs += (numPlayers-2); // Extra POSITION!! time } + + if (K_InRaceDuel()) + numlaps = 200; } } @@ -4151,7 +4174,7 @@ void K_CheckpointCrossAward(player_t *player) K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. - if (inDuel && player->position == 1) + if (K_InRaceDuel() && player->position == 1) { player->duelscore += 1; @@ -4168,10 +4191,48 @@ void K_CheckpointCrossAward(player_t *player) players[i].duelscore -= 1; } - if (player->duelscore == 3) + if (player->duelscore == DUELWINNINGSCORE) { + S_StartSound(NULL, sfx_s3k6a); P_DoPlayerExit(player, 0); P_DoAllPlayersExit(PF_NOCONTEST, 0); + + player_t *opp = K_DuelOpponent(player); + opp->position = 2; + player->position = 1; + + if (opp->distancetofinish - player->distancetofinish < 128) + { + K_StartRoundWinCamera( + player->mo, + player->angleturn + ANGLE_180, + 400*mapobjectscale, + 6*TICRATE, + FRACUNIT/16 + ); + } + else + { + K_StartRoundWinCamera( + opp->mo, + opp->angleturn + ANGLE_180, + 400*mapobjectscale, + 6*TICRATE, + FRACUNIT/16 + ); + } + + } + else + { + // Doing this here because duel exit is a weird path, and we don't want to transform for endcam. + UINT32 skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[(player-players)]].flags + : skins[player->skin].flags; + if (skinflags & SF_IRONMAN) + { + SetRandomFakePlayerSkin(player, true, false); + } } } @@ -10697,7 +10758,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection + extralaps; + const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } @@ -11707,6 +11768,11 @@ void K_KartUpdatePosition(player_t *player) realplayers++; } } + else if (K_InRaceDuel() && player->exiting) + { + // Positions directly set in K_CheckpointCrossAward, don't touch. + return; + } else { for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/k_kart.h b/src/k_kart.h index 3e4995dd2..3b0b10a5b 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -75,6 +75,7 @@ Make sure this matches the actual number of states #define RINGTRANSPARENCYREGEN 3 #define DUELOVERTIME (3*60*TICRATE) +#define DUELWINNINGSCORE (1) #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) @@ -94,6 +95,8 @@ angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t boolean K_IsDuelItem(mobjtype_t type); boolean K_DuelItemAlwaysSpawns(mapthing_t *mt); +boolean K_InRaceDuel(void); +player_t *K_DuelOpponent(player_t *player); void K_TimerReset(void); void K_TimerInit(void); diff --git a/src/k_tally.cpp b/src/k_tally.cpp index b095ff202..3b49c352f 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -46,6 +46,9 @@ boolean level_tally_t::UseBonuses(void) return false; } + if (K_InRaceDuel()) + return false; + // No bonuses / ranking in FREE PLAY or Time Attack return (grandprixinfo.gp == true || K_TimeAttackRules() == false); } diff --git a/src/p_saveg.c b/src/p_saveg.c index a4679ea8d..2183821c2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6680,7 +6680,6 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITESINT8(save->p, spbplace); WRITEUINT8(save->p, rainbowstartavailable); WRITEUINT8(save->p, inDuel); - WRITEUINT8(save->p, extralaps); WRITEUINT8(save->p, overtimecheckpoints); WRITEUINT32(save->p, introtime); @@ -6888,7 +6887,6 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) spbplace = READSINT8(save->p); rainbowstartavailable = (boolean)READUINT8(save->p); inDuel = (boolean)READUINT8(save->p); - extralaps = (boolean)READUINT8(save->p); overtimecheckpoints = (boolean)READUINT8(save->p); introtime = READUINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index de2547392..0712f049f 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -52,6 +52,7 @@ #include "m_easing.h" #include "music.h" #include "k_battle.h" // battleprisons +#include "k_endcam.h" // K_EndCameraIsFreezing() // Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog #include @@ -1993,11 +1994,6 @@ static void K_HandleLapIncrement(player_t *player) boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); - if (specialduelexit) - { - extralaps += 1; - } - // finished race exit setup if (player->laps > numlaps && !specialduelexit) { @@ -2025,7 +2021,8 @@ static void K_HandleLapIncrement(player_t *player) : skins[player->skin].flags; if (skinflags & SF_IRONMAN) { - SetRandomFakePlayerSkin(player, true, false); + if (!K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. + SetRandomFakePlayerSkin(player, true, false); } // Always trust waypoints entering the first lap. @@ -2055,7 +2052,7 @@ static void K_HandleLapIncrement(player_t *player) K_SpawnDriftBoostExplosion(player, 4); K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, (inDuel) ? 20 : 50, player->mo); + K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 50, player->mo); rainbowstartavailable = false; } @@ -2077,7 +2074,9 @@ static void K_HandleLapIncrement(player_t *player) } else if (P_IsDisplayPlayer(player)) { - if (numlaps > 1 && player->laps == numlaps) // final lap + if (K_InRaceDuel()) + S_StartSound(NULL, sfx_s221); + else if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap S_StartSound(NULL, sfx_s221); @@ -2090,7 +2089,7 @@ static void K_HandleLapIncrement(player_t *player) } else { - if ((player->laps > numlaps) && (player->position == 1)) + if ((player->laps > numlaps) && (player->position == 1) && (!K_InRaceDuel())) { // opponent finished S_StartSound(NULL, sfx_s253); @@ -4724,7 +4723,7 @@ void P_SetupSignExit(player_t *player, boolean tie) return; // SRB2Kart: FINALLY, add in an alternative if no place is found - if (player->mo && !P_MobjWasRemoved(player->mo)) + if (player->mo && !P_MobjWasRemoved(player->mo) && !K_EndCameraIsFreezing()) { thing = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->floorz, MT_SIGN); thing->angle = bestAngle; diff --git a/src/p_user.c b/src/p_user.c index c39b105bd..fc19292a6 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1368,7 +1368,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) { UINT8 i; - const boolean dofinishsound = (musiccountdown == 0); + const boolean dofinishsound = (musiccountdown == 0) && (!K_InRaceDuel()); if (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_SPECIAL From bb51c2df4d2fde0bc3933905dbfae40a94086486 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Fri, 6 Sep 2024 21:45:05 -0700 Subject: [PATCH 13/36] Fix lap display in duel --- src/k_hud.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 30cc7bc48..88088915e 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3010,7 +3010,9 @@ static void K_drawKartLaps(void) V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); } - if (numlaps != 1) + boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel()); + + if (drawinglaps) { if (r_splitscreen > 1) bump = 27; @@ -3018,7 +3020,7 @@ static void K_drawKartLaps(void) bump = 40; } - if (numlaps != 1 && !K_InRaceDuel()) + if (drawinglaps) { if (r_splitscreen > 1) { From bdd32cd08f4b84ce880b70e62a850a2da0c67383 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 00:08:38 -0700 Subject: [PATCH 14/36] Tighten "close finish" ending camera --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 43b430e8a..91dd91fa1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4201,7 +4201,7 @@ void K_CheckpointCrossAward(player_t *player) opp->position = 2; player->position = 1; - if (opp->distancetofinish - player->distancetofinish < 128) + if (opp->distancetofinish - player->distancetofinish < 200) { K_StartRoundWinCamera( player->mo, From abc0187b7e07ec20135fb2d5ded7dcbe6888e266 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 01:07:27 -0700 Subject: [PATCH 15/36] Oops, make duels actually score correctly --- src/k_kart.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.h b/src/k_kart.h index 3b0b10a5b..faabd9704 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -75,7 +75,7 @@ Make sure this matches the actual number of states #define RINGTRANSPARENCYREGEN 3 #define DUELOVERTIME (3*60*TICRATE) -#define DUELWINNINGSCORE (1) +#define DUELWINNINGSCORE (3) #define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9) From 1660d9c143ea50f971e2fdadbfa53229c7725157 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 7 Sep 2024 18:57:35 -0700 Subject: [PATCH 16/36] Yet slower Margin Boost --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 91dd91fa1..2c008048c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -491,7 +491,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) value = 3; fixed_t base = ((13 + (3*value)) << FRACBITS) / 16; - fixed_t duel = overtimecheckpoints*(1< Date: Mon, 9 Sep 2024 02:09:30 -0700 Subject: [PATCH 17/36] WIP --- src/k_kart.c | 31 ++++++++++++++++++++++--------- src/p_spec.c | 6 ++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 727b0cca7..217a2aae9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -297,7 +297,7 @@ void K_TimerInit(void) } if (K_InRaceDuel()) - numlaps = 200; + numlaps = 1; } } @@ -10764,15 +10764,28 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) } Z_Free(pathtofinish.array); - // distancetofinish is currently a flat distance to the finish line, but in order to be fully - // correct we need to add to it the length of the entire circuit multiplied by the number of laps - // left after this one. This will give us the total distance to the finish line, and allow item - // distance calculation to work easily - const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; - if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) + if (K_InRaceDuel() && player->position == 1) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; - player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + // As far as we're concerned, the race starts and ends with our position. + // Don't care about laps at all! + } + else + { + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; + if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) + { + UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; + if (K_InRaceDuel()) + { + player_t *opp = K_DuelOpponent(player); + numfulllapsleft = opp->laps - player->laps; + } + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + } } } } diff --git a/src/p_spec.c b/src/p_spec.c index 0712f049f..f9091c145 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1992,10 +1992,8 @@ static void K_HandleLapIncrement(player_t *player) player->latestlap = player->laps; } - boolean specialduelexit = (inDuel && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); - // finished race exit setup - if (player->laps > numlaps && !specialduelexit) + if (player->laps > numlaps && !K_InRaceDuel()) { pflags_t applyflags = 0; if (specialstageinfo.valid == true) @@ -2021,7 +2019,7 @@ static void K_HandleLapIncrement(player_t *player) : skins[player->skin].flags; if (skinflags & SF_IRONMAN) { - if (!K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. + if ((player->laps == 1 && lapisfresh) || !K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary. SetRandomFakePlayerSkin(player, true, false); } From 6e51815df3896174a97c5233fbb7749ff4c8307f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:08:58 -0700 Subject: [PATCH 18/36] Race Duel: only endcam on close finishes --- src/k_kart.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 217a2aae9..3b877bf6d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4211,16 +4211,6 @@ void K_CheckpointCrossAward(player_t *player) FRACUNIT/16 ); } - else - { - K_StartRoundWinCamera( - opp->mo, - opp->angleturn + ANGLE_180, - 400*mapobjectscale, - 6*TICRATE, - FRACUNIT/16 - ); - } } else From fc56b1c802c3d0781c60ac3762336a4cb52cb27d Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:09:40 -0700 Subject: [PATCH 19/36] Race Duel: prettier Margin Boost text --- src/k_kart.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 3b877bf6d..5d3548b4b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4181,7 +4181,10 @@ void K_CheckpointCrossAward(player_t *player) if (leveltime > DUELOVERTIME) { overtimecheckpoints++; - K_AddMessage(va("MARGIN BOOST x%d", overtimecheckpoints), true, false); + if (overtimecheckpoints > 1) + K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false); + else + K_AddMessage(va("Margin Boost!", overtimecheckpoints), true, false); S_StartSound(NULL, sfx_gsha6); } From 2a42d664f0f71d61f7ce7f04f02b76300e4cdedb Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:11:47 -0700 Subject: [PATCH 20/36] Special Stage is never Race Duel --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 5d3548b4b..31b5eb956 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -121,7 +121,7 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt) boolean K_InRaceDuel(void) { - return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)); + return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) && !specialstageinfo.valid; } player_t *K_DuelOpponent(player_t *player) From 62e8b435aad2aad142d3fb3d95e08dc9cb9a70ea Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 10 Sep 2024 15:31:48 -0700 Subject: [PATCH 21/36] 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 0fc8077375ca466ac6b3463ef45b02136e460f2a Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sat, 24 May 2025 17:35:51 -0400 Subject: [PATCH 22/36] 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 23/36] 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 24/36] 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 25/36] 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 26/36] 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 27/36] 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 28/36] 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 938000cd7eb2adb37acfcfa3e248afd1e190bd9f Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 03:01:18 -0400 Subject: [PATCH 29/36] 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 e9f4cf58fe10f96a8270e1f8efabebc82c351fae Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 15:22:23 -0400 Subject: [PATCH 30/36] 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 31/36] 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 32/36] 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 33/36] 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 34/36] 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 d079783e338b5b45d58aa93b4e536c5b531a7478 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 17:17:03 -0400 Subject: [PATCH 35/36] 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 066a8046147ad88c24d25b29bf3c70b8c9dca353 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 25 May 2025 18:19:05 -0400 Subject: [PATCH 36/36] 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);