From 71f9b79e717b35d625e8273b783c6ca1dcaafdeb Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 20 Aug 2023 18:20:46 -0700 Subject: [PATCH 01/46] Prisons: fix exit conditions around spectating Fixes player death not ending a Prisons round, bugged debug feature. Now, do it properly. Don't end the round if the last player spectates. This is more than a debug feature; in Free Play, it lets the player spectate and fly around if they want to, and even come back in, all without restarting the level. --- src/k_battle.c | 7 ++----- src/p_inter.c | 12 +++++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index 7020e7fcf..64673953d 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -127,10 +127,7 @@ void K_CheckBumpers(void) { if (nobumpers > 0 && nobumpers >= numingame) { - // TODO: this would make a great debug feature for release -#ifndef DEVELOP P_DoAllPlayersExit(PF_NOCONTEST, false); -#endif return; } } @@ -142,9 +139,9 @@ void K_CheckBumpers(void) if (numingame <= 1) { - if ((gametyperules & GTR_PRISONS) && (K_CanChangeRules(true) == true)) + if ((gametyperules & GTR_PRISONS) && !battleprisons && (K_CanChangeRules(true) == true)) { - // Reset map to turn on battle capsules + // Reset map to turn on battle prisons if (server) D_MapChange(gamemap, gametype, encoremode, true, 0, false, false); } diff --git a/src/p_inter.c b/src/p_inter.c index 890644320..b68ee18dd 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1556,7 +1556,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget P_PlayDeathSound(target); } - if (K_Cooperative()) + // Prisons Free Play: don't eliminate P1 for + // spectating. Because in Free Play, this player + // can enter the game again, and these flags would + // make them intangible. + if (K_Cooperative() && !target->player->spectator) { target->player->pflags |= PF_ELIMINATED; @@ -2160,6 +2164,12 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, player->pflags |= PF_ELIMINATED; } + if (type == DMG_SPECTATOR) + { + // Set it here so K_CheckBumpers knows about it later. + player->spectator = true; + } + return true; } From e123ed7480ef733e2c48d7093c0448c0476e653d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 26 Aug 2023 19:48:20 +0100 Subject: [PATCH 02/46] MAJOR cleanup of Spectator set/unset - G_AddPlayer now contains CL_ClearPlayer, G_DestroyParty, and playeringame set - Instead of a nasty, complicated block in P_SpawnPlayer, externalise it into G_SpectatePlayerOnJoin - All mid-game human player-to-spectator transitions are handled by P_SetPlayerSpectator, instead of lots of `spectator = true` and associated boilerplate - Simplifies Got_Teamchange MASSIVELY - Of course this is helped by also stripping back team change - This is called by P_KillPlayer, too - P_KillPlayer no longer eats DMG_SPECTATOR when lightsnaking or exiting - G_GametypeHasSpectators condition tidied --- src/d_clisrv.c | 16 ++------ src/d_netcmd.c | 99 ++++++++++++++++++++--------------------------- src/d_netcmd.h | 1 + src/g_demo.c | 39 ++++++++++--------- src/g_demo.h | 2 + src/g_game.c | 50 ++++++++++++++++++++++-- src/g_game.h | 1 + src/k_bot.c | 6 +-- src/k_grandprix.c | 2 +- src/p_inter.c | 41 ++++++++------------ src/p_mobj.c | 75 +++++------------------------------ src/p_user.c | 2 +- 12 files changed, 146 insertions(+), 188 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 41279577f..92d67f224 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3875,22 +3875,14 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum); - { - // Clear player before joining, lest some things get set incorrectly - CL_ClearPlayer(newplayernum); - G_DestroyParty(newplayernum); + G_AddPlayer(newplayernum); + //G_SpectatePlayerOnJoin(newplayernum); -- caused desyncs in this spot :( - playeringame[newplayernum] = true; - G_AddPlayer(newplayernum); - - if (newplayernum+1 > doomcom->numslots) - doomcom->numslots = (INT16)(newplayernum+1); - } + if (newplayernum+1 > doomcom->numslots) + doomcom->numslots = (INT16)(newplayernum+1); newplayer = &players[newplayernum]; - newplayer->jointime = 0; - READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); READMEM(*p, players[newplayernum].public_key, PUBKEYLENGTH); READMEM(*p, clientpowerlevels[newplayernum], sizeof(((serverplayer_t *)0)->powerlevels)); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e5e3e267f..5ca7f30cf 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1189,14 +1189,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) && LUA_HookTeamSwitch(&players[playernum], 0, false, false, false)) // fiiiine, lua can except it { P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); - players[playernum].playerstate = PST_REBORN; - players[playernum].pflags &= ~PF_WANTSTOJOIN; - players[playernum].spectator = true; + if (players[i].spectator) + { + HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); - HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); - - FinalisePlaystateChange(playernum); + FinalisePlaystateChange(playernum); + } } } } @@ -3448,6 +3447,22 @@ static void Command_ServerTeamChange_f(void) SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); } +void P_SetPlayerSpectator(INT32 playernum) +{ + //Make sure you're in the right gametype. + if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) + return; + + // Don't duplicate efforts. + if (players[playernum].spectator) + return; + + players[playernum].spectator = true; + players[playernum].pflags &= ~PF_WANTSTOJOIN; + + players[playernum].playerstate = PST_REBORN; +} + //todo: This and the other teamchange functions are getting too long and messy. Needs cleaning. static void Got_Teamchange(UINT8 **cp, INT32 playernum) { @@ -3519,55 +3534,38 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) { CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); SendKick(playernum, KICK_MSG_CON_FAIL); + return; } //Safety first! // (not respawning spectators here...) wasspectator = (players[playernum].spectator == true); - - if (!wasspectator && gamestate == GS_LEVEL) - { - if (players[playernum].mo) - { - P_DamageMobj(players[playernum].mo, NULL, NULL, 1, - (NetPacket.packet.newteam ? DMG_INSTAKILL : DMG_SPECTATOR)); - } - //else - if (!NetPacket.packet.newteam) - { - players[playernum].playerstate = PST_REBORN; - } - } - players[playernum].pflags &= ~PF_WANTSTOJOIN; + if (!wasspectator) + { + if (gamestate == GS_LEVEL && players[playernum].mo) + { + // The following will call P_SetPlayerSpectator if successful + P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); + } + + //...but because the above could return early under some contexts, we try again here + P_SetPlayerSpectator(playernum); + } //Now that we've done our error checking and killed the player //if necessary, put the player on the correct team/status. - boolean nochangeoccourred = false; + + if (NetPacket.packet.newteam != 0) + { + // This serves us in both teamchange contexts. + players[playernum].pflags |= PF_WANTSTOJOIN; + } if (G_GametypeHasTeams()) { - if (!NetPacket.packet.newteam) - { - players[playernum].ctfteam = 0; - players[playernum].spectator = true; - } - else - { - players[playernum].ctfteam = NetPacket.packet.newteam; - players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false; - nochangeoccourred = true; - } - } - else if (G_GametypeHasSpectators()) - { - if (!NetPacket.packet.newteam) - players[playernum].spectator = true; - else - { - players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false; - nochangeoccourred = true; - } + // This one is, of course, specific. + players[playernum].ctfteam = NetPacket.packet.newteam; } if (NetPacket.packet.autobalance) @@ -3595,20 +3593,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) else if (NetPacket.packet.newteam == 0 && !wasspectator) HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame - /*if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam) - { - UINT8 i; - for (i = 0; i <= splitscreen; i++) - { - if (playernum == g_localplayers[i]) //CTF and Team Match colors. - CV_SetValue(&cv_playercolor[i], NetPacket.packet.newteam + 5); - -this calculation is totally wrong - } - } - }*/ - - if (gamestate != GS_LEVEL || nochangeoccourred == true) + if (gamestate != GS_LEVEL || wasspectator == true) return; FinalisePlaystateChange(playernum); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index ebd24b266..a2129195f 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -245,6 +245,7 @@ void D_SetupVote(void); void D_ModifyClientVote(UINT8 player, SINT8 voted); void D_PickVote(void); void ObjectPlace_OnChange(void); +void P_SetPlayerSpectator(INT32 playernum); boolean IsPlayerAdmin(INT32 playernum); void SetAdminPlayer(INT32 playernum); void ClearAdminPlayers(void); diff --git a/src/g_demo.c b/src/g_demo.c index 2a94b8af9..90959dc20 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -228,10 +228,7 @@ void G_ReadDemoExtraData(void) { if (!playeringame[p]) { - CL_ClearPlayer(p); - playeringame[p] = true; G_AddPlayer(p); - players[p].spectator = true; } for (i = 0; i < MAXAVAILABILITY; i++) @@ -253,28 +250,34 @@ void G_ReadDemoExtraData(void) switch (i) { case DXD_PST_PLAYING: - if (players[p].bot) + if (players[p].spectator == true) { - players[p].spectator = false; - } - else - { - players[p].pflags |= PF_WANTSTOJOIN; + if (players[p].bot) + { + players[p].spectator = false; + } + else + { + players[p].pflags |= PF_WANTSTOJOIN; + } } //CONS_Printf("player %s is despectating on tic %d\n", player_names[p], leveltime); break; case DXD_PST_SPECTATING: - players[p].pflags &= ~PF_WANTSTOJOIN; // double-fuck you - if (players[p].spectator != true) + if (players[p].spectator) { - //CONS_Printf("player %s is spectating on tic %d\n", player_names[p], leveltime); - players[p].spectator = true; - if (players[p].mo) - P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_INSTAKILL); - else - players[p].playerstate = PST_REBORN; + players[p].pflags &= ~PF_WANTSTOJOIN; } + else + { + if (players[p].mo) + { + P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_SPECTATOR); + } + P_SetPlayerSpectator(p); + } + break; case DXD_PST_LEFT: @@ -3420,7 +3423,7 @@ void G_DoPlayDemo(const char *defdemoname) if (!playeringame[displayplayers[0]] || players[displayplayers[0]].spectator) displayplayers[0] = consoleplayer = serverplayer = p; - playeringame[p] = true; + G_AddPlayer(p); players[p].spectator = spectator; if (flags & DEMO_KICKSTART) diff --git a/src/g_demo.h b/src/g_demo.h index 66e0705c1..96217a310 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -127,6 +127,8 @@ extern UINT8 demo_writerng; #define DXD_COLOR 0x10 // color changed #define DXD_FOLLOWER 0x20 // follower was changed +#define DXD_ADDPLAYER (DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER) + #define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed #define DXD_PST_PLAYING 0x01 diff --git a/src/g_game.c b/src/g_game.c index 345b65d95..98f823a18 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2822,11 +2822,53 @@ void G_DoReborn(INT32 playernum) } } +// These are the barest esentials. +// This func probably doesn't even need to know if the player is a bot. void G_AddPlayer(INT32 playernum) { - player_t *p = &players[playernum]; - p->playerstate = PST_REBORN; - demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything + CL_ClearPlayer(playernum); + G_DestroyParty(playernum); + + playeringame[playernum] = true; + + player_t *newplayer = &players[playernum]; + + newplayer->playerstate = PST_REBORN; + newplayer->jointime = 0; + + demo_extradata[playernum] |= DXD_ADDPLAYER; +} + +void G_SpectatePlayerOnJoin(INT32 playernum) +{ + // This is only ever called shortly after the above. + // That calls CL_ClearPlayer, so spectator is false by default + + if (!netgame && !G_GametypeHasTeams() && !G_GametypeHasSpectators()) + return; + + // These are handled automatically elsewhere + if (demo.playback || players[playernum].bot) + return; + + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + // Spectators are of no consequence + if (players[i].spectator) + continue; + + // Prevent splitscreen hosters/joiners from only adding 1 player at a time in empty servers (this will also catch yourself) + if (!players[i].jointime) + continue; + + // A ha! An established player! It's time to spectate + players[playernum].spectator = true; + break; + } } void G_BeginLevelExit(void) @@ -3245,7 +3287,7 @@ boolean G_GametypeHasSpectators(void) #ifdef DEVELOP return true; #endif - return (netgame || (multiplayer && demo.netgame)); + return (netgame || (demo.playback && demo.netgame)); } // diff --git a/src/g_game.h b/src/g_game.h index 84dc5f4ac..8b1ae5b9f 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -239,6 +239,7 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive); void G_AdjustView(UINT8 viewnum, INT32 offset, boolean onlyactive); void G_AddPlayer(INT32 playernum); +void G_SpectatePlayerOnJoin(INT32 playernum); void G_SetExitGameFlag(void); void G_ClearExitGameFlag(void); diff --git a/src/k_bot.c b/src/k_bot.c index 89c7cd244..2a634c415 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -47,12 +47,8 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st { CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); - // Clear player before joining, lest some things get set incorrectly - CL_ClearPlayer(newplayernum); - G_DestroyParty(newplayernum); - - playeringame[newplayernum] = true; G_AddPlayer(newplayernum); + if (newplayernum+1 > doomcom->numslots) doomcom->numslots = (INT16)(newplayernum+1); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 0d4b4b654..65a8e3acd 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -201,7 +201,7 @@ void K_InitGrandPrixBots(void) } else { - players[i].spectator = true; // force spectate for all other players, if they happen to exist? + P_SetPlayerSpectator(i); // force spectate for all other players, if they happen to exist? } } } diff --git a/src/p_inter.c b/src/p_inter.c index b68ee18dd..4cb50bd7a 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2056,30 +2056,29 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 type) { - if (player->respawn.state != RESPAWNST_NONE) + if (type == DMG_SPECTATOR && (G_GametypeHasTeams() || G_GametypeHasSpectators())) { - K_DoInstashield(player); - return false; + P_SetPlayerSpectator(player-players); } - - if (!player->exiting && (specialstageinfo.valid == true || modeattacking & ATTACKING_SPB)) + else { - // TODO: this would make a great debug feature for release -#ifdef DEVELOP - if (type != DMG_SPECTATOR) + if (player->respawn.state != RESPAWNST_NONE) + { + K_DoInstashield(player); + return false; + } + + if (player->exiting) + { + player->mo->destscale = 1; + player->mo->flags |= MF_NOCLIPTHING; + return false; + } + + if (specialstageinfo.valid == true || (modeattacking & ATTACKING_SPB)) { P_DoPlayerExit(player, PF_NOCONTEST); } -#else - P_DoPlayerExit(player, PF_NOCONTEST); -#endif - } - - if (player->exiting) - { - player->mo->destscale = 1; - player->mo->flags |= MF_NOCLIPTHING; - return false; } switch (type) @@ -2164,12 +2163,6 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, player->pflags |= PF_ELIMINATED; } - if (type == DMG_SPECTATOR) - { - // Set it here so K_CheckBumpers knows about it later. - player->spectator = true; - } - return true; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 1e6a797d1..e1090bf33 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11770,61 +11770,24 @@ void P_RespawnSpecials(void) // void P_SpawnPlayer(INT32 playernum) { - UINT8 i, pcount = 0; // MAXPLAYERS if exiting + UINT8 i; player_t *p = &players[playernum]; mobj_t *mobj; + boolean justjoined = (p->jointime <= 1); + if (p->playerstate == PST_REBORN) { - G_PlayerReborn(playernum, (p->jointime <= 1)); + G_PlayerReborn(playernum, justjoined); } - for (i = 0; i < MAXPLAYERS; i++) - { - if (i == playernum) - continue; - if (!playeringame[i] || players[i].spectator) - continue; - if (players[i].exiting) - { - pcount = MAXPLAYERS; - break; - } - if (players[i].jointime <= 1) // Prevent splitscreen hosters/joiners from only adding 1 player at a time in empty servers - continue; - pcount++; - } + if (justjoined) + G_SpectatePlayerOnJoin(playernum); - // spawn as spectator determination - if (multiplayer && demo.playback) - { - ; // Don't mess with spectator values since the demo setup handles them already. - } - else if (p->bot) - { - if (K_PodiumSequence() == true) - ; // This is too late to correct spectator status. Whatever state we're in at this point, our (dog) bed is made. - else if (!(gametyperules & GTR_BOTS) - || (grandprixinfo.gp == true - && grandprixinfo.eventmode != GPEVENT_NONE)) - { - // Bots aren't supposed to be here. - p->spectator = true; - } - else - { - // No point in a spectating bot! - p->spectator = false; - } - } - else if (netgame && p->jointime <= 1 && pcount) - { - p->spectator = true; - } - else if (multiplayer && !netgame) + if (G_GametypeHasTeams()) { // If you're in a team game and you don't have a team assigned yet... - if (G_GametypeHasTeams() && p->ctfteam == 0) + if (!p->spectator && p->ctfteam == 0) { changeteam_union NetPacket; UINT16 usvalue; @@ -11834,9 +11797,6 @@ void P_SpawnPlayer(INT32 playernum) // yes even in splitscreen mode p->spectator = true; - if (playernum&1) p->skincolor = skincolor_redteam; - else p->skincolor = skincolor_blueteam; - // but immediately send a team change packet. NetPacket.packet.playernum = playernum; NetPacket.packet.verification = true; @@ -11845,22 +11805,6 @@ void P_SpawnPlayer(INT32 playernum) usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); } - else // Otherwise, never spectator. - { - // TODO: this would make a great debug feature for release -#ifndef DEVELOP - p->spectator = false; -#endif - } - } - - if (G_GametypeHasTeams()) - { - // Fix stupid non spectator spectators. - if (!p->spectator && !p->ctfteam) - { - p->spectator = true; - } // Fix team colors. // This code isn't being done right somewhere else. Oh well. @@ -11959,8 +11903,7 @@ void P_SpawnPlayer(INT32 playernum) S_StartSound(body, sfx_s1af); } - // I'm not refactoring the loop at the top of this file. - pcount = 0; + UINT8 pcount = 0; for (i = 0; i < MAXPLAYERS; ++i) { diff --git a/src/p_user.c b/src/p_user.c index a5176e580..d31977e8d 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3504,7 +3504,7 @@ boolean P_SpectatorJoinGame(player_t *player) // Pressing fire assigns you to a team that needs players if allowed. // Partial code reproduction from p_tick.c autobalance code. // a surprise tool that will help us later... - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() && player->ctfteam == 0) { INT32 z, numplayersred = 0, numplayersblue = 0; From 4f60d046ba7a0db37cce28e9fb7dd983fa28863d Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 20 Aug 2023 15:18:26 -0700 Subject: [PATCH 03/46] V_DrawTitleCardString, V_TitleCardStringWidth: add 4P font support --- src/hu_stuff.c | 10 +++++++-- src/hu_stuff.h | 3 +++ src/lua_hudlib.c | 8 +++++--- src/lua_hudlib_drawlist.c | 7 +++++-- src/lua_hudlib_drawlist.h | 3 ++- src/st_stuff.c | 8 ++++---- src/v_video.cpp | 43 +++++++++++++++++++++++++++------------ src/v_video.h | 4 ++-- src/y_inter.c | 4 ++-- 9 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index fd9fa3c97..a32365fb6 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -283,6 +283,12 @@ void HU_Init(void) PR ("GTFN"); REG; + PR ("4GTOL"); + REG; + + PR ("4GTFN"); + REG; + DIG (1); DIM (0, 10); @@ -1874,8 +1880,8 @@ static void HU_DrawTitlecardCEcho(void) *line = '\0'; - w = V_TitleCardStringWidth(echoptr); - V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4); + w = V_TitleCardStringWidth(echoptr, false); + V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4, false); y += 32; diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 92ecdbc41..ed483c3a6 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -77,6 +77,9 @@ enum X (GTOL), X (GTFN), + X (GTOL4), + X (GTFN4), + X (TALLNUM), X (NIGHTSNUM), X (PINGNUM), diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 5a5583f1a..a95ff78c2 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -948,6 +948,7 @@ static int libd_drawTitleCardString(lua_State *L) boolean rightalign = lua_optboolean(L, 5); INT32 timer = luaL_optinteger(L, 6, 0); INT32 threshold = luaL_optinteger(L, 7, 0); + boolean p4 = lua_optboolean(L, 8); huddrawlist_h list; flags &= ~V_PARAMMASK; // Don't let crashes happen. @@ -958,9 +959,9 @@ static int libd_drawTitleCardString(lua_State *L) lua_pop(L, 1); if (LUA_HUD_IsDrawListValid(list)) - LUA_HUD_AddDrawTitleCardString(list, x, y, flags, str, rightalign, timer, threshold); + LUA_HUD_AddDrawTitleCardString(list, x, y, flags, str, rightalign, timer, threshold, p4); else - V_DrawTitleCardString(x, y, str, flags, rightalign, timer, threshold); + V_DrawTitleCardString(x, y, str, flags, rightalign, timer, threshold, p4); return 0; } @@ -989,9 +990,10 @@ static int libd_drawKartString(lua_State *L) static int libd_titleCardStringWidth(lua_State *L) { const char *str = luaL_checkstring(L, 1); + boolean p4 = lua_optboolean(L, 2); HUDONLY - lua_pushinteger(L, V_TitleCardStringWidth(str)); + lua_pushinteger(L, V_TitleCardStringWidth(str, p4)); return 1; } diff --git a/src/lua_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c index 68515ae20..8418f1b5a 100644 --- a/src/lua_hudlib_drawlist.c +++ b/src/lua_hudlib_drawlist.c @@ -61,6 +61,7 @@ typedef struct drawitem_s { INT32 timer; INT32 threshold; boolean bossmode; + boolean p4; } drawitem_t; // The internal structure of a drawlist. @@ -358,7 +359,8 @@ void LUA_HUD_AddDrawTitleCardString( const char *str, boolean bossmode, INT32 timer, - INT32 threshold + INT32 threshold, + boolean p4 ) { size_t i = AllocateDrawItem(list); @@ -371,6 +373,7 @@ void LUA_HUD_AddDrawTitleCardString( item->bossmode = bossmode; item->timer = timer; item->threshold = threshold; + item->p4 = p4; } void LUA_HUD_AddDrawKartString( @@ -465,7 +468,7 @@ void LUA_HUD_DrawList(huddrawlist_h list) V_DrawFadeScreen(item->color, item->strength); break; case DI_DrawTitleCardString: - V_DrawTitleCardString(item->x, item->y, itemstr, item->flags, item->bossmode, item->timer, item->threshold); + V_DrawTitleCardString(item->x, item->y, itemstr, item->flags, item->bossmode, item->timer, item->threshold, item->p4); break; case DI_DrawKartString: V_DrawTimerString(item->x, item->y, item->flags, itemstr); diff --git a/src/lua_hudlib_drawlist.h b/src/lua_hudlib_drawlist.h index 15249e4f9..bf2a161e2 100644 --- a/src/lua_hudlib_drawlist.h +++ b/src/lua_hudlib_drawlist.h @@ -111,7 +111,8 @@ void LUA_HUD_AddDrawTitleCardString( const char *str, boolean bossmode, INT32 timer, - INT32 threshold + INT32 threshold, + boolean p4 ); void LUA_HUD_AddDrawKartString( huddrawlist_h list, diff --git a/src/st_stuff.c b/src/st_stuff.c index c1ff32972..1e738dc5f 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -943,10 +943,10 @@ void ST_drawTitleCard(void) // Everything else... if (bossinfo.enemyname) { - bx = V_TitleCardStringWidth(bossinfo.enemyname); + bx = V_TitleCardStringWidth(bossinfo.enemyname, false); // Name. - V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker); + V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker, false); // Under-bar. { @@ -1067,10 +1067,10 @@ void ST_drawTitleCard(void) V_DrawFixedPatch(eggx2*FRACUNIT, eggy2*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tccirclebottom, NULL); // Now the level name. - V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, false, lt_ticker, TTANIMENDTHRESHOLD); + V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, false, lt_ticker, TTANIMENDTHRESHOLD, false); if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) - V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", V_SNAPTORIGHT, false, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD); + V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", V_SNAPTORIGHT, false, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD, false); // the act has a similar graphic animation, but we'll handle it here since it's only like 2 graphics lmfao. if (actnum && actnum < 10) diff --git a/src/v_video.cpp b/src/v_video.cpp index 9c6583916..224530269 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1854,8 +1854,17 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercase, UINT8 *co // V_TitleCardStringWidth // Get the string's width using the titlecard font. -INT32 V_TitleCardStringWidth(const char *str) +INT32 V_TitleCardStringWidth(const char *str, boolean p4) { + int bg_font = GTOL_FONT; + int fg_font = GTFN_FONT; + + if (p4) + { + bg_font = GTOL4_FONT; + fg_font = GTFN4_FONT; + } + INT32 xoffs = 0; const char *ch = str; char c; @@ -1877,15 +1886,15 @@ INT32 V_TitleCardStringWidth(const char *str) c -= LT_FONTSTART; // check if character exists, if not, it's a space. - if (c < 0 || c >= LT_FONTSIZE || !fontv[GTOL_FONT].font[(INT32)c]) + if (c < 0 || c >= LT_FONTSIZE || !fontv[bg_font].font[(INT32)c]) { - xoffs += 10; + xoffs += p4 ? 5 : 10; continue; } - pp = fontv[GTFN_FONT].font[(INT32)c]; + pp = fontv[fg_font].font[(INT32)c]; - xoffs += pp->width-5; + xoffs += pp->width - (p4 ? 3 : 5); } return xoffs; @@ -1894,8 +1903,16 @@ INT32 V_TitleCardStringWidth(const char *str) // V_DrawTitleCardScreen. // see v_video.h's prototype for more information. // -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold) +void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4) { + int bg_font = GTOL_FONT; + int fg_font = GTFN_FONT; + + if (p4) + { + bg_font = GTOL4_FONT; + fg_font = GTFN4_FONT; + } INT32 xoffs = 0; INT32 yoffs = 0; @@ -1916,7 +1933,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole x -= 2; // Account for patch width... if (flags & V_SNAPTORIGHT) - x -= V_TitleCardStringWidth(str); + x -= V_TitleCardStringWidth(str, p4); for (;;ch++, i++) @@ -1933,7 +1950,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole if (*ch == '\n') { xoffs = x; - yoffs += 32; + yoffs += p4 ? 18 : 32; continue; } @@ -1944,14 +1961,14 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole c -= LT_FONTSTART; // check if character exists, if not, it's a space. - if (c < 0 || c >= LT_FONTSIZE || !fontv[GTFN_FONT].font[(INT32)c]) + if (c < 0 || c >= LT_FONTSIZE || !fontv[fg_font].font[(INT32)c]) { - xoffs += 10; + xoffs += p4 ? 5 : 10; continue; } - ol = fontv[GTOL_FONT].font[(INT32)c]; - pp = fontv[GTFN_FONT].font[(INT32)c]; + ol = fontv[bg_font].font[(INT32)c]; + pp = fontv[fg_font].font[(INT32)c]; if (bossmode) { @@ -2004,7 +2021,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, pp, NULL); } - xoffs += pp->width -5; + xoffs += pp->width - (p4 ? 3 : 5); } } diff --git a/src/v_video.h b/src/v_video.h index f7c32505c..b1e47b73d 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -347,10 +347,10 @@ void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, con // threshold: when the letters start disappearing (leave to 0 to disable) (both are INT32 in case you supply negative values...) // NOTE: This function ignores most conventional string flags (V_RETURN8, V_FORCEUPPERCASE ...) // NOTE: This font only works with uppercase letters. -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold); +void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4); // returns thr width of a string drawn using the above function. -INT32 V_TitleCardStringWidth(const char *str); +INT32 V_TitleCardStringWidth(const char *str, boolean p4); // Draw tall nums, used for menu, HUD, intermission void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num); diff --git a/src/y_inter.c b/src/y_inter.c index 6c980a7ee..9ea49d4a0 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1462,7 +1462,7 @@ void Y_IntermissionDrawer(void) } else { - headerwidth = V_TitleCardStringWidth(data.headerstring); + headerwidth = V_TitleCardStringWidth(data.headerstring, false); headerx = (BASEVIDWIDTH - headerwidth)/2; headery = 17; @@ -1490,7 +1490,7 @@ void Y_IntermissionDrawer(void) V_DrawMappedPatch(x + roundx, 39, 0, roundpatch, NULL); } - V_DrawTitleCardString(x + headerx, headery, data.headerstring, 0, false, 0, 0); + V_DrawTitleCardString(x + headerx, headery, data.headerstring, 0, false, 0, 0, false); } // Returns early if there's no players to draw From baf7f82af35f67c39ec63784e3dd3ab44189590c Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 20 Aug 2023 15:21:13 -0700 Subject: [PATCH 04/46] HU_DoTitlecardCEcho: add splitscreen support and interrupt feature - Takes player argument and only appears on the screen if they're a display player. - Uses 4P font when in splitscreen. - Uses 1P font if not in splitscreen, or player argument is NULL -- message to everyone. - Interrupt argument controls whether the message should overwrite another message that's already playing. - Global and player specific messages are considered separately. --- src/acs/call-funcs.cpp | 11 +++- src/hu_stuff.c | 129 +++++++++++++++++++++++++++++++---------- src/hu_stuff.h | 2 +- src/lua_baselib.c | 9 ++- 4 files changed, 112 insertions(+), 39 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index c7ff38591..24170bf41 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -338,6 +338,8 @@ static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type) return false; } +// Unused, but it's here if you need it. +#if 0 /*-------------------------------------------------- static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) @@ -365,6 +367,7 @@ static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) return false; } +#endif /*-------------------------------------------------- static UINT32 ACS_SectorThingCounter(sector_t *sec, mtag_t thingTag, bool (*filter)(mobj_t *)) @@ -823,8 +826,10 @@ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo (void)argV; (void)argC; - if (ACS_ActivatorIsLocal(thread) == true) - HU_DoTitlecardCEcho(thread->printBuf.data()); + auto& info = static_cast(thread)->info; + + if (P_MobjWasRemoved(info.mo) == false && info.mo->player != nullptr) + HU_DoTitlecardCEcho(info.mo->player, thread->printBuf.data(), true); thread->printBuf.drop(); return false; @@ -1203,7 +1208,7 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM (void)argV; (void)argC; - HU_DoTitlecardCEcho(thread->printBuf.data()); + HU_DoTitlecardCEcho(nullptr, thread->printBuf.data(), true); thread->printBuf.drop(); return false; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index a32365fb6..7ab2e7b36 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -62,6 +62,7 @@ #include "r_fps.h" #include "d_clisrv.h" #include "y_inter.h" // Y_PlayerStandingsDrawer +#include "g_party.h" // coords are scaled #define HU_INPUTX 0 @@ -157,10 +158,15 @@ static tic_t cechotimer = 0; static tic_t cechoduration = 5*TICRATE; static INT32 cechoflags = 0; -static char tcechotext[1024]; // buffer for the titlecard text -static tic_t tcechotimer = 0; // goes up by 1 each frame this is active -static tic_t tcechoduration = 0; // Set automatically +struct tcecho_state +{ + char text[1024]; // buffer for the titlecard text + tic_t start; // gametic that the message started + tic_t duration; // Set automatically +}; +#define NUM_TCECHO_STATES (1 + MAXSPLITSCREENPLAYERS) +static struct tcecho_state g_tcecho[NUM_TCECHO_STATES]; static tic_t resynch_ticker = 0; @@ -966,13 +972,6 @@ void HU_Ticker(void) if (cechotimer) cechotimer--; - - if (tcechotimer) - { - tcechotimer++; - if (tcechotimer > tcechoduration) - tcechotimer = 0; - } if (gamestate != GS_LEVEL) { @@ -1835,12 +1834,31 @@ static void HU_DrawCEcho(void) } } -static void HU_DrawTitlecardCEcho(void) +static tic_t HU_TitlecardCEchoElapsed(const struct tcecho_state *state) { - if (tcechotimer) + return max(gametic, state->start) - state->start; +} + +static void HU_DrawTitlecardCEcho(size_t num) +{ + const struct tcecho_state *state = &g_tcecho[num]; + + tic_t elapsed = HU_TitlecardCEchoElapsed(state); + UINT8 viewnum = max(1, num) - 1; + boolean p4 = (num != 0 && r_splitscreen); + + // If the splitscreens were somehow decreased in the + // middle of drawing this, don't draw it. + if (viewnum > r_splitscreen) + { + return; + } + + if (elapsed < state->duration) { INT32 i = 0; - INT32 y = (BASEVIDHEIGHT/2)-16; + INT32 x = BASEVIDWIDTH/2; + INT32 y = BASEVIDHEIGHT/2; INT32 pnumlines = 0; INT32 timeroffset = 0; @@ -1848,11 +1866,28 @@ static void HU_DrawTitlecardCEcho(void) char *echoptr; char temp[1024]; - for (i = 0; tcechotext[i] != '\0'; ++i) - if (tcechotext[i] == '\\') + for (i = 0; state->text[i] != '\0'; ++i) + if (state->text[i] == '\\') pnumlines++; - y -= (pnumlines-1)*16; + if (p4) + { + if (r_splitscreen == 1) // 2P + { + y -= (1 - (viewnum * 2)) * (y / 2); + } + else // 3P / 4P + { + x -= (1 - ((viewnum % 2) * 2)) * (x / 2); + y -= (1 - ((viewnum / 2) * 2)) * (y / 2); + } + + y -= 11 + ((pnumlines-1) * 9); + } + else + { + y -= 18 + ((pnumlines-1) * 16); + } // Prevent crashing because I'm sick of this if (y < 0) @@ -1862,13 +1897,13 @@ static void HU_DrawTitlecardCEcho(void) return; } - strcpy(temp, tcechotext); + strcpy(temp, state->text); echoptr = &temp[0]; while (*echoptr != '\0') { INT32 w; - INT32 timer = (INT32)(tcechotimer - timeroffset); + INT32 timer = (INT32)(elapsed - timeroffset); if (timer <= 0) return; // we don't care. @@ -1880,10 +1915,10 @@ static void HU_DrawTitlecardCEcho(void) *line = '\0'; - w = V_TitleCardStringWidth(echoptr, false); - V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4, false); + w = V_TitleCardStringWidth(echoptr, p4); + V_DrawTitleCardString(x -w/2, y, echoptr, 0, false, timer, TICRATE*4, p4); - y += 32; + y += p4 ? 18 : 32; // offset the timer for the next line. timeroffset += strlen(echoptr); @@ -2043,9 +2078,15 @@ drawontop: if (cechotimer) HU_DrawCEcho(); - - if (tcechotimer) - HU_DrawTitlecardCEcho(); + + { + size_t i; + + for (i = 0; i < NUM_TCECHO_STATES; ++i) + { + HU_DrawTitlecardCEcho(i); + } + } } //====================================================================== @@ -2582,17 +2623,41 @@ void HU_DoCEcho(const char *msg) // No need to bother clearing the buffer or anything. void HU_ClearTitlecardCEcho(void) { - tcechotimer = 0; + size_t i; + + for (i = 0; i < NUM_TCECHO_STATES; ++i) + { + g_tcecho[i].duration = 0; + } } // Similar but for titlecard CEcho and also way less convoluted because I have no clue whatever the fuck they were trying above. -void HU_DoTitlecardCEcho(const char *msg) +void HU_DoTitlecardCEcho(player_t *player, const char *msg, boolean interrupt) { + if (player && !P_IsDisplayPlayer(player)) + { + return; + } + + struct tcecho_state *state = &g_tcecho[0]; + + if (player) + { + state = &g_tcecho[1 + G_PartyPosition(player - players)]; + } + + // If this message should not interrupt an existing + // message. Check if another message is already running. + if (!interrupt && HU_TitlecardCEchoElapsed(state) < state->duration) + { + return; + } + I_OutputMsg("%s\n", msg); // print to log - - strncpy(tcechotext, msg, sizeof(tcechotext)); - strncat(tcechotext, "\\", sizeof(tcechotext) - strlen(tcechotext) - 1); - tcechotext[sizeof(tcechotext) - 1] = '\0'; - tcechotimer = 1; - tcechoduration = TICRATE*6 + strlen(tcechotext); + + strncpy(state->text, msg, sizeof(state->text)); + strncat(state->text, "\\", sizeof(state->text) - strlen(state->text) - 1); + state->text[sizeof(state->text) - 1] = '\0'; + state->start = gametic; + state->duration = TICRATE*6 + strlen(state->text); } diff --git a/src/hu_stuff.h b/src/hu_stuff.h index ed483c3a6..db1cfcc57 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -166,7 +166,7 @@ void HU_SetCEchoFlags(INT32 flags); void HU_DoCEcho(const char *msg); // Titlecard CECHO shite -void HU_DoTitlecardCEcho(const char *msg); +void HU_DoTitlecardCEcho(player_t *player, const char *msg, boolean interrupt); void HU_ClearTitlecardCEcho(void); void DoSayCommand(char *message, SINT8 target, UINT8 flags, UINT8 source); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index bd1771a89..69f0aed21 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3515,9 +3515,12 @@ static int lib_getTimeMicros(lua_State *L) static int lib_startTitlecardCecho(lua_State *L) { - const char *str = luaL_checkstring(L, 1); - HU_DoTitlecardCEcho(str); - + player_t *player = lua_isnil(L, 1) ? NULL : *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + const char *str = luaL_checkstring(L, 2); + boolean interrupt = lua_optboolean(L, 3); + + HU_DoTitlecardCEcho(player, str, interrupt); + return 1; } From ae2fdcbd12ac107476a9c950424b848db6f6f28c Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 19 Aug 2023 18:18:17 -0700 Subject: [PATCH 05/46] cecho command: use titlecard font --- src/command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index fd5044e8b..7ff65cb05 100644 --- a/src/command.c +++ b/src/command.c @@ -785,7 +785,7 @@ static void COM_CEcho_f(void) cechotext[sizeof(cechotext) - 1] = '\0'; - HU_DoCEcho(cechotext); + HU_DoTitlecardCEcho(NULL, cechotext, true); } /** Sets drawing flags for the CECHO command. From 2328ceb4bcaab01da2fa455191d16ecaba664ce9 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 16 Aug 2023 00:43:40 -0700 Subject: [PATCH 06/46] V_TitleCardStringWidth: don't count punctuation, in order to help centering Punctuation, like ellipsis, normally offsets the centered position of text. But this looks subjectively weird. By not counting it, punctuation is basically allowed to extend further off to the sides. The difference looks like: CENTER || \/ || OLD: | TOO LATE... | || NEW: | TOO LATE... | || /\ || CENTER --- src/v_video.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/v_video.cpp b/src/v_video.cpp index 224530269..740df7b80 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1881,6 +1881,17 @@ INT32 V_TitleCardStringWidth(const char *str, boolean p4) continue; } + // For the sake of centering, don't count punctuation. + // TODO: This should ideally be more sophisticated: + // - Only apply on the ends of each line. + // - Check patch width directly for monospace or + // punctuation that isn't necessarily thin. + // - Apply to all string drawing. + if (ispunct(*ch)) + { + continue; + } + c = *ch; c = toupper(c); c -= LT_FONTSTART; From 5d8efb66a4ae3597a81aa03e46d647b4502e3af4 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 16 Aug 2023 00:40:24 -0700 Subject: [PATCH 07/46] Sealed Stars: show text on the screen for loss conditions Player dies: FALL OUT! Player finishes before collecting the emerald: EMPTY HANDED? UFO finishes before player: TOO LATE... --- src/objects/ufo.c | 3 +++ src/p_inter.c | 5 +++++ src/p_spec.c | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 6a9f4bbdc..71ae0b585 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -26,6 +26,7 @@ #include "../r_skins.h" #include "../k_hitlag.h" #include "../acs/interface.h" +#include "../hu_stuff.h" #define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed. #define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration @@ -453,6 +454,8 @@ static void UFOMove(mobj_t *ufo) // Disable player P_DoAllPlayersExit(PF_NOCONTEST, false); + + HU_DoTitlecardCEcho(NULL, "TOO LATE...", false); } if (pathfindsuccess == true) diff --git a/src/p_inter.c b/src/p_inter.c index 890644320..e56e68400 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2069,6 +2069,11 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, #else P_DoPlayerExit(player, PF_NOCONTEST); #endif + + if (specialstageinfo.valid == true) + { + HU_DoTitlecardCEcho(player, "FALL OUT!", false); + } } if (player->exiting) diff --git a/src/p_spec.c b/src/p_spec.c index 85003e43e..1c454f8b5 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2053,6 +2053,8 @@ static void K_HandleLapIncrement(player_t *player) if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) { applyflags |= PF_NOCONTEST; + + HU_DoTitlecardCEcho(player, "EMPTY\\HANDED?", false); } } From 908022c8219351e8bb717f47b20af2abbf73ae29 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 26 Aug 2023 21:44:51 +0100 Subject: [PATCH 08/46] Fix overlapping Title CEcho text If an admin title cecho is active, do not render individual viewport title cechos. Some information is lost, but currently everything expressed through them is not unique while admin title cechos generally are, so they're a safe thing to sacrifice given limited screen space. --- src/hu_stuff.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 7ab2e7b36..4433adf0d 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2079,10 +2079,18 @@ drawontop: if (cechotimer) HU_DrawCEcho(); + const struct tcecho_state *firststate = &g_tcecho[0]; + + // Server messages overwrite player-specific messages + if (HU_TitlecardCEchoElapsed(firststate) < firststate->duration) + { + HU_DrawTitlecardCEcho(0); + } + else { size_t i; - for (i = 0; i < NUM_TCECHO_STATES; ++i) + for (i = 1; i < NUM_TCECHO_STATES; ++i) { HU_DrawTitlecardCEcho(i); } From 6869496843741725e9b42239500a4cf7f5d1d4e4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 26 Aug 2023 23:30:12 +0100 Subject: [PATCH 09/46] Fade to white before the Podium if you successfully get the Emerald/Prize Further Sealed Star polish requested on the gitlab. --- src/g_game.c | 4 ++++ src/k_podium.c | 16 ++++++++++++++++ src/k_podium.h | 14 ++++++++++++++ src/p_setup.c | 14 +++++++++++++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 345b65d95..a2804a566 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1249,6 +1249,10 @@ boolean G_IsTitleCardAvailable(void) if (gametyperules & GTR_SPECIALSTART) return false; + // ALso. + if (K_PodiumSequence() == true) + return false; + // The title card is available. return true; } diff --git a/src/k_podium.c b/src/k_podium.c index 2f1694f52..ef455475b 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -94,6 +94,21 @@ gp_rank_e K_PodiumGrade(void) return podiumData.grade; } +/*-------------------------------------------------- + boolean K_PodiumHasEmerald(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumHasEmerald(void) +{ + if (K_PodiumSequence() == false) + { + return false; + } + + return podiumData.rank.specialWon; +} + /*-------------------------------------------------- UINT8 K_GetPodiumPosition(player_t *player) @@ -284,6 +299,7 @@ boolean K_StartCeremony(void) G_SetGametype(GT_RACE); G_DoLoadLevelEx(false, GS_CEREMONY); + wipegamestate = GS_CEREMONY; // I don't know what else to do here r_splitscreen = 0; // Only one screen for the ceremony R_ExecuteSetViewSize(); diff --git a/src/k_podium.h b/src/k_podium.h index d7b04e3e4..eb73a7aae 100644 --- a/src/k_podium.h +++ b/src/k_podium.h @@ -69,6 +69,20 @@ boolean K_PodiumRanking(void); gp_rank_e K_PodiumGrade(void); +/*-------------------------------------------------- + boolean K_PodiumHasEmerald(void) + + Returns whether the Emerald or Prize was collected. + + Input Arguments:- + N/A + + Return:- + true if the Emerald/Prize was collected during the GP, otherwise false. +--------------------------------------------------*/ +boolean K_PodiumHasEmerald(void); + + /*-------------------------------------------------- UINT8 K_GetPodiumPosition(player_t *player); diff --git a/src/p_setup.c b/src/p_setup.c index 958bfa22e..ee0c5d2eb 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8216,8 +8216,17 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } } - if (gametyperules & GTR_SPECIALSTART) + if (K_PodiumHasEmerald()) { + // Special Stage out + if (ranspecialwipe != 2) + S_StartSound(NULL, sfx_s3k6a); + levelfadecol = 0; + wipetype = wipe_encore_towhite; + } + else if (gametyperules & GTR_SPECIALSTART) + { + // Special Stage in if (ranspecialwipe != 2) S_StartSound(NULL, sfx_s3kaf); levelfadecol = 0; @@ -8225,6 +8234,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } else if (skipstats == 1) { + // MapWarp if (ranspecialwipe != 2) S_StartSound(NULL, sfx_s3k73); levelfadecol = 0; @@ -8232,11 +8242,13 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } else if (encoremode) { + // Encore levelfadecol = 0; wipetype = wipe_encore_towhite; } else { + // Default levelfadecol = 31; } From 6fb7d16c85be4022b7721890c404d48ad9facca2 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 26 Aug 2023 23:47:36 +0100 Subject: [PATCH 10/46] Don't give extra lives in the final entry on a Round Queue (which fixes sealed star sound overload) --- src/p_user.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index a5176e580..11a213730 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1326,7 +1326,9 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) G_BeginLevelExit(); } - if (grandprixinfo.gp == true && player->bot == false && losing == false) + if (grandprixinfo.gp == true + && (roundqueue.size && roundqueue.position < roundqueue.size) // Not the last map of GP + && player->bot == false && losing == false) { const UINT8 lifethreshold = 20; From 79699f47d5aa433bd8d0f0bba992452639a89ebb Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 26 Aug 2023 23:48:18 +0100 Subject: [PATCH 11/46] Sealed Star finish cleanup - No FINISH text - No voices - Warp sound on crossing the line, not any other stuff --- src/k_hud.c | 3 +++ src/p_spec.c | 10 +++++++++- src/p_user.c | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index af15b474a..5b64059d3 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4332,6 +4332,9 @@ static void K_drawKartFinish(boolean finish) if (finish) { + if (gametyperules & GTR_SPECIALSTART) + return; + timer = stplyr->karthud[khud_finish]; kptodraw = kp_racefinish; minsplitstationary = 2; diff --git a/src/p_spec.c b/src/p_spec.c index 1c454f8b5..d3efd7ae5 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1987,7 +1987,15 @@ static void K_HandleLapIncrement(player_t *player) player->starpostnum = 0; - if (P_IsDisplayPlayer(player)) + if (gametyperules & GTR_SPECIALSTART) + { + if (player->laps > numlaps) + { + // Warp out + S_StartSound(NULL, sfx_s3kb3); + } + } + else if (P_IsDisplayPlayer(player)) { if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); diff --git a/src/p_user.c b/src/p_user.c index 11a213730..0fb242fbf 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1296,7 +1296,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) { K_UpdateAllPlayerPositions(); - if (cv_kartvoices.value) + if (cv_kartvoices.value && !(gametyperules & GTR_SPECIALSTART)) { if (P_IsDisplayPlayer(player)) { @@ -1409,6 +1409,11 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) // You've already finished, don't play again ; } + else if (gametyperules & GTR_SPECIALSTART) + { + // Warp out + S_StartSound(NULL, sfx_s3kb3); + } else if (musiccountdown == 0) { // Other people finish From b03056e4c8045ab76c1b9275dc11a92519ae6226 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 26 Aug 2023 16:05:34 -0700 Subject: [PATCH 12/46] Smoothlanding leniency --- src/k_kart.c | 15 ++++++++++++--- src/k_kart.h | 6 ++++-- src/objects/dash-rings.c | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 529ca6c7f..1f3785134 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3936,6 +3936,14 @@ boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, bool return false; } + if (fromAir && player->airtime < STUMBLE_AIRTIME + && player->airtime > 1) // ACHTUNG HACK, sorry. Ground-to-ground transitions sometimes have 1-tic airtime because collision blows + { + // Short airtime with no reaction window, probably a track traversal setpiece. + // Don't punish for these. + return false; + } + if ((player->mo->pitch == oldPitch) && (player->mo->roll == oldRoll)) { @@ -4099,6 +4107,9 @@ void K_UpdateStumbleIndicator(player_t *player) mobj->renderflags &= ~RF_HORIZONTALFLIP; } + if (air && player->airtime < STUMBLE_AIRTIME) + delta = 0; + steepRange = ANGLE_90 - steepVal; delta = max(0, abs(delta) - ((signed)steepVal)); trans = ((FixedDiv(AngleFixed(delta), AngleFixed(steepRange)) * (NUMTRANSMAPS - 2)) + (FRACUNIT/2)) / FRACUNIT; @@ -8253,9 +8264,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->incontrol++; } - player->incontrol = min(player->incontrol, 5*TICRATE); - player->incontrol = max(player->incontrol, -5*TICRATE); - if (player->tumbleBounces > 0) { K_HandleTumbleSound(player); @@ -10436,6 +10444,7 @@ static void K_KartSpindash(player_t *player) // Update fastfall. player->fastfall = player->mo->momz; player->spindash = 0; + player->mo->roll = 0; if (player->fastfallBase == 0) { diff --git a/src/k_kart.h b/src/k_kart.h index e0bcabf06..a85459ddc 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -40,8 +40,10 @@ Make sure this matches the actual number of states #define RR_PROJECTILE_FUSE (8*TICRATE) -#define STUMBLE_STEEP_VAL ANG60 -#define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10) +// 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron +#define STUMBLE_STEEP_VAL (ANG60 + ANG20) +#define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20) +#define STUMBLE_AIRTIME TICRATE*3 #define MAXRINGVOLUME 255 #define MINRINGVOLUME 100 diff --git a/src/objects/dash-rings.c b/src/objects/dash-rings.c index 527157cb1..a680820ed 100644 --- a/src/objects/dash-rings.c +++ b/src/objects/dash-rings.c @@ -161,6 +161,7 @@ static void DashRingLaunch(player_t *player, mobj_t *ring) player->dashRingPushTics = DASHRING_PUSH_TICS; player->mo->rollangle = 0; + player->mo->roll = 0; player->flashing = 0; player->fastfall = 0; K_TumbleInterrupt(player); From ea7ad31fefbd62157c3f04879fa16680ba7f7b4d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 26 Aug 2023 20:28:50 -0400 Subject: [PATCH 13/46] Add Radius Action thing Thing type 4096, activates its action when a player enters the specified radius --- src/deh_tables.c | 2 ++ src/info.c | 27 +++++++++++++++++++++++++++ src/info.h | 2 ++ src/p_mobj.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index e51e2e878..e39a910cb 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5810,6 +5810,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BATTLEUFO_BEAM", "MT_POWERUP_AURA", + + "MT_SCRIPT_THING", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index a1fca74c2..6db8ffdf1 100644 --- a/src/info.c +++ b/src/info.c @@ -30313,6 +30313,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags S_NULL // raisestate }, + + { // MT_SCRIPT_THING + 4096, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index dec72563c..c7a3c2e02 100644 --- a/src/info.h +++ b/src/info.h @@ -7000,6 +7000,8 @@ typedef enum mobj_type MT_POWERUP_AURA, + MT_SCRIPT_THING, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/p_mobj.c b/src/p_mobj.c index 1e6a797d1..bd000ffb1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6716,6 +6716,49 @@ static void P_MobjSceneryThink(mobj_t *mobj) case MT_ARKARROW: Obj_ArkArrowThink(mobj); break; + case MT_SCRIPT_THING: + { + if (mobj->thing_args[2] != 0) + { + // turned off + break; + } + + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false) + { + continue; + } + + player_t *player = &players[i]; + if (P_MobjWasRemoved(player->mo) == true) + { + continue; + } + + fixed_t dist = R_PointToDist2( + mobj->x, mobj->y, + player->mo->x, player->mo->y + ); + + if (dist < mobj->thing_args[0] * FRACUNIT) + { + P_ActivateThingSpecial(mobj, player->mo); + + if (mobj->thing_args[1] == 0) + { + P_RemoveMobj(mobj); + return; + } + + break; + } + } + + break; + } case MT_VWREF: case MT_VWREB: { From b43df44156613c7a13905a5ff975ddaecd7190b5 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 26 Aug 2023 22:23:10 -0700 Subject: [PATCH 14/46] Tyron you suck at using your editor --- src/k_kart.c | 3 +++ src/k_kart.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 1f3785134..a2f188fef 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8263,6 +8263,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->incontrol = 0; player->incontrol++; } + + player->incontrol = min(player->incontrol, 5*TICRATE); + player->incontrol = max(player->incontrol, -5*TICRATE); if (player->tumbleBounces > 0) { diff --git a/src/k_kart.h b/src/k_kart.h index a85459ddc..2f5936064 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -43,7 +43,7 @@ Make sure this matches the actual number of states // 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron #define STUMBLE_STEEP_VAL (ANG60 + ANG20) #define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20) -#define STUMBLE_AIRTIME TICRATE*3 +#define STUMBLE_AIRTIME 35 #define MAXRINGVOLUME 255 #define MINRINGVOLUME 100 From 2f331a4d10051914579586e9e5d2363a4ff5c5eb Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 26 Aug 2023 22:24:18 -0700 Subject: [PATCH 15/46] Let's be slightly more semantically correct about this --- 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 2f5936064..e33580f81 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -43,7 +43,7 @@ Make sure this matches the actual number of states // 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron #define STUMBLE_STEEP_VAL (ANG60 + ANG20) #define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20) -#define STUMBLE_AIRTIME 35 +#define STUMBLE_AIRTIME TICRATE #define MAXRINGVOLUME 255 #define MINRINGVOLUME 100 From f67b4914b9265edacdeabf88887b5ad15564a8e9 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 26 Aug 2023 23:02:27 -0700 Subject: [PATCH 16/46] Remove Texture Filtering option from Hardware Options --- src/menus/options-video-gl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/menus/options-video-gl.c b/src/menus/options-video-gl.c index bb91244f0..48f3ccff8 100644 --- a/src/menus/options-video-gl.c +++ b/src/menus/options-video-gl.c @@ -28,8 +28,10 @@ menuitem_t OPTIONS_VideoOGL[] = {IT_STRING | IT_CVAR, "Texture Quality", "Texture depth. Higher values are recommended.", NULL, {.cvar = &cv_scr_depth}, 0, 0}, + /* {IT_STRING | IT_CVAR, "Texture Filter", "Texture Filter. Nearest is recommended.", NULL, {.cvar = &cv_glfiltermode}, 0, 0}, + */ {IT_STRING | IT_CVAR, "Anisotropic", "Lower values will improve performance at a minor quality loss.", NULL, {.cvar = &cv_glanisotropicmode}, 0, 0}, From eabef184f176878ec95dc15d0eb4481852447add Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 04:14:00 -0700 Subject: [PATCH 17/46] Add P_ResetPitchRoll --- src/k_kart.c | 7 +++---- src/p_local.h | 1 + src/p_mobj.c | 13 +++++++++++-- src/p_user.c | 3 +-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index a2f188fef..556bde894 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3918,7 +3918,7 @@ void K_StumblePlayer(player_t *player) P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); // Reset slope. - player->mo->pitch = player->mo->roll = 0; + P_ResetPitchRoll(player->mo); } boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, boolean fromAir) @@ -4260,7 +4260,7 @@ static void K_HandleTumbleBounce(player_t *player) player->tumbleHeight = 10; player->pflags |= PF_TUMBLELASTBOUNCE; player->mo->rollangle = 0; // p_user.c will stop rotating the player automatically - player->mo->pitch = player->mo->roll = 0; // Prevent Kodachrome Void infinite + P_ResetPitchRoll(player->mo); // Prevent Kodachrome Void infinite } } @@ -6162,8 +6162,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) mo->momz = FixedDiv(mo->momz, FixedSqrt(3*FRACUNIT)); } - mo->pitch = 0; - mo->roll = 0; + P_ResetPitchRoll(mo); if (sound) { diff --git a/src/p_local.h b/src/p_local.h index eaf1d6349..ba3f65438 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -569,6 +569,7 @@ void P_ExplodeMissile(mobj_t *mo); void P_CheckGravity(mobj_t *mo, boolean affect); void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope); void P_SetPitchRoll(mobj_t *mo, angle_t pitch, angle_t yaw); +void P_ResetPitchRoll(mobj_t *mo); fixed_t P_ScaleFromMap(fixed_t n, fixed_t scale); fixed_t P_GetMobjHead(const mobj_t *); fixed_t P_GetMobjFeet(const mobj_t *); diff --git a/src/p_mobj.c b/src/p_mobj.c index 1e6a797d1..8aa533169 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1333,7 +1333,7 @@ void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope) } else { - mo->pitch = mo->roll = 0; + P_ResetPitchRoll(mo); } } @@ -1348,6 +1348,15 @@ void P_SetPitchRoll(mobj_t *mo, angle_t pitch, angle_t yaw) mo->pitch = FixedMul(pitch, FINECOSINE (yaw)); } +// +// P_ResetPitchRoll +// +void P_ResetPitchRoll(mobj_t *mo) +{ + mo->pitch = 0; + mo->roll = 0; +} + #define STOPSPEED (FRACUNIT) // @@ -7191,7 +7200,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } case MT_FLOATINGITEM: { - mobj->pitch = mobj->roll = 0; + P_ResetPitchRoll(mobj); if (mobj->flags & MF_NOCLIPTHING) { if (P_CheckDeathPitCollide(mobj)) diff --git a/src/p_user.c b/src/p_user.c index a5176e580..49c92f86f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -488,8 +488,7 @@ void P_ResetPlayer(player_t *player) if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) { - player->mo->pitch = 0; - player->mo->roll = 0; + P_ResetPitchRoll(player->mo); } } From c080828dde5ad920022145a21b274443eafa7a6e Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 04:16:20 -0700 Subject: [PATCH 18/46] Dash Rings, Fast Fall: reset pitch too --- src/k_kart.c | 2 +- src/objects/dash-rings.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 556bde894..32f312a22 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10446,7 +10446,7 @@ static void K_KartSpindash(player_t *player) // Update fastfall. player->fastfall = player->mo->momz; player->spindash = 0; - player->mo->roll = 0; + P_ResetPitchRoll(player->mo); if (player->fastfallBase == 0) { diff --git a/src/objects/dash-rings.c b/src/objects/dash-rings.c index a680820ed..d40e341bb 100644 --- a/src/objects/dash-rings.c +++ b/src/objects/dash-rings.c @@ -161,7 +161,7 @@ static void DashRingLaunch(player_t *player, mobj_t *ring) player->dashRingPushTics = DASHRING_PUSH_TICS; player->mo->rollangle = 0; - player->mo->roll = 0; + P_ResetPitchRoll(player->mo); player->flashing = 0; player->fastfall = 0; K_TumbleInterrupt(player); From 652792ddd32a9586a79e9fe3805b5d8df4f0f733 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 04:36:07 -0700 Subject: [PATCH 19/46] K_KartSpindash: reorganize so fastfallBase doesn't need to be reset on landing Makes resetting fastfall due to special conditions more straightforward, because you don't need to reset fastfallBase also. --- src/k_kart.c | 10 +++++----- src/p_inter.c | 1 - src/p_user.c | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index c31ef9cdc..b3370c20a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10437,16 +10437,17 @@ static void K_KartSpindash(player_t *player) { if (player->pflags & PF_NOFASTFALL) return; - // Update fastfall. - player->fastfall = player->mo->momz; - player->spindash = 0; - if (player->fastfallBase == 0) + if (player->fastfall == 0) { // Factors 3D momentum. player->fastfallBase = FixedHypot(player->speed, player->mo->momz); } + // Update fastfall. + player->fastfall = player->mo->momz; + player->spindash = 0; + return; } else if (player->fastfall != 0) @@ -10574,7 +10575,6 @@ boolean K_FastFallBounce(player_t *player) player->mo->momz = bounce * P_MobjFlip(player->mo); player->fastfall = 0; - player->fastfallBase = 0; return true; } diff --git a/src/p_inter.c b/src/p_inter.c index 890644320..d73e3622e 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2578,7 +2578,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->driftboost = player->strongdriftboost = 0; player->gateBoost = 0; player->fastfall = 0; - player->fastfallBase = 0; player->ringboost = 0; player->glanceDir = 0; player->pflags &= ~PF_GAINAX; diff --git a/src/p_user.c b/src/p_user.c index a5176e580..cde8642d8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -484,7 +484,6 @@ void P_ResetPlayer(player_t *player) player->trickpanel = 0; player->glanceDir = 0; player->fastfall = 0; - player->fastfallBase = 0; if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) { From 2e748636d71051e6b25a94b9ded81afdcd476d54 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 15:06:51 -0700 Subject: [PATCH 20/46] Create V_CenteredTitleCardStringOffset - V_TitleCardStringWidth no longer has the special case for punctuation, moved to this function instead. - Handles both leading and trailing punctuation, and even spaces. toaster-assisted, thank-you-toaster --- src/hu_stuff.c | 6 +-- src/v_video.cpp | 135 +++++++++++++++++++++++++++++++++++++----------- src/v_video.h | 3 ++ 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 4433adf0d..0b0434f70 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -1902,7 +1902,7 @@ static void HU_DrawTitlecardCEcho(size_t num) while (*echoptr != '\0') { - INT32 w; + INT32 ofs; INT32 timer = (INT32)(elapsed - timeroffset); if (timer <= 0) @@ -1915,8 +1915,8 @@ static void HU_DrawTitlecardCEcho(size_t num) *line = '\0'; - w = V_TitleCardStringWidth(echoptr, p4); - V_DrawTitleCardString(x -w/2, y, echoptr, 0, false, timer, TICRATE*4, p4); + ofs = V_CenteredTitleCardStringOffset(echoptr, p4); + V_DrawTitleCardString(x - ofs, y, echoptr, 0, false, timer, TICRATE*4, p4); y += p4 ? 18 : 32; diff --git a/src/v_video.cpp b/src/v_video.cpp index 740df7b80..d472d4ab3 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1852,9 +1852,8 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercase, UINT8 *co ); } -// V_TitleCardStringWidth -// Get the string's width using the titlecard font. -INT32 V_TitleCardStringWidth(const char *str, boolean p4) +template +static INT32 Internal_TitleCardStringOffset(const char *str, boolean p4) { int bg_font = GTOL_FONT; int fg_font = GTFN_FONT; @@ -1870,45 +1869,123 @@ INT32 V_TitleCardStringWidth(const char *str, boolean p4) char c; patch_t *pp; - for (;;ch++) + // Returns true if it reached the end, false if interrupted. + auto scan = [&](auto keep_going) { - if (!*ch) - break; - - if (*ch == '\n') + for (;;ch++) { - xoffs = 0; - continue; + if (*ch == '\n') + { + xoffs = 0; + return false; + } + + if (!keep_going(*ch)) + { + break; + } + + c = *ch; + c = toupper(c); + c -= LT_FONTSTART; + + // check if character exists, if not, it's a space. + if (c < 0 || c >= LT_FONTSIZE || !fontv[bg_font].font[(INT32)c]) + { + xoffs += p4 ? 5 : 10; + continue; + } + + pp = fontv[fg_font].font[(INT32)c]; + + xoffs += pp->width - (p4 ? 3 : 5); } - // For the sake of centering, don't count punctuation. + return true; + }; + + do + { + // For the sake of centering, don't count spaces or + // punctuation at each end of a line. // TODO: This should ideally be more sophisticated: - // - Only apply on the ends of each line. // - Check patch width directly for monospace or // punctuation that isn't necessarily thin. - // - Apply to all string drawing. - if (ispunct(*ch)) + // - Apply to all centered string drawing. + if constexpr (Centered) { - continue; + // Count leading fluff + if (!scan([](int c) { return c && !isalnum(c); })) + { + continue; + } + + if (!*ch) + { + // ALL fluff, so center it normally. + break; + } + + // xoffs gets halved later, which centers the + // string. If we don't want leading fluff to push + // everything to the right, its full width needs + // to be subtracted, so it's doubled here to + // cancel out the division. + xoffs *= 2; + + INT32 trim = -1; + + bool reached_end = scan( + [&trim, &xoffs](int c) + { + if (isalnum(c)) + { + trim = -1; + } + else if (trim < 0) + { + trim = xoffs; + } + + return c; + } + ); + + // Discount trailing fluff + if (reached_end && trim >= 0) + { + xoffs = trim; + } } - - c = *ch; - c = toupper(c); - c -= LT_FONTSTART; - - // check if character exists, if not, it's a space. - if (c < 0 || c >= LT_FONTSIZE || !fontv[bg_font].font[(INT32)c]) + else { - xoffs += p4 ? 5 : 10; - continue; + scan([](int c) { return c; }); } - - pp = fontv[fg_font].font[(INT32)c]; - - xoffs += pp->width - (p4 ? 3 : 5); } + while (*(ch++)); - return xoffs; + if constexpr (Centered) + { + return xoffs / 2; + } + else + { + return xoffs; + } +} + +// V_TitleCardStringWidth +// Get the string's width using the titlecard font. +INT32 V_TitleCardStringWidth(const char *str, boolean p4) +{ + return Internal_TitleCardStringOffset(str, p4); +} + +// V_CenteredTitleCardStringOffset +// Subtract this offset from an X coordinate to center the string around that point. +INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4) +{ + return Internal_TitleCardStringOffset(str, p4); } // V_DrawTitleCardScreen. diff --git a/src/v_video.h b/src/v_video.h index b1e47b73d..2f4989a24 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -352,6 +352,9 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole // returns thr width of a string drawn using the above function. INT32 V_TitleCardStringWidth(const char *str, boolean p4); +// offset that can be subtracted to center align. +INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4); + // Draw tall nums, used for menu, HUD, intermission void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num); void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits); From e3da0611923836d60754ebbc41d38d7cacf4bcc7 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 15:11:12 -0700 Subject: [PATCH 21/46] cecho command: don't add trailing space Fixes centering for cechos of only punctuation. --- src/command.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/command.c b/src/command.c index 7ff65cb05..8f0574f03 100644 --- a/src/command.c +++ b/src/command.c @@ -777,10 +777,12 @@ static void COM_CEcho_f(void) size_t i; char cechotext[1024] = ""; - for (i = 1; i < COM_Argc(); i++) + strncpy(cechotext, COM_Argv(1), sizeof(cechotext)-1); + + for (i = 2; i < COM_Argc(); i++) { - strncat(cechotext, COM_Argv(i), sizeof(cechotext)-1); strncat(cechotext, " ", sizeof(cechotext)-1); + strncat(cechotext, COM_Argv(i), sizeof(cechotext)-1); } cechotext[sizeof(cechotext) - 1] = '\0'; From 67c2ae021bff62709b16e58ff20cd6e24fed32aa Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 27 Aug 2023 16:00:39 -0700 Subject: [PATCH 22/46] P_SpawnPlayer: add back explicit spectator handling for bots; also remove it from k_grandprix.c Not needed in K_UpdateGrandPrixBots or K_LoadGrandPrixSaveGame because P_SpawnPlayer takes priority. --- src/k_grandprix.c | 12 ------------ src/p_mobj.c | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 65a8e3acd..f65e7aec3 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -307,8 +307,6 @@ void K_LoadGrandPrixSaveGame(void) players[i].botvars.rival = savedata.bots[i].rival; players[i].score = savedata.bots[i].score; - - players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); } } @@ -381,16 +379,6 @@ void K_UpdateGrandPrixBots(void) return; } - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || !players[i].bot) - { - continue; - } - - players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); - } - // Find the rival. for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/p_mobj.c b/src/p_mobj.c index e1090bf33..81ebeb814 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11784,6 +11784,24 @@ void P_SpawnPlayer(INT32 playernum) if (justjoined) G_SpectatePlayerOnJoin(playernum); + if (p->bot && !demo.playback) // Don't mess with spectator values since the demo setup handles them already. + { + if (K_PodiumSequence() == true) + ; // This is too late to correct spectator status. Whatever state we're in at this point, our (dog) bed is made. + else if (!(gametyperules & GTR_BOTS) + || (grandprixinfo.gp == true + && grandprixinfo.eventmode != GPEVENT_NONE)) + { + // Bots aren't supposed to be here. + p->spectator = true; + } + else + { + // No point in a spectating bot! + p->spectator = false; + } + } + if (G_GametypeHasTeams()) { // If you're in a team game and you don't have a team assigned yet... From ecbb6f3af2b1e717b3d4ac71a04e295209c02221 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 28 Aug 2023 00:53:25 -0700 Subject: [PATCH 23/46] G_DoLoadLevelEx: fix compiler error on HU_DoTitlecardCEcho after merge --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index b7f71b4ea..a7377834c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1146,7 +1146,7 @@ void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate) else { // Podium: writetextmap is finished. Yay! - HU_DoTitlecardCEcho(va("Congratulations,\\%s!\\Check the console!", cv_playername[0].string)); + HU_DoTitlecardCEcho(NULL, va("Congratulations,\\%s!\\Check the console!", cv_playername[0].string), true); livestudioaudience_timer = 0; LiveStudioAudience(); From b1782793b58401e9fa6cc5a4bd86b74c73ca53e7 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 28 Aug 2023 01:55:34 -0700 Subject: [PATCH 24/46] R_GenerateBlendTables: allocate copy of palette for use in thread The palette can be freed while this thread is still running, which means the thread would end up reading from freed memory. --- src/r_draw.c | 50 +++++++++++++++++++++++++++++++++++++------------- src/r_draw.h | 1 + 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/r_draw.c b/src/r_draw.c index 44959c9d7..3142388b0 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -200,7 +200,13 @@ CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; #define TRANSTAB_AMTMUL10 (255.0f / 10.0f) -static void R_GenerateBlendTables(void); +struct GenerateBlendTables_State +{ + RGBA_t *masterPalette; + RGBA_t *gammaCorrectedPalette; +}; + +static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state); static void R_GenerateTranslucencyTable(UINT8 *table, RGBA_t* sourcepal, int style, UINT8 blendamt); static void R_AllocateBlendTables(void) @@ -221,8 +227,13 @@ static void R_AllocateBlendTables(void) #ifdef HAVE_THREADS static void R_GenerateBlendTables_Thread(void *userdata) { - (void)userdata; - R_GenerateBlendTables(); + struct GenerateBlendTables_State *state = userdata; + + R_GenerateBlendTables_Core(state); + + free(state->masterPalette); + free(state->gammaCorrectedPalette); + free(state); } #endif @@ -247,16 +258,29 @@ void R_InitTranslucencyTables(void) W_ReadLump(W_GetNumForName("TRANS90"), transtables+0x80000); R_AllocateBlendTables(); - -#ifdef HAVE_THREADS - I_spawn_thread("blend-tables", - R_GenerateBlendTables_Thread, NULL); -#else R_GenerateBlendTables(); -#endif } void R_GenerateBlendTables(void) +{ +#ifdef HAVE_THREADS + // Allocate copies for the worker thread since the originals can be freed in the main thread. + struct GenerateBlendTables_State *state = malloc(sizeof *state); + size_t palsize = 256 * sizeof(RGBA_t); + + state->masterPalette = memcpy(malloc(palsize), pMasterPalette, palsize); + state->gammaCorrectedPalette = memcpy(malloc(palsize), pGammaCorrectedPalette, palsize); + + I_spawn_thread("blend-tables", + R_GenerateBlendTables_Thread, state); +#else + struct GenerateBlendTables_State state = {pMasterPalette, pGammaCorrectedPalette}; + + R_GenerateBlendTables_Core(&state); +#endif +} + +static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state) { INT32 i; @@ -265,12 +289,12 @@ void R_GenerateBlendTables(void) const size_t offs = (0x10000 * i); const UINT8 alpha = (TRANSTAB_AMTMUL10 * ((float)(10-i))); - R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, pGammaCorrectedPalette, AST_ADD, alpha); - R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, pMasterPalette, AST_SUBTRACT, alpha); // intentionally uses pMasterPalette - R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, pGammaCorrectedPalette, AST_REVERSESUBTRACT, alpha); + R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, state->gammaCorrectedPalette, AST_ADD, alpha); + R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, state->masterPalette, AST_SUBTRACT, alpha); // intentionally uses pMasterPalette + R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, state->gammaCorrectedPalette, AST_REVERSESUBTRACT, alpha); } - R_GenerateTranslucencyTable(blendtables[blendtab_modulate], pGammaCorrectedPalette, AST_MODULATE, 0); + R_GenerateTranslucencyTable(blendtables[blendtab_modulate], state->gammaCorrectedPalette, AST_MODULATE, 0); } void R_GenerateTranslucencyTable(UINT8 *table, RGBA_t* sourcepal, int style, UINT8 blendamt) diff --git a/src/r_draw.h b/src/r_draw.h index 200ad5183..6b2fa4044 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -157,6 +157,7 @@ enum extern UINT8 *blendtables[NUMBLENDMAPS]; void R_InitTranslucencyTables(void); +void R_GenerateBlendTables(void); UINT8 *R_GetTranslucencyTable(INT32 alphalevel); UINT8 *R_GetBlendTable(int style, INT32 alphalevel); From 48d1b72ac74724b6917bf980d6a41171fc766e6d Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 31 Aug 2023 05:22:13 -0700 Subject: [PATCH 25/46] SPRTINFO: print warnings instead of I_Error (don't crash the game) --- src/r_picformats.c | 102 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/src/r_picformats.c b/src/r_picformats.c index 054affcf6..996662886 100644 --- a/src/r_picformats.c +++ b/src/r_picformats.c @@ -1466,7 +1466,7 @@ static void copy_to_skin (struct ParseSpriteInfoState *parser, INT32 skinnum) } } -static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean all) +static boolean R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean all) { char *sprinfoToken; size_t sprinfoTokenLength; @@ -1487,12 +1487,15 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be"); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be\n"); + return false; } sprinfoTokenLength = strlen(sprinfoToken); if (sprinfoTokenLength != 1) { - I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Invalid frame \"%s\"\n",sprinfoToken); + Z_Free(sprinfoToken); + return false; } else frameChar = sprinfoToken; @@ -1504,7 +1507,10 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean // Left Curly Brace sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) - I_Error("Error parsing SPRTINFO lump: Missing sprite info"); + { + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Missing sprite info\n"); + return false; + } else { if (strcmp(sprinfoToken,"{")==0) @@ -1513,7 +1519,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be"); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be\n"); + return false; } while (strcmp(sprinfoToken,"}")!=0) { @@ -1550,7 +1557,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be"); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be\n"); + return false; } } } @@ -1574,7 +1582,11 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean INT32 i; if (!parser->foundskins) - I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition"); + { + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: No skins specified in this sprite2 definition\n"); + Z_Free(bright); + return false; + } if (parser->foundskins < 0) { @@ -1607,6 +1619,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean M_Memcpy(&spriteinfo[parser->sprnum], parser->info, sizeof(spriteinfo_t)); } } + + return true; } // @@ -1614,7 +1628,7 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean // // Parse a SPRTINFO lump. // -static void R_ParseSpriteInfo(boolean spr2) +static boolean R_ParseSpriteInfo(boolean spr2) { char *sprinfoToken; size_t sprinfoTokenLength; @@ -1634,7 +1648,8 @@ static void R_ParseSpriteInfo(boolean spr2) sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be"); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be\n"); + return false; } if (!strcmp(sprinfoToken, "*")) // All sprites @@ -1646,7 +1661,9 @@ static void R_ParseSpriteInfo(boolean spr2) sprinfoTokenLength = strlen(sprinfoToken); if (sprinfoTokenLength != 4) { - I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long\n",sprinfoToken); + Z_Free(sprinfoToken); + return false; } else { @@ -1666,7 +1683,10 @@ static void R_ParseSpriteInfo(boolean spr2) for (i = 0; i <= NUMSPRITES; i++) { if (i == NUMSPRITES) - I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName); + { + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown sprite name \"%s\"\n", newSpriteName); + return false; + } if (!memcmp(newSpriteName,sprnames[i],4)) { parser.sprnum = i; @@ -1679,7 +1699,10 @@ static void R_ParseSpriteInfo(boolean spr2) for (i = 0; i <= NUMPLAYERSPRITES; i++) { if (i == NUMPLAYERSPRITES) - I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName); + { + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"\n", newSpriteName); + return false; + } if (!memcmp(newSpriteName,spr2names[i],4)) { parser.spr2num = i; @@ -1695,22 +1718,33 @@ static void R_ParseSpriteInfo(boolean spr2) sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be\n",newSpriteName); + Z_Free(parser.info); + return false; } + + boolean error = false; + if (strcmp(sprinfoToken,"{")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be\n",newSpriteName); + Z_Free(parser.info); + return false; } while (strcmp(sprinfoToken,"}")!=0) { if (stricmp(sprinfoToken, "SKIN")==0) { if (!spr2) - I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition"); + { + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition\n"); + error = true; + break; + } Z_Free(sprinfoToken); R_ParseSpriteInfoSkin(&parser); @@ -1718,31 +1752,46 @@ static void R_ParseSpriteInfo(boolean spr2) else if (stricmp(sprinfoToken, "FRAME")==0) { Z_Free(sprinfoToken); - R_ParseSpriteInfoFrame(&parser, PARSER_FRAME); + if (!R_ParseSpriteInfoFrame(&parser, PARSER_FRAME)) + { + error = true; + break; + } } else if (stricmp(sprinfoToken, "DEFAULT")==0) { Z_Free(sprinfoToken); - R_ParseSpriteInfoFrame(&parser, PARSER_DEFAULT); + if (!R_ParseSpriteInfoFrame(&parser, PARSER_DEFAULT)) + { + error = true; + break; + } } else { - I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s\n",sprinfoToken,newSpriteName); + error = true; + break; } sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { - I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be\n",newSpriteName); + error = true; + break; } } } else { - I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"\n",newSpriteName,sprinfoToken); + error = true; } Z_Free(sprinfoToken); Z_Free(parser.info); + + return !error; } // @@ -1777,13 +1826,20 @@ void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum) sprinfoToken = M_GetToken(sprinfoText); while (sprinfoToken != NULL) { + boolean error = true; + if (!stricmp(sprinfoToken, "SPRITE")) - R_ParseSpriteInfo(false); + error = !R_ParseSpriteInfo(false); else if (!stricmp(sprinfoToken, "SPRITE2")) - R_ParseSpriteInfo(true); + error = !R_ParseSpriteInfo(true); else - I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken); + CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown keyword \"%s\"\n", sprinfoToken); + Z_Free(sprinfoToken); + + if (error) + break; + sprinfoToken = M_GetToken(NULL); } Z_Free((void *)sprinfoText); From 1dd2e7031a7aeaaa7ae9958c8a1811c6570979ea Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 31 Aug 2023 16:46:46 +0100 Subject: [PATCH 26/46] Fix the spectating bots in GP issue a different way The solution in 67c2ae021bff62709b16e58ff20cd6e24fed32aa happens too late for K_TimeAttackRules --- src/k_grandprix.c | 17 +++++++++++++++++ src/p_mobj.c | 18 ------------------ src/p_setup.c | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index f65e7aec3..e4ab54aab 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -307,6 +307,8 @@ void K_LoadGrandPrixSaveGame(void) players[i].botvars.rival = savedata.bots[i].rival; players[i].score = savedata.bots[i].score; + + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); } } @@ -379,6 +381,21 @@ void K_UpdateGrandPrixBots(void) return; } + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || !players[i].bot) + { + continue; + } + + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); + } + + if (grandprixinfo.wonround == false) + { + return; + } + // Find the rival. for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 39a4c7de8..f9763afb5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11801,24 +11801,6 @@ void P_SpawnPlayer(INT32 playernum) if (justjoined) G_SpectatePlayerOnJoin(playernum); - if (p->bot && !demo.playback) // Don't mess with spectator values since the demo setup handles them already. - { - if (K_PodiumSequence() == true) - ; // This is too late to correct spectator status. Whatever state we're in at this point, our (dog) bed is made. - else if (!(gametyperules & GTR_BOTS) - || (grandprixinfo.gp == true - && grandprixinfo.eventmode != GPEVENT_NONE)) - { - // Bots aren't supposed to be here. - p->spectator = true; - } - else - { - // No point in a spectating bot! - p->spectator = false; - } - } - if (G_GametypeHasTeams()) { // If you're in a team game and you don't have a team assigned yet... diff --git a/src/p_setup.c b/src/p_setup.c index e9281f11b..e205bad70 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8559,7 +8559,7 @@ void P_PostLoadLevel(void) K_InitGrandPrixBots(); grandprixinfo.initalize = false; } - else if (grandprixinfo.wonround == true) + else { K_UpdateGrandPrixBots(); grandprixinfo.wonround = false; From a7b065c4d8ea1df9f22679ba8389d8e30bdbcb6d Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 31 Aug 2023 17:30:05 +0100 Subject: [PATCH 27/46] P_InitPlayers: Tidy up the barely-significant difference between G_DoReborn and G_SpawnPlayer Only kills objectplace at map start, not general respawn --- src/g_game.c | 3 --- src/p_setup.c | 12 ++++-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 9dab1231a..1f16f07c3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2831,9 +2831,6 @@ void G_DoReborn(INT32 playernum) { player_t *player = &players[playernum]; - // Make sure objectplace is OFF when you first start the level! - OP_ResetObjectplace(); - { // respawn at the start mobj_t *oldmo = NULL; diff --git a/src/p_setup.c b/src/p_setup.c index e205bad70..8c57bace8 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7835,6 +7835,9 @@ static void P_InitPlayers(void) UINT8 i; INT32 skin = -1; + // Make sure objectplace is OFF when you first start the level! + OP_ResetObjectplace(); + // Are we forcing a character? if (gametype == GT_TUTORIAL) { @@ -7871,14 +7874,7 @@ static void P_InitPlayers(void) // followercolor can be left alone for hopefully obvious reasons } - if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false) - { - G_DoReborn(i); - } - else // gametype is race - { - G_SpawnPlayer(i); - } + G_SpawnPlayer(i); players[i].xtralife = 0; // extra lives do not ever carry over from the previous round } From 093510618980031cb96296650c2fee955dd15793 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 21:03:47 +0100 Subject: [PATCH 28/46] F_RunWipe: Don't draw menus on top if there's no renderer Fixes crash on dedicated --- src/f_wipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/f_wipe.c b/src/f_wipe.c index 5a1df0e96..48dfc0f60 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -454,7 +454,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col I_OsPolling(); I_UpdateNoBlit(); - if (drawMenu) + if (drawMenu && rendermode != render_none) { #ifdef HAVE_THREADS I_lock_mutex(&k_menu_mutex); From c2148bf501f6f5decf387fb46d8f4e568e60de2c Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 21:08:49 +0100 Subject: [PATCH 29/46] Testing #define for testing Master Server Listing by yourself, without a second IP address to host Disabled by default --- src/http-mserv.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/http-mserv.c b/src/http-mserv.c index d47122cbf..368f1cdda 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -441,7 +441,12 @@ HMS_fetch_servers (msg_server_t *list, int query_id) break; #endif +//#define MSERVTESTALONE +#ifdef MSERVTESTALONE + strcpy(list[i].ip, "127.0.0.1"); // MS test without needing a second person to host +#else strlcpy(list[i].ip, address, sizeof list[i].ip); +#endif strlcpy(list[i].port, port, sizeof list[i].port); if (contact) From 170894ee7129ec7592c7b30906c37b86444919c6 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 21:11:31 +0100 Subject: [PATCH 30/46] Update SRB2VERSION for impending release Was previously "2.0" in reference to how this was once just a major patch for the previous entry in the series. --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index e350082fc..c8b673542 100644 --- a/src/version.h +++ b/src/version.h @@ -1,4 +1,4 @@ -#define SRB2VERSION "2.0"/* this must be the first line, for cmake !! */ +#define SRB2VERSION "1.0"/* this must be the first line, for cmake !! */ // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/showgroups.php ). // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server. From 7fd64a4c3de36e27e38f3b19672d2bf178570cc1 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 21:41:51 +0100 Subject: [PATCH 31/46] Get the Server Browser working again - KartKrew/Kart-Public!313 has finally been integrated with the changed menu code - Attempts multiple times to get every public server, up to nettimeout - Clear the server list when entering/exiting and refrehing - Show visible servers and potential servers opposite the Refresh input - I would have done this in another commit, but the system was too broken to repair without just putting the working solution in wholesale - Improved visual scrolling behaviour - Pressing A actually joins the server - CORE vs MODDED is handled in SL_InsertServer, not a seperate function to drop/memcpy things around Also, abstract out the little "PLEASE WAIT..." message popup during potential IO block into M_PleaseWait (only used on connect process via Menu) --- src/d_clisrv.c | 63 ++++++++++--- src/d_clisrv.h | 3 +- src/k_menu.h | 9 +- src/k_menudraw.c | 85 +++++++++++++++++ src/menus/play-online-join-ip.c | 24 +++-- src/menus/play-online-server-browser.c | 126 +++++++++++++------------ 6 files changed, 223 insertions(+), 87 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 92d67f224..bebaae8f3 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1550,6 +1550,10 @@ static void SendAskInfo(INT32 node) serverelem_t serverlist[MAXSERVERLIST]; UINT32 serverlistcount = 0; +UINT32 serverlistultimatecount = 0; + +static boolean resendserverlistnode[MAXNETNODES]; +static tic_t serverlistepoch; static void SL_ClearServerList(INT32 connectedserver) { @@ -1562,6 +1566,8 @@ static void SL_ClearServerList(INT32 connectedserver) serverlist[i].node = 0; } serverlistcount = 0; + + memset(resendserverlistnode, 0, sizeof resendserverlistnode); } static UINT32 SL_SearchServer(INT32 node) @@ -1578,6 +1584,8 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) { UINT32 i; + resendserverlistnode[node] = false; + // search if not already on it i = SL_SearchServer(node); if (i == UINT32_MAX) @@ -1601,6 +1609,9 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) if (strcmp(info->application, SRB2APPLICATION)) return;/* that's a different mod */ + if (info->modifiedgame != (mpmenu.room == 1)) + return;/* CORE vs MODDED! */ + i = serverlistcount++; } @@ -1617,6 +1628,8 @@ void CL_QueryServerList (msg_server_t *server_list) CL_UpdateServerList(); + serverlistepoch = I_GetTime(); + for (i = 0; server_list[i].header.buffer[0]; i++) { // Make sure MS version matches our own, to @@ -1629,19 +1642,43 @@ void CL_QueryServerList (msg_server_t *server_list) if (node == -1) break; // no more node free SendAskInfo(node); - // Force close the connection so that servers can't eat - // up nodes forever if we never get a reply back from them - // (usually when they've not forwarded their ports). - // - // Don't worry, we'll get in contact with the working - // servers again when they send SERVERINFO to us later! - // - // (Note: as a side effect this probably means every - // server in the list will probably be using the same node (e.g. node 1), - // not that it matters which nodes they use when - // the connections are closed afterwards anyway) - // -- Monster Iestyn 12/11/18 - Net_CloseConnection(node|FORCECLOSE); + + resendserverlistnode[node] = true; + // Leave this node open. It'll be closed if the + // request times out (CL_TimeoutServerList). + } + } + + serverlistultimatecount = i; +} + +#define SERVERLISTRESENDRATE NEWTICRATE + +void CL_TimeoutServerList(void) +{ + if (netgame && serverlistultimatecount > serverlistcount) + { + const tic_t timediff = I_GetTime() - serverlistepoch; + const tic_t timetoresend = timediff % SERVERLISTRESENDRATE; + const boolean timedout = timediff > connectiontimeout; + + if (timedout || (timediff > 0 && timetoresend == 0)) + { + INT32 node; + + for (node = 1; node < MAXNETNODES; ++node) + { + if (resendserverlistnode[node]) + { + if (timedout) + Net_CloseConnection(node|FORCECLOSE); + else + SendAskInfo(node); + } + } + + if (timedout) + serverlistultimatecount = serverlistcount; } } } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index fdcf4d0fe..04221aeb9 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -460,7 +460,7 @@ struct serverelem_t }; extern serverelem_t serverlist[MAXSERVERLIST]; -extern UINT32 serverlistcount; +extern UINT32 serverlistcount, serverlistultimatecount; extern INT32 mapchangepending; // Points inside doomcom @@ -595,6 +595,7 @@ void CL_ClearPlayer(INT32 playernum); void CL_RemovePlayer(INT32 playernum, kickreason_t reason); void CL_QueryServerList(msg_server_t *list); void CL_UpdateServerList(void); +void CL_TimeoutServerList(void); // Is there a game running boolean Playing(void); diff --git a/src/k_menu.h b/src/k_menu.h index 875da6535..cd740df55 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -29,8 +29,6 @@ extern "C" { #endif -#define SERVERLISTDEBUG - // flags for items in the menu // menu handle (what we do when key is pressed #define IT_TYPE 14 // (2+4+8) @@ -842,6 +840,8 @@ extern struct mpmenu_s { } mpmenu; +void M_PleaseWait(void); + // Time Attack void M_PrepareTimeAttack(INT32 choice); void M_StartTimeAttack(INT32 choice); @@ -895,11 +895,6 @@ void Fetch_servers_thread (int *id); void M_RefreshServers(INT32 choice); void M_ServersMenu(INT32 choice); -// for debugging purposes... -#ifdef SERVERLISTDEBUG -void M_ServerListFillDebug(void); -#endif - // Options menu: // mode descriptions for video mode menu diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 800ff3c6c..78b96ee87 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3408,6 +3408,89 @@ void M_DrawMPRoomSelect(void) } // SERVER BROWSER +static void M_DrawServerCountAndHorizontalBar(void) +{ + const char *text; + INT32 y = currentMenu->y+STRINGHEIGHT; + + const char throbber[4] = {'-', '\\', '|', '/'}; + UINT8 throbindex = (mpmenu.ticker/4) % 4; + + switch (M_GetWaitingMode()) + { + case M_WAITING_VERSION: + text = "Checking for updates"; + break; + + case M_WAITING_SERVERS: + text = "Loading server list"; + break; + + default: + if (serverlistultimatecount > serverlistcount) + { + text = va("%d/%d server%s found...", + serverlistcount, + serverlistultimatecount, + serverlistultimatecount == 1 ? "" : "s" + ); + } + else + { + throbindex = UINT8_MAX; // No throbber! + if (serverlistcount > 0) + { + text = va("%d server%s found", + serverlistcount, + serverlistcount == 1 ? "" : "s" + ); + } + else + { + text = "No servers found"; + } + } + } + + if (throbindex == UINT8_MAX) + { + V_DrawRightAlignedString( + BASEVIDWIDTH - currentMenu->x, + y, + highlightflags, + text + ); + } + else + { + V_DrawRightAlignedString( + BASEVIDWIDTH - currentMenu->x - 12, y, + highlightflags, + text + ); + + V_DrawCenteredString( // Only clean way to center the throbber without exposing character width + BASEVIDWIDTH - currentMenu->x - 4, y, + highlightflags, + va("%c", throbber[throbindex]) + ); + } + + // Did you change the Server Browser address? Have a little reminder. + + INT32 mservflags = 0; + if (CV_IsSetToDefault(&cv_masterserver)) + mservflags = highlightflags|V_30TRANS; + else + mservflags = warningflags; + + y = BASEVIDHEIGHT - 24; + + V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10, 0, 31, 5); + V_DrawCenteredString(BASEVIDWIDTH/2, y, + mservflags, va("MS: %s", cv_masterserver.string)); +} + void M_DrawMPServerBrowser(void) { patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE); @@ -3508,6 +3591,8 @@ void M_DrawMPServerBrowser(void) // normal menu options M_DrawGenericMenu(); + // And finally, the overlay bar! + M_DrawServerCountAndHorizontalBar(); } // OPTIONS MENU diff --git a/src/menus/play-online-join-ip.c b/src/menus/play-online-join-ip.c index a176d4e72..b3b0b3e8e 100644 --- a/src/menus/play-online-join-ip.c +++ b/src/menus/play-online-join-ip.c @@ -6,6 +6,7 @@ #include "../i_system.h" // I_OsPolling #include "../i_video.h" // I_UpdateNoBlit #include "../m_misc.h" // NUMLOGIP +#include "../f_finale.h" // g_wipeskiprender menuitem_t PLAY_MP_JoinIP[] = { @@ -56,6 +57,21 @@ void M_MPJoinIPInit(INT32 choice) M_SetupNextMenu(&PLAY_MP_JoinIPDef, true); } +void M_PleaseWait(void) +{ + if (rendermode == render_none) + return; + + g_wipeskiprender = true; + + M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "PLEASE WAIT..."); + I_OsPolling(); + I_UpdateNoBlit(); + if (rendermode == render_soft) + I_FinishUpdate(); // page flip or blit buffer +} + // Attempts to join a given IP from the menu. void M_JoinIP(const char *ipa) { @@ -67,13 +83,7 @@ void M_JoinIP(const char *ipa) COM_BufAddText(va("connect \"%s\"\n", ipa)); - // A little "please wait" message. - M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server..."); - I_OsPolling(); - I_UpdateNoBlit(); - if (rendermode == render_soft) - I_FinishUpdate(); // page flip or blit buffer + M_PleaseWait(); } boolean M_JoinIPInputs(INT32 ch) diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 730a34956..80aa41038 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -3,11 +3,14 @@ #include "../k_menu.h" #include "../v_video.h" -#include "../i_system.h" // I_OsPolling -#include "../i_video.h" // I_UpdateNoBlit +#include "../s_sound.h" + +//#define SERVERLISTDEBUG #ifdef SERVERLISTDEBUG #include "../m_random.h" + +void M_ServerListFillDebug(void); #endif menuitem_t PLAY_MP_ServerBrowser[] = @@ -16,8 +19,8 @@ menuitem_t PLAY_MP_ServerBrowser[] = {IT_STRING | IT_CVAR, "SORT BY", NULL, // tooltip MUST be null. NULL, {.cvar = &cv_serversort}, 0, 0}, - {IT_STRING, "REFRESH", NULL, - NULL, {NULL}, 0, 0}, + {IT_STRING | IT_CALL, "REFRESH", NULL, + NULL, {.routine = &M_RefreshServers}, 0, 0}, {IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, }; @@ -42,28 +45,6 @@ menu_t PLAY_MP_ServerBrowserDef = { // for server fetch threads... M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; -// depending on mpmenu.room, either allows only unmodded servers or modded ones. Remove others from the list. -// we do this by iterating backwards. -static void M_CleanServerList(void) -{ - UINT8 i = serverlistcount; - - while (i) - { - - if (serverlist[i].info.modifiedgame != mpmenu.room) - { - // move everything after this index 1 slot down... - if (i != serverlistcount) - memcpy(&serverlist[i], &serverlist[i+1], sizeof(serverelem_t)*(serverlistcount-i)); - - serverlistcount--; - } - - i--; - } -} - void M_SetWaitingMode (int mode) { @@ -180,31 +161,19 @@ void M_RefreshServers(INT32 choice) { (void)choice; - // Display a little "please wait" message. - M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers..."); - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); - I_OsPolling(); - I_UpdateNoBlit(); - if (rendermode == render_soft) - I_FinishUpdate(); // page flip or blit buffer + CL_UpdateServerList(); +#ifdef SERVERLISTDEBUG + M_ServerListFillDebug(); +#else /*SERVERLISTDEBUG*/ #ifdef MASTERSERVER #ifdef HAVE_THREADS Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); #else/*HAVE_THREADS*/ Fetch_servers_thread(NULL); #endif/*HAVE_THREADS*/ -#else/*MASTERSERVER*/ - CL_UpdateServerList(); #endif/*MASTERSERVER*/ - -#ifdef SERVERLISTDEBUG - M_ServerListFillDebug(); -#endif - M_CleanServerList(); - M_SortServerList(); - +#endif /*SERVERLISTDEBUG*/ } #ifdef UPDATE_ALERT @@ -253,6 +222,9 @@ void M_ServersMenu(INT32 choice) // modified game check: no longer handled // we don't request a restart unless the filelist differs + CL_UpdateServerList(); + + mpmenu.ticker = 0; mpmenu.servernum = 0; mpmenu.scrolln = 0; mpmenu.slide = 0; @@ -260,6 +232,10 @@ void M_ServersMenu(INT32 choice) M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false); itemOn = 0; +#ifdef SERVERLISTDEBUG + M_ServerListFillDebug(); +#else /*SERVERLISTDEBUG*/ + #if defined (MASTERSERVER) && defined (HAVE_THREADS) I_lock_mutex(&ms_QueryId_mutex); { @@ -289,12 +265,7 @@ void M_ServersMenu(INT32 choice) M_RefreshServers(0); #endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ -#ifdef SERVERLISTDEBUG - M_ServerListFillDebug(); -#endif - - M_CleanServerList(); - M_SortServerList(); +#endif /*SERVERLISTDEBUG*/ } #ifdef SERVERLISTDEBUG @@ -304,7 +275,7 @@ void M_ServerListFillDebug(void) { UINT8 i = 0; - serverlistcount = 10; + serverlistcount = 40; memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience... for (i = 0; i < serverlistcount; i++) @@ -327,6 +298,8 @@ void M_ServerListFillDebug(void) CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame); } + + M_SortServerList(); } #endif // SERVERLISTDEBUG @@ -397,14 +370,33 @@ void M_SortServerList(void) // Server browser inputs & ticker void M_MPServerBrowserTick(void) { + mpmenu.ticker++; mpmenu.slide /= 2; + +#if defined (MASTERSERVER) && defined (HAVE_THREADS) + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + CL_QueryServerList(ms_ServerList); + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); +#endif + + CL_TimeoutServerList(); } // Input handler for server browser. boolean M_ServerBrowserInputs(INT32 ch) { UINT8 pid = 0; - UINT8 maxscroll = serverlistcount-(SERVERSPERPAGE/2); + INT16 maxscroll = serverlistcount - (SERVERSPERPAGE/2) - 2; // Why? Because + if (maxscroll < 0) + maxscroll = 0; + (void) ch; if (!itemOn && menucmd[pid].dpad_ud < 0) @@ -412,21 +404,37 @@ boolean M_ServerBrowserInputs(INT32 ch) M_PrevOpt(); // go to itemOn 2 if (serverlistcount) { - UINT8 prevscroll = mpmenu.scrolln; + INT32 prevscroll = mpmenu.scrolln; - mpmenu.servernum = serverlistcount; + mpmenu.servernum = serverlistcount-1; mpmenu.scrolln = maxscroll; - mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); + mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln); } else { itemOn = 1; // Sike! If there are no servers, go to refresh instead. } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + return true; // overwrite behaviour. } else if (itemOn == 2) // server browser itself... { +#ifndef SERVERLISTDEBUG + if (M_MenuConfirmPressed(pid)) + { + M_SetMenuDelay(pid); + + COM_BufAddText(va("connect node %d\n", serverlist[mpmenu.servernum].node)); + + M_PleaseWait(); + + return true; + } +#endif + // we have to manually do that here. if (M_MenuBackPressed(pid)) { @@ -436,12 +444,13 @@ boolean M_ServerBrowserInputs(INT32 ch) else if (menucmd[pid].dpad_ud > 0) // down { - if (mpmenu.servernum >= serverlistcount-1) + if ((UINT32)(mpmenu.servernum+1) >= serverlistcount) { - UINT8 prevscroll = mpmenu.scrolln; + INT32 prevscroll = mpmenu.scrolln; + mpmenu.servernum = 0; mpmenu.scrolln = 0; - mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); + mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln); M_NextOpt(); // Go back to the top of the real menu. } @@ -460,14 +469,13 @@ boolean M_ServerBrowserInputs(INT32 ch) } else if (menucmd[pid].dpad_ud < 0) { - if (!mpmenu.servernum) { M_PrevOpt(); } else { - if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln) + if (mpmenu.servernum <= (INT16)maxscroll && mpmenu.scrolln) { mpmenu.scrolln--; mpmenu.slide -= SERVERSPACE; From 000e6e273cd7d7cd1c5bc77a4eb461af16ecc0b0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 23:22:41 +0100 Subject: [PATCH 32/46] M_DrawServerListDebug: Produce more useful spread of test case fake servers --- src/menus/play-online-server-browser.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 80aa41038..e6db83227 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -281,20 +281,20 @@ void M_ServerListFillDebug(void) for (i = 0; i < serverlistcount; i++) { // We don't really care about the server node for this, let's just fill in the info so that we have a visual... - serverlist[i].info.numberofplayer = min(i, 8); - serverlist[i].info.maxplayer = 8; + serverlist[i].info.maxplayer = M_RandomRange(8, 16); + UINT8 val = i % 16; + serverlist[i].info.numberofplayer = min(val, serverlist[i].info.maxplayer); - serverlist[i].info.avgpwrlv = P_RandomRange(PR_UNDEFINED, 500, 1500); - serverlist[i].info.time = P_RandomRange(PR_UNDEFINED, 1, 8); // ping + serverlist[i].info.avgpwrlv = M_RandomRange(500, 1500); + serverlist[i].info.time = M_RandomRange(1, 8); // ping strcpy(serverlist[i].info.servername, va("Serv %d", i+1)); strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle"); - P_RandomRange(PR_UNDEFINED, 0, 5); // change results... - serverlist[i].info.kartvars = P_RandomRange(PR_UNDEFINED, 0, 3) & SV_SPEEDMASK; + serverlist[i].info.kartvars = M_RandomRange(0, 3) & SV_SPEEDMASK; - serverlist[i].info.modifiedgame = P_RandomRange(PR_UNDEFINED, 0, 1); + serverlist[i].info.modifiedgame = M_RandomRange(0, 1); CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame); } From b009ab3c23efa19499d5d9db0dc1799350e19da0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Aug 2023 23:40:58 +0100 Subject: [PATCH 33/46] "Recommended" server sorting option Sorts by reverse player count, with 0-player servers sitting empty at the far end. Breaks ties with ping instead of strcmp. --- src/cvars.cpp | 15 +++++----- src/menus/play-online-server-browser.c | 40 +++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 6f7e3e812..416ed70f9 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -540,13 +540,14 @@ consvar_t cv_server_contact = Server("server_contact", "").onchange_noinit(Updat consvar_t cv_servername = Server("servername", "Ring Racers server").onchange_noinit(Update_parameters); void M_SortServerList(void); -consvar_t cv_serversort = Server("serversort", "Ping").dont_save().onchange(M_SortServerList).values({ - {0,"Ping"}, - {1,"AVG. Power Level"}, - {2,"Most Players"}, - {3,"Least Players"}, - {4,"Max Player Slots"}, - {5,"Gametype"}, +consvar_t cv_serversort = Server("serversort", "Recommended").dont_save().onchange(M_SortServerList).values({ + {-1, "Recommended"}, + { 0, "Ping"}, + { 1, "AVG. Power Level"}, + { 2, "Most Players"}, + { 3, "Least Players"}, + { 4, "Max Player Slots"}, + { 5, "Gametype"}, }); // show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping) diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index e6db83227..687f8e51c 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -338,13 +338,51 @@ static int ServerListEntryComparator_gametypename(const void *entry1, const void int c; if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) )) return c; - return strcmp(sa->info.servername, sb->info.servername); \ + return strcmp(sa->info.servername, sb->info.servername); +} + +static int ServerListEntryComparator_recommended(const void *entry1, const void *entry2) +{ + const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; + + INT32 saseedval = sa->info.numberofplayer; + INT32 sbseedval = sb->info.numberofplayer; + + // Tyron wrote the following on 25072022: + // "sort should be two parts + // top part of the list is "all non-empty servers sorted by reverse playercount", with servers above a certain reported ping marked as bad connection or whatever + // bottom part of the list is all empty servers sorted by ping" + // toast is implementing on 27082023, over a year later, because + // "fixing server join flow" is saner to do near the end + + { + const UINT8 SERVER_EMPTY = 1; + + // The intent with this nudge is to show you + // good games you could get a memorable Duel in, + // with the possibility to really katamari into + // something more substantial. + // By comparison, empty games are not nearly as + // fun to get going, so let's lower their SEO. + if (!saseedval) + saseedval = MAXPLAYERS + SERVER_EMPTY; + if (!sbseedval) + sbseedval = MAXPLAYERS + SERVER_EMPTY; + } + + if (saseedval != sbseedval) + return saseedval - sbseedval; + + return sa->info.time - sb->info.time; } void M_SortServerList(void) { switch(cv_serversort.value) { + case -1: + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_recommended); + break; case 0: // Ping. qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time); break; From acca45d611d5c1daa872931b4e8a67e84019fe03 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 00:02:54 +0100 Subject: [PATCH 34/46] SL_InsertServer: If we have cause to reject a server, remove it from the serverlistultimatecount immediately rather than waiting for it to time out --- src/d_clisrv.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index bebaae8f3..bb051a2ef 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1580,7 +1580,7 @@ static UINT32 SL_SearchServer(INT32 node) return UINT32_MAX; } -static void SL_InsertServer(serverinfo_pak* info, SINT8 node) +static boolean SL_InsertServer(serverinfo_pak* info, SINT8 node) { UINT32 i; @@ -1592,25 +1592,25 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) { // not found add it if (serverlistcount >= MAXSERVERLIST) - return; // list full + return false; // list full if (info->_255 != 255) - return;/* old packet format */ + return false;/* old packet format */ if (info->packetversion != PACKETVERSION) - return;/* old new packet format */ + return false;/* old new packet format */ if (info->version != VERSION) - return; // Not same version. + return false; // Not same version. if (info->subversion != SUBVERSION) - return; // Close, but no cigar. + return false; // Close, but no cigar. if (strcmp(info->application, SRB2APPLICATION)) - return;/* that's a different mod */ + return false;/* that's a different mod */ if (info->modifiedgame != (mpmenu.room == 1)) - return;/* CORE vs MODDED! */ + return false;/* CORE vs MODDED! */ i = serverlistcount++; } @@ -1620,6 +1620,8 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) // resort server list M_SortServerList(); + + return true; } void CL_QueryServerList (msg_server_t *server_list) @@ -4646,7 +4648,9 @@ static void HandleServerInfo(SINT8 node) memcpy(servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME); CopyCaretColors(netbuffer->u.serverinfo.servername, servername, MAXSERVERNAME); - SL_InsertServer(&netbuffer->u.serverinfo, node); + // If we have cause to reject it, it's not worth observing. + if (SL_InsertServer(&netbuffer->u.serverinfo, node) == false) + serverlistultimatecount--; } static void PT_WillResendGamestate(void) From 4483f9fc0ea6911156d553014045308c77731b5f Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 14:17:19 +0100 Subject: [PATCH 35/46] M_ServerBrowserInputs; Clean up significantly - No longer reimplement base functionality in confusing overlapping situations, only add on top of it where necessary - Fix the case where no servers were found to not allow scrolling past the bottom --- src/menus/play-online-server-browser.c | 71 +++++++++++--------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 687f8e51c..68e496982 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -435,13 +435,15 @@ boolean M_ServerBrowserInputs(INT32 ch) if (maxscroll < 0) maxscroll = 0; + const INT16 serverbrowserOn = (currentMenu->numitems - 1); + (void) ch; if (!itemOn && menucmd[pid].dpad_ud < 0) { - M_PrevOpt(); // go to itemOn 2 if (serverlistcount) { + // Return the MS listing to the bottom. INT32 prevscroll = mpmenu.scrolln; mpmenu.servernum = serverlistcount-1; @@ -450,15 +452,14 @@ boolean M_ServerBrowserInputs(INT32 ch) } else { - itemOn = 1; // Sike! If there are no servers, go to refresh instead. + M_PrevOpt(); // Double apply } - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - - return true; // overwrite behaviour. } - else if (itemOn == 2) // server browser itself... + else if (itemOn == (serverbrowserOn - 1) && menucmd[pid].dpad_ud > 0 && !serverlistcount) + { + M_NextOpt(); // Double apply + } + else if (itemOn == serverbrowserOn) // server browser itself... { #ifndef SERVERLISTDEBUG if (M_MenuConfirmPressed(pid)) @@ -473,59 +474,49 @@ boolean M_ServerBrowserInputs(INT32 ch) } #endif - // we have to manually do that here. - if (M_MenuBackPressed(pid)) + if (menucmd[pid].dpad_ud > 0) // down { - M_GoBack(0); - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_ud > 0) // down - { - if ((UINT32)(mpmenu.servernum+1) >= serverlistcount) - { - INT32 prevscroll = mpmenu.scrolln; - - mpmenu.servernum = 0; - mpmenu.scrolln = 0; - mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln); - - M_NextOpt(); // Go back to the top of the real menu. - } - else + if ((UINT32)(mpmenu.servernum+1) < serverlistcount) { + // Listing scroll down mpmenu.servernum++; if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2) { mpmenu.scrolln++; mpmenu.slide += SERVERSPACE; } - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + + return true; + } + + // Return the MS listing to the top. + INT32 prevscroll = mpmenu.scrolln; + + mpmenu.servernum = 0; + mpmenu.scrolln = 0; + mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln); } else if (menucmd[pid].dpad_ud < 0) { - if (!mpmenu.servernum) - { - M_PrevOpt(); - } - else + if (mpmenu.servernum) { + // Listing scroll up if (mpmenu.servernum <= (INT16)maxscroll && mpmenu.scrolln) { mpmenu.scrolln--; mpmenu.slide -= SERVERSPACE; } - mpmenu.servernum--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + + return true; + } } - return true; // Overwrite behaviour. } return false; // use normal behaviour. } From 9d689c5e0c327c4cc9d1ed3a5820e9f762b74958 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 16:32:42 +0100 Subject: [PATCH 36/46] M_DrawServerCountAndHorizontalBar: Improve general overlaid text --- src/k_menudraw.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 78b96ee87..bc0233f1d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3419,11 +3419,11 @@ static void M_DrawServerCountAndHorizontalBar(void) switch (M_GetWaitingMode()) { case M_WAITING_VERSION: - text = "Checking for updates"; + text = "Checking for updates..."; break; case M_WAITING_SERVERS: - text = "Loading server list"; + text = "Loading server list..."; break; default: @@ -3438,17 +3438,10 @@ static void M_DrawServerCountAndHorizontalBar(void) else { throbindex = UINT8_MAX; // No throbber! - if (serverlistcount > 0) - { - text = va("%d server%s found", - serverlistcount, - serverlistcount == 1 ? "" : "s" - ); - } - else - { - text = "No servers found"; - } + text = va("%d server%s found", + serverlistcount, + serverlistcount == 1 ? "" : "s" + ); } } @@ -3480,15 +3473,15 @@ static void M_DrawServerCountAndHorizontalBar(void) INT32 mservflags = 0; if (CV_IsSetToDefault(&cv_masterserver)) - mservflags = highlightflags|V_30TRANS; + mservflags = highlightflags; else mservflags = warningflags; y = BASEVIDHEIGHT - 24; - V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10, 0, 31, 5); - V_DrawCenteredString(BASEVIDWIDTH/2, y, - mservflags, va("MS: %s", cv_masterserver.string)); + V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10+1, 0, 31, 5); + V_DrawCenteredThinString(BASEVIDWIDTH/2, y, + mservflags, va("List from \"%s\"", cv_masterserver.string)); } void M_DrawMPServerBrowser(void) From 47055bcda7bf8da326969299fec8c99cd250c778 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 20:13:17 +0100 Subject: [PATCH 37/46] M_StartMessage: ACTUALLY use automatic newline generation --- src/menus/transient/message-box.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index f69b3ebfe..72bc50731 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -31,19 +31,20 @@ static inline size_t M_StringHeight(const char *string) void M_StartMessage(const char *header, const char *string, void (*routine)(INT32), menumessagetype_t itemtype, const char *confirmstr, const char *defaultstr) { const UINT8 pid = 0; - static char *message = NULL; - Z_Free(message); DEBFILE(string); - message = V_ScaledWordWrap( - BASEVIDWIDTH << FRACBITS, + char *message = V_ScaledWordWrap( + (BASEVIDWIDTH - 8) << FRACBITS, FRACUNIT, FRACUNIT, FRACUNIT, 0, HU_FONT, string ); - strncpy(menumessage.message, string, MAXMENUMESSAGE); + strncpy(menumessage.message, message, MAXMENUMESSAGE); + + Z_Free(message); + menumessage.header = header; menumessage.flags = itemtype; menumessage.routine = routine; @@ -84,7 +85,7 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 // oogh my god this was replaced in 2023 menumessage.x = (8 * MAXSTRINGLENGTH) - 1; - menumessage.y = M_StringHeight(message); + menumessage.y = M_StringHeight(menumessage.message); M_SetMenuDelay(pid); // Set menu delay to avoid setting off any of the handlers. } From afb7328aa4e3b48c3663e8c680dd70f706408c14 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 20:16:12 +0100 Subject: [PATCH 38/46] SV_SpawnServer: Register netgame-advertised servers with the MS This is done in a different location in the previous entry in the series, but the author of this commit couldn't find it. Since we're using the 2.2 codebase, it's been put where they have it instead. --- src/d_clisrv.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index bb051a2ef..3fc62490e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4226,6 +4226,11 @@ boolean SV_SpawnServer(void) I_NetOpenSocket(); } + if (cv_advertise.value) + { + RegisterServer(); + } + ourIP = 0; STUN_bind(GotOurIP); } From 5b958f5a0ce7b7455ed83636624b2c70e7335256 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 20:41:01 +0100 Subject: [PATCH 39/46] http-mserv.c: Update documentation URL to newest version --- src/http-mserv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http-mserv.c b/src/http-mserv.c index 368f1cdda..ddb47ad03 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -11,7 +11,7 @@ /* Documentation available here. - + */ #ifdef HAVE_CURL From 782d98fb0972c4be49fd4ac7955cc42e05179283 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 22:07:56 +0100 Subject: [PATCH 40/46] Master Server Listing rules prompts Ported from KartKrew/Kart-Public!286 Unlike the previous entry in the series, this actually defers outbound rules checks for either selecting "Host Game" on the menu, or starting a server via the console/command line parameters. Only attempting to host a non-advertised game via the menu will have sent out an unnecessary rules request to the MS! --- src/http-mserv.c | 29 +++++++++++ src/k_menu.h | 1 + src/menus/play-online-host.c | 32 +++++++++++- src/mserv.c | 98 +++++++++++++++++++++++++++++++++++- src/mserv.h | 5 ++ 5 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/http-mserv.c b/src/http-mserv.c index ddb47ad03..46ab9cd42 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -516,6 +516,35 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size) return ok; } +const char * +HMS_fetch_rules (char *buffer, size_t buffer_size) +{ + struct HMS_buffer *hms; + + hms = HMS_connect("rules"); + + if (! hms) + return NULL; + + if (HMS_do(hms)) + { + char *p = strstr(hms->buffer, "\n\n"); + + if (p) + { + p[1] = '\0'; + + strlcpy(buffer, hms->buffer, buffer_size); + } + else + buffer = NULL; + } + + HMS_end(hms); + + return buffer; +} + static char * Strip_trailing_slashes (char *api) { diff --git a/src/k_menu.h b/src/k_menu.h index cd740df55..6360896ae 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -841,6 +841,7 @@ extern struct mpmenu_s { } mpmenu; void M_PleaseWait(void); +void M_PopupMasterServerRules(void); // Time Attack void M_PrepareTimeAttack(INT32 choice); diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index a7061a32a..0b34f36b1 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -3,6 +3,8 @@ #include "../k_menu.h" #include "../s_sound.h" +#include "../z_zone.h" +#include "../mserv.h" // MULTIPLAYER HOST SCREEN -- see mhost_e menuitem_t PLAY_MP_Host[] = @@ -43,13 +45,41 @@ menu_t PLAY_MP_HostDef = { NULL }; +void M_PopupMasterServerRules(void) +{ +#ifdef MASTERSERVER + if (cv_advertise.value && (serverrunning || currentMenu == &PLAY_MP_HostDef)) + { + char *rules = GetMasterServerRules(); + + if (rules != NULL) + { + M_StartMessage("Server List Rules", rules, NULL, MM_NOTHING, NULL, NULL); + Z_Free(rules); + } + } +#endif +} + void M_MPHostInit(INT32 choice) { - (void)choice; mpmenu.modewinextend[0][0] = 1; M_SetupNextMenu(&PLAY_MP_HostDef, true); itemOn = mhost_go; + + Get_rules(); + // There's one downside to doing it this way: + // if you turn advertise on via the console, + // then access this menu for the first time, + // no rules will pop up because they haven't + // arrived yet. + M_PopupMasterServerRules(); + // HOWEVER, this menu popup isn't for people + // who know how to use the Developer Console. + // People who CAN do that should already know + // what kind of service they're connecting to. + // (it'll still appear in the logs later, too!) } void M_HandleHostMenuGametype(INT32 choice) diff --git a/src/mserv.c b/src/mserv.c index e1f2b9820..2f851d3e8 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -17,6 +17,7 @@ #include "doomstat.h" #include "doomdef.h" +#include "console.h" // con_startup #include "command.h" #include "i_threads.h" #include "mserv.h" @@ -40,6 +41,8 @@ static boolean MSUpdateAgain; static time_t MSLastPing; +static char *MSRules; + #ifdef HAVE_THREADS static I_mutex MSMutex; static I_cond MSCond; @@ -157,6 +160,43 @@ static void Command_Listserv_f(void) } } +static boolean firstmsrules = false; + +static void +Get_masterserver_rules (boolean checkfirst) +{ + char rules[256]; + + if (checkfirst) + { + boolean MSRulesExist; + + Lock_state(); + MSRulesExist = (MSRules != NULL); + Unlock_state(); + + if (MSRulesExist) + return; + } + + if (HMS_fetch_rules(rules, sizeof rules)) + { + Lock_state(); + Z_Free(MSRules); + MSRules = Z_StrDup(rules); + + if (MSRegistered == true) + { + CONS_Printf("\n"); + CONS_Alert(CONS_NOTICE, "%s\n", rules); + } + + firstmsrules = true; + + Unlock_state(); + } +} + static void Finish_registration (void) { @@ -175,6 +215,17 @@ Finish_registration (void) } Unlock_state(); + char *rules = GetMasterServerRules(); + if (rules == NULL) + { + Get_masterserver_rules(true); + } + else + { + CONS_Printf("\n"); + CONS_Alert(CONS_NOTICE, "%s\n", rules); + } + if (registered) CONS_Printf("Master server registration successful.\n"); } @@ -257,6 +308,15 @@ Finish_unlist (void) } } +static void +Finish_masterserver_change (char *api) +{ + HMS_set_api(api); + + if (!con_startup) + Get_masterserver_rules(false); +} + #ifdef HAVE_THREADS static int * Server_id (void) @@ -350,7 +410,14 @@ Change_masterserver_thread (char *api) } Unlock_state(); - HMS_set_api(api); + Finish_masterserver_change(api); +} + +static void +Get_masterserver_rules_thread (void) +{ + // THIS FUNC has its own lock check in it + Get_masterserver_rules(true); } #endif/*HAVE_THREADS*/ @@ -397,6 +464,17 @@ void UnregisterServer(void) #endif/*MASTERSERVER*/ } +char *GetMasterServerRules(void) +{ + char *rules; + + Lock_state(); + rules = MSRules ? Z_StrDup(MSRules) : NULL; + Unlock_state(); + + return rules; +} + static boolean Online (void) { @@ -447,7 +525,21 @@ Set_api (const char *api) strdup(api) ); #else - HMS_set_api(strdup(api)); + Finish_masterserver_change(strdup(api)); +#endif +} + +void +Get_rules (void) +{ +#ifdef HAVE_THREADS + I_spawn_thread( + "get-masterserver-rules", + (I_thread_fn)Get_masterserver_rules_thread, + NULL + ); +#else + Get_masterserver_rules(true); #endif } @@ -521,6 +613,8 @@ void Advertise_OnChange(void) #ifdef HAVE_DISCORDRPC DRPC_UpdatePresence(); #endif + + M_PopupMasterServerRules(); } #ifdef DEVELOP diff --git a/src/mserv.h b/src/mserv.h index 7417585d6..dd9a548fd 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -77,6 +77,8 @@ extern I_mutex ms_ServerList_mutex; void RegisterServer(void); void UnregisterServer(void); +void Get_rules(void); + void MasterClient_Ticker(void); msg_server_t *GetShortServersList(int id); @@ -84,6 +86,8 @@ msg_server_t *GetShortServersList(int id); char *GetMODVersion(int id); #endif +char *GetMasterServerRules(void); + void AddMServCommands(void); /* HTTP */ @@ -94,6 +98,7 @@ int HMS_update (void); void HMS_list_servers (void); msg_server_t * HMS_fetch_servers (msg_server_t *list, int id); int HMS_compare_mod_version (char *buffer, size_t size_of_buffer); +const char * HMS_fetch_rules (char *buffer, size_t size_of_buffer); #ifdef __cplusplus } // extern "C" From 339d368d7dd82feeb182ccda26f504efb577762b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 23:33:39 +0100 Subject: [PATCH 41/46] HMS_fetch_rules: Return NULL if the curl connection failed, instead of producing garbled rules --- src/http-mserv.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/http-mserv.c b/src/http-mserv.c index 46ab9cd42..7794a8f7c 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -526,7 +526,9 @@ HMS_fetch_rules (char *buffer, size_t buffer_size) if (! hms) return NULL; - if (HMS_do(hms)) + boolean ok = HMS_do(hms); + + if (ok) { char *p = strstr(hms->buffer, "\n\n"); @@ -542,6 +544,9 @@ HMS_fetch_rules (char *buffer, size_t buffer_size) HMS_end(hms); + if (!ok) + return NULL; + return buffer; } From c14ff69d9a6ce316a5bc35575656ece4570a023d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 23:38:00 +0100 Subject: [PATCH 42/46] Server Browser change alert (KartKrew/Kart-Public!277) - Let players know if their cv_masterserver was changed, and give them the opportunity to change it back - A limited number of nag attempts, so if people are using a legitimate custom Server Browser they won't be annoyed for the rest of the game's lifespan - Show the Server Browser at the bottom of all online menus - KNOWN BUG: currently overlaps with the Join IP - we're redoing this menu before launch because it's buggy so this is just another bug on that pile --- src/cvars.cpp | 1 + src/k_menudraw.c | 33 +++++++++++--------- src/menus/play-online-1.c | 66 +++++++++++++++++++++++++++++++++++++-- src/mserv.h | 1 + 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 416ed70f9..e1dbbc8af 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -502,6 +502,7 @@ consvar_t cv_kicktime = Server("kicktime", "20").values(CV_Unsigned); void MasterServer_OnChange(void); consvar_t cv_masterserver = Server("masterserver", "https://ms.kartkrew.org/ms/api").onchange(MasterServer_OnChange); +consvar_t cv_masterserver_nagattempts = Server("masterserver_nagattempts", "5").values(CV_Unsigned); void MasterServer_Debug_OnChange (void); consvar_t cv_masterserver_debug = Server("masterserver_debug", "Off").on_off().onchange(MasterServer_Debug_OnChange); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index bc0233f1d..8fef30a7b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3086,6 +3086,23 @@ void M_DrawTimeAttack(void) // NOTE: This is pretty rigid and only intended for use with the multiplayer options menu which has *3* choices. +static void M_DrawMasterServerReminder(void) +{ + // Did you change the Server Browser address? Have a little reminder. + + INT32 mservflags = 0; + if (CV_IsSetToDefault(&cv_masterserver)) + mservflags = highlightflags; + else + mservflags = warningflags; + + INT32 y = BASEVIDHEIGHT - 24; + + V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10+1, 0, 31, 5); + V_DrawCenteredThinString(BASEVIDWIDTH/2, y, + mservflags, va("List via \"%s\"", cv_masterserver.string)); +} + static void M_MPOptDrawer(menu_t *m, INT16 extend[3][3]) { // This is a copypaste of the generic gamemode menu code with a few changes. @@ -3149,6 +3166,7 @@ void M_DrawMPOptSelect(void) M_DrawEggaChannel(); M_DrawMenuTooltips(); M_MPOptDrawer(&PLAY_MP_OptSelectDef, mpmenu.modewinextend); + M_DrawMasterServerReminder(); } // Multiplayer mode option select: HOST GAME! @@ -3468,20 +3486,6 @@ static void M_DrawServerCountAndHorizontalBar(void) va("%c", throbber[throbindex]) ); } - - // Did you change the Server Browser address? Have a little reminder. - - INT32 mservflags = 0; - if (CV_IsSetToDefault(&cv_masterserver)) - mservflags = highlightflags; - else - mservflags = warningflags; - - y = BASEVIDHEIGHT - 24; - - V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10+1, 0, 31, 5); - V_DrawCenteredThinString(BASEVIDWIDTH/2, y, - mservflags, va("List from \"%s\"", cv_masterserver.string)); } void M_DrawMPServerBrowser(void) @@ -3586,6 +3590,7 @@ void M_DrawMPServerBrowser(void) // And finally, the overlay bar! M_DrawServerCountAndHorizontalBar(); + M_DrawMasterServerReminder(); } // OPTIONS MENU diff --git a/src/menus/play-online-1.c b/src/menus/play-online-1.c index 7f9a9c294..e58490187 100644 --- a/src/menus/play-online-1.c +++ b/src/menus/play-online-1.c @@ -4,6 +4,7 @@ #include "../k_menu.h" #include "../m_cond.h" #include "../s_sound.h" +#include "../mserv.h" // cv_masterserver #if defined (TESTERS) #define IT_STRING_CALL_NOTESTERS IT_DISABLED @@ -11,13 +12,74 @@ #define IT_STRING_CALL_NOTESTERS (IT_STRING | IT_CALL) #endif // TESTERS +static boolean firstDismissedNagThisBoot = true; + +static void M_HandleMasterServerResetChoice(INT32 ch) +{ + if (ch == MA_YES) + { + CV_Set(&cv_masterserver, cv_masterserver.defaultvalue); + CV_Set(&cv_masterserver_nagattempts, cv_masterserver_nagattempts.defaultvalue); + S_StartSound(NULL, sfx_s221); + } + else + { + if (firstDismissedNagThisBoot) + { + if (cv_masterserver_nagattempts.value > 0) + { + CV_SetValue(&cv_masterserver_nagattempts, cv_masterserver_nagattempts.value - 1); + } + firstDismissedNagThisBoot = false; + } + } +} + +static void M_PreMPHostInitChoice(INT32 ch) +{ + M_HandleMasterServerResetChoice(ch); + M_MPHostInit(0); +} + +static void M_PreMPHostInit(INT32 choice) +{ + (void)choice; + + if (!CV_IsSetToDefault(&cv_masterserver) && cv_masterserver_nagattempts.value > 0) + { + M_StartMessage("Server Browser Alert", M_GetText("Hey! You've changed the game's\naddress for the Server Browser.\n\nYou won't be able to host games on\nthe official Server Browser.\n\nUnless you're from the future, this\nprobably isn't what you want.\n"), &M_PreMPHostInitChoice, MM_YESNO, "Fix and continue", "I changed the URL intentionally"); + return; + } + + M_MPHostInit(0); +} + +static void M_PreMPRoomSelectInitChoice(INT32 ch) +{ + M_HandleMasterServerResetChoice(ch); + M_MPRoomSelectInit(0); +} + +static void M_PreMPRoomSelectInit(INT32 choice) +{ + (void)choice; + + if (!CV_IsSetToDefault(&cv_masterserver) && cv_masterserver_nagattempts.value > 0) + { + M_StartMessage("Server Browser Alert", M_GetText("Hey! You've changed the game's\naddress for the Server Browser.\n\nYou won't be able to see games from\nthe official Server Browser.\n\nUnless you're from the future, this\nprobably isn't what you want.\n"), &M_PreMPRoomSelectInitChoice, MM_YESNO, "Fix and continue", "I changed the URL intentionally"); + return; + } + + M_MPRoomSelectInit(0); +} + menuitem_t PLAY_MP_OptSelect[] = { {IT_STRING_CALL_NOTESTERS, "Host Game", "Start your own online game!", - NULL, {.routine = M_MPHostInit}, 0, 0}, + NULL, {.routine = M_PreMPHostInit}, 0, 0}, {IT_STRING_CALL_NOTESTERS, "Server Browser", "Search for game servers to play in.", - NULL, {.routine = M_MPRoomSelectInit}, 0, 0}, + NULL, {.routine = M_PreMPRoomSelectInit}, 0, 0}, {IT_STRING | IT_CALL, "Join by IP", "Join an online game by its IP address.", NULL, {.routine = M_MPJoinIPInit}, 0, 0}, diff --git a/src/mserv.h b/src/mserv.h index dd9a548fd..6fe9a77c4 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -57,6 +57,7 @@ struct msg_ban_t // ================================ GLOBALS =============================== extern consvar_t cv_masterserver, cv_servername; +extern consvar_t cv_masterserver_nagattempts; extern consvar_t cv_server_contact; extern consvar_t cv_masterserver_update_rate; extern consvar_t cv_masterserver_timeout; From cdf5eaaa16d425cd2543563db6f0889c3ae4bc97 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Aug 2023 23:44:37 +0100 Subject: [PATCH 43/46] Server Browser Room Select changes Instead of putting you on the room select but forbidding changing the room... - You haven't unlocked add-ons (formerly forced to CORE) - Skips the Room Select and takes you directly to the CORE mode Server Browser - Game has add-ons loaded (formerly forced to MODDED) - Forbid accessing the Server Browser with a helpful message. - Reduces tech support burden. - Parity with the previous entry in the series. --- src/k_menu.h | 1 - src/k_menudraw.c | 8 ++++---- src/menus/play-online-room-select.c | 21 ++++++++++++++++++--- src/menus/play-online-server-browser.c | 1 + 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 6360896ae..ea894fd3e 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -829,7 +829,6 @@ extern struct mpmenu_s { // See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess! UINT8 room; - boolean roomforced; tic_t ticker; UINT8 servernum; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8fef30a7b..7a1f883b7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3418,11 +3418,11 @@ void M_DrawMPRoomSelect(void) // Draw buttons: - if (!mpmenu.roomforced || mpmenu.room == 0) - V_DrawFixedPatch(160< Date: Mon, 28 Aug 2023 23:55:54 +0100 Subject: [PATCH 44/46] Room Select: Add a tooltip --- src/k_menudraw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 7a1f883b7..acbfd41df 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3422,6 +3422,9 @@ void M_DrawMPRoomSelect(void) V_DrawFixedPatch(160< Date: Tue, 29 Aug 2023 00:35:59 +0100 Subject: [PATCH 45/46] Server sort: Tiebreaker for all methods is Ping, except for Ping only, which is still strcmp Reduces the toxicity of "userlist-bumping", the phenomena where you pick a name that begins as early in ASCII as possible for clout. Now that only continues to apply to the sort that is already just a little toxic. --- src/menus/play-online-server-browser.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 2c6a3def4..29c2e4379 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -313,7 +313,7 @@ static int ServerListEntryComparator_##key(const void *entry1, const void *entry const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ if (sa->info.key != sb->info.key) \ return sa->info.key - sb->info.key; \ - return strcmp(sa->info.servername, sb->info.servername); \ + return sa->info.time - sb->info.time; \ } // This does descending instead of ascending. @@ -323,15 +323,22 @@ static int ServerListEntryComparator_##key##_reverse(const void *entry1, const v const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ if (sb->info.key != sa->info.key) \ return sb->info.key - sa->info.key; \ - return strcmp(sb->info.servername, sa->info.servername); \ + return sa->info.time - sb->info.time; \ } -SERVER_LIST_ENTRY_COMPARATOR(time) +//SERVER_LIST_ENTRY_COMPARATOR(time) -- done seperately due to the usual tiebreaker being time SERVER_LIST_ENTRY_COMPARATOR(numberofplayer) SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer) SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer) SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv) +static int ServerListEntryComparator_time(const void *entry1, const void *entry2) +{ + const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; + if (sa->info.time != sb->info.time) + return sa->info.time - sb->info.time; + return strcmp(sa->info.servername, sb->info.servername); +} static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2) { @@ -339,7 +346,7 @@ static int ServerListEntryComparator_gametypename(const void *entry1, const void int c; if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) )) return c; - return strcmp(sa->info.servername, sb->info.servername); + return sa->info.time - sb->info.time; } static int ServerListEntryComparator_recommended(const void *entry1, const void *entry2) From 244db81f04c767abd9fb964784fa190a5334a340 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 31 Aug 2023 20:50:15 +0100 Subject: [PATCH 46/46] Visual adjustments to the Server Browser and Room Select Based on VC discussions - When you've unlocked add-ons, show "Core Servers" or "Modded Servers" in lieu of "Server Browser - Adjust the vertical height of the Room Select graphics to not be visually overlapped --- src/k_menudraw.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index acbfd41df..6e6e5864a 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3418,9 +3418,9 @@ void M_DrawMPRoomSelect(void) // Draw buttons: - V_DrawFixedPatch(160<