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/command.c b/src/command.c index fd5044e8b..8f0574f03 100644 --- a/src/command.c +++ b/src/command.c @@ -777,15 +777,17 @@ 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'; - HU_DoCEcho(cechotext); + HU_DoTitlecardCEcho(NULL, cechotext, true); } /** Sets drawing flags for the CECHO command. diff --git a/src/cvars.cpp b/src/cvars.cpp index 3e142a6dd..47dbcbf0e 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); @@ -540,13 +541,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/d_clisrv.c b/src/d_clisrv.c index 41279577f..3fc62490e 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) @@ -1574,32 +1580,37 @@ 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; + resendserverlistnode[node] = false; + // search if not already on it i = SL_SearchServer(node); if (i == UINT32_MAX) { // 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 false;/* CORE vs MODDED! */ i = serverlistcount++; } @@ -1609,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) @@ -1617,6 +1630,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 +1644,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; } } } @@ -3875,22 +3914,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)); @@ -4195,6 +4226,11 @@ boolean SV_SpawnServer(void) I_NetOpenSocket(); } + if (cv_advertise.value) + { + RegisterServer(); + } + ourIP = 0; STUN_bind(GotOurIP); } @@ -4617,7 +4653,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) 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/d_netcmd.c b/src/d_netcmd.c index 003af15c4..83171f68a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1193,14 +1193,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); + } } } } @@ -3452,6 +3451,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) { @@ -3523,55 +3538,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) @@ -3599,20 +3597,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/deh_tables.c b/src/deh_tables.c index 8dd77706d..5043d5641 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5809,6 +5809,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/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); 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 5dcd41f39..1f16f07c3 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(); @@ -1276,6 +1276,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; } @@ -2827,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; @@ -2850,11 +2851,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) @@ -3273,7 +3316,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 b12124aef..d4f933df7 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -240,6 +240,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/http-mserv.c b/src/http-mserv.c index d47122cbf..7794a8f7c 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -11,7 +11,7 @@ /* Documentation available here. - + */ #ifdef HAVE_CURL @@ -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) @@ -511,6 +516,40 @@ 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; + + boolean ok = HMS_do(hms); + + if (ok) + { + char *p = strstr(hms->buffer, "\n\n"); + + if (p) + { + p[1] = '\0'; + + strlcpy(buffer, hms->buffer, buffer_size); + } + else + buffer = NULL; + } + + HMS_end(hms); + + if (!ok) + return NULL; + + return buffer; +} + static char * Strip_trailing_slashes (char *api) { diff --git a/src/hu_stuff.c b/src/hu_stuff.c index fd9fa3c97..0b0434f70 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; @@ -283,6 +289,12 @@ void HU_Init(void) PR ("GTFN"); REG; + PR ("4GTOL"); + REG; + + PR ("4GTFN"); + REG; + DIG (1); DIM (0, 10); @@ -960,13 +972,6 @@ void HU_Ticker(void) if (cechotimer) cechotimer--; - - if (tcechotimer) - { - tcechotimer++; - if (tcechotimer > tcechoduration) - tcechotimer = 0; - } if (gamestate != GS_LEVEL) { @@ -1829,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; @@ -1842,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) @@ -1856,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 ofs; + INT32 timer = (INT32)(elapsed - timeroffset); if (timer <= 0) return; // we don't care. @@ -1874,10 +1915,10 @@ static void HU_DrawTitlecardCEcho(void) *line = '\0'; - w = V_TitleCardStringWidth(echoptr); - V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4); + ofs = V_CenteredTitleCardStringOffset(echoptr, p4); + V_DrawTitleCardString(x - ofs, y, echoptr, 0, false, timer, TICRATE*4, p4); - y += 32; + y += p4 ? 18 : 32; // offset the timer for the next line. timeroffset += strlen(echoptr); @@ -2037,9 +2078,23 @@ drawontop: if (cechotimer) HU_DrawCEcho(); - - if (tcechotimer) - HU_DrawTitlecardCEcho(); + + 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 = 1; i < NUM_TCECHO_STATES; ++i) + { + HU_DrawTitlecardCEcho(i); + } + } } //====================================================================== @@ -2576,17 +2631,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 92ecdbc41..db1cfcc57 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), @@ -163,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/info.c b/src/info.c index 21c1d0869..2adb1a1c1 100644 --- a/src/info.c +++ b/src/info.c @@ -30312,6 +30312,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 dfff7d216..84c8b045c 100644 --- a/src/info.h +++ b/src/info.h @@ -6999,6 +6999,8 @@ typedef enum mobj_type MT_POWERUP_AURA, + MT_SCRIPT_THING, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES 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/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..e4ab54aab 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? } } } @@ -391,6 +391,11 @@ void K_UpdateGrandPrixBots(void) 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/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/k_kart.c b/src/k_kart.c index c2ffd737a..0f8720a74 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3921,7 +3921,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) @@ -3939,6 +3939,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)) { @@ -4102,6 +4110,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; @@ -4252,7 +4263,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 } } @@ -6155,8 +6166,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) { @@ -8256,7 +8266,7 @@ 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); @@ -10447,16 +10457,18 @@ 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; + P_ResetPitchRoll(player->mo); + return; } else if (player->fastfall != 0) @@ -10584,7 +10596,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/k_kart.h b/src/k_kart.h index e0bcabf06..e33580f81 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 #define MAXRINGVOLUME 255 #define MINRINGVOLUME 100 diff --git a/src/k_menu.h b/src/k_menu.h index 875da6535..ea894fd3e 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) @@ -831,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; @@ -842,6 +839,9 @@ extern struct mpmenu_s { } mpmenu; +void M_PleaseWait(void); +void M_PopupMasterServerRules(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..6e6e5864a 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! @@ -3400,14 +3418,79 @@ void M_DrawMPRoomSelect(void) // Draw buttons: - if (!mpmenu.roomforced || mpmenu.room == 0) - V_DrawFixedPatch(160<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! + text = va("%d server%s found", + serverlistcount, + serverlistcount == 1 ? "" : "s" + ); + } + } + + 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]) + ); + } +} + void M_DrawMPServerBrowser(void) { patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE); @@ -3503,11 +3586,19 @@ void M_DrawMPServerBrowser(void) V_DrawFill(0, 53, 320, 1, 31); V_DrawFill(0, 55, 320, 1, 31); - V_DrawCenteredGamemodeString(160, 2, 0, 0, "Server Browser"); + const char *headertext; + if (M_SecretUnlocked(SECRET_ADDONS, true)) + headertext = va("%s Servers", mpmenu.room ? "Modded" : "Core"); + else + headertext = "Server Browser"; + V_DrawCenteredGamemodeString(160, 2, 0, 0, headertext); // normal menu options M_DrawGenericMenu(); + // And finally, the overlay bar! + M_DrawServerCountAndHorizontalBar(); + M_DrawMasterServerReminder(); } // OPTIONS MENU 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/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; } 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/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}, 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/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/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-room-select.c b/src/menus/play-online-room-select.c index 666b7c987..d0d6b9a58 100644 --- a/src/menus/play-online-room-select.c +++ b/src/menus/play-online-room-select.c @@ -42,8 +42,7 @@ void M_MPRoomSelect(INT32 choice) M_ServersMenu(0); M_SetMenuDelay(pid); } - else if (mpmenu.roomforced == false - && menucmd[pid].dpad_lr != 0) + else if (menucmd[pid].dpad_lr != 0) { mpmenu.room ^= 1; S_StartSound(NULL, sfx_s3k5b); @@ -59,12 +58,28 @@ void M_MPRoomSelectTick(void) void M_MPRoomSelectInit(INT32 choice) { (void)choice; + + if (modifiedgame) + { + M_StartMessage("Server Browser & Add-Ons", M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\n\n\"Dr. Robotnik's Ring Racers\" will\nautomatically add everything\nyou need when you join.\n"), NULL, MM_NOTHING, NULL, NULL); + return; + } + + // The following behaviour is affected by modifiedgame despite the above restriction. + // It's a sanity check were that to be removed, wheither by us or by a modified client. + // "wheither"? That typo rules, I'm keeping that ~toast 280823 + mpmenu.room = (modifiedgame == true) ? 1 : 0; - mpmenu.roomforced = ((modifiedgame == true) || (!M_SecretUnlocked(SECRET_ADDONS, true))); mpmenu.ticker = 0; mpmenu.servernum = 0; mpmenu.scrolln = 0; mpmenu.slide = 0; + if ((modifiedgame == true) || (M_SecretUnlocked(SECRET_ADDONS, true) == false)) + { + M_ServersMenu(0); + return; + } + M_SetupNextMenu(&PLAY_MP_RoomSelectDef, false); } diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 730a34956..29c2e4379 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,13 +222,21 @@ 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; + PLAY_MP_ServerBrowserDef.prevMenu = currentMenu; 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 +266,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,29 +276,31 @@ 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++) { // 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); } + + M_SortServerList(); } #endif // SERVERLISTDEBUG @@ -339,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. @@ -349,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) { @@ -365,13 +346,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 sa->info.time - sb->info.time; +} + +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; @@ -397,89 +416,115 @@ 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; + + const INT16 serverbrowserOn = (currentMenu->numitems - 1); + (void) ch; if (!itemOn && menucmd[pid].dpad_ud < 0) { - M_PrevOpt(); // go to itemOn 2 if (serverlistcount) { - UINT8 prevscroll = mpmenu.scrolln; + // Return the MS listing to the bottom. + 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. + M_PrevOpt(); // Double apply } - - return true; // overwrite behaviour. } - else if (itemOn == 2) // server browser itself... + else if (itemOn == (serverbrowserOn - 1) && menucmd[pid].dpad_ud > 0 && !serverlistcount) { - // we have to manually do that here. - if (M_MenuBackPressed(pid)) + M_NextOpt(); // Double apply + } + else if (itemOn == serverbrowserOn) // server browser itself... + { +#ifndef SERVERLISTDEBUG + if (M_MenuConfirmPressed(pid)) { - M_GoBack(0); M_SetMenuDelay(pid); + + COM_BufAddText(va("connect node %d\n", serverlist[mpmenu.servernum].node)); + + M_PleaseWait(); + + return true; } +#endif - else if (menucmd[pid].dpad_ud > 0) // down + if (menucmd[pid].dpad_ud > 0) // down { - if (mpmenu.servernum >= serverlistcount-1) - { - UINT8 prevscroll = mpmenu.scrolln; - mpmenu.servernum = 0; - mpmenu.scrolln = 0; - mpmenu.slide = SERVERSPACE * (prevscroll - 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) + if (mpmenu.servernum) { - M_PrevOpt(); - } - else - { - if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln) + // 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. } 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. } 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..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; @@ -77,6 +78,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 +87,8 @@ msg_server_t *GetShortServersList(int id); char *GetMODVersion(int id); #endif +char *GetMasterServerRules(void); + void AddMServCommands(void); /* HTTP */ @@ -94,6 +99,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" diff --git a/src/objects/dash-rings.c b/src/objects/dash-rings.c index 527157cb1..d40e341bb 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; + P_ResetPitchRoll(player->mo); player->flashing = 0; player->fastfall = 0; K_TumbleInterrupt(player); 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..315e6e124 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; @@ -2052,30 +2056,34 @@ 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) + { + HU_DoTitlecardCEcho(player, "FALL OUT!", false); + P_DoPlayerExit(player, PF_NOCONTEST); + } + else if (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) @@ -2578,7 +2586,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_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 bdd82d6cc..51d038c28 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) // @@ -6716,6 +6725,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: { @@ -7191,7 +7243,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)) @@ -11778,61 +11830,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; @@ -11842,9 +11857,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; @@ -11853,22 +11865,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. @@ -11967,8 +11963,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_setup.c b/src/p_setup.c index 1c2f8e06a..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 } @@ -8245,8 +8241,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; @@ -8254,6 +8259,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } else if (skipstats == 1) { + // MapWarp if (ranspecialwipe != 2) S_StartSound(NULL, sfx_s3k73); levelfadecol = 0; @@ -8261,11 +8267,13 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } else if (encoremode) { + // Encore levelfadecol = 0; wipetype = wipe_encore_towhite; } else { + // Default levelfadecol = 31; } @@ -8547,7 +8555,7 @@ void P_PostLoadLevel(void) K_InitGrandPrixBots(); grandprixinfo.initalize = false; } - else if (grandprixinfo.wonround == true) + else { K_UpdateGrandPrixBots(); grandprixinfo.wonround = false; diff --git a/src/p_spec.c b/src/p_spec.c index 85003e43e..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); @@ -2053,6 +2061,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); } } diff --git a/src/p_user.c b/src/p_user.c index a5176e580..389689c69 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -484,12 +484,10 @@ 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) { - player->mo->pitch = 0; - player->mo->roll = 0; + P_ResetPitchRoll(player->mo); } } @@ -1296,7 +1294,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)) { @@ -1326,7 +1324,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; @@ -1407,6 +1407,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 @@ -3504,7 +3509,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; 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); 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); 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..d472d4ab3 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1852,50 +1852,155 @@ 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) +template +static INT32 Internal_TitleCardStringOffset(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; 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); } - c = *ch; - c = toupper(c); - c -= LT_FONTSTART; + return true; + }; - // check if character exists, if not, it's a space. - if (c < 0 || c >= LT_FONTSIZE || !fontv[GTOL_FONT].font[(INT32)c]) + 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: + // - Check patch width directly for monospace or + // punctuation that isn't necessarily thin. + // - Apply to all centered string drawing. + if constexpr (Centered) { - xoffs += 10; - 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; + } + } + else + { + scan([](int c) { return c; }); } - - pp = fontv[GTFN_FONT].font[(INT32)c]; - - xoffs += pp->width-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. // 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 +2021,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 +2038,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 +2049,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 +2109,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..2f4989a24 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -347,10 +347,13 @@ 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); + +// 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); 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. 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