From 990a3500bca3b6923d4907dee579539018f7a31e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Nov 2022 15:50:41 +0000 Subject: [PATCH 01/95] SetSkin refactor for skin setting Port from 2.2. Prevents some infinite loops caused by skin 0 being marked unusable. --- src/r_skins.c | 97 ++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/src/r_skins.c b/src/r_skins.c index 9779fdcfc..a09b22547 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -253,6 +253,52 @@ INT32 R_SkinAvailable(const char *name) return -1; } +// Auxillary function that actually sets the skin +static void SetSkin(player_t *player, INT32 skinnum) +{ + skin_t *skin = &skins[skinnum]; + + player->skin = skinnum; + + player->followitem = skin->followitem; + + player->kartspeed = skin->kartspeed; + player->kartweight = skin->kartweight; + player->charflags = skin->flags; + +#if 0 + if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback)) + { + for (i = 0; i <= r_splitscreen; i++) + { + if (playernum == g_localplayers[i]) + { + CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor); + } + } + + player->skincolor = skin->prefcolor; + K_KartResetPlayerColor(player); + } +#endif + + if (player->followmobj) + { + P_RemoveMobj(player->followmobj); + P_SetTarget(&player->followmobj, NULL); + } + + if (player->mo) + { + player->mo->skin = skin; + P_SetScale(player->mo, player->mo->scale); + P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames + } + + // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! + demo_extradata[playernum] |= DXD_SKIN; +} + // network code calls this when a 'skin change' is received void SetPlayerSkin(INT32 playernum, const char *skinname) { @@ -261,7 +307,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) if ((i != -1) && R_SkinUsable(playernum, i)) { - SetPlayerSkinByNum(playernum, i); + SetSkin(player, i); return; } @@ -270,7 +316,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) else if(server || IsPlayerAdmin(consoleplayer)) CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname); - SetPlayerSkinByNum(playernum, 0); + SetSkin(player, 0); } // Same as SetPlayerSkin, but uses the skin #. @@ -278,53 +324,10 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; - skin_t *skin = &skins[skinnum]; - //UINT16 newcolor = 0; - //UINT8 i; if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists! { - player->skin = skinnum; - - player->charflags = (UINT32)skin->flags; - - player->followitem = skin->followitem; - - player->kartspeed = skin->kartspeed; - player->kartweight = skin->kartweight; - -#if 0 - if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback)) - { - for (i = 0; i <= r_splitscreen; i++) - { - if (playernum == g_localplayers[i]) - { - CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor); - } - } - - player->skincolor = newcolor = skin->prefcolor; - K_KartResetPlayerColor(player); - } -#endif - - if (player->followmobj) - { - P_RemoveMobj(player->followmobj); - P_SetTarget(&player->followmobj, NULL); - } - - if (player->mo) - { - player->mo->skin = skin; - P_SetScale(player->mo, player->mo->scale); - P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames - } - - // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! - demo_extradata[playernum] |= DXD_SKIN; - + SetSkin(player, skinnum); return; } @@ -333,7 +336,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) else if(server || IsPlayerAdmin(consoleplayer)) CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum); - SetPlayerSkinByNum(playernum, 0); // not found, put in the default skin + SetSkin(player, 0); // not found put the eggman skin } // Set mo skin but not player_t skin, for ironman From 8179b39773063428f1d600af3ca53c9e14460b2d Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Nov 2022 16:21:45 +0000 Subject: [PATCH 02/95] ForceAllSkins cleanup --- src/d_netcmd.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 086c4cbfa..11c919a05 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1407,26 +1407,25 @@ boolean CanChangeSkinWhilePlaying(INT32 playernum) static void ForceAllSkins(INT32 forcedskin) { - INT32 i, j; + INT32 i; for (i = 0; i < MAXPLAYERS; ++i) { if (!playeringame[i]) continue; SetPlayerSkinByNum(i, forcedskin); + } - // If it's me (or my brother (or my sister (or my trusty pet dog))), set appropriate skin value in cv_skin - if (dedicated) // But don't do this for dedicated servers, of course. + if (dedicated) + return; + + // If it's me (or my brother (or my sister (or my trusty pet dog))), set appropriate skin value in cv_skin + for (i = 0; i <= splitscreen; i++) + { + if (!playeringame[g_localplayers[i]]) continue; - for (j = 0; j <= splitscreen; j++) - { - if (i == g_localplayers[j]) - { - CV_StealthSet(&cv_skin[j], skins[forcedskin].name); - break; - } - } + CV_StealthSet(&cv_skin[i], skins[forcedskin].name); } } From 6d0637d39d464f7f52e79ca05cb32f5ada78d02e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 27 Nov 2022 22:53:29 +0000 Subject: [PATCH 03/95] Unlockable skins (in a way friendly to #define MAXSKINS 255) Mammoth commit, sorry. I only realised halfway through writing it that SECRET_SKIN was only partially merged. Ports from 2.2: - Merge SECRET_SKIN (STJr/SRB2!1474) - Default skin is now handled by checking all skins for unlock status, and I_Erroring if none are available - Don't show skin names on game startup, to keep our secrets hidden - Unlockables now have string variables zallocated. - For skin names rather than numbers. - Correctly clean up memory when freeing unlockables and emblems. Bespoke code: - For temporary testing. `unlocks.pk3` - Using this for rapid testing gameboot SOC instead of patch.pk3 because of the intent to turn that into scripts.pk3 - Don't not save gamedata in DEVELOP builds, even if you've used cheats! - `player->availabilities` is now an array of UINT8 - (MAXSKINS + 7)/8 entries, or 32 bytes. - Included with XD_ADDPLAYER instead of XD_NAMEANDCOLOR. - Simplifies a lot of logic with respect to demos, skin changes mid-game, etc. - Seriously, there's a lot of random places in the code that just iterate over MAXSPLITSCREENPLAYERS and g_localplayers to update availabilities in real time in a way that's not particularly netsafe... - Lines up with the plan for handling unlocks when returning to menus. - Was included with XD_ADDBOT, but that actually overruns the netxcmd buffer at first mapload with 7 bots. We might need to consider expanding the size of the netxcmd buffer... - In demos, can be interpreted as both relative to the original replay and the current skin list depending on boolean context provided to R_SkinUsable. - Used for SF_IRONMAN (and will crash if all other skins are inaccessible). - Grand Prix bot randomisation uses the host's unlocks. - Don't show locked characters on the fancy new character select. - DXD_JOINDATA for demos - Replaces the dual-purpose behaviour of DXD_PLAYSTATE - Contains availabilities - Handles bot material in a different way - Forceskin restrictions - Won't run in demos, because it's assumed recorded DXD_SKIN will handle all the conversions the original match had - Won't run if K_CanChangeRules says no - Correctly set `mapvisited` on level visit, even in [fake gasp] MULTIPLAYER/NETGAMES!! :face_holding_back_tears: - Added updating unlockables and extra emblems on `mapvisited` update. - Currently fails to produce the cecho, but that'll be stripped out entirely in a future commit so I'm not bothered. --- src/command.c | 6 +- src/d_clisrv.c | 53 ++++++++++++---- src/d_clisrv.h | 1 + src/d_main.c | 12 ++-- src/d_netcmd.c | 67 +++++--------------- src/d_player.h | 2 +- src/deh_soc.c | 38 +++++++++++- src/deh_soc.h | 2 + src/dehacked.c | 7 ++- src/doomdef.h | 3 +- src/g_demo.c | 115 +++++++++++++++++++++++++--------- src/g_demo.h | 10 +-- src/g_game.c | 14 +++-- src/k_grandprix.c | 8 +-- src/k_menufunc.c | 3 + src/lua_baselib.c | 4 +- src/lua_mobjlib.c | 2 +- src/lua_playerlib.c | 4 -- src/m_cond.c | 35 +++++++++-- src/m_cond.h | 5 ++ src/p_saveg.c | 14 ++++- src/p_setup.c | 14 +++-- src/r_skins.c | 146 ++++++++++++++++++++++++++++---------------- src/r_skins.h | 10 +-- 24 files changed, 377 insertions(+), 198 deletions(-) diff --git a/src/command.c b/src/command.c index eb1204108..30198c3e2 100644 --- a/src/command.c +++ b/src/command.c @@ -1481,7 +1481,7 @@ boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval) { v = R_SkinAvailable(valstr); - if (!R_SkinUsable(-1, v)) + if (!R_SkinUsable(-1, v, false)) v = -1; goto finish; @@ -1984,7 +1984,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth) if (var == &cv_forceskin) { INT32 skin = R_SkinAvailable(value); - if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin))) + if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin, false))) { CONS_Printf("Please provide a valid skin name (\"None\" disables).\n"); return; @@ -2109,7 +2109,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) else if (newvalue >= numskins) newvalue = -1; } while ((oldvalue != newvalue) - && !(R_SkinUsable(-1, newvalue))); + && !(R_SkinUsable(-1, newvalue, false))); } else newvalue = var->value + increment; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 6ce85db27..2ecea2764 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -824,6 +824,8 @@ static boolean CL_SendJoin(void) for (; i < MAXSPLITSCREENPLAYERS; i++) strncpy(netbuffer->u.clientcfg.names[i], va("Player %c", 'A' + i), MAXPLAYERNAME); + memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false), MAXAVAILABILITY*sizeof(UINT8)); + return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak)); } @@ -3565,8 +3567,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) return; } - node = (UINT8)READUINT8(*p); - newplayernum = (UINT8)READUINT8(*p); + node = READUINT8(*p); + newplayernum = READUINT8(*p); CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum); @@ -3587,8 +3589,13 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); - console = (UINT8)READUINT8(*p); - splitscreenplayer = (UINT8)READUINT8(*p); + console = READUINT8(*p); + splitscreenplayer = READUINT8(*p); + + for (i = 0; i < MAXAVAILABILITY; i++) + { + newplayer->availabilities[i] = READUINT8(*p); + } // the server is creating my player if (node == mynode) @@ -3690,8 +3697,9 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) static void Got_AddBot(UINT8 **p, INT32 playernum) { INT16 newplayernum; - UINT8 skinnum = 0; + UINT8 i, skinnum = 0; UINT8 difficulty = DIFFICULTBOT; + UINT8 availabilitiesbuffer[MAXAVAILABILITY]; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -3704,9 +3712,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) return; } - newplayernum = (UINT8)READUINT8(*p); - skinnum = (UINT8)READUINT8(*p); - difficulty = (UINT8)READUINT8(*p); + newplayernum = READUINT8(*p); + skinnum = READUINT8(*p); + difficulty = READUINT8(*p); CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); @@ -3720,6 +3728,15 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) playernode[newplayernum] = servernode; + // todo find a way to have all auto unlocked for dedicated + if (playeringame[0]) + { + for (i = 0; i < MAXAVAILABILITY; i++) + { + players[newplayernum].availabilities[i] = players[0].availabilities[i]; + } + } + players[newplayernum].splitscreenindex = 0; players[newplayernum].bot = true; players[newplayernum].botvars.difficulty = difficulty; @@ -3737,10 +3754,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) LUA_HookInt(newplayernum, HOOK(PlayerJoin)); } -static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *name2, const char *name3, const char *name4) +static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const char *name, const char *name2, const char *name3, const char *name4) { - INT32 n, newplayernum; - UINT8 buf[4 + MAXPLAYERNAME]; + INT32 n, newplayernum, i; + UINT8 buf[4 + MAXPLAYERNAME + MAXAVAILABILITY]; UINT8 *buf_p = buf; boolean newplayer = false; @@ -3823,6 +3840,11 @@ static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *na WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num + for (i = 0; i < MAXAVAILABILITY; i++) + { + WRITEUINT8(buf_p, availabilities[i]); + } + playerpernode[node]++; SendNetXCmd(XD_ADDPLAYER, buf, buf_p - buf); @@ -3886,9 +3908,10 @@ boolean SV_SpawnServer(void) // strictly speaking, i'm not convinced the following is necessary // but I'm not confident enough to remove it entirely in case it breaks something { + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false); SINT8 node = 0; for (; node < MAXNETNODES; node++) - result |= SV_AddWaitingPlayers(node, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring); + result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring); } return result; #endif @@ -3971,6 +3994,7 @@ static void HandleConnect(SINT8 node) { char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; INT32 i; + UINT8 availabilitiesbuffer[MAXAVAILABILITY]; // Sal: Dedicated mode is INCREDIBLY hacked together. // If a server filled out, then it'd overwrite the host and turn everyone into weird husks..... @@ -4077,6 +4101,8 @@ static void HandleConnect(SINT8 node) } } + memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer)); + // client authorised to join nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]); if (!nodeingame[node]) @@ -4111,7 +4137,8 @@ static void HandleConnect(SINT8 node) SV_SendSaveGame(node, false); // send a complete game state DEBFILE("send savegame\n"); } - SV_AddWaitingPlayers(node, names[0], names[1], names[2], names[3]); + + SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], names[1], names[2], names[3]); joindelay += cv_joindelay.value * TICRATE; player_joining = true; } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index eb11ca04a..01b9836e4 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -246,6 +246,7 @@ typedef struct UINT8 localplayers; // number of splitscreen players UINT8 mode; char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME]; + UINT8 availabilities[MAXAVAILABILITY]; } ATTRPACK clientconfig_pak; #define SV_SPEEDMASK 0x03 // used to send kartspeed diff --git a/src/d_main.c b/src/d_main.c index bd63cd874..af438e1b4 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -950,11 +950,6 @@ void D_StartTitle(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities(); - } - splitscreen = 0; SplitScreen_OnChange(); @@ -1148,6 +1143,10 @@ static void IdentifyVersion(void) #ifdef USE_PATCH_FILE D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME)); #endif +#define UNLOCKTESTING +#if defined(DEVELOP) && defined(UNLOCKTESTING) + D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3")); +#endif //// #undef TEXTURESNAME #undef MAPSNAME @@ -1449,6 +1448,9 @@ void D_SRB2Main(void) #ifdef USE_PATCH_FILE mainwads++; // patch.pk3 #endif +#ifdef UNLOCKTESTING + mainwads++; // unlocks.pk3 +#endif #endif //ifndef DEVELOP diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 11c919a05..f6ef19d81 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1408,6 +1408,10 @@ boolean CanChangeSkinWhilePlaying(INT32 playernum) static void ForceAllSkins(INT32 forcedskin) { INT32 i; + + if (demo.playback) + return; // DXD_SKIN should handle all changes for us + for (i = 0; i < MAXPLAYERS; ++i) { if (!playeringame[i]) @@ -1476,6 +1480,8 @@ static void SendNameAndColor(UINT8 n) char buf[MAXPLAYERNAME+12]; char *p; + UINT16 i = 0; + if (splitscreen < n) return; // can happen if skin4/color4/name4 changed @@ -1493,9 +1499,10 @@ static void SendNameAndColor(UINT8 n) CV_StealthSetValue(&cv_playercolor[n], skins[player->skin].prefcolor); else if (skincolors[atoi(cv_playercolor[n].defaultvalue)].accessible) CV_StealthSet(&cv_playercolor[n], cv_playercolor[n].defaultvalue); - else { - UINT16 i = 0; - while (ifollowercolor) return; - player->availabilities = R_GetSkinAvailabilities(); - // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1530,7 +1535,7 @@ static void SendNameAndColor(UINT8 n) K_KartResetPlayerColor(player); - if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin)) + if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin, false)) { SetPlayerSkin(playernum, cv_skin[n].string); CV_StealthSet(&cv_skin[n], skins[foundskin].name); @@ -1575,7 +1580,7 @@ static void SendNameAndColor(UINT8 n) // check if player has the skin loaded (cv_skin may have // the name of a skin that was available in the previous game) cv_skin[n].value = R_SkinAvailable(cv_skin[n].string); - if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value)) + if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value, false)) { CV_StealthSet(&cv_skin[n], DEFAULTSKIN); cv_skin[n].value = 0; @@ -1590,7 +1595,6 @@ static void SendNameAndColor(UINT8 n) // Finally write out the complete packet and send it off. WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME); - WRITEUINT32(p, (UINT32)player->availabilities); WRITEUINT16(p, (UINT16)cv_playercolor[n].value); WRITEUINT8(p, (UINT8)cv_skin[n].value); WRITEINT16(p, (INT16)cv_follower[n].value); @@ -1632,11 +1636,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } READSTRINGN(*cp, name, MAXPLAYERNAME); - p->availabilities = READUINT32(*cp); color = READUINT16(*cp); skin = READUINT8(*cp); follower = READINT16(*cp); - //CONS_Printf("Recieved follower id %d\n", follower); followercolor = READUINT16(*cp); // set name @@ -1657,56 +1659,20 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) { boolean kick = false; - // team colors - if (G_GametypeHasTeams()) - { - if (p->ctfteam == 1 && p->skincolor != skincolor_redteam) - kick = true; - else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) - kick = true; - } - // don't allow inaccessible colors if (skincolors[p->skincolor].accessible == false) kick = true; - // availabilities - for (i = 0; i < MAXSKINS; i++) - { - UINT32 playerhasunlocked = (p->availabilities & (1 << i)); - boolean islocked = false; - UINT8 j; - - for (j = 0; j < MAXUNLOCKABLES; j++) - { - if (unlockables[j].type != SECRET_SKIN) - continue; - - if (unlockables[j].variable == i) - { - islocked = true; - break; - } - } - - if (islocked == false && playerhasunlocked == true) - { - // hacked client that enabled every bit - kick = true; - break; - } - } - if (kick) { - CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor); + CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s, color: %d)\n"), player_names[playernum], p->skincolor); SendKick(playernum, KICK_MSG_CON_FAIL); return; } } // set skin - if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player + if (cv_forceskin.value >= 0 && K_CanChangeRules(true)) // Server wants everyone to use the same player { const INT32 forcedskin = cv_forceskin.value; SetPlayerSkinByNum(playernum, forcedskin); @@ -5729,11 +5695,6 @@ void Command_ExitGame_f(void) for (i = 0; i < MAXPLAYERS; i++) CL_ClearPlayer(i); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities(); - } - splitscreen = 0; SplitScreen_OnChange(); diff --git a/src/d_player.h b/src/d_player.h index e9ebe7668..36169e850 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -385,7 +385,7 @@ typedef struct player_s UINT16 skincolor; INT32 skin; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; UINT8 fakeskin; // ironman UINT8 lastfakeskin; diff --git a/src/deh_soc.c b/src/deh_soc.c index f23c71970..74a0920e4 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -131,6 +131,36 @@ static float searchfvalue(const char *s) #endif // These are for clearing all of various things +void clear_emblems(void) +{ + INT32 i; + + for (i = 0; i < MAXEMBLEMS; ++i) + { + Z_Free(emblemlocations[i].level); + emblemlocations[i].level = NULL; + + Z_Free(emblemlocations[i].stringVar); + emblemlocations[i].stringVar = NULL; + } + + memset(&emblemlocations, 0, sizeof(emblemlocations)); + numemblems = 0; +} + +void clear_unlockables(void) +{ + INT32 i; + + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + Z_Free(unlockables[i].stringVar); + unlockables[i].stringVar = NULL; + } + + memset(&unlockables, 0, sizeof(unlockables)); +} + void clear_conditionsets(void) { UINT8 i; @@ -2140,7 +2170,12 @@ void reademblemdata(MYFILE *f, INT32 num) else if (fastcmp(word, "COLOR")) emblemlocations[num-1].color = get_number(word2); else if (fastcmp(word, "VAR")) + { + Z_Free(emblemlocations[num-1].stringVar); + emblemlocations[num-1].stringVar = Z_StrDup(word2); + emblemlocations[num-1].var = get_number(word2); + } else deh_warning("Emblem %d: unknown word '%s'", num, word); } @@ -2344,7 +2379,8 @@ void readunlockable(MYFILE *f, INT32 num) } else if (fastcmp(word, "VAR")) { - // TODO: different field for level name string + Z_Free(unlockables[num].stringVar); + unlockables[num].stringVar = Z_StrDup(word2); unlockables[num].variable = (INT16)i; } else diff --git a/src/deh_soc.h b/src/deh_soc.h index 0d9beccab..92985ad2f 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -77,6 +77,8 @@ void readlight(MYFILE *f, INT32 num); void readskincolor(MYFILE *f, INT32 num); void readthing(MYFILE *f, INT32 num); void readfreeslots(MYFILE *f); +void clear_emblems(void); +void clear_unlockables(void); void clear_levels(void); void clear_conditionsets(void); diff --git a/src/dehacked.c b/src/dehacked.c index 99ee7b0e8..1e400a1fa 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -584,12 +584,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } if (clearall || fastcmp(word2, "UNLOCKABLES")) - memset(&unlockables, 0, sizeof(unlockables)); + { + clear_unlockables(); + } if (clearall || fastcmp(word2, "EMBLEMS")) { - memset(&emblemlocations, 0, sizeof(emblemlocations)); - numemblems = 0; + clear_emblems(); } if (clearall || fastcmp(word2, "EXTRAEMBLEMS")) diff --git a/src/doomdef.h b/src/doomdef.h index 8271a4ae2..4f03d0085 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -201,7 +201,8 @@ extern char logfilename[1024]; #define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing #define MAXSKINS UINT8_MAX -#define SKINNAMESIZE 16 // Moved from r_skins.h as including that particular header causes issues later down the line. +#define SKINNAMESIZE 16 +#define MAXAVAILABILITY ((MAXSKINS + 7)/8) #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 diff --git a/src/g_demo.c b/src/g_demo.c index 8566a4c22..c6ee247e2 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -276,10 +276,8 @@ void G_ReadDemoExtraData(void) { extradata = READUINT8(demo_p); - if (extradata & DXD_PLAYSTATE) + if (extradata & DXD_JOINDATA) { - i = READUINT8(demo_p); - if (!playeringame[p]) { CL_ClearPlayer(p); @@ -288,14 +286,22 @@ void G_ReadDemoExtraData(void) players[p].spectator = true; } - if ((players[p].bot = !!(i & DXD_PST_ISBOT))) + for (i = 0; i < MAXAVAILABILITY; i++) + { + players[p].availabilities[i] = READUINT8(demo_p); + } + + players[p].bot = !!(READUINT8(demo_p)); + if (players[p].bot) { players[p].botvars.difficulty = READUINT8(demo_p); players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic players[p].botvars.rival = (boolean)READUINT8(demo_p); - - i &= ~DXD_PST_ISBOT; } + } + if (extradata & DXD_PLAYSTATE) + { + i = READUINT8(demo_p); switch (i) { case DXD_PST_PLAYING: @@ -334,14 +340,6 @@ void G_ReadDemoExtraData(void) K_CheckBumpers(); P_CheckRacers(); } - if (extradata & DXD_RESPAWN) - { - if (players[p].mo) - { - // Is this how this should work..? - K_DoIngameRespawn(&players[p]); - } - } if (extradata & DXD_SKIN) { UINT8 skinid; @@ -397,6 +395,14 @@ void G_ReadDemoExtraData(void) } } } + if (extradata & DXD_RESPAWN) + { + if (players[p].mo) + { + // Is this how this should work..? + K_DoIngameRespawn(&players[p]); + } + } if (extradata & DXD_WEAPONPREF) { WeaponPref_Parse(&demo_p, p); @@ -454,6 +460,21 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, i); WRITEUINT8(demo_p, demo_extradata[i]); + if (demo_extradata[i] & DXD_JOINDATA) + { + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(demo_p, players[i].availabilities[i]); + } + + WRITEUINT8(demo_p, (UINT8)players[i].bot); + if (players[i].bot) + { + WRITEUINT8(demo_p, players[i].botvars.difficulty); + WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic + WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival); + } + } if (demo_extradata[i] & DXD_PLAYSTATE) { UINT8 pst = DXD_PST_PLAYING; @@ -472,19 +493,7 @@ void G_WriteDemoExtraData(void) pst = DXD_PST_SPECTATING; } - if (players[i].bot) - { - pst |= DXD_PST_ISBOT; - } - WRITEUINT8(demo_p, pst); - - if (pst & DXD_PST_ISBOT) - { - WRITEUINT8(demo_p, players[i].botvars.difficulty); - WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic - WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival); - } } //if (demo_extradata[i] & DXD_RESPAWN) has no extra data if (demo_extradata[i] & DXD_SKIN) @@ -1218,8 +1227,14 @@ void G_GhostTicker(void) if (ziptic == 0) // Only support player 0 info for now { ziptic = READUINT8(g->p); + if (ziptic & DXD_JOINDATA) + { + g->p += MAXAVAILABILITY; + if (READUINT8(g->p) != 0) + I_Error("Ghost is not a record attack ghost (bot JOINDATA)"); + } if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) - I_Error("Ghost is not a record attack ghost PLAYSTATE"); //@TODO lmao don't blow up like this + I_Error("Ghost is not a record attack ghost (has PLAYSTATE)"); if (ziptic & DXD_SKIN) g->p++; // We _could_ read this info, but it shouldn't change anything in record attack... if (ziptic & DXD_COLOR) @@ -2248,6 +2263,7 @@ static void G_SaveDemoSkins(UINT8 **pp) { char skin[16]; UINT8 i; + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true); WRITEUINT8((*pp), numskins); for (i = 0; i < numskins; i++) @@ -2262,12 +2278,17 @@ static void G_SaveDemoSkins(UINT8 **pp) WRITEUINT8((*pp), skins[i].kartweight); WRITEUINT32((*pp), skins[i].flags); } + + for (i = 0; i < MAXAVAILABILITY; i++) + { + WRITEUINT8((*pp), availabilitiesbuffer[i]); + } } static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean getclosest) { char skin[17]; - UINT8 i; + UINT8 i, byte, shif; democharlist_t *skinlist = NULL; (*worknumskins) = READUINT8((*pp)); @@ -2310,6 +2331,24 @@ static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean } } + for (byte = 0; byte < MAXAVAILABILITY; byte++) + { + UINT8 availabilitiesbuffer = READUINT8((*pp)); + + for (shif = 0; shif < 8; shif++) + { + i = (byte*8) + shif; + + if (i >= (*worknumskins)) + break; + + if (availabilitiesbuffer & (1 << shif)) + { + skinlist[i].unlockrequired = true; + } + } + } + return skinlist; } @@ -2326,6 +2365,8 @@ static void G_SkipDemoSkins(UINT8 **pp) (*pp)++; // kartweight (*pp) += 4; // flags } + + (*pp) += MAXAVAILABILITY; } void G_BeginRecording(void) @@ -2443,6 +2484,11 @@ void G_BeginRecording(void) M_Memcpy(demo_p,name,16); demo_p += 16; + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(demo_p, player->availabilities[j]); + } + // Skin (now index into demo.skinlist) WRITEUINT8(demo_p, player->skin); WRITEUINT8(demo_p, player->lastfakeskin); @@ -2928,6 +2974,7 @@ void G_DoPlayDemo(char *defdemoname) UINT8 i, p, numslots = 0; lumpnum_t l; char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; char msg[1024]; @@ -3293,6 +3340,11 @@ void G_DoPlayDemo(char *defdemoname) M_Memcpy(player_names[p],demo_p,16); demo_p += 16; + for (i = 0; i < MAXAVAILABILITY; i++) + { + availabilities[p][i] = READUINT8(demo_p); + } + // Skin i = READUINT8(demo_p); @@ -3376,6 +3428,8 @@ void G_DoPlayDemo(char *defdemoname) for (i = 0; i < numslots; i++) { + UINT8 j; + p = slots[i]; if (players[p].mo) { @@ -3392,6 +3446,11 @@ void G_DoPlayDemo(char *defdemoname) players[p].kartweight = ghostext[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight; players[p].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; players[p].lastfakeskin = lastfakeskin[p]; + + for (j = 0; j < MAXAVAILABILITY; j++) + { + players[p].availabilities[j] = availabilities[p][j]; + } } demo.deferstart = true; diff --git a/src/g_demo.h b/src/g_demo.h index a00756a74..ee38cd5d7 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -33,6 +33,7 @@ typedef struct democharlist_s { UINT8 kartspeed; UINT8 kartweight; UINT32 flags; + boolean unlockrequired; } democharlist_t; // Publicly-accessible demo vars @@ -119,20 +120,19 @@ typedef enum extern UINT8 demo_extradata[MAXPLAYERS]; extern UINT8 demo_writerng; -#define DXD_PLAYSTATE 0x01 // state changed between playing, spectating, or not in-game -#define DXD_RESPAWN 0x02 // "respawn" command in console +#define DXD_JOINDATA 0x01 // join-specific data +#define DXD_PLAYSTATE 0x02 // state changed between playing, spectating, or not in-game #define DXD_SKIN 0x04 // skin changed #define DXD_NAME 0x08 // name changed #define DXD_COLOR 0x10 // color changed #define DXD_FOLLOWER 0x20 // follower was changed -#define DXD_WEAPONPREF 0x40 // netsynced playsim settings were changed +#define DXD_RESPAWN 0x40 // "respawn" command in console +#define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed #define DXD_PST_PLAYING 0x01 #define DXD_PST_SPECTATING 0x02 #define DXD_PST_LEFT 0x03 -#define DXD_PST_ISBOT 0x80 // extra flag - // Record/playback tics void G_ReadDemoExtraData(void); void G_WriteDemoExtraData(void); diff --git a/src/g_game.c b/src/g_game.c index fe73d8ae6..c2ab6d216 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2238,7 +2238,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 latestlap; UINT16 skincolor; INT32 skin; - UINT32 availabilities; + UINT8 availabilities[MAXAVAILABILITY]; UINT8 fakeskin; UINT8 lastfakeskin; @@ -2307,7 +2307,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) followercolor = players[player].followercolor; followerskin = players[player].followerskin; - availabilities = players[player].availabilities; + memcpy(availabilities, players[player].availabilities, sizeof(availabilities)); followitem = players[player].followitem; @@ -2432,7 +2432,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->charflags = charflags; p->lastfakeskin = lastfakeskin; - p->availabilities = availabilities; + memcpy(players[player].availabilities, availabilities, sizeof(availabilities)); p->followitem = followitem; p->starpostnum = starpostnum; @@ -2918,7 +2918,7 @@ void G_AddPlayer(INT32 playernum) { player_t *p = &players[playernum]; p->playerstate = PST_REBORN; - demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything + demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything } void G_ExitLevel(void) @@ -3690,6 +3690,10 @@ static void G_UpdateVisited(void) if ((earnedEmblems = M_CompletionEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); + + if (M_UpdateUnlockablesAndExtraEmblems()) + S_StartSound(NULL, sfx_ncitem); + G_SaveGameData(); } static boolean CanSaveLevel(INT32 mapnum) @@ -4507,12 +4511,14 @@ void G_SaveGameData(void) return; } +#ifndef DEVELOP if (usedCheats) { free(savebuffer); save_p = savebuffer = NULL; return; } +#endif // Version test diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 6149434c7..221e9d10c 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -231,9 +231,9 @@ void K_InitGrandPrixBots(void) // Rearrange usable bot skins list to prevent gaps for randomised selection for (i = 0; i < usableskins; i++) { - if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true))) continue; - while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true))) { usableskins--; } @@ -557,9 +557,9 @@ void K_RetireBots(void) // Rearrange usable bot skins list to prevent gaps for randomised selection for (i = 0; i < usableskins; i++) { - if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true))) continue; - while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/)) + while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true))) usableskins--; grabskins[i] = grabskins[usableskins]; grabskins[usableskins] = MAXSKINS; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index ab43217ff..696c14593 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2175,6 +2175,9 @@ void M_CharacterSelectInit(void) UINT8 x = skins[i].kartspeed-1; UINT8 y = skins[i].kartweight-1; + if (!R_SkinUsable(g_localplayers[0], i, false)) + continue; + if (setup_chargrid[x][y].numskins >= MAXCLONES) CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1); else diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 06fbb789c..c42a5ffcb 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2351,7 +2351,7 @@ static int lib_rSetPlayerSkin(lua_State *L) return luaL_error(L, "skin %s (argument 2) is not loaded", skinname); } - if (!R_SkinUsable(j, i)) + if (!R_SkinUsable(j, i, false)) return luaL_error(L, "skin %d (argument 2) not usable - check with R_SkinUsable(player_t, skin) first.", i); SetPlayerSkinByNum(j, i); return 0; @@ -2381,7 +2381,7 @@ static int lib_rSkinUsable(lua_State *L) return luaL_error(L, "skin %s (argument 2) is not loaded", skinname); } - lua_pushboolean(L, R_SkinUsable(j, i)); + lua_pushboolean(L, R_SkinUsable(j, i, false)); return 1; } diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index b87ad7234..6d4b6b6e4 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -669,7 +669,7 @@ static int mobj_set(lua_State *L) for (i = 0; i < numskins; i++) if (fastcmp(skins[i].name, skin)) { - if (!mo->player || R_SkinUsable(mo->player-players, i)) + if (!mo->player || R_SkinUsable(mo->player-players, i, false)) mo->skin = &skins[i]; return 0; } diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2a3b9e46a..03d299b01 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -408,8 +408,6 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->skincolor); else if (fastcmp(field,"skin")) lua_pushinteger(L, plr->skin); - else if (fastcmp(field,"availabilities")) - lua_pushinteger(L, plr->availabilities); else if (fastcmp(field,"fakeskin")) lua_pushinteger(L, plr->fakeskin); else if (fastcmp(field,"lastfakeskin")) @@ -577,8 +575,6 @@ static int player_set(lua_State *L) } else if (fastcmp(field,"skin")) return NOSET; - else if (fastcmp(field,"availabilities")) - return NOSET; else if (fastcmp(field,"fakeskin")) return NOSET; else if (fastcmp(field,"lastfakeskin")) diff --git a/src/m_cond.c b/src/m_cond.c index 1568d0845..53ff790a6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -290,11 +290,6 @@ void M_SilentUpdateUnlockablesAndEmblems(void) continue; unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); } - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities(); - } } // Emblem unlocking shit @@ -372,6 +367,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa if (res) ++somethingUnlocked; } + return somethingUnlocked; } @@ -502,6 +498,35 @@ UINT8 M_GotLowEnoughTime(INT32 tictime) return true; } +// Gets the skin number for a SECRET_SKIN unlockable. +INT32 M_UnlockableSkinNum(unlockable_t *unlock) +{ + if (unlock->type != SECRET_SKIN) + { + // This isn't a skin unlockable... + return -1; + } + + if (unlock->stringVar && strcmp(unlock->stringVar, "")) + { + // Get the skin from the string. + INT32 skinnum = R_SkinAvailable(unlock->stringVar); + if (skinnum != -1) + { + return skinnum; + } + } + + if (unlock->variable >= 0 && unlock->variable < numskins) + { + // Use the number directly. + return unlock->variable; + } + + // Invalid skin unlockable. + return -1; +} + // ---------------- // Misc Emblem shit // ---------------- diff --git a/src/m_cond.h b/src/m_cond.h index 323c84347..7633cf39b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -77,6 +77,7 @@ typedef struct UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT16 color; ///< skincolor to use INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) + char *stringVar; ///< String version char hint[110]; ///< Hint for emblem hints menu UINT8 collected; ///< Do you have this emblem? } emblem_t; @@ -100,6 +101,7 @@ typedef struct UINT8 showconditionset; INT16 type; INT16 variable; + char *stringVar; UINT8 nocecho; UINT8 nochecklist; UINT8 unlocked; @@ -177,4 +179,7 @@ const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big); UINT8 M_GotEnoughEmblems(INT32 number); UINT8 M_GotLowEnoughTime(INT32 tictime); +INT32 M_UnlockableSkinNum(unlockable_t *unlock); +INT32 M_EmblemSkinNum(emblem_t *emblem); + #define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved) diff --git a/src/p_saveg.c b/src/p_saveg.c index 73d4a7d1f..100672534 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -150,7 +150,12 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].skincolor); WRITEINT32(save_p, players[i].skin); - WRITEUINT32(save_p, players[i].availabilities); + + for (j = 0; j < MAXAVAILABILITY; j++) + { + WRITEUINT8(save_p, players[i].availabilities[j]); + } + WRITEUINT8(save_p, players[i].fakeskin); WRITEUINT8(save_p, players[i].lastfakeskin); WRITEUINT32(save_p, players[i].score); @@ -472,7 +477,12 @@ static void P_NetUnArchivePlayers(void) players[i].skincolor = READUINT8(save_p); players[i].skin = READINT32(save_p); - players[i].availabilities = READUINT32(save_p); + + for (j = 0; i < MAXAVAILABILITY; j++) + { + players[i].availabilities[j] = READUINT8(save_p); + } + players[i].fakeskin = READUINT8(save_p); players[i].lastfakeskin = READUINT8(save_p); players[i].score = READUINT32(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 92f054eb6..57e90e697 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7559,10 +7559,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) nextmapoverride = 0; skipstats = 0; - if (!(netgame || multiplayer || demo.playback) && !majormods) + if (!demo.playback) + { mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; - else if (!demo.playback) - mapheaderinfo[gamemap-1]->mapvisited |= MV_MP; // you want to record that you've been there this session, but not permanently + + if (M_UpdateUnlockablesAndExtraEmblems()) + S_StartSound(NULL, sfx_ncitem); + G_SaveGameData(); + } G_AddMapToBuffer(gamemap-1); @@ -8036,8 +8040,8 @@ UINT16 P_PartialAddWadFile(const char *wadfilename) // // look for skins // - R_AddSkins(wadnum); // faB: wadfile index in wadfiles[] - R_PatchSkins(wadnum); // toast: PATCH PATCH + R_AddSkins(wadnum, false); // faB: wadfile index in wadfiles[] + R_PatchSkins(wadnum, false); // toast: PATCH PATCH // // edit music defs diff --git a/src/r_skins.c b/src/r_skins.c index a09b22547..e4f3a317c 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -32,6 +32,7 @@ #if 0 #include "k_kart.h" // K_KartResetPlayerColor #endif +#include "k_grandprix.h" // K_CanChangeRules #ifdef HWRENDER #include "hardware/hw_md2.h" #endif @@ -150,36 +151,51 @@ void R_InitSkins(void) for (i = 0; i < numwadfiles; i++) { - R_AddSkins((UINT16)i); - R_PatchSkins((UINT16)i); + R_AddSkins((UINT16)i, true); + R_PatchSkins((UINT16)i, true); R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); } ST_ReloadSkinFaceGraphics(); } -UINT32 R_GetSkinAvailabilities(void) +UINT8 *R_GetSkinAvailabilities(boolean demolock) { - UINT8 i; - UINT32 response = 0; + UINT8 i, shif, byte; + INT32 skinid; + static UINT8 responsebuffer[MAXAVAILABILITY]; + + memset(&responsebuffer, 0, sizeof(responsebuffer)); for (i = 0; i < MAXUNLOCKABLES; i++) { - if (unlockables[i].type == SECRET_SKIN && unlockables[i].unlocked) - { - UINT8 s = min(unlockables[i].variable, MAXSKINS); - response |= (1 << s); - } + if (unlockables[i].type != SECRET_SKIN) + continue; + + if (unlockables[i].unlocked != true && !demolock) + continue; + + skinid = M_UnlockableSkinNum(&unlockables[i]); + + if (skinid < 0 || skinid >= MAXSKINS) + continue; + + shif = (skinid % 8); + byte = (skinid / 8); + + responsebuffer[byte] |= (1 << shif); } - return response; + return responsebuffer; } // returns true if available in circumstances, otherwise nope // warning don't use with an invalid skinnum other than -1 which always returns true -boolean R_SkinUsable(INT32 playernum, INT32 skinnum) +boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) { boolean needsunlocked = false; + boolean useplayerstruct = (Playing() && playernum != -1); UINT8 i; + INT32 skinid; if (skinnum == -1) { @@ -187,55 +203,54 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) return true; } - if (modeattacking) - { - // If you have someone else's run, you should be able to take a look - return true; - } - - if (netgame && (cv_forceskin.value == skinnum)) + if (K_CanChangeRules(true) && (cv_forceskin.value == skinnum)) { // Being forced to play as this character by the server return true; } - if (metalrecording) - { - // Recording a Metal Sonic race - const INT32 metalskin = R_SkinAvailable("metalsonic"); - return (skinnum == metalskin); - } - // Determine if this character is supposed to be unlockable or not - for (i = 0; i < MAXUNLOCKABLES; i++) + if (useplayerstruct && demo.playback) { - if (unlockables[i].type == SECRET_SKIN && unlockables[i].variable == skinnum) + if (!demoskins) + skinnum = demo.skinlist[skinnum].mapping; + needsunlocked = demo.skinlist[skinnum].unlockrequired; + } + else + { + for (i = 0; i < MAXUNLOCKABLES; i++) { + if (unlockables[i].type != SECRET_SKIN) + continue; + + skinid = M_UnlockableSkinNum(&unlockables[i]); + + if (skinid != skinnum) + continue; + // i is now the unlockable index, we can use this later needsunlocked = true; break; } } - if (needsunlocked == true) - { - // You can use this character IF you have it unlocked. - if ((netgame || multiplayer) && playernum != -1) - { - // Use the netgame synchronized unlocks. - return (boolean)(!(players[playernum].availabilities & (1 << skinnum))); - } - else - { - // Use the unlockables table directly - return (boolean)(unlockables[i].unlocked); - } - } - else + if (needsunlocked == false) { // Didn't trip anything, so we can use this character. return true; } + + // Ok, you can use this character IF you have it unlocked. + if (useplayerstruct) + { + // Use the netgame synchronized unlocks. + UINT8 shif = (skinnum % 8); + UINT8 byte = (skinnum / 8); + return !!(players[playernum].availabilities[byte] & (1 << shif)); + } + + // Use the unlockables table directly + return (boolean)(unlockables[i].unlocked); } // returns true if the skin name is found (loaded from pwad) @@ -296,7 +311,25 @@ static void SetSkin(player_t *player, INT32 skinnum) } // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! - demo_extradata[playernum] |= DXD_SKIN; + demo_extradata[(player-players)] |= DXD_SKIN; +} + +// Gets the player to the first usuable skin in the game. +// (If your mod locked them all, then you kinda stupid) +static INT32 GetPlayerDefaultSkin(INT32 playernum) +{ + INT32 i; + + for (i = 0; i < numskins; i++) + { + if (R_SkinUsable(playernum, i, false)) + { + return i; + } + } + + I_Error("All characters are locked!"); + return 0; } // network code calls this when a 'skin change' is received @@ -305,7 +338,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) INT32 i = R_SkinAvailable(skinname); player_t *player = &players[playernum]; - if ((i != -1) && R_SkinUsable(playernum, i)) + if ((i != -1) && R_SkinUsable(playernum, i, false)) { SetSkin(player, i); return; @@ -316,7 +349,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname) else if(server || IsPlayerAdmin(consoleplayer)) CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname); - SetSkin(player, 0); + SetSkin(player, GetPlayerDefaultSkin(playernum)); } // Same as SetPlayerSkin, but uses the skin #. @@ -325,7 +358,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; - if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists! + if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum, false)) // Make sure it exists! { SetSkin(player, skinnum); return; @@ -336,7 +369,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) else if(server || IsPlayerAdmin(consoleplayer)) CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum); - SetSkin(player, 0); // not found put the eggman skin + SetSkin(player, GetPlayerDefaultSkin(playernum)); // not found put the eggman skin } // Set mo skin but not player_t skin, for ironman @@ -386,11 +419,14 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) } else if (skins[i].flags & SF_IRONMAN) continue; - /*if (K_SkinLocked(i)) - continue;*/ + if (!R_SkinUsable(player-players, i, true)) + continue; grabskins[usableskins++] = i; } + if (!usableskins) + I_Error("SetRandomFakePlayerSkin: No valid skins to pick from!?"); + i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)]; SetFakePlayerSkin(player, i); @@ -662,7 +698,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) // // Find skin sprites, sounds & optional status bar face, & add them // -void R_AddSkins(UINT16 wadnum) +void R_AddSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; @@ -810,7 +846,8 @@ next_token: R_FlushTranslationColormapCache(); - CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); + if (mainfile == false) + CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); #ifdef SKINVALUES skin_cons_t[numskins].value = numskins; @@ -834,7 +871,7 @@ next_token: // // Patch skin sprites // -void R_PatchSkins(UINT16 wadnum) +void R_PatchSkins(UINT16 wadnum, boolean mainfile) { UINT16 lump, lastlump = 0; char *buf; @@ -976,7 +1013,8 @@ next_token: R_FlushTranslationColormapCache(); - CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name); + if (mainfile == false) + CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name); } return; } diff --git a/src/r_skins.h b/src/r_skins.h index 1cc06cca4..7b32d3e0c 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -84,11 +84,13 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 void SetFakePlayerSkin(player_t* player, INT32 skinnum); void SetRandomFakePlayerSkin(player_t* player, boolean fast); void ClearFakePlayerSkin(player_t* player); -boolean R_SkinUsable(INT32 playernum, INT32 skinnum); -UINT32 R_GetSkinAvailabilities(void); +boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins); + +UINT8 *R_GetSkinAvailabilities(boolean demolock); INT32 R_SkinAvailable(const char *name); -void R_PatchSkins(UINT16 wadnum); -void R_AddSkins(UINT16 wadnum); + +void R_PatchSkins(UINT16 wadnum, boolean mainfile); +void R_AddSkins(UINT16 wadnum, boolean mainfile); UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player); From a0bcd52bf11e10b5ce63e4ee0d8940778467a65a Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 28 Nov 2022 01:47:00 -0500 Subject: [PATCH 04/95] Break the Capsules HUD tracking --- src/k_hud.c | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/k_hud.h | 2 + 2 files changed, 339 insertions(+), 4 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 5325f07eb..64b0bf911 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -172,6 +172,11 @@ static patch_t *kp_bossret[4]; static patch_t *kp_trickcool[2]; +static patch_t *kp_capsuletarget_arrow[2][2]; +static patch_t *kp_capsuletarget_icon[2]; +static patch_t *kp_capsuletarget_far[2]; +static patch_t *kp_capsuletarget_near[8]; + void K_LoadKartHUDGraphics(void) { INT32 i, j, k; @@ -643,6 +648,39 @@ void K_LoadKartHUDGraphics(void) buffer[7] = '0'+((i+1)%10); HU_UpdatePatch(&kp_bossret[i], "%s", buffer); } + + sprintf(buffer, "HCAPARxx"); + for (i = 0; i < 2; i++) + { + buffer[6] = 'A'+i; + + for (j = 0; j < 2; j++) + { + buffer[7] = '0'+j; + HU_UpdatePatch(&kp_capsuletarget_arrow[i][j], "%s", buffer); + } + } + + sprintf(buffer, "HUDCAPCx"); + for (i = 0; i < 2; i++) + { + buffer[7] = '0'+i; + HU_UpdatePatch(&kp_capsuletarget_icon[i], "%s", buffer); + } + + sprintf(buffer, "HUDCAPBx"); + for (i = 0; i < 2; i++) + { + buffer[7] = '0'+i; + HU_UpdatePatch(&kp_capsuletarget_far[i], "%s", buffer); + } + + sprintf(buffer, "HUDCAPAx"); + for (i = 0; i < 8; i++) + { + buffer[7] = '0'+i; + HU_UpdatePatch(&kp_capsuletarget_near[i], "%s", buffer); + } } // For the item toggle menu @@ -888,7 +926,7 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers fixed_t fovDiff, fov, fovTangent, fg; fixed_t h; - INT32 da; + INT32 da, dp; UINT8 cameraNum = R_GetViewNumber(); @@ -949,6 +987,7 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers // Determine viewpoint factors. h = R_PointToDist2(point->x, point->y, viewx, viewy); da = AngleDeltaSigned(viewpointAngle, R_PointToAngle2(point->x, point->y, viewx, viewy)); + dp = AngleDeltaSigned(viewpointAiming, R_PointToAngle2(0, 0, h, viewz)); if (reverse) { @@ -959,6 +998,10 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers result->x = FixedMul(NEWTAN(da), fg); result->y = FixedMul((NEWTAN(viewpointAiming) - FixedDiv((viewz - point->z), 1 + FixedMul(NEWCOS(da), h))), fg); + result->angle = da; + result->pitch = dp; + result->fov = fg; + // Rotate for screen roll... if (viewpointRoll) { @@ -3070,6 +3113,217 @@ static void K_DrawWeakSpot(weakspotdraw_t *ws) V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j+1], colormap); } +typedef struct capsuletracking_s +{ + mobj_t *mobj; + vector3_t point; + fixed_t camDist; +} capsuletracking_t; + +static void K_DrawCapsuleTracking(capsuletracking_t *caps) +{ + trackingResult_t result = {0}; + INT32 timer = 0; + + K_ObjectTracking(&result, &caps->point, false); + + if (result.onScreen == false) + { + // Off-screen, draw alongside the borders of the screen. + // Probably the most complicated thing. + + INT32 scrVal = 240; + vector2_t screenSize = {0}; + + INT32 borderSize = 7; + vector2_t borderWin = {0}; + vector2_t borderDir = {0}; + fixed_t borderLen = FRACUNIT; + + vector2_t arrowDir = {0}; + + vector2_t arrowPos = {0}; + patch_t *arrowPatch = NULL; + INT32 arrowFlags = 0; + + vector2_t capsulePos = {0}; + patch_t *capsulePatch = NULL; + + timer = (leveltime / 3); + + screenSize.x = vid.width / vid.dupx; + screenSize.y = vid.height / vid.dupy; + + if (r_splitscreen >= 2) + { + // Half-wide screens + screenSize.x >>= 1; + borderSize >>= 1; + } + + if (r_splitscreen >= 1) + { + // Half-tall screens + screenSize.y >>= 1; + } + + scrVal = max(screenSize.x, screenSize.y) - 80; + + borderWin.x = screenSize.x - borderSize; + borderWin.y = screenSize.y - borderSize; + + arrowDir.x = 0; + arrowDir.y = P_MobjFlip(caps->mobj) * FRACUNIT; + + // Simply pointing towards the result doesn't work, so inaccurate hack... + borderDir.x = FixedMul( + FixedMul( + FINESINE((-result.angle >> ANGLETOFINESHIFT) & FINEMASK), + FINECOSINE((-result.pitch >> ANGLETOFINESHIFT) & FINEMASK) + ), + result.fov + ); + + borderDir.y = FixedMul( + FINESINE((-result.pitch >> ANGLETOFINESHIFT) & FINEMASK), + result.fov + ); + + borderLen = R_PointToDist2(0, 0, borderDir.x, borderDir.y); + + if (borderLen > 0) + { + borderDir.x = FixedDiv(borderDir.x, borderLen); + borderDir.y = FixedDiv(borderDir.y, borderLen); + } + else + { + // Eh just put it at the bottom. + borderDir.x = 0; + borderDir.y = FRACUNIT; + } + + capsulePatch = kp_capsuletarget_icon[timer & 1]; + + if (abs(borderDir.x) > abs(borderDir.y)) + { + // Horizontal arrow + arrowPatch = kp_capsuletarget_arrow[1][timer & 1]; + arrowDir.y = 0; + + if (borderDir.x < 0) + { + // LEFT + arrowDir.x = -FRACUNIT; + } + else + { + // RIGHT + arrowDir.x = FRACUNIT; + } + } + else + { + // Vertical arrow + arrowPatch = kp_capsuletarget_arrow[0][timer & 1]; + arrowDir.x = 0; + + if (borderDir.y < 0) + { + // UP + arrowDir.y = -FRACUNIT; + } + else + { + // DOWN + arrowDir.y = FRACUNIT; + } + } + + arrowPos.x = (screenSize.x >> 1) + FixedMul(scrVal, borderDir.x); + arrowPos.y = (screenSize.y >> 1) + FixedMul(scrVal, borderDir.y); + + arrowPos.x = min(max(arrowPos.x, borderSize), borderWin.x) * FRACUNIT; + arrowPos.y = min(max(arrowPos.y, borderSize), borderWin.y) * FRACUNIT; + + capsulePos.x = arrowPos.x - (arrowDir.x * 12); + capsulePos.y = arrowPos.y - (arrowDir.y * 12); + + arrowPos.x -= (arrowPatch->width << FRACBITS) >> 1; + arrowPos.y -= (arrowPatch->height << FRACBITS) >> 1; + + capsulePos.x -= (capsulePatch->width << FRACBITS) >> 1; + capsulePos.y -= (capsulePatch->height << FRACBITS) >> 1; + + if (arrowDir.x < 0) + { + arrowPos.x += arrowPatch->width << FRACBITS; + arrowFlags |= V_FLIP; + } + + if (arrowDir.y < 0) + { + arrowPos.y += arrowPatch->height << FRACBITS; + arrowFlags |= V_VFLIP; + } + + V_DrawFixedPatch( + capsulePos.x, capsulePos.y, + FRACUNIT, + V_SPLITSCREEN, + capsulePatch, NULL + ); + + V_DrawFixedPatch( + arrowPos.x, arrowPos.y, + FRACUNIT, + V_SPLITSCREEN | arrowFlags, + arrowPatch, NULL + ); + } + else + { + // Draw simple overlay. + const fixed_t farDistance = 1280*mapobjectscale; + boolean useNear = (caps->camDist < farDistance); + + patch_t *capsulePatch = NULL; + vector2_t capsulePos = {0}; + + boolean visible = P_CheckSight(stplyr->mo, caps->mobj); + + if (visible == false && (leveltime & 1)) + { + // Flicker when not visible. + return; + } + + capsulePos.x = result.x; + capsulePos.y = result.y; + + if (useNear == true) + { + timer = (leveltime / 2); + capsulePatch = kp_capsuletarget_near[timer % 8]; + } + else + { + timer = (leveltime / 3); + capsulePatch = kp_capsuletarget_far[timer & 1]; + } + + capsulePos.x -= (capsulePatch->width << FRACBITS) >> 1; + capsulePos.y -= (capsulePatch->height << FRACBITS) >> 1; + + V_DrawFixedPatch( + capsulePos.x, capsulePos.y, + FRACUNIT, + V_SPLITSCREEN, + capsulePatch, NULL + ); + } +} + static void K_drawKartNameTags(void) { const fixed_t maxdistance = 8192*mapobjectscale; @@ -3078,7 +3332,7 @@ static void K_drawKartNameTags(void) UINT8 tobesorted[MAXPLAYERS]; fixed_t sortdist[MAXPLAYERS]; UINT8 sortlen = 0; - UINT8 i, j; + size_t i, j; if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) { @@ -3169,6 +3423,85 @@ static void K_drawKartNameTags(void) } } + if (battlecapsules == true) + { +#define MAX_CAPSULE_HUD 32 + capsuletracking_t capsuleList[MAX_CAPSULE_HUD]; + size_t capsuleListLen = 0; + + mobj_t *mobj = NULL; + mobj_t *next = NULL; + + for (mobj = kitemcap; mobj; mobj = next) + { + capsuletracking_t *caps = NULL; + + next = mobj->itnext; + + if (mobj->health <= 0) + { + continue; + } + + if (mobj->type != MT_BATTLECAPSULE) + { + continue; + } + + caps = &capsuleList[capsuleListLen]; + + caps->mobj = mobj; + caps->point.x = R_InterpolateFixed(mobj->old_x, mobj->x); + caps->point.y = R_InterpolateFixed(mobj->old_y, mobj->y); + caps->point.z = R_InterpolateFixed(mobj->old_z, mobj->z); + caps->point.z += (mobj->height >> 1); + caps->camDist = R_PointToDist2(c.x, c.y, caps->point.x, caps->point.y); + + capsuleListLen++; + + if (capsuleListLen >= MAX_CAPSULE_HUD) + { + break; + } + } + + if (capsuleListLen > 0) + { + // Sort by distance from camera. + if (capsuleListLen > 1) + { + for (i = 0; i < capsuleListLen-1; i++) + { + size_t swap = i; + + for (j = i + 1; j < capsuleListLen; j++) + { + capsuletracking_t *cj = &capsuleList[j]; + capsuletracking_t *cSwap = &capsuleList[swap]; + + if (cj->camDist > cSwap->camDist) + { + swap = j; + } + } + + if (swap != i) + { + capsuletracking_t temp = capsuleList[swap]; + capsuleList[swap] = capsuleList[i]; + capsuleList[i] = temp; + } + } + } + + for (i = 0; i < capsuleListLen; i++) + { + K_DrawCapsuleTracking(&capsuleList[i]); + } + } +#undef MAX_CAPSULE_HUD + } + for (i = 0; i < MAXPLAYERS; i++) { player_t *ntplayer = &players[i]; @@ -3283,7 +3616,7 @@ static void K_drawKartNameTags(void) { if (!(demo.playback == true && demo.freecam == true)) { - for (j = 0; j <= r_splitscreen; j++) + for (j = 0; j <= (unsigned)r_splitscreen; j++) { if (ntplayer == &players[displayplayers[j]]) { @@ -3291,7 +3624,7 @@ static void K_drawKartNameTags(void) } } - if (j <= r_splitscreen && j != cnum) + if (j <= (unsigned)r_splitscreen && j != cnum) { localindicator = j; } diff --git a/src/k_hud.h b/src/k_hud.h index 4f84f9dd3..3ea839385 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -28,6 +28,8 @@ typedef struct trackingResult_s fixed_t x, y; fixed_t scale; boolean onScreen; + INT32 angle, pitch; + fixed_t fov; } trackingResult_t; void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean reverse); From 81cefa2697b7ea25a455bc541c2cef0da7d7b3f5 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 28 Nov 2022 02:41:42 -0500 Subject: [PATCH 05/95] Gachabom initial pass They simply use Kitchen Sink's item icon and Orbinaut's sprites. The forward toss behavior needs fine-tuning to use more MT_BANANA style behavior. --- src/d_netcmd.c | 2 ++ src/d_netcmd.h | 6 +++-- src/d_player.h | 4 ++- src/deh_tables.c | 3 +++ src/info.c | 27 +++++++++++++++++++ src/info.h | 2 ++ src/k_botitem.c | 5 ++++ src/k_botsearch.c | 1 + src/k_collide.c | 7 ++--- src/k_hud.c | 5 ++++ src/k_kart.c | 59 +++++++++++++++++++++++++++++++++++------- src/objects/orbinaut.c | 4 +-- src/p_inter.c | 3 ++- src/p_map.c | 12 ++++----- src/p_mobj.c | 15 ++++++++++- 15 files changed, 129 insertions(+), 26 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 086c4cbfa..3494ad8c3 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -382,6 +382,7 @@ consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_gachabom = CVAR_INIT ("gachabom", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); @@ -389,6 +390,7 @@ consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_O consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_triplegachabom = CVAR_INIT ("triplegachabom", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_kartspeed = CVAR_INIT ("gamespeed", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange); static CV_PossibleValue_t kartbumpers_cons_t[] = {{1, "MIN"}, {12, "MAX"}, {0, NULL}}; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 1583ec5d8..a6b11d694 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -94,7 +94,8 @@ extern consvar_t cv_superring, cv_kitchensink, cv_droptarget, - cv_gardentop; + cv_gardentop, + cv_gachabom; extern consvar_t cv_dualsneaker, @@ -102,7 +103,8 @@ extern consvar_t cv_triplebanana, cv_tripleorbinaut, cv_quadorbinaut, - cv_dualjawz; + cv_dualjawz, + cv_triplegachabom; extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; diff --git a/src/d_player.h b/src/d_player.h index e9ebe7668..a2581db8c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -154,7 +154,8 @@ Run this macro, then #undef FOREACH afterward FOREACH (SUPERRING, 19),\ FOREACH (KITCHENSINK, 20),\ FOREACH (DROPTARGET, 21),\ - FOREACH (GARDENTOP, 22) + FOREACH (GARDENTOP, 22),\ + FOREACH (GACHABOM, 23) typedef enum { @@ -171,6 +172,7 @@ typedef enum KRITEM_TRIPLEORBINAUT, KRITEM_QUADORBINAUT, KRITEM_DUALJAWZ, + KRITEM_TRIPLEGACHABOM, NUMKARTRESULTS } kartitems_t; diff --git a/src/deh_tables.c b/src/deh_tables.c index a36b7af9d..47b282030 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5374,6 +5374,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SINK_SHIELD", "MT_SINKTRAIL", + "MT_GACHABOM", + "MT_DUELBOMB", // Duel mode bombs "MT_BATTLEBUMPER", // Battle Mode bumper @@ -6778,6 +6780,7 @@ struct int_const_s const INT_CONST[] = { {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, {"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT}, {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, + {"KRITEM_TRIPLEGACHABOM",KRITEM_TRIPLEGACHABOM}, {"NUMKARTRESULTS",NUMKARTRESULTS}, // kartshields_t diff --git a/src/info.c b/src/info.c index 3824731d1..b71241509 100644 --- a/src/info.c +++ b/src/info.c @@ -24187,6 +24187,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_GACHABOM + -1, // doomednum + S_ORBINAUT1, // spawnstate + 7, // spawnhealth + S_NULL, // seestate + sfx_tossed, // seesound + 8, // reactiontime + sfx_s3k49, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_ORBINAUT_DEAD,// deathstate + S_NULL, // xdeathstate + sfx_s3k5d, // deathsound + 64*FRACUNIT, // speed + 24*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_s3k96, // activesound + MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags + S_NULL // raisestate + }, + { // MT_DUELBOMB 2050, // doomednum S_SPB1, // spawnstate diff --git a/src/info.h b/src/info.h index 7dff6e2de..f2d0f62dd 100644 --- a/src/info.h +++ b/src/info.h @@ -6426,6 +6426,8 @@ typedef enum mobj_type MT_SINK_SHIELD, MT_SINKTRAIL, + MT_GACHABOM, + MT_DUELBOMB, // Duel mode bombs MT_BATTLEBUMPER, // Battle Mode bumpers diff --git a/src/k_botitem.c b/src/k_botitem.c index bb90ef736..e51b04b3a 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1373,6 +1373,11 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) case KITEM_FLAMESHIELD: K_BotItemFlame(player, cmd); break; + /* + case KITEM_GACHABOM: + K_BotItemGachabom(player, cmd); + break; + */ } } } diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 2d9c9774d..8af4dcc75 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -415,6 +415,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) case MT_SPB: case MT_BUBBLESHIELDTRAP: case MT_DUELBOMB: + case MT_GACHABOM: K_AddDodgeObject(thing, side, 20); break; case MT_SHRINK_GUN: diff --git a/src/k_collide.c b/src/k_collide.c index 6a67e5a04..5ff75dc17 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -86,7 +86,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD || t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD - || t2->type == MT_BALLHOG) + || t2->type == MT_BALLHOG || t2->type == MT_GACHABOM) { // Other Item Damage angle_t bounceangle = K_GetCollideAngle(t1, t2); @@ -340,7 +340,8 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) } } else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ - || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD) + || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD + || t2->type == MT_GACHABOM) { // Bomb death angle_t bounceangle = K_GetCollideAngle(t1, t2); @@ -401,7 +402,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD || t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD - || t2->type == MT_BALLHOG) + || t2->type == MT_BALLHOG || t2->type == MT_GACHABOM) { // Other Item Damage angle_t bounceangle = K_GetCollideAngle(t1, t2); diff --git a/src/k_hud.c b/src/k_hud.c index 64b0bf911..c33879360 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -736,6 +736,9 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISDTRG" : "K_ITDTRG"); case KITEM_GARDENTOP: return (tiny ? "K_ISGTOP" : "K_ITGTOP"); + case KITEM_GACHABOM: // temp + case KRITEM_TRIPLEGACHABOM: // temp + return (tiny ? "K_ISSINK" : "K_ITSINK"); case KRITEM_TRIPLEORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB3"); case KRITEM_QUADORBINAUT: @@ -772,6 +775,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) kp_kitchensink, kp_droptarget, kp_gardentop, + kp_kitchensink, // temp }; if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) @@ -1208,6 +1212,7 @@ static void K_drawKartItem(void) default: localpatch = K_GetCachedItemPatch(item, offset); + break; } } else diff --git a/src/k_kart.c b/src/k_kart.c index 9973bc5d5..c6c3c30f0 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -423,12 +423,14 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_kitchensink, &cv_droptarget, &cv_gardentop, + &cv_gachabom, &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, &cv_tripleorbinaut, &cv_quadorbinaut, - &cv_dualjawz + &cv_dualjawz, + &cv_triplegachabom }; #define NUMKARTODDS 80 @@ -459,12 +461,14 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 + { 0, 0, 1, 2, 1, 0, 0, 0 }, // Jawz x2 + { 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3 }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = @@ -492,12 +496,14 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { 0, 0 }, // Kitchen Sink { 2, 0 }, // Drop Target { 4, 0 }, // Garden Top + { 0, 0 }, // Gachabom { 0, 0 }, // Sneaker x2 { 0, 1 }, // Sneaker x3 { 0, 0 }, // Banana x3 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 - { 5, 1 } // Jawz x2 + { 5, 1 }, // Jawz x2 + { 0, 0 } // Gachabom x3 }; // TODO: add back when this gets used @@ -527,12 +533,14 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 }, // Kitchen Sink { 0, 0, 0, 0 }, // Drop Target { 0, 0, 0, 0 }, // Garden Top + { 0, 0, 0, 0 }, // Gachabom { 0, 1, 1, 0 }, // Sneaker x2 { 0, 0, 1, 1 }, // Sneaker x3 { 0, 0, 0, 0 }, // Banana x3 { 0, 1, 1, 0 }, // Orbinaut x3 { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 } // Jawz x2 + { 0, 0, 1, 1 }, // Jawz x2 + { 0, 0, 0, 0 } // Gachabom x3 }; #endif @@ -599,6 +607,9 @@ SINT8 K_ItemResultToType(SINT8 getitem) case KRITEM_DUALJAWZ: return KITEM_JAWZ; + case KRITEM_TRIPLEGACHABOM: + return KITEM_GACHABOM; + default: I_Error("Bad item cooldown redirect for result %d\n", getitem); break; @@ -619,6 +630,7 @@ UINT8 K_ItemResultToAmount(SINT8 getitem) case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEBANANA: case KRITEM_TRIPLEORBINAUT: + case KRITEM_TRIPLEGACHABOM: return 3; case KRITEM_QUADORBINAUT: @@ -1370,24 +1382,24 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } else if (gametype == GT_BATTLE) { - if (mashed && (modeattacking || bossinfo.boss || cv_banana.value)) // ANY mashed value? You get a banana. + if (mashed && (modeattacking || bossinfo.boss || cv_gachabom.value)) // ANY mashed value? You get a Gachabom. { - K_KartGetItemResult(player, KITEM_BANANA); + K_KartGetItemResult(player, KITEM_GACHABOM); player->karthud[khud_itemblinkmode] = 1; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else if (bossinfo.boss) { - K_KartGetItemResult(player, KITEM_ORBINAUT); + K_KartGetItemResult(player, KITEM_ORBINAUT); // FIXME player->karthud[khud_itemblinkmode] = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } else { - if (modeattacking || cv_tripleorbinaut.value) // Waited patiently? You get Orbinaut x3! - K_KartGetItemResult(player, KRITEM_TRIPLEORBINAUT); + if (modeattacking || cv_triplegachabom.value) // Waited patiently? You get Gachabom x3! + K_KartGetItemResult(player, KRITEM_TRIPLEGACHABOM); else // Default to sad if nothing's enabled... K_KartGetItemResult(player, KITEM_SAD); player->karthud[khud_itemblinkmode] = 0; @@ -1550,6 +1562,7 @@ fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) break; case MT_ORBINAUT: case MT_ORBINAUT_SHIELD: + case MT_GACHABOM: case MT_DUELBOMB: if (against->player) weight = K_PlayerWeight(against, NULL); @@ -5271,7 +5284,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I finalscale = source->scale; } - if (dir == -1 && (type == MT_ORBINAUT || type == MT_BALLHOG)) + if (dir == -1 && (type == MT_ORBINAUT || type == MT_GACHABOM || type == MT_BALLHOG)) { // Backwards nerfs finalspeed /= 8; @@ -5328,6 +5341,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I switch (type) { case MT_ORBINAUT: + case MT_GACHABOM: Obj_OrbinautThrown(th, finalspeed, dir); break; case MT_JAWZ: @@ -6175,6 +6189,12 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, dir = defaultDir; } + if (mapthing == MT_GACHABOM && dir > 0) + { + // This item is both a missile and not! + missile = false; + } + if (missile) // Shootables { if (dir < 0 && mapthing != MT_SPB && mapthing != MT_GARDENTOP) @@ -6242,6 +6262,16 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS); } + if (mapthing == MT_GACHABOM) + { + // Set dropped flag + mo->flags2 |= MF2_AMBUSH; + mo->movecount = 2; + P_SetMobjState(mo, mo->info->deathstate); + mo->tics = -1; + mo->color = player->skincolor; + } + // this is the small graphic effect that plops in you when you throw an item: throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); P_SetTarget(&throwmo->target, player->mo); @@ -11418,6 +11448,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground) K_UpdateHnextList(player, true); } break; + case KITEM_GACHABOM: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0); + K_PlayAttackTaunt(player->mo); + player->itemamount--; + K_UpdateHnextList(player, false); + } + break; case KITEM_SAD: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && !player->sadtimer) diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index b4daefb9b..0e8bf7d24 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -184,7 +184,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) if (t2->player) { if ((t2->player->flashing > 0 && t2->hitlag == 0) - && !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ)) + && !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ || t1->type == MT_GACHABOM)) return true; if (t2->player->hyudorotimer) @@ -209,7 +209,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD || t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD - || t2->type == MT_BALLHOG) + || t2->type == MT_BALLHOG || t2->type == MT_GACHABOM) { // Other Item Damage angle_t bounceangle = K_GetCollideAngle(t1, t2); diff --git a/src/p_inter.c b/src/p_inter.c index e77a5d8ff..6498397d8 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -955,7 +955,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget || target->type == MT_BANANA || target->type == MT_BANANA_SHIELD || target->type == MT_DROPTARGET || target->type == MT_DROPTARGET_SHIELD || target->type == MT_EGGMANITEM || target->type == MT_EGGMANITEM_SHIELD - || target->type == MT_BALLHOG || target->type == MT_SPB)) // kart dead items + || target->type == MT_BALLHOG || target->type == MT_SPB + || target->type == MT_GACHABOM)) // kart dead items target->flags |= MF_NOGRAVITY; // Don't drop Tails 03-08-2000 else target->flags &= ~MF_NOGRAVITY; // lose it if you for whatever reason have it, I'm looking at you shields diff --git a/src/p_map.c b/src/p_map.c index cb5d030a2..ddc02ca0e 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -849,7 +849,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) // Bubble Shield reflect if (((thing->type == MT_BUBBLESHIELD && thing->target->player && thing->target->player->bubbleblowup) || (thing->player && thing->player->bubbleblowup)) - && (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ + && (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM || tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG || tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK || tm.thing->type == MT_GARDENTOP @@ -865,7 +865,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } else if (((tm.thing->type == MT_BUBBLESHIELD && tm.thing->target->player && tm.thing->target->player->bubbleblowup) || (tm.thing->player && tm.thing->player->bubbleblowup)) - && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ + && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP @@ -886,7 +886,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) // Droptarget reflect if ((thing->type == MT_DROPTARGET || thing->type == MT_DROPTARGET_SHIELD) - && (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ + && (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM || tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG || tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK || tm.thing->type == MT_GARDENTOP @@ -901,7 +901,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return K_DropTargetCollide(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; } else if ((tm.thing->type == MT_DROPTARGET || tm.thing->type == MT_DROPTARGET_SHIELD) - && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ + && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP @@ -921,7 +921,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || thing->type == MT_DROPTARGET_SHIELD || tm.thing->type == MT_DROPTARGET_SHIELD) return BMIT_CONTINUE; - if (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ + if (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM || tm.thing->type == MT_ORBINAUT_SHIELD || tm.thing->type == MT_JAWZ_SHIELD || tm.thing->type == MT_GARDENTOP) { @@ -933,7 +933,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return Obj_OrbinautJawzCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; } - else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ + else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM || thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD || thing->type == MT_GARDENTOP) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 814aa6b57..d08e5ed3a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1195,6 +1195,13 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_BATTLEBUMPER: gravityadd /= 2; break; + case MT_GACHABOM: + if (!(mo->flags2 & MF2_AMBUSH)) + { + // Use normal gravity, unless if it was tossed. + break; + } + /*FALLTHRU*/ case MT_BANANA: case MT_EGGMANITEM: case MT_SSMINE: @@ -1742,7 +1749,7 @@ void P_XYMovement(mobj_t *mo) //{ SRB2kart - Orbinaut, Ballhog // Bump sparks - if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG) + if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG || mo->type == MT_GACHABOM) { mobj_t *fx; fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); @@ -1756,6 +1763,7 @@ void P_XYMovement(mobj_t *mo) switch (mo->type) { case MT_ORBINAUT: // Orbinaut speed decreasing + case MT_GACHABOM: case MT_GARDENTOP: if (mo->health > 1) { @@ -5174,6 +5182,7 @@ boolean P_IsKartFieldItem(INT32 type) case MT_SINK: case MT_DROPTARGET: case MT_DUELBOMB: + case MT_GACHABOM: return true; default: @@ -6540,6 +6549,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) case MT_LANDMINE: //case MT_DROPTARGET: case MT_SPB: + case MT_GACHABOM: if (P_IsObjectOnGround(mobj)) { P_RemoveMobj(mobj); @@ -6936,6 +6946,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; case MT_ORBINAUT: + case MT_GACHABOM: { Obj_OrbinautThink(mobj); P_MobjCheckWater(mobj); @@ -9743,6 +9754,7 @@ void P_MobjThinker(mobj_t *mobj) || mobj->type == MT_CANNONBALLDECOR || mobj->type == MT_FALLINGROCK || mobj->type == MT_ORBINAUT + || mobj->type == MT_GACHABOM || mobj->type == MT_JAWZ || (mobj->type == MT_DROPTARGET && mobj->reactiontime)) { @@ -10020,6 +10032,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_ROCKETSNEAKER: case MT_SPB: case MT_DUELBOMB: + case MT_GACHABOM: thing->shadowscale = 3*FRACUNIT/2; break; case MT_BANANA_SHIELD: From b074d6cbdda621f92116ab9c4ce0dd1fecf587a6 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Nov 2022 19:21:02 +0000 Subject: [PATCH 06/95] Fix the actual problem with saving in DEVELOP: `cv_cheats` didn't have CV_NOINIT Also fixes a likely waiting-in-the-wings crash for the next time we make a non-DEVELOP build. --- src/command.c | 2 +- src/g_game.c | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/command.c b/src/command.c index 30198c3e2..28b73bdbc 100644 --- a/src/command.c +++ b/src/command.c @@ -84,7 +84,7 @@ CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}}; #else #define VALUE "Off" #endif -consvar_t cv_cheats = CVAR_INIT ("cheats", VALUE, CV_NETVAR|CV_CALL, CV_OnOff, CV_CheatsChanged); +consvar_t cv_cheats = CVAR_INIT ("cheats", VALUE, CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, CV_CheatsChanged); #undef VALUE // SRB2kart diff --git a/src/g_game.c b/src/g_game.c index c2ab6d216..bebaa11b0 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4501,6 +4501,14 @@ void G_SaveGameData(void) if (!gamedataloaded) return; // If never loaded (-nodata), don't save + if (usedCheats) + { +#ifdef DEVELOP + CONS_Alert(CONS_WARNING, M_GetText("Cheats used - Gamedata will not be saved.\n")); +#endif + return; + } + length = (4+4+4+1+(MAXEMBLEMS)+MAXEXTRAEMBLEMS+MAXUNLOCKABLES+MAXCONDITIONSETS+4+4); length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); @@ -4511,15 +4519,6 @@ void G_SaveGameData(void) return; } -#ifndef DEVELOP - if (usedCheats) - { - free(savebuffer); - save_p = savebuffer = NULL; - return; - } -#endif - // Version test WRITEUINT32(save_p, GD_VERSIONCHECK); // 4 From 49c71962f5c6cdeb92cc341e6673f25ed9b559e8 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 28 Nov 2022 22:29:36 +0000 Subject: [PATCH 07/95] Update linedef type 441 to only not happen in MP (or demo playback) Only areas missed from the precursor to the "multiplayer emblems" MR --- src/p_spec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_spec.c b/src/p_spec.c index 07ec1f102..b158c9dfc 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2752,7 +2752,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 441: // Trigger unlockable - if ((!modifiedgame || savemoddata) && !(netgame || multiplayer)) + if (!(demo.playback || netgame || multiplayer)) { INT32 trigid = line->args[0]; From 6a2aa87e95196e685ed6fcf078f0e7c376dfd135 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 29 Nov 2022 17:44:25 +0000 Subject: [PATCH 08/95] Fix unlock bounds check for linedef type 319 Fix from (STJr/SRB2!1767) --- src/p_spec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 6f1a75c9e..f2b9398bc 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1497,12 +1497,12 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller if ((modifiedgame && !savemoddata) || (netgame || multiplayer)) return false; - else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count + else if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count { CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); return false; } - else if (!(unlockables[unlockid-1].unlocked)) + else if (!(unlockables[unlockid].unlocked)) return false; } break; From 845fe12b526f89f7800b48b7f4ab847956241448 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 30 Nov 2022 13:19:21 +0000 Subject: [PATCH 09/95] Conversion to using gamedata_t A small piece of (STJr/SRB2!1756). Due to how RR currently handles time attack records and how it WILL handle unlocks, it's not currently feasible to split everything such that you can have two independent gamedata_t... but what's done so far is certainly more sane and less dependent on global variables Other minor refactors: - M_UpdateUnlockablesAndExtraEmblems and M_SilentUpdateUnlockablesAndEmblems are now one function with a boolean for loudness - Unlock prints are currently living in the console, since the cecho stuff was a little broken --- src/d_main.c | 2 + src/doomstat.h | 7 --- src/f_finale.c | 33 ++-------- src/g_game.c | 66 +++++++++---------- src/k_hud.c | 2 +- src/k_menudraw.c | 6 +- src/k_menufunc.c | 8 +-- src/k_pwrlv.c | 4 +- src/m_cheat.c | 8 +-- src/m_cond.c | 160 +++++++++++++++++++++++------------------------ src/m_cond.h | 41 +++++++++--- src/m_random.c | 4 +- src/p_inter.c | 4 +- src/p_mobj.c | 2 +- src/p_setup.c | 2 +- src/p_spec.c | 4 +- src/p_tick.c | 3 +- src/r_skins.c | 4 +- 18 files changed, 174 insertions(+), 186 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index af438e1b4..de93741ba 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1377,6 +1377,8 @@ void D_SRB2Main(void) Z_Init(); CON_SetLoadingProgress(LOADED_ZINIT); + M_NewGameDataStruct(); + // Do this up here so that WADs loaded through the command line can use ExecCfg COM_Init(); diff --git a/src/doomstat.h b/src/doomstat.h index 3338e9b76..55644bfb2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -186,8 +186,6 @@ extern INT32 postimgparam[MAXSPLITSCREENPLAYERS]; extern INT32 viewwindowx, viewwindowy; extern INT32 viewwidth, scaledviewwidth; -extern boolean gamedataloaded; - // Player taking events, and displaying. extern INT32 consoleplayer; extern INT32 displayplayers[MAXSPLITSCREENPLAYERS]; @@ -538,9 +536,6 @@ typedef struct extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES]; extern UINT32 lastcustomtol; -extern tic_t totalplaytime; -extern UINT32 matchesplayed; - extern UINT8 stagefailed; // Emeralds stored as bits to throw savegame hackers off. @@ -682,8 +677,6 @@ extern INT16 votelevels[4][2]; extern SINT8 votes[MAXPLAYERS]; extern SINT8 pickedvote; -extern UINT32 timesBeaten; // # of times the game has been beaten. - // =========================== // Internal parameters, fixed. // =========================== diff --git a/src/f_finale.c b/src/f_finale.c index ca5b44fb3..fb11ee3f2 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -866,7 +866,7 @@ boolean F_CreditResponder(event_t *event) return false; } - /*if (!(timesBeaten) && !(netgame || multiplayer) && !cht_debug) + /*if (!(gamedata->timesBeaten) && !(netgame || multiplayer) && !cht_debug) return false;*/ if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE) @@ -1024,31 +1024,6 @@ void F_GameEvaluationDrawer(void) V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext); -#if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables - if (finalecount >= 5*TICRATE) - { - V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:"); - - if (!usedCheats) - { - INT32 startcoord = 32; - - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS - && unlockables[i].type && !unlockables[i].nocecho) - { - if (unlockables[i].unlocked) - V_DrawString(8, startcoord, 0, unlockables[i].name); - startcoord += 8; - } - } - } - else - V_DrawString(8, 96, V_YELLOWMAP, "Cheated games\ncan't unlock\nextras!"); - } -#endif - if (marathonmode) { const char *rtatext, *cuttext; @@ -1101,9 +1076,9 @@ void F_GameEvaluationTicker(void) { if (!usedCheats) { - ++timesBeaten; + ++gamedata->timesBeaten; - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_s3k68); G_SaveGameData(); @@ -1611,7 +1586,7 @@ void F_EndingDrawer(void) //colset(linkmap, 164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<'|(trans<= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<"); + V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((gamedata->timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<"); } if (finalecount > STOPPINGPOINT-(20+(2*TICRATE))) diff --git a/src/g_game.c b/src/g_game.c index bebaa11b0..fcdc4b628 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -189,7 +189,8 @@ struct quake quake; // Map Header Information mapheader_t** mapheaderinfo = {NULL}; -INT32 nummapheaders, mapallocsize = 0; +INT32 nummapheaders = 0; +INT32 mapallocsize = 0; // Kart cup definitions cupheader_t *kartcupheaders = NULL; @@ -208,10 +209,6 @@ UINT32 tokenlist; // List of tokens collected boolean gottoken; // Did you get a token? Used for end of act INT32 tokenbits; // Used for setting token bits -tic_t totalplaytime; -UINT32 matchesplayed; // SRB2Kart -boolean gamedataloaded = false; - // Temporary holding place for nights data for the current map //nightsdata_t ntemprecords; @@ -340,9 +337,6 @@ static void G_ResetRandMapBuffer(void) //intentionally not resetting randmaps.counttogametype here } -// Grading -UINT32 timesBeaten; - typedef struct joystickvector2_s { INT32 xaxis; @@ -600,7 +594,7 @@ static void G_UpdateRecordReplays(void) if ((earnedEmblems = M_CheckLevelEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu medal%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_ncitem); // SRB2Kart - save here so you NEVER lose your earned times/medals. @@ -2194,8 +2188,8 @@ static inline void G_PlayerFinishLevel(INT32 player) { if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { - matchesplayed++; - if (M_UpdateUnlockablesAndExtraEmblems()) + gamedata->matchesplayed++; + if (M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); } @@ -3691,7 +3685,7 @@ static void G_UpdateVisited(void) if ((earnedEmblems = M_CompletionEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); } @@ -3876,7 +3870,7 @@ static void G_GetNextMap(void) while (cup) { // Not unlocked? Grab the next result afterwards - if (!marathonmode && cup->unlockrequired != -1 && !unlockables[cup->unlockrequired].unlocked) + if (!marathonmode && cup->unlockrequired != -1 && !gamedata->unlocked[cup->unlockrequired]) { cup = cup->next; gettingresult = 1; @@ -4326,15 +4320,15 @@ void G_LoadGameData(void) UINT32 numgamedatamapheaders; // Stop saving, until we successfully load it again. - gamedataloaded = false; + gamedata->loaded = false; // Clear things so previously read gamedata doesn't transfer // to new gamedata - G_ClearRecords(); // main and nights records + G_ClearRecords(); // records M_ClearSecrets(); // emblems, unlocks, maps visited, etc - totalplaytime = 0; // total play time (separate from all) - matchesplayed = 0; // SRB2Kart: matches played & finished + gamedata->totalplaytime = 0; // total play time (separate from all) + gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished if (M_CheckParm("-nodata")) { @@ -4345,7 +4339,7 @@ void G_LoadGameData(void) if (M_CheckParm("-resetdata")) { // Don't load, but do save. (essentially, reset) - gamedataloaded = true; + gamedata->loaded = true; return; } @@ -4353,7 +4347,7 @@ void G_LoadGameData(void) if (!length) { // No gamedata. We can save a new one. - gamedataloaded = true; + gamedata->loaded = true; return; } @@ -4372,8 +4366,8 @@ void G_LoadGameData(void) I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } - totalplaytime = READUINT32(save_p); - matchesplayed = READUINT32(save_p); + gamedata->totalplaytime = READUINT32(save_p); + gamedata->matchesplayed = READUINT32(save_p); { // Quick & dirty hash for what mod this save file is for. @@ -4392,32 +4386,32 @@ void G_LoadGameData(void) { rtemp = READUINT8(save_p); for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) - emblemlocations[j+i].collected = ((rtemp >> j) & 1); + gamedata->collected[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXEXTRAEMBLEMS;) { rtemp = READUINT8(save_p); for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) - extraemblems[j+i].collected = ((rtemp >> j) & 1); + gamedata->extraCollected[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXUNLOCKABLES;) { rtemp = READUINT8(save_p); for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) - unlockables[j+i].unlocked = ((rtemp >> j) & 1); + gamedata->unlocked[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXCONDITIONSETS;) { rtemp = READUINT8(save_p); for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) - conditionSets[j+i].achieved = ((rtemp >> j) & 1); + gamedata->achieved[j+i] = ((rtemp >> j) & 1); i += j; } - timesBeaten = READUINT32(save_p); + gamedata->timesBeaten = READUINT32(save_p); // Main records numgamedatamapheaders = READUINT32(save_p); @@ -4469,10 +4463,10 @@ void G_LoadGameData(void) // It used to do this much earlier, but this would cause the gamedata to // save over itself when it I_Errors from the corruption landing point below, // which can accidentally delete players' legitimate data if the code ever has any tiny mistakes! - gamedataloaded = true; + gamedata->loaded = true; // Silent update unlockables in case they're out of sync with conditions - M_SilentUpdateUnlockablesAndEmblems(); + M_UpdateUnlockablesAndExtraEmblems(false); return; @@ -4498,7 +4492,7 @@ void G_SaveGameData(void) INT32 i, j; UINT8 btemp; - if (!gamedataloaded) + if (!gamedata->loaded) return; // If never loaded (-nodata), don't save if (usedCheats) @@ -4522,8 +4516,8 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save_p, GD_VERSIONCHECK); // 4 - WRITEUINT32(save_p, totalplaytime); // 4 - WRITEUINT32(save_p, matchesplayed); // 4 + WRITEUINT32(save_p, gamedata->totalplaytime); // 4 + WRITEUINT32(save_p, gamedata->matchesplayed); // 4 WRITEUINT32(save_p, quickncasehash(timeattackfolder, 64)); // To save space, use one bit per collected/achieved/unlocked flag @@ -4531,7 +4525,7 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) - btemp |= (emblemlocations[j+i].collected << j); + btemp |= (gamedata->collected[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4539,7 +4533,7 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) - btemp |= (extraemblems[j+i].collected << j); + btemp |= (gamedata->extraCollected[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4547,7 +4541,7 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) - btemp |= (unlockables[j+i].unlocked << j); + btemp |= (gamedata->unlocked[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4555,12 +4549,12 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) - btemp |= (conditionSets[j+i].achieved << j); + btemp |= (gamedata->achieved[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } - WRITEUINT32(save_p, timesBeaten); // 4 + WRITEUINT32(save_p, gamedata->timesBeaten); // 4 // Main records WRITEUINT32(save_p, nummapheaders); // 4 diff --git a/src/k_hud.c b/src/k_hud.c index 5325f07eb..73625dba7 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1494,7 +1494,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI static boolean canplaysound = true; tic_t timetoreach = emblem->var; - if (emblem->collected) + if (gamedata->collected[(emblem-emblemlocations)]) { emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE); emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8bfec4716..6c2c2029e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1908,7 +1908,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (cup && (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked)) + if (cup && (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired])) { i = (cupgrid.previewanim / 82) % cup->numlevels; while (x < BASEVIDWIDTH + pad) @@ -1946,7 +1946,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) if (cup) { - boolean unlocked = (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked); + boolean unlocked = (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired]); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); @@ -2014,7 +2014,7 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (iconcup->unlockrequired != -1 && !unlockables[iconcup->unlockrequired].unlocked) + if (iconcup->unlockrequired != -1 && !gamedata->unlocked[iconcup->unlockrequired]) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 696c14593..39f94a1d9 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -343,8 +343,8 @@ static void M_EraseDataResponse(INT32 ch) if (optionsmenu.erasecontext == 2) { // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets - totalplaytime = 0; - matchesplayed = 0; + gamedata->totalplaytime = 0; + gamedata->matchesplayed = 0; } if (optionsmenu.erasecontext != 1) G_ClearRecords(); @@ -3441,7 +3441,7 @@ static void M_LevelListFromGametype(INT16 gt) while (cup) { - if (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked) + if (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired]) { highestid = cup->id; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) @@ -3575,7 +3575,7 @@ void M_CupSelectHandler(INT32 choice) M_SetMenuDelay(pid); if ((!newcup) - || (newcup && newcup->unlockrequired != -1 && !unlockables[newcup->unlockrequired].unlocked) + || (newcup && newcup->unlockrequired != -1 && !gamedata->unlocked[newcup->unlockrequired]) || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 32a4b8ecd..e4623281b 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -418,7 +418,7 @@ void K_CashInPowerLevels(void) { pr->powerlevels[powerType] = clientpowerlevels[i][powerType]; - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) { S_StartSound(NULL, sfx_ncitem); } @@ -642,7 +642,7 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) { pr->powerlevels[powerType] = yourPower + inc; - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) { S_StartSound(NULL, sfx_ncitem); } diff --git a/src/m_cheat.c b/src/m_cheat.c index be4f231e1..edd23041c 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -76,16 +76,16 @@ static UINT8 cheatf_warp(void) { if (!unlockables[i].conditionset) continue; - if (!unlockables[i].unlocked) + if (!gamedata->unlocked[i]) { - unlockables[i].unlocked = true; + gamedata->unlocked[i] = true; success = true; } } if (success) { - G_SaveGameData(); //G_SetUsedCheats(); + G_SetUsedCheats(); S_StartSound(0, sfx_kc42); } @@ -111,7 +111,7 @@ static UINT8 cheatf_devmode(void) // Just unlock all the things and turn on -debug and console devmode. G_SetUsedCheats(); for (i = 0; i < MAXUNLOCKABLES; i++) - unlockables[i].unlocked = true; + gamedata->unlocked[i] = true; devparm = true; cht_debug |= 0x8000; diff --git a/src/m_cond.c b/src/m_cond.c index 53ff790a6..79dd92d2a 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -24,6 +24,8 @@ #include "k_pwrlv.h" #include "k_profiles.h" +gamedata_t *gamedata = NULL; + // Map triggers for linedef executors // 32 triggers, one bit each UINT32 unlocktriggers; @@ -44,6 +46,14 @@ unlockable_t unlockables[MAXUNLOCKABLES]; INT32 numemblems = 0; INT32 numextraemblems = 0; +// Create a new gamedata_t, for start-up +void M_NewGameDataStruct(void) +{ + gamedata = Z_Calloc(sizeof (gamedata_t), PU_STATIC, NULL); + M_ClearSecrets(); + G_ClearRecords(); +} + void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) { condition_t *cond; @@ -73,7 +83,7 @@ void M_ClearConditionSet(UINT8 set) conditionSets[set - 1].condition = NULL; conditionSets[set - 1].numconditions = 0; } - conditionSets[set - 1].achieved = false; + gamedata->achieved[set - 1] = false; } // Clear ALL secrets. @@ -87,18 +97,18 @@ void M_ClearSecrets(void) } for (i = 0; i < MAXEMBLEMS; ++i) - emblemlocations[i].collected = false; + gamedata->collected[i] = false; for (i = 0; i < MAXEXTRAEMBLEMS; ++i) - extraemblems[i].collected = false; + gamedata->extraCollected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) - unlockables[i].unlocked = false; + gamedata->unlocked[i] = false; for (i = 0; i < MAXCONDITIONSETS; ++i) - conditionSets[i].achieved = false; + gamedata->achieved[i] = false; - timesBeaten = 0; + gamedata->timesBeaten = 0; // Re-unlock any always unlocked things - M_SilentUpdateUnlockablesAndEmblems(); + M_UpdateUnlockablesAndExtraEmblems(false); } // ---------------------- @@ -109,9 +119,9 @@ UINT8 M_CheckCondition(condition_t *cn) switch (cn->type) { case UC_PLAYTIME: // Requires total playing time >= x - return (totalplaytime >= (unsigned)cn->requirement); + return (gamedata->totalplaytime >= (unsigned)cn->requirement); case UC_MATCHESPLAYED: // Requires any level completed >= x times - return (matchesplayed >= (unsigned)cn->requirement); + return (gamedata->matchesplayed >= (unsigned)cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype { UINT8 i; @@ -128,7 +138,7 @@ UINT8 M_CheckCondition(condition_t *cn) return false; } case UC_GAMECLEAR: // Requires game beaten >= x times - return (timesBeaten >= (unsigned)cn->requirement); + return (gamedata->timesBeaten >= (unsigned)cn->requirement); case UC_OVERALLTIME: // Requires overall time <= x return (M_GotLowEnoughTime(cn->requirement)); case UC_MAPVISITED: // Requires map x to be visited @@ -152,9 +162,9 @@ UINT8 M_CheckCondition(condition_t *cn) case UC_TOTALEMBLEMS: // Requires number of emblems >= x return (M_GotEnoughEmblems(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained - return emblemlocations[cn->requirement-1].collected; + return gamedata->collected[cn->requirement-1]; case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained - return extraemblems[cn->requirement-1].collected; + return gamedata->extraCollected[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); } @@ -196,14 +206,14 @@ void M_CheckUnlockConditions(void) for (i = 0; i < MAXCONDITIONSETS; ++i) { c = &conditionSets[i]; - if (!c->numconditions || c->achieved) + if (!c->numconditions || gamedata->achieved[i]) continue; - c->achieved = (M_CheckConditionSet(c)); + gamedata->achieved[i] = (M_CheckConditionSet(c)); } } -UINT8 M_UpdateUnlockablesAndExtraEmblems(void) +boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) { INT32 i; char cechoText[992] = ""; @@ -211,16 +221,31 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void) M_CheckUnlockConditions(); + if (!loud) + { + // Just in case they aren't to sync + M_CheckLevelEmblems(); + M_CompletionEmblems(); + } + // Go through extra emblems for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected || !extraemblems[i].conditionset) - continue; - if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) + if (gamedata->extraCollected[i] || !extraemblems[i].conditionset) { - strcat(cechoText, va(M_GetText("Got \"%s\" medal!\\"), extraemblems[i].name)); - ++cechoLines; + continue; } + + if ((gamedata->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1)) == false) + { + continue; + } + + if (loud) + { + strcat(cechoText, va("Got \"%s\" medal!\n", extraemblems[i].name)); + } + ++cechoLines; } // Fun part: if any of those unlocked we need to go through the @@ -231,67 +256,40 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void) // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].unlocked || !unlockables[i].conditionset) - continue; - if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false) + if (gamedata->unlocked[i] || !unlockables[i].conditionset) { - if (unlockables[i].nocecho) - continue; - strcat(cechoText, va(M_GetText("\"%s\" unlocked!\\"), unlockables[i].name)); - ++cechoLines; + continue; } + + if ((gamedata->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1)) == false) + { + continue; + } + + if (unlockables[i].nocecho) + { + continue; + } + + if (loud) + { + strcat(cechoText, va("\"%s\" unlocked!\n", unlockables[i].name)); + } + ++cechoLines; } // Announce - if (cechoLines) + if (cechoLines && loud) { - char slashed[1024] = ""; - for (i = 0; (i < 19) && (i < 24 - cechoLines); ++i) - slashed[i] = '\\'; - slashed[i] = 0; - - strcat(slashed, cechoText); - - HU_SetCEchoFlags(V_YELLOWMAP); - HU_SetCEchoDuration(6); - HU_DoCEcho(slashed); +#ifdef DEVELOP + // todo make debugmode + CONS_Printf("%s\n", cechoText); +#endif return true; } return false; } -// Used when loading gamedata to make sure all unlocks are synched with conditions -void M_SilentUpdateUnlockablesAndEmblems(void) -{ - INT32 i; - boolean checkAgain = false; - - // Just in case they aren't to sync - M_CheckUnlockConditions(); - M_CheckLevelEmblems(); - - // Go through extra emblems - for (i = 0; i < numextraemblems; ++i) - { - if (extraemblems[i].collected || !extraemblems[i].conditionset) - continue; - if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) - checkAgain = true; - } - - // check again if extra emblems unlocked, blah blah, etc - if (checkAgain) - M_CheckUnlockConditions(); - - // Go through unlockables - for (i = 0; i < MAXUNLOCKABLES; ++i) - { - if (unlockables[i].unlocked || !unlockables[i].conditionset) - continue; - unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); - } -} - // Emblem unlocking shit UINT8 M_CheckLevelEmblems(void) { @@ -306,7 +304,7 @@ UINT8 M_CheckLevelEmblems(void) { INT32 checkLevel; - if (emblemlocations[i].type < ET_TIME || emblemlocations[i].collected) + if (emblemlocations[i].type < ET_TIME || gamedata->collected[i]) continue; checkLevel = G_MapNumber(emblemlocations[i].level); @@ -326,7 +324,7 @@ UINT8 M_CheckLevelEmblems(void) continue; } - emblemlocations[i].collected = res; + gamedata->collected[i] = res; if (res) ++somethingUnlocked; } @@ -346,7 +344,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa { INT32 checkLevel; - if (emblemlocations[i].type < ET_TIME || emblemlocations[i].collected) + if (emblemlocations[i].type < ET_TIME || gamedata->collected[i]) continue; checkLevel = G_MapNumber(emblemlocations[i].level); @@ -363,7 +361,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa res = ((mapheaderinfo[levelnum]->mapvisited & flags) == flags); - emblemlocations[i].collected = res; + gamedata->collected[i] = res; if (res) ++somethingUnlocked; } @@ -385,7 +383,7 @@ UINT8 M_AnySecretUnlocked(void) for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (!unlockables[i].nocecho && unlockables[i].unlocked) + if (!unlockables[i].nocecho && gamedata->unlocked[i]) return true; } return false; @@ -412,7 +410,7 @@ UINT8 M_SecretUnlocked(INT32 type) for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type == type && unlockables[i].unlocked != CHADYES) + if (unlockables[i].type == type && gamedata->unlocked[i] != CHADYES) return !CHADYES; } return CHADYES; @@ -435,7 +433,7 @@ UINT8 M_MapLocked(INT32 mapnum) if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) return false; - if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked) + if (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired]) return true; return false; @@ -447,12 +445,12 @@ INT32 M_CountEmblems(void) INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].collected) + if (gamedata->collected[i]) found++; } for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected) + if (gamedata->extraCollected[i]) found++; } return found; @@ -469,12 +467,12 @@ UINT8 M_GotEnoughEmblems(INT32 number) INT32 i, gottenemblems = 0; for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].collected) + if (gamedata->collected[i]) if (++gottenemblems >= number) return true; } for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected) + if (gamedata->extraCollected[i]) if (++gottenemblems >= number) return true; } return false; diff --git a/src/m_cond.h b/src/m_cond.h index 7633cf39b..4df268277 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -52,8 +52,6 @@ typedef struct { UINT32 numconditions; /// <- number of conditions. condition_t *condition; /// <- All conditionals to be checked. - UINT8 achieved; /// <- Whether this conditional has been achieved already or not. - /// (Conditional checking is skipped if true -- it's assumed you can't relock an unlockable) } conditionset_t; // Emblem information @@ -79,7 +77,6 @@ typedef struct INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) char *stringVar; ///< String version char hint[110]; ///< Hint for emblem hints menu - UINT8 collected; ///< Do you have this emblem? } emblem_t; typedef struct { @@ -89,7 +86,6 @@ typedef struct UINT8 showconditionset; ///< Condition set that shows this emblem. UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT16 color; ///< skincolor to use - UINT8 collected; ///< Do you have this emblem? } extraemblem_t; // Unlockable information @@ -104,7 +100,6 @@ typedef struct char *stringVar; UINT8 nocecho; UINT8 nochecklist; - UINT8 unlocked; } unlockable_t; #define SECRET_NONE 0 // Does nil. Use with levels locked by UnlockRequired @@ -135,6 +130,35 @@ typedef struct #define MAXEXTRAEMBLEMS 16 #define MAXUNLOCKABLES 32 +// GAMEDATA STRUCTURE +// Everything that would get saved in gamedata.dat +typedef struct +{ + // WHENEVER OR NOT WE'RE READY TO SAVE + boolean loaded; + + // CONDITION SETS ACHIEVED + boolean achieved[MAXCONDITIONSETS]; + + // EMBLEMS COLLECTED + boolean collected[MAXEMBLEMS]; + + // EXTRA EMBLEMS COLLECTED + boolean extraCollected[MAXEXTRAEMBLEMS]; + + // UNLOCKABLES UNLOCKED + boolean unlocked[MAXUNLOCKABLES]; + + // # OF TIMES THE GAME HAS BEEN BEATEN + UINT32 timesBeaten; + + // PLAY TIME + UINT32 totalplaytime; + UINT32 matchesplayed; +} gamedata_t; + +extern gamedata_t *gamedata; + extern conditionset_t conditionSets[MAXCONDITIONSETS]; extern emblem_t emblemlocations[MAXEMBLEMS]; extern extraemblem_t extraemblems[MAXEXTRAEMBLEMS]; @@ -145,6 +169,8 @@ extern INT32 numextraemblems; extern UINT32 unlocktriggers; +void M_NewGameDataStruct(void); + // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); @@ -155,8 +181,7 @@ void M_ClearSecrets(void); // Updating conditions and unlockables void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); -UINT8 M_UpdateUnlockablesAndExtraEmblems(void); -void M_SilentUpdateUnlockablesAndEmblems(void); +boolean M_UpdateUnlockablesAndExtraEmblems(boolean silent); UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); @@ -182,4 +207,4 @@ UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); -#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved) +#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a]) diff --git a/src/m_random.c b/src/m_random.c index 6d8beeaa3..755f7ca65 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -14,7 +14,7 @@ #include "doomdef.h" #include "doomtype.h" -#include "doomstat.h" // totalplaytime +#include "m_cond.h" // gamedata->totalplaytime #include "m_random.h" #include "m_fixed.h" @@ -372,5 +372,5 @@ void P_ClearRandom(UINT32 seed) */ UINT32 M_RandomizedSeed(void) { - return ((totalplaytime & 0xFFFF) << 16) | M_RandomFixed(); + return ((gamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed(); } diff --git a/src/p_inter.c b/src/p_inter.c index e77a5d8ff..7c65c6315 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -504,8 +504,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (demo.playback || special->health > MAXEMBLEMS) return; - emblemlocations[special->health-1].collected = true; - M_UpdateUnlockablesAndExtraEmblems(); + gamedata->collected[special->health-1] = true; + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); break; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 0613d816c..4f447ebc9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11969,7 +11969,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting mobj->color = (UINT16)emcolor; - if (emblemlocations[j].collected) + if (gamedata->collected[j]) { P_UnsetThingPosition(mobj); mobj->flags |= MF_NOCLIP; diff --git a/src/p_setup.c b/src/p_setup.c index a308e4ff1..9c06e7038 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7567,7 +7567,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) { mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); } diff --git a/src/p_spec.c b/src/p_spec.c index f2b9398bc..06c658ee1 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1502,7 +1502,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); return false; } - else if (!(unlockables[unlockid].unlocked)) + else if (!(gamedata->unlocked[unlockid])) return false; } break; @@ -2763,7 +2763,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) unlocktriggers |= 1 << trigid; // Unlocked something? - if (M_UpdateUnlockablesAndExtraEmblems()) + if (M_UpdateUnlockablesAndExtraEmblems(true)) { S_StartSound(NULL, sfx_s3k68); G_SaveGameData(); // only save if unlocked something diff --git a/src/p_tick.c b/src/p_tick.c index 7cf1748b5..8312a18a8 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -20,6 +20,7 @@ #include "st_stuff.h" #include "p_polyobj.h" #include "m_random.h" +#include "m_cond.h" // gamedata->playtime #include "lua_script.h" #include "lua_hook.h" #include "m_perfstats.h" @@ -628,7 +629,7 @@ void P_Ticker(boolean run) // Keep track of how long they've been playing! if (!demo.playback) // Don't increment if a demo is playing. - totalplaytime++; + gamedata->totalplaytime++; // formality so kitemcap gets updated properly each frame. P_RunKartItems(); diff --git a/src/r_skins.c b/src/r_skins.c index e4f3a317c..03753d1b5 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -171,7 +171,7 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock) if (unlockables[i].type != SECRET_SKIN) continue; - if (unlockables[i].unlocked != true && !demolock) + if (gamedata->unlocked[i] != true && !demolock) continue; skinid = M_UnlockableSkinNum(&unlockables[i]); @@ -250,7 +250,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) } // Use the unlockables table directly - return (boolean)(unlockables[i].unlocked); + return (boolean)(gamedata->unlocked[i]); } // returns true if the skin name is found (loaded from pwad) From 8b1407c096e8ebc283f66ca8b454114bfa29761b Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 30 Nov 2022 14:32:33 +0000 Subject: [PATCH 10/95] Proper MP emblem support - Also adapted from (STJr/SRB2!1756) - Unlike that MR, absolutely no sharing - this is a game of exclusively competitive gametypes. --- src/p_inter.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++---- src/p_local.h | 2 ++ src/p_mobj.c | 29 +++++++++++++-------------- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/p_inter.c b/src/p_inter.c index 7c65c6315..0c714178a 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -156,6 +156,40 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) return true; } +boolean P_CanPickupEmblem(player_t *player, INT32 emblemID) +{ + if (emblemID < 0 || emblemID >= MAXEMBLEMS) + { + // Invalid emblem ID, can't pickup. + return false; + } + + if (demo.playback) + { + // Never collect emblems in replays. + return false; + } + + if (player->bot) + { + // Your nefarious opponent puppy can't grab these for you. + return false; + } + + return true; +} + +boolean P_EmblemWasCollected(INT32 emblemID) +{ + if (emblemID < 0 || emblemID >= numemblems) + { + // Invalid emblem ID, can't pickup. + return true; + } + + return gamedata->collected[emblemID]; +} + /** Takes action based on a ::MF_SPECIAL thing touched by a player. * Actually, this just checks a few things (heights, toucher->player, no * objectplace, no dead or disappearing things) @@ -501,12 +535,24 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Secret emblem thingy case MT_EMBLEM: { - if (demo.playback || special->health > MAXEMBLEMS) + boolean gotcollected = false; + + if (!P_CanPickupEmblem(player, special->health - 1)) return; - gamedata->collected[special->health-1] = true; - M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + if (P_IsLocalPlayer(player) && !gamedata->collected[special->health-1]) + { + gamedata->collected[special->health-1] = gotcollected = true; + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(); + } + + if (netgame) + { + // Don't delete the object in netgames, just fade it. + return; + } + break; } diff --git a/src/p_local.h b/src/p_local.h index 9bc92cb64..572ff7ad0 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -544,6 +544,8 @@ void P_CheckPointLimit(void); boolean P_CheckRacers(void); boolean P_CanPickupItem(player_t *player, UINT8 weapon); +boolean P_CanPickupEmblem(player_t *player, INT32 emblemID); +boolean P_EmblemWasCollected(INT32 emblemID); // // P_SPEC diff --git a/src/p_mobj.c b/src/p_mobj.c index 4f447ebc9..2e0f4dbfc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6825,6 +6825,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; case MT_EMBLEM: + if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1)) + { + mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); + } + else + { + mobj->frame &= ~FF_TRANSMASK; + } if (mobj->flags2 & MF2_NIGHTSPULL) P_NightsItemChase(mobj); break; @@ -11969,24 +11977,13 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting mobj->color = (UINT16)emcolor; - if (gamedata->collected[j]) - { - P_UnsetThingPosition(mobj); - mobj->flags |= MF_NOCLIP; - mobj->flags &= ~MF_SPECIAL; - mobj->flags |= MF_NOBLOCKMAP; - mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); - P_SetThingPosition(mobj); - } - else - { - mobj->frame &= ~FF_TRANSMASK; + mobj->frame &= ~FF_TRANSMASK; - if (emblemlocations[j].type == ET_GLOBAL) - { - mobj->reactiontime = emblemlocations[j].var; - } + if (emblemlocations[j].type == ET_GLOBAL) + { + mobj->reactiontime = emblemlocations[j].var; } + return true; } From 0aa85bf29137dac7e910955a539809febb17fdf4 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 30 Nov 2022 17:17:40 +0000 Subject: [PATCH 11/95] Unused variable in Got_AddBot --- src/d_clisrv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 2ecea2764..29c460434 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3699,7 +3699,6 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) INT16 newplayernum; UINT8 i, skinnum = 0; UINT8 difficulty = DIFFICULTBOT; - UINT8 availabilitiesbuffer[MAXAVAILABILITY]; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { From 5ba1d4c197874d5a09feb32205b6e582d97cc1c7 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 30 Nov 2022 17:26:33 +0000 Subject: [PATCH 12/95] Unlocks now only get marked unlocked when heading to the main menu EXTREMELY early, but... - M_InterruptMenuWithChallenges hijacks certain transitions - Currently only initial profile selection and title screen, but the intercept is low-profile enough to add wherever necessary - M_GetNextAchievedUnlock gets the first unlock in the listing that has all its conditions achieved but isn't unlocked - Preliminary Challenges menu - Currently only ticks between Achieved but not Unlocked unlocks with the above function, no tilegrid yet --- src/k_menu.h | 28 +++++++++++++++++--- src/k_menudef.c | 21 +++++++++++++++ src/k_menudraw.c | 25 ++++++++++++++++++ src/k_menufunc.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++-- src/m_cond.c | 38 +++++++++++++++++++++++++-- src/m_cond.h | 1 + 6 files changed, 174 insertions(+), 7 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 588542002..5a314ab0a 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -398,12 +398,15 @@ extern menu_t EXTRAS_ReplayStartDef; extern menuitem_t PAUSE_Main[]; extern menu_t PAUSE_MainDef; +// EXTRAS +extern menuitem_t MISC_Manual[]; +extern menu_t MISC_ManualDef; + extern menuitem_t MISC_Addons[]; extern menu_t MISC_AddonsDef; -// MANUAL -extern menuitem_t MISC_Manual[]; -extern menu_t MISC_ManualDef; +extern menuitem_t MISC_Challenges[]; +extern menu_t MISC_ChallengesDef; // We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. typedef enum @@ -1076,6 +1079,25 @@ void M_DrawReplayStartMenu(void); #define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make addons!" void M_DrawAddons(void); +// Challenges menu: +#define UNLOCKTIME 35 + +// Keep track of some pause menu data for visual goodness. +extern struct challengesmenu_s { + + tic_t ticker; // How long the menu's been open for + INT16 offset; // To make the icons move smoothly when we transition! + + UINT8 currentunlock; + tic_t unlockanim; + + boolean pending; +} challengesmenu; + +void M_DrawChallenges(void); +void M_ChallengesTick(void); +boolean M_ChallengesInputs(INT32 ch); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(source, prev, x, y)\ {\ diff --git a/src/k_menudef.c b/src/k_menudef.c index f080ff4e5..9e10560a3 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1744,3 +1744,24 @@ menu_t MISC_AddonsDef = { NULL, NULL }; + +// Challenges. +menuitem_t MISC_ChallengesMenu[] = +{ + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t MISC_ChallengesDef = { + sizeof (MISC_ChallengesMenu)/sizeof (menuitem_t), + &MainDef, + 0, + MISC_ChallengesMenu, + 48, 80, + 0, 0, + 98, 0, + M_DrawChallenges, + M_ChallengesTick, + NULL, + NULL, + M_ChallengesInputs, +}; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 6c2c2029e..48652c6f9 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4457,3 +4457,28 @@ void M_DrawAddons(void) } #undef addonsseperation + +void M_DrawChallenges(void) +{ + INT32 x, y; + + { + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + } + + if (challengesmenu.currentunlock < MAXUNLOCKABLES) + { + V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name); + + if (challengesmenu.unlockanim >= UNLOCKTIME) + V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (A)"); + } + else + { + V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F')); + + if (challengesmenu.unlockanim >= UNLOCKTIME) + V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (B)"); + } +} diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 39f94a1d9..a696a8eb8 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -775,6 +775,20 @@ static boolean M_PrevOpt(void) return true; } + static menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) +{ + M_UpdateUnlockablesAndExtraEmblems(false); + + if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES) + { + challengesmenu.pending = true; + challengesmenu.currentunlock = MAXUNLOCKABLES; + return &MISC_ChallengesDef; + } + + return desiredmenu; +} + // // M_Responder // @@ -981,7 +995,7 @@ void M_StartControlPanel(void) } else { - currentMenu = &MainDef; + currentMenu = M_InterruptMenuWithChallenges(&MainDef); } } else @@ -4811,7 +4825,7 @@ static void M_FirstPickProfile(INT32 c) optionsmenu.profile = NULL; // Make sure to get rid of that, too. PR_ApplyProfile(optionsmenu.profilen, 0); - M_SetupNextMenu(&MainDef, false); + M_SetupNextMenu(M_InterruptMenuWithChallenges(&MainDef), false); // Tell the game this is the last profile we picked. CV_StealthSetValue(&cv_ttlprofilen, optionsmenu.profilen); @@ -6807,3 +6821,53 @@ void M_Manual(INT32 choice) MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); M_SetupNextMenu(&MISC_ManualDef, true); } + +struct challengesmenu_s challengesmenu; + +void M_ChallengesTick(void) +{ + challengesmenu.ticker++; + + if (challengesmenu.pending && challengesmenu.currentunlock >= MAXUNLOCKABLES) + { + if ((challengesmenu.currentunlock = M_GetNextAchievedUnlock(true)) >= MAXUNLOCKABLES) + challengesmenu.pending = false; + } + else if (challengesmenu.unlockanim >= UNLOCKTIME) + { + ; + } + else + { + challengesmenu.unlockanim++; + } +} + +boolean M_ChallengesInputs(INT32 ch) +{ + const UINT8 pid = 0; + boolean start = M_MenuButtonPressed(pid, MBT_START); + (void) ch; + + if (challengesmenu.unlockanim < UNLOCKTIME) + { + ; + } + else if (challengesmenu.pending) + { + if ((M_MenuConfirmPressed(pid) || start)) + { + challengesmenu.currentunlock = MAXUNLOCKABLES; + challengesmenu.unlockanim = 0; + } + return true; + } + else if (M_MenuBackPressed(pid) || start) + { + M_GoBack(0); + M_SetMenuDelay(pid); + return true; + } + + return true; +} diff --git a/src/m_cond.c b/src/m_cond.c index 79dd92d2a..68ec3e206 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -261,12 +261,12 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) continue; } - if ((gamedata->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1)) == false) + if (gamedata->unlocked[i] == true) { continue; } - if (unlockables[i].nocecho) + if (M_Achieved(unlockables[i].conditionset - 1) == false) { continue; } @@ -281,6 +281,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) // Announce if (cechoLines && loud) { + strcat(cechoText, "Return to main menu to see"); #ifdef DEVELOP // todo make debugmode CONS_Printf("%s\n", cechoText); @@ -290,6 +291,39 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) return false; } +UINT8 M_GetNextAchievedUnlock(boolean set) +{ + UINT8 i; + + // Go through unlockables + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + if (gamedata->unlocked[i] || !unlockables[i].conditionset) + { + continue; + } + + if (gamedata->unlocked[i] == true) + { + continue; + } + + if (M_Achieved(unlockables[i].conditionset - 1) == false) + { + continue; + } + + if (set) + { + gamedata->unlocked[i] = true; + } + + return i; + } + + return MAXUNLOCKABLES; +} + // Emblem unlocking shit UINT8 M_CheckLevelEmblems(void) { diff --git a/src/m_cond.h b/src/m_cond.h index 4df268277..e60ae6f31 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -182,6 +182,7 @@ void M_ClearSecrets(void); void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); boolean M_UpdateUnlockablesAndExtraEmblems(boolean silent); +UINT8 M_GetNextAchievedUnlock(boolean set); UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); From f0e5e1b71affaa0f9f0bd697aa0e6737aa0ffd1b Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 30 Nov 2022 19:27:26 +0000 Subject: [PATCH 13/95] Save gamedata once all pending unlocks have been updated --- src/k_menufunc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index a696a8eb8..84070b3d4 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6831,7 +6831,10 @@ void M_ChallengesTick(void) if (challengesmenu.pending && challengesmenu.currentunlock >= MAXUNLOCKABLES) { if ((challengesmenu.currentunlock = M_GetNextAchievedUnlock(true)) >= MAXUNLOCKABLES) + { challengesmenu.pending = false; + G_SaveGameData(); + } } else if (challengesmenu.unlockanim >= UNLOCKTIME) { From 1391cbe01e4363d5b85e7a4579c9ac53c9f33e0a Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 1 Dec 2022 16:04:12 +0000 Subject: [PATCH 14/95] M_PopulateChallengeGrid Basic challenge grid data is now generated the first time you head to the challenges menu. - Large tiles are placed first. - `UINT8 majorunlock` in `unlockable_t`. - Builds a list of all possible positions the first large tile could be plonked at. - Randomly selects from that list, then removes every position that overlaps the given spot before the next large tile is handled. - Smaller tiles are filled into all the remaining gaps. - Currently bubbles gaps through the random list if empty spots after large tile placement > number of small tiles to place, but all the gaps could be forced to the end. - Has a REALLY prelim drawer, literally just enough to confirm the tilegrid data is correct visually. - DEVELOP: Can be regenerated by pressing (C) while the challenges are up. Also, general maintenance. - Remove `showconditionset`, `nocecho`, and `nochecklist` from `unlockable_t` for not fitting with our new intent for challenges - Remove M_AnySecretUnlocked - Not currently used, but its eventual use - stopping a player from seeing a completely blank challenges grid - isn't in the spirit. - M_MapLocked no longer permits all map transitions in DEVELOP, so unlocks can actually be tested. --- src/deh_soc.c | 8 +- src/g_game.c | 30 +++++++ src/k_menudraw.c | 29 +++++-- src/k_menufunc.c | 13 ++++ src/m_cond.c | 199 ++++++++++++++++++++++++++++++++++++++++++----- src/m_cond.h | 12 ++- 6 files changed, 255 insertions(+), 36 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 74a0920e4..5c01f5d43 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2336,12 +2336,8 @@ void readunlockable(MYFILE *f, INT32 num) if (fastcmp(word, "CONDITIONSET")) unlockables[num].conditionset = (UINT8)i; - else if (fastcmp(word, "SHOWCONDITIONSET")) - unlockables[num].showconditionset = (UINT8)i; - else if (fastcmp(word, "NOCECHO")) - unlockables[num].nocecho = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); - else if (fastcmp(word, "NOCHECKLIST")) - unlockables[num].nochecklist = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); + else if (fastcmp(word, "MAJORUNLOCK")) + unlockables[num].majorunlock = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); else if (fastcmp(word, "TYPE")) { if (fastcmp(word2, "NONE")) diff --git a/src/g_game.c b/src/g_game.c index fcdc4b628..e2f0524ee 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4411,6 +4411,23 @@ void G_LoadGameData(void) i += j; } + gamedata->challengegridwidth = READUINT16(save_p); + Z_Free(gamedata->challengegrid); + if (gamedata->challengegridwidth) + { + gamedata->challengegrid = Z_Malloc( + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), + PU_STATIC, NULL); + for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) + { + gamedata->challengegrid[i] = READUINT8(save_p); + } + } + else + { + gamedata->challengegrid = NULL; + } + gamedata->timesBeaten = READUINT32(save_p); // Main records @@ -4554,6 +4571,19 @@ void G_SaveGameData(void) i += j; } + if (gamedata->challengegrid) + { + WRITEUINT16(save_p, gamedata->challengegridwidth); + for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) + { + WRITEUINT8(save_p, gamedata->challengegrid[i]); + } + } + else + { + WRITEUINT16(save_p, 0); + } + WRITEUINT32(save_p, gamedata->timesBeaten); // 4 // Main records diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 48652c6f9..d2093c970 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4460,7 +4460,8 @@ void M_DrawAddons(void) void M_DrawChallenges(void) { - INT32 x, y; + INT32 x = 20, y = 20; + UINT8 i, j, unlock; { patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); @@ -4469,16 +4470,34 @@ void M_DrawChallenges(void) if (challengesmenu.currentunlock < MAXUNLOCKABLES) { - V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name); + V_DrawThinString(x, y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name); if (challengesmenu.unlockanim >= UNLOCKTIME) - V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (A)"); + V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (A)"); } else { - V_DrawThinString(currentMenu->x, currentMenu->y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F')); + V_DrawThinString(x, y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F')); if (challengesmenu.unlockanim >= UNLOCKTIME) - V_DrawThinString(currentMenu->x, currentMenu->y + 10, V_ALLOWLOWERCASE, "Press (B)"); + V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (B)"); + } + + x = currentMenu->x; + y = currentMenu->y; + + for (i = 0; i < gamedata->challengegridwidth; i++) + { + for (j = 0; j < CHALLENGEGRIDHEIGHT; j++) + { + unlock = gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + j]; + // very WIP render of tilegrid + if (unlock >= MAXUNLOCKABLES) + V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, "."); + else if (gamedata->unlocked[unlock] == false) + V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, "?"); + else + V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, va("%c", unlockables[unlock].name[0])); + } } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 84070b3d4..89bd748f2 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -781,8 +781,10 @@ static boolean M_PrevOpt(void) if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES) { + MISC_ChallengesDef.prevMenu = desiredmenu; challengesmenu.pending = true; challengesmenu.currentunlock = MAXUNLOCKABLES; + M_PopulateChallengeGrid(); return &MISC_ChallengesDef; } @@ -6856,6 +6858,17 @@ boolean M_ChallengesInputs(INT32 ch) { ; } +#ifdef DEVELOP + else if (M_MenuExtraPressed(pid)) // debugging + { + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + M_PopulateChallengeGrid(); + challengesmenu.unlockanim = 0; + return true; + } +#endif else if (challengesmenu.pending) { if ((M_MenuConfirmPressed(pid) || start)) diff --git a/src/m_cond.c b/src/m_cond.c index 68ec3e206..4d33a0a05 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -11,6 +11,7 @@ /// \brief Unlockable condition system for SRB2 version 2.1 #include "m_cond.h" +#include "m_random.h" // M_RandomKey #include "doomstat.h" #include "z_zone.h" @@ -54,6 +55,179 @@ void M_NewGameDataStruct(void) G_ClearRecords(); } +void M_PopulateChallengeGrid(void) +{ + UINT16 i, j; + UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0; + UINT8 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)]; + + if (gamedata->challengegrid != NULL) + { + // todo tweak your grid if unlocks are changed + return; + } + + // Go through unlockables + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + if (!unlockables[i].conditionset) + { + continue; + } + + if (unlockables[i].majorunlock) + { + selection[1][nummajorunlocks++] = i; + //CONS_Printf(" found %d (LARGE)\n", selection[1][nummajorunlocks-1]); + continue; + } + + selection[0][numunlocks++] = i; + //CONS_Printf(" found %d\n", selection[0][numunlocks-1]); + } + + if (numunlocks + nummajorunlocks == 0) + { + gamedata->challengegridwidth = 0; + return; + } + + gamedata->challengegridwidth = (numunlocks + (nummajorunlocks * 4) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + + if (nummajorunlocks) + { + // Getting the number of 2-highs you can fit into two adjacent columns. + UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); + majorpad = (nummajorunlocks/majorpad)+1; + if (gamedata->challengegridwidth < majorpad*2) + gamedata->challengegridwidth = majorpad*2; + } + + gamedata->challengegrid = Z_Malloc( + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), + PU_STATIC, NULL); + + memset(gamedata->challengegrid, + MAXUNLOCKABLES, + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8))); + + // Attempt to place all large tiles first. + if (nummajorunlocks) + { + // You lose one from CHALLENGEGRIDHEIGHT because it is impossible to place a 2-high tile on the bottom row. + UINT16 numspots = gamedata->challengegridwidth * (CHALLENGEGRIDHEIGHT-1); + // 0 is row, 1 is column + UINT8 quickcheck[numspots][2]; + + // Prepare the easy-grab spots. + for (i = 0; i < numspots; i++) + { + quickcheck[i][0] = i%(CHALLENGEGRIDHEIGHT-1); + quickcheck[i][1] = i/(CHALLENGEGRIDHEIGHT-1); + } + + // Place in random valid locations. + while (nummajorunlocks > 0 && numspots > 0) + { + UINT8 row, col; + j = M_RandomKey(numspots); + row = quickcheck[j][0]; + col = quickcheck[j][1]; + + // We always take from selection[1][] in order, but the PLACEMENT is still random. + nummajorunlocks--; + + //CONS_Printf("--- %d (LARGE) placed at (%d, %d)\n", selection[1][nummajorunlocks], row, col); + + i = row + (col * CHALLENGEGRIDHEIGHT); + gamedata->challengegrid[i] = gamedata->challengegrid[i+1] = selection[1][nummajorunlocks]; + if (col == gamedata->challengegridwidth-1) + { + i = row; + } + else + { + i += CHALLENGEGRIDHEIGHT; + } + gamedata->challengegrid[i] = gamedata->challengegrid[i+1] = selection[1][nummajorunlocks]; + + if (nummajorunlocks == 0) + { + break; + } + + for (i = 0; i < numspots; i++) + { +quickcheckagain: + if (abs(((signed)quickcheck[i][0]) - ((signed)row)) <= 1 // Row distance + || abs(((signed)quickcheck[i][1]) - ((signed)col)) <= 1 // Column distance + || (quickcheck[i][1] == 0 && col == gamedata->challengegridwidth-1) // Wraparounds l->r + || (quickcheck[i][1] == gamedata->challengegridwidth-1 && col == 0)) // Wraparounds r->l + { + numspots--; // Remove from possible indicies + if (i == numspots) + break; + // Shuffle remaining so we can keep on using M_RandomKey + quickcheck[i][0] = quickcheck[numspots][0]; + quickcheck[i][1] = quickcheck[numspots][1]; + // Woah there - we've gotta check the one that just got put in our place. + goto quickcheckagain; + } + continue; + } + } + + if (nummajorunlocks > 0) + { + I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles", nummajorunlocks); + } + } + + // Space out empty entries to pepper into unlock list + for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++) + { + if (gamedata->challengegrid[i] != MAXUNLOCKABLES) + { + continue; + } + + numempty++; + } + + if (numunlocks > numempty) + { + I_Error("M_PopulateChallengeGrid: %d small unlocks vs %d empty spaces (%d gap)", numunlocks, numempty, (numunlocks-numempty)); + } + + //CONS_Printf(" %d unlocks vs %d empty spaces\n", numunlocks, numempty); + + while (numunlocks < numempty) + { + //CONS_Printf(" adding empty)\n"); + selection[0][numunlocks++] = MAXUNLOCKABLES; + } + + // Fill the remaining spots with random ordinary unlocks (and empties). + for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++) + { + if (gamedata->challengegrid[i] != MAXUNLOCKABLES) + { + continue; + } + + j = M_RandomKey(numunlocks); // Get an entry + gamedata->challengegrid[i] = selection[0][j]; // Set that entry + //CONS_Printf(" %d placed at (%d, %d)\n", selection[0][j], i/CHALLENGEGRIDHEIGHT, i%CHALLENGEGRIDHEIGHT); + numunlocks--; // Remove from possible indicies + selection[0][j] = selection[0][numunlocks]; // Shuffle remaining so we can keep on using M_RandomKey + + if (numunlocks == 0) + { + break; + } + } +} + void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) { condition_t *cond; @@ -105,6 +279,10 @@ void M_ClearSecrets(void) for (i = 0; i < MAXCONDITIONSETS; ++i) gamedata->achieved[i] = false; + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + gamedata->timesBeaten = 0; // Re-unlock any always unlocked things @@ -406,22 +584,6 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa // ------------------- // Quick unlock checks // ------------------- -UINT8 M_AnySecretUnlocked(void) -{ - INT32 i; - -#ifdef DEVELOP - if (1) - return true; -#endif - - for (i = 0; i < MAXUNLOCKABLES; ++i) - { - if (!unlockables[i].nocecho && gamedata->unlocked[i]) - return true; - } - return false; -} UINT8 M_SecretUnlocked(INT32 type) { @@ -455,10 +617,6 @@ UINT8 M_SecretUnlocked(INT32 type) UINT8 M_MapLocked(INT32 mapnum) { -#ifdef DEVELOP - (void)mapnum; - return false; -#else // Don't lock maps in dedicated servers. // That just makes hosts' lives hell. if (dedicated) @@ -471,7 +629,6 @@ UINT8 M_MapLocked(INT32 mapnum) return true; return false; -#endif } INT32 M_CountEmblems(void) diff --git a/src/m_cond.h b/src/m_cond.h index e60ae6f31..54f266859 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -94,12 +94,10 @@ typedef struct char name[64]; char objective[64]; UINT8 conditionset; - UINT8 showconditionset; INT16 type; INT16 variable; char *stringVar; - UINT8 nocecho; - UINT8 nochecklist; + UINT8 majorunlock; } unlockable_t; #define SECRET_NONE 0 // Does nil. Use with levels locked by UnlockRequired @@ -130,6 +128,8 @@ typedef struct #define MAXEXTRAEMBLEMS 16 #define MAXUNLOCKABLES 32 +#define CHALLENGEGRIDHEIGHT 5 + // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat typedef struct @@ -149,6 +149,10 @@ typedef struct // UNLOCKABLES UNLOCKED boolean unlocked[MAXUNLOCKABLES]; + // CHALLENGE GRID + UINT16 challengegridwidth; + UINT8 *challengegrid; + // # OF TIMES THE GAME HAS BEEN BEATEN UINT32 timesBeaten; @@ -170,6 +174,7 @@ extern INT32 numextraemblems; extern UINT32 unlocktriggers; void M_NewGameDataStruct(void); +void M_PopulateChallengeGrid(void); // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); @@ -187,7 +192,6 @@ UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); // Checking unlockable status -UINT8 M_AnySecretUnlocked(void); UINT8 M_SecretUnlocked(INT32 type); UINT8 M_MapLocked(INT32 mapnum); INT32 M_CountEmblems(void); From 8dc2b6bf8102b0ebd6d5db143638dc6de02ad7b3 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 1 Dec 2022 16:57:30 +0000 Subject: [PATCH 15/95] I_Error on failed challengegrid malloc --- src/m_cond.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/m_cond.c b/src/m_cond.c index 4d33a0a05..02e86b018 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -107,6 +107,11 @@ void M_PopulateChallengeGrid(void) (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), PU_STATIC, NULL); + if (!gamedata->challengegrid) + { + I_Error("M_PopulateChallengeGrid: was not able to allocate grid"); + } + memset(gamedata->challengegrid, MAXUNLOCKABLES, (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8))); From b8674e35e15b14b6d4a0ea6c07ca5668d0493656 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 1 Dec 2022 17:47:52 +0000 Subject: [PATCH 16/95] Challenge tilegrid rendering fundamentals now down - Shows 16x16 or 32x32 grey areas of checkerboarding brightness for locked - Shows SECRET_SKIN face icons when a challenge of that type is unlocked - Don't re-render if entry above or to the left is also that challenge (`majorunlock`) --- src/k_menudraw.c | 82 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d2093c970..47705e6bb 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4461,7 +4461,8 @@ void M_DrawAddons(void) void M_DrawChallenges(void) { INT32 x = 20, y = 20; - UINT8 i, j, unlock; + UINT8 i, j, num; + unlockable_t *ref = NULL; { patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); @@ -4488,16 +4489,79 @@ void M_DrawChallenges(void) for (i = 0; i < gamedata->challengegridwidth; i++) { + y = currentMenu->y-16; for (j = 0; j < CHALLENGEGRIDHEIGHT; j++) { - unlock = gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + j]; - // very WIP render of tilegrid - if (unlock >= MAXUNLOCKABLES) - V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, "."); - else if (gamedata->unlocked[unlock] == false) - V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, "?"); - else - V_DrawString(x + 10*i, y + 10*j, V_ALLOWLOWERCASE, va("%c", unlockables[unlock].name[0])); + y += 16; + num = gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + j]; + + // Empty spots in the grid are always unconnected. + if (num >= MAXUNLOCKABLES) + { + V_DrawFill(x, y, 16, 16, 27); + continue; + } + + // Is the spot above me also me? + if (j > 0 && gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + (j - 1)] == num) + { + continue; + } + + // Is the spot to the left of me also me? + if (i > 0) + { + // Standard + if (gamedata->challengegrid[((i - 1) * CHALLENGEGRIDHEIGHT) + j] == num) + { + continue; + } + } + else if (gamedata->challengegridwidth > 2) + { + // Conditional modulo + if (gamedata->challengegrid[((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j] == num) + { + continue; + } + } + + // Okay, this is what we want to draw. + ref = &unlockables[num]; + + // ...unless we simply aren't unlocked yet. + if ((gamedata->unlocked[num] == false) + || (num == challengesmenu.currentunlock && challengesmenu.unlockanim < UNLOCKTIME/2)) + { + num = (ref->majorunlock) ? 2 : 1; + V_DrawFill(x, y, 16*num, 16*num, + ((i & num) != (j & num) ? 12 : 14) + (num-1)*4); // funny checkerboard pattern + continue; + } + + switch (ref->type) + { + case SECRET_SKIN: + { + INT32 skin = M_UnlockableSkinNum(ref); + if (skin != -1) + { + UINT8 *colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + UINT8 size = (ref->majorunlock) ? FACE_WANTED : FACE_RANK; + V_DrawMappedPatch(x, y, 0, faceprefix[skin][size], colormap); + } + else + { + V_DrawPatch(x, y, 0, missingpat); + } + + break; + } + default: + V_DrawString(x, y, V_ALLOWLOWERCASE, va("%c", ref->name[0])); + break; + } } + x += 16; } } From 85160b1dc12dc5ffd2ca3b18116bcb889486b595 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 1 Dec 2022 21:55:05 +0000 Subject: [PATCH 17/95] M_ChallengeGridExtraData Precalculates adjacency rules for challengegrid instead of doing it in the drawer - Simplifies `M_DrawChallenges` a lot, at the expense of a pretty complicated function living in `m_cond.c` - Handles registering entries both not to be drawn (for non-primary components of `majorunlock` unlockables) and for hint highlights (new for this commit) - Hint highlights are a sky blue --- src/k_menu.h | 2 + src/k_menudraw.c | 48 ++++++-------- src/k_menufunc.c | 8 +++ src/m_cond.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++ src/m_cond.h | 4 ++ 5 files changed, 197 insertions(+), 29 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 5a314ab0a..6ee3e6c4d 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1091,6 +1091,8 @@ extern struct challengesmenu_s { UINT8 currentunlock; tic_t unlockanim; + UINT8 *extradata; + boolean pending; } challengesmenu; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 47705e6bb..784b2eaba 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4461,7 +4461,7 @@ void M_DrawAddons(void) void M_DrawChallenges(void) { INT32 x = 20, y = 20; - UINT8 i, j, num; + UINT8 i, j, id, num, work; unlockable_t *ref = NULL; { @@ -4487,13 +4487,26 @@ void M_DrawChallenges(void) x = currentMenu->x; y = currentMenu->y; + if (!gamedata->challengegrid) + { + V_DrawString(x, y, V_REDMAP, "No challenges available!?"); + return; + } + for (i = 0; i < gamedata->challengegridwidth; i++) { y = currentMenu->y-16; for (j = 0; j < CHALLENGEGRIDHEIGHT; j++) { y += 16; - num = gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + j]; + id = (i * CHALLENGEGRIDHEIGHT) + j; + + if (challengesmenu.extradata[id] == CHE_DONTDRAW) + { + continue; + } + + num = gamedata->challengegrid[id]; // Empty spots in the grid are always unconnected. if (num >= MAXUNLOCKABLES) @@ -4502,30 +4515,6 @@ void M_DrawChallenges(void) continue; } - // Is the spot above me also me? - if (j > 0 && gamedata->challengegrid[(i * CHALLENGEGRIDHEIGHT) + (j - 1)] == num) - { - continue; - } - - // Is the spot to the left of me also me? - if (i > 0) - { - // Standard - if (gamedata->challengegrid[((i - 1) * CHALLENGEGRIDHEIGHT) + j] == num) - { - continue; - } - } - else if (gamedata->challengegridwidth > 2) - { - // Conditional modulo - if (gamedata->challengegrid[((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j] == num) - { - continue; - } - } - // Okay, this is what we want to draw. ref = &unlockables[num]; @@ -4533,9 +4522,10 @@ void M_DrawChallenges(void) if ((gamedata->unlocked[num] == false) || (num == challengesmenu.currentunlock && challengesmenu.unlockanim < UNLOCKTIME/2)) { - num = (ref->majorunlock) ? 2 : 1; - V_DrawFill(x, y, 16*num, 16*num, - ((i & num) != (j & num) ? 12 : 14) + (num-1)*4); // funny checkerboard pattern + work = (ref->majorunlock) ? 2 : 1; + V_DrawFill(x, y, 16*work, 16*work, + ((challengesmenu.extradata[id] == CHE_HINT) ? 130 : 12) + + ((i & work) != (j & work) ? 0 : 2) + (work-1)); // funny checkerboard pattern continue; } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 89bd748f2..abba32891 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6837,6 +6837,8 @@ void M_ChallengesTick(void) challengesmenu.pending = false; G_SaveGameData(); } + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); } else if (challengesmenu.unlockanim >= UNLOCKTIME) { @@ -6865,6 +6867,8 @@ boolean M_ChallengesInputs(INT32 ch) gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; M_PopulateChallengeGrid(); + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); challengesmenu.unlockanim = 0; return true; } @@ -6882,6 +6886,10 @@ boolean M_ChallengesInputs(INT32 ch) { M_GoBack(0); M_SetMenuDelay(pid); + + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = NULL; + return true; } diff --git a/src/m_cond.c b/src/m_cond.c index 02e86b018..edad6b4c5 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -233,6 +233,170 @@ quickcheckagain: } } +UINT8 *M_ChallengeGridExtraData(void) +{ + UINT8 i, j, num, id, tempid, work; + UINT8 *extradata; + boolean idchange; + + if (!gamedata->challengegrid) + { + return NULL; + } + + extradata = Z_Malloc( + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)), + PU_STATIC, NULL); + + if (!extradata) + { + I_Error("M_ChallengeGridExtraData: was not able to allocate extradata"); + } + + //CONS_Printf(" --- \n"); + + for (i = 0; i < gamedata->challengegridwidth; i++) + { + for (j = 0; j < CHALLENGEGRIDHEIGHT; j++) + { + id = (i * CHALLENGEGRIDHEIGHT) + j; + num = gamedata->challengegrid[id]; + idchange = false; + + extradata[id] = CHE_NONE; + + // Empty spots in the grid are always unconnected. + if (num >= MAXUNLOCKABLES) + { + continue; + } + + // Check the spot above. + if (j > 0) + { + tempid = (i * CHALLENGEGRIDHEIGHT) + (j - 1); + work = gamedata->challengegrid[tempid]; + if (work == num) + { + extradata[id] = CHE_DONTDRAW; + + // Get the id to write extra hint data to. + // This check is safe because extradata's order of population + if (extradata[tempid] == CHE_DONTDRAW) + { + //CONS_Printf(" %d - %d above %d is invalid, check to left\n", num, tempid, id); + if (i > 0) + { + tempid -= CHALLENGEGRIDHEIGHT; + } + else + { + tempid = ((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j - 1; + } + } + /*else + CONS_Printf(" %d - %d above %d is valid\n", num, tempid, id);*/ + + id = tempid; + idchange = true; + + if (extradata[id] == CHE_HINT) + { + continue; + } + } + else if (work < MAXUNLOCKABLES && gamedata->unlocked[work]) + { + extradata[id] = CHE_HINT; + } + } + + // Check the spot to the left. + { + if (i > 0) + { + tempid = ((i - 1) * CHALLENGEGRIDHEIGHT) + j; + } + else + { + tempid = ((gamedata->challengegridwidth - 1) * CHALLENGEGRIDHEIGHT) + j; + } + work = gamedata->challengegrid[tempid]; + + if (work == num) + { + if (!idchange && (i > 0 || gamedata->challengegridwidth > 2)) + { + //CONS_Printf(" %d - %d to left of %d is valid\n", work, tempid, id); + // If we haven't already updated our id, it's the one to our left. + if (extradata[id] == CHE_HINT) + { + extradata[tempid] = CHE_HINT; + } + extradata[id] = CHE_DONTDRAW; + id = tempid; + } + /*else + CONS_Printf(" %d - %d to left of %d is invalid\n", work, tempid, id);*/ + } + else if (work < MAXUNLOCKABLES && gamedata->unlocked[work]) + { + extradata[id] = CHE_HINT; + continue; + } + } + + // Since we're not modifying id past this point, the conditions become much simpler. + if (extradata[id] == CHE_HINT) + { + continue; + } + + // Check the spot below. + if (j < CHALLENGEGRIDHEIGHT-1) + { + tempid = (i * CHALLENGEGRIDHEIGHT) + (j + 1); + work = gamedata->challengegrid[tempid]; + + if (work == num) + { + ; + } + else if (work < MAXUNLOCKABLES && gamedata->unlocked[work]) + { + extradata[id] = CHE_HINT; + continue; + } + } + + // Check the spot to the right. + { + if (i < (gamedata->challengegridwidth - 1)) + { + tempid = ((i + 1) * CHALLENGEGRIDHEIGHT) + j; + } + else + { + tempid = j; + } + work = gamedata->challengegrid[tempid]; + + if (work == num) + { + ; + } + else if (work < MAXUNLOCKABLES && gamedata->unlocked[work]) + { + extradata[id] = CHE_HINT; + continue; + } + } + } + } + + return extradata; +} + void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) { condition_t *cond; diff --git a/src/m_cond.h b/src/m_cond.h index 54f266859..1447e2206 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -175,6 +175,10 @@ extern UINT32 unlocktriggers; void M_NewGameDataStruct(void); void M_PopulateChallengeGrid(void); +UINT8 *M_ChallengeGridExtraData(void); +#define CHE_NONE 0 +#define CHE_HINT 1 +#define CHE_DONTDRAW 2 // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); From ac15d4caa31b7d08efc1076b5af4aa2d99943742 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 2 Dec 2022 20:13:52 +0000 Subject: [PATCH 18/95] Icon selection + Extras menu access * Icon selection * You can use up, down, left, right, etc to select icons on the Challenges menu * The text at the top changes based on the highlighted icon * This text is now ??? if not yet unlocked * Uses the border for character rank icons * Challenges are now accessible on the Extras menu alongside Addons and Replay Hut. * Previously there was a "Extras Checklist" dummy which did nothing when selected * Now I won't have to clear my gamedata every time I want to check this menu! * M_ChallengeGridExtraData's array is modified to now contain specific info on whether a given tile is connected to above or left (or both). --- src/k_hud.c | 2 +- src/k_hud.h | 2 + src/k_menu.h | 4 + src/k_menudef.c | 6 +- src/k_menudraw.c | 44 +++++--- src/k_menufunc.c | 288 ++++++++++++++++++++++++++++++++++++++++++----- src/m_cond.c | 7 +- src/m_cond.h | 8 +- 8 files changed, 311 insertions(+), 50 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 73625dba7..5037132d8 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -69,7 +69,7 @@ static patch_t *kp_racefinish[6]; static patch_t *kp_positionnum[10][2][2]; // number, overlay or underlay, splitscreen static patch_t *kp_facenum[MAXPLAYERS+1]; -static patch_t *kp_facehighlight[8]; +patch_t *kp_facehighlight[8]; static patch_t *kp_nocontestminimap; static patch_t *kp_spbminimap; diff --git a/src/k_hud.h b/src/k_hud.h index 4f84f9dd3..7f8e37cf6 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -39,4 +39,6 @@ void K_drawKartFreePlay(void); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol); +extern patch_t *kp_facehighlight[8]; + #endif diff --git a/src/k_menu.h b/src/k_menu.h index 6ee3e6c4d..7156e07c5 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1091,11 +1091,15 @@ extern struct challengesmenu_s { UINT8 currentunlock; tic_t unlockanim; + UINT8 row, col, hilix, hiliy; + UINT8 *extradata; boolean pending; } challengesmenu; +menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu); +void M_Challenges(INT32 choice); void M_DrawChallenges(void); void M_ChallengesTick(void); boolean M_ChallengesInputs(INT32 ch); diff --git a/src/k_menudef.c b/src/k_menudef.c index 9e10560a3..f355a3e34 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1496,14 +1496,14 @@ menuitem_t EXTRAS_Main[] = {IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.", NULL, {.routine = M_Addons}, 0, 0}, + {IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!", + NULL, {.routine = M_Challenges}, 0, 0}, + {IT_STRING | IT_CALL, "Replay Hut", "Play the replays you've saved throughout your many races & battles!", NULL, {.routine = M_ReplayHut}, 0, 0}, {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", NULL, {NULL}, 0, 0}, - - {IT_STRING | IT_TRANSTEXT, "Extras Checklist", "View the requirement for some of the secret content you can unlock!", - NULL, {NULL}, 0, 0}, }; // the extras menu essentially reuses the options menu stuff diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 784b2eaba..dcec6b86c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -600,10 +600,10 @@ static void M_DrawMenuTooltips(void) // Used for the secrets menu, to hide yet-to-be-unlocked stuff. static const char *M_CreateSecretMenuOption(const char *str) { - static char qbuf[32]; + static char qbuf[64]; int i; - for (i = 0; i < 31; ++i) + for (i = 0; i < 63; ++i) { if (!str[i]) { @@ -616,7 +616,7 @@ static const char *M_CreateSecretMenuOption(const char *str) qbuf[i] = ' '; } - qbuf[31] = '\0'; + qbuf[63] = '\0'; return qbuf; } @@ -4471,19 +4471,21 @@ void M_DrawChallenges(void) if (challengesmenu.currentunlock < MAXUNLOCKABLES) { - V_DrawThinString(x, y, V_ALLOWLOWERCASE, unlockables[challengesmenu.currentunlock].name); - - if (challengesmenu.unlockanim >= UNLOCKTIME) - V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (A)"); + const char *str = unlockables[challengesmenu.currentunlock].name; + if (!gamedata->unlocked[challengesmenu.currentunlock]) + { + str = "???"; //M_CreateSecretMenuOption(str); + } + V_DrawThinString(x, y, V_ALLOWLOWERCASE, str); } else { - V_DrawThinString(x, y, V_ALLOWLOWERCASE, va("pending = %c", challengesmenu.pending ? 'T' : 'F')); - - if (challengesmenu.unlockanim >= UNLOCKTIME) - V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, "Press (B)"); + V_DrawThinString(x, y, V_ALLOWLOWERCASE, "---"); } + if (challengesmenu.unlockanim >= UNLOCKTIME) + V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, va("Press (%c)", challengesmenu.pending ? 'A' : 'B')); + x = currentMenu->x; y = currentMenu->y; @@ -4501,7 +4503,7 @@ void M_DrawChallenges(void) y += 16; id = (i * CHALLENGEGRIDHEIGHT) + j; - if (challengesmenu.extradata[id] == CHE_DONTDRAW) + if (challengesmenu.extradata[id] & CHE_DONTDRAW) { continue; } @@ -4512,7 +4514,8 @@ void M_DrawChallenges(void) if (num >= MAXUNLOCKABLES) { V_DrawFill(x, y, 16, 16, 27); - continue; + ref = NULL; + goto drawborder; } // Okay, this is what we want to draw. @@ -4526,7 +4529,7 @@ void M_DrawChallenges(void) V_DrawFill(x, y, 16*work, 16*work, ((challengesmenu.extradata[id] == CHE_HINT) ? 130 : 12) + ((i & work) != (j & work) ? 0 : 2) + (work-1)); // funny checkerboard pattern - continue; + goto drawborder; } switch (ref->type) @@ -4551,6 +4554,19 @@ void M_DrawChallenges(void) V_DrawString(x, y, V_ALLOWLOWERCASE, va("%c", ref->name[0])); break; } + +drawborder: + if (i != challengesmenu.hilix) + continue; + if (j != challengesmenu.hiliy) + continue; + + V_DrawFixedPatch( + x*FRACUNIT, y*FRACUNIT, + ((ref != NULL && ref->majorunlock) ? FRACUNIT*2 : FRACUNIT), + 0, kp_facehighlight[(challengesmenu.ticker / 4) % 8], + NULL + ); } x += 16; } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index abba32891..f8e80bb15 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -775,22 +775,6 @@ static boolean M_PrevOpt(void) return true; } - static menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) -{ - M_UpdateUnlockablesAndExtraEmblems(false); - - if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES) - { - MISC_ChallengesDef.prevMenu = desiredmenu; - challengesmenu.pending = true; - challengesmenu.currentunlock = MAXUNLOCKABLES; - M_PopulateChallengeGrid(); - return &MISC_ChallengesDef; - } - - return desiredmenu; -} - // // M_Responder // @@ -6826,19 +6810,122 @@ void M_Manual(INT32 choice) struct challengesmenu_s challengesmenu; +menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) +{ + M_UpdateUnlockablesAndExtraEmblems(false); + + if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES) + { + challengesmenu.pending = true; + MISC_ChallengesDef.prevMenu = desiredmenu; + } + + if (challengesmenu.pending || desiredmenu == NULL) + { + challengesmenu.currentunlock = MAXUNLOCKABLES; + M_PopulateChallengeGrid(); + return &MISC_ChallengesDef; + } + + return desiredmenu; +} + +void M_Challenges(INT32 choice) +{ + UINT8 i; + (void)choice; + + M_InterruptMenuWithChallenges(NULL); + MISC_ChallengesDef.prevMenu = currentMenu; + + if (gamedata->challengegrid) + { + UINT8 selection[MAXUNLOCKABLES]; + UINT8 numunlocks = 0; + + // Get a random available unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + if (!gamedata->unlocked[i]) + { + continue; + } + + selection[numunlocks++] = i; + } + + if (!numunlocks) + { + // ...OK, get a random unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + selection[numunlocks++] = i; + } + } + + challengesmenu.currentunlock = selection[M_RandomKey(numunlocks)]; + + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) + { + if (gamedata->challengegrid[i] != challengesmenu.currentunlock) + { + continue; + } + + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; + challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + break; + } + } + + M_SetupNextMenu(&MISC_ChallengesDef, false); +} + void M_ChallengesTick(void) { + UINT8 i, newunlock = MAXUNLOCKABLES; + challengesmenu.ticker++; if (challengesmenu.pending && challengesmenu.currentunlock >= MAXUNLOCKABLES) { - if ((challengesmenu.currentunlock = M_GetNextAchievedUnlock(true)) >= MAXUNLOCKABLES) + if ((newunlock = M_GetNextAchievedUnlock(true)) < MAXUNLOCKABLES) + { + challengesmenu.currentunlock = newunlock; + challengesmenu.unlockanim = 0; + + if (gamedata->challengegrid) + { + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) + { + if (gamedata->challengegrid[i] != challengesmenu.currentunlock) + { + continue; + } + + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; + challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + break; + } + } + } + else { challengesmenu.pending = false; G_SaveGameData(); } Z_Free(challengesmenu.extradata); - challengesmenu.extradata = M_ChallengeGridExtraData(); + challengesmenu.extradata = NULL; } else if (challengesmenu.unlockanim >= UNLOCKTIME) { @@ -6848,14 +6935,25 @@ void M_ChallengesTick(void) { challengesmenu.unlockanim++; } + + if (challengesmenu.extradata == NULL) + { + challengesmenu.extradata = M_ChallengeGridExtraData(); + } } boolean M_ChallengesInputs(INT32 ch) { const UINT8 pid = 0; - boolean start = M_MenuButtonPressed(pid, MBT_START); + UINT8 i; + const boolean start = M_MenuButtonPressed(pid, MBT_START); + const boolean move = (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr); (void) ch; + if (!challengesmenu.extradata) + { + ; + } if (challengesmenu.unlockanim < UNLOCKTIME) { ; @@ -6870,6 +6968,21 @@ boolean M_ChallengesInputs(INT32 ch) Z_Free(challengesmenu.extradata); challengesmenu.extradata = M_ChallengeGridExtraData(); challengesmenu.unlockanim = 0; + + if (challengesmenu.currentunlock != MAXUNLOCKABLES) + { + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) + { + if (gamedata->challengegrid[i] != challengesmenu.currentunlock) + { + continue; + } + + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; + challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + break; + } + } return true; } #endif @@ -6878,19 +6991,142 @@ boolean M_ChallengesInputs(INT32 ch) if ((M_MenuConfirmPressed(pid) || start)) { challengesmenu.currentunlock = MAXUNLOCKABLES; - challengesmenu.unlockanim = 0; } return true; } - else if (M_MenuBackPressed(pid) || start) + else { - M_GoBack(0); - M_SetMenuDelay(pid); + if (M_MenuBackPressed(pid) || start) + { + M_GoBack(0); + M_SetMenuDelay(pid); - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = NULL; + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = NULL; - return true; + return true; + } + + // Determine movement around the grid + if (move) + { + if (menucmd[pid].dpad_ud > 0) + { + i = 2; + while (i > 0) + { + if (challengesmenu.row < CHALLENGEGRIDHEIGHT-1) + { + challengesmenu.row++; + } + else + { + challengesmenu.row = 0; + } + if (!(challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDUP)) + { + break; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + i = (challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDUP) ? 2 : 1; + while (i > 0) + { + if (challengesmenu.row > 0) + { + challengesmenu.row--; + } + else + { + challengesmenu.row = CHALLENGEGRIDHEIGHT-1; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (menucmd[pid].dpad_lr > 0) + { + i = 2; + while (i > 0) + { + if (challengesmenu.col < gamedata->challengegridwidth-1) + { + challengesmenu.col++; + } + else + { + challengesmenu.col = 0; + } + if (!(challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDLEFT)) + { + break; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + i = (challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDLEFT) ? 2 : 1; + while (i > 0) + { + if (challengesmenu.col > 0) + { + challengesmenu.col--; + } + else + { + challengesmenu.col = gamedata->challengegridwidth-1; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + // After movement has been determined, figure out the current selection. + i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row; + challengesmenu.currentunlock = (gamedata->challengegrid[i]); + + challengesmenu.hilix = challengesmenu.col; + challengesmenu.hiliy = challengesmenu.row; + if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) + { + challengesmenu.hiliy--; + } + + if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)) + { + if (challengesmenu.hilix > 0) + { + challengesmenu.hilix--; + } + else + { + challengesmenu.hilix = gamedata->challengegridwidth-1; + } + } + } } return true; diff --git a/src/m_cond.c b/src/m_cond.c index edad6b4c5..3f9e6e86c 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -278,12 +278,13 @@ UINT8 *M_ChallengeGridExtraData(void) work = gamedata->challengegrid[tempid]; if (work == num) { - extradata[id] = CHE_DONTDRAW; + extradata[id] = CHE_CONNECTEDUP; // Get the id to write extra hint data to. // This check is safe because extradata's order of population - if (extradata[tempid] == CHE_DONTDRAW) + if (extradata[tempid] & CHE_CONNECTEDLEFT) { + extradata[id] |= CHE_CONNECTEDLEFT; //CONS_Printf(" %d - %d above %d is invalid, check to left\n", num, tempid, id); if (i > 0) { @@ -333,7 +334,7 @@ UINT8 *M_ChallengeGridExtraData(void) { extradata[tempid] = CHE_HINT; } - extradata[id] = CHE_DONTDRAW; + extradata[id] = CHE_CONNECTEDLEFT; id = tempid; } /*else diff --git a/src/m_cond.h b/src/m_cond.h index 1447e2206..5f3500813 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -176,9 +176,11 @@ extern UINT32 unlocktriggers; void M_NewGameDataStruct(void); void M_PopulateChallengeGrid(void); UINT8 *M_ChallengeGridExtraData(void); -#define CHE_NONE 0 -#define CHE_HINT 1 -#define CHE_DONTDRAW 2 +#define CHE_NONE 0 +#define CHE_HINT 1 +#define CHE_CONNECTEDLEFT 2 +#define CHE_CONNECTEDUP 4 +#define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP) // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); From 3c915d8f6820fc10c7fb629aea8c5b5389244c1b Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 2 Dec 2022 21:18:47 +0000 Subject: [PATCH 19/95] Fix Challenge Grid population where multiple large tiles should be packed closely together --- src/m_cond.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 3f9e6e86c..d54096870 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -98,7 +98,7 @@ void M_PopulateChallengeGrid(void) { // Getting the number of 2-highs you can fit into two adjacent columns. UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); - majorpad = (nummajorunlocks/majorpad)+1; + majorpad = (nummajorunlocks+1)/majorpad; if (gamedata->challengegridwidth < majorpad*2) gamedata->challengegridwidth = majorpad*2; } @@ -122,7 +122,7 @@ void M_PopulateChallengeGrid(void) // You lose one from CHALLENGEGRIDHEIGHT because it is impossible to place a 2-high tile on the bottom row. UINT16 numspots = gamedata->challengegridwidth * (CHALLENGEGRIDHEIGHT-1); // 0 is row, 1 is column - UINT8 quickcheck[numspots][2]; + INT16 quickcheck[numspots][2]; // Prepare the easy-grab spots. for (i = 0; i < numspots; i++) @@ -134,7 +134,7 @@ void M_PopulateChallengeGrid(void) // Place in random valid locations. while (nummajorunlocks > 0 && numspots > 0) { - UINT8 row, col; + INT16 row, col; j = M_RandomKey(numspots); row = quickcheck[j][0]; col = quickcheck[j][1]; @@ -164,10 +164,10 @@ void M_PopulateChallengeGrid(void) for (i = 0; i < numspots; i++) { quickcheckagain: - if (abs(((signed)quickcheck[i][0]) - ((signed)row)) <= 1 // Row distance - || abs(((signed)quickcheck[i][1]) - ((signed)col)) <= 1 // Column distance + if (abs((quickcheck[i][0]) - (row)) <= 1 // Row distance + && (abs((quickcheck[i][1]) - (col)) <= 1 // Column distance || (quickcheck[i][1] == 0 && col == gamedata->challengegridwidth-1) // Wraparounds l->r - || (quickcheck[i][1] == gamedata->challengegridwidth-1 && col == 0)) // Wraparounds r->l + || (quickcheck[i][1] == gamedata->challengegridwidth-1 && col == 0))) // Wraparounds r->l { numspots--; // Remove from possible indicies if (i == numspots) @@ -184,7 +184,7 @@ quickcheckagain: if (nummajorunlocks > 0) { - I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles", nummajorunlocks); + I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles (width %d)", nummajorunlocks, gamedata->challengegridwidth); } } From 5e76896ac431d959ce73785f19304da21e4498e7 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 2 Dec 2022 21:31:13 +0000 Subject: [PATCH 20/95] Challenge unlock explosions (from character select screen) * Generalised the existing system to work here, no need to copypaste * Spawned on unlock AND on debug button, so you can watch how they propogate * Currently unsure on how large tile does it - right now it just spawns two sets of explosions on topleft and bottomright corners. Also fixed an issue where a large tile could fail to show its highlight border if it looped between the two ends of the challenge grid --- src/k_menu.h | 4 +-- src/k_menudraw.c | 21 +++++++----- src/k_menufunc.c | 88 +++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 88 insertions(+), 25 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 7156e07c5..e3d494679 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -653,9 +653,9 @@ extern UINT8 setup_maxpage; #define CSEXPLOSIONS 48 extern struct setup_explosions_s { - UINT8 x, y; + UINT16 x, y; UINT8 tics; - UINT8 color; + UINT16 color; } setup_explosions[CSEXPLOSIONS]; typedef enum diff --git a/src/k_menudraw.c b/src/k_menudraw.c index dcec6b86c..1b1577f40 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1481,13 +1481,13 @@ static void M_DrawCharSelectPreview(UINT8 num) } } -static void M_DrawCharSelectExplosions(void) +static void M_DrawCharSelectExplosions(boolean charsel, UINT16 basex, UINT16 basey) { UINT8 i; + INT16 quadx = 0, quady = 0; for (i = 0; i < CSEXPLOSIONS; i++) { - INT16 quadx, quady; UINT8 *colormap; UINT8 frame; @@ -1496,14 +1496,17 @@ static void M_DrawCharSelectExplosions(void) frame = 6 - setup_explosions[i].tics; - quadx = 4 * (setup_explosions[i].x / 3); - quady = 4 * (setup_explosions[i].y / 3); + if (charsel) + { + quadx = 4 * (setup_explosions[i].x / 3); + quady = 4 * (setup_explosions[i].y / 3); + } colormap = R_GetTranslationColormap(TC_DEFAULT, setup_explosions[i].color, GTC_MENUCACHE); V_DrawMappedPatch( - 82 + (setup_explosions[i].x*16) + quadx - 6, - 22 + (setup_explosions[i].y*16) + quady - 6, + basex + (setup_explosions[i].x*16) + quadx - 6, + basey + (setup_explosions[i].y*16) + quady - 6, 0, W_CachePatchName(va("CHCNFRM%d", frame), PU_CACHE), colormap ); @@ -1742,7 +1745,7 @@ void M_DrawCharacterSelect(void) } // Explosions when you've made your final selection - M_DrawCharSelectExplosions(); + M_DrawCharSelectExplosions(true, 82, 22); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { @@ -4523,7 +4526,7 @@ void M_DrawChallenges(void) // ...unless we simply aren't unlocked yet. if ((gamedata->unlocked[num] == false) - || (num == challengesmenu.currentunlock && challengesmenu.unlockanim < UNLOCKTIME/2)) + || (num == challengesmenu.currentunlock && challengesmenu.unlockanim < 2)) { work = (ref->majorunlock) ? 2 : 1; V_DrawFill(x, y, 16*work, 16*work, @@ -4570,4 +4573,6 @@ drawborder: } x += 16; } + + M_DrawCharSelectExplosions(false, currentMenu->x, currentMenu->y); } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index f8e80bb15..e978b6d78 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2229,10 +2229,12 @@ void M_CharacterSelect(INT32 choice) M_SetupNextMenu(&PLAY_CharSelectDef, false); } -static void M_SetupReadyExplosions(setup_player_t *p) +static void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, UINT16 color) { UINT8 i, j; UINT8 e = 0; + UINT16 maxx = (charsel ? 9 : gamedata->challengegridwidth); + UINT16 maxy = (charsel ? 9 : CHALLENGEGRIDHEIGHT); while (setup_explosions[e].tics) { @@ -2248,7 +2250,7 @@ static void M_SetupReadyExplosions(setup_player_t *p) for (j = 0; j < 4; j++) { - SINT8 x = p->gridx, y = p->gridy; + INT16 x = basex, y = basey; switch (j) { @@ -2258,11 +2260,27 @@ static void M_SetupReadyExplosions(setup_player_t *p) case 3: y -= offset; break; } - if ((x < 0 || x > 8) || (y < 0 || y > 8)) + if ((y < 0 || y >= maxy)) continue; + if (/*charsel &&*/ (x < 0 || x >= maxx)) + continue; + + /*if (!charsel) + { + while (x < 0) + { + x += maxx; + } + + while (x >= maxx) + { + x -= maxx; + } + }*/ + setup_explosions[e].tics = t; - setup_explosions[e].color = p->color; + setup_explosions[e].color = color; setup_explosions[e].x = x; setup_explosions[e].y = y; @@ -2550,7 +2568,7 @@ static void M_HandleCharAskChange(setup_player_t *p, UINT8 num) p->delay = TICRATE; S_StartSound(NULL, sfx_s3k4e); - M_SetupReadyExplosions(p); + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); } else { @@ -2850,7 +2868,7 @@ static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) p->followern = -1; p->mdepth = CSSTEP_READY; p->delay = TICRATE; - M_SetupReadyExplosions(p); + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); S_StartSound(NULL, sfx_s3k4e); } else @@ -2951,7 +2969,7 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) { p->mdepth = CSSTEP_READY; p->delay = TICRATE; - M_SetupReadyExplosions(p); + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); S_StartSound(NULL, sfx_s3k4e); } @@ -3000,7 +3018,7 @@ static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) { p->mdepth = CSSTEP_READY; p->delay = TICRATE; - M_SetupReadyExplosions(p); + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); S_StartSound(NULL, sfx_s3k4e); M_SetMenuDelay(num); } @@ -6822,6 +6840,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending || desiredmenu == NULL) { + memset(setup_explosions, 0, sizeof(setup_explosions)); challengesmenu.currentunlock = MAXUNLOCKABLES; M_PopulateChallengeGrid(); return &MISC_ChallengesDef; @@ -6843,6 +6862,8 @@ void M_Challenges(INT32 choice) UINT8 selection[MAXUNLOCKABLES]; UINT8 numunlocks = 0; + challengesmenu.extradata = M_ChallengeGridExtraData(); + // Get a random available unlockable. for (i = 0; i < MAXUNLOCKABLES; i++) { @@ -6882,6 +6903,11 @@ void M_Challenges(INT32 choice) continue; } + if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration + { + continue; + } + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; break; @@ -6897,6 +6923,12 @@ void M_ChallengesTick(void) challengesmenu.ticker++; + for (i = 0; i < CSEXPLOSIONS; i++) + { + if (setup_explosions[i].tics > 0) + setup_explosions[i].tics--; + } + if (challengesmenu.pending && challengesmenu.currentunlock >= MAXUNLOCKABLES) { if ((newunlock = M_GetNextAchievedUnlock(true)) < MAXUNLOCKABLES) @@ -6906,6 +6938,9 @@ void M_ChallengesTick(void) if (gamedata->challengegrid) { + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) { if (gamedata->challengegrid[i] != challengesmenu.currentunlock) @@ -6913,10 +6948,25 @@ void M_ChallengesTick(void) continue; } + if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration + { + continue; + } + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; break; } + + S_StartSound(NULL, sfx_s3k4e); + M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); + if (unlockables[challengesmenu.currentunlock].majorunlock) + { + UINT8 temp = challengesmenu.col+1; + if (temp == gamedata->challengegridwidth) + temp = 0; + M_SetupReadyExplosions(false, temp, challengesmenu.row+1, SKINCOLOR_KETCHUP); + } } } else @@ -6924,8 +6974,6 @@ void M_ChallengesTick(void) challengesmenu.pending = false; G_SaveGameData(); } - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = NULL; } else if (challengesmenu.unlockanim >= UNLOCKTIME) { @@ -6935,11 +6983,6 @@ void M_ChallengesTick(void) { challengesmenu.unlockanim++; } - - if (challengesmenu.extradata == NULL) - { - challengesmenu.extradata = M_ChallengeGridExtraData(); - } } boolean M_ChallengesInputs(INT32 ch) @@ -6978,10 +7021,25 @@ boolean M_ChallengesInputs(INT32 ch) continue; } + if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration + { + continue; + } + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; break; } + + S_StartSound(NULL, sfx_s3k4e); + M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); + if (unlockables[challengesmenu.currentunlock].majorunlock) + { + UINT8 temp = challengesmenu.col+1; + if (temp == gamedata->challengegridwidth) + temp = 0; + M_SetupReadyExplosions(false, temp, challengesmenu.row+1, SKINCOLOR_KETCHUP); + } } return true; } From 9de675165fc46c5b84b2f2f1c3fe87c895cca8af Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 2 Dec 2022 21:42:17 +0000 Subject: [PATCH 21/95] Roughest layout from cup grid --- src/k_menudef.c | 2 +- src/k_menudraw.c | 49 ++++++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/k_menudef.c b/src/k_menudef.c index f355a3e34..53c55a415 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1756,7 +1756,7 @@ menu_t MISC_ChallengesDef = { &MainDef, 0, MISC_ChallengesMenu, - 48, 80, + 14, 32, 0, 0, 98, 0, M_DrawChallenges, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 1b1577f40..922c4d662 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4463,8 +4463,10 @@ void M_DrawAddons(void) void M_DrawChallenges(void) { - INT32 x = 20, y = 20; + INT32 x = currentMenu->x, y = currentMenu->y; UINT8 i, j, id, num, work; + const char *str; + INT16 offset; unlockable_t *ref = NULL; { @@ -4472,30 +4474,10 @@ void M_DrawChallenges(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } - if (challengesmenu.currentunlock < MAXUNLOCKABLES) - { - const char *str = unlockables[challengesmenu.currentunlock].name; - if (!gamedata->unlocked[challengesmenu.currentunlock]) - { - str = "???"; //M_CreateSecretMenuOption(str); - } - V_DrawThinString(x, y, V_ALLOWLOWERCASE, str); - } - else - { - V_DrawThinString(x, y, V_ALLOWLOWERCASE, "---"); - } - - if (challengesmenu.unlockanim >= UNLOCKTIME) - V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE, va("Press (%c)", challengesmenu.pending ? 'A' : 'B')); - - x = currentMenu->x; - y = currentMenu->y; - if (!gamedata->challengegrid) { V_DrawString(x, y, V_REDMAP, "No challenges available!?"); - return; + goto challengedesc; } for (i = 0; i < gamedata->challengegridwidth; i++) @@ -4575,4 +4557,27 @@ drawborder: } M_DrawCharSelectExplosions(false, currentMenu->x, currentMenu->y); + +challengedesc: + y = 120; + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); + + if (challengesmenu.currentunlock < MAXUNLOCKABLES) + { + str = unlockables[challengesmenu.currentunlock].name; + if (!gamedata->unlocked[challengesmenu.currentunlock]) + { + str = "???"; //M_CreateSecretMenuOption(str); + } + } + else + { + str = "---"; + } + + offset = V_LSTitleLowStringWidth(str, 0) / 2; + V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); + + if (challengesmenu.unlockanim >= UNLOCKTIME) + V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, va("Press (%c)", challengesmenu.pending ? 'A' : 'B')); } From 9df1f1d34cdb67d7541f218bddd9fc3a0b370266 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 2 Dec 2022 22:39:45 +0000 Subject: [PATCH 22/95] Unlock explosions are now slightly delayed Done so it doesn't begin during a transition, and you get to properly appreciate the first one Comes with the following changes and refactors: * Uses the default UN_RR00c icons for entries without icons (currently all except SECRET_SKIN) * `challengesmenu->extradata` updated in less places * `M_GetNextAchievedUnlock` no longer handles setting `unlocked` so we can do that during the above timer * Test button explosion repetition no longer duplicates M_ChallengesTick code * Instead of giving `challengesmenu->currentunlock == MAXUNLOCKABLES` special behaviour, add a `challengesmenu->requestnew` * The crash prevention for nonexistent extradata in the input handler is now actually functional. --- src/k_menu.h | 4 ++- src/k_menudraw.c | 12 +++++--- src/k_menufunc.c | 71 ++++++++++++++++++++++++------------------------ src/m_cond.c | 7 +---- src/m_cond.h | 2 +- 5 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index e3d494679..43a4dd90d 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1080,7 +1080,8 @@ void M_DrawReplayStartMenu(void); void M_DrawAddons(void); // Challenges menu: -#define UNLOCKTIME 35 +#define UNLOCKTIME 5 +#define MAXUNLOCKTIME 35 // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1096,6 +1097,7 @@ extern struct challengesmenu_s { UINT8 *extradata; boolean pending; + boolean requestnew; } challengesmenu; menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 922c4d662..7816b34f7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4508,7 +4508,7 @@ void M_DrawChallenges(void) // ...unless we simply aren't unlocked yet. if ((gamedata->unlocked[num] == false) - || (num == challengesmenu.currentunlock && challengesmenu.unlockanim < 2)) + || (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME)) { work = (ref->majorunlock) ? 2 : 1; V_DrawFill(x, y, 16*work, 16*work, @@ -4530,14 +4530,18 @@ void M_DrawChallenges(void) } else { - V_DrawPatch(x, y, 0, missingpat); + V_DrawMappedPatch(x, y, 0, missingpat, NULL); } break; } default: - V_DrawString(x, y, V_ALLOWLOWERCASE, va("%c", ref->name[0])); + { + patch_t *pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); + V_DrawMappedPatch(x, y, 0, pat, NULL); + //V_DrawString(x, y, V_ALLOWLOWERCASE, va("%c", ref->name[0])); break; + } } drawborder: @@ -4578,6 +4582,6 @@ challengedesc: offset = V_LSTitleLowStringWidth(str, 0) / 2; V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); - if (challengesmenu.unlockanim >= UNLOCKTIME) + if (challengesmenu.unlockanim >= MAXUNLOCKTIME) V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, va("Press (%c)", challengesmenu.pending ? 'A' : 'B')); } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index e978b6d78..013d00dcc 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6832,9 +6832,8 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { M_UpdateUnlockablesAndExtraEmblems(false); - if (M_GetNextAchievedUnlock(false) < MAXUNLOCKABLES) + if ((challengesmenu.pending = challengesmenu.requestnew = (M_GetNextAchievedUnlock() < MAXUNLOCKABLES))) { - challengesmenu.pending = true; MISC_ChallengesDef.prevMenu = desiredmenu; } @@ -6843,6 +6842,10 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) memset(setup_explosions, 0, sizeof(setup_explosions)); challengesmenu.currentunlock = MAXUNLOCKABLES; M_PopulateChallengeGrid(); + + if (gamedata->challengegrid) + challengesmenu.extradata = M_ChallengeGridExtraData(); + return &MISC_ChallengesDef; } @@ -6862,8 +6865,6 @@ void M_Challenges(INT32 choice) UINT8 selection[MAXUNLOCKABLES]; UINT8 numunlocks = 0; - challengesmenu.extradata = M_ChallengeGridExtraData(); - // Get a random available unlockable. for (i = 0; i < MAXUNLOCKABLES; i++) { @@ -6929,18 +6930,15 @@ void M_ChallengesTick(void) setup_explosions[i].tics--; } - if (challengesmenu.pending && challengesmenu.currentunlock >= MAXUNLOCKABLES) + if (challengesmenu.pending && challengesmenu.requestnew) { - if ((newunlock = M_GetNextAchievedUnlock(true)) < MAXUNLOCKABLES) + if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) { challengesmenu.currentunlock = newunlock; challengesmenu.unlockanim = 0; if (gamedata->challengegrid) { - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = M_ChallengeGridExtraData(); - for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) { if (gamedata->challengegrid[i] != challengesmenu.currentunlock) @@ -6957,16 +6955,6 @@ void M_ChallengesTick(void) challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; break; } - - S_StartSound(NULL, sfx_s3k4e); - M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); - if (unlockables[challengesmenu.currentunlock].majorunlock) - { - UINT8 temp = challengesmenu.col+1; - if (temp == gamedata->challengegridwidth) - temp = 0; - M_SetupReadyExplosions(false, temp, challengesmenu.row+1, SKINCOLOR_KETCHUP); - } } } else @@ -6975,14 +6963,35 @@ void M_ChallengesTick(void) G_SaveGameData(); } } - else if (challengesmenu.unlockanim >= UNLOCKTIME) + else if (challengesmenu.unlockanim >= MAXUNLOCKTIME) { ; } else { challengesmenu.unlockanim++; + if (challengesmenu.pending + && challengesmenu.currentunlock < MAXUNLOCKABLES + && challengesmenu.unlockanim == UNLOCKTIME) + { + gamedata->unlocked[challengesmenu.currentunlock] = true; + + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); + + S_StartSound(NULL, sfx_s3k4e); + M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); + if (unlockables[challengesmenu.currentunlock].majorunlock) + { + i = challengesmenu.col+1; + if (i == gamedata->challengegridwidth) + i = 0; + M_SetupReadyExplosions(false, i, challengesmenu.row+1, SKINCOLOR_KETCHUP); + } + } } + + challengesmenu.requestnew = false; } boolean M_ChallengesInputs(INT32 ch) @@ -6997,10 +7006,6 @@ boolean M_ChallengesInputs(INT32 ch) { ; } - if (challengesmenu.unlockanim < UNLOCKTIME) - { - ; - } #ifdef DEVELOP else if (M_MenuExtraPressed(pid)) // debugging { @@ -7031,24 +7036,20 @@ boolean M_ChallengesInputs(INT32 ch) break; } - S_StartSound(NULL, sfx_s3k4e); - M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); - if (unlockables[challengesmenu.currentunlock].majorunlock) - { - UINT8 temp = challengesmenu.col+1; - if (temp == gamedata->challengegridwidth) - temp = 0; - M_SetupReadyExplosions(false, temp, challengesmenu.row+1, SKINCOLOR_KETCHUP); - } + challengesmenu.pending = true; } return true; } #endif else if (challengesmenu.pending) { - if ((M_MenuConfirmPressed(pid) || start)) + if (challengesmenu.unlockanim < MAXUNLOCKTIME) { - challengesmenu.currentunlock = MAXUNLOCKABLES; + ; + } + else if ((M_MenuConfirmPressed(pid) || start)) + { + challengesmenu.requestnew = true; } return true; } diff --git a/src/m_cond.c b/src/m_cond.c index d54096870..8b4c8e5e6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -639,7 +639,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) return false; } -UINT8 M_GetNextAchievedUnlock(boolean set) +UINT8 M_GetNextAchievedUnlock(void) { UINT8 i; @@ -661,11 +661,6 @@ UINT8 M_GetNextAchievedUnlock(boolean set) continue; } - if (set) - { - gamedata->unlocked[i] = true; - } - return i; } diff --git a/src/m_cond.h b/src/m_cond.h index 5f3500813..2d701ec56 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -193,7 +193,7 @@ void M_ClearSecrets(void); void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); boolean M_UpdateUnlockablesAndExtraEmblems(boolean silent); -UINT8 M_GetNextAchievedUnlock(boolean set); +UINT8 M_GetNextAchievedUnlock(void); UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); From 6671e181b07c4175ef5139bbc48399c23d4d4ff3 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 3 Dec 2022 11:40:58 +0000 Subject: [PATCH 23/95] Increase unlockable/conditionset limits - MAXCONDITIONSETS is UINT8_MAX - MAXUNLOCKABLES is (MAXCONDITIONSETS-MAXEXTRAEMBLEMS) Haven't come to a decision yet on whether Extra Emblems should just be made with the Unlockable struct or maintain their distinct status, this commit kicks that can down the road --- src/deh_soc.c | 24 ++++++++++++------------ src/dehacked.c | 2 +- src/k_menufunc.c | 2 +- src/m_cond.c | 22 +++++++++++----------- src/m_cond.h | 4 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 5c01f5d43..e5a5ca41b 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -165,7 +165,7 @@ void clear_conditionsets(void) { UINT8 i; for (i = 0; i < MAXCONDITIONSETS; ++i) - M_ClearConditionSet(i+1); + M_ClearConditionSet(i); } void clear_levels(void) @@ -2415,7 +2415,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (!params[0]) { - deh_warning("condition line is empty for condition ID %d", id); + deh_warning("condition line is empty for condition ID %d", id+1); return; } @@ -2435,7 +2435,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (x1 < 0 || x1 >= PWRLV_NUMTYPES) { - deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id); + deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id+1); return; } } @@ -2459,7 +2459,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (re >= nummapheaders) { - deh_warning("Invalid level %s for condition ID %d", params[1], id); + deh_warning("Invalid level %s for condition ID %d", params[1], id+1); return; } } @@ -2472,7 +2472,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (x1 >= nummapheaders) { - deh_warning("Invalid level %s for condition ID %d", params[1], id); + deh_warning("Invalid level %s for condition ID %d", params[1], id+1); return; } } @@ -2485,7 +2485,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) // constrained by 32 bits if (re < 0 || re > 31) { - deh_warning("Trigger ID %d out of range (0 - 31) for condition ID %d", re, id); + deh_warning("Trigger ID %d out of range (0 - 31) for condition ID %d", re, id+1); return; } } @@ -2503,7 +2503,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (re <= 0 || re > MAXEMBLEMS) { - deh_warning("Emblem %d out of range (1 - %d) for condition ID %d", re, MAXEMBLEMS, id); + deh_warning("Emblem %d out of range (1 - %d) for condition ID %d", re, MAXEMBLEMS, id+1); return; } } @@ -2515,7 +2515,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (re <= 0 || re > MAXEXTRAEMBLEMS) { - deh_warning("Extra emblem %d out of range (1 - %d) for condition ID %d", re, MAXEXTRAEMBLEMS, id); + deh_warning("Extra emblem %d out of range (1 - %d) for condition ID %d", re, MAXEXTRAEMBLEMS, id+1); return; } } @@ -2527,13 +2527,13 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) if (re <= 0 || re > MAXCONDITIONSETS) { - deh_warning("Condition set %d out of range (1 - %d) for condition ID %d", re, MAXCONDITIONSETS, id); + deh_warning("Condition set %d out of range (1 - %d) for condition ID %d", re, MAXCONDITIONSETS, id+1); return; } } else { - deh_warning("Invalid condition name %s for condition ID %d", params[0], id); + deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1); return; } @@ -2586,13 +2586,13 @@ void readconditionset(MYFILE *f, UINT8 setnum) id = (UINT8)atoi(word + 9); if (id == 0) { - deh_warning("Condition set %d: unknown word '%s'", setnum, word); + deh_warning("Condition set %d: unknown word '%s'", setnum+1, word); continue; } else if (previd > id) { // out of order conditions can cause problems, so enforce proper order - deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum); + deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum+1); continue; } previd = id; diff --git a/src/dehacked.c b/src/dehacked.c index 1e400a1fa..cc8ecc58e 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -479,7 +479,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) ignorelines(f); } else if (i > 0 && i <= MAXCONDITIONSETS) - readconditionset(f, (UINT8)i); + readconditionset(f, (UINT8)(i-1)); else { deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 013d00dcc..194c4cc1e 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -7017,7 +7017,7 @@ boolean M_ChallengesInputs(INT32 ch) challengesmenu.extradata = M_ChallengeGridExtraData(); challengesmenu.unlockanim = 0; - if (challengesmenu.currentunlock != MAXUNLOCKABLES) + if (challengesmenu.currentunlock < MAXUNLOCKABLES) { for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) { diff --git a/src/m_cond.c b/src/m_cond.c index 8b4c8e5e6..c05a6a169 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -191,7 +191,7 @@ quickcheckagain: // Space out empty entries to pepper into unlock list for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++) { - if (gamedata->challengegrid[i] != MAXUNLOCKABLES) + if (gamedata->challengegrid[i] < MAXUNLOCKABLES) { continue; } @@ -215,7 +215,7 @@ quickcheckagain: // Fill the remaining spots with random ordinary unlocks (and empties). for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++) { - if (gamedata->challengegrid[i] != MAXUNLOCKABLES) + if (gamedata->challengegrid[i] < MAXUNLOCKABLES) { continue; } @@ -405,12 +405,12 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1 I_Assert(set && set <= MAXCONDITIONSETS); - wnum = conditionSets[set - 1].numconditions; - num = ++conditionSets[set - 1].numconditions; + wnum = conditionSets[set].numconditions; + num = ++conditionSets[set].numconditions; - conditionSets[set - 1].condition = Z_Realloc(conditionSets[set - 1].condition, sizeof(condition_t)*num, PU_STATIC, 0); + conditionSets[set].condition = Z_Realloc(conditionSets[set].condition, sizeof(condition_t)*num, PU_STATIC, 0); - cond = conditionSets[set - 1].condition; + cond = conditionSets[set].condition; cond[wnum].id = id; cond[wnum].type = c; @@ -421,13 +421,13 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1 void M_ClearConditionSet(UINT8 set) { - if (conditionSets[set - 1].numconditions) + if (conditionSets[set].numconditions) { - Z_Free(conditionSets[set - 1].condition); - conditionSets[set - 1].condition = NULL; - conditionSets[set - 1].numconditions = 0; + Z_Free(conditionSets[set].condition); + conditionSets[set].condition = NULL; + conditionSets[set].numconditions = 0; } - gamedata->achieved[set - 1] = false; + gamedata->achieved[set] = false; } // Clear ALL secrets. diff --git a/src/m_cond.h b/src/m_cond.h index 2d701ec56..fec228f10 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -123,10 +123,10 @@ typedef struct // If you have more secrets than these variables allow in your game, // you seriously need to get a life. -#define MAXCONDITIONSETS 128 +#define MAXCONDITIONSETS UINT8_MAX #define MAXEMBLEMS 512 #define MAXEXTRAEMBLEMS 16 -#define MAXUNLOCKABLES 32 +#define MAXUNLOCKABLES (MAXCONDITIONSETS-MAXEXTRAEMBLEMS) #define CHALLENGEGRIDHEIGHT 5 From 017871eaad1dfd16a44254fe6d56784a98c2afea Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 3 Dec 2022 12:18:59 +0000 Subject: [PATCH 24/95] Custom Icon support for `unlockable_t` --- src/deh_soc.c | 8 ++++++++ src/k_menudraw.c | 17 +++++++++++++++++ src/m_cond.h | 1 + 3 files changed, 26 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index e5a5ca41b..c85c3da4a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -156,6 +156,9 @@ void clear_unlockables(void) { Z_Free(unlockables[i].stringVar); unlockables[i].stringVar = NULL; + + Z_Free(unlockables[i].icon); + unlockables[i].icon = NULL; } memset(&unlockables, 0, sizeof(unlockables)); @@ -2379,6 +2382,11 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].stringVar = Z_StrDup(word2); unlockables[num].variable = (INT16)i; } + else if (fastcmp(word, "ICON")) + { + Z_Free(unlockables[num].icon); + unlockables[num].icon = Z_StrDup(word2); + } else deh_warning("Unlockable %d: unknown word '%s'", num+1, word); } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 7816b34f7..d33794e37 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4517,6 +4517,23 @@ void M_DrawChallenges(void) goto drawborder; } + if (ref->icon != NULL) + { + patch_t *pat = W_CachePatchName(ref->icon, PU_CACHE); + fixed_t siz = (SHORT(pat->width) << FRACBITS); + + siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); + + V_DrawFixedPatch( + x*FRACUNIT, y*FRACUNIT, + siz, + 0, pat, + NULL + ); + + goto drawborder; + } + switch (ref->type) { case SECRET_SKIN: diff --git a/src/m_cond.h b/src/m_cond.h index fec228f10..1332cd45f 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -93,6 +93,7 @@ typedef struct { char name[64]; char objective[64]; + char *icon; UINT8 conditionset; INT16 type; INT16 variable; From d061dd09fdd0633e2ae30992080113d71400431d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 3 Dec 2022 14:52:58 +0000 Subject: [PATCH 25/95] Removing `extraemblem_t` - You can now create an unlockable that gives you an emblem only with SECRET_EXTRAEMBLEM. - One step above the completely rewardless squares from Kirby Air Ride. - Added color to `unlockable_t`. - Can be used both with user-specified icons (remappable green->color) and with non-SECRET_SKIN default graphics (invincibility full-range remap) - Replaced condition type UC_EXTRAEMBLEM with the more general UC_UNLOCKABLE. - MAXUNLOCKABLES is now == MAXCONDITIONSETS --- src/deh_soc.c | 103 +++++------------------------------------------ src/deh_soc.h | 1 - src/dehacked.c | 32 --------------- src/g_game.c | 17 +------- src/k_menudraw.c | 56 ++++++++++++++------------ src/m_cond.c | 99 ++++++++++++++------------------------------- src/m_cond.h | 28 +++---------- 7 files changed, 77 insertions(+), 259 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index c85c3da4a..d63301901 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2203,88 +2203,6 @@ void reademblemdata(MYFILE *f, INT32 num) Z_Free(s); } -void readextraemblemdata(MYFILE *f, INT32 num) -{ - char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); - char *word = s; - char *word2; - char *tmp; - INT32 value; - - memset(&extraemblems[num-1], 0, sizeof(extraemblem_t)); - - do - { - if (myfgets(s, MAXLINELEN, f)) - { - if (s[0] == '\n') - break; - - // First remove trailing newline, if there is one - tmp = strchr(s, '\n'); - if (tmp) - *tmp = '\0'; - - tmp = strchr(s, '#'); - if (tmp) - *tmp = '\0'; - if (s == tmp) - continue; // Skip comment lines, but don't break. - - // Get the part before the " = " - tmp = strchr(s, '='); - if (tmp) - *(tmp-1) = '\0'; - else - break; - strupr(word); - - // Now get the part after - word2 = tmp += 2; - - value = atoi(word2); // used for numerical settings - - if (fastcmp(word, "NAME")) - deh_strlcpy(extraemblems[num-1].name, word2, - sizeof (extraemblems[num-1].name), va("Extra emblem %d: name", num)); - else if (fastcmp(word, "OBJECTIVE")) - deh_strlcpy(extraemblems[num-1].description, word2, - sizeof (extraemblems[num-1].description), va("Extra emblem %d: objective", num)); - else if (fastcmp(word, "CONDITIONSET")) - extraemblems[num-1].conditionset = (UINT8)value; - else if (fastcmp(word, "SHOWCONDITIONSET")) - extraemblems[num-1].showconditionset = (UINT8)value; - else - { - strupr(word2); - if (fastcmp(word, "SPRITE")) - { - if (word2[0] >= 'A' && word2[0] <= 'Z') - value = word2[0]; - else - value += 'A'-1; - - if (value < 'A' || value > 'Z') - deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num); - else - extraemblems[num-1].sprite = (UINT8)value; - } - else if (fastcmp(word, "COLOR")) - extraemblems[num-1].color = get_number(word2); - else - deh_warning("Extra emblem %d: unknown word '%s'", num, word); - } - } - } while (!myfeof(f)); - - if (!extraemblems[num-1].sprite) - extraemblems[num-1].sprite = 'C'; - if (!extraemblems[num-1].color) - extraemblems[num-1].color = SKINCOLOR_RED; - - Z_Free(s); -} - void readunlockable(MYFILE *f, INT32 num) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -2294,7 +2212,6 @@ void readunlockable(MYFILE *f, INT32 num) INT32 i; memset(&unlockables[num], 0, sizeof(unlockable_t)); - unlockables[num].objective[0] = '/'; do { @@ -2329,10 +2246,7 @@ void readunlockable(MYFILE *f, INT32 num) if (fastcmp(word, "NAME")) deh_strlcpy(unlockables[num].name, word2, - sizeof (unlockables[num].name), va("Unlockable %d: name", num)); - else if (fastcmp(word, "OBJECTIVE")) - deh_strlcpy(unlockables[num].objective, word2, - sizeof (unlockables[num].objective), va("Unlockable %d: objective", num)); + sizeof (unlockables[num].name), va("Unlockable %d: name", num+1)); else { strupr(word2); @@ -2351,8 +2265,6 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_SKIN; else if (fastcmp(word2, "WARP")) unlockables[num].type = SECRET_WARP; - else if (fastcmp(word2, "LEVELSELECT")) - unlockables[num].type = SECRET_LEVELSELECT; else if (fastcmp(word2, "TIMEATTACK")) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) @@ -2387,6 +2299,11 @@ void readunlockable(MYFILE *f, INT32 num) Z_Free(unlockables[num].icon); unlockables[num].icon = Z_StrDup(word2); } + else if (fastcmp(word, "COLOR")) + { + unlockables[num].color = get_number(word2); + CONS_Printf("%d+1 has color %s\n",num+1, skincolors[unlockables[num].color].name); + } else deh_warning("Unlockable %d: unknown word '%s'", num+1, word); } @@ -2515,15 +2432,15 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } - else if (fastcmp(params[0], "EXTRAEMBLEM")) + else if (fastcmp(params[0], "UNLOCKABLE")) { PARAMCHECK(1); - ty = UC_EXTRAEMBLEM; + ty = UC_UNLOCKABLE; re = atoi(params[1]); - if (re <= 0 || re > MAXEXTRAEMBLEMS) + if (re <= 0 || re > MAXUNLOCKABLES) { - deh_warning("Extra emblem %d out of range (1 - %d) for condition ID %d", re, MAXEXTRAEMBLEMS, id+1); + deh_warning("Unlockable %d out of range (1 - %d) for condition ID %d", re, MAXUNLOCKABLES, id+1); return; } } diff --git a/src/deh_soc.h b/src/deh_soc.h index 92985ad2f..d6d4d9e01 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -60,7 +60,6 @@ void readwipes(MYFILE *f); void readmaincfg(MYFILE *f); void readconditionset(MYFILE *f, UINT8 setnum); void readunlockable(MYFILE *f, INT32 num); -void readextraemblemdata(MYFILE *f, INT32 num); void reademblemdata(MYFILE *f, INT32 num); void readsound(MYFILE *f, INT32 num); void readframe(MYFILE *f, INT32 num); diff --git a/src/dehacked.c b/src/dehacked.c index cc8ecc58e..005924ef4 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -278,32 +278,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } continue; } - else if (fastcmp(word, "EXTRAEMBLEM")) - { - if (!mainfile && !gamedataadded) - { - deh_warning("You must define a custom gamedata to use \"%s\"", word); - ignorelines(f); - } - else - { - if (!word2) - i = numextraemblems + 1; - - if (i > 0 && i <= MAXEXTRAEMBLEMS) - { - if (numextraemblems < i) - numextraemblems = i; - readextraemblemdata(f, i); - } - else - { - deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS); - ignorelines(f); - } - } - continue; - } if (word2) { if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT")) @@ -593,12 +567,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) clear_emblems(); } - if (clearall || fastcmp(word2, "EXTRAEMBLEMS")) - { - memset(&extraemblems, 0, sizeof(extraemblems)); - numextraemblems = 0; - } - if (clearall || fastcmp(word2, "CONDITIONSETS")) clear_conditionsets(); diff --git a/src/g_game.c b/src/g_game.c index e2f0524ee..bafbf2ec3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4389,13 +4389,6 @@ void G_LoadGameData(void) gamedata->collected[j+i] = ((rtemp >> j) & 1); i += j; } - for (i = 0; i < MAXEXTRAEMBLEMS;) - { - rtemp = READUINT8(save_p); - for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) - gamedata->extraCollected[j+i] = ((rtemp >> j) & 1); - i += j; - } for (i = 0; i < MAXUNLOCKABLES;) { rtemp = READUINT8(save_p); @@ -4520,7 +4513,7 @@ void G_SaveGameData(void) return; } - length = (4+4+4+1+(MAXEMBLEMS)+MAXEXTRAEMBLEMS+MAXUNLOCKABLES+MAXCONDITIONSETS+4+4); + length = (4+4+4+1+(MAXEMBLEMS+MAXUNLOCKABLES+MAXCONDITIONSETS)+4+4); length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); save_p = savebuffer = (UINT8 *)malloc(length); @@ -4546,14 +4539,6 @@ void G_SaveGameData(void) WRITEUINT8(save_p, btemp); i += j; } - for (i = 0; i < MAXEXTRAEMBLEMS;) // MAXEXTRAEMBLEMS * 1; - { - btemp = 0; - for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) - btemp |= (gamedata->extraCollected[j+i] << j); - WRITEUINT8(save_p, btemp); - i += j; - } for (i = 0; i < MAXUNLOCKABLES;) // MAXUNLOCKABLES * 1; { btemp = 0; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d33794e37..c72871577 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4468,6 +4468,9 @@ void M_DrawChallenges(void) const char *str; INT16 offset; unlockable_t *ref = NULL; + patch_t *pat; + UINT8 *colormap; + fixed_t siz; { patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); @@ -4517,50 +4520,51 @@ void M_DrawChallenges(void) goto drawborder; } + pat = missingpat; + colormap = NULL; if (ref->icon != NULL) { - patch_t *pat = W_CachePatchName(ref->icon, PU_CACHE); - fixed_t siz = (SHORT(pat->width) << FRACBITS); - - siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); - - V_DrawFixedPatch( - x*FRACUNIT, y*FRACUNIT, - siz, - 0, pat, - NULL - ); - - goto drawborder; + pat = W_CachePatchName(ref->icon, PU_CACHE); + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, ref->color, GTC_MENUCACHE); + } } - - switch (ref->type) + else switch (ref->type) { + // add all SECRET_ENCORE, etc up here before SECRET_SKIN case SECRET_SKIN: { INT32 skin = M_UnlockableSkinNum(ref); if (skin != -1) { - UINT8 *colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); - UINT8 size = (ref->majorunlock) ? FACE_WANTED : FACE_RANK; - V_DrawMappedPatch(x, y, 0, faceprefix[skin][size], colormap); + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + pat = faceprefix[skin][(ref->majorunlock) ? FACE_WANTED : FACE_RANK]; } - else - { - V_DrawMappedPatch(x, y, 0, missingpat, NULL); - } - break; } default: { - patch_t *pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); - V_DrawMappedPatch(x, y, 0, pat, NULL); - //V_DrawString(x, y, V_ALLOWLOWERCASE, va("%c", ref->name[0])); + pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + CONS_Printf(" color for %d is %s\n", num, skincolors[unlockables[num].color].name); + colormap = R_GetTranslationColormap(TC_RAINBOW, ref->color, GTC_MENUCACHE); + } break; } } + siz = (SHORT(pat->width) << FRACBITS); + siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); + + V_DrawFixedPatch( + x*FRACUNIT, y*FRACUNIT, + siz, + 0, pat, + colormap + ); + drawborder: if (i != challengesmenu.hilix) continue; diff --git a/src/m_cond.c b/src/m_cond.c index c05a6a169..4bf644e23 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -37,15 +37,11 @@ conditionset_t conditionSets[MAXCONDITIONSETS]; // Emblem locations emblem_t emblemlocations[MAXEMBLEMS]; -// Extra emblems -extraemblem_t extraemblems[MAXEXTRAEMBLEMS]; - // Unlockables unlockable_t unlockables[MAXUNLOCKABLES]; -// Number of emblems and extra emblems +// Number of emblems INT32 numemblems = 0; -INT32 numextraemblems = 0; // Create a new gamedata_t, for start-up void M_NewGameDataStruct(void) @@ -442,8 +438,6 @@ void M_ClearSecrets(void) for (i = 0; i < MAXEMBLEMS; ++i) gamedata->collected[i] = false; - for (i = 0; i < MAXEXTRAEMBLEMS; ++i) - gamedata->extraCollected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) gamedata->unlocked[i] = false; for (i = 0; i < MAXCONDITIONSETS; ++i) @@ -511,8 +505,8 @@ UINT8 M_CheckCondition(condition_t *cn) return (M_GotEnoughEmblems(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained return gamedata->collected[cn->requirement-1]; - case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained - return gamedata->extraCollected[cn->requirement-1]; + case UC_UNLOCKABLE: // Requires unlockable x to be obtained + return gamedata->unlocked[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); } @@ -574,32 +568,11 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) // Just in case they aren't to sync M_CheckLevelEmblems(); M_CompletionEmblems(); - } - // Go through extra emblems - for (i = 0; i < numextraemblems; ++i) - { - if (gamedata->extraCollected[i] || !extraemblems[i].conditionset) - { - continue; - } - - if ((gamedata->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1)) == false) - { - continue; - } - - if (loud) - { - strcat(cechoText, va("Got \"%s\" medal!\n", extraemblems[i].name)); - } - ++cechoLines; - } - - // Fun part: if any of those unlocked we need to go through the - // unlock conditions AGAIN just in case an emblem reward was reached - if (cechoLines) + // Fun part: if any of those unlocked we need to go through the + // unlock conditions AGAIN just in case an emblem reward was reached M_CheckUnlockConditions(); + } // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) @@ -643,6 +616,8 @@ UINT8 M_GetNextAchievedUnlock(void) { UINT8 i; + M_CheckUnlockConditions(); + // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { @@ -801,13 +776,17 @@ INT32 M_CountEmblems(void) INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { - if (gamedata->collected[i]) - found++; + if (!gamedata->collected[i]) + continue; + found++; } - for (i = 0; i < numextraemblems; ++i) + for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (gamedata->extraCollected[i]) - found++; + if (unlockables[i].type != SECRET_EXTRAEMBLEM) + continue; + if (!gamedata->unlocked[i]) + continue; + found++; } return found; } @@ -823,13 +802,21 @@ UINT8 M_GotEnoughEmblems(INT32 number) INT32 i, gottenemblems = 0; for (i = 0; i < numemblems; ++i) { - if (gamedata->collected[i]) - if (++gottenemblems >= number) return true; + if (!gamedata->collected[i]) + continue; + if (++gottenemblems < number) + continue; + return true; } - for (i = 0; i < numextraemblems; ++i) + for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (gamedata->extraCollected[i]) - if (++gottenemblems >= number) return true; + if (unlockables[i].type != SECRET_EXTRAEMBLEM) + continue; + if (!gamedata->unlocked[i]) + continue; + if (++gottenemblems < number) + continue; + return true; } return false; } @@ -938,29 +925,3 @@ const char *M_GetEmblemPatch(emblem_t *em, boolean big) return pnamebuf; } - -skincolornum_t M_GetExtraEmblemColor(extraemblem_t *em) -{ - if (!em || em->color >= numskincolors) - return SKINCOLOR_NONE; - return em->color; -} - -const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big) -{ - static char pnamebuf[7]; - - if (!big) - strcpy(pnamebuf, "GOTITn"); - else - strcpy(pnamebuf, "EMBMn0"); - - I_Assert(em->sprite >= 'A' && em->sprite <= 'Z'); - - if (!big) - pnamebuf[5] = em->sprite; - else - pnamebuf[4] = em->sprite; - - return pnamebuf; -} diff --git a/src/m_cond.h b/src/m_cond.h index 1332cd45f..f839155fa 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -32,7 +32,7 @@ typedef enum UC_TRIGGER, // TRIGGER [trigger number] UC_TOTALEMBLEMS, // TOTALEMBLEMS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] - UC_EXTRAEMBLEM, // EXTRAEMBLEM [extra emblem number] + UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] } conditiontype_t; @@ -78,22 +78,13 @@ typedef struct char *stringVar; ///< String version char hint[110]; ///< Hint for emblem hints menu } emblem_t; -typedef struct -{ - char name[20]; ///< Name of the goal (used in the "emblem awarded" cecho) - char description[40]; ///< Description of goal (used in statistics) - UINT8 conditionset; ///< Condition set that awards this emblem. - UINT8 showconditionset; ///< Condition set that shows this emblem. - UINT8 sprite; ///< emblem sprite to use, 0 - 25 - UINT16 color; ///< skincolor to use -} extraemblem_t; // Unlockable information typedef struct { char name[64]; - char objective[64]; char *icon; + UINT16 color; UINT8 conditionset; INT16 type; INT16 variable; @@ -105,8 +96,9 @@ typedef struct #define SECRET_HEADER 1 // Does nothing on its own, just serves as a header for the menu #define SECRET_SKIN 2 // Allow this character to be selected -#define SECRET_WARP 3 // Selectable warp -#define SECRET_LEVELSELECT 4 // Selectable level select +#define SECRET_WARP 3 // Selectable warp (todo Followers) + +#define SECRET_EXTRAEMBLEM 4 // Extra Emblems (formerly extraemblem_t) #define SECRET_TIMEATTACK 5 // Enables Time Attack on the main menu #define SECRET_BREAKTHECAPSULES 6 // Enables Break the Capsules on the main menu @@ -126,8 +118,7 @@ typedef struct // you seriously need to get a life. #define MAXCONDITIONSETS UINT8_MAX #define MAXEMBLEMS 512 -#define MAXEXTRAEMBLEMS 16 -#define MAXUNLOCKABLES (MAXCONDITIONSETS-MAXEXTRAEMBLEMS) +#define MAXUNLOCKABLES MAXCONDITIONSETS #define CHALLENGEGRIDHEIGHT 5 @@ -144,9 +135,6 @@ typedef struct // EMBLEMS COLLECTED boolean collected[MAXEMBLEMS]; - // EXTRA EMBLEMS COLLECTED - boolean extraCollected[MAXEXTRAEMBLEMS]; - // UNLOCKABLES UNLOCKED boolean unlocked[MAXUNLOCKABLES]; @@ -166,11 +154,9 @@ extern gamedata_t *gamedata; extern conditionset_t conditionSets[MAXCONDITIONSETS]; extern emblem_t emblemlocations[MAXEMBLEMS]; -extern extraemblem_t extraemblems[MAXEXTRAEMBLEMS]; extern unlockable_t unlockables[MAXUNLOCKABLES]; extern INT32 numemblems; -extern INT32 numextraemblems; extern UINT32 unlocktriggers; @@ -207,8 +193,6 @@ INT32 M_CountEmblems(void); emblem_t *M_GetLevelEmblems(INT32 mapnum); skincolornum_t M_GetEmblemColor(emblem_t *em); const char *M_GetEmblemPatch(emblem_t *em, boolean big); -skincolornum_t M_GetExtraEmblemColor(extraemblem_t *em); -const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big); // If you're looking to compare stats for unlocks or what not, use these // They stop checking upon reaching the target number so they From f25907d7bcde2f6860f129d47ef30778231085ad Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 3 Dec 2022 16:26:46 +0000 Subject: [PATCH 26/95] M_Challenges: only get a random challenge to focus on if you're not about to unlock something --- src/k_menufunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 194c4cc1e..07af02502 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6860,7 +6860,7 @@ void M_Challenges(INT32 choice) M_InterruptMenuWithChallenges(NULL); MISC_ChallengesDef.prevMenu = currentMenu; - if (gamedata->challengegrid) + if (gamedata->challengegrid && !challengesmenu.pending) { UINT8 selection[MAXUNLOCKABLES]; UINT8 numunlocks = 0; From a33d6d92350f57fa0becce82f71126efe2109de0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 3 Dec 2022 17:29:14 +0000 Subject: [PATCH 27/95] Fix level/cup unlocks - M_MapLocked - If a level has a cup, will return true if that cup is locked - will return always false in marathon mode (not yet accessible, that'll be another branch's work, but thinking ahead) - Getting rid of a bunch of index fudging caused by SINT8 rather than UINT8 - we can use MAXUNLOCKABLES as the special invalid value --- src/deh_soc.c | 12 ++++++++---- src/dehacked.c | 2 +- src/doomstat.h | 4 ++-- src/g_game.c | 2 +- src/k_menudraw.c | 6 +++--- src/k_menufunc.c | 4 ++-- src/m_cond.c | 16 ++++++++++++++-- src/p_setup.c | 2 +- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d63301901..87cc55328 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1183,8 +1183,10 @@ void readlevelheader(MYFILE *f, char * name) mapheaderinfo[num]->numlaps = (UINT8)i; else if (fastcmp(word, "UNLOCKABLE")) { - if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something - mapheaderinfo[num]->unlockrequired = (SINT8)i - 1; + if (i == 0 || word2[0] == 'F' || word2[0] == 'N') + mapheaderinfo[num]->unlockrequired = MAXUNLOCKABLES; + else if (i > 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something + mapheaderinfo[num]->unlockrequired = (UINT8)(i-1); else deh_warning("Level header %d: invalid unlockable number %d", num, i); } @@ -3087,8 +3089,10 @@ void readcupheader(MYFILE *f, cupheader_t *cup) } else if (fastcmp(word, "UNLOCKABLE")) { - if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something - cup->unlockrequired = (SINT8)i - 1; + if (i == 0 || word2[0] == 'F' || word2[0] == 'N') + cup->unlockrequired = MAXUNLOCKABLES; + else if (i > 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something + cup->unlockrequired = (UINT8)(i-1); else deh_warning("%s Cup: invalid unlockable number %d", cup->name, i); } diff --git a/src/dehacked.c b/src/dehacked.c index 005924ef4..b2fd16e03 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -486,7 +486,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); cup->id = numkartcupheaders; - cup->unlockrequired = -1; + cup->unlockrequired = MAXUNLOCKABLES; deh_strlcpy(cup->name, word2, sizeof(cup->name), va("Cup header %s: name", word2)); if (prev != NULL) diff --git a/src/doomstat.h b/src/doomstat.h index 55644bfb2..d7b09f060 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -355,7 +355,7 @@ typedef struct cupheader_s UINT8 numlevels; ///< Number of levels defined in levellist UINT8 numbonus; ///< Number of bonus stages defined UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) - SINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required. + UINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required. struct cupheader_s *next; ///< Next cup in linked list } cupheader_t; @@ -391,7 +391,7 @@ typedef struct // Selection metadata char keywords[33]; ///< Keywords separated by space to search for. 32 characters. - SINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no. + UINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no. UINT8 levelselect; ///< Is this map available in the level select? If so, which map list is it available in? UINT16 menuflags; ///< LF2_flags: options that affect record attack menus diff --git a/src/g_game.c b/src/g_game.c index bafbf2ec3..f00ffcbe5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3870,7 +3870,7 @@ static void G_GetNextMap(void) while (cup) { // Not unlocked? Grab the next result afterwards - if (!marathonmode && cup->unlockrequired != -1 && !gamedata->unlocked[cup->unlockrequired]) + if (!marathonmode && cup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[cup->unlockrequired]) { cup = cup->next; gettingresult = 1; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index c72871577..a3cae5651 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1911,7 +1911,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (cup && (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired])) + if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired])) { i = (cupgrid.previewanim / 82) % cup->numlevels; while (x < BASEVIDWIDTH + pad) @@ -1949,7 +1949,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) if (cup) { - boolean unlocked = (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired]); + boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); @@ -2017,7 +2017,7 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (iconcup->unlockrequired != -1 && !gamedata->unlocked[iconcup->unlockrequired]) + if (iconcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[iconcup->unlockrequired]) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 07af02502..b1c34c19f 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3459,7 +3459,7 @@ static void M_LevelListFromGametype(INT16 gt) while (cup) { - if (cup->unlockrequired == -1 || gamedata->unlocked[cup->unlockrequired]) + if (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]) { highestid = cup->id; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) @@ -3593,7 +3593,7 @@ void M_CupSelectHandler(INT32 choice) M_SetMenuDelay(pid); if ((!newcup) - || (newcup && newcup->unlockrequired != -1 && !gamedata->unlocked[newcup->unlockrequired]) + || (newcup && newcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[newcup->unlockrequired]) || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); diff --git a/src/m_cond.c b/src/m_cond.c index 4bf644e23..46f81cfc8 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -761,11 +761,23 @@ UINT8 M_MapLocked(INT32 mapnum) // That just makes hosts' lives hell. if (dedicated) return false; + + // No skipping over any part of your marathon. + if (marathonmode) + return false; - if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) + if (!mapheaderinfo[mapnum-1]) return false; - if (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired]) + if (mapheaderinfo[mapnum-1]->cup) + { + if ((mapheaderinfo[mapnum-1]->cup->unlockrequired < MAXUNLOCKABLES) + && (!gamedata->unlocked[mapheaderinfo[mapnum-1]->cup->unlockrequired])) + return true; + } + + if ((mapheaderinfo[mapnum-1]->unlockrequired < MAXUNLOCKABLES) + && (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired])) return true; return false; diff --git a/src/p_setup.c b/src/p_setup.c index 9c06e7038..3e9747505 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -400,7 +400,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->palette = UINT16_MAX; mapheaderinfo[num]->encorepal = UINT16_MAX; mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT; - mapheaderinfo[num]->unlockrequired = -1; + mapheaderinfo[num]->unlockrequired = MAXUNLOCKABLES; mapheaderinfo[num]->levelselect = 0; mapheaderinfo[num]->levelflags = 0; mapheaderinfo[num]->menuflags = 0; From fefaee198294960373c2ad0c42364bd565667238 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 4 Dec 2022 01:09:08 +0000 Subject: [PATCH 28/95] SECRET_FOLLOWER Completely clientside, unlike SECRET_SKIN. Followers have no gameplay function, and it saves us having to write even more to our demos/netsaves. Replaces SECRET_WARP. Also: - Now don't apply skins or followers from profiles if the skin is locked - instead, get the closest skin in stats. (Same function as from demos!) - If you're looking at a profile and the skin or follower are locked, make them solid colour (TC_BLINK). - Don't show the ring cursor before you've selected a profile. --- src/d_netcmd.c | 7 ++- src/deh_soc.c | 4 +- src/g_demo.c | 43 +----------------- src/k_follower.c | 44 +++++++++++++++++++ src/k_follower.h | 16 +++++++ src/k_menu.h | 3 ++ src/k_menudraw.c | 75 ++++++++++++++++++++++---------- src/k_menufunc.c | 110 +++++++++++++++++++++++++++++------------------ src/m_cond.c | 32 +++++++++++++- src/m_cond.h | 3 +- src/r_skins.c | 45 +++++++++++++++++++ src/r_skins.h | 1 + 12 files changed, 271 insertions(+), 112 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f6ef19d81..cb144ab45 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1535,7 +1535,8 @@ static void SendNameAndColor(UINT8 n) K_KartResetPlayerColor(player); - if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin, false)) + foundskin = R_SkinAvailable(cv_skin[n].string); + if (foundskin != -1 && R_SkinUsable(playernum, foundskin, false)) { SetPlayerSkin(playernum, cv_skin[n].string); CV_StealthSet(&cv_skin[n], skins[foundskin].name); @@ -1553,6 +1554,8 @@ static void SendNameAndColor(UINT8 n) // Update follower for local games: foundskin = K_FollowerAvailable(cv_follower[n].string); + if (!K_FollowerUsable(foundskin)) + foundskin = -1; CV_StealthSet(&cv_follower[n], (foundskin == -1) ? "None" : followers[foundskin].name); cv_follower[n].value = foundskin; K_SetFollowerByNum(playernum, foundskin); @@ -1587,7 +1590,7 @@ static void SendNameAndColor(UINT8 n) } cv_follower[n].value = K_FollowerAvailable(cv_follower[n].string); - if (cv_follower[n].value < 0) + if (cv_follower[n].value < 0 || !K_FollowerUsable(cv_follower[n].value)) { CV_StealthSet(&cv_follower[n], "None"); cv_follower[n].value = -1; diff --git a/src/deh_soc.c b/src/deh_soc.c index 87cc55328..9469e3858 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2265,8 +2265,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_HEADER; else if (fastcmp(word2, "SKIN")) unlockables[num].type = SECRET_SKIN; - else if (fastcmp(word2, "WARP")) - unlockables[num].type = SECRET_WARP; + else if (fastcmp(word2, "FOLLOWER")) + unlockables[num].type = SECRET_FOLLOWER; else if (fastcmp(word2, "TIMEATTACK")) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) diff --git a/src/g_demo.c b/src/g_demo.c index c6ee247e2..9d4f1123a 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -212,47 +212,6 @@ void G_LoadMetal(UINT8 **buffer) metal_p = metalbuffer + READUINT32(*buffer); } -// Finds a skin with the closest stats if the expected skin doesn't exist. -static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags) -{ - INT32 i, closest_skin = 0; - UINT8 closest_stats, stat_diff; - boolean doflagcheck = true; - UINT32 flagcheck = flags; - -flaglessretry: - closest_stats = UINT8_MAX; - - for (i = 0; i < numskins; i++) - { - stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight); - if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck) - { - continue; - } - if (stat_diff < closest_stats) - { - closest_stats = stat_diff; - closest_skin = i; - } - } - - if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX)) - { - // Just grab *any* SF_IRONMAN if we don't get it on the first pass. - if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN)) - { - flagcheck = SF_IRONMAN; - } - - doflagcheck = false; - - goto flaglessretry; - } - - return closest_skin; -} - void G_ReadDemoExtraData(void) { INT32 p, extradata, i; @@ -2321,7 +2280,7 @@ static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean } else { - result = GetSkinNumClosestToStats(skinlist[i].kartspeed, skinlist[i].kartweight, skinlist[i].flags); + result = GetSkinNumClosestToStats(skinlist[i].kartspeed, skinlist[i].kartweight, skinlist[i].flags, true); } } diff --git a/src/k_follower.c b/src/k_follower.c index 636620ec8..b340ba266 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -11,6 +11,7 @@ #include "r_skins.h" #include "p_local.h" #include "p_mobj.h" +#include "m_cond.h" INT32 numfollowers = 0; follower_t followers[MAXSKINS]; @@ -38,6 +39,49 @@ INT32 K_FollowerAvailable(const char *name) return -1; } +/*-------------------------------------------------- + INT32 K_FollowerUsable(INT32 followernum) + + See header file for description. +--------------------------------------------------*/ +boolean K_FollowerUsable(INT32 skinnum) +{ + // Unlike R_SkinUsable, not netsynced. + // Solely used to prevent an invalid value being sent over the wire. + UINT8 i; + INT32 fid; + + if (skinnum == -1 || demo.playback) + { + // Simplifies things elsewhere, since there's already plenty of checks for less-than-0... + return true; + } + + // Determine if this follower is supposed to be unlockable or not + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (unlockables[i].type != SECRET_FOLLOWER) + continue; + + fid = M_UnlockableFollowerNum(&unlockables[i]); + + if (fid != skinnum) + continue; + + // i is now the unlockable index, we can use this later + break; + } + + if (i == MAXUNLOCKABLES) + { + // Didn't trip anything, so we can use this follower. + return true; + } + + // Use the unlockables table directly + return (boolean)(gamedata->unlocked[i]); +} + /*-------------------------------------------------- boolean K_SetFollowerByName(INT32 playernum, const char *skinname) diff --git a/src/k_follower.h b/src/k_follower.h index 3c1e2a7d1..323f80b22 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -115,6 +115,22 @@ extern followercategory_t followercategories[MAXFOLLOWERCATEGORIES]; INT32 K_FollowerAvailable(const char *name); +/*-------------------------------------------------- + boolean K_FollowerUsable(INT32 followernum); + + Check if a follower is usable or not. + + Input Arguments:- + skinnum - The follower's skin ID + + Return:- + true if it was a valid follower, + otherwise false. +--------------------------------------------------*/ + +boolean K_FollowerUsable(INT32 skinnum); + + /*-------------------------------------------------- boolean K_SetFollowerByName(INT32 playernum, const char *skinname) diff --git a/src/k_menu.h b/src/k_menu.h index 43a4dd90d..49576eaad 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -596,6 +596,9 @@ extern struct setup_chargrid_s { UINT8 numskins; } setup_chargrid[9][9]; +extern UINT8 setup_followercategories[MAXFOLLOWERCATEGORIES][2]; +extern UINT8 setup_numfollowercategories; + typedef enum { CSSTEP_NONE = 0, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a3cae5651..ea0bed6e4 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -981,10 +981,10 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) numoptions = nummenucolors; break; case CSSTEP_FOLLOWERCATEGORY: - numoptions = numfollowercategories+1; + numoptions = setup_numfollowercategories+1; break; case CSSTEP_FOLLOWER: - numoptions = followercategories[p->followercategory].numincategory; + numoptions = setup_followercategories[p->followercategory][0]; break; case CSSTEP_FOLLOWERCOLORS: numoptions = nummenucolors+2; @@ -1084,7 +1084,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) } else { - fc = &followercategories[n - 1]; + fc = &followercategories[setup_followercategories[n - 1][1]]; patch = W_CachePatchName(fc->icon, PU_CACHE); } @@ -1111,7 +1111,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) n = numfollowers-1; if (n == startfollowern) break; - if (followers[n].category == p->followercategory) + if (followers[n].category == setup_followercategories[p->followercategory][1]) r--; } l = r = n; @@ -1126,7 +1126,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) if (l == startfollowern) break; } - while (followers[l].category != p->followercategory); + while (followers[l].category != setup_followercategories[p->followercategory][1]); n = l; } else @@ -1139,7 +1139,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) if (r == startfollowern) break; } - while (followers[r].category != p->followercategory); + while (followers[r].category != setup_followercategories[p->followercategory][1]); n = r; } @@ -1266,7 +1266,7 @@ static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, boolean charf // Returns false is the follower shouldn't be rendered. // 'num' can be used to directly specify the follower number, but doing this will not animate it. // if a setup_player_t is specified instead, its data will be used to animate the follower sprite. -static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charflip, INT32 addflags, UINT16 color, setup_player_t *p) +static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charflip, INT32 addflags, UINT8 *colormap, setup_player_t *p) { spritedef_t *sprdef; @@ -1276,7 +1276,6 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charfli state_t *usestate; UINT32 useframe; follower_t fl; - UINT8 *colormap = NULL; UINT8 rotation = (charflip ? 1 : 7); if (p != NULL) @@ -1320,13 +1319,13 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charfli if (p != NULL) { - sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * p->follower_timer)>>ANGLETOFINESHIFT) & FINEMASK)); - color = K_GetEffectiveFollowerColor( + UINT16 color = K_GetEffectiveFollowerColor( (p->mdepth < CSSTEP_FOLLOWERCOLORS) ? fl.defaultcolor : p->followercolor, p->color); + sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * p->follower_timer)>>ANGLETOFINESHIFT) & FINEMASK)); + colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE); } - colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE); V_DrawFixedPatch((x*FRACUNIT), ((y-12)*FRACUNIT) + sine, fl.scale, addflags, patch, colormap); return true; @@ -1540,6 +1539,9 @@ static void M_DrawCharSelectCursor(UINT8 num) INT16 x, y; INT16 quadx, quady; + if (p->mdepth < CSSTEP_ASKCHANGES) + return; + quadx = 4 * (p->gridx / 3); quady = 4 * (p->gridy / 3); @@ -1583,6 +1585,7 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) patch_t *card = W_CachePatchName("PR_CARD", PU_CACHE); patch_t *cardbot = W_CachePatchName("PR_CARDB", PU_CACHE); patch_t *pwrlv = W_CachePatchName("PR_PWR", PU_CACHE); + UINT16 truecol = SKINCOLOR_BLACK; UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE); INT32 skinnum = -1; INT32 powerlevel = -1; @@ -1591,7 +1594,8 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) if (p != NULL && p->version) { - colormap = R_GetTranslationColormap(TC_DEFAULT, PR_GetProfileColor(p), GTC_CACHE); + truecol = PR_GetProfileColor(p); + colormap = R_GetTranslationColormap(TC_DEFAULT, truecol, GTC_CACHE); strcpy(pname, p->profilename); skinnum = R_SkinAvailable(p->skinname); powerlevel = p->powerlevels[0]; // Only display race power level. @@ -1600,7 +1604,10 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) // check setup_player for colormap for the card. // we'll need to check again for drawing afterwards unfortunately. if (sp->mdepth >= CSSTEP_CHARS) + { + truecol = PR_GetProfileColor(p); colormap = R_GetTranslationColormap(skinnum, sp->color, GTC_MENUCACHE); + } // Card V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, greyedout ? V_TRANSLUCENT : 0, card, colormap); @@ -1634,25 +1641,34 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) { UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, sp->color);; patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE); - UINT8 *fcolormap; - - fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); } } } else if (skinnum > -1) // otherwise, read from profile. { - UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color);; + UINT8 *ccolormap, *fcolormap; + UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color); UINT8 fln = K_FollowerAvailable(p->follower); - if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, colormap)) - V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); + if (R_SkinUsable(g_localplayers[0], skinnum, false)) + ccolormap = colormap; + else + ccolormap = R_GetTranslationColormap(TC_BLINK, truecol, GTC_MENUCACHE); - if (M_DrawFollowerSprite(x-22 - 16, y+119, fln, false, 0, col, NULL)) + fcolormap = R_GetTranslationColormap( + (K_FollowerUsable(fln) ? TC_DEFAULT : TC_BLINK), + col, GTC_MENUCACHE); + + if (M_DrawCharacterSprite(x-22, y+119, skinnum, false, false, 0, ccolormap)) + { + V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], ccolormap); + } + + if (M_DrawFollowerSprite(x-22 - 16, y+119, fln, false, 0, fcolormap, NULL)) { patch_t *ico = W_CachePatchName(followers[fln].icon, PU_CACHE); - UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); } } @@ -1718,8 +1734,11 @@ void M_DrawCharacterSelect(void) { for (k = 0; k < setup_numplayers; k++) { - if (setup_player[k].gridx == i && setup_player[k].gridy == j) - break; // k == setup_numplayers means no one has it selected + if (setup_player[k].mdepth < CSSTEP_ASKCHANGES) + continue; + if (setup_player[k].gridx != i || setup_player[k].gridy != j) + continue; + break; // k == setup_numplayers means no one has it selected } skin = setup_chargrid[i][j].skinlist[setup_page]; @@ -4532,7 +4551,6 @@ void M_DrawChallenges(void) } else switch (ref->type) { - // add all SECRET_ENCORE, etc up here before SECRET_SKIN case SECRET_SKIN: { INT32 skin = M_UnlockableSkinNum(ref); @@ -4543,6 +4561,17 @@ void M_DrawChallenges(void) } break; } + case SECRET_FOLLOWER: + { + INT32 skin = M_UnlockableFollowerNum(ref); + if (skin != -1) + { + UINT16 col = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + colormap = R_GetTranslationColormap(skin, col, GTC_MENUCACHE); + pat = W_CachePatchName(followers[skin].icon, PU_CACHE); + } + break; + } default: { pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index b1c34c19f..86cc29ffe 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2053,7 +2053,10 @@ void M_QuitSRB2(INT32 choice) struct setup_chargrid_s setup_chargrid[9][9]; setup_player_t setup_player[MAXSPLITSCREENPLAYERS]; -struct setup_explosions_s setup_explosions[48]; +struct setup_explosions_s setup_explosions[CSEXPLOSIONS]; + +UINT8 setup_followercategories[MAXFOLLOWERCATEGORIES][2]; +UINT8 setup_numfollowercategories; UINT8 setup_numplayers = 0; // This variable is very important, it was extended to determine how many players exist in ALL menus. tic_t setup_animcounter = 0; @@ -2065,64 +2068,61 @@ UINT8 setup_maxpage = 0; // For charsel page to identify alts easier... static void M_SetupProfileGridPos(setup_player_t *p) { profile_t *pr = PR_GetProfile(p->profilen); - INT32 i; + INT32 i = R_SkinAvailable(pr->skinname); + INT32 alt = 0; // Hey it's my character's name! // While we're here, read follower values. p->followern = K_FollowerAvailable(pr->follower); - if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories) - p->followercategory = -1; + if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) + p->followercategory = p->followern = -1; else p->followercategory = followers[p->followern].category; p->followercolor = pr->followercolor; - // Now position the grid for skin - for (i = 0; i < numskins; i++) + if (!R_SkinUsable(g_localplayers[0], i, false)) { - if (!(strcmp(pr->skinname, skins[i].name))) - { - INT32 alt = 0; // Hey it's my character's name! - p->gridx = skins[i].kartspeed-1; - p->gridy = skins[i].kartweight-1; - - // Now this put our cursor on the good alt - while (setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) - alt++; - - p->clonenum = alt; - p->color = PR_GetProfileColor(pr); - return; // we're done here - } + i = GetSkinNumClosestToStats(skins[i].kartspeed, skins[i].kartweight, skins[i].flags, false); } + + // Now position the grid for skin + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; + + // Now this put our cursor on the good alt + while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) + alt++; + + p->clonenum = alt; + p->color = PR_GetProfileColor(pr); } static void M_SetupMidGameGridPos(setup_player_t *p, UINT8 num) { - INT32 i; + INT32 i = R_SkinAvailable(cv_skin[num].zstring); + INT32 alt = 0; // Hey it's my character's name! // While we're here, read follower values. p->followern = cv_follower[num].value; p->followercolor = cv_followercolor[num].value; + if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) + p->followercategory = p->followern = -1; + else + p->followercategory = followers[p->followern].category; + // Now position the grid for skin - for (i = 0; i < numskins; i++) - { - if (!(strcmp(cv_skin[num].zstring, skins[i].name))) - { - INT32 alt = 0; // Hey it's my character's name! - p->gridx = skins[i].kartspeed-1; - p->gridy = skins[i].kartweight-1; + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; - // Now this put our cursor on the good alt - while (setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) - alt++; + // Now this put our cursor on the good alt + while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) + alt++; - p->clonenum = alt; - p->color = cv_playercolor[num].value; - return; // we're done here - } - } + p->clonenum = alt; + p->color = cv_playercolor[num].value; + return; // we're done here } @@ -2219,6 +2219,32 @@ void M_CharacterSelectInit(void) } } + setup_numfollowercategories = 0; + for (i = 0; i < numfollowercategories; i++) + { + if (followercategories[i].numincategory == 0) + continue; + + setup_followercategories[setup_numfollowercategories][0] = 0; + + for (j = 0; j < numfollowers; j++) + { + if (followers[j].category != i) + continue; + + if (!K_FollowerUsable(j)) + continue; + + setup_followercategories[setup_numfollowercategories][0]++; + setup_followercategories[setup_numfollowercategories][1] = i; + } + + if (!setup_followercategories[setup_numfollowercategories][0]) + continue; + + setup_numfollowercategories++; + } + setup_page = 0; } @@ -2843,7 +2869,7 @@ static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) if (menucmd[num].dpad_lr > 0) { p->followercategory++; - if (p->followercategory >= numfollowercategories) + if (p->followercategory >= setup_numfollowercategories) p->followercategory = -1; p->rotate = CSROTATETICS; @@ -2854,7 +2880,7 @@ static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) { p->followercategory--; if (p->followercategory < -1) - p->followercategory = numfollowercategories-1; + p->followercategory = setup_numfollowercategories-1; p->rotate = -CSROTATETICS; p->delay = CSROTATETICS; @@ -2876,7 +2902,9 @@ static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) if (p->followern < 0 || followers[p->followern].category != p->followercategory) { p->followern = 0; - while (p->followern < numfollowers && followers[p->followern].category != p->followercategory) + while (p->followern < numfollowers + && (followers[p->followern].category != setup_followercategories[p->followercategory][1] + || !K_FollowerUsable(p->followern))) p->followern++; } @@ -2931,7 +2959,7 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) if (p->followern == startfollowern) break; } - while (followers[p->followern].category != p->followercategory); + while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); M_GetFollowerState(p); @@ -2949,7 +2977,7 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) if (p->followern == startfollowern) break; } - while (followers[p->followern].category != p->followercategory); + while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); M_GetFollowerState(p); diff --git a/src/m_cond.c b/src/m_cond.c index 46f81cfc8..74e8c09fd 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -20,6 +20,7 @@ #include "g_game.h" // record info #include "r_skins.h" // numskins +#include "k_follower.h" #include "r_draw.h" // R_GetColorByName #include "k_pwrlv.h" @@ -860,7 +861,7 @@ INT32 M_UnlockableSkinNum(unlockable_t *unlock) return -1; } - if (unlock->stringVar && strcmp(unlock->stringVar, "")) + if (unlock->stringVar && unlock->stringVar[0]) { // Get the skin from the string. INT32 skinnum = R_SkinAvailable(unlock->stringVar); @@ -880,6 +881,35 @@ INT32 M_UnlockableSkinNum(unlockable_t *unlock) return -1; } +// Gets the skin number for a SECRET_FOLLOWER unlockable. +INT32 M_UnlockableFollowerNum(unlockable_t *unlock) +{ + if (unlock->type != SECRET_FOLLOWER) + { + // This isn't a follower unlockable... + return -1; + } + + if (unlock->stringVar && unlock->stringVar[0]) + { + // Get the skin from the string. + INT32 skinnum = K_FollowerAvailable(unlock->stringVar); + if (skinnum != -1) + { + return skinnum; + } + } + + if (unlock->variable >= 0 && unlock->variable < numfollowers) + { + // Use the number directly. + return unlock->variable; + } + + // Invalid follower unlockable. + return -1; +} + // ---------------- // Misc Emblem shit // ---------------- diff --git a/src/m_cond.h b/src/m_cond.h index f839155fa..d7445cd68 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -96,7 +96,7 @@ typedef struct #define SECRET_HEADER 1 // Does nothing on its own, just serves as a header for the menu #define SECRET_SKIN 2 // Allow this character to be selected -#define SECRET_WARP 3 // Selectable warp (todo Followers) +#define SECRET_FOLLOWER 3 // Allow this follower to be selected #define SECRET_EXTRAEMBLEM 4 // Extra Emblems (formerly extraemblem_t) @@ -201,6 +201,7 @@ UINT8 M_GotEnoughEmblems(INT32 number); UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); +INT32 M_UnlockableFollowerNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); #define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a]) diff --git a/src/r_skins.c b/src/r_skins.c index 03753d1b5..3fea4b2cc 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -505,6 +505,51 @@ void ClearFakePlayerSkin(player_t* player) player->fakeskin = MAXSKINS; } +// Finds a skin with the closest stats if the expected skin doesn't exist. +INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock) +{ + INT32 i, closest_skin = 0; + UINT8 closest_stats, stat_diff; + boolean doflagcheck = true; + UINT32 flagcheck = flags; + +flaglessretry: + closest_stats = stat_diff = UINT8_MAX; + + for (i = 0; i < numskins; i++) + { + if (!unlock && !R_SkinUsable(-1, i, false)) + { + continue; + } + stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight); + if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck) + { + continue; + } + if (stat_diff < closest_stats) + { + closest_stats = stat_diff; + closest_skin = i; + } + } + + if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX)) + { + // Just grab *any* SF_IRONMAN if we don't get it on the first pass. + if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN)) + { + flagcheck = SF_IRONMAN; + } + + doflagcheck = false; + + goto flaglessretry; + } + + return closest_skin; +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // diff --git a/src/r_skins.h b/src/r_skins.h index 7b32d3e0c..fcc710f2b 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -85,6 +85,7 @@ void SetFakePlayerSkin(player_t* player, INT32 skinnum); void SetRandomFakePlayerSkin(player_t* player, boolean fast); void ClearFakePlayerSkin(player_t* player); boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins); +INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock); UINT8 *R_GetSkinAvailabilities(boolean demolock); INT32 R_SkinAvailable(const char *name); From be38ba15111f9dd88a8eff74fcbfbbcae07b9d42 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 4 Dec 2022 15:30:38 +0000 Subject: [PATCH 29/95] A heaping of polish * Challengegrid now loops and scrolls horizontally. - Scroll position currently tied directly to selected row/column, but can (and will) be seperated relatively easily later. - In `DEVELOP` builds always scrolls - but in release builds, if someone makes a custom gamedata with less than 20 challengegrid columns (BASEVIDWIDTH/16), it'll remain static. * Challenge ticker now automatically unlocks all pending at a rate of ~one per second. - Based on VC discussion and Kirby Air Ride. - Since no user input necessary, screen is now part-faded when unlocks are pending. * Drawing a specific grid tile is now its own `k_menudraw.c` function. * There is now a light grey grid around unselected tiles. * Unlock explosions now use the tile's colour or, if not provided, the current profile's color. --- src/k_menu.h | 5 +- src/k_menudef.c | 2 +- src/k_menudraw.c | 271 +++++++++++++++++++++++++++++------------------ src/k_menufunc.c | 100 +++++++++-------- src/m_cond.c | 6 +- src/m_cond.h | 6 ++ 6 files changed, 241 insertions(+), 149 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 49576eaad..81de34e74 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -656,7 +656,7 @@ extern UINT8 setup_maxpage; #define CSEXPLOSIONS 48 extern struct setup_explosions_s { - UINT16 x, y; + INT16 x, y; UINT8 tics; UINT16 color; } setup_explosions[CSEXPLOSIONS]; @@ -1084,7 +1084,7 @@ void M_DrawAddons(void); // Challenges menu: #define UNLOCKTIME 5 -#define MAXUNLOCKTIME 35 +#define MAXUNLOCKTIME TICRATE // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1092,6 +1092,7 @@ extern struct challengesmenu_s { tic_t ticker; // How long the menu's been open for INT16 offset; // To make the icons move smoothly when we transition! + UINT8 fade; UINT8 currentunlock; tic_t unlockanim; diff --git a/src/k_menudef.c b/src/k_menudef.c index 53c55a415..9cbbdf932 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1756,7 +1756,7 @@ menu_t MISC_ChallengesDef = { &MainDef, 0, MISC_ChallengesMenu, - 14, 32, + BASEVIDWIDTH/2, 32, 0, 0, 98, 0, M_DrawChallenges, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ea0bed6e4..625deb577 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1480,7 +1480,7 @@ static void M_DrawCharSelectPreview(UINT8 num) } } -static void M_DrawCharSelectExplosions(boolean charsel, UINT16 basex, UINT16 basey) +static void M_DrawCharSelectExplosions(boolean charsel, INT16 basex, INT16 basey) { UINT8 i; INT16 quadx = 0, quady = 0; @@ -4480,16 +4480,123 @@ void M_DrawAddons(void) #undef addonsseperation -void M_DrawChallenges(void) +#define challengesbordercolor 8 + +static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) { - INT32 x = currentMenu->x, y = currentMenu->y; - UINT8 i, j, id, num, work; - const char *str; - INT16 offset; unlockable_t *ref = NULL; patch_t *pat; UINT8 *colormap; fixed_t siz; + UINT8 id, num, work; + + id = (i * CHALLENGEGRIDHEIGHT) + j; + num = gamedata->challengegrid[id]; + + // Empty spots in the grid are always unconnected. + if (num >= MAXUNLOCKABLES) + { + V_DrawFill(x, y, 16, 16, 27); + ref = NULL; + goto drawborder; + } + + // Okay, this is what we want to draw. + ref = &unlockables[num]; + + // ...unless we simply aren't unlocked yet. + if ((gamedata->unlocked[num] == false) + || (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME)) + { + work = (ref->majorunlock) ? 2 : 1; + V_DrawFill(x, y, 16*work, 16*work, + ((challengesmenu.extradata[id] == CHE_HINT) ? 134 : 12)); + goto drawborder; + } + + pat = missingpat; + colormap = NULL; + if (ref->icon != NULL && ref->icon[0]) + { + pat = W_CachePatchName(ref->icon, PU_CACHE); + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, ref->color, GTC_MENUCACHE); + } + } + else switch (ref->type) + { + case SECRET_SKIN: + { + INT32 skin = M_UnlockableSkinNum(ref); + if (skin != -1) + { + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + pat = faceprefix[skin][(ref->majorunlock) ? FACE_WANTED : FACE_RANK]; + } + break; + } + case SECRET_FOLLOWER: + { + INT32 skin = M_UnlockableFollowerNum(ref); + if (skin != -1) + { + UINT16 col = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + colormap = R_GetTranslationColormap(skin, col, GTC_MENUCACHE); + pat = W_CachePatchName(followers[skin].icon, PU_CACHE); + } + break; + } + default: + { + pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + //CONS_Printf(" color for %d is %s\n", num, skincolors[unlockables[num].color].name); + colormap = R_GetTranslationColormap(TC_RAINBOW, ref->color, GTC_MENUCACHE); + } + break; + } + } + + siz = (SHORT(pat->width) << FRACBITS); + siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); + + V_DrawFixedPatch( + x*FRACUNIT, y*FRACUNIT, + siz, + 0, pat, + colormap + ); + +drawborder: + if (!hili) + { + work = 16 * ((ref && ref->majorunlock) ? 2 : 1); + // Horizontal + V_DrawFill(x, y , work, 1, challengesbordercolor); + V_DrawFill(x, y + work-1, work, 1, challengesbordercolor); + // Vertical + V_DrawFill(x , y+1, 1, work-2, challengesbordercolor); + V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor); + return; + } + + V_DrawFixedPatch( + x*FRACUNIT, y*FRACUNIT, + ((ref != NULL && ref->majorunlock) ? FRACUNIT*2 : FRACUNIT), + 0, kp_facehighlight[(challengesmenu.ticker / 4) % 8], + NULL + ); +} + +void M_DrawChallenges(void) +{ + INT32 x = currentMenu->x, explodex, selectx; + INT32 y = currentMenu->y; + INT16 i, j; + const char *str; + INT16 offset; { patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); @@ -4498,119 +4605,79 @@ void M_DrawChallenges(void) if (!gamedata->challengegrid) { - V_DrawString(x, y, V_REDMAP, "No challenges available!?"); + V_DrawCenteredString(x, y, V_REDMAP, "No challenges available!?"); goto challengedesc; } - for (i = 0; i < gamedata->challengegridwidth; i++) + x -= 16; + + if (challengegridloops) + { + i = challengesmenu.col; + explodex = x - (i*16); + + while (x < BASEVIDWIDTH-16) + { + i = (i + 1) % gamedata->challengegridwidth; + x += 16; + } + } + else + { + if (gamedata->challengegridwidth & 1) + x += 8; + + i = gamedata->challengegridwidth-1; + explodex = x - (i*16)/2; + x += (i*16)/2; + + V_DrawFill(0, currentMenu->y, explodex, (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); + V_DrawFill((x+16), currentMenu->y, BASEVIDWIDTH - (x+16), (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); + } + + selectx = explodex + (challengesmenu.hilix*16); + + V_DrawFill(0, (currentMenu->y)-1 , BASEVIDWIDTH, 1, challengesbordercolor); + V_DrawFill(0, (currentMenu->y) + (CHALLENGEGRIDHEIGHT*16), BASEVIDWIDTH, 1, challengesbordercolor); + while (i >= 0 && x >= -32) { y = currentMenu->y-16; for (j = 0; j < CHALLENGEGRIDHEIGHT; j++) { y += 16; - id = (i * CHALLENGEGRIDHEIGHT) + j; - if (challengesmenu.extradata[id] & CHE_DONTDRAW) + if (challengesmenu.extradata[(i * CHALLENGEGRIDHEIGHT) + j] & CHE_DONTDRAW) { continue; } - num = gamedata->challengegrid[id]; - - // Empty spots in the grid are always unconnected. - if (num >= MAXUNLOCKABLES) + if (x == selectx && j == challengesmenu.hiliy) { - V_DrawFill(x, y, 16, 16, 27); - ref = NULL; - goto drawborder; - } - - // Okay, this is what we want to draw. - ref = &unlockables[num]; - - // ...unless we simply aren't unlocked yet. - if ((gamedata->unlocked[num] == false) - || (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME)) - { - work = (ref->majorunlock) ? 2 : 1; - V_DrawFill(x, y, 16*work, 16*work, - ((challengesmenu.extradata[id] == CHE_HINT) ? 130 : 12) - + ((i & work) != (j & work) ? 0 : 2) + (work-1)); // funny checkerboard pattern - goto drawborder; - } - - pat = missingpat; - colormap = NULL; - if (ref->icon != NULL) - { - pat = W_CachePatchName(ref->icon, PU_CACHE); - if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) - { - colormap = R_GetTranslationColormap(TC_DEFAULT, ref->color, GTC_MENUCACHE); - } - } - else switch (ref->type) - { - case SECRET_SKIN: - { - INT32 skin = M_UnlockableSkinNum(ref); - if (skin != -1) - { - colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); - pat = faceprefix[skin][(ref->majorunlock) ? FACE_WANTED : FACE_RANK]; - } - break; - } - case SECRET_FOLLOWER: - { - INT32 skin = M_UnlockableFollowerNum(ref); - if (skin != -1) - { - UINT16 col = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); - colormap = R_GetTranslationColormap(skin, col, GTC_MENUCACHE); - pat = W_CachePatchName(followers[skin].icon, PU_CACHE); - } - break; - } - default: - { - pat = W_CachePatchName(va("UN_RR00%c", ref->majorunlock ? 'B' : 'A'), PU_CACHE); - if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) - { - CONS_Printf(" color for %d is %s\n", num, skincolors[unlockables[num].color].name); - colormap = R_GetTranslationColormap(TC_RAINBOW, ref->color, GTC_MENUCACHE); - } - break; - } - } - - siz = (SHORT(pat->width) << FRACBITS); - siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); - - V_DrawFixedPatch( - x*FRACUNIT, y*FRACUNIT, - siz, - 0, pat, - colormap - ); - -drawborder: - if (i != challengesmenu.hilix) - continue; - if (j != challengesmenu.hiliy) continue; + } - V_DrawFixedPatch( - x*FRACUNIT, y*FRACUNIT, - ((ref != NULL && ref->majorunlock) ? FRACUNIT*2 : FRACUNIT), - 0, kp_facehighlight[(challengesmenu.ticker / 4) % 8], - NULL - ); + M_DrawChallengeTile(i, j, x, y, false); + } + + x -= 16; + i--; + if (challengegridloops && i < 0) + { + i = (i + gamedata->challengegridwidth) + % gamedata->challengegridwidth; } - x += 16; } - M_DrawCharSelectExplosions(false, currentMenu->x, currentMenu->y); + if (challengesmenu.fade) + V_DrawFadeScreen(31, challengesmenu.fade); + + M_DrawChallengeTile( + challengesmenu.hilix, + challengesmenu.hiliy, + selectx, + currentMenu->y + (challengesmenu.hiliy*16), + true); + M_DrawCharSelectExplosions(false, explodex, currentMenu->y); challengedesc: y = 120; @@ -4632,6 +4699,6 @@ challengedesc: offset = V_LSTitleLowStringWidth(str, 0) / 2; V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); - if (challengesmenu.unlockanim >= MAXUNLOCKTIME) - V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, va("Press (%c)", challengesmenu.pending ? 'A' : 'B')); + if (!challengesmenu.fade) + V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, "Press (B)"); } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 86cc29ffe..185a47ad3 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2289,21 +2289,11 @@ static void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, if ((y < 0 || y >= maxy)) continue; - if (/*charsel &&*/ (x < 0 || x >= maxx)) - continue; - - /*if (!charsel) + if (charsel || !challengegridloops) { - while (x < 0) - { - x += maxx; - } - - while (x >= maxx) - { - x -= maxx; - } - }*/ + if (x < 0 || x >= maxx) + continue; + } setup_explosions[e].tics = t; setup_explosions[e].color = color; @@ -6960,6 +6950,7 @@ void M_ChallengesTick(void) if (challengesmenu.pending && challengesmenu.requestnew) { + challengesmenu.requestnew = false; if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) { challengesmenu.currentunlock = newunlock; @@ -6991,15 +6982,14 @@ void M_ChallengesTick(void) G_SaveGameData(); } } - else if (challengesmenu.unlockanim >= MAXUNLOCKTIME) + else if (challengesmenu.pending && challengesmenu.fade < 5) + challengesmenu.fade++; + else if (challengesmenu.pending) { - ; - } - else - { - challengesmenu.unlockanim++; - if (challengesmenu.pending - && challengesmenu.currentunlock < MAXUNLOCKABLES + if (++challengesmenu.unlockanim >= MAXUNLOCKTIME) + challengesmenu.requestnew = true; + + if (challengesmenu.currentunlock < MAXUNLOCKABLES && challengesmenu.unlockanim == UNLOCKTIME) { gamedata->unlocked[challengesmenu.currentunlock] = true; @@ -7008,18 +6998,54 @@ void M_ChallengesTick(void) challengesmenu.extradata = M_ChallengeGridExtraData(); S_StartSound(NULL, sfx_s3k4e); - M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row, SKINCOLOR_KETCHUP); - if (unlockables[challengesmenu.currentunlock].majorunlock) { - i = challengesmenu.col+1; - if (i == gamedata->challengegridwidth) - i = 0; - M_SetupReadyExplosions(false, i, challengesmenu.row+1, SKINCOLOR_KETCHUP); + unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; + UINT16 bombcolor = SKINCOLOR_NONE; + + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + bombcolor = ref->color; + } + else switch (ref->type) + { + case SECRET_SKIN: + { + INT32 skin = M_UnlockableSkinNum(ref); + if (skin != -1) + { + bombcolor = skins[skin].prefcolor; + } + break; + } + case SECRET_FOLLOWER: + { + INT32 skin = M_UnlockableFollowerNum(ref); + if (skin != -1) + { + bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + } + break; + } + default: + break; + } + + if (bombcolor == SKINCOLOR_NONE) + { + bombcolor = cv_playercolor[0].value; + } + + i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; + M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row+i, bombcolor); + if (ref->majorunlock) + { + M_SetupReadyExplosions(false, challengesmenu.col+1, challengesmenu.row+(1-i), bombcolor); + } } } } - - challengesmenu.requestnew = false; + else if (challengesmenu.fade > 0) + challengesmenu.fade--; } boolean M_ChallengesInputs(INT32 ch) @@ -7069,18 +7095,8 @@ boolean M_ChallengesInputs(INT32 ch) return true; } #endif - else if (challengesmenu.pending) - { - if (challengesmenu.unlockanim < MAXUNLOCKTIME) - { - ; - } - else if ((M_MenuConfirmPressed(pid) || start)) - { - challengesmenu.requestnew = true; - } - return true; - } + else if (challengesmenu.fade) + ; else { if (M_MenuBackPressed(pid) || start) diff --git a/src/m_cond.c b/src/m_cond.c index 74e8c09fd..20d57d85e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -117,7 +117,9 @@ void M_PopulateChallengeGrid(void) if (nummajorunlocks) { // You lose one from CHALLENGEGRIDHEIGHT because it is impossible to place a 2-high tile on the bottom row. - UINT16 numspots = gamedata->challengegridwidth * (CHALLENGEGRIDHEIGHT-1); + // You lose one from the width if it doesn't loop. + UINT16 numspots = (gamedata->challengegridwidth - (challengegridloops ? 0 : 1)) + * (CHALLENGEGRIDHEIGHT-1); // 0 is row, 1 is column INT16 quickcheck[numspots][2]; @@ -323,7 +325,7 @@ UINT8 *M_ChallengeGridExtraData(void) if (work == num) { - if (!idchange && (i > 0 || gamedata->challengegridwidth > 2)) + if (!idchange && (i > 0 || challengegridloops)) { //CONS_Printf(" %d - %d to left of %d is valid\n", work, tempid, id); // If we haven't already updated our id, it's the one to our left. diff --git a/src/m_cond.h b/src/m_cond.h index d7445cd68..5854808c8 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -121,6 +121,12 @@ typedef struct #define MAXUNLOCKABLES MAXCONDITIONSETS #define CHALLENGEGRIDHEIGHT 5 +#ifdef DEVELOP +#define CHALLENGEGRIDLOOPWIDTH 3 +#else +#define CHALLENGEGRIDLOOPWIDTH (BASEVIDWIDTH/16) +#endif +#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH) // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat From a657a5e59134a1b1a8f14ce8bf283e5a463cbc5b Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 4 Dec 2022 20:58:32 +0000 Subject: [PATCH 30/95] Disjointed scroll and selection * You can be at most 3*16 pixels away from the centerline. * Bonus polish: during unlock sequence, movement is smooth and zips back and forth. --- src/k_menu.h | 8 +++- src/k_menudraw.c | 6 ++- src/k_menufunc.c | 95 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 81de34e74..d6c6534dd 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1085,6 +1085,8 @@ void M_DrawAddons(void); // Challenges menu: #define UNLOCKTIME 5 #define MAXUNLOCKTIME TICRATE +#define RIGHTUNLOCKSCROLL 3 +#define LEFTUNLOCKSCROLL (RIGHTUNLOCKSCROLL-1) // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1092,16 +1094,18 @@ extern struct challengesmenu_s { tic_t ticker; // How long the menu's been open for INT16 offset; // To make the icons move smoothly when we transition! - UINT8 fade; UINT8 currentunlock; tic_t unlockanim; - UINT8 row, col, hilix, hiliy; + SINT8 row, hilix, focusx; + UINT8 col, hiliy; UINT8 *extradata; boolean pending; boolean requestnew; + + UINT8 fade; } challengesmenu; menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 625deb577..2a2adabae 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4611,9 +4611,13 @@ void M_DrawChallenges(void) x -= 16; + x += challengesmenu.offset; + if (challengegridloops) { - i = challengesmenu.col; + if (!challengesmenu.col && challengesmenu.hilix) + x -= gamedata->challengegridwidth*16; + i = challengesmenu.col + challengesmenu.focusx; explodex = x - (i*16); while (x < BASEVIDWIDTH-16) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 185a47ad3..173b2e928 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6929,6 +6929,12 @@ void M_Challenges(INT32 choice) challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + + challengesmenu.focusx = 0; + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && !unlockables[challengesmenu.currentunlock].majorunlock + && M_RandomChance(FRACUNIT/2)) + challengesmenu.focusx--; break; } } @@ -6939,6 +6945,8 @@ void M_Challenges(INT32 choice) void M_ChallengesTick(void) { UINT8 i, newunlock = MAXUNLOCKABLES; + SINT8 work; + boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); challengesmenu.ticker++; @@ -6970,8 +6978,60 @@ void M_ChallengesTick(void) continue; } + work = (challengesmenu.col + challengesmenu.focusx); + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + + if (fresh) + { + challengesmenu.focusx = 0; + if (!unlockables[challengesmenu.currentunlock].majorunlock + && M_RandomChance(FRACUNIT/2)) + challengesmenu.focusx--; + } + else + { + work -= challengesmenu.col; + if (work <= -gamedata->challengegridwidth/2) + work += gamedata->challengegridwidth; + else if (work >= gamedata->challengegridwidth/2) + work -= gamedata->challengegridwidth; + + if (work > 0) + { + if (work > LEFTUNLOCKSCROLL) + { + work -= LEFTUNLOCKSCROLL; + challengesmenu.focusx = LEFTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else if (work < 0) + { + if (work < -RIGHTUNLOCKSCROLL) + { + challengesmenu.focusx = -RIGHTUNLOCKSCROLL; + work += RIGHTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else + { + challengesmenu.focusx = 0; + } + + challengesmenu.offset = -work*16; + } + break; } } @@ -7046,6 +7106,8 @@ void M_ChallengesTick(void) } else if (challengesmenu.fade > 0) challengesmenu.fade--; + + challengesmenu.offset /= 2; } boolean M_ChallengesInputs(INT32 ch) @@ -7087,6 +7149,12 @@ boolean M_ChallengesInputs(INT32 ch) challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + + challengesmenu.focusx = 0; + if (!unlockables[challengesmenu.currentunlock].majorunlock + && M_RandomChance(FRACUNIT/2)) + challengesmenu.focusx--; + break; } @@ -7165,6 +7233,8 @@ boolean M_ChallengesInputs(INT32 ch) i = 2; while (i > 0) { + if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL) + challengesmenu.focusx--; if (challengesmenu.col < gamedata->challengegridwidth-1) { challengesmenu.col++; @@ -7193,6 +7263,8 @@ boolean M_ChallengesInputs(INT32 ch) & CHE_CONNECTEDLEFT) ? 2 : 1; while (i > 0) { + if (challengesmenu.focusx < LEFTUNLOCKSCROLL) + challengesmenu.focusx++; if (challengesmenu.col > 0) { challengesmenu.col--; @@ -7213,20 +7285,25 @@ boolean M_ChallengesInputs(INT32 ch) challengesmenu.hilix = challengesmenu.col; challengesmenu.hiliy = challengesmenu.row; - if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) - { - challengesmenu.hiliy--; - } - if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)) + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && unlockables[challengesmenu.currentunlock].majorunlock) { - if (challengesmenu.hilix > 0) + if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) { - challengesmenu.hilix--; + challengesmenu.hiliy--; } - else + + if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)) { - challengesmenu.hilix = gamedata->challengegridwidth-1; + if (challengesmenu.hilix > 0) + { + challengesmenu.hilix--; + } + else + { + challengesmenu.hilix = gamedata->challengegridwidth-1; + } } } } From d78668e0a6bb01b4d1342fa7e6c4db41249918a7 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 4 Dec 2022 20:59:58 +0000 Subject: [PATCH 31/95] Crash prevention: Only do debugging switcharoo on a tile that has a valid unlockable assigned. --- src/k_menufunc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 173b2e928..325eee093 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -7125,16 +7125,16 @@ boolean M_ChallengesInputs(INT32 ch) #ifdef DEVELOP else if (M_MenuExtraPressed(pid)) // debugging { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = NULL; - gamedata->challengegridwidth = 0; - M_PopulateChallengeGrid(); - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = M_ChallengeGridExtraData(); - challengesmenu.unlockanim = 0; - if (challengesmenu.currentunlock < MAXUNLOCKABLES) { + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + M_PopulateChallengeGrid(); + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); + challengesmenu.unlockanim = 0; + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) { if (gamedata->challengegrid[i] != challengesmenu.currentunlock) From e7c79ab461122fd0179e858d3dfffc967241cbb4 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 9 Dec 2022 17:26:52 +0000 Subject: [PATCH 32/95] `M_CheckNetUnlockByID` System for netsyncing unlocks, inspired by but with nowhere near as many moving parts as (STJr/SRB2!1756). * `gamedata->unlocked[MAXUNLOCKABLES]` is duplicated to `netUnlocked[MAXUNLOCKABLES]` (or all `true` in `dedicated` * New `local` boolean for M_SecretUnlocked * Removed last vestiges of SRB2 special stage token code because it occupied the spot in the netsave we wanted to use. * Correct typing of multiple `m_cond` functions that returned `boolean` constants as `UINT8`s. --- src/command.c | 6 +++--- src/d_clisrv.c | 6 ++++++ src/d_netcmd.c | 4 ++-- src/doomstat.h | 4 ---- src/g_game.c | 12 ++--------- src/k_follower.c | 1 + src/k_menudraw.c | 6 +++--- src/k_menufunc.c | 8 ++++---- src/lua_script.c | 2 -- src/m_cond.c | 52 +++++++++++++++++++++++++++--------------------- src/m_cond.h | 8 ++++++-- src/p_saveg.c | 27 ++++++++++++++++--------- src/p_setup.c | 1 - src/p_spec.c | 4 ++-- src/r_skins.c | 2 ++ 15 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/command.c b/src/command.c index 28b73bdbc..88d11105a 100644 --- a/src/command.c +++ b/src/command.c @@ -1966,13 +1966,13 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth) return; } - if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE)) + if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE, false)) { CONS_Printf(M_GetText("You haven't unlocked Encore Mode yet!\n")); return; } - if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED)) + if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED, false)) { if (!stricmp(value, "Hard") || atoi(value) >= KARTSPEED_HARD) { @@ -2227,7 +2227,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) || var->PossibleValue == dummykartspeed_cons_t || var->PossibleValue == gpdifficulty_cons_t) { - if (!M_SecretUnlocked(SECRET_HARDSPEED)) + if (!M_SecretUnlocked(SECRET_HARDSPEED, false)) { max = KARTSPEED_NORMAL+1; if (var->PossibleValue == kartspeed_cons_t) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 29c460434..27a489916 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -57,6 +57,7 @@ #include "k_boss.h" #include "doomstat.h" #include "s_sound.h" // sfx_syfail +#include "m_cond.h" // netUnlocked // cl loading screen #include "v_video.h" @@ -3463,6 +3464,11 @@ void SV_ResetServer(void) CV_RevertNetVars(); + // Copy our unlocks to a place where net material can grab at/overwrite them safely. + // (permits all unlocks in dedicated) + for (i = 0; i < MAXUNLOCKABLES; i++) + netUnlocked[i] = (dedicated || gamedata->unlocked[i]); + DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n"); } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index cb144ab45..5b76aa16d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2857,7 +2857,7 @@ static void Command_Map_f(void) { newencoremode = !newencoremode; - if (!M_SecretUnlocked(SECRET_ENCORE) && newencoremode == true && !usingcheats) + if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) { CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); return; @@ -4848,7 +4848,7 @@ void ItemFinder_OnChange(void) if (!cv_itemfinder.value) return; // it's fine. - if (!M_SecretUnlocked(SECRET_ITEMFINDER)) + if (!M_SecretUnlocked(SECRET_ITEMFINDER, true)) { CONS_Printf(M_GetText("You haven't earned this yet.\n")); CV_StealthSetValue(&cv_itemfinder, 0); diff --git a/src/doomstat.h b/src/doomstat.h index d7b09f060..dfbb23966 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -573,10 +573,6 @@ extern INT32 luabanks[NUM_LUABANKS]; extern INT32 nummaprings; //keep track of spawned rings/coins -extern UINT32 token; ///< Number of tokens collected in a level -extern UINT32 tokenlist; ///< List of tokens collected -extern boolean gottoken; ///< Did you get a token? Used for end of act -extern INT32 tokenbits; ///< Used for setting token bits extern UINT32 bluescore; ///< Blue Team Scores extern UINT32 redscore; ///< Red Team Scores diff --git a/src/g_game.c b/src/g_game.c index f00ffcbe5..8407a7f8d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -204,10 +204,6 @@ UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage. UINT16 emeralds; INT32 luabanks[NUM_LUABANKS]; -UINT32 token; // Number of tokens collected in a level -UINT32 tokenlist; // List of tokens collected -boolean gottoken; // Did you get a token? Used for end of act -INT32 tokenbits; // Used for setting token bits // Temporary holding place for nights data for the current map //nightsdata_t ntemprecords; @@ -3309,7 +3305,7 @@ INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) // Most of the gametype references in this condition are intentionally not prefgametype. // This is so a server CAN continue playing a gametype if they like the taste of it. // The encore check needs prefgametype so can't use G_RaceGametype... - boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1) + boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1) && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); UINT8 encoremodifier = 0; @@ -3870,7 +3866,7 @@ static void G_GetNextMap(void) while (cup) { // Not unlocked? Grab the next result afterwards - if (!marathonmode && cup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[cup->unlockrequired]) + if (!marathonmode && M_CheckNetUnlockByID(cup->unlockrequired)) { cup = cup->next; gettingresult = 1; @@ -4218,10 +4214,6 @@ static void G_DoContinued(void) // Reset score pl->score = 0; - // Allow tokens to come back - tokenlist = 0; - token = 0; - if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0) G_SaveGameOver((UINT32)cursaveslot, true); diff --git a/src/k_follower.c b/src/k_follower.c index b340ba266..0a8869077 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -79,6 +79,7 @@ boolean K_FollowerUsable(INT32 skinnum) } // Use the unlockables table directly + // DEFINITELY not M_CheckNetUnlockByID return (boolean)(gamedata->unlocked[i]); } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 2a2adabae..d23da7114 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1930,7 +1930,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired])) + if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired))) { i = (cupgrid.previewanim / 82) % cup->numlevels; while (x < BASEVIDWIDTH + pad) @@ -1968,7 +1968,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) if (cup) { - boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]); + boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired)); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); @@ -2036,7 +2036,7 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (iconcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[iconcup->unlockrequired]) + if (iconcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(iconcup->unlockrequired)) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 325eee093..5e5a82cd0 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3362,7 +3362,7 @@ void M_SetupDifficultySelect(INT32 choice) PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default. } - if (M_SecretUnlocked(SECRET_ENCORE)) + if (M_SecretUnlocked(SECRET_ENCORE, false)) { PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off } @@ -3477,7 +3477,7 @@ static void M_LevelListFromGametype(INT16 gt) while (cup) { - if (cup->unlockrequired >= MAXUNLOCKABLES || gamedata->unlocked[cup->unlockrequired]) + if (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired)) { highestid = cup->id; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) @@ -3611,7 +3611,7 @@ void M_CupSelectHandler(INT32 choice) M_SetMenuDelay(pid); if ((!newcup) - || (newcup && newcup->unlockrequired < MAXUNLOCKABLES && !gamedata->unlocked[newcup->unlockrequired]) + || (newcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(newcup->unlockrequired)) || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); @@ -4614,7 +4614,7 @@ void M_InitOptions(INT32 choice) OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU; OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU; OPTIONS_GameplayDef.menuitems[gopt_encore].status = - (M_SecretUnlocked(SECRET_ENCORE) ? (IT_STRING | IT_CVAR) : IT_DISABLED); + (M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED); } OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU diff --git a/src/lua_script.c b/src/lua_script.c index 4fcd7dd00..4f9eab969 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -403,8 +403,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word) skincolor_bluering = (UINT16)luaL_checkinteger(L, 2); else if (fastcmp(word, "emeralds")) emeralds = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "token")) - token = (UINT32)luaL_checkinteger(L, 2); else if (fastcmp(word, "gravity")) gravity = (fixed_t)luaL_checkinteger(L, 2); else if (fastcmp(word, "stoppedclock")) diff --git a/src/m_cond.c b/src/m_cond.c index 20d57d85e..050eb2e9e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -27,6 +27,7 @@ #include "k_profiles.h" gamedata_t *gamedata = NULL; +boolean netUnlocked[MAXUNLOCKABLES]; // Map triggers for linedef executors // 32 triggers, one bit each @@ -442,7 +443,7 @@ void M_ClearSecrets(void) for (i = 0; i < MAXEMBLEMS; ++i) gamedata->collected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) - gamedata->unlocked[i] = false; + gamedata->unlocked[i] = netUnlocked[i] = false; for (i = 0; i < MAXCONDITIONSETS; ++i) gamedata->achieved[i] = false; @@ -728,37 +729,47 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa // Quick unlock checks // ------------------- -UINT8 M_SecretUnlocked(INT32 type) +boolean M_CheckNetUnlockByID(UINT8 unlockid) +{ + if (unlockid >= MAXUNLOCKABLES) + { + return true; // default permit + } + + if (netgame) + { + return netUnlocked[unlockid]; + } + + return gamedata->unlocked[unlockid]; +} + +boolean M_SecretUnlocked(INT32 type, boolean local) { INT32 i; - if (dedicated) - return true; - #if 0 (void)type; (void)i; return false; // for quick testing #else -#ifdef DEVELOP -#define CHADYES true -#else -#define CHADYES false -#endif - for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type == type && gamedata->unlocked[i] != CHADYES) - return !CHADYES; + if (unlockables[i].type != type) + continue; + if ((local && gamedata->unlocked[i]) + || M_CheckNetUnlockByID(i)) + continue; + return false; } - return CHADYES; -#undef CHADYES + return true; + #endif //if 0 } -UINT8 M_MapLocked(INT32 mapnum) +boolean M_MapLocked(INT32 mapnum) { // Don't lock maps in dedicated servers. // That just makes hosts' lives hell. @@ -774,16 +785,11 @@ UINT8 M_MapLocked(INT32 mapnum) if (mapheaderinfo[mapnum-1]->cup) { - if ((mapheaderinfo[mapnum-1]->cup->unlockrequired < MAXUNLOCKABLES) - && (!gamedata->unlocked[mapheaderinfo[mapnum-1]->cup->unlockrequired])) + if (!M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->cup->unlockrequired)) return true; } - if ((mapheaderinfo[mapnum-1]->unlockrequired < MAXUNLOCKABLES) - && (!gamedata->unlocked[mapheaderinfo[mapnum-1]->unlockrequired])) - return true; - - return false; + return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->unlockrequired); } INT32 M_CountEmblems(void) diff --git a/src/m_cond.h b/src/m_cond.h index 5854808c8..5348b12f5 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -158,6 +158,9 @@ typedef struct extern gamedata_t *gamedata; +// Netsynced functional alternative to gamedata->unlocked +extern boolean netUnlocked[MAXUNLOCKABLES]; + extern conditionset_t conditionSets[MAXCONDITIONSETS]; extern emblem_t emblemlocations[MAXEMBLEMS]; extern unlockable_t unlockables[MAXUNLOCKABLES]; @@ -191,8 +194,9 @@ UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); // Checking unlockable status -UINT8 M_SecretUnlocked(INT32 type); -UINT8 M_MapLocked(INT32 mapnum); +boolean M_CheckNetUnlockByID(UINT8 unlockid); +boolean M_SecretUnlocked(INT32 type, boolean local); +boolean M_MapLocked(INT32 mapnum); INT32 M_CountEmblems(void); // Emblem shit diff --git a/src/p_saveg.c b/src/p_saveg.c index 100672534..1882e2602 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -35,6 +35,7 @@ #include "p_polyobj.h" #include "lua_script.h" #include "p_slopes.h" +#include "m_cond.h" // netUnlocked // SRB2Kart #include "k_battle.h" @@ -4600,9 +4601,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) //lastmapsaved = gamemap; lastmaploaded = gamemap; - tokenlist = 0; - token = 0; - savedata.emeralds = READUINT16(save_p)-357; READSTRINGN(save_p, testname, sizeof(testname)); @@ -4621,7 +4619,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) static void P_NetArchiveMisc(boolean resending) { - size_t i; + size_t i, j; WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); @@ -4642,7 +4640,14 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT32(save_p, pig); } - WRITEUINT32(save_p, tokenlist); + for (i = 0; i < MAXUNLOCKABLES;) + { + UINT8 btemp = 0; + for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + btemp |= (netUnlocked[j+i] << j); + WRITEUINT8(save_p, btemp); + i += j; + } WRITEUINT8(save_p, encoremode); @@ -4671,7 +4676,6 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT8(save_p, globools); } - WRITEUINT32(save_p, token); WRITEUINT32(save_p, bluescore); WRITEUINT32(save_p, redscore); @@ -4767,7 +4771,7 @@ static void P_NetArchiveMisc(boolean resending) static inline boolean P_NetUnArchiveMisc(boolean reloading) { - size_t i; + size_t i, j; size_t numTasks; if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) @@ -4801,7 +4805,13 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) } } - tokenlist = READUINT32(save_p); + for (i = 0; i < MAXUNLOCKABLES;) + { + UINT8 rtemp = READUINT8(save_p); + for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + netUnlocked[j+i] = ((rtemp >> j) & 1); + i += j; + } encoremode = (boolean)READUINT8(save_p); @@ -4834,7 +4844,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) stoppedclock = !!(globools & (1<<1)); } - token = READUINT32(save_p); bluescore = READUINT32(save_p); redscore = READUINT32(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 3e9747505..3a4dd06af 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6807,7 +6807,6 @@ static void P_InitLevelSettings(void) modulothing = 0; // special stage tokens, emeralds, and ring total - tokenbits = 0; runemeraldmanager = false; emeraldspawndelay = 60*TICRATE; diff --git a/src/p_spec.c b/src/p_spec.c index 06c658ee1..70450c576 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1495,14 +1495,14 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller { // An unlockable itself must be unlocked! INT32 unlockid = triggerline->args[1]; - if ((modifiedgame && !savemoddata) || (netgame || multiplayer)) + if (modifiedgame && !savemoddata) return false; else if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count { CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid); return false; } - else if (!(gamedata->unlocked[unlockid])) + else if (!(M_CheckNetUnlockByID(unlockid))) return false; } break; diff --git a/src/r_skins.c b/src/r_skins.c index 3fea4b2cc..d38e7a41b 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -171,6 +171,7 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock) if (unlockables[i].type != SECRET_SKIN) continue; + // NEVER EVER EVER M_CheckNetUnlockByID if (gamedata->unlocked[i] != true && !demolock) continue; @@ -250,6 +251,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) } // Use the unlockables table directly + // NOTE: M_CheckNetUnlockByID would be correct in many circumstances... but not all. TODO figure out how to discern. return (boolean)(gamedata->unlocked[i]); } From d554856d047ead16bcdc7f572db02a66f4884d04 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 9 Dec 2022 17:29:16 +0000 Subject: [PATCH 33/95] No longer necessary to call `M_CheckUnlockConditions` twice in `M_UpdateUnlockablesAndExtraEmblems` --- src/m_cond.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 050eb2e9e..59e753109 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -565,19 +565,16 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) char cechoText[992] = ""; UINT8 cechoLines = 0; - M_CheckUnlockConditions(); - if (!loud) { // Just in case they aren't to sync + // Done first so that emblems are ready before check M_CheckLevelEmblems(); M_CompletionEmblems(); - - // Fun part: if any of those unlocked we need to go through the - // unlock conditions AGAIN just in case an emblem reward was reached - M_CheckUnlockConditions(); } + M_CheckUnlockConditions(); + // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { From 358305d872bb06b49e55756e6b2142cc3b332bbc Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 10 Dec 2022 19:47:21 +0000 Subject: [PATCH 34/95] Clean up the Challenges menu code - M_ChallengesAutoFocus handles all scenarioes where the game automatically highlights a tile, rather than via user input - Fix leftward scrolling onto highlighted large tiles being inconsistent between manual and auto selection - Fix nonexistent challengegrid error case for input handler - Way more comments, to help the maintainability of the code --- src/k_menudraw.c | 2 +- src/k_menufunc.c | 383 ++++++++++++++++++++++++----------------------- 2 files changed, 200 insertions(+), 185 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d23da7114..2e14f1d99 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4603,7 +4603,7 @@ void M_DrawChallenges(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } - if (!gamedata->challengegrid) + if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) { V_DrawCenteredString(x, y, V_REDMAP, "No challenges available!?"); goto challengedesc; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 5e5a82cd0..5c13b3e21 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6870,6 +6870,106 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) return desiredmenu; } +static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) +{ + UINT8 i; + SINT8 work; + + if (unlockid >= MAXUNLOCKABLES) + return; + + challengesmenu.currentunlock = unlockid; + challengesmenu.unlockanim = 0; + + if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) + return; + + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) + { + if (gamedata->challengegrid[i] != unlockid) + { + // Not what we're looking for. + continue; + } + + if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) + { + // no need to check for CHE_CONNECTEDUP in linear iteration + continue; + } + + // Helper calculation for non-fresh scrolling. + work = (challengesmenu.col + challengesmenu.focusx); + + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; + challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + + if (fresh) + { + // We're just entering the menu. Immediately jump to the desired position... + challengesmenu.focusx = 0; + // ...and since the menu is even-width, randomly select whether it's left or right of center. + if (!unlockables[unlockid].majorunlock + && M_RandomChance(FRACUNIT/2)) + challengesmenu.focusx--; + } + else + { + // We're jumping between multiple unlocks in sequence. Get the difference (looped from -range/2 < work <= range/2). + work -= challengesmenu.col; + if (work <= -gamedata->challengegridwidth/2) + work += gamedata->challengegridwidth; + else if (work >= gamedata->challengegridwidth/2) + work -= gamedata->challengegridwidth; + + if (work > 0) + { + // Offset left, scroll right? + if (work > LEFTUNLOCKSCROLL) + { + work -= LEFTUNLOCKSCROLL; + challengesmenu.focusx = LEFTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else if (work < 0) + { + // We only need to scroll as far as the rightward edge. + if (unlockables[unlockid].majorunlock) + { + work++; + } + + // Offset right, scroll left? + if (work < -RIGHTUNLOCKSCROLL) + { + challengesmenu.focusx = -RIGHTUNLOCKSCROLL; + work += RIGHTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else + { + // We're right where we want to be. + challengesmenu.focusx = 0; + } + + // And put the pixel-based scrolling in play, too. + challengesmenu.offset = -work*16; + } + + break; + } +} + void M_Challenges(INT32 choice) { UINT8 i; @@ -6878,7 +6978,7 @@ void M_Challenges(INT32 choice) M_InterruptMenuWithChallenges(NULL); MISC_ChallengesDef.prevMenu = currentMenu; - if (gamedata->challengegrid && !challengesmenu.pending) + if (gamedata->challengegrid != NULL && !challengesmenu.pending) { UINT8 selection[MAXUNLOCKABLES]; UINT8 numunlocks = 0; @@ -6913,30 +7013,7 @@ void M_Challenges(INT32 choice) } } - challengesmenu.currentunlock = selection[M_RandomKey(numunlocks)]; - - for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) - { - if (gamedata->challengegrid[i] != challengesmenu.currentunlock) - { - continue; - } - - if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration - { - continue; - } - - challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; - challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; - - challengesmenu.focusx = 0; - if (challengesmenu.currentunlock < MAXUNLOCKABLES - && !unlockables[challengesmenu.currentunlock].majorunlock - && M_RandomChance(FRACUNIT/2)) - challengesmenu.focusx--; - break; - } + M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true); } M_SetupNextMenu(&MISC_ChallengesDef, false); @@ -6945,169 +7022,113 @@ void M_Challenges(INT32 choice) void M_ChallengesTick(void) { UINT8 i, newunlock = MAXUNLOCKABLES; - SINT8 work; boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); + // Ticking challengesmenu.ticker++; - + challengesmenu.offset /= 2; for (i = 0; i < CSEXPLOSIONS; i++) { if (setup_explosions[i].tics > 0) setup_explosions[i].tics--; } - if (challengesmenu.pending && challengesmenu.requestnew) + if (challengesmenu.pending) { - challengesmenu.requestnew = false; - if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) + // Pending mode. + + if (challengesmenu.requestnew) { - challengesmenu.currentunlock = newunlock; - challengesmenu.unlockanim = 0; - - if (gamedata->challengegrid) + // The menu apparatus is requesting a new unlock. + challengesmenu.requestnew = false; + if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) { - for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) - { - if (gamedata->challengegrid[i] != challengesmenu.currentunlock) - { - continue; - } - - if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration - { - continue; - } - - work = (challengesmenu.col + challengesmenu.focusx); - - challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; - challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; - - if (fresh) - { - challengesmenu.focusx = 0; - if (!unlockables[challengesmenu.currentunlock].majorunlock - && M_RandomChance(FRACUNIT/2)) - challengesmenu.focusx--; - } - else - { - work -= challengesmenu.col; - if (work <= -gamedata->challengegridwidth/2) - work += gamedata->challengegridwidth; - else if (work >= gamedata->challengegridwidth/2) - work -= gamedata->challengegridwidth; - - if (work > 0) - { - if (work > LEFTUNLOCKSCROLL) - { - work -= LEFTUNLOCKSCROLL; - challengesmenu.focusx = LEFTUNLOCKSCROLL; - } - else - { - challengesmenu.focusx = work; - work = 0; - } - } - else if (work < 0) - { - if (work < -RIGHTUNLOCKSCROLL) - { - challengesmenu.focusx = -RIGHTUNLOCKSCROLL; - work += RIGHTUNLOCKSCROLL; - } - else - { - challengesmenu.focusx = work; - work = 0; - } - } - else - { - challengesmenu.focusx = 0; - } - - challengesmenu.offset = -work*16; - } - - break; - } + // We got one! + M_ChallengesAutoFocus(newunlock, fresh); } + else + { + // All done! Let's save the unlocks we've busted open. + challengesmenu.pending = false; + G_SaveGameData(); + } + } + else if (challengesmenu.fade < 5) + { + // Fade increase. + challengesmenu.fade++; } else { - challengesmenu.pending = false; - G_SaveGameData(); - } - } - else if (challengesmenu.pending && challengesmenu.fade < 5) - challengesmenu.fade++; - else if (challengesmenu.pending) - { - if (++challengesmenu.unlockanim >= MAXUNLOCKTIME) - challengesmenu.requestnew = true; + // Unlock sequence. - if (challengesmenu.currentunlock < MAXUNLOCKABLES - && challengesmenu.unlockanim == UNLOCKTIME) - { - gamedata->unlocked[challengesmenu.currentunlock] = true; - - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = M_ChallengeGridExtraData(); - - S_StartSound(NULL, sfx_s3k4e); + if (++challengesmenu.unlockanim >= MAXUNLOCKTIME) { - unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; - UINT16 bombcolor = SKINCOLOR_NONE; + challengesmenu.requestnew = true; + } - if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && challengesmenu.unlockanim == UNLOCKTIME) + { + // Unlock animation... also tied directly to the actual unlock! + gamedata->unlocked[challengesmenu.currentunlock] = true; + + Z_Free(challengesmenu.extradata); + if ((challengesmenu.extradata = M_ChallengeGridExtraData())) { - bombcolor = ref->color; - } - else switch (ref->type) - { - case SECRET_SKIN: + unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; + UINT16 bombcolor = SKINCOLOR_NONE; + + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) { - INT32 skin = M_UnlockableSkinNum(ref); - if (skin != -1) - { - bombcolor = skins[skin].prefcolor; - } - break; + bombcolor = ref->color; } - case SECRET_FOLLOWER: + else switch (ref->type) { - INT32 skin = M_UnlockableFollowerNum(ref); - if (skin != -1) + case SECRET_SKIN: { - bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + INT32 skin = M_UnlockableSkinNum(ref); + if (skin != -1) + { + bombcolor = skins[skin].prefcolor; + } + break; } - break; + case SECRET_FOLLOWER: + { + INT32 skin = M_UnlockableFollowerNum(ref); + if (skin != -1) + { + bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + } + break; + } + default: + break; } - default: - break; - } - if (bombcolor == SKINCOLOR_NONE) - { - bombcolor = cv_playercolor[0].value; - } + if (bombcolor == SKINCOLOR_NONE) + { + bombcolor = cv_playercolor[0].value; + } - i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; - M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row+i, bombcolor); - if (ref->majorunlock) - { - M_SetupReadyExplosions(false, challengesmenu.col+1, challengesmenu.row+(1-i), bombcolor); + i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; + M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row+i, bombcolor); + if (ref->majorunlock) + { + M_SetupReadyExplosions(false, challengesmenu.col+1, challengesmenu.row+(1-i), bombcolor); + } + + S_StartSound(NULL, sfx_s3k4e); } } } } else if (challengesmenu.fade > 0) + { + // Fade decrease. challengesmenu.fade--; - - challengesmenu.offset /= 2; + } } boolean M_ChallengesInputs(INT32 ch) @@ -7115,15 +7136,15 @@ boolean M_ChallengesInputs(INT32 ch) const UINT8 pid = 0; UINT8 i; const boolean start = M_MenuButtonPressed(pid, MBT_START); - const boolean move = (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr); + const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); (void) ch; - if (!challengesmenu.extradata) + if (challengesmenu.fade) { ; } #ifdef DEVELOP - else if (M_MenuExtraPressed(pid)) // debugging + else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging { if (challengesmenu.currentunlock < MAXUNLOCKABLES) { @@ -7133,38 +7154,14 @@ boolean M_ChallengesInputs(INT32 ch) M_PopulateChallengeGrid(); Z_Free(challengesmenu.extradata); challengesmenu.extradata = M_ChallengeGridExtraData(); - challengesmenu.unlockanim = 0; - for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) - { - if (gamedata->challengegrid[i] != challengesmenu.currentunlock) - { - continue; - } - - if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) // no need to check for CHE_CONNECTEDUP in linear iteration - { - continue; - } - - challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; - challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; - - challengesmenu.focusx = 0; - if (!unlockables[challengesmenu.currentunlock].majorunlock - && M_RandomChance(FRACUNIT/2)) - challengesmenu.focusx--; - - break; - } + M_ChallengesAutoFocus(challengesmenu.currentunlock, true); challengesmenu.pending = true; } return true; } #endif - else if (challengesmenu.fade) - ; else { if (M_MenuBackPressed(pid) || start) @@ -7178,9 +7175,12 @@ boolean M_ChallengesInputs(INT32 ch) return true; } - // Determine movement around the grid - if (move) + if (challengesmenu.extradata != NULL && move) { + // Determine movement around the grid + // For right/down movement, we can pre-determine the number of steps based on extradata. + // For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile. + if (menucmd[pid].dpad_ud > 0) { i = 2; @@ -7233,8 +7233,13 @@ boolean M_ChallengesInputs(INT32 ch) i = 2; while (i > 0) { + // Slide the focus counter to movement, if we can. if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL) + { challengesmenu.focusx--; + } + + // Step the actual column right. if (challengesmenu.col < gamedata->challengegridwidth-1) { challengesmenu.col++; @@ -7243,6 +7248,7 @@ boolean M_ChallengesInputs(INT32 ch) { challengesmenu.col = 0; } + if (!(challengesmenu.extradata[ (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row] @@ -7250,6 +7256,7 @@ boolean M_ChallengesInputs(INT32 ch) { break; } + i--; } S_StartSound(NULL, sfx_s3k5b); @@ -7263,8 +7270,13 @@ boolean M_ChallengesInputs(INT32 ch) & CHE_CONNECTEDLEFT) ? 2 : 1; while (i > 0) { + // Slide the focus counter to movement, if we can. if (challengesmenu.focusx < LEFTUNLOCKSCROLL) + { challengesmenu.focusx++; + } + + // Step the actual column left. if (challengesmenu.col > 0) { challengesmenu.col--; @@ -7273,6 +7285,7 @@ boolean M_ChallengesInputs(INT32 ch) { challengesmenu.col = gamedata->challengegridwidth-1; } + i--; } S_StartSound(NULL, sfx_s3k5b); @@ -7289,6 +7302,8 @@ boolean M_ChallengesInputs(INT32 ch) if (challengesmenu.currentunlock < MAXUNLOCKABLES && unlockables[challengesmenu.currentunlock].majorunlock) { + // Adjust highlight coordinates up/to the left for large tiles. + if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) { challengesmenu.hiliy--; From d2c9c7027d3f91b2729790d2735326688b38f26d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 10 Dec 2022 21:05:06 +0000 Subject: [PATCH 35/95] Correct some issues with the previous commit. - Unlock explosions should occour relative to the highlighted coords, not the selected coords. (Yes, they can be disjoint.) - I got the comments for left and right on challenge tile auto focus swapped. - The rightwards cap on auto focus scroll for large tiles was handled incorrectly. --- src/k_menudraw.c | 4 ++-- src/k_menufunc.c | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 2e14f1d99..a3b27c54f 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4615,8 +4615,8 @@ void M_DrawChallenges(void) if (challengegridloops) { - if (!challengesmenu.col && challengesmenu.hilix) - x -= gamedata->challengegridwidth*16; + //if (!challengesmenu.col && challengesmenu.hilix) + //x -= gamedata->challengegridwidth*16; i = challengesmenu.col + challengesmenu.focusx; explodex = x - (i*16); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 5c13b3e21..3319e32b8 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6924,7 +6924,16 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) if (work > 0) { - // Offset left, scroll right? + // We only need to scroll as far as the rightward edge. + if (unlockables[unlockid].majorunlock) + { + work--; + challengesmenu.col++; + if (challengesmenu.col >= gamedata->challengegridwidth) + challengesmenu.col = 0; + } + + // Offset right, scroll left? if (work > LEFTUNLOCKSCROLL) { work -= LEFTUNLOCKSCROLL; @@ -6938,13 +6947,7 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) } else if (work < 0) { - // We only need to scroll as far as the rightward edge. - if (unlockables[unlockid].majorunlock) - { - work++; - } - - // Offset right, scroll left? + // Offset left, scroll right? if (work < -RIGHTUNLOCKSCROLL) { challengesmenu.focusx = -RIGHTUNLOCKSCROLL; @@ -7113,10 +7116,10 @@ void M_ChallengesTick(void) } i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; - M_SetupReadyExplosions(false, challengesmenu.col, challengesmenu.row+i, bombcolor); + M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor); if (ref->majorunlock) { - M_SetupReadyExplosions(false, challengesmenu.col+1, challengesmenu.row+(1-i), bombcolor); + M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor); } S_StartSound(NULL, sfx_s3k4e); From 6d4b55a2d506bbfe8bd3c5f7cfa59b52cff07611 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 10 Dec 2022 21:06:24 +0000 Subject: [PATCH 36/95] Add a x/X counter to the unlocks menu, like KAR and Smash --- src/k_menu.h | 8 ++++++++ src/k_menudraw.c | 39 ++++++++++++++++++++++++++------------- src/k_menufunc.c | 42 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index d6c6534dd..1701b3911 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1088,6 +1088,12 @@ void M_DrawAddons(void); #define RIGHTUNLOCKSCROLL 3 #define LEFTUNLOCKSCROLL (RIGHTUNLOCKSCROLL-1) +#define CC_TOTAL 0 +#define CC_UNLOCKED 1 +#define CC_TALLY 2 +#define CC_ANIM 3 +#define CC_MAX 4 + // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1105,6 +1111,8 @@ extern struct challengesmenu_s { boolean pending; boolean requestnew; + UINT8 unlockcount[CC_MAX]; + UINT8 fade; } challengesmenu; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a3b27c54f..bc5db7449 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4684,24 +4684,37 @@ void M_DrawChallenges(void) M_DrawCharSelectExplosions(false, explodex, currentMenu->y); challengedesc: - y = 120; - V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); - if (challengesmenu.currentunlock < MAXUNLOCKABLES) + // Tally { - str = unlockables[challengesmenu.currentunlock].name; - if (!gamedata->unlocked[challengesmenu.currentunlock]) + str = va("%d/%d", + challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY], + challengesmenu.unlockcount[CC_TOTAL] + ); + V_DrawRightAlignedKartString(BASEVIDWIDTH-7, 9-challengesmenu.unlockcount[CC_ANIM], 0, str); + } + + // Name bar + { + y = 120; + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); + + if (challengesmenu.currentunlock < MAXUNLOCKABLES) { - str = "???"; //M_CreateSecretMenuOption(str); + str = unlockables[challengesmenu.currentunlock].name; + if (!gamedata->unlocked[challengesmenu.currentunlock]) + { + str = "???"; //M_CreateSecretMenuOption(str); + } + } + else + { + str = "---"; } - } - else - { - str = "---"; - } - offset = V_LSTitleLowStringWidth(str, 0) / 2; - V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); + offset = V_LSTitleLowStringWidth(str, 0) / 2; + V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); + } if (!challengesmenu.fade) V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, "Press (B)"); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 3319e32b8..6b28385dc 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6848,6 +6848,8 @@ struct challengesmenu_s challengesmenu; menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { + UINT8 i; + M_UpdateUnlockablesAndExtraEmblems(false); if ((challengesmenu.pending = challengesmenu.requestnew = (M_GetNextAchievedUnlock() < MAXUNLOCKABLES))) @@ -6864,6 +6866,24 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (gamedata->challengegrid) challengesmenu.extradata = M_ChallengeGridExtraData(); + memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount)); + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + challengesmenu.unlockcount[CC_TOTAL]++; + + if (!gamedata->unlocked[i]) + { + continue; + } + + challengesmenu.unlockcount[CC_UNLOCKED]++; + } + return &MISC_ChallengesDef; } @@ -7035,6 +7055,8 @@ void M_ChallengesTick(void) if (setup_explosions[i].tics > 0) setup_explosions[i].tics--; } + if (challengesmenu.unlockcount[CC_ANIM] > 0) + challengesmenu.unlockcount[CC_ANIM]--; if (challengesmenu.pending) { @@ -7075,6 +7097,8 @@ void M_ChallengesTick(void) { // Unlock animation... also tied directly to the actual unlock! gamedata->unlocked[challengesmenu.currentunlock] = true; + challengesmenu.unlockcount[CC_TALLY]++; + challengesmenu.unlockcount[CC_ANIM]++; Z_Free(challengesmenu.extradata); if ((challengesmenu.extradata = M_ChallengeGridExtraData())) @@ -7127,10 +7151,22 @@ void M_ChallengesTick(void) } } } - else if (challengesmenu.fade > 0) + else { - // Fade decrease. - challengesmenu.fade--; + + // Tick down the tally. (currently not visible) + /*if ((challengesmenu.ticker & 1) + && challengesmenu.unlockcount[CC_TALLY] > 0) + { + challengesmenu.unlockcount[CC_TALLY]--; + challengesmenu.unlockcount[CC_UNLOCKED]++; + }*/ + + if (challengesmenu.fade > 0) + { + // Fade decrease. + challengesmenu.fade--; + } } } From 0211bed1ebcc16e122d809a39ab993a824c477bc Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 16:14:12 +0000 Subject: [PATCH 37/95] Fix the condition for non-net unlock check with M_SecretUnlocked to not trip if the host has it unlocked --- src/m_cond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 59e753109..b8d3c7066 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -756,7 +756,7 @@ boolean M_SecretUnlocked(INT32 type, boolean local) if (unlockables[i].type != type) continue; if ((local && gamedata->unlocked[i]) - || M_CheckNetUnlockByID(i)) + || (!local && M_CheckNetUnlockByID(i))) continue; return false; } From f179a3523f45b9018b4c73b1377424f39e3face2 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 16:59:11 +0000 Subject: [PATCH 38/95] Clean up `M_UpdateUnlockablesAndExtraEmblems` and related * `gamedata->unlockpending[MAXUNLOCKABLES]` stores info to prevent the same unlock causing multiple sounds, and simplify `M_GetNextAchievedUnlock` * Remove the DEVELOP cechotext * Each unlock on the challenges menu updates all the unlockables, rather than just `M_CheckUnlockConditions` * The unlock update function handles the incoming unlock sound itself if `loud` is true. This will allow us to quickly replace every sound at once when we've made a decision what to use Also: * Fixes the size of the savebuffer allocation in `G_SaveGame` to account for the challengegrid array. --- src/f_finale.c | 4 +--- src/g_game.c | 41 +++++++++++++++++++++++++++++------------ src/k_menufunc.c | 2 ++ src/k_pwrlv.c | 12 ++---------- src/m_cond.c | 31 +++++++++++++------------------ src/m_cond.h | 3 ++- src/p_setup.c | 3 +-- src/p_spec.c | 1 - 8 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/f_finale.c b/src/f_finale.c index fb11ee3f2..022524105 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1078,9 +1078,7 @@ void F_GameEvaluationTicker(void) { ++gamedata->timesBeaten; - if (M_UpdateUnlockablesAndExtraEmblems(true)) - S_StartSound(NULL, sfx_s3k68); - + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } else diff --git a/src/g_game.c b/src/g_game.c index 3b6e56c8d..5f5a9f8a7 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -590,10 +590,7 @@ static void G_UpdateRecordReplays(void) if ((earnedEmblems = M_CheckLevelEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu medal%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); - if (M_UpdateUnlockablesAndExtraEmblems(true)) - S_StartSound(NULL, sfx_ncitem); - - // SRB2Kart - save here so you NEVER lose your earned times/medals. + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } @@ -2185,8 +2182,7 @@ static inline void G_PlayerFinishLevel(INT32 player) if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { gamedata->matchesplayed++; - if (M_UpdateUnlockablesAndExtraEmblems(true)) - S_StartSound(NULL, sfx_ncitem); + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } @@ -3686,8 +3682,7 @@ static void G_UpdateVisited(void) if ((earnedEmblems = M_CompletionEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); - if (M_UpdateUnlockablesAndExtraEmblems(true)) - S_StartSound(NULL, sfx_ncitem); + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } @@ -4393,6 +4388,13 @@ void G_LoadGameData(void) gamedata->unlocked[j+i] = ((rtemp >> j) & 1); i += j; } + for (i = 0; i < MAXUNLOCKABLES;) + { + rtemp = READUINT8(save_p); + for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + gamedata->unlockpending[j+i] = ((rtemp >> j) & 1); + i += j; + } for (i = 0; i < MAXCONDITIONSETS;) { rtemp = READUINT8(save_p); @@ -4510,7 +4512,11 @@ void G_SaveGameData(void) return; } - length = (4+4+4+1+(MAXEMBLEMS+MAXUNLOCKABLES+MAXCONDITIONSETS)+4+4); + length = (4+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + if (gamedata->challengegrid) + { + length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; + } length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); save_p = savebuffer = (UINT8 *)malloc(length); @@ -4536,7 +4542,9 @@ void G_SaveGameData(void) WRITEUINT8(save_p, btemp); i += j; } - for (i = 0; i < MAXUNLOCKABLES;) // MAXUNLOCKABLES * 1; + + // MAXUNLOCKABLES * 2; + for (i = 0; i < MAXUNLOCKABLES;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) @@ -4544,6 +4552,15 @@ void G_SaveGameData(void) WRITEUINT8(save_p, btemp); i += j; } + for (i = 0; i < MAXUNLOCKABLES;) + { + btemp = 0; + for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) + btemp |= (gamedata->unlockpending[j+i] << j); + WRITEUINT8(save_p, btemp); + i += j; + } + for (i = 0; i < MAXCONDITIONSETS;) // MAXCONDITIONSETS * 1; { btemp = 0; @@ -4553,7 +4570,7 @@ void G_SaveGameData(void) i += j; } - if (gamedata->challengegrid) + if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT { WRITEUINT16(save_p, gamedata->challengegridwidth); for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) @@ -4561,7 +4578,7 @@ void G_SaveGameData(void) WRITEUINT8(save_p, gamedata->challengegrid[i]); } } - else + else // 2 { WRITEUINT16(save_p, 0); } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6b28385dc..6a7c23007 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -7097,6 +7097,8 @@ void M_ChallengesTick(void) { // Unlock animation... also tied directly to the actual unlock! gamedata->unlocked[challengesmenu.currentunlock] = true; + M_UpdateUnlockablesAndExtraEmblems(true); + challengesmenu.unlockcount[CC_TALLY]++; challengesmenu.unlockcount[CC_ANIM]++; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index e4623281b..6ad088cae 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -418,11 +418,7 @@ void K_CashInPowerLevels(void) { pr->powerlevels[powerType] = clientpowerlevels[i][powerType]; - if (M_UpdateUnlockablesAndExtraEmblems(true)) - { - S_StartSound(NULL, sfx_ncitem); - } - + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } } @@ -642,11 +638,7 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) { pr->powerlevels[powerType] = yourPower + inc; - if (M_UpdateUnlockablesAndExtraEmblems(true)) - { - S_StartSound(NULL, sfx_ncitem); - } - + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } } diff --git a/src/m_cond.c b/src/m_cond.c index b8d3c7066..38df2d4d9 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -22,6 +22,7 @@ #include "r_skins.h" // numskins #include "k_follower.h" #include "r_draw.h" // R_GetColorByName +#include "s_sound.h" // S_StartSound #include "k_pwrlv.h" #include "k_profiles.h" @@ -443,7 +444,7 @@ void M_ClearSecrets(void) for (i = 0; i < MAXEMBLEMS; ++i) gamedata->collected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) - gamedata->unlocked[i] = netUnlocked[i] = false; + gamedata->unlocked[i] = gamedata->unlockpending[i] = netUnlocked[i] = false; for (i = 0; i < MAXCONDITIONSETS; ++i) gamedata->achieved[i] = false; @@ -562,8 +563,7 @@ void M_CheckUnlockConditions(void) boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) { INT32 i; - char cechoText[992] = ""; - UINT8 cechoLines = 0; + UINT8 response = 0; if (!loud) { @@ -583,7 +583,8 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) continue; } - if (gamedata->unlocked[i] == true) + if (gamedata->unlocked[i] == true + || gamedata->unlockpending[i] == true) { continue; } @@ -593,21 +594,17 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) continue; } - if (loud) - { - strcat(cechoText, va("\"%s\" unlocked!\n", unlockables[i].name)); - } - ++cechoLines; + gamedata->unlockpending[i] = true; + response++; } // Announce - if (cechoLines && loud) + if (response) { - strcat(cechoText, "Return to main menu to see"); -#ifdef DEVELOP - // todo make debugmode - CONS_Printf("%s\n", cechoText); -#endif + if (loud) + { + S_StartSound(NULL, sfx_ncitem); + } return true; } return false; @@ -617,8 +614,6 @@ UINT8 M_GetNextAchievedUnlock(void) { UINT8 i; - M_CheckUnlockConditions(); - // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { @@ -632,7 +627,7 @@ UINT8 M_GetNextAchievedUnlock(void) continue; } - if (M_Achieved(unlockables[i].conditionset - 1) == false) + if (gamedata->unlockpending[i] == false) { continue; } diff --git a/src/m_cond.h b/src/m_cond.h index dd72d5976..04be99379 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -143,6 +143,7 @@ struct gamedata_t // UNLOCKABLES UNLOCKED boolean unlocked[MAXUNLOCKABLES]; + boolean unlockpending[MAXUNLOCKABLES]; // CHALLENGE GRID UINT16 challengegridwidth; @@ -188,7 +189,7 @@ void M_ClearSecrets(void); // Updating conditions and unlockables void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); -boolean M_UpdateUnlockablesAndExtraEmblems(boolean silent); +boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); UINT8 M_GetNextAchievedUnlock(void); UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); diff --git a/src/p_setup.c b/src/p_setup.c index 3a4dd06af..e5cbba81e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7566,8 +7566,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) { mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; - if (M_UpdateUnlockablesAndExtraEmblems(true)) - S_StartSound(NULL, sfx_ncitem); + M_UpdateUnlockablesAndExtraEmblems(true); G_SaveGameData(); } diff --git a/src/p_spec.c b/src/p_spec.c index 70450c576..4988ad881 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2765,7 +2765,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // Unlocked something? if (M_UpdateUnlockablesAndExtraEmblems(true)) { - S_StartSound(NULL, sfx_s3k68); G_SaveGameData(); // only save if unlocked something } } From edeb9d072c4592abdf1aefcab7243b6bb1214f46 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 17:01:25 +0000 Subject: [PATCH 39/95] Re-add cross-around hack for `M_DrawChallenges` --- src/k_menudraw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index bc5db7449..451731f78 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4615,8 +4615,8 @@ void M_DrawChallenges(void) if (challengegridloops) { - //if (!challengesmenu.col && challengesmenu.hilix) - //x -= gamedata->challengegridwidth*16; + if (!challengesmenu.col && challengesmenu.hilix) + x -= gamedata->challengegridwidth*16; i = challengesmenu.col + challengesmenu.focusx; explodex = x - (i*16); From b92af9222fe6a633bd0fbe5493e4c9174bab1bd2 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 17:28:23 +0000 Subject: [PATCH 40/95] Change the colours on the Challenges menu for increased contrast --- src/k_menudraw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 451731f78..006e00c61 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4480,7 +4480,7 @@ void M_DrawAddons(void) #undef addonsseperation -#define challengesbordercolor 8 +#define challengesbordercolor 27 static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) { @@ -4496,7 +4496,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili // Empty spots in the grid are always unconnected. if (num >= MAXUNLOCKABLES) { - V_DrawFill(x, y, 16, 16, 27); + V_DrawFill(x, y, 16, 16, challengesbordercolor); ref = NULL; goto drawborder; } @@ -4510,7 +4510,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili { work = (ref->majorunlock) ? 2 : 1; V_DrawFill(x, y, 16*work, 16*work, - ((challengesmenu.extradata[id] == CHE_HINT) ? 134 : 12)); + ((challengesmenu.extradata[id] == CHE_HINT) ? 132 : 11)); goto drawborder; } From ae43e3a6ce797943df70d0df830fe70098367c15 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 20:28:41 +0000 Subject: [PATCH 41/95] A little extra cleanup to M_DrawChallengeTile --- src/k_menudraw.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 006e00c61..9ea0b8775 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4485,8 +4485,8 @@ void M_DrawAddons(void) static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) { unlockable_t *ref = NULL; - patch_t *pat; - UINT8 *colormap; + patch_t *pat = missingpat; + UINT8 *colormap = NULL; fixed_t siz; UINT8 id, num, work; @@ -4497,7 +4497,6 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili if (num >= MAXUNLOCKABLES) { V_DrawFill(x, y, 16, 16, challengesbordercolor); - ref = NULL; goto drawborder; } @@ -4514,8 +4513,6 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili goto drawborder; } - pat = missingpat; - colormap = NULL; if (ref->icon != NULL && ref->icon[0]) { pat = W_CachePatchName(ref->icon, PU_CACHE); From 7f45ae42122359fce097fc5ffe09ddcc1db2e259 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 11 Dec 2022 20:34:52 +0000 Subject: [PATCH 42/95] Now that the background and border colour is the same, only draw unselected border when a tile is being drawn. --- src/k_menudraw.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9ea0b8775..a651a6cb4 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4569,13 +4569,16 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili drawborder: if (!hili) { - work = 16 * ((ref && ref->majorunlock) ? 2 : 1); - // Horizontal - V_DrawFill(x, y , work, 1, challengesbordercolor); - V_DrawFill(x, y + work-1, work, 1, challengesbordercolor); - // Vertical - V_DrawFill(x , y+1, 1, work-2, challengesbordercolor); - V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor); + if (ref != NULL) + { + work = 16 * (ref->majorunlock ? 2 : 1); + // Horizontal + V_DrawFill(x, y , work, 1, challengesbordercolor); + V_DrawFill(x, y + work-1, work, 1, challengesbordercolor); + // Vertical + V_DrawFill(x , y+1, 1, work-2, challengesbordercolor); + V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor); + } return; } From 81c9a7b9284e5a61a5d9d1a8f1d3957d967ca1e8 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 13:01:35 +0000 Subject: [PATCH 43/95] Clean up the calculation of challengegridwidth to be more explicitly correct. Also elegantly prevent the case where a non-looping grid stuffed to the brim with large tiles has them offset, preventing one tile from being placed. --- src/m_cond.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 38df2d4d9..997bae94b 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -59,6 +59,7 @@ void M_PopulateChallengeGrid(void) UINT16 i, j; UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0; UINT8 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)]; + UINT16 majorcompact = 2; if (gamedata->challengegrid != NULL) { @@ -85,21 +86,37 @@ void M_PopulateChallengeGrid(void) //CONS_Printf(" found %d\n", selection[0][numunlocks-1]); } + gamedata->challengegridwidth = 0; + if (numunlocks + nummajorunlocks == 0) { - gamedata->challengegridwidth = 0; return; } - gamedata->challengegridwidth = (numunlocks + (nummajorunlocks * 4) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; - if (nummajorunlocks) { // Getting the number of 2-highs you can fit into two adjacent columns. UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); majorpad = (nummajorunlocks+1)/majorpad; - if (gamedata->challengegridwidth < majorpad*2) - gamedata->challengegridwidth = majorpad*2; + + gamedata->challengegridwidth = majorpad*2; + +#if (CHALLENGEGRIDHEIGHT % 2) + // One empty per column. + numempty = gamedata->challengegridwidth; +#endif + } + + if (numunlocks > numempty) + { + // Getting the number of extra columns to store normal unlocks + gamedata->challengegridwidth += ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + majorcompact = 1; + } + else if (challengegridloops) + { + // Another case where offset large tiles are permitted. + majorcompact = 1; } gamedata->challengegrid = Z_Malloc( @@ -120,8 +137,9 @@ void M_PopulateChallengeGrid(void) { // You lose one from CHALLENGEGRIDHEIGHT because it is impossible to place a 2-high tile on the bottom row. // You lose one from the width if it doesn't loop. - UINT16 numspots = (gamedata->challengegridwidth - (challengegridloops ? 0 : 1)) - * (CHALLENGEGRIDHEIGHT-1); + // You divide by two if the grid is so compacted that large tiles can't be in offset columns. + UINT16 numspots = (gamedata->challengegridwidth - (challengegridloops ? 0 : majorcompact)) + * ((CHALLENGEGRIDHEIGHT-1) / majorcompact); // 0 is row, 1 is column INT16 quickcheck[numspots][2]; @@ -129,7 +147,7 @@ void M_PopulateChallengeGrid(void) for (i = 0; i < numspots; i++) { quickcheck[i][0] = i%(CHALLENGEGRIDHEIGHT-1); - quickcheck[i][1] = i/(CHALLENGEGRIDHEIGHT-1); + quickcheck[i][1] = majorcompact * i/(CHALLENGEGRIDHEIGHT-1); } // Place in random valid locations. @@ -189,6 +207,7 @@ quickcheckagain: } } + numempty = 0; // Space out empty entries to pepper into unlock list for (i = 0; i < gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; i++) { From 5a404799b3ab34499cfdbb29370db69c01c9671e Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 16:43:27 +0000 Subject: [PATCH 44/95] Add limits to PWR unlock condition --- src/deh_soc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index 9469e3858..8621b495e 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2360,6 +2360,12 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) re = atoi(params[1]); x1 = atoi(params[2]); + if (re < PWRLVRECORD_MIN || re > PWRLVRECORD_MAX) + { + deh_warning("Power level requirement %d out of range (%d - %d) for condition ID %d", re, PWRLVRECORD_MIN, PWRLVRECORD_MAX, id+1); + return; + } + if (x1 < 0 || x1 >= PWRLV_NUMTYPES) { deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id+1); From 6d3a812ff3ed1cd926b3762d576b3207143f188d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 16:44:40 +0000 Subject: [PATCH 45/95] Fix some G_BuildMapTitle memory leaks (found while writing the next commit) --- src/discord.c | 4 +++- src/g_demo.c | 6 +++++- src/k_menudraw.c | 6 +++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/discord.c b/src/discord.c index 0e9cb35c9..2fcad7833 100644 --- a/src/discord.c +++ b/src/discord.c @@ -540,7 +540,9 @@ void DRPC_UpdatePresence(void) else { // Map name on tool tip - snprintf(mapname, 48, "Map: %s", G_BuildMapTitle(gamemap)); + char *title = G_BuildMapTitle(gamemap); + snprintf(mapname, 48, "Map: %s", title); + Z_Free(title); discordPresence.largeImageText = mapname; } diff --git a/src/g_demo.c b/src/g_demo.c index 9d4f1123a..e47d8ba28 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2358,7 +2358,11 @@ void G_BeginRecording(void) // Full replay title demo_p += 64; - snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Record Attack" : connectedservername); + { + char *title = G_BuildMapTitle(gamemap); + snprintf(demo.titlename, 64, "%s - %s", title, modeattacking ? "Record Attack" : connectedservername); + Z_Free(title); + } // demo checksum demo_p += 16; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a651a6cb4..9c6e2de64 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3970,7 +3970,11 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) x += 85; if (demoref->map < nummapheaders && mapheaderinfo[demoref->map]) - V_DrawString(x, y, V_SNAPTOTOP, G_BuildMapTitle(demoref->map+1)); + { + char *title = G_BuildMapTitle(demoref->map+1); + V_DrawString(x, y, V_SNAPTOTOP, title); + Z_Free(title); + } else V_DrawString(x, y, V_SNAPTOTOP|V_ALLOWLOWERCASE|V_TRANSLUCENT, "Level is not loaded."); From cc9a65c8f8e28bace77b1c320b2aba21684c1615 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 16:55:56 +0000 Subject: [PATCH 46/95] Add unlock condition text to Challenges menu Has ifdef'd out code for conditions to change between white and yellow when achieved to match V1 behaviour... wasn't able to figure out why it wasn't working, so dummied out for now. --- src/k_menu.h | 2 + src/k_menudraw.c | 7 +- src/k_menufunc.c | 19 ++-- src/m_cond.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++- src/m_cond.h | 8 +- 5 files changed, 248 insertions(+), 11 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index bf4d69a57..f992bb3b7 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1101,6 +1101,8 @@ extern struct challengesmenu_s { INT16 offset; // To make the icons move smoothly when we transition! UINT8 currentunlock; + char *unlockcondition; + tic_t unlockanim; SINT8 row, hilix, focusx; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9c6e2de64..92b242ab0 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4720,6 +4720,9 @@ challengedesc: V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); } - if (!challengesmenu.fade) - V_DrawThinString(20, 120 + 60, V_ALLOWLOWERCASE, "Press (B)"); + // Conditions for unlock + if (challengesmenu.unlockcondition != NULL) + { + V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 40, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); + } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6a7c23007..7c23c3d78 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1776,15 +1776,14 @@ static inline size_t M_StringHeight(const char *string) void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype) { const UINT8 pid = 0; - size_t max = 0, start = 0, i, strlines; + size_t max = 0, start = 0, strlines = 0, i; static char *message = NULL; Z_Free(message); message = Z_StrDup(string); DEBFILE(message); // Rudementary word wrapping. - // Simple and effective. Does not handle nonuniform letter sizes, colors, etc. but who cares. - strlines = 0; + // Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares. for (i = 0; message[i]; i++) { if (message[i] == ' ') @@ -1799,6 +1798,8 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp max = 0; continue; } + else if (message[i] & 0x80) + continue; else max += 8; @@ -6859,13 +6860,14 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending || desiredmenu == NULL) { - memset(setup_explosions, 0, sizeof(setup_explosions)); challengesmenu.currentunlock = MAXUNLOCKABLES; - M_PopulateChallengeGrid(); + challengesmenu.unlockcondition = NULL; + M_PopulateChallengeGrid(); if (gamedata->challengegrid) challengesmenu.extradata = M_ChallengeGridExtraData(); + memset(setup_explosions, 0, sizeof(setup_explosions)); memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount)); for (i = 0; i < MAXUNLOCKABLES; i++) { @@ -6899,6 +6901,7 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) return; challengesmenu.currentunlock = unlockid; + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); challengesmenu.unlockanim = 0; if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) @@ -7099,6 +7102,9 @@ void M_ChallengesTick(void) gamedata->unlocked[challengesmenu.currentunlock] = true; M_UpdateUnlockablesAndExtraEmblems(true); + // Update shown description just in case..? + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + challengesmenu.unlockcount[CC_TALLY]++; challengesmenu.unlockcount[CC_ANIM]++; @@ -7213,6 +7219,8 @@ boolean M_ChallengesInputs(INT32 ch) Z_Free(challengesmenu.extradata); challengesmenu.extradata = NULL; + challengesmenu.unlockcondition = NULL; + return true; } @@ -7336,6 +7344,7 @@ boolean M_ChallengesInputs(INT32 ch) // After movement has been determined, figure out the current selection. i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row; challengesmenu.currentunlock = (gamedata->challengegrid[i]); + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); challengesmenu.hilix = challengesmenu.col; challengesmenu.hiliy = challengesmenu.row; diff --git a/src/m_cond.c b/src/m_cond.c index 997bae94b..1735b01e6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -480,6 +480,8 @@ void M_ClearSecrets(void) // ---------------------- // Condition set checking // ---------------------- + +// See also M_GetConditionString UINT8 M_CheckCondition(condition_t *cn) { switch (cn->type) @@ -564,7 +566,226 @@ static UINT8 M_CheckConditionSet(conditionset_t *c) return achievedSoFar; } -void M_CheckUnlockConditions(void) +// See also M_CheckCondition +static const char *M_GetConditionString(condition_t *cn) +{ + INT32 i; + char *title = NULL; + const char *work = NULL; + +#define BUILDCONDITIONTITLE(i) (M_MapLocked(i+1) ? Z_StrDup("???") : G_BuildMapTitle(i+1)) + + switch (cn->type) + { + case UC_PLAYTIME: // Requires total playing time >= x + return va("Play for %i:%02i:%02i", + G_TicsToHours(cn->requirement), + G_TicsToMinutes(cn->requirement, false), + G_TicsToSeconds(cn->requirement)); + case UC_MATCHESPLAYED: // Requires any level completed >= x times + return va("Play %d matches", cn->requirement); + case UC_POWERLEVEL: // Requires power level >= x on a certain gametype + return va("Get a PWR of %d in %s", cn->requirement, + (cn->extrainfo1 == PWRLV_RACE) + ? "Race" + : "Battle"); + case UC_GAMECLEAR: // Requires game beaten >= x times + if (cn->requirement > 1) + return va("Beat game %d times", cn->requirement); + else + return va("Beat the game"); + case UC_OVERALLTIME: // Requires overall time <= x + return va("Get overall time of %i:%02i:%02i", + G_TicsToHours(cn->requirement), + G_TicsToMinutes(cn->requirement, false), + G_TicsToSeconds(cn->requirement)); + case UC_MAPVISITED: // Requires map x to be visited + case UC_MAPBEATEN: // Requires map x to be beaten + case UC_MAPENCORE: // Requires map x to be beaten in encore + { + if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement]) + return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement); + + title = BUILDCONDITIONTITLE(cn->requirement); + work = va("%s %s%s", + (cn->type == UC_MAPVISITED) ? "Visit" : "Beat", + title, + (cn->type == UC_MAPENCORE) ? " in Encore Mode" : ""); + Z_Free(title); + return work; + } + case UC_MAPTIME: // Requires time on map <= x + { + if (cn->extrainfo1 >= nummapheaders || !mapheaderinfo[cn->extrainfo1]) + return va("INVALID MAP CONDITION \"%d:%d:%d\"", cn->type, cn->extrainfo1, cn->requirement); + + title = BUILDCONDITIONTITLE(cn->extrainfo1); + work = va("Beat %s in %i:%02i.%02i", title, + G_TicsToMinutes(cn->requirement, true), + G_TicsToSeconds(cn->requirement), + G_TicsToCentiseconds(cn->requirement)); + + Z_Free(title); + return work; + } + case UC_TOTALEMBLEMS: // Requires number of emblems >= x + return va("Get %d medals", cn->requirement); + case UC_EMBLEM: // Requires emblem x to be obtained + { + INT32 checkLevel; + + i = cn->requirement-1; + checkLevel = G_MapNumber(emblemlocations[i].level); + + if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) + return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel); + + title = BUILDCONDITIONTITLE(checkLevel); + switch (emblemlocations[i].type) + { + case ET_MAP: + work = va("Beat %s", title); + break; + case ET_TIME: + if (emblemlocations[i].color <= 0 || emblemlocations[i].color >= numskincolors) + { + Z_Free(title); + return va("INVALID MEDAL COLOR \"%d:%d\"", cn->requirement, checkLevel); + } + work = va("Get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title); + break; + case ET_GLOBAL: + default: + work = va("Find a secret in %s", title); + break; + } + + Z_Free(title); + return work; + } + case UC_UNLOCKABLE: // Requires unlockable x to be obtained + return va("Get \"%s\"", + gamedata->unlocked[cn->requirement-1] + ? unlockables[cn->requirement-1].name + : "???"); + default: + break; + } + // UC_MAPTRIGGER and UC_CONDITIONSET are explicitly very hard to support proper descriptions for + return va("UNSUPPORTED CONDITION \"%d\"", cn->type); + +#undef BUILDCONDITIONTITLE +} + +//#define ACHIEVEDBRITE + +char *M_BuildConditionSetString(UINT8 unlockid) +{ + conditionset_t *c = NULL; + UINT32 lastID = 0; + condition_t *cn; +#ifdef ACHIEVEDBRITE + boolean achieved = false; +#endif + size_t len = 1024, worklen; + static char message[1024] = ""; + const char *work = NULL; + size_t max = 0, start = 0, strlines = 0, i; + + message[0] = '\0'; + + if (unlockid >= MAXUNLOCKABLES) + { + return NULL; + } + + if (!unlockables[unlockid].conditionset) + { + return NULL; + } + + c = &conditionSets[unlockables[unlockid].conditionset-1]; + + for (i = 0; i < c->numconditions; ++i) + { + cn = &c->condition[i]; + + if (i > 0) + { + worklen = 3; + if (lastID == cn->id) + { + strncat(message, "\n& ", len); + } + else + { + strncat(message, "\nOR ", len); + worklen++; + } + len -= worklen; + } + lastID = cn->id; + +#ifdef ACHIEVEDBRITE + achieved = M_CheckCondition(cn); + + if (achieved) + { + strncat(message, "\0x82", len); + len--; + } +#endif + + work = M_GetConditionString(cn); + worklen = strlen(work); + + strncat(message, work, len); + len -= worklen; + +#ifdef ACHIEVEDBRITE + if (achieved) + { + strncat(message, "\0x80", len); + len--; + } +#endif + } + + // Rudementary word wrapping. + // Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares. + for (i = 0; message[i]; i++) + { + if (message[i] == ' ') + { + start = i; + max += 4; + } + else if (message[i] == '\n') + { + strlines = i; + start = 0; + max = 0; + continue; + } + else if (message[i] & 0x80) + continue; + else + max += 8; + + // Start trying to wrap if presumed length exceeds the screen width. + if (max >= BASEVIDWIDTH && start > 0) + { + message[start] = '\n'; + max -= (start-strlines)*8; + strlines = start; + start = 0; + } + } + + return message; +} + +static void M_CheckUnlockConditions(void) { INT32 i; conditionset_t *c; diff --git a/src/m_cond.h b/src/m_cond.h index 04be99379..bab7fc854 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -171,12 +171,15 @@ extern INT32 numemblems; extern UINT32 unlocktriggers; void M_NewGameDataStruct(void); + +// Challenges menu stuff void M_PopulateChallengeGrid(void); UINT8 *M_ChallengeGridExtraData(void); +char *M_BuildConditionSetString(UINT8 unlockid); #define CHE_NONE 0 #define CHE_HINT 1 -#define CHE_CONNECTEDLEFT 2 -#define CHE_CONNECTEDUP 4 +#define CHE_CONNECTEDLEFT (1<<1) +#define CHE_CONNECTEDUP (1<<2) #define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP) // Condition set setup @@ -187,7 +190,6 @@ void M_ClearConditionSet(UINT8 set); void M_ClearSecrets(void); // Updating conditions and unlockables -void M_CheckUnlockConditions(void); UINT8 M_CheckCondition(condition_t *cn); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); UINT8 M_GetNextAchievedUnlock(void); From cc4518f80addc183af44df064043cb71184a978c Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 20:39:53 +0000 Subject: [PATCH 47/95] M_DrawChallengePreview Draws a preview of an unlock in the bottom left corner. - Currently only supports unlocked ones (needs a roughly character-sized question mark graphic created) - Currently only supports SECRET_SKIN and SECRET_FOLLOWER Also, makes the area available to M_BuildConditionSetString smaller to avoid crossing into that region --- src/k_menudraw.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++- src/m_cond.c | 4 +-- src/m_cond.h | 3 ++- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 92b242ab0..38e55886e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4594,6 +4594,65 @@ drawborder: ); } +static void M_DrawChallengePreview(INT32 x, INT32 y) +{ + unlockable_t *ref = NULL; + UINT8 *colormap = NULL; + + if (challengesmenu.currentunlock >= MAXUNLOCKABLES) + { + return; + } + + // Okay, this is what we want to draw. + ref = &unlockables[challengesmenu.currentunlock]; + + if (!gamedata->unlocked[challengesmenu.currentunlock]) + { + // todo draw some sort of question mark? + return; + } + + switch (ref->type) + { + case SECRET_SKIN: + { + INT32 skin = M_UnlockableSkinNum(ref); + // Draw our character! + if (skin != -1) + { + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap); + } + break; + } + case SECRET_FOLLOWER: + { + INT32 skin = R_SkinAvailable(cv_skin[0].string); + INT32 fskin = M_UnlockableFollowerNum(ref); + + // Draw proximity reference for character + if (skin == -1) + skin = 0; + colormap = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_MENUCACHE); + M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap); + + // Draw follower next to them + if (fskin != -1) + { + UINT16 col = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, cv_playercolor[0].value); + colormap = R_GetTranslationColormap(fskin, col, GTC_MENUCACHE); + M_DrawFollowerSprite(x - 16, y, fskin, false, 0, colormap, NULL); + } + break; + } + default: + { + break; + } + } +} + void M_DrawChallenges(void) { INT32 x = currentMenu->x, explodex, selectx; @@ -4723,6 +4782,13 @@ challengedesc: // Conditions for unlock if (challengesmenu.unlockcondition != NULL) { - V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 40, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); + V_DrawCenteredString(BASEVIDWIDTH/2, y + 40, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); } + + // Derived from M_DrawCharSelectPreview + x = 40; + y = BASEVIDHEIGHT-16; + + // Unlock preview + M_DrawChallengePreview(x, y); } diff --git a/src/m_cond.c b/src/m_cond.c index 1735b01e6..7e8845815 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -772,8 +772,8 @@ char *M_BuildConditionSetString(UINT8 unlockid) else max += 8; - // Start trying to wrap if presumed length exceeds the screen width. - if (max >= BASEVIDWIDTH && start > 0) + // Start trying to wrap if presumed length exceeds the space we have on-screen. + if (max >= DESCRIPTIONWIDTH && start > 0) { message[start] = '\n'; max -= (start-strlines)*8; diff --git a/src/m_cond.h b/src/m_cond.h index bab7fc854..e3b1c568d 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -175,12 +175,13 @@ void M_NewGameDataStruct(void); // Challenges menu stuff void M_PopulateChallengeGrid(void); UINT8 *M_ChallengeGridExtraData(void); -char *M_BuildConditionSetString(UINT8 unlockid); #define CHE_NONE 0 #define CHE_HINT 1 #define CHE_CONNECTEDLEFT (1<<1) #define CHE_CONNECTEDUP (1<<2) #define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP) +char *M_BuildConditionSetString(UINT8 unlockid); +#define DESCRIPTIONWIDTH 170 // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); From bd5d51ac7b8e85a67bc81cbcd49d1e5ff60f2c16 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 12 Dec 2022 21:51:11 +0000 Subject: [PATCH 48/95] K_DrawMapThumbnail - Handles map thumbnail drawing in a way agnostic to patch size - Specify a desired final width in pixels `<< FRACBITS`, not a scale - more specific for our incoming varied purposes. - Encore and voting screen Random have to be handled externally - Put in k_hud.c because I'm not sure where would be most appropriate snd it works well enough, can be moved later --- src/k_hud.c | 29 ++++++++++++++ src/k_hud.h | 1 + src/k_menudraw.c | 75 ++++++++++++----------------------- src/y_inter.c | 100 ++++++++++++++++++++++------------------------- 4 files changed, 102 insertions(+), 103 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 5037132d8..3aa2f7a68 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1124,6 +1124,35 @@ static void K_initKartHUD(void) } } +void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap) +{ + patch_t *PictureOfLevel = NULL; + + if (map >= nummapheaders || !mapheaderinfo[map]) + { + PictureOfLevel = W_CachePatchName("M_NOLVL", PU_CACHE); + } + else if (!mapheaderinfo[map]->thumbnailPic) + { + PictureOfLevel = blanklvl; + } + else + { + PictureOfLevel = mapheaderinfo[map]->thumbnailPic; + } + + if (flags & V_FLIP) + x += width; + + V_DrawFixedPatch( + x, y, + FixedDiv(width, (SHORT(PictureOfLevel->width) << FRACBITS)), + flags, + PictureOfLevel, + colormap + ); +} + // see also MT_PLAYERARROW mobjthinker in p_mobj.c static void K_drawKartItem(void) { diff --git a/src/k_hud.h b/src/k_hud.h index dd27654fd..be3e6b5b9 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -38,6 +38,7 @@ void K_drawKartHUD(void); void K_drawKartFreePlay(void); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol); +void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap); extern patch_t *kp_facehighlight[8]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 38e55886e..e31d5df28 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1925,28 +1925,24 @@ void M_DrawRaceDifficulty(void) static void M_DrawCupPreview(INT16 y, cupheader_t *cup) { UINT8 i; - const INT16 pad = ((vid.width/vid.dupx) - BASEVIDWIDTH)/2; - INT16 x = -(cupgrid.previewanim % 82) - pad; + INT16 x = -(cupgrid.previewanim % 82); V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired))) { i = (cupgrid.previewanim / 82) % cup->numlevels; - while (x < BASEVIDWIDTH + pad) + while (x < BASEVIDWIDTH) { INT32 cupLevelNum = cup->cachedlevels[i]; - patch_t *PictureOfLevel = NULL; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) - { - PictureOfLevel = mapheaderinfo[cupLevelNum]->thumbnailPic; - } + K_DrawMapThumbnail( + (x+1)<numlevels; x += 82; } @@ -2164,26 +2160,22 @@ static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink, boolean greyscale) { - patch_t *PictureOfLevel = NULL; UINT8 *colormap = NULL; if (greyscale) colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); - if (mapheaderinfo[map]) - { - PictureOfLevel = mapheaderinfo[map]->thumbnailPic; - } - - if (!PictureOfLevel) - PictureOfLevel = blanklvl; - if (redblink) V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL2", PU_CACHE)); else V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL", PU_CACHE)); - V_DrawSmallMappedPatch(9+x, y+6, 0, PictureOfLevel, colormap); + K_DrawMapThumbnail( + (9+x)<type) { @@ -3937,34 +3929,19 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) // Draw level stuff x = 15; y = 15; - // A 160x100 image of the level as entry MAPxxP - if (demoref->map < nummapheaders && mapheaderinfo[demoref->map]) - { - patch = mapheaderinfo[demoref->map]->thumbnailPic; - if (!patch) - { - patch = blanklvl; - } - } - else - { - patch = W_CachePatchName("M_NOLVL", PU_CACHE); - } + K_DrawMapThumbnail( + x<kartspeed & DF_ENCORE) ? V_FLIP : 0), + demoref->map, + NULL); - if (!(demoref->kartspeed & DF_ENCORE)) - V_DrawSmallScaledPatch(x, y, V_SNAPTOTOP, patch); - else + if (demoref->kartspeed & DF_ENCORE) { - w = SHORT(patch->width); - h = SHORT(patch->height); - V_DrawSmallScaledPatch(x+(w>>1), y, V_SNAPTOTOP|V_FLIP, patch); - - { - static angle_t rubyfloattime = 0; - const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); - V_DrawFixedPatch((x+(w>>2))<>2))<>ANGLETOFINESHIFT); + V_DrawFixedPatch((x+40)<thumbnailPic; - } - - if (!pic) - { - pic = blanklvl; - } - } - if (selected[i]) { + const char *str; UINT8 sizeadd = selected[i]; for (j = 0; j <= splitscreen; j++) // another loop for drawing the selection backgrounds in the right order, grumble grumble.. @@ -1093,11 +1071,24 @@ void Y_VoteDrawer(void) sizeadd--; } - if (!levelinfo[i].encore) - V_DrawSmallScaledPatch(BASEVIDWIDTH-100, y, V_SNAPTORIGHT, pic); + if (i == 3) + { + str = "RANDOM"; + V_DrawSmallScaledPatch(BASEVIDWIDTH-100, y, V_SNAPTORIGHT, randomlvl); + } else { - V_DrawFixedPatch((BASEVIDWIDTH-20)<= 3 && (i != pickedvote || voteendtic == -1)) - { - pic = randomlvl; - } - else - { - pic = NULL; - - if (mapheaderinfo[votelevels[votes[i]][0]]) - { - pic = mapheaderinfo[votelevels[votes[i]][0]]->thumbnailPic; - } - - if (!pic) - { - pic = blanklvl; - } - } - if (!timer && i == voteclient.ranim) { V_DrawScaledPatch(x-18, y+9, V_SNAPTOLEFT, cursor); @@ -1176,11 +1157,22 @@ void Y_VoteDrawer(void) V_DrawFill(x-1, y-1, 42, 27, levelinfo[votes[i]].gtc|V_SNAPTOLEFT); } - if (!levelinfo[votes[i]].encore) - V_DrawTinyScaledPatch(x, y, V_SNAPTOLEFT, pic); + if (votes[i] >= 3 && (i != pickedvote || voteendtic == -1)) + { + V_DrawTinyScaledPatch(x, y, V_SNAPTOLEFT, randomlvl); + } else { - V_DrawFixedPatch((x+40)< Date: Tue, 13 Dec 2022 13:07:46 +0000 Subject: [PATCH 49/95] Groundwork for later commits - Make the `SECRET_` constants an easily reshuffable `enum` instead of a series of byzantine `#define`s - Includes SECRET_CUP and SECRET_MAP in preperation - Begin the conceptual seperation between Emblems (special in-level objects) and Medals (specific type of emblem that adds to the counter) - Rename UC_TOTALMEDALS and M_GotEnoughMedals, since the count is a Medals only thing - M_CountMedals, in addition to being renamed, now has an `all` boolean parameter since getting the total is no longer as easy as `emblems + extraemblems` --- src/deh_soc.c | 30 ++++++++++++++---------------- src/m_cond.c | 28 ++++++++++++++-------------- src/m_cond.h | 43 +++++++++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 8621b495e..52d2e7501 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2261,32 +2261,30 @@ void readunlockable(MYFILE *f, INT32 num) { if (fastcmp(word2, "NONE")) unlockables[num].type = SECRET_NONE; - else if (fastcmp(word2, "HEADER")) - unlockables[num].type = SECRET_HEADER; + else if (fastcmp(word2, "EXTRAMEDAL")) + unlockables[num].type = SECRET_EXTRAMEDAL; + else if (fastcmp(word2, "CUP")) + unlockables[num].type = SECRET_CUP; + else if (fastcmp(word2, "MAP")) + unlockables[num].type = SECRET_MAP; else if (fastcmp(word2, "SKIN")) unlockables[num].type = SECRET_SKIN; else if (fastcmp(word2, "FOLLOWER")) unlockables[num].type = SECRET_FOLLOWER; + else if (fastcmp(word2, "HARDSPEED")) + unlockables[num].type = SECRET_HARDSPEED; + else if (fastcmp(word2, "ENCORE")) + unlockables[num].type = SECRET_ENCORE; + else if (fastcmp(word2, "LEGACYBOXRUMMAGE")) + unlockables[num].type = SECRET_LEGACYBOXRUMMAGE; else if (fastcmp(word2, "TIMEATTACK")) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) unlockables[num].type = SECRET_BREAKTHECAPSULES; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; - else if (fastcmp(word2, "CREDITS")) - unlockables[num].type = SECRET_CREDITS; else if (fastcmp(word2, "ITEMFINDER")) unlockables[num].type = SECRET_ITEMFINDER; - else if (fastcmp(word2, "EMBLEMHINTS")) - unlockables[num].type = SECRET_EMBLEMHINTS; - else if (fastcmp(word2, "ENCORE")) - unlockables[num].type = SECRET_ENCORE; - else if (fastcmp(word2, "HARDSPEED")) - unlockables[num].type = SECRET_HARDSPEED; - else if (fastcmp(word2, "HELLATTACK")) - unlockables[num].type = SECRET_HELLATTACK; - else if (fastcmp(word2, "PANDORA")) - unlockables[num].type = SECRET_PANDORA; else unlockables[num].type = (INT16)i; } @@ -2422,10 +2420,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } - else if (fastcmp(params[0], "TOTALEMBLEMS")) + else if (fastcmp(params[0], "TOTALMEDALS")) { PARAMCHECK(1); - ty = UC_TOTALEMBLEMS; + ty = UC_TOTALMEDALS; re = atoi(params[1]); } else if (fastcmp(params[0], "EMBLEM")) diff --git a/src/m_cond.c b/src/m_cond.c index 7e8845815..1bd2c13ac 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -527,8 +527,8 @@ UINT8 M_CheckCondition(condition_t *cn) return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); case UC_TRIGGER: // requires map trigger set return !!(unlocktriggers & (1 << cn->requirement)); - case UC_TOTALEMBLEMS: // Requires number of emblems >= x - return (M_GotEnoughEmblems(cn->requirement)); + case UC_TOTALMEDALS: // Requires number of emblems >= x + return (M_GotEnoughMedals(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained return gamedata->collected[cn->requirement-1]; case UC_UNLOCKABLE: // Requires unlockable x to be obtained @@ -628,7 +628,7 @@ static const char *M_GetConditionString(condition_t *cn) Z_Free(title); return work; } - case UC_TOTALEMBLEMS: // Requires number of emblems >= x + case UC_TOTALMEDALS: // Requires number of emblems >= x return va("Get %d medals", cn->requirement); case UC_EMBLEM: // Requires emblem x to be obtained { @@ -1024,20 +1024,20 @@ boolean M_MapLocked(INT32 mapnum) return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->unlockrequired); } -INT32 M_CountEmblems(void) +INT32 M_CountMedals(boolean all) { INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { - if (!gamedata->collected[i]) + if (!all && !gamedata->collected[i]) continue; found++; } for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type != SECRET_EXTRAEMBLEM) + if (unlockables[i].type != SECRET_EXTRAMEDAL) continue; - if (!gamedata->unlocked[i]) + if (!all && !gamedata->unlocked[i]) continue; found++; } @@ -1048,26 +1048,26 @@ INT32 M_CountEmblems(void) // Quick functions for calculating things // -------------------------------------- -// Theoretically faster than using M_CountEmblems() -// Stops when it reaches the target number of emblems. -UINT8 M_GotEnoughEmblems(INT32 number) +// Theoretically faster than using M_CountMedals() +// Stops when it reaches the target number of medals. +UINT8 M_GotEnoughMedals(INT32 number) { - INT32 i, gottenemblems = 0; + INT32 i, gottenmedals = 0; for (i = 0; i < numemblems; ++i) { if (!gamedata->collected[i]) continue; - if (++gottenemblems < number) + if (++gottenmedals < number) continue; return true; } for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type != SECRET_EXTRAEMBLEM) + if (unlockables[i].type != SECRET_EXTRAMEDAL) continue; if (!gamedata->unlocked[i]) continue; - if (++gottenemblems < number) + if (++gottenmedals < number) continue; return true; } diff --git a/src/m_cond.h b/src/m_cond.h index e3b1c568d..8932805d4 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -30,7 +30,7 @@ typedef enum UC_MAPENCORE, // MAPENCORE [map number] UC_MAPTIME, // MAPTIME [map number] [time to beat, tics] UC_TRIGGER, // TRIGGER [trigger number] - UC_TOTALEMBLEMS, // TOTALEMBLEMS [number of emblems] + UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] @@ -92,27 +92,34 @@ struct unlockable_t UINT8 majorunlock; }; -#define SECRET_NONE 0 // Does nil. Use with levels locked by UnlockRequired -#define SECRET_HEADER 1 // Does nothing on its own, just serves as a header for the menu +typedef enum +{ + SECRET_NONE = 0, // Does nil, useful as a default only -#define SECRET_SKIN 2 // Allow this character to be selected -#define SECRET_FOLLOWER 3 // Allow this follower to be selected + // One step above bragging rights + SECRET_EXTRAMEDAL, // Extra medal for your counter -#define SECRET_EXTRAEMBLEM 4 // Extra Emblems (formerly extraemblem_t) + // Level restrictions (TODO) + SECRET_CUP, // Permit access to entire cup (overrides SECRET_MAP) + SECRET_MAP, // Permit access to single map -#define SECRET_TIMEATTACK 5 // Enables Time Attack on the main menu -#define SECRET_BREAKTHECAPSULES 6 // Enables Break the Capsules on the main menu -#define SECRET_SOUNDTEST 7 // Sound Test -#define SECRET_CREDITS 8 // Enables Credits + // Player restrictions + SECRET_SKIN, // Permit this character + SECRET_FOLLOWER, // Permit this follower -#define SECRET_ITEMFINDER 9 // Enables Item Finder/Emblem Radar -#define SECRET_EMBLEMHINTS 10 // Enables Emblem Hints + // Difficulty restrictions + SECRET_HARDSPEED, // Permit Hard gamespeed + SECRET_ENCORE, // Permit Encore option + SECRET_LEGACYBOXRUMMAGE, // Permit the Legacy Box for record attack, etc -#define SECRET_ENCORE 11 // Enables Encore mode cvar -#define SECRET_HARDSPEED 12 // Enables Hard gamespeed -#define SECRET_HELLATTACK 13 // Map Hell in record attack + // Menu restrictions + SECRET_TIMEATTACK, // Permit Time attack + SECRET_BREAKTHECAPSULES, // Permit SP Capsules + SECRET_SOUNDTEST, // Permit Sound Test -#define SECRET_PANDORA 14 // Enables Pandora's Box + // Assist restrictions + SECRET_ITEMFINDER, // Permit locating in-level secrets +} secrettype_t; // If you have more secrets than these variables allow in your game, // you seriously need to get a life. @@ -201,7 +208,7 @@ UINT8 M_CompletionEmblems(void); boolean M_CheckNetUnlockByID(UINT8 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_MapLocked(INT32 mapnum); -INT32 M_CountEmblems(void); +INT32 M_CountMedals(boolean all); // Emblem shit emblem_t *M_GetLevelEmblems(INT32 mapnum); @@ -211,7 +218,7 @@ const char *M_GetEmblemPatch(emblem_t *em, boolean big); // If you're looking to compare stats for unlocks or what not, use these // They stop checking upon reaching the target number so they // should be (theoretically?) slightly faster. -UINT8 M_GotEnoughEmblems(INT32 number); +UINT8 M_GotEnoughMedals(INT32 number); UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); From 2871ccb4f1c95e01afe7436f162b6f35d3d7670e Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 13:45:17 +0000 Subject: [PATCH 50/95] Rewrite level restrictions Now uses `SECRET_CUP` and `SECRET_MAP` with a stringVar saying the map lump/cup name, instead of `SECRET_NONE` and a levelheader `unlockrequired` property. --- src/deh_soc.c | 18 ----------- src/dehacked.c | 1 - src/doomstat.h | 2 -- src/g_game.c | 2 +- src/k_menudraw.c | 6 ++-- src/k_menufunc.c | 4 +-- src/lua_maplib.c | 2 -- src/m_cond.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++-- src/m_cond.h | 5 ++- src/p_setup.c | 1 - 10 files changed, 87 insertions(+), 34 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 52d2e7501..ce7ac6515 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1181,15 +1181,6 @@ void readlevelheader(MYFILE *f, char * name) mapheaderinfo[num]->encorepal = (UINT16)i; else if (fastcmp(word, "NUMLAPS")) mapheaderinfo[num]->numlaps = (UINT8)i; - else if (fastcmp(word, "UNLOCKABLE")) - { - if (i == 0 || word2[0] == 'F' || word2[0] == 'N') - mapheaderinfo[num]->unlockrequired = MAXUNLOCKABLES; - else if (i > 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something - mapheaderinfo[num]->unlockrequired = (UINT8)(i-1); - else - deh_warning("Level header %d: invalid unlockable number %d", num, i); - } else if (fastcmp(word, "SKYBOXSCALE")) mapheaderinfo[num]->skybox_scalex = mapheaderinfo[num]->skybox_scaley = mapheaderinfo[num]->skybox_scalez = (INT16)i; else if (fastcmp(word, "SKYBOXSCALEX")) @@ -3091,15 +3082,6 @@ void readcupheader(MYFILE *f, cupheader_t *cup) else deh_warning("%s Cup: invalid emerald number %d", cup->name, i); } - else if (fastcmp(word, "UNLOCKABLE")) - { - if (i == 0 || word2[0] == 'F' || word2[0] == 'N') - cup->unlockrequired = MAXUNLOCKABLES; - else if (i > 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something - cup->unlockrequired = (UINT8)(i-1); - else - deh_warning("%s Cup: invalid unlockable number %d", cup->name, i); - } else deh_warning("%s Cup: unknown word '%s'", cup->name, word); } diff --git a/src/dehacked.c b/src/dehacked.c index b2fd16e03..35f603bdd 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -486,7 +486,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); cup->id = numkartcupheaders; - cup->unlockrequired = MAXUNLOCKABLES; deh_strlcpy(cup->name, word2, sizeof(cup->name), va("Cup header %s: name", word2)); if (prev != NULL) diff --git a/src/doomstat.h b/src/doomstat.h index 7c311ace1..a4cfb246f 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -355,7 +355,6 @@ struct cupheader_t UINT8 numlevels; ///< Number of levels defined in levellist UINT8 numbonus; ///< Number of bonus stages defined UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) - UINT8 unlockrequired; ///< An unlockable is required to select this cup. MAXUNLOCKABLES for no unlocking required. cupheader_t *next; ///< Next cup in linked list }; @@ -391,7 +390,6 @@ struct mapheader_t // Selection metadata char keywords[33]; ///< Keywords separated by space to search for. 32 characters. - UINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no. UINT8 levelselect; ///< Is this map available in the level select? If so, which map list is it available in? UINT16 menuflags; ///< LF2_flags: options that affect record attack menus diff --git a/src/g_game.c b/src/g_game.c index 5f5a9f8a7..1b090dd7a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3866,7 +3866,7 @@ static void G_GetNextMap(void) while (cup) { // Not unlocked? Grab the next result afterwards - if (!marathonmode && M_CheckNetUnlockByID(cup->unlockrequired)) + if (!marathonmode && M_CupLocked(cup)) { cup = cup->next; gettingresult = 1; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index e31d5df28..d9a23ac97 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1929,7 +1929,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (cup && (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired))) + if (cup && !M_CupLocked(cup)) { i = (cupgrid.previewanim / 82) % cup->numlevels; while (x < BASEVIDWIDTH) @@ -1964,7 +1964,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) if (cup) { - boolean unlocked = (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired)); + boolean unlocked = !M_CupLocked(cup); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); @@ -2032,7 +2032,7 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (iconcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(iconcup->unlockrequired)) + if (M_CupLocked(iconcup)) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 7c23c3d78..e21a5902d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3478,7 +3478,7 @@ static void M_LevelListFromGametype(INT16 gt) while (cup) { - if (cup->unlockrequired >= MAXUNLOCKABLES || M_CheckNetUnlockByID(cup->unlockrequired)) + if (!M_CupLocked(cup)) { highestid = cup->id; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) @@ -3612,7 +3612,7 @@ void M_CupSelectHandler(INT32 choice) M_SetMenuDelay(pid); if ((!newcup) - || (newcup->unlockrequired < MAXUNLOCKABLES && !M_CheckNetUnlockByID(newcup->unlockrequired)) + || (M_CupLocked(newcup)) || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 0557456b2..37ccc955b 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2449,8 +2449,6 @@ static int mapheaderinfo_get(lua_State *L) lua_pushinteger(L, header->palette); else if (fastcmp(field,"numlaps")) lua_pushinteger(L, header->numlaps); - else if (fastcmp(field,"unlockrequired")) - lua_pushinteger(L, header->unlockrequired); else if (fastcmp(field,"levelselect")) lua_pushinteger(L, header->levelselect); else if (fastcmp(field,"levelflags")) diff --git a/src/m_cond.c b/src/m_cond.c index 1bd2c13ac..fb1f6ee1a 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1001,8 +1001,38 @@ boolean M_SecretUnlocked(INT32 type, boolean local) #endif //if 0 } +boolean M_CupLocked(cupheader_t *cup) +{ + UINT8 i; + + // Don't lock maps in dedicated servers. + // That just makes hosts' lives hell. + if (dedicated) + return false; + + // No skipping over any part of your marathon. + if (marathonmode) + return false; + + if (!cup) + return false; + + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + if (unlockables[i].type != SECRET_CUP) + continue; + if (M_UnlockableCup(&unlockables[i]) != cup) + continue; + return !M_CheckNetUnlockByID(i); + } + + return false; +} + boolean M_MapLocked(INT32 mapnum) { + UINT8 i; + // Don't lock maps in dedicated servers. // That just makes hosts' lives hell. if (dedicated) @@ -1017,11 +1047,21 @@ boolean M_MapLocked(INT32 mapnum) if (mapheaderinfo[mapnum-1]->cup) { - if (!M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->cup->unlockrequired)) - return true; + return M_CupLocked(mapheaderinfo[mapnum-1]->cup); } - return !M_CheckNetUnlockByID(mapheaderinfo[mapnum-1]->unlockrequired); + for (i = 0; i < MAXUNLOCKABLES; ++i) + { + if (unlockables[i].type != SECRET_CUP) + continue; + if (!unlockables[i].stringVar || !unlockables[i].stringVar[0]) + continue; + if (G_MapNumber(unlockables[i].stringVar) != mapnum-1) + continue; + return !M_CheckNetUnlockByID(i); + } + + return false; } INT32 M_CountMedals(boolean all) @@ -1150,6 +1190,40 @@ INT32 M_UnlockableFollowerNum(unlockable_t *unlock) return -1; } +cupheader_t *M_UnlockableCup(unlockable_t *unlock) +{ + cupheader_t *cup = kartcupheaders; + + if (unlock->type != SECRET_CUP) + { + // This isn't a cup unlockable... + return NULL; + } + + if (unlock->stringVar && unlock->stringVar[0]) + { + // Get the cup from the string. + while (cup) + { + if (!strcmp(cup->name, unlock->stringVar)) + break; + cup = cup->next; + } + } + else + { + // Use the number directly. + while (cup) + { + if (cup->id == unlock->variable) + break; + cup = cup->next; + } + } + + return cup; +} + // ---------------- // Misc Emblem shit // ---------------- diff --git a/src/m_cond.h b/src/m_cond.h index 8932805d4..fd8037e2f 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -99,7 +99,7 @@ typedef enum // One step above bragging rights SECRET_EXTRAMEDAL, // Extra medal for your counter - // Level restrictions (TODO) + // Level restrictions SECRET_CUP, // Permit access to entire cup (overrides SECRET_MAP) SECRET_MAP, // Permit access to single map @@ -207,6 +207,7 @@ UINT8 M_CompletionEmblems(void); // Checking unlockable status boolean M_CheckNetUnlockByID(UINT8 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); +boolean M_CupLocked(cupheader_t *cup); boolean M_MapLocked(INT32 mapnum); INT32 M_CountMedals(boolean all); @@ -223,6 +224,8 @@ UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_UnlockableFollowerNum(unlockable_t *unlock); +cupheader_t *M_UnlockableCup(unlockable_t *unlock); + INT32 M_EmblemSkinNum(emblem_t *emblem); #define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a]) diff --git a/src/p_setup.c b/src/p_setup.c index e5cbba81e..4b2e9323b 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -400,7 +400,6 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->palette = UINT16_MAX; mapheaderinfo[num]->encorepal = UINT16_MAX; mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT; - mapheaderinfo[num]->unlockrequired = MAXUNLOCKABLES; mapheaderinfo[num]->levelselect = 0; mapheaderinfo[num]->levelflags = 0; mapheaderinfo[num]->menuflags = 0; From 20c754ca66dac991c408d28fa86501e24ca85b7f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 14:49:33 +0000 Subject: [PATCH 51/95] Preview for SECRET_CUP and SECRET_MAP Includes a dummied out alternate SECRET_CUP with more consistency to the idea of graphics exclusively in lower left, but matching the cup select screen won out. --- src/k_menudraw.c | 51 ++++++++++++++++++++++++++++++++++++++++++------ src/k_menufunc.c | 1 + 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d9a23ac97..3f7ded054 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4623,6 +4623,45 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } break; } + case SECRET_CUP: + { + cupheader_t *cup = M_UnlockableCup(ref); +#if 0 // First attempt + UINT8 i = cup->numlevels; + + x = 4; + y = (BASEVIDHEIGHT-4) - 38; + + while (i > 0) + { + i--; + K_DrawMapThumbnail( + (x+(i*2))<cachedlevels[i], + NULL); + } +#else + M_DrawCupPreview(146, cup); +#endif + break; + } + case SECRET_MAP: + { + if (ref->stringVar && ref->stringVar[0]) + { + UINT16 mapnum = G_MapNumber(ref->stringVar); + K_DrawMapThumbnail( + (x-30)< 0) challengesmenu.unlockcount[CC_ANIM]--; + M_CupSelectTick(); if (challengesmenu.pending) { From fc6eff65c2ca0df40b5c3cfa5e20910f2cb59baf Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 16:15:31 +0000 Subject: [PATCH 52/95] Cache the result of M_UnlockableSkinNum/FollowerNum/MapNum/Cup Improves performance on mapheader iteration for M_Map/CupLocked significantly. --- src/deh_soc.c | 2 ++ src/k_menudraw.c | 17 ++++------ src/m_cond.c | 85 ++++++++++++++++++++++++++++++++++++++---------- src/m_cond.h | 2 ++ 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index ce7ac6515..f49e1df20 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2278,11 +2278,13 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_ITEMFINDER; else unlockables[num].type = (INT16)i; + unlockables[num].stringVarCache = -1; } else if (fastcmp(word, "VAR")) { Z_Free(unlockables[num].stringVar); unlockables[num].stringVar = Z_StrDup(word2); + unlockables[num].stringVarCache = -1; unlockables[num].variable = (INT16)i; } else if (fastcmp(word, "ICON")) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 3f7ded054..8f2cb4a34 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4650,16 +4650,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } case SECRET_MAP: { - if (ref->stringVar && ref->stringVar[0]) - { - UINT16 mapnum = G_MapNumber(ref->stringVar); - K_DrawMapThumbnail( - (x-30)<stringVar && unlock->stringVar[0]) { + INT32 skinnum; + + if (unlock->stringVarCache != -1) + { + return unlock->stringVarCache; + } + // Get the skin from the string. - INT32 skinnum = R_SkinAvailable(unlock->stringVar); + skinnum = R_SkinAvailable(unlock->stringVar); if (skinnum != -1) { + unlock->stringVarCache = skinnum; return skinnum; } } @@ -1172,10 +1178,18 @@ INT32 M_UnlockableFollowerNum(unlockable_t *unlock) if (unlock->stringVar && unlock->stringVar[0]) { + INT32 skinnum; + + if (unlock->stringVarCache != -1) + { + return unlock->stringVarCache; + } + // Get the skin from the string. - INT32 skinnum = K_FollowerAvailable(unlock->stringVar); + skinnum = K_FollowerAvailable(unlock->stringVar); if (skinnum != -1) { + unlock->stringVarCache = skinnum; return skinnum; } } @@ -1193,6 +1207,7 @@ INT32 M_UnlockableFollowerNum(unlockable_t *unlock) cupheader_t *M_UnlockableCup(unlockable_t *unlock) { cupheader_t *cup = kartcupheaders; + INT16 val = unlock->variable-1; if (unlock->type != SECRET_CUP) { @@ -1202,28 +1217,62 @@ cupheader_t *M_UnlockableCup(unlockable_t *unlock) if (unlock->stringVar && unlock->stringVar[0]) { - // Get the cup from the string. - while (cup) + if (unlock->stringVarCache == -1) { - if (!strcmp(cup->name, unlock->stringVar)) - break; - cup = cup->next; + // Get the cup from the string. + while (cup) + { + if (!strcmp(cup->name, unlock->stringVar)) + break; + cup = cup->next; + } + + if (cup) + { + unlock->stringVarCache = cup->id; + } + return cup; } + + val = unlock->stringVarCache; } - else + else if (val == -1) { - // Use the number directly. - while (cup) - { - if (cup->id == unlock->variable) - break; - cup = cup->next; - } + return NULL; + } + + // Use the number directly. + while (cup) + { + if (cup->id == val) + break; + cup = cup->next; } return cup; } +INT16 M_UnlockableMapNum(unlockable_t *unlock) +{ + if (unlock->type != SECRET_MAP) + { + // This isn't a map unlockable... + return NEXTMAP_INVALID; + } + + if (unlock->stringVar && unlock->stringVar[0]) + { + if (unlock->stringVarCache == -1) + { + unlock->stringVarCache = G_MapNumber(unlock->stringVar); + } + + return unlock->stringVarCache; + } + + return NEXTMAP_INVALID; +} + // ---------------- // Misc Emblem shit // ---------------- diff --git a/src/m_cond.h b/src/m_cond.h index fd8037e2f..7dca580da 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -89,6 +89,7 @@ struct unlockable_t INT16 type; INT16 variable; char *stringVar; + INT16 stringVarCache; UINT8 majorunlock; }; @@ -225,6 +226,7 @@ UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_UnlockableFollowerNum(unlockable_t *unlock); cupheader_t *M_UnlockableCup(unlockable_t *unlock); +INT16 M_UnlockableMapNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); From 0ef95875fa2a9c1caa8229a32def35c7de940810 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 16:15:48 +0000 Subject: [PATCH 53/95] Fix copypaste typo for M_MapLocked --- src/m_cond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 5248008ed..8c9223c13 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1052,7 +1052,7 @@ boolean M_MapLocked(INT32 mapnum) for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type != SECRET_CUP) + if (unlockables[i].type != SECRET_MAP) continue; if (M_UnlockableMapNum(&unlockables[i]) != mapnum-1) continue; From d5ab51fe38388354eafbdc3f8b34760de76b1007 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 16:18:56 +0000 Subject: [PATCH 54/95] Fixed unsignedness of M_UnlockableMapNum return type --- src/m_cond.c | 2 +- src/m_cond.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 8c9223c13..d0a911b25 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1252,7 +1252,7 @@ cupheader_t *M_UnlockableCup(unlockable_t *unlock) return cup; } -INT16 M_UnlockableMapNum(unlockable_t *unlock) +UINT16 M_UnlockableMapNum(unlockable_t *unlock) { if (unlock->type != SECRET_MAP) { diff --git a/src/m_cond.h b/src/m_cond.h index 7dca580da..7aaf6e23a 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -226,7 +226,7 @@ UINT8 M_GotLowEnoughTime(INT32 tictime); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_UnlockableFollowerNum(unlockable_t *unlock); cupheader_t *M_UnlockableCup(unlockable_t *unlock); -INT16 M_UnlockableMapNum(unlockable_t *unlock); +UINT16 M_UnlockableMapNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); From ac95f8b494e99c8517a7ea85c858359f6dedf794 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 17:25:47 +0000 Subject: [PATCH 55/95] Improve the Preview area for Challenges significantly. - New Previews of random-per-session maps with an overlay for - SECRET_ENCORE (Phantom Ruby) - SECRET_TIMEATTACK and SECRET_BREAKTHECAPSULES (Lap center circle stopwatch) - SECRET_HARDSPEED (Rocket Sneaker, a sneaky reference to the current MS webview) - Fix the position of SECRET_MAP's map to match the above - Add a grey background at the bottom to really bring the feng shui together --- src/k_menudraw.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8f2cb4a34..563fa1e45 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3940,8 +3940,8 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) { static angle_t rubyfloattime = 0; const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); - V_DrawFixedPatch((x+40)<= MAXUNLOCKABLES) { + V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); return; } @@ -4587,9 +4589,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) if (!gamedata->unlocked[challengesmenu.currentunlock]) { // todo draw some sort of question mark? + V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); return; } + if (ref->type != SECRET_CUP) + V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); + switch (ref->type) { case SECRET_SKIN: @@ -4652,18 +4658,94 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) { UINT16 mapnum = M_UnlockableMapNum(ref); K_DrawMapThumbnail( - (x-30)< nummapheaders) + { + encoremapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL); + } + specialmap = encoremapcache; + break; + } + case SECRET_TIMEATTACK: + { + static UINT16 tamapcache = NEXTMAP_INVALID; + if (tamapcache > nummapheaders) + { + tamapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL); + } + specialmap = tamapcache; + break; + } + case SECRET_BREAKTHECAPSULES: + { + static UINT16 btcmapcache = NEXTMAP_INVALID; + if (btcmapcache > nummapheaders) + { + btcmapcache = G_RandMap(G_TOLFlag(GT_BATTLE), -1, 2, 0, false, NULL); + } + specialmap = btcmapcache; + break; + } + case SECRET_HARDSPEED: + { + static UINT16 hardmapcache = NEXTMAP_INVALID; + if (hardmapcache > nummapheaders) + { + hardmapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL); + } + specialmap = hardmapcache; + break; + } default: { break; } } + + if (specialmap == NEXTMAP_INVALID || !ref) + return; + + x -= 50; + y = 146+2; + + K_DrawMapThumbnail( + (x)<type == SECRET_ENCORE) ? V_FLIP : 0, + specialmap, + NULL); + + if (ref->type == SECRET_ENCORE) + { + static angle_t rubyfloattime = 0; + const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); + V_DrawFixedPatch((x+40)<type == SECRET_HARDSPEED) + { + V_DrawFixedPatch((x+40-25)< Date: Tue, 13 Dec 2022 17:37:26 +0000 Subject: [PATCH 56/95] Actually lock Time Attack and Capsule Attack behind SECRET_TIMEATTACK and SECRET_BREAKTHECAPSULES --- src/k_menufunc.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index a831ab7c2..bcfc87bef 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3295,17 +3295,25 @@ void M_SetupGametypeMenu(INT32 choice) PLAY_GamemodesDef.prevMenu = currentMenu; - if (cv_splitplayers.value <= 1) + // Battle and Capsules disabled + PLAY_GamemodesMenu[1].status = IT_DISABLED; + PLAY_GamemodesMenu[2].status = IT_DISABLED; + + if (cv_splitplayers.value > 1) { - // Remove Battle, add Capsules - PLAY_GamemodesMenu[1].status = IT_DISABLED; + // Re-add Battle + PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; + } + else if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) + { + // Re-add Capsules PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; } else { - // Add Battle, remove Capsules - PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; - PLAY_GamemodesMenu[2].status = IT_DISABLED; + // Only one non-Back entry, let's skip straight to Race. + M_SetupRaceMenu(-1); + return; } M_SetupNextMenu(&PLAY_GamemodesDef, false); @@ -3317,15 +3325,15 @@ void M_SetupRaceMenu(INT32 choice) PLAY_RaceGamemodesDef.prevMenu = currentMenu; + // Time Attack disabled + PLAY_RaceGamemodesMenu[2].status = IT_DISABLED; + // Time Attack is 1P only - if (cv_splitplayers.value <= 1) + if (cv_splitplayers.value <= 1 + && M_SecretUnlocked(SECRET_TIMEATTACK, true)) { PLAY_RaceGamemodesMenu[2].status = IT_STRING | IT_CALL; } - else - { - PLAY_RaceGamemodesMenu[2].status = IT_DISABLED; - } M_SetupNextMenu(&PLAY_RaceGamemodesDef, false); } From ff1574f80f1e98344ac488125c40732a1e8d8790 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 17:48:35 +0000 Subject: [PATCH 57/95] Allow for marking Emblems as "not a Medal" via SOC definition Also remove long-unused `hint` string --- src/deh_soc.c | 10 ++-------- src/m_cond.c | 2 ++ src/m_cond.h | 16 ++++++++-------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index f49e1df20..90434c057 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2124,14 +2124,6 @@ void reademblemdata(MYFILE *f, INT32 num) word2 = tmp += 2; value = atoi(word2); // used for numerical settings - // Up here to allow lowercase in hints - if (fastcmp(word, "HINT")) - { - while ((tmp = strchr(word2, '\\'))) - *tmp = '\n'; - deh_strlcpy(emblemlocations[num-1].hint, word2, sizeof (emblemlocations[num-1].hint), va("Emblem %d: hint", num)); - continue; - } strupr(word2); if (fastcmp(word, "TYPE")) @@ -2172,6 +2164,8 @@ void reademblemdata(MYFILE *f, INT32 num) emblemlocations[num-1].var = get_number(word2); } + else if (fastcmp(word, "NOTMEDAL")) + emblemlocations[num-1].notMedal = (boolean)(i != 0 || word2[0] == 'T' || word2[0] == 'Y'); else deh_warning("Emblem %d: unknown word '%s'", num, word); } diff --git a/src/m_cond.c b/src/m_cond.c index d0a911b25..d14fedb44 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1067,6 +1067,8 @@ INT32 M_CountMedals(boolean all) INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { + if (emblemlocations[i].notMedal) + continue; if (!all && !gamedata->collected[i]) continue; found++; diff --git a/src/m_cond.h b/src/m_cond.h index 7aaf6e23a..99e3492b9 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -69,14 +69,14 @@ struct conditionset_t struct emblem_t { - UINT8 type; ///< Emblem type - INT16 tag; ///< Tag of emblem mapthing - char * level; ///< Level on which this emblem can be found. - UINT8 sprite; ///< emblem sprite to use, 0 - 25 - UINT16 color; ///< skincolor to use - INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) - char *stringVar; ///< String version - char hint[110]; ///< Hint for emblem hints menu + UINT8 type; ///< Emblem type + INT16 tag; ///< Tag of emblem mapthing + char * level; ///< Level on which this emblem can be found. + UINT8 sprite; ///< emblem sprite to use, 0 - 25 + UINT16 color; ///< skincolor to use + INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) + char *stringVar; ///< String version + boolean notMedal; ///< Not a Medal? }; // Unlockable information From b26da374774fe2ccf0ed1c7870920c7242f7b52c Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 18:10:47 +0000 Subject: [PATCH 58/95] Forgot to stage compilation fix --- src/deh_soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 90434c057..d2cfa6c49 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2165,7 +2165,7 @@ void reademblemdata(MYFILE *f, INT32 num) emblemlocations[num-1].var = get_number(word2); } else if (fastcmp(word, "NOTMEDAL")) - emblemlocations[num-1].notMedal = (boolean)(i != 0 || word2[0] == 'T' || word2[0] == 'Y'); + emblemlocations[num-1].notMedal = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y'); else deh_warning("Emblem %d: unknown word '%s'", num, word); } From 117b4c6a7b88f697838848db8b008a75a79ab6ea Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 18:20:52 +0000 Subject: [PATCH 59/95] `GAMEDATA` `MainCfg` SOC block adustment Discussed in DMs - All other `MainCfg` block properties require a custom gamedata (or be a main file) to be modified - When a custom gamedata is set, clear all unlockables, conditionsets, and emblems are unconditionally cleared - You may also Clear Levels only if a custom gamedata is used --- src/deh_soc.c | 80 +++++++++++++++++++++++++++----------------------- src/deh_soc.h | 2 +- src/dehacked.c | 19 ++---------- 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d2cfa6c49..d66d568df 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2525,7 +2525,7 @@ void readconditionset(MYFILE *f, UINT8 setnum) Z_Free(s); } -void readmaincfg(MYFILE *f) +void readmaincfg(MYFILE *f, boolean mainfile) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); char *word = s; @@ -2565,7 +2565,49 @@ void readmaincfg(MYFILE *f) value = atoi(word2); // used for numerical settings - if (fastcmp(word, "EXECCFG")) + if (fastcmp(word, "GAMEDATA")) + { + size_t filenamelen; + + // Check the data filename so that mods + // can't write arbitrary files. + if (!GoodDataFileName(word2)) + I_Error("Maincfg: bad data file name '%s'\n", word2); + + G_SaveGameData(); + strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); + strlwr(gamedatafilename); + savemoddata = true; + majormods = false; + + // Also save a time attack folder + filenamelen = strlen(gamedatafilename)-4; // Strip off the extension + filenamelen = min(filenamelen, sizeof (timeattackfolder)); + strncpy(timeattackfolder, gamedatafilename, filenamelen); + timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0'; + + strcpy(savegamename, timeattackfolder); + strlcat(savegamename, "%u.ssg", sizeof(savegamename)); + // can't use sprintf since there is %u in savegamename + strcatbf(savegamename, srb2home, PATHSEP); + + strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder)); + strcatbf(liveeventbackup, srb2home, PATHSEP); + + refreshdirmenu |= REFRESHDIR_GAMEDATA; + gamedataadded = true; + titlechanged = true; + + clear_unlockables(); + clear_conditionsets(); + clear_emblems(); + //clear_levels(); + } + else if (!mainfile && !gamedataadded) + { + deh_warning("You must define a custom gamedata to use \"%s\"", word); + } + else if (fastcmp(word, "EXECCFG")) { if (strchr(word2, '.')) COM_BufAddText(va("exec %s\n", word2)); @@ -2750,40 +2792,6 @@ void readmaincfg(MYFILE *f) { maxXtraLife = (UINT8)get_number(word2); } - - else if (fastcmp(word, "GAMEDATA")) - { - size_t filenamelen; - - // Check the data filename so that mods - // can't write arbitrary files. - if (!GoodDataFileName(word2)) - I_Error("Maincfg: bad data file name '%s'\n", word2); - - G_SaveGameData(); - strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); - strlwr(gamedatafilename); - savemoddata = true; - majormods = false; - - // Also save a time attack folder - filenamelen = strlen(gamedatafilename)-4; // Strip off the extension - filenamelen = min(filenamelen, sizeof (timeattackfolder)); - strncpy(timeattackfolder, gamedatafilename, filenamelen); - timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0'; - - strcpy(savegamename, timeattackfolder); - strlcat(savegamename, "%u.ssg", sizeof(savegamename)); - // can't use sprintf since there is %u in savegamename - strcatbf(savegamename, srb2home, PATHSEP); - - strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder)); - strcatbf(liveeventbackup, srb2home, PATHSEP); - - refreshdirmenu |= REFRESHDIR_GAMEDATA; - gamedataadded = true; - titlechanged = true; - } else if (fastcmp(word, "RESETDATA")) { P_ResetData(value); diff --git a/src/deh_soc.h b/src/deh_soc.h index d6d4d9e01..6aed7e7f2 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -57,7 +57,7 @@ sfxenum_t get_sfx(const char *word); skincolornum_t get_skincolor(const char *word); void readwipes(MYFILE *f); -void readmaincfg(MYFILE *f); +void readmaincfg(MYFILE *f, boolean mainfile); void readconditionset(MYFILE *f, UINT8 setnum); void readunlockable(MYFILE *f, INT32 num); void reademblemdata(MYFILE *f, INT32 num); diff --git a/src/dehacked.c b/src/dehacked.c index 35f603bdd..037bd548e 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -217,7 +217,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) else if (fastcmp(word, "MAINCFG")) { G_SetGameModified(multiplayer, true); - readmaincfg(f); + readmaincfg(f, mainfile); continue; } else if (fastcmp(word, "WIPES")) @@ -548,28 +548,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) // (then again, modifiedgame will prevent game data saving anyway) else if (fastcmp(word, "CLEAR")) { - boolean clearall = (fastcmp(word2, "ALL")); - if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); continue; } - if (clearall || fastcmp(word2, "UNLOCKABLES")) - { - clear_unlockables(); - } - - if (clearall || fastcmp(word2, "EMBLEMS")) - { - clear_emblems(); - } - - if (clearall || fastcmp(word2, "CONDITIONSETS")) - clear_conditionsets(); - - if (clearall || fastcmp(word2, "LEVELS")) + if (fastcmp(word2, "LEVELS")) clear_levels(); } else From e2e6ce610826a4dcdf1304612c77eac4f91a793f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 21:48:49 +0000 Subject: [PATCH 60/95] Fix follower translation colormap to use TC_DEFAULT --- src/k_menudraw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 563fa1e45..0616a553b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4520,7 +4520,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili if (skin != -1) { UINT16 col = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); - colormap = R_GetTranslationColormap(skin, col, GTC_MENUCACHE); + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); pat = W_CachePatchName(followers[skin].icon, PU_CACHE); } break; @@ -4624,7 +4624,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) if (fskin != -1) { UINT16 col = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, cv_playercolor[0].value); - colormap = R_GetTranslationColormap(fskin, col, GTC_MENUCACHE); + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); M_DrawFollowerSprite(x - 16, y, fskin, false, 0, colormap, NULL); } break; From dbb96dabd6a904ffeeb4d1f9bded04e1fec7a0ec Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:10:35 +0000 Subject: [PATCH 61/95] Fix M_UnlockableFollowerNum to work with followers whose names have spaces in --- src/m_cond.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index d14fedb44..98152cce7 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1181,14 +1181,25 @@ INT32 M_UnlockableFollowerNum(unlockable_t *unlock) if (unlock->stringVar && unlock->stringVar[0]) { INT32 skinnum; + size_t i; + char testname[SKINNAMESIZE+1]; if (unlock->stringVarCache != -1) { return unlock->stringVarCache; } + // match deh_soc readfollower() + for (i = 0; unlock->stringVar[i]; i++) + { + testname[i] = unlock->stringVar[i]; + if (unlock->stringVar[i] == '_') + testname[i] = ' '; + } + testname[i] = '\0'; + // Get the skin from the string. - skinnum = K_FollowerAvailable(unlock->stringVar); + skinnum = K_FollowerAvailable(testname); if (skinnum != -1) { unlock->stringVarCache = skinnum; From 0e8c739efc9ce1310107dbadc6d247cd0b890e2f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:11:07 +0000 Subject: [PATCH 62/95] Fix M_GetLevelEmblems to work with gamemap index (as it was used for) instead of mapheaderinfo index --- src/m_cond.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 98152cce7..d9694db8b 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1301,7 +1301,7 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum) if (mapnum > 0) { - map = mapnum; + map = mapnum-1; i = numemblems; } @@ -1312,8 +1312,10 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum) if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) continue; - if (checkLevel == map) - return &emblemlocations[i]; + if (checkLevel != map) + continue; + + return &emblemlocations[i]; } return NULL; } From 7081ffd509cb914d739dd9f3fffc7c690fffede7 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:19:37 +0000 Subject: [PATCH 63/95] Fixes and changes for Emblem system - New `flags` field - Permits coexistence of var and flags - `notMedal` boolean is now `GE_NOTMEDAL` - New `GE_TIMED` flag - Disappears `var` tics after `starttime` - Improved M_GetConditionString handling for specific Emblem grabs - More explicit error handling for invalid Emblem --- src/deh_soc.c | 4 ++-- src/deh_tables.c | 3 ++- src/info.c | 2 +- src/m_cond.c | 41 +++++++++++++++++++++++++++++++++++++++-- src/m_cond.h | 16 ++++++++-------- src/p_mobj.c | 38 +++++++++++++++++++++++++++++++------- 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d66d568df..c0ebb7d9c 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2164,8 +2164,8 @@ void reademblemdata(MYFILE *f, INT32 num) emblemlocations[num-1].var = get_number(word2); } - else if (fastcmp(word, "NOTMEDAL")) - emblemlocations[num-1].notMedal = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y'); + else if (fastcmp(word, "FLAGS")) + emblemlocations[num-1].flags = get_number(word2); else deh_warning("Emblem %d: unknown word '%s'", num, word); } diff --git a/src/deh_tables.c b/src/deh_tables.c index ab0ef138b..6767e81ca 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6341,7 +6341,8 @@ struct int_const_s const INT_CONST[] = { {"SF_X2AWAYSOUND",SF_X2AWAYSOUND}, // Global emblem var flags - // none in kart yet + {"GE_NOTMEDAL", GE_NOTMEDAL}, + {"GE_TIMED", GE_TIMED}, // Map emblem var flags {"ME_ENCORE",ME_ENCORE}, diff --git a/src/info.c b/src/info.c index 3c6612ac4..6708b528a 100644 --- a/src/info.c +++ b/src/info.c @@ -7985,7 +7985,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound - 8, // reactiontime + 0, // reactiontime sfx_None, // attacksound S_NULL, // painstate 0, // painchance diff --git a/src/m_cond.c b/src/m_cond.c index d9694db8b..cf10e499b 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -655,6 +655,42 @@ static const char *M_GetConditionString(condition_t *cn) work = va("Get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title); break; case ET_GLOBAL: + { + const char *astr, *colorstr, *medalstr; + + if (emblemlocations[i].flags & GE_NOTMEDAL) + { + astr = "a "; + colorstr = ""; + medalstr = "secret"; + } + else if (emblemlocations[i].color <= 0 || emblemlocations[i].color >= numskincolors) + { + Z_Free(title); + return va("INVALID MEDAL COLOR \"%d:%d:%d\"", cn->requirement, emblemlocations[i].tag, checkLevel); + } + else + { + astr = "the "; + colorstr = skincolors[emblemlocations[i].color].name; + medalstr = " Medal"; + } + + if (emblemlocations[i].flags & GE_TIMED) + { + work = va("Find %s%s%s in %s before %i:%02i.%02i", + astr, colorstr, medalstr, title, + G_TicsToMinutes(emblemlocations[i].var, true), + G_TicsToSeconds(emblemlocations[i].var), + G_TicsToCentiseconds(emblemlocations[i].var)); + } + else + { + work = va("Find %s%s%s in %s", + astr, colorstr, medalstr, title); + } + break; + } default: work = va("Find a secret in %s", title); break; @@ -941,7 +977,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa continue; levelnum = checkLevel; - embtype = emblemlocations[i].var; + embtype = emblemlocations[i].flags; flags = MV_BEATEN; if (embtype & ME_ENCORE) @@ -1067,7 +1103,8 @@ INT32 M_CountMedals(boolean all) INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].notMedal) + if ((emblemlocations[i].type == ET_GLOBAL) + && (emblemlocations[i].flags & GE_NOTMEDAL)) continue; if (!all && !gamedata->collected[i]) continue; diff --git a/src/m_cond.h b/src/m_cond.h index 99e3492b9..045772f12 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -55,17 +55,17 @@ struct conditionset_t }; // Emblem information -#define ET_GLOBAL 0 // Emblem with a position in space -#define ET_MAP 1 // Beat the map -#define ET_TIME 2 // Get the time +#define ET_GLOBAL 0 // Emblem with a position in space +#define ET_MAP 1 // Beat the map +#define ET_TIME 2 // Get the time //#define ET_DEVTIME 3 // Time, but the value is tied to a Time Trial demo, not pre-defined // Global emblem flags -// (N/A to Kart yet) -//#define GE_OH 1 +#define GE_NOTMEDAL 1 // Doesn't count towards number of medals +#define GE_TIMED 2 // Disappears after var time // Map emblem flags -#define ME_ENCORE 1 +#define ME_ENCORE 1 // Achieve in Encore struct emblem_t { @@ -74,9 +74,9 @@ struct emblem_t char * level; ///< Level on which this emblem can be found. UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT16 color; ///< skincolor to use - INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) + INT32 flags; ///< GE or ME constants + INT32 var; ///< If needed, specifies extra information char *stringVar; ///< String version - boolean notMedal; ///< Not a Medal? }; // Unlockable information diff --git a/src/p_mobj.c b/src/p_mobj.c index e4b5fc86d..1019bf7f7 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6931,17 +6931,38 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; case MT_EMBLEM: + { + INT32 trans = 0; + + mobj->frame &= ~FF_TRANSMASK; + mobj->renderflags &= ~RF_TRANSMASK; + if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1)) { - mobj->frame |= (tr_trans50 << FF_TRANSSHIFT); + trans = tr_trans50; + mobj->renderflags |= (tr_trans50 << RF_TRANSSHIFT); } - else + + if (mobj->reactiontime > 0 + && leveltime > starttime) { - mobj->frame &= ~FF_TRANSMASK; + INT32 diff = mobj->reactiontime - (signed)(leveltime - starttime); + if (diff < 10) + { + if (diff <= 0) + { + P_RemoveMobj(mobj); + return false; + } + + trans = max(trans, 10-diff); + } } - if (mobj->flags2 & MF2_NIGHTSPULL) - P_NightsItemChase(mobj); + + mobj->renderflags |= (trans << RF_TRANSSHIFT); + break; + } case MT_FLOATINGITEM: { mobj->pitch = mobj->roll = 0; @@ -12008,7 +12029,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) if (!emblem) { - CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags)); + CONS_Alert(CONS_WARNING, "P_SetupEmblem: No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags)); return false; } @@ -12023,7 +12044,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) mobj->frame &= ~FF_TRANSMASK; - if (emblemlocations[j].type == ET_GLOBAL) + if (emblemlocations[j].flags & GE_TIMED) { mobj->reactiontime = emblemlocations[j].var; } @@ -12471,7 +12492,10 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean case MT_EMBLEM: { if (!P_SetupEmblem(mthing, mobj)) + { + P_RemoveMobj(mobj); return false; + } break; } case MT_SKYBOX: From 5bb149c47a07210aa28ecb67aa45f48043940aa3 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:20:06 +0000 Subject: [PATCH 64/95] Permit M_CheckNetUnlockByID if conditionset is zeroed --- src/m_cond.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index cf10e499b..8730e28b4 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -999,7 +999,8 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa boolean M_CheckNetUnlockByID(UINT8 unlockid) { - if (unlockid >= MAXUNLOCKABLES) + if (unlockid >= MAXUNLOCKABLES + || !unlockables[unlockid].conditionset) { return true; // default permit } From 2a35f7d19764d8cbdd4b3f60fed616e5cdcf9644 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:20:24 +0000 Subject: [PATCH 65/95] Adjust y offset for challenge description --- src/k_menudraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0616a553b..d5f42ad60 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4884,6 +4884,6 @@ challengedesc: // Conditions for unlock if (challengesmenu.unlockcondition != NULL) { - V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 40, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); + V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 32, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); } } From 19ab3a84456c8711fc559c2afff01a471c2c4cbd Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 13 Dec 2022 22:28:46 +0000 Subject: [PATCH 66/95] Only show hints if an unlocked tile is adjacent --- src/k_menudraw.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d5f42ad60..300b9543a 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4882,7 +4882,14 @@ challengedesc: M_DrawChallengePreview(x, y); // Conditions for unlock - if (challengesmenu.unlockcondition != NULL) + i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; + + if (challengesmenu.unlockcondition != NULL + && ((gamedata->unlocked[challengesmenu.currentunlock] == true) + || ((challengesmenu.extradata != NULL) + && (challengesmenu.extradata[i] & CHE_HINT)) + ) + ) { V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 32, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); } From f87db3cfabd03a2c9c3dcefb13b8c7befa9c9f9a Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 13:08:06 +0000 Subject: [PATCH 67/95] Further K_DrawMapThumbnail changes. - Precache M_NOLVL. - Make all width calculations relative to a lump 320 pixels wide, to make outdated map thumbnails much, MUCH more visible. - Add K_DrawLikeMapThumbnail to draw a 320x200 patch like a map thumbnail (currently used only for RANDOMLVL). --- src/hu_stuff.c | 3 ++- src/k_hud.c | 11 ++++++++--- src/k_hud.h | 1 + src/r_defs.h | 2 +- src/y_inter.c | 24 +++++++++++++++++++++--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e30a725e9..40944e8b2 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -98,7 +98,7 @@ static char hu_tick; //------------------------------------------- patch_t *missingpat; -patch_t *blanklvl; +patch_t *blanklvl, *nolvl; // song credits static patch_t *songcreditbg; @@ -186,6 +186,7 @@ void HU_LoadGraphics(void) Font_Load(); HU_UpdatePatch(&blanklvl, "BLANKLVL"); + HU_UpdatePatch(&nolvl, "M_NOLVL"); HU_UpdatePatch(&songcreditbg, "K_SONGCR"); diff --git a/src/k_hud.c b/src/k_hud.c index 3aa2f7a68..486a576d4 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1130,7 +1130,7 @@ void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, if (map >= nummapheaders || !mapheaderinfo[map]) { - PictureOfLevel = W_CachePatchName("M_NOLVL", PU_CACHE); + PictureOfLevel = nolvl; } else if (!mapheaderinfo[map]->thumbnailPic) { @@ -1141,14 +1141,19 @@ void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, PictureOfLevel = mapheaderinfo[map]->thumbnailPic; } + K_DrawLikeMapThumbnail(x, y, width, flags, PictureOfLevel, colormap); +} + +void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap) +{ if (flags & V_FLIP) x += width; V_DrawFixedPatch( x, y, - FixedDiv(width, (SHORT(PictureOfLevel->width) << FRACBITS)), + FixedDiv(width, (320 << FRACBITS)), flags, - PictureOfLevel, + patch, colormap ); } diff --git a/src/k_hud.h b/src/k_hud.h index be3e6b5b9..ba4f8f5cf 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -39,6 +39,7 @@ void K_drawKartFreePlay(void); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol); void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap); +void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap); extern patch_t *kp_facehighlight[8]; diff --git a/src/r_defs.h b/src/r_defs.h index 59faac3c0..9216603ed 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -821,7 +821,7 @@ struct patch_t }; extern patch_t *missingpat; -extern patch_t *blanklvl; +extern patch_t *blanklvl, *nolvl; #if defined(_MSC_VER) #pragma pack(1) diff --git a/src/y_inter.c b/src/y_inter.c index 63cd54fe7..8cb21fe04 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1074,7 +1074,13 @@ void Y_VoteDrawer(void) if (i == 3) { str = "RANDOM"; - V_DrawSmallScaledPatch(BASEVIDWIDTH-100, y, V_SNAPTORIGHT, randomlvl); + K_DrawLikeMapThumbnail( + (BASEVIDWIDTH-100)<= 3 && (i != pickedvote || voteendtic == -1)) { - V_DrawTinyScaledPatch(x, y, V_SNAPTOLEFT, randomlvl); + K_DrawLikeMapThumbnail( + (x)< Date: Wed, 14 Dec 2022 17:29:22 +0000 Subject: [PATCH 68/95] Make level titles capitalised on Challenges menu --- src/m_cond.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 8730e28b4..67a8733cc 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -566,6 +566,27 @@ static UINT8 M_CheckConditionSet(conditionset_t *c) return achievedSoFar; } +static char *M_BuildConditionTitle(UINT16 map) +{ + char *title, *ref; + + if (M_MapLocked(map+1)) + return Z_StrDup("???"); + + title = ref = G_BuildMapTitle(map+1); + + if (!title) + I_Error("M_BuildConditionTitle: out of memory"); + + while (*ref != '\0') + { + *ref = toupper(*ref); + ref++; + } + + return title; +} + // See also M_CheckCondition static const char *M_GetConditionString(condition_t *cn) { @@ -573,7 +594,7 @@ static const char *M_GetConditionString(condition_t *cn) char *title = NULL; const char *work = NULL; -#define BUILDCONDITIONTITLE(i) (M_MapLocked(i+1) ? Z_StrDup("???") : G_BuildMapTitle(i+1)) +#define BUILDCONDITIONTITLE(i) (M_BuildConditionTitle(i)) switch (cn->type) { From c693be47f8f39f50f045c48254c79e59c2b74830 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 17:30:39 +0000 Subject: [PATCH 69/95] Fix possible out of bounds memory access for M_MapLocked --- src/m_cond.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/m_cond.c b/src/m_cond.c index 67a8733cc..de31176ea 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1099,6 +1099,9 @@ boolean M_MapLocked(INT32 mapnum) // No skipping over any part of your marathon. if (marathonmode) return false; + + if (!mapnum || mapnum > nummapheaders) + return false; if (!mapheaderinfo[mapnum-1]) return false; From c7db58033809ca8da0d2c58f78c83b88a0fb9189 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 17:31:10 +0000 Subject: [PATCH 70/95] Remove debug print from Unlockable SOC color parse --- src/deh_soc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index c0ebb7d9c..b4482daa2 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2289,7 +2289,6 @@ void readunlockable(MYFILE *f, INT32 num) else if (fastcmp(word, "COLOR")) { unlockables[num].color = get_number(word2); - CONS_Printf("%d+1 has color %s\n",num+1, skincolors[unlockables[num].color].name); } else deh_warning("Unlockable %d: unknown word '%s'", num+1, word); From 2fbbe52ded0d6846e0b85539c684993d01008ea6 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 17:50:00 +0000 Subject: [PATCH 71/95] Cache level ID for Emblems, too --- src/deh_soc.c | 1 + src/m_cond.c | 30 +++++++++++++++++++++++++----- src/m_cond.h | 4 +++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index b4482daa2..a7726200c 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2142,6 +2142,7 @@ void reademblemdata(MYFILE *f, INT32 num) else if (fastcmp(word, "MAPNAME")) { emblemlocations[num-1].level = Z_StrDup(word2); + emblemlocations[num-1].levelCache = NEXTMAP_INVALID; } else if (fastcmp(word, "SPRITE")) { diff --git a/src/m_cond.c b/src/m_cond.c index de31176ea..9c0448ba6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -656,7 +656,7 @@ static const char *M_GetConditionString(condition_t *cn) INT32 checkLevel; i = cn->requirement-1; - checkLevel = G_MapNumber(emblemlocations[i].level); + checkLevel = M_EmblemMapNum(&emblemlocations[i]); if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel); @@ -952,7 +952,7 @@ UINT8 M_CheckLevelEmblems(void) if (emblemlocations[i].type < ET_TIME || gamedata->collected[i]) continue; - checkLevel = G_MapNumber(emblemlocations[i].level); + checkLevel = M_EmblemMapNum(&emblemlocations[i]); if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) continue; @@ -992,7 +992,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa if (emblemlocations[i].type < ET_TIME || gamedata->collected[i]) continue; - checkLevel = G_MapNumber(emblemlocations[i].level); + checkLevel = M_EmblemMapNum(&emblemlocations[i]); if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) continue; @@ -1339,7 +1339,12 @@ UINT16 M_UnlockableMapNum(unlockable_t *unlock) { if (unlock->stringVarCache == -1) { - unlock->stringVarCache = G_MapNumber(unlock->stringVar); + INT32 result = G_MapNumber(unlock->stringVar); + + if (result >= nummapheaders) + return result; + + unlock->stringVarCache = result; } return unlock->stringVarCache; @@ -1352,6 +1357,21 @@ UINT16 M_UnlockableMapNum(unlockable_t *unlock) // Misc Emblem shit // ---------------- +UINT16 M_EmblemMapNum(emblem_t *emblem) +{ + if (emblem->levelCache == NEXTMAP_INVALID) + { + UINT16 result = G_MapNumber(emblem->level); + + if (result >= nummapheaders) + return result; + + emblem->levelCache = result; + } + + return emblem->levelCache; +} + // Returns pointer to an emblem if an emblem exists for that level. // Pass -1 mapnum to continue from last emblem. // NULL if not found. @@ -1369,7 +1389,7 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum) while (--i >= 0) { - INT32 checkLevel = G_MapNumber(emblemlocations[i].level); + INT32 checkLevel = M_EmblemMapNum(&emblemlocations[i]); if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel]) continue; diff --git a/src/m_cond.h b/src/m_cond.h index 045772f12..af4bd4fc8 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -71,7 +71,8 @@ struct emblem_t { UINT8 type; ///< Emblem type INT16 tag; ///< Tag of emblem mapthing - char * level; ///< Level on which this emblem can be found. + char *level; ///< Level on which this emblem can be found. + INT16 levelCache; ///< Stored G_MapNumber()+1 result UINT8 sprite; ///< emblem sprite to use, 0 - 25 UINT16 color; ///< skincolor to use INT32 flags; ///< GE or ME constants @@ -229,5 +230,6 @@ cupheader_t *M_UnlockableCup(unlockable_t *unlock); UINT16 M_UnlockableMapNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); +UINT16 M_EmblemMapNum(emblem_t *emblem); #define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a]) From 42f4f28e06479a23b8f221f7f24a45662e7becad Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 17:53:35 +0000 Subject: [PATCH 72/95] Statistics menu Relatively quick port from v1., but with under-the-hood changes to match the Emblem/Medal and Extra Medal reworks. We can make it prettier (and contain more data) later. --- src/k_menu.h | 14 ++- src/k_menudef.c | 23 ++++- src/k_menudraw.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++ src/k_menufunc.c | 89 +++++++++++++++++++ src/m_cond.c | 23 ++--- src/m_cond.h | 2 +- 6 files changed, 362 insertions(+), 16 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index f992bb3b7..ac58a5e5c 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -405,8 +405,9 @@ extern menu_t MISC_ManualDef; extern menuitem_t MISC_Addons[]; extern menu_t MISC_AddonsDef; -extern menuitem_t MISC_Challenges[]; +extern menuitem_t MISC_ChallengesStatsDummyMenu[]; extern menu_t MISC_ChallengesDef; +extern menu_t MISC_StatisticsDef; // We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. typedef enum @@ -1124,6 +1125,17 @@ void M_DrawChallenges(void); void M_ChallengesTick(void); boolean M_ChallengesInputs(INT32 ch); +extern struct statisticsmenu_s { + INT32 location; + INT32 nummaps; + INT32 maxscroll; + UINT16 *maplist; +} statisticsmenu; + +void M_Statistics(INT32 choice); +void M_DrawStatistics(void); +boolean M_StatisticsInputs(INT32 ch); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(source, prev, x, y)\ {\ diff --git a/src/k_menudef.c b/src/k_menudef.c index 9cbbdf932..99903d788 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1503,7 +1503,7 @@ menuitem_t EXTRAS_Main[] = NULL, {.routine = M_ReplayHut}, 0, 0}, {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", - NULL, {NULL}, 0, 0}, + NULL, {.routine = M_Statistics}, 0, 0}, }; // the extras menu essentially reuses the options menu stuff @@ -1746,16 +1746,16 @@ menu_t MISC_AddonsDef = { }; // Challenges. -menuitem_t MISC_ChallengesMenu[] = +menuitem_t MISC_ChallengesStatsDummyMenu[] = { {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, }; menu_t MISC_ChallengesDef = { - sizeof (MISC_ChallengesMenu)/sizeof (menuitem_t), + sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t), &MainDef, 0, - MISC_ChallengesMenu, + MISC_ChallengesStatsDummyMenu, BASEVIDWIDTH/2, 32, 0, 0, 98, 0, @@ -1765,3 +1765,18 @@ menu_t MISC_ChallengesDef = { NULL, M_ChallengesInputs, }; + +menu_t MISC_StatisticsDef = { + sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t), + &MainDef, + 0, + MISC_ChallengesStatsDummyMenu, + 280, 185, + 0, 0, + 98, 0, + M_DrawStatistics, + NULL, + NULL, + NULL, + M_StatisticsInputs, +}; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 300b9543a..55c358648 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4461,6 +4461,8 @@ void M_DrawAddons(void) #undef addonsseperation +// Challenges Menu + #define challengesbordercolor 27 static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) @@ -4894,3 +4896,228 @@ challengedesc: V_DrawCenteredString(BASEVIDWIDTH/2, 120 + 32, V_ALLOWLOWERCASE, challengesmenu.unlockcondition); } } + +// Statistics menu + +#define STATSSTEP 10 + +static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y) +{ + UINT8 lasttype = UINT8_MAX, curtype; + emblem_t *emblem = M_GetLevelEmblems(mapnum); + + while (emblem) + { + switch (emblem->type) + { + case ET_TIME: + curtype = 1; + break; + case ET_GLOBAL: + { + if (emblem->flags & GE_NOTMEDAL) + { + emblem = M_GetLevelEmblems(-1); + continue; + } + curtype = 2; + break; + } + default: + curtype = 0; + break; + } + + // Shift over if emblem is of a different discipline + if (lasttype != UINT8_MAX && lasttype != curtype) + x -= 4; + lasttype = curtype; + + if (gamedata->collected[emblem-emblemlocations]) + V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); + else + V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + + emblem = M_GetLevelEmblems(-1); + x -= 8; + } +} + +static void M_DrawStatsMaps(void) +{ + INT32 y = 80, i = -1; + INT16 mnum; + boolean dotopname = true, dobottomarrow = (statisticsmenu.location < statisticsmenu.maxscroll); + INT32 location = statisticsmenu.location; + + if (location) + V_DrawCharacter(10, y-(skullAnimCounter/5), + '\x1A' | highlightflags, false); // up arrow + + while (statisticsmenu.maplist[++i] != NEXTMAP_INVALID) + { + if (location) + { + --location; + continue; + } + else if (dotopname) + { + V_DrawThinString(20, y, highlightflags, "LEVEL NAME"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, highlightflags, "MEDALS"); + y += STATSSTEP; + dotopname = false; + } + + mnum = statisticsmenu.maplist[i]+1; + M_DrawMapMedals(mnum, 291, y); + + { + char *title = G_BuildMapTitle(mnum); + V_DrawThinString(20, y, 0, title); + Z_Free(title); + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } + if (dotopname && !location) + { + V_DrawString(20, y, highlightflags, "LEVEL NAME"); + V_DrawString(256, y, highlightflags, "MEDALS"); + y += STATSSTEP; + } + else if (location) + --location; + + // Extra Emblem headers + for (i = 0; i < 2; ++i) + { + if (i == 1) + { + V_DrawThinString(20, y, highlightflags, "EXTRA MEDALS"); + if (location) + { + y += STATSSTEP; + location++; + } + } + if (location) + { + --location; + continue; + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } + + // Extra Emblems + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (unlockables[i].type != SECRET_EXTRAMEDAL) + { + continue; + } + + if (location) + { + --location; + continue; + } + + if (i >= 0) + { + if (gamedata->unlocked[i]) + { + UINT16 color = min(unlockables[i].color, numskincolors-1); + if (!color) + color = SKINCOLOR_GOLD; + V_DrawSmallMappedPatch(291, y+1, 0, W_CachePatchName("GOTITA", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE)); + } + else + { + V_DrawSmallScaledPatch(291, y+1, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + } + + V_DrawThinString(20, y, 0, va("%s", unlockables[i].name)); + } + + y += STATSSTEP; + + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + } +bottomarrow: + if (dobottomarrow) + V_DrawCharacter(10, y-STATSSTEP + (skullAnimCounter/5), + '\x1B' | highlightflags, false); // down arrow +} + +void M_DrawStatistics(void) +{ + char beststr[40]; + + tic_t besttime = 0; + + INT32 i; + INT32 mapsunfinished = 0; + + { + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + } + + V_DrawThinString(20, 22, V_ALLOWLOWERCASE|highlightflags, "Total Play Time:"); + V_DrawCenteredThinString(BASEVIDWIDTH/2, 32, 0, + va("%i hours, %i minutes, %i seconds", + G_TicsToHours(gamedata->totalplaytime), + G_TicsToMinutes(gamedata->totalplaytime, false), + G_TicsToSeconds(gamedata->totalplaytime))); + V_DrawThinString(20, 42, V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, 0, va("%i played", gamedata->matchesplayed)); + + if (!statisticsmenu.maplist) + { + V_DrawCenteredThinString(BASEVIDWIDTH/2, 62, V_ALLOWLOWERCASE, "No maps!?"); + return; + } + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))) + continue; + + if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0) + { + mapsunfinished++; + continue; + } + + besttime += mapheaderinfo[i]->mainrecord->time; + } + + V_DrawThinString(20, 60, V_ALLOWLOWERCASE, "Combined time records:"); + + sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 60, V_ALLOWLOWERCASE|(mapsunfinished ? V_REDMAP : 0), beststr); + + if (mapsunfinished) + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE|V_REDMAP, va("(%d unfinished)", mapsunfinished)); + else + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE, "(complete)"); + + V_DrawThinString(32, 70, 0, va("x %d/%d", M_CountMedals(false, false), M_CountMedals(true, false))); + V_DrawSmallMappedPatch(20, 70, 0, W_CachePatchName("GOTITA", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GOLD, GTC_MENUCACHE)); + + M_DrawStatsMaps(); +} + +#undef STATSSTEP diff --git a/src/k_menufunc.c b/src/k_menufunc.c index bcfc87bef..76fb8b738 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6853,6 +6853,8 @@ void M_Manual(INT32 choice) M_SetupNextMenu(&MISC_ManualDef, true); } +// Challenges menu + struct challengesmenu_s challengesmenu; menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) @@ -7385,3 +7387,90 @@ boolean M_ChallengesInputs(INT32 ch) return true; } + +// Statistics menu + +struct statisticsmenu_s statisticsmenu; + +void M_Statistics(INT32 choice) +{ + UINT16 i = 0; + + (void)choice; + + statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL); + statisticsmenu.nummaps = 0; + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + + if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) + continue; + + if (M_MapLocked(i+1)) + continue; + + statisticsmenu.maplist[statisticsmenu.nummaps++] = i; + } + statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID; + statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10; + statisticsmenu.location = 0; + + if (statisticsmenu.maxscroll < 0) + { + statisticsmenu.maxscroll = 0; + } + + MISC_StatisticsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_StatisticsDef, false); +} + +boolean M_StatisticsInputs(INT32 ch) +{ + const UINT8 pid = 0; + + (void)ch; + + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + + Z_Free(statisticsmenu.maplist); + statisticsmenu.maplist = NULL; + + return true; + } + + if (M_MenuExtraPressed(pid)) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud > 0) + { + if (statisticsmenu.location < statisticsmenu.maxscroll) + { + statisticsmenu.location++; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud < 0) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location--; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + + return true; +} diff --git a/src/m_cond.c b/src/m_cond.c index 9c0448ba6..052f23dc0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1123,17 +1123,20 @@ boolean M_MapLocked(INT32 mapnum) return false; } -INT32 M_CountMedals(boolean all) +INT32 M_CountMedals(boolean all, boolean extraonly) { INT32 found = 0, i; - for (i = 0; i < numemblems; ++i) + if (!extraonly) { - if ((emblemlocations[i].type == ET_GLOBAL) - && (emblemlocations[i].flags & GE_NOTMEDAL)) - continue; - if (!all && !gamedata->collected[i]) - continue; - found++; + for (i = 0; i < numemblems; ++i) + { + if ((emblemlocations[i].type == ET_GLOBAL) + && (emblemlocations[i].flags & GE_NOTMEDAL)) + continue; + if (!all && !gamedata->collected[i]) + continue; + found++; + } } for (i = 0; i < MAXUNLOCKABLES; ++i) { @@ -1404,8 +1407,8 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum) skincolornum_t M_GetEmblemColor(emblem_t *em) { - if (!em || em->color >= numskincolors) - return SKINCOLOR_NONE; + if (!em || !em->color || em->color >= numskincolors) + return SKINCOLOR_GOLD; return em->color; } diff --git a/src/m_cond.h b/src/m_cond.h index af4bd4fc8..1150b57ef 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -211,7 +211,7 @@ boolean M_CheckNetUnlockByID(UINT8 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_CupLocked(cupheader_t *cup); boolean M_MapLocked(INT32 mapnum); -INT32 M_CountMedals(boolean all); +INT32 M_CountMedals(boolean all, boolean extraonly); // Emblem shit emblem_t *M_GetLevelEmblems(INT32 mapnum); From e10df0ee68c28c88b903480013e7a6500d38f215 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 18:06:21 +0000 Subject: [PATCH 73/95] Gamedata minor version Matches Profiles, etc (including the funny "from the future" alert) Also updates major VERSIONCHECK, so when this starts getting tested amongst the devteam it'll be explicitly not cross-compatible --- src/g_game.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 1b090dd7a..df33f18ed 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4297,7 +4297,8 @@ void G_LoadGameSettings(void) Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL; } -#define GD_VERSIONCHECK 0xBA5ED444 // Change every major version, as usual +#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual +#define GD_VERSIONMINOR 0 // Change every format update // G_LoadGameData // Loads the main data file, which stores information such as emblems found, etc. @@ -4306,6 +4307,7 @@ void G_LoadGameData(void) size_t length; UINT32 i, j; UINT32 versionID; + UINT8 versionMinor; UINT8 rtemp; //For records @@ -4358,6 +4360,14 @@ void G_LoadGameData(void) I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } + versionMinor = READUINT8(save_p); + if (versionMinor > GD_VERSIONMINOR) + { + Z_Free(savebuffer); + save_p = NULL; + I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor); + } + gamedata->totalplaytime = READUINT32(save_p); gamedata->matchesplayed = READUINT32(save_p); @@ -4512,7 +4522,7 @@ void G_SaveGameData(void) return; } - length = (4+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4529,6 +4539,7 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save_p, GD_VERSIONCHECK); // 4 + WRITEUINT8(save_p, GD_VERSIONMINOR); // 1 WRITEUINT32(save_p, gamedata->totalplaytime); // 4 WRITEUINT32(save_p, gamedata->matchesplayed); // 4 WRITEUINT32(save_p, quickncasehash(timeattackfolder, 64)); From 88d41f6721100a7fa84247363fcefebcf187ce87 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 14 Dec 2022 20:18:53 +0000 Subject: [PATCH 74/95] SECRET_ALTTITLE My final indulgence for this branch. One cvar that swaps a patch --- src/d_netcmd.c | 15 +++++++++++++++ src/d_netcmd.h | 3 ++- src/deh_soc.c | 2 ++ src/f_finale.c | 9 ++++++++- src/k_menudraw.c | 7 +++++++ src/k_menufunc.c | 20 ++++++++++++++++++++ src/m_cond.h | 1 + 7 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5b76aa16d..6cf24aedb 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -466,6 +466,7 @@ consvar_t cv_autobalance = CVAR_INIT ("autobalance", "Off", CV_SAVE|CV_NETVAR|CV consvar_t cv_teamscramble = CVAR_INIT ("teamscramble", "Off", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange); consvar_t cv_scrambleonchange = CVAR_INIT ("scrambleonchange", "Off", CV_SAVE|CV_NETVAR, teamscramble_cons_t, NULL); +consvar_t cv_alttitle = CVAR_INIT ("alttitle", "Off", CV_CALL|CV_NOSHOWHELP|CV_NOINIT|CV_SAVE, CV_OnOff, AltTitle_OnChange); consvar_t cv_itemfinder = CVAR_INIT ("itemfinder", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, ItemFinder_OnChange); // Scoring type options @@ -938,6 +939,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_mindelay); // HUD + CV_RegisterVar(&cv_alttitle); CV_RegisterVar(&cv_itemfinder); CV_RegisterVar(&cv_showinputjoy); @@ -4843,6 +4845,19 @@ FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void) I_Quit(); } +void AltTitle_OnChange(void) +{ + if (!cv_alttitle.value) + return; // it's fine. + + if (!M_SecretUnlocked(SECRET_ALTTITLE, true)) + { + CONS_Printf(M_GetText("You haven't earned this yet.\n")); + CV_StealthSetValue(&cv_itemfinder, 0); + return; + } +} + void ItemFinder_OnChange(void) { if (!cv_itemfinder.value) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 3f4d00645..eda2d565c 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -123,7 +123,7 @@ extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_spbtest, cv_gptest, cv_reducevfx; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict; -extern consvar_t cv_itemfinder; +extern consvar_t cv_alttitle, cv_itemfinder; extern consvar_t cv_inttime, cv_advancemap; extern consvar_t cv_overtime; @@ -267,6 +267,7 @@ boolean IsPlayerAdmin(INT32 playernum); void SetAdminPlayer(INT32 playernum); void ClearAdminPlayers(void); void RemoveAdminPlayer(INT32 playernum); +void AltTitle_OnChange(void); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); diff --git a/src/deh_soc.c b/src/deh_soc.c index a7726200c..3d9bf9b42 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2269,6 +2269,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_BREAKTHECAPSULES; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; + else if (fastcmp(word2, "ALTTITLE")) + unlockables[num].type = SECRET_ALTTITLE; else if (fastcmp(word2, "ITEMFINDER")) unlockables[num].type = SECRET_ITEMFINDER; else diff --git a/src/f_finale.c b/src/f_finale.c index 022524105..e20deae1f 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1777,7 +1777,14 @@ static void F_CacheTitleScreen(void) break; // idk do we still want this? case TTMODE_RINGRACERS: - kts_bumper = W_CachePatchName("KTSBUMPR1", PU_PATCH_LOWPRIORITY); + if (!M_SecretUnlocked(SECRET_ALTTITLE, true)) + { + CV_StealthSetValue(&cv_alttitle, 0); + } + + kts_bumper = W_CachePatchName( + (cv_alttitle.value ? "KTSJUMPR1" : "KTSBUMPR1"), + PU_PATCH_LOWPRIORITY); kts_eggman = W_CachePatchName("KTSEGG01", PU_PATCH_LOWPRIORITY); kts_tails = W_CachePatchName("KTSTAL01", PU_PATCH_LOWPRIORITY); kts_tails_tails = W_CachePatchName("KTSTAL02", PU_PATCH_LOWPRIORITY); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 55c358648..a101f9516 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4707,6 +4707,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = hardmapcache; break; } + case SECRET_ALTTITLE: + { + x = 8; + y = BASEVIDHEIGHT-16; + V_DrawGamemodeString(x, y - 32, V_ALLOWLOWERCASE, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), cv_alttitle.string); + V_DrawThinString(x, y, V_ALLOWLOWERCASE|highlightflags, "Press (A) to toggle"); + } default: { break; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 76fb8b738..f5d483bb8 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -7382,6 +7382,26 @@ boolean M_ChallengesInputs(INT32 ch) } } } + + return true; + } + + if (M_MenuConfirmPressed(pid) + && challengesmenu.currentunlock < MAXUNLOCKABLES + && gamedata->unlocked[challengesmenu.currentunlock]) + { + switch (unlockables[challengesmenu.currentunlock].type) + { + case SECRET_ALTTITLE: + CV_AddValue(&cv_alttitle, 1); + S_StartSound(NULL, sfx_s3kc3s); + M_SetMenuDelay(pid); + break; + default: + break; + } + + return true; } } diff --git a/src/m_cond.h b/src/m_cond.h index 1150b57ef..5c50146f0 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -118,6 +118,7 @@ typedef enum SECRET_TIMEATTACK, // Permit Time attack SECRET_BREAKTHECAPSULES, // Permit SP Capsules SECRET_SOUNDTEST, // Permit Sound Test + SECRET_ALTTITLE, // Permit alternate titlescreen // Assist restrictions SECRET_ITEMFINDER, // Permit locating in-level secrets From eb15f3f4280708f4b6b2e405c1476ffb076293ec Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 15 Dec 2022 12:43:52 +0000 Subject: [PATCH 75/95] Forgot V_6WIDTHSPACE in thin strings for Statistics + SECRET_ALTTITLE preview --- src/k_menudraw.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a101f9516..bd9e660e1 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4712,7 +4712,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) x = 8; y = BASEVIDHEIGHT-16; V_DrawGamemodeString(x, y - 32, V_ALLOWLOWERCASE, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), cv_alttitle.string); - V_DrawThinString(x, y, V_ALLOWLOWERCASE|highlightflags, "Press (A) to toggle"); + V_DrawThinString(x, y, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Press (A)"); } default: { @@ -4971,8 +4971,8 @@ static void M_DrawStatsMaps(void) } else if (dotopname) { - V_DrawThinString(20, y, highlightflags, "LEVEL NAME"); - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, highlightflags, "MEDALS"); + V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, V_6WIDTHSPACE|highlightflags, "MEDALS"); y += STATSSTEP; dotopname = false; } @@ -4982,7 +4982,7 @@ static void M_DrawStatsMaps(void) { char *title = G_BuildMapTitle(mnum); - V_DrawThinString(20, y, 0, title); + V_DrawThinString(20, y, V_6WIDTHSPACE, title); Z_Free(title); } @@ -4993,8 +4993,8 @@ static void M_DrawStatsMaps(void) } if (dotopname && !location) { - V_DrawString(20, y, highlightflags, "LEVEL NAME"); - V_DrawString(256, y, highlightflags, "MEDALS"); + V_DrawString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME"); + V_DrawString(256, y, V_6WIDTHSPACE|highlightflags, "MEDALS"); y += STATSSTEP; } else if (location) @@ -5005,7 +5005,7 @@ static void M_DrawStatsMaps(void) { if (i == 1) { - V_DrawThinString(20, y, highlightflags, "EXTRA MEDALS"); + V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, "EXTRA MEDALS"); if (location) { y += STATSSTEP; @@ -5045,15 +5045,15 @@ static void M_DrawStatsMaps(void) UINT16 color = min(unlockables[i].color, numskincolors-1); if (!color) color = SKINCOLOR_GOLD; - V_DrawSmallMappedPatch(291, y+1, 0, W_CachePatchName("GOTITA", PU_CACHE), + V_DrawSmallMappedPatch(291, y+1, V_6WIDTHSPACE, W_CachePatchName("GOTITA", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE)); } else { - V_DrawSmallScaledPatch(291, y+1, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + V_DrawSmallScaledPatch(291, y+1, V_6WIDTHSPACE, W_CachePatchName("NEEDIT", PU_CACHE)); } - V_DrawThinString(20, y, 0, va("%s", unlockables[i].name)); + V_DrawThinString(20, y, V_6WIDTHSPACE, va("%s", unlockables[i].name)); } y += STATSSTEP; @@ -5081,18 +5081,18 @@ void M_DrawStatistics(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } - V_DrawThinString(20, 22, V_ALLOWLOWERCASE|highlightflags, "Total Play Time:"); - V_DrawCenteredThinString(BASEVIDWIDTH/2, 32, 0, + V_DrawThinString(20, 22, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Play Time:"); + V_DrawCenteredThinString(BASEVIDWIDTH/2, 32, V_6WIDTHSPACE, va("%i hours, %i minutes, %i seconds", G_TicsToHours(gamedata->totalplaytime), G_TicsToMinutes(gamedata->totalplaytime, false), G_TicsToSeconds(gamedata->totalplaytime))); - V_DrawThinString(20, 42, V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, 0, va("%i played", gamedata->matchesplayed)); + V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, va("%i played", gamedata->matchesplayed)); if (!statisticsmenu.maplist) { - V_DrawCenteredThinString(BASEVIDWIDTH/2, 62, V_ALLOWLOWERCASE, "No maps!?"); + V_DrawCenteredThinString(BASEVIDWIDTH/2, 62, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "No maps!?"); return; } @@ -5110,17 +5110,17 @@ void M_DrawStatistics(void) besttime += mapheaderinfo[i]->mainrecord->time; } - V_DrawThinString(20, 60, V_ALLOWLOWERCASE, "Combined time records:"); + V_DrawThinString(20, 60, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "Combined time records:"); sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 60, V_ALLOWLOWERCASE|(mapsunfinished ? V_REDMAP : 0), beststr); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 60, V_6WIDTHSPACE|V_ALLOWLOWERCASE|(mapsunfinished ? V_REDMAP : 0), beststr); if (mapsunfinished) - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE|V_REDMAP, va("(%d unfinished)", mapsunfinished)); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_REDMAP, va("(%d unfinished)", mapsunfinished)); else - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_ALLOWLOWERCASE, "(complete)"); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 70, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "(complete)"); - V_DrawThinString(32, 70, 0, va("x %d/%d", M_CountMedals(false, false), M_CountMedals(true, false))); + V_DrawThinString(32, 70, V_6WIDTHSPACE, va("x %d/%d", M_CountMedals(false, false), M_CountMedals(true, false))); V_DrawSmallMappedPatch(20, 70, 0, W_CachePatchName("GOTITA", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GOLD, GTC_MENUCACHE)); From 7181e05cce4b85947c8b13773dfa053122c4a35b Mon Sep 17 00:00:00 2001 From: VelocitOni Date: Fri, 16 Dec 2022 23:12:01 -0500 Subject: [PATCH 76/95] Update k_hud.h " trackingResult_t;" on line 31 turned into just ";" because structs use a new format now. --- src/k_hud.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_hud.h b/src/k_hud.h index 22aec1188..6360ca3de 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -28,7 +28,7 @@ struct trackingResult_t boolean onScreen; INT32 angle, pitch; fixed_t fov; -} trackingResult_t; +}; void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean reverse); From 32e53bbd83b24150503376199946dde7a5bdf732 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Dec 2022 22:23:30 +0000 Subject: [PATCH 77/95] Remove dynamic base from CMake target link options Permits functional debugging of crash reports by preventing ASLR --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61e5ff86f..0bcb05e22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,7 +134,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") # On MinGW with internal libraries, link the standard library statically - target_link_options(SRB2SDL2 PRIVATE "-static") + target_link_options(SRB2SDL2 PRIVATE "-static" "-Wl,--disable-dynamicbase") endif() target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17) From d3b8f295b8856358bf6c731990df222dcfdee311 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 18 Dec 2022 21:10:31 -0800 Subject: [PATCH 78/95] Fix not being able to play sounds from sector->soundorg --- src/p_mobj.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/p_mobj.h b/src/p_mobj.h index 8811c0149..0f163236b 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -275,14 +275,17 @@ struct mobj_t // List: thinker links. thinker_t thinker; - mobjtype_t type; - const mobjinfo_t *info; // &mobjinfo[mobj->type] - // Info for drawing: position. fixed_t x, y, z; + // --- Please make sure you keep the fields up to this + // --- point in sync with degenmobj_t. + fixed_t old_x, old_y, old_z; // position interpolation fixed_t old_x2, old_y2, old_z2; + mobjtype_t type; + const mobjinfo_t *info; // &mobjinfo[mobj->type] + // More list: links in sector (if needed) mobj_t *snext; mobj_t **sprev; // killough 8/11/98: change to ptr-to-ptr @@ -428,14 +431,14 @@ struct precipmobj_t // List: thinker links. thinker_t thinker; - mobjtype_t type; - const mobjinfo_t *info; // &mobjinfo[mobj->type] - // Info for drawing: position. fixed_t x, y, z; fixed_t old_x, old_y, old_z; // position interpolation fixed_t old_x2, old_y2, old_z2; + mobjtype_t type; + const mobjinfo_t *info; // &mobjinfo[mobj->type] + // More list: links in sector (if needed) precipmobj_t *snext; precipmobj_t **sprev; // killough 8/11/98: change to ptr-to-ptr From 021f82911243d8710a88afed3239ee8609259e70 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 19 Dec 2022 01:12:18 -0500 Subject: [PATCH 79/95] Update k_roulette.c --- src/k_roulette.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 40cd68af4..042b1a685 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -98,12 +98,14 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 + { 0, 0, 1, 2, 1, 0, 0, 0 }, // Jawz x2 + { 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3 }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = @@ -131,12 +133,14 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = { 0, 0 }, // Kitchen Sink { 2, 0 }, // Drop Target { 4, 0 }, // Garden Top + { 0, 0 }, // Gachabom { 0, 0 }, // Sneaker x2 { 0, 1 }, // Sneaker x3 { 0, 0 }, // Banana x3 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 - { 5, 1 } // Jawz x2 + { 5, 1 }, // Jawz x2 + { 0, 0 } // Gachabom x3 }; static kartitems_t K_KartItemReelTimeAttack[] = From 321c8bf9761bf5e38c0b34623ed7cffe0bb7faf0 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 19 Dec 2022 01:13:44 -0500 Subject: [PATCH 80/95] Update k_roulette.c --- src/k_roulette.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 042b1a685..dff3282e7 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -152,13 +152,14 @@ static kartitems_t K_KartItemReelTimeAttack[] = static kartitems_t K_KartItemReelBreakTheCapsules[] = { - KRITEM_TRIPLEORBINAUT, - KITEM_BANANA, + KITEM_GACHABOM, + KRITEM_TRIPLEGACHABOM, KITEM_NONE }; static kartitems_t K_KartItemReelBoss[] = { + // FIXME: gachabom...? KITEM_ORBINAUT, KITEM_BANANA, KITEM_NONE From fed08a28a1b298d35686a24689bfddd7dbc4b028 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 19 Dec 2022 01:50:14 -0500 Subject: [PATCH 81/95] Make Gachabom stay still when tossing forward --- src/p_mobj.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 43aa79294..900076abc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7058,8 +7058,40 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; - case MT_ORBINAUT: case MT_GACHABOM: + { + if (mobj->flags2 & MF2_AMBUSH) + { + mobj->friction = ORIG_FRICTION/4; + + if (mobj->momx || mobj->momy) + { + mobj_t *ghost = P_SpawnGhostMobj(mobj); + + if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player) + { + ghost->color = mobj->target->player->skincolor; + ghost->colorized = true; + } + } + + if (P_IsObjectOnGround(mobj)) + { + if (mobj->movecount > 1) + { + S_StartSound(mobj, mobj->info->activesound); + mobj->momx = mobj->momy = 0; + mobj->movecount = 1; + } + } + + if (mobj->threshold > 0) + mobj->threshold--; + break; + } + } + /* FALLTHRU */ + case MT_ORBINAUT: { Obj_OrbinautThink(mobj); P_MobjCheckWater(mobj); From a62282aa4e01ead14694769b81f50b4aa3bd6c73 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 19:43:31 +0000 Subject: [PATCH 82/95] Make `Wl,--disable-dynamicbase` its own link option seperate from `-static` --- src/CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0bcb05e22..8d646fe0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,9 +132,12 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_roulette.c ) -if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") - # On MinGW with internal libraries, link the standard library statically - target_link_options(SRB2SDL2 PRIVATE "-static" "-Wl,--disable-dynamicbase") +if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows") + target_link_options(SRB2SDL2 PRIVATE "-Wl,--disable-dynamicbase") + if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") + # On MinGW with internal libraries, link the standard library statically + target_link_options(SRB2SDL2 PRIVATE "-static") + endif() endif() target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17) From d106c0a3299c3db59d4d019f8112c47bb3da8280 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 19:52:53 +0000 Subject: [PATCH 83/95] Add TESTERS and HOSTTESTERS builds to CMake options --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61e5ff86f..a99bd2f41 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -482,6 +482,12 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DCMAKECONFIG) if(SRB2_CONFIG_DEBUGMODE) target_compile_definitions(SRB2SDL2 PRIVATE -DZDEBUG -DPARANOIA -DRANGECHECK -DPACKETDROP) endif() +if(SRB2_CONFIG_TESTERS) + target_compile_definitions(SRB2SDL2 PRIVATE -DTESTERS) +endif() +if(SRB2_CONFIG_HOSTTESTERS) + target_compile_definitions(SRB2SDL2 PRIVATE -DHOSTTESTERS) +endif() if(SRB2_CONFIG_MOBJCONSISTANCY) target_compile_definitions(SRB2SDL2 PRIVATE -DMOBJCONSISTANCY) endif() From d0cd059c5e9905b8a0aa540559a49d7efe5d2d7a Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 21:18:36 +0000 Subject: [PATCH 84/95] Rework the Level List functions to operate off a provided `levelsearch_t` pointer. * Prevents the dependency on a `levellist` global that could be corrupted * Fixes the issue where cups that were not unlocked would not show up on the cup select (because `M_MapLocked` would make `M_CanShowLevelInList` fail). * Shows both Race and Battle maps in the cup unlock preview. --- src/g_game.c | 20 ++++--- src/k_menu.h | 21 +++++--- src/k_menudraw.c | 52 ++++++++---------- src/k_menufunc.c | 136 +++++++++++++++++++++++++++-------------------- 4 files changed, 125 insertions(+), 104 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 08ae1d90c..fb49b598c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3404,23 +3404,27 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype) { UINT8 i = 0; INT16 mapnum = NEXTMAP_INVALID; - UINT32 tol = G_TOLFlag(pgametype); + levelsearch_t templevelsearch; - levellist.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); - levellist.timeattack = false; + templevelsearch.cup = NULL; + templevelsearch.typeoflevel = G_TOLFlag(pgametype); + templevelsearch.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); + templevelsearch.timeattack = false; + templevelsearch.checklocked = true; - if (levellist.cupmode) + if (templevelsearch.cupmode) { - cupheader_t *cup = kartcupheaders; - while (cup && mapnum >= nummapheaders) + templevelsearch.cup = kartcupheaders; + while (templevelsearch.cup && mapnum >= nummapheaders) { - mapnum = M_GetFirstLevelInList(&i, tol, cup); + mapnum = M_GetFirstLevelInList(&i, &templevelsearch); i = 0; + templevelsearch.cup = templevelsearch.cup->next; } } else { - mapnum = M_GetFirstLevelInList(&i, tol, NULL); + mapnum = M_GetFirstLevelInList(&i, &templevelsearch); } return mapnum; diff --git a/src/k_menu.h b/src/k_menu.h index 6578595e8..eb57069b7 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -695,23 +695,28 @@ extern struct cupgrid_s { boolean netgame; // Start the game in an actual server } cupgrid; +typedef struct levelsearch_s { + UINT32 typeoflevel; + cupheader_t *cup; + boolean timeattack; + boolean cupmode; + boolean checklocked; +} levelsearch_t; + extern struct levellist_s { SINT8 cursor; UINT16 y; UINT16 dest; - cupheader_t *selectedcup; INT16 choosemap; UINT8 newgametype; - UINT32 typeoflevel; - boolean cupmode; - boolean timeattack; // Setup time attack menu after picking + levelsearch_t levelsearch; boolean netgame; // Start the game in an actual server } levellist; -boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup); -UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup); -UINT16 M_GetFirstLevelInList(UINT8 *i, UINT32 tol, cupheader_t *cup); -UINT16 M_GetNextLevelInList(UINT16 map, UINT8 *i, UINT32 tol, cupheader_t *cup); +boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch); +UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch); +UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch); +UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch); void M_LevelSelectInit(INT32 choice); void M_CupSelectHandler(INT32 choice); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8bc19a3a4..9071907bc 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1922,24 +1922,24 @@ void M_DrawRaceDifficulty(void) // LEVEL SELECT -static void M_DrawCupPreview(INT16 y, cupheader_t *cup) +static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) { UINT8 i = 0; - INT16 maxlevels = M_CountLevelsToShowInList(levellist.typeoflevel, cup); + INT16 maxlevels = M_CountLevelsToShowInList(levelsearch); INT16 x = -(cupgrid.previewanim % 82); INT16 add; - INT16 map, start = M_GetFirstLevelInList(&i, levellist.typeoflevel, cup); + INT16 map, start = M_GetFirstLevelInList(&i, levelsearch); UINT8 starti = i; V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (cup && !M_CupLocked(cup)) + if (levelsearch->cup && !M_CupLocked(levelsearch->cup)) { add = (cupgrid.previewanim / 82) % maxlevels; map = start; while (add > 0) { - map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, cup); + map = M_GetNextLevelInList(map, &i, levelsearch); if (map >= nummapheaders) { @@ -1965,7 +1965,7 @@ static void M_DrawCupPreview(INT16 y, cupheader_t *cup) x += 82; - map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, cup); + map = M_GetNextLevelInList(map, &i, levelsearch); } } else @@ -2009,7 +2009,8 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) void M_DrawCupSelect(void) { UINT8 i, j; - cupheader_t *cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + levelsearch_t templevelsearch = levellist.levelsearch; // full copy + templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; for (i = 0; i < CUPMENU_COLUMNS; i++) { @@ -2057,8 +2058,8 @@ void M_DrawCupSelect(void) 0, W_CachePatchName("CUPCURS", PU_CACHE) ); - M_DrawCupPreview(146 + (24*menutransition.tics), cup); - M_DrawCupTitle(120 - (24*menutransition.tics), cup); + M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); + M_DrawCupTitle(120 - (24*menutransition.tics), templevelsearch.cup); } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) @@ -2190,7 +2191,7 @@ void M_DrawLevelSelect(void) { INT16 i = 0; UINT8 j = 0; - INT16 map = M_GetFirstLevelInList(&j, levellist.typeoflevel, levellist.selectedcup); + INT16 map = M_GetFirstLevelInList(&j, &levellist.levelsearch); INT16 t = (64*menutransition.tics), tay = 0; INT16 y = 80 - (12 * levellist.y); boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics); @@ -2221,10 +2222,10 @@ void M_DrawLevelSelect(void) y += 72; i++; - map = M_GetNextLevelInList(map, &j, levellist.typeoflevel, levellist.selectedcup); + map = M_GetNextLevelInList(map, &j, &levellist.levelsearch); } - M_DrawCupTitle(tay, levellist.selectedcup); + M_DrawCupTitle(tay, levellist.levelsearch.cup); } void M_DrawTimeAttack(void) @@ -4638,27 +4639,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } case SECRET_CUP: { - cupheader_t *cup = M_UnlockableCup(ref); -#if 0 // First attempt - UINT8 i = cup->numlevels; + levelsearch_t templevelsearch; - x = 4; - y = (BASEVIDHEIGHT-4) - 38; + templevelsearch.cup = M_UnlockableCup(ref); + templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE); + templevelsearch.cupmode = true; + templevelsearch.timeattack = false; + templevelsearch.checklocked = false; + + M_DrawCupPreview(146, &templevelsearch); - while (i > 0) - { - i--; - K_DrawMapThumbnail( - (x+(i*2))<cachedlevels[i], - NULL); - } -#else - M_DrawCupPreview(146, cup); -#endif break; } case SECRET_MAP: diff --git a/src/k_menufunc.c b/src/k_menufunc.c index a0a5ebd76..71a447a9e 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3386,8 +3386,11 @@ void M_SetupDifficultySelect(INT32 choice) // // Determines whether to show a given map in the various level-select lists. // -boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup) +boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) { + if (!levelsearch) + return false; + if (mapnum >= nummapheaders) return false; @@ -3403,11 +3406,11 @@ boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - if (M_MapLocked(mapnum+1)) + if (levelsearch->checklocked && M_MapLocked(mapnum+1)) return false; // not unlocked // Check for TOL - if (!(mapheaderinfo[mapnum]->typeoflevel & tol)) + if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; // Should the map be hidden? @@ -3415,26 +3418,31 @@ boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup) return false; // I don't know why, but some may have exceptions. - if (levellist.timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) + if (levelsearch->timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) return false; // Don't permit cup when no cup requested (also no dupes in time attack) - if (levellist.cupmode && (levellist.timeattack || !cup) && mapheaderinfo[mapnum]->cup != cup) + if (levelsearch->cupmode + && (levelsearch->timeattack || !levelsearch->cup) + && mapheaderinfo[mapnum]->cup != levelsearch->cup) return false; // Survived our checks. return true; } -UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup) +UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) { INT16 i, count = 0; - if (cup) + if (!levelsearch) + return false; + + if (levelsearch->cup) { for (i = 0; i < CUPCACHE_MAX; i++) { - if (!M_CanShowLevelInList(cup->cachedlevels[i], tol, cup)) + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) continue; count++; } @@ -3443,60 +3451,66 @@ UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup) } for (i = 0; i < nummapheaders; i++) - if (M_CanShowLevelInList(i, tol, NULL)) + if (M_CanShowLevelInList(i, levelsearch)) count++; return count; } -UINT16 M_GetFirstLevelInList(UINT8 *i, UINT32 tol, cupheader_t *cup) +UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) { INT16 mapnum = NEXTMAP_INVALID; - if (cup) + if (!levelsearch) + return false; + + if (levelsearch->cup) { *i = 0; mapnum = NEXTMAP_INVALID; for (; *i < CUPCACHE_MAX; (*i)++) { - if (!M_CanShowLevelInList(cup->cachedlevels[*i], tol, cup)) + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) continue; - mapnum = cup->cachedlevels[*i]; + mapnum = levelsearch->cup->cachedlevels[*i]; break; } } else { for (mapnum = 0; mapnum < nummapheaders; mapnum++) - if (M_CanShowLevelInList(mapnum, tol, NULL)) + if (M_CanShowLevelInList(mapnum, levelsearch)) break; } return mapnum; } -UINT16 M_GetNextLevelInList(UINT16 map, UINT8 *i, UINT32 tol, cupheader_t *cup) +UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) { - if (cup) + if (!levelsearch) + return false; + + if (levelsearch->cup) { - map = NEXTMAP_INVALID; + mapnum = NEXTMAP_INVALID; (*i)++; for (; *i < CUPCACHE_MAX; (*i)++) { - if (!M_CanShowLevelInList(cup->cachedlevels[*i], tol, cup)) + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) continue; - map = cup->cachedlevels[*i]; + mapnum = levelsearch->cup->cachedlevels[*i]; break; } } else { - map++; - while (!M_CanShowLevelInList(map, tol, NULL) && map < nummapheaders) - map++; + mapnum++; + while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders) + mapnum++; } - return map; + return mapnum; } struct cupgrid_s cupgrid; @@ -3504,7 +3518,7 @@ struct levellist_s levellist; static void M_LevelSelectScrollDest(void) { - UINT16 m = M_CountLevelsToShowInList(levellist.typeoflevel, levellist.selectedcup)-1; + UINT16 m = M_CountLevelsToShowInList(&levellist.levelsearch)-1; levellist.dest = (6*levellist.cursor); @@ -3522,9 +3536,9 @@ static void M_LevelListFromGametype(INT16 gt) if (first || gt != levellist.newgametype) { levellist.newgametype = gt; - levellist.typeoflevel = G_TOLFlag(gt); - levellist.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); - levellist.selectedcup = NULL; + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); + levellist.levelsearch.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); + levellist.levelsearch.cup = NULL; first = false; } @@ -3533,14 +3547,17 @@ static void M_LevelListFromGametype(INT16 gt) // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. - if (levellist.cupmode) + if (levellist.levelsearch.cupmode) { - cupheader_t *cup = kartcupheaders; + levelsearch_t templevelsearch = levellist.levelsearch; // full copy size_t currentid = 0, highestunlockedid = 0; const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + templevelsearch.cup = kartcupheaders; + templevelsearch.checklocked = false; + // Make sure there's valid cups before going to this menu. - if (cup == NULL) + if (templevelsearch.cup == NULL) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); if (!cupgrid.builtgrid) @@ -3558,12 +3575,12 @@ static void M_LevelListFromGametype(INT16 gt) } memset(cupgrid.builtgrid, 0, cupgrid.cappages * unitlen); - while (cup) + while (templevelsearch.cup) { - if (!M_CountLevelsToShowInList(levellist.typeoflevel, cup)) + if (!M_CountLevelsToShowInList(&templevelsearch)) { // No valid maps, skip. - cup = cup->next; + templevelsearch.cup = templevelsearch.cup->next; continue; } @@ -3584,21 +3601,21 @@ static void M_LevelListFromGametype(INT16 gt) cupgrid.cappages *= 2; } - cupgrid.builtgrid[currentid] = cup; + cupgrid.builtgrid[currentid] = templevelsearch.cup; - if (!M_CupLocked(cup)) + if (!M_CupLocked(templevelsearch.cup)) { highestunlockedid = currentid; - if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == cup) + if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) { - cupgrid.x = cup->id % CUPMENU_COLUMNS; - cupgrid.y = (cup->id / CUPMENU_COLUMNS) % CUPMENU_ROWS; - cupgrid.pageno = cup->id / (CUPMENU_COLUMNS * CUPMENU_ROWS); + cupgrid.x = currentid % CUPMENU_COLUMNS; + cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; + cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); } } currentid++; - cup = cup->next; + templevelsearch.cup = templevelsearch.cup->next; } cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; @@ -3614,10 +3631,10 @@ static void M_LevelListFromGametype(INT16 gt) } // Reset position properly if you go back & forth between gametypes - if (levellist.selectedcup) + if (levellist.levelsearch.cup) { levellist.cursor = 0; - levellist.selectedcup = NULL; + levellist.levelsearch.cup = NULL; } M_LevelSelectScrollDest(); @@ -3636,22 +3653,24 @@ void M_LevelSelectInit(INT32 choice) { (void)choice; - levellist.netgame = false; // Make sure this is reset as we'll only be using this function for offline games! - cupgrid.netgame = false; // Ditto + // Make sure this is reset as we'll only be using this function for offline games! + cupgrid.netgame = false; + levellist.netgame = false; + levellist.levelsearch.checklocked = true; switch (currentMenu->menuitems[itemOn].mvar1) { case 0: cupgrid.grandprix = false; - levellist.timeattack = false; + levellist.levelsearch.timeattack = false; break; case 1: cupgrid.grandprix = false; - levellist.timeattack = true; + levellist.levelsearch.timeattack = true; break; case 2: cupgrid.grandprix = true; - levellist.timeattack = false; + levellist.levelsearch.timeattack = false; break; default: CONS_Alert(CONS_WARNING, "Bad level select init\n"); @@ -3787,10 +3806,10 @@ void M_CupSelectHandler(INT32 choice) else { // Keep cursor position if you select the same cup again, reset if it's a different cup - if (levellist.selectedcup != newcup) + if (levellist.levelsearch.cup != newcup) { levellist.cursor = 0; - levellist.selectedcup = newcup; + levellist.levelsearch.cup = newcup; } M_LevelSelectScrollDest(); @@ -3818,7 +3837,7 @@ void M_CupSelectTick(void) void M_LevelSelectHandler(INT32 choice) { - INT16 maxlevels = M_CountLevelsToShowInList(levellist.typeoflevel, levellist.selectedcup); + INT16 maxlevels = M_CountLevelsToShowInList(&levellist.levelsearch); const UINT8 pid = 0; (void)choice; @@ -3850,14 +3869,14 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { UINT8 i = 0; - INT16 map = M_GetFirstLevelInList(&i, levellist.typeoflevel, levellist.selectedcup); + INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); INT16 add = levellist.cursor; M_SetMenuDelay(pid); while (add > 0) { - map = M_GetNextLevelInList(map, &i, levellist.typeoflevel, levellist.selectedcup); + map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); if (map >= nummapheaders) { @@ -3875,7 +3894,7 @@ void M_LevelSelectHandler(INT32 choice) levellist.choosemap = map; - if (levellist.timeattack) + if (levellist.levelsearch.timeattack) { M_SetupNextMenu(&PLAY_TimeAttackDef, false); S_StartSound(NULL, sfx_s3k63); @@ -4106,10 +4125,13 @@ void M_MPSetupNetgameMapSelect(INT32 choice) INT16 gt = GT_RACE; (void)choice; - levellist.netgame = true; // Yep, we'll be starting a netgame. - cupgrid.netgame = true; // Ditto - levellist.timeattack = false; // Make sure we reset those - cupgrid.grandprix = false; // Ditto + // Yep, we'll be starting a netgame. + levellist.netgame = true; + cupgrid.netgame = true; + // Make sure we reset those + levellist.levelsearch.timeattack = false; + levellist.levelsearch.checklocked = true; + cupgrid.grandprix = false; // In case we ever want to add new gamemodes there somehow, have at it! switch (cv_dummygametype.value) From 8edf099f78b03cf7d5c0d1684c6336f52e4bc1d8 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 21:26:52 +0000 Subject: [PATCH 85/95] Sal's review: Clear levels on gamedata addition by default Instead, if you want an exception - in the *same* MainCfg block, set `doClearLevels = False` (or `0`, or `No`) --- src/deh_soc.c | 11 +++++++++++ src/dehacked.c | 15 --------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 5e294c152..1d7b94214 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2534,6 +2534,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) char *word2; char *tmp; INT32 value; + boolean doClearLevels = false; do { @@ -2604,11 +2605,16 @@ void readmaincfg(MYFILE *f, boolean mainfile) clear_conditionsets(); clear_emblems(); //clear_levels(); + doClearLevels = true; } else if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); } + else if (fastcmp(word, "CLEARLEVELS")) + { + doClearLevels = (UINT8)(value == 0 || word2[0] == 'F' || word2[0] == 'N'); + } else if (fastcmp(word, "EXECCFG")) { if (strchr(word2, '.')) @@ -2818,6 +2824,11 @@ void readmaincfg(MYFILE *f, boolean mainfile) } } while (!myfeof(f)); + if (doClearLevels) + { + clear_levels(); + } + Z_Free(s); } diff --git a/src/dehacked.c b/src/dehacked.c index 037bd548e..7a916c950 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -542,21 +542,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { deh_warning("Patch is only compatible with base SRB2."); } - // Clear all data in certain locations (mostly for unlocks) - // Unless you REALLY want to piss people off, - // define a custom gamedata /before/ doing this!! - // (then again, modifiedgame will prevent game data saving anyway) - else if (fastcmp(word, "CLEAR")) - { - if (!mainfile && !gamedataadded) - { - deh_warning("You must define a custom gamedata to use \"%s\"", word); - continue; - } - - if (fastcmp(word2, "LEVELS")) - clear_levels(); - } else deh_warning("Unknown word: %s", word); } From 0214f62057635e0cff98c47fc8e1664ac7713243 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 21:47:37 +0000 Subject: [PATCH 86/95] Add descriptions for TESTERS and HOSTTESTERS builds. --- src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0841f3b03..93176024d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,10 @@ set(SRB2_CONFIG_YASM OFF CACHE BOOL "Use YASM in place of NASM.") set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL "Compile a development build of SRB2Kart.") +set(SRB2_CONFIG_TESTERS OFF CACHE BOOL + "Compile a build for testers.") +set(SRB2_CONFIG_HOSTTESTERS OFF CACHE BOOL + "Compile a build to host netgames for testers builds.") add_subdirectory(blua) From c89fb1539953db1e17e42bc58d77340ddf5db82c Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 19 Dec 2022 21:48:19 +0000 Subject: [PATCH 87/95] Use our updated name where appropriate. (text strings only) --- src/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93176024d..318c9cb7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -152,7 +152,7 @@ set(SRB2_CONFIG_USEASM OFF CACHE BOOL set(SRB2_CONFIG_YASM OFF CACHE BOOL "Use YASM in place of NASM.") set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL - "Compile a development build of SRB2Kart.") + "Compile a development build of Dr Robotnik's Ring Racers.") set(SRB2_CONFIG_TESTERS OFF CACHE BOOL "Compile a build for testers.") set(SRB2_CONFIG_HOSTTESTERS OFF CACHE BOOL @@ -240,7 +240,7 @@ if(${SRB2_CONFIG_HAVE_ZLIB}) set(SRB2_HAVE_ZLIB ON) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB) else() - message(WARNING "You have specified that ZLIB is available but it was not found. SRB2Kart may not compile correctly.") + message(WARNING "You have specified that ZLIB is available but it was not found. Dr Robotnik's Ring Racers may not compile correctly.") endif() endif() @@ -263,7 +263,7 @@ if(${SRB2_CONFIG_HAVE_PNG} AND ${SRB2_CONFIG_HAVE_ZLIB}) target_compile_definitions(SRB2SDL2 PRIVATE -D_LARGEFILE64_SOURCE) target_sources(SRB2SDL2 PRIVATE apng.c) else() - message(WARNING "You have specified that PNG is available but it was not found. SRB2Kart may not compile correctly.") + message(WARNING "You have specified that PNG is available but it was not found. Dr Robotnik's Ring Racers may not compile correctly.") endif() endif() endif() From b73bbd3712cdda0b89190c4d73e3db20d6beef90 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 19 Dec 2022 15:25:15 -0800 Subject: [PATCH 88/95] Add invulnhitlag, timeshit and timeshitprev fields to player_t --- src/d_player.h | 8 +++++++- src/lua_playerlib.c | 12 ++++++++++++ src/p_saveg.c | 8 ++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index af6e75517..265bd13cb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -466,6 +466,7 @@ struct player_t UINT16 spinouttimer; // Spin-out from a banana peel or oil slick (was "pw_bananacam") UINT8 spinouttype; // Determines the mode of spinout/wipeout, see kartspinoutflags_t UINT8 instashield; // Instashield no-damage animation timer + INT32 invulnhitlag; // Numbers of tics of hitlag added this tic for "potential" damage -- not real damage UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out UINT8 justbumped; // Prevent players from endlessly bumping into each other UINT8 tumbleBounces; @@ -615,7 +616,12 @@ struct player_t INT16 lastsidehit, lastlinehit; - //UINT8 timeshit; // That's TIMES HIT, not TIME SHIT, you doofus! -- in memoriam + // These track how many things tried to damage you, not + // whether you actually took damage. + UINT8 timeshit; // times hit this tic + UINT8 timeshitprev; // times hit before + // That's TIMES HIT, not TIME SHIT, you doofus! -- in memoriam + // No longer in memoriam =P -jart INT32 onconveyor; // You are on a conveyor belt if nonzero diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 3bc7e1584..7195c90f0 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -232,6 +232,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->spinouttimer); else if (fastcmp(field,"instashield")) lua_pushinteger(L, plr->instashield); + else if (fastcmp(field,"invulnhitlag")) + lua_pushinteger(L, plr->invulnhitlag); else if (fastcmp(field,"wipeoutslow")) lua_pushinteger(L, plr->wipeoutslow); else if (fastcmp(field,"justbumped")) @@ -472,6 +474,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->lastsidehit); else if (fastcmp(field,"lastlinehit")) lua_pushinteger(L, plr->lastlinehit); + else if (fastcmp(field,"timeshit")) + lua_pushinteger(L, plr->timeshit); + else if (fastcmp(field,"timeshitprev")) + lua_pushinteger(L, plr->timeshitprev); else if (fastcmp(field,"onconveyor")) lua_pushinteger(L, plr->onconveyor); else if (fastcmp(field,"awayviewmobj")) @@ -604,6 +610,8 @@ static int player_set(lua_State *L) plr->spinouttimer = luaL_checkinteger(L, 3); else if (fastcmp(field,"instashield")) plr->instashield = luaL_checkinteger(L, 3); + else if (fastcmp(field,"invulnhitlag")) + plr->invulnhitlag = luaL_checkinteger(L, 3); else if (fastcmp(field,"wipeoutslow")) plr->wipeoutslow = luaL_checkinteger(L, 3); else if (fastcmp(field,"justbumped")) @@ -830,6 +838,10 @@ static int player_set(lua_State *L) plr->lastsidehit = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"lastlinehit")) plr->lastlinehit = (INT16)luaL_checkinteger(L, 3); + else if (fastcmp(field,"timeshit")) + plr->timeshit = (UINT8)luaL_checkinteger(L, 3); + else if (fastcmp(field,"timeshitprev")) + plr->timeshitprev = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"onconveyor")) plr->onconveyor = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"awayviewmobj")) diff --git a/src/p_saveg.c b/src/p_saveg.c index f36d94319..b7d3802ff 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -190,6 +190,9 @@ static void P_NetArchivePlayers(void) WRITEINT32(save_p, players[i].onconveyor); + WRITEUINT8(save_p, players[i].timeshit); + WRITEUINT8(save_p, players[i].timeshitprev); + WRITEUINT32(save_p, players[i].jointime); WRITEUINT8(save_p, players[i].splitscreenindex); @@ -266,6 +269,7 @@ static void P_NetArchivePlayers(void) WRITEUINT16(save_p, players[i].spinouttimer); WRITEUINT8(save_p, players[i].spinouttype); WRITEUINT8(save_p, players[i].instashield); + WRITEINT32(save_p, players[i].invulnhitlag); WRITEUINT8(save_p, players[i].wipeoutslow); WRITEUINT8(save_p, players[i].justbumped); WRITEUINT8(save_p, players[i].tumbleBounces); @@ -557,6 +561,9 @@ static void P_NetUnArchivePlayers(void) players[i].lastsidehit = READINT16(save_p); players[i].lastlinehit = READINT16(save_p); + players[i].timeshit = READUINT8(save_p); + players[i].timeshitprev = READUINT8(save_p); + players[i].onconveyor = READINT32(save_p); players[i].jointime = READUINT32(save_p); @@ -615,6 +622,7 @@ static void P_NetUnArchivePlayers(void) players[i].spinouttimer = READUINT16(save_p); players[i].spinouttype = READUINT8(save_p); players[i].instashield = READUINT8(save_p); + players[i].invulnhitlag = READINT32(save_p); players[i].wipeoutslow = READUINT8(save_p); players[i].justbumped = READUINT8(save_p); players[i].tumbleBounces = READUINT8(save_p); From ba2a7744d170cfb3257d0f4c4a208921ae996ff3 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 16 Dec 2022 23:49:05 -0800 Subject: [PATCH 89/95] Add half hitlag to invincible players if they would've been damaged --- src/k_collide.c | 1 + src/k_kart.c | 26 ++++++++++++++++++++++++++ src/p_inter.c | 21 ++++++++++++++++++++- src/p_mobj.c | 3 ++- src/p_mobj.h | 3 ++- src/p_user.c | 6 ++++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/k_collide.c b/src/k_collide.c index 8ac2f68bf..f85b3a236 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -403,6 +403,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) { // Melt item S_StartSound(t2, sfx_s3k43); + K_SetHitLagForObjects(t2, t1, 3, false); } else { diff --git a/src/k_kart.c b/src/k_kart.c index 429c70762..968147a06 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7983,6 +7983,32 @@ void K_KartPlayerAfterThink(player_t *player) { K_LookForRings(player->mo); } + + if (player->invulnhitlag > 0) + { + // Hitlag from what would normally be damage but the + // player was invulnerable. + // + // If we're constantly getting hit the same number of + // times, we're probably standing on a damage floor. + // + // Checking if we're hit more than before ensures + // that: + // + // 1) repeating damage doesn't count + // 2) new damage sources still count + + if (player->timeshit <= player->timeshitprev) + { + if (!P_MobjWasRemoved(player->mo)) + { + player->mo->hitlag -= player->invulnhitlag; + player->mo->eflags &= ~(MFE_DAMAGEHITLAG); + } + } + + player->invulnhitlag = 0; + } } /*-------------------------------------------------- diff --git a/src/p_inter.c b/src/p_inter.c index 7909c3116..609a77610 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2015,7 +2015,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (!(target->flags & MF_SHOOTABLE)) return false; // shouldn't happen... - if (!(damagetype & DMG_DEATHMASK) && target->hitlag > 0 && inflictor == NULL) + if (!(damagetype & DMG_DEATHMASK) && (target->eflags & MFE_PAUSED)) return false; } @@ -2038,6 +2038,18 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (player) // Player is the target { + { + const INT32 oldtimeshit = player->timeshit; + + player->timeshit++; + + // overflow prevention + if (player->timeshit < oldtimeshit) + { + player->timeshit = oldtimeshit; + } + } + if (player->pflags & PF_GODMODE) return false; @@ -2092,6 +2104,13 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (player->invincibilitytimer > 0 || K_IsBigger(target, inflictor) == true || player->hyudorotimer > 0) { + const INT32 oldhitlag = target->hitlag; + + laglength = max(laglength / 2, 1); + K_SetHitLagForObjects(target, inflictor, laglength, false); + + player->invulnhitlag += (target->hitlag - oldhitlag); + // Full invulnerability K_DoInstashield(player); return false; diff --git a/src/p_mobj.c b/src/p_mobj.c index b2c4cdb3b..f0bc4927b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9665,6 +9665,7 @@ void P_MobjThinker(mobj_t *mobj) // Don't run any thinker code while in hitlag if (mobj->hitlag > 0) { + mobj->eflags |= MFE_PAUSED; mobj->hitlag--; if (mobj->type == MT_DROPTARGET && mobj->reactiontime > 0 && mobj->hitlag == 2) @@ -9684,7 +9685,7 @@ void P_MobjThinker(mobj_t *mobj) return; } - mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED); + mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED|MFE_PAUSED); // sal: what the hell? is there any reason this isn't done, like, literally ANYWHERE else? P_SetTarget(&tm.floorthing, NULL); diff --git a/src/p_mobj.h b/src/p_mobj.h index 0f163236b..8e2686900 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -255,7 +255,8 @@ typedef enum MFE_DAMAGEHITLAG = 1<<13, // Slope physics sent you airborne MFE_SLOPELAUNCHED = 1<<14, - // free: to and including 1<<15 + // Thinker is paused due to hitlag + MFE_PAUSED = 1<<15, } mobjeflag_t; // diff --git a/src/p_user.c b/src/p_user.c index 57f4b2af9..ac96ea345 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4341,6 +4341,12 @@ void P_PlayerAfterThink(player_t *player) // so a lag value of 1 is exactly attached to the player. K_HandleFollower(player); + if (P_MobjWasRemoved(player->mo) || (player->mo->eflags & MFE_PAUSED) == 0) + { + player->timeshitprev = player->timeshit; + player->timeshit = 0; + } + if (K_PlayerUsesBotMovement(player)) { From 9ddf10c9a59affbd5464954cf27ff4e999be3614 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 16 Dec 2022 23:49:59 -0800 Subject: [PATCH 90/95] Broly: detect added hitlag even if damage call didn't deal real damage --- src/k_collide.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/k_collide.c b/src/k_collide.c index f85b3a236..06d6900e6 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -204,7 +204,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) static mobj_t *grenade; static fixed_t explodedist; static boolean explodespin; -static tic_t minehitlag; +static INT32 minehitlag; static inline boolean PIT_SSMineChecks(mobj_t *thing) { @@ -272,6 +272,9 @@ void K_DoMineSearch(mobj_t *actor, fixed_t size) static inline BlockItReturn_t PIT_SSMineExplode(mobj_t *thing) { + const INT32 oldhitlag = thing->hitlag; + INT32 lagadded; + if (grenade == NULL || P_MobjWasRemoved(grenade)) return BMIT_ABORT; // There's the possibility these can chain react onto themselves after they've already died if there are enough all in one spot @@ -283,9 +286,13 @@ static inline BlockItReturn_t PIT_SSMineExplode(mobj_t *thing) if (PIT_SSMineChecks(thing) == true) return BMIT_CONTINUE; - if (P_DamageMobj(thing, grenade, grenade->target, 1, (explodespin ? DMG_NORMAL : DMG_EXPLODE))) + P_DamageMobj(thing, grenade, grenade->target, 1, (explodespin ? DMG_NORMAL : DMG_EXPLODE)); + + lagadded = (thing->hitlag - oldhitlag); + + if (lagadded > 0) { - minehitlag = thing->hitlag; + minehitlag = lagadded; } return BMIT_CONTINUE; @@ -392,6 +399,8 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if (t2->player) { + const INT32 oldhitlag = t2->hitlag; + if (t2->player->flashing) return true; @@ -411,7 +420,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) P_DamageMobj(t2, t1, t1->target, 1, DMG_TUMBLE); } - t1->reactiontime = t2->hitlag; + t1->reactiontime = (t2->hitlag - oldhitlag); P_KillMobj(t1, t2, t2, DMG_NORMAL); } else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD From d496e0f298d2ca60927b35a083adf2aadee68eaa Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 19 Dec 2022 15:42:44 -0800 Subject: [PATCH 91/95] Add Tyron's invinc/grow blocked hit sfx --- src/p_inter.c | 25 ++++++++++++++++++++++++- src/sounds.c | 4 ++++ src/sounds.h | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index 609a77610..09f098e4b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2083,6 +2083,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da // If not, then spawn the instashield effect instead. if (!force) { + boolean invincible = true; + sfxenum_t sfx = sfx_None; + if (gametyperules & GTR_BUMPERS) { if (player->bumpers <= 0 && player->karmadelay) @@ -2102,7 +2105,22 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } } - if (player->invincibilitytimer > 0 || K_IsBigger(target, inflictor) == true || player->hyudorotimer > 0) + if (player->invincibilitytimer > 0) + { + sfx= sfx_invind; + } + else if (K_IsBigger(target, inflictor) == true) + { + sfx = sfx_grownd; + } + else if (player->hyudorotimer > 0) + ; + else + { + invincible = false; + } + + if (invincible) { const INT32 oldhitlag = target->hitlag; @@ -2111,6 +2129,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->invulnhitlag += (target->hitlag - oldhitlag); + if (player->timeshit > player->timeshitprev) + { + S_StartSound(target, sfx); + } + // Full invulnerability K_DoInstashield(player); return false; diff --git a/src/sounds.c b/src/sounds.c index 016cf5029..b18670667 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1148,6 +1148,10 @@ sfxinfo_t S_sfx[NUMSFX] = {"pass15", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"pass16", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SRB2Kart - Blocked damage + {"grownd", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND + {"invind", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND + // SRB2Kart - Engine sounds // Engine class A {"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 01dfbfc2b..51aecb94a 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1212,6 +1212,10 @@ typedef enum sfx_pass15, sfx_pass16, + // Blocked damage SFX + sfx_grownd, + sfx_invind, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, From 0d8e13e94208ff614c5de160b8f217774cd6c9ef Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 22 Dec 2022 01:35:08 -0500 Subject: [PATCH 92/95] Fix spring stack overflow Closes #377 --- src/p_map.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/p_map.c b/src/p_map.c index 254706ce6..50f0fb991 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -277,6 +277,12 @@ P_DoSpringEx angle_t finalAngle, UINT16 starcolor) { + if (object->eflags & MFE_SPRUNG) + { + // Object was already sprung this tic + return; + } + if (horizspeed < 0) { horizspeed = -(horizspeed); @@ -402,7 +408,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) return false; } - spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify + spring->flags |= MF_NOCLIPTHING; // De-solidify if (spring->eflags & MFE_VERTICALFLIP) vertispeed *= -1; @@ -445,7 +451,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) spring->angle, starcolor); // Re-solidify - spring->flags |= (spring->info->flags & (MF_SPRING|MF_SPECIAL)); + spring->flags = (spring->flags & ~(MF_NOCLIPTHING)) | (spring->info->flags & (MF_NOCLIPTHING)); if (object->player) { From a29d43d3b5a4453b0cd012ffba2e9b2ec727a3b8 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 21 Dec 2022 22:45:42 -0800 Subject: [PATCH 93/95] Restore save_p after P_LoadLevel P_LoadLevel calls some functions that save gamedata and unset save_p. --- src/p_saveg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/p_saveg.c b/src/p_saveg.c index f36d94319..7fb110e49 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4867,6 +4867,7 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) { size_t i, j; size_t numTasks; + UINT8 *old_save_p; if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) I_Error("Bad $$$.sav at archive block Misc"); @@ -4909,12 +4910,17 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) encoremode = (boolean)READUINT8(save_p); + // FIXME: save_p should not be global!!! + old_save_p = save_p; + if (!P_LoadLevel(true, reloading)) { CONS_Alert(CONS_ERROR, M_GetText("Can't load the level!\n")); return false; } + save_p = old_save_p; + // get the time leveltime = READUINT32(save_p); lastmap = READINT16(save_p); From 9c9d2934c58a287f5d9e8d1499b7d70ee6850f3d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 22 Dec 2022 01:53:50 -0500 Subject: [PATCH 94/95] Make ring respawn a division Lets it stay very long for 1v1 like we want, but not matter the closer it gets to 8 players. Tired of people spamming rings to compensate for this problem :V --- src/p_mobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index b2c4cdb3b..5d268a931 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11461,7 +11461,7 @@ void P_RespawnSpecials(void) } else if (pcount > 1) { - time = (120 - ((pcount-2) * 20)) * TICRATE; + time = (120 * TICRATE) / (pcount - 1); // If the map is longer or shorter than 3 laps, then adjust ring respawn to account for this. // 5 lap courses would have more retreaded ground, while 2 lap courses would have less. From 90903e999fb801d0021764de593a08cc72a09428 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 21 Dec 2022 23:17:49 -0800 Subject: [PATCH 95/95] Fix MAXAVAILABILITY loop --- src/p_saveg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 7fb110e49..e9b5b237e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -521,7 +521,7 @@ static void P_NetUnArchivePlayers(void) players[i].skincolor = READUINT8(save_p); players[i].skin = READINT32(save_p); - for (j = 0; i < MAXAVAILABILITY; j++) + for (j = 0; j < MAXAVAILABILITY; j++) { players[i].availabilities[j] = READUINT8(save_p); }