From 102ada37ca543eb55e6f453f19b5fb68a03b8111 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Feb 2023 13:39:47 +0000 Subject: [PATCH 001/103] Unlock system: Imprement SECRET_MASTERMODE - Locks Master difficulty in GP difficulty select. - Remove SECRET_LEGACYBOXRUMMAGE at the same time, because that plan is long dead. --- src/command.c | 5 +++++ src/deh_soc.c | 4 ++-- src/k_menudraw.c | 25 +++++++++++++++++++++---- src/m_cond.h | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/command.c b/src/command.c index 651540b5f..24d58a28c 100644 --- a/src/command.c +++ b/src/command.c @@ -2241,6 +2241,11 @@ void CV_AddValue(consvar_t *var, INT32 increment) if (var->PossibleValue == kartspeed_cons_t) max++; // Accommodate KARTSPEED_AUTO } + else if (var->PossibleValue == gpdifficulty_cons_t + && !M_SecretUnlocked(SECRET_MASTERMODE, false)) + { + max = KARTSPEED_HARD+1; + } } #ifdef PARANOIA if (currentindice == -1) diff --git a/src/deh_soc.c b/src/deh_soc.c index 5da9a4173..1cf6f5d57 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2280,10 +2280,10 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_FOLLOWER; else if (fastcmp(word2, "HARDSPEED")) unlockables[num].type = SECRET_HARDSPEED; + else if (fastcmp(word2, "MASTERMODE")) + unlockables[num].type = SECRET_MASTERMODE; 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")) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 735f82134..5ad49a066 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4711,8 +4711,8 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili case SECRET_CUP: categoryid = '4'; break; - //case SECRET_MASTERBOTS: case SECRET_HARDSPEED: + case SECRET_MASTERMODE: case SECRET_ENCORE: categoryid = '5'; break; @@ -4773,12 +4773,12 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili break; } - /*case SECRET_MASTERBOTS: - iconid = 4; - break;*/ case SECRET_HARDSPEED: iconid = 3; break; + case SECRET_MASTERMODE: + iconid = 4; + break; case SECRET_ENCORE: iconid = 5; break; @@ -5026,6 +5026,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = hardmapcache; break; } + case SECRET_MASTERMODE: + { + static UINT16 mastermapcache = NEXTMAP_INVALID; + if (mastermapcache > nummapheaders) + { + mastermapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL); + } + specialmap = mastermapcache; + break; + } case SECRET_ALTTITLE: { x = 8; @@ -5066,6 +5076,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) W_CachePatchName(K_GetItemPatch(KITEM_ROCKETSNEAKER, false), PU_CACHE), NULL); } + else if (ref->type == SECRET_MASTERMODE) + { + V_DrawFixedPatch((x+40-25)< Date: Mon, 27 Feb 2023 14:02:54 +0000 Subject: [PATCH 002/103] Fixes for -skill parameter on game boot - Forbid Hard Speed and Master Mode when not unlocked - Don't set invalid game speed if provided skill is invalid --- src/d_main.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 7cbddf194..2f75bb5a9 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1828,23 +1828,32 @@ void D_SRB2Main(void) newskill = (INT16)j; } - if (grandprixinfo.gp == true) + // Invalidate if locked. + if ((newskill >= KARTSPEED_HARD && !M_SecretUnlocked(SECRET_HARDSPEED, true)) + || (newskill >= KARTGP_MASTER && !M_SecretUnlocked(SECRET_MASTERMODE, true))) { - if (newskill == KARTGP_MASTER) - { - grandprixinfo.masterbots = true; - newskill = KARTSPEED_HARD; - } - - grandprixinfo.gamespeed = newskill; - } - else if (newskill == KARTGP_MASTER) - { - newskill = KARTSPEED_HARD; + newskill = -1; } if (newskill != -1) + { + if (grandprixinfo.gp == true) + { + if (newskill == KARTGP_MASTER) + { + grandprixinfo.masterbots = true; + newskill = KARTSPEED_HARD; + } + + grandprixinfo.gamespeed = newskill; + } + else if (newskill == KARTGP_MASTER) + { + newskill = KARTSPEED_HARD; + } + CV_SetValue(&cv_kartspeed, newskill); + } } if (server && (dedicated || !M_CheckParm("+map"))) From 6d43d8ef09d226fef49d26d68da9214a4aa4d638 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Feb 2023 17:01:15 +0000 Subject: [PATCH 003/103] Add SECRET_ONLINE. - Online menu is inaccessible until unlocked. - Unlike most unlocks, the fact that it's not enabled is very clearly signposted. - This is because the previous entry in the series barely had any offline content at all, so the fact you'll have to work for it will catch a lot of people by surprise. - Has a message that straight up tells you you need experience in Grand Prix mode. - Has no affect in TESTERS builds, for which this is the only method of play available. --- src/deh_soc.c | 2 ++ src/k_menu.h | 1 + src/k_menudraw.c | 28 ++++++++++++++++++++++++++-- src/k_menufunc.c | 1 + src/m_cond.h | 3 +++ src/menus/play-1.c | 28 +++++++++++++++++++++++++++- src/menus/play-char-select.c | 3 ++- src/menus/play-online-1.c | 11 +++++++++++ 8 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 1cf6f5d57..a4bd8dc77 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2290,6 +2290,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_BREAKTHECAPSULES; else if (fastcmp(word2, "SPECIALATTACK")) unlockables[num].type = SECRET_SPECIALATTACK; + else if (fastcmp(word2, "ONLINE")) + unlockables[num].type = SECRET_ONLINE; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; else if (fastcmp(word2, "ALTTITLE")) diff --git a/src/k_menu.h b/src/k_menu.h index 3adbf25de..9145027ae 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -704,6 +704,7 @@ boolean M_CharacterSelectHandler(INT32 choice); void M_CharacterSelectTick(void); boolean M_CharacterSelectQuit(void); +void M_SetupPlayMenu(INT32 choice); void M_SetupGametypeMenu(INT32 choice); void M_SetupRaceMenu(INT32 choice); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 5ad49a066..7f2fa0f7e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -801,6 +801,8 @@ void M_DrawKartGamemodeMenu(void) for (i = 0; i < currentMenu->numitems; i++) { + INT32 type; + if (currentMenu->menuitems[i].status == IT_DISABLED) { continue; @@ -817,9 +819,12 @@ void M_DrawKartGamemodeMenu(void) } } - switch (currentMenu->menuitems[i].status & IT_DISPLAY) + type = (currentMenu->menuitems[i].status & IT_DISPLAY); + + switch (type) { case IT_STRING: + case IT_TRANSTEXT2: { UINT8 *colormap = NULL; @@ -833,7 +838,13 @@ void M_DrawKartGamemodeMenu(void) } V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap); - V_DrawGamemodeString(x + 16, y - 3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + V_DrawGamemodeString(x + 16, y - 3, + (type == IT_TRANSTEXT2 + ? V_TRANSLUCENT + : 0 + )|V_ALLOWLOWERCASE, + colormap, + currentMenu->menuitems[i].text); } break; } @@ -4716,6 +4727,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili case SECRET_ENCORE: categoryid = '5'; break; + case SECRET_ONLINE: case SECRET_ALTTITLE: case SECRET_SOUNDTEST: categoryid = '6'; @@ -4783,6 +4795,9 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili iconid = 5; break; + case SECRET_ONLINE: + iconid = 10; + break; case SECRET_ALTTITLE: iconid = 6; break; @@ -5036,12 +5051,21 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = mastermapcache; break; } + case SECRET_ONLINE: + { + V_DrawFixedPatch(-3*FRACUNIT, (y-40)*FRACUNIT, + FRACUNIT, + 0, W_CachePatchName("EGGASTLA", PU_CACHE), + NULL); + 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_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Press (A)"); + break; } default: { diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 05d282e7f..ab4cca6bc 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -447,6 +447,7 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore) } // One last catch. + M_SetupPlayMenu(-1); PLAY_CharSelectDef.prevMenu = &MainDef; return torestore; diff --git a/src/m_cond.h b/src/m_cond.h index 01f055758..dafbf8048 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -126,6 +126,9 @@ typedef enum SECRET_TIMEATTACK, // Permit Time attack SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) + + // Option restrictions + SECRET_ONLINE, // Permit netplay (ankle-high barrier to jumping in the deep end) SECRET_SOUNDTEST, // Permit Sound Test SECRET_ALTTITLE, // Permit alternate titlescreen diff --git a/src/menus/play-1.c b/src/menus/play-1.c index d4c8ee099..53ca72dc3 100644 --- a/src/menus/play-1.c +++ b/src/menus/play-1.c @@ -2,16 +2,42 @@ /// \brief Play Menu #include "../k_menu.h" +#include "../m_cond.h" menuitem_t PLAY_MainMenu[] = { {IT_STRING | IT_CALL, "Local Play", "Play only on this computer.", NULL, {.routine = M_SetupGametypeMenu}, 0, 0}, - {IT_STRING | IT_CALL, "Online", "Connect to other computers.", + {IT_STRING | IT_CALL, "Online", NULL, NULL, {.routine = M_MPOptSelectInit}, /*M_MPRoomSelectInit,*/ 0, 0}, {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, }; menu_t PLAY_MainDef = KARTGAMEMODEMENU(PLAY_MainMenu, &PLAY_CharSelectDef); + +void M_SetupPlayMenu(INT32 choice) +{ +#ifdef TESTERS + (void)choice; +#else + if (choice != -1) + PLAY_MainDef.prevMenu = currentMenu; + + // Enable/disable online play. + if (!M_SecretUnlocked(SECRET_ONLINE, true)) + { + PLAY_MainMenu[1].status = IT_TRANSTEXT2 | IT_CALL; + PLAY_MainMenu[1].tooltip = "You'll need experience to play over the internet!"; + } + else + { + PLAY_MainMenu[1].status = IT_STRING | IT_CALL; + PLAY_MainMenu[1].tooltip = "Connect to other computers over the internet."; + } + + if (choice != -1) + M_SetupNextMenu(&PLAY_MainDef, false); +#endif +} diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index a6ac59d1a..d7d1d4e44 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -5,6 +5,7 @@ #include "../r_skins.h" #include "../s_sound.h" #include "../k_grandprix.h" // K_CanChangeRules +#include "../m_cond.h" // Condition Sets menuitem_t PLAY_CharSelect[] = { @@ -1491,7 +1492,7 @@ void M_CharacterSelectTick(void) #if defined (TESTERS) M_MPOptSelectInit(0); #else - M_SetupNextMenu(&PLAY_MainDef, false); + M_SetupPlayMenu(0); #endif } diff --git a/src/menus/play-online-1.c b/src/menus/play-online-1.c index e48154648..e146926b1 100644 --- a/src/menus/play-online-1.c +++ b/src/menus/play-online-1.c @@ -2,6 +2,8 @@ /// \brief MULTIPLAYER OPTION SELECT #include "../k_menu.h" +#include "../m_cond.h" +#include "../s_sound.h" #if defined (TESTERS) #define IT_STRING_CALL_NOTESTERS IT_DISABLED @@ -62,6 +64,15 @@ void M_MPOptSelectInit(INT32 choice) INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}}; const UINT32 forbidden = GTR_FORBIDMP; +#ifndef TESTERS + if (choice != -1 && !M_SecretUnlocked(SECRET_ONLINE, true)) + { + M_StartMessage("Online play is ""\x8B""not yet unlocked""\x80"".\n\nYou'll want experience in ""\x8B""Grand Prix""\x80""\nbefore even thinking about facing\nopponents from across the world.\n\nPress (B)", NULL, MM_NOTHING); + S_StartSound(NULL, sfx_s3k36); + return; + } +#endif + mpmenu.modechoice = 0; mpmenu.ticker = 0; From b0ae6a75325ae09d0472345d85596e1144bdb53a Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Feb 2023 17:39:31 +0000 Subject: [PATCH 004/103] M_HandleMenuInput: remove extra newline from "this cannot be done in a modified game" --- 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 ab4cca6bc..8ac717d9c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -932,7 +932,7 @@ static void M_HandleMenuInput(void) { if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods) { - M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\nPress (B)\n"), NULL, MM_NOTHING); + M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\nPress (B)"), NULL, MM_NOTHING); return; } } From 54a58178a155b0896c0f35440c1c5e88e21e2f60 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Feb 2023 19:55:11 +0000 Subject: [PATCH 005/103] Add Extras-related unlocks - When SECRET_ADDONS is locked - No Addons option on Extras submenu - No mid-game Addons in K_CanChangeRules - Forced onto CORE on Server Browser room selection page - Semi-related: If modifiedgame is true, force onto MODDED, even if you haven't unlocked it - TODO: Update UI to show you can't switch in a more elegant way - Hide Addons Options on Data Options submenu - When SECRET_EGGTV is locked - No Egg TV option on Extras submenu - Semi-related: Rename to Egg TV per team discussion --- src/deh_soc.c | 4 +++ src/k_menu.h | 1 + src/k_menudraw.c | 16 ++++++++-- src/m_cond.h | 2 ++ src/menus/extras-1.c | 46 ++++++++++++++++++++++++++--- src/menus/options-1.c | 3 ++ src/menus/play-online-room-select.c | 20 +++++++------ src/menus/transient/pause-game.c | 7 ++++- 8 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index a4bd8dc77..f79cf63d7 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2292,6 +2292,10 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_SPECIALATTACK; else if (fastcmp(word2, "ONLINE")) unlockables[num].type = SECRET_ONLINE; + else if (fastcmp(word2, "ADDONS")) + unlockables[num].type = SECRET_ADDONS; + else if (fastcmp(word2, "EGGTV")) + unlockables[num].type = SECRET_EGGTV; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; else if (fastcmp(word2, "ALTTITLE")) diff --git a/src/k_menu.h b/src/k_menu.h index 9145027ae..fccbf0675 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -781,6 +781,7 @@ extern struct mpmenu_s { // See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess! UINT8 room; + boolean roomforced; tic_t ticker; UINT8 servernum; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 7f2fa0f7e..ff5becb1b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2775,8 +2775,12 @@ void M_DrawMPRoomSelect(void) // Draw buttons: - V_DrawFixedPatch(160< Date: Mon, 27 Feb 2023 19:56:07 +0000 Subject: [PATCH 006/103] play-local-race-difficulty: Make check for Encore option based off the local state (do not attempt to check online unlock) --- src/menus/play-local-race-difficulty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/play-local-race-difficulty.c b/src/menus/play-local-race-difficulty.c index 30e6436fc..db9704be2 100644 --- a/src/menus/play-local-race-difficulty.c +++ b/src/menus/play-local-race-difficulty.c @@ -89,7 +89,7 @@ void M_SetupDifficultyOptions(INT32 choice) PLAY_RaceDifficulty[drace_mapselect].status = IT_STRING|IT_CALL; // Level Select (Match Race) PLAY_RaceDifficultyDef.lastOn = drace_mapselect; // Select map select by default. - if (M_SecretUnlocked(SECRET_ENCORE, false)) + if (M_SecretUnlocked(SECRET_ENCORE, true)) { PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off } From e07802d3bf9986f4d016567fdac0b0fec81a84a3 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 2 Mar 2023 21:04:23 +0000 Subject: [PATCH 007/103] M_SetupGametypeMenu: Fix a bug with RestoreMenu corrupting menu state. --- src/menus/play-local-1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/play-local-1.c b/src/menus/play-local-1.c index edcfc2bc0..3d0e2c22f 100644 --- a/src/menus/play-local-1.c +++ b/src/menus/play-local-1.c @@ -59,7 +59,7 @@ void M_SetupGametypeMenu(INT32 choice) if (!anyunlocked) { // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(0); + M_SetupRaceMenu(choice); return; } } From 5b1117921393027469a1b96961bc7c7a877b0446 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 2 Mar 2023 23:02:42 +0000 Subject: [PATCH 008/103] A start on in-level empheral conditions - M_UpdateUnlockablesAndExtraEmblems: If Playing() and GS_LEVEL, call a bunch of funny conditions for every non-spectator splitscreen player. - Called every tick in P_Ticker. May be foolish. May duplicate effort. Worth extra attention later. - Add a bunch of stacking-together UCRP_REQUIRESPLAYING condition types. - A collection of PREFIXes - Grand Prix - Bonus Round - Time Attack - Break The Capsules - Sealed Star - A collection of specific prerequisites - Current map is [map value] - Character is [character string that gets parsed after skin load] - M_UpdateConditionSetsPending(), because SOC is bad and loaded before skins - A collection of win conditions (TODO: these still work in FREE PLAY) - Finish in good standing - Break all the capsules - No Contest - Finish at least [place] - Finish at [place] exactly - Finish at [time, tics] - Finish at [time, tics] exactly - Finish with at least [time, tics] left (GTR_TIMELIMIT only) - An AND to concatenate them together (literally just for text adjustment) --- src/deh_soc.c | 93 +++++++++++++++++- src/m_cond.c | 264 +++++++++++++++++++++++++++++++++++++++++++------- src/m_cond.h | 38 ++++++-- src/p_tick.c | 4 + src/r_skins.c | 1 + 5 files changed, 354 insertions(+), 46 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index f79cf63d7..076713297 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2336,9 +2336,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) INT32 i; char *params[4]; // condition, requirement, extra info, extra info char *spos; + char *pendingstring = NULL; conditiontype_t ty; - INT32 re; + INT32 re = 0; INT16 x1 = 0, x2 = 0; INT32 offset = 0; @@ -2362,8 +2363,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } - if (fastcmp(params[0], "PLAYTIME") - || (++offset && fastcmp(params[0], "MATCHESPLAYED"))) + if ((offset=0) || fastcmp(params[0], "PLAYTIME") + || (++offset && fastcmp(params[0], "MATCHESPLAYED"))) { PARAMCHECK(1); ty = UC_PLAYTIME + offset; @@ -2400,7 +2401,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) re = atoi(params[1]); } else if ((offset=0) || fastcmp(params[0], "MAPVISITED") - || (++offset && fastcmp(params[0], "MAPBEATEN"))) + || (++offset && fastcmp(params[0], "MAPBEATEN")) + || (++offset && fastcmp(params[0], "MAPENCORE"))) { PARAMCHECK(1); ty = UC_MAPVISITED + offset; @@ -2480,13 +2482,94 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + else if (fastcmp(params[0], "AND")) + { + //PARAMCHECK(1); + ty = UC_AND; + } + else if ((offset=0) || fastcmp(params[0], "PREFIX_GRANDPRIX") + || (++offset && fastcmp(params[0], "PREFIX_BONUSROUND")) + || (++offset && fastcmp(params[0], "PREFIX_TIMEATTACK")) + || (++offset && fastcmp(params[0], "PREFIX_BREAKTHECAPSULES")) + || (++offset && fastcmp(params[0], "PREFIX_SEALEDSTAR"))) + { + //PARAMCHECK(1); + ty = UCRP_PREFIX_GRANDPRIX + offset; + } + else if (fastcmp(params[0], "ISMAP")) + { + PARAMCHECK(1); + ty = UCRP_ISMAP; + re = G_MapNumber(params[1]); + + if (re >= nummapheaders) + { + deh_warning("Invalid level %s for condition ID %d", params[1], id+1); + return; + } + } + else if (fastcmp(params[0], "ISCHARACTER")) + { + PARAMCHECK(1); + ty = UCRP_ISCHARACTER; +#if 0 + { + re = R_SkinAvailable(params[1]); + + if (re < 0) + { + deh_warning("Invalid character %s for condition ID %d", params[1], id+1); + return; + } + } +#else + { + pendingstring = Z_StrDup(params[1]); + re = -1; + } +#endif + } + else if ((offset=0) || fastcmp(params[0], "FINISHCOOL") + || (++offset && fastcmp(params[0], "FINISHALLCAPSULES")) + || (++offset && fastcmp(params[0], "NOCONTEST"))) + { + //PARAMCHECK(1); + ty = UCRP_FINISHCOOL + offset; + } + else if ((offset=0) || fastcmp(params[0], "FINISHPLACE") + || (++offset && fastcmp(params[0], "FINISHPLACEEXACT"))) + { + PARAMCHECK(1); + ty = UCRP_FINISHPLACE + offset; + re = atoi(params[1]); + + if (re < 1 || re > MAXPLAYERS) + { + deh_warning("Invalid place %s for condition ID %d", params[1], id+1); + return; + } + } + else if ((offset=0) || fastcmp(params[0], "FINISHTIME") + || (++offset && fastcmp(params[0], "FINISHTIMEEXACT")) + || (++offset && fastcmp(params[0], "FINISHTIMELEFT"))) + { + PARAMCHECK(1); + ty = UCRP_FINISHTIME + offset; + re = atoi(params[1]); + + if (re < 0) + { + deh_warning("Invalid time %s for condition ID %d", params[1], id+1); + return; + } + } else { deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1); return; } - M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2); + M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, pendingstring); } void readconditionset(MYFILE *f, UINT8 setnum) diff --git a/src/m_cond.c b/src/m_cond.c index 070c5b9ea..76fe45199 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -25,6 +25,10 @@ #include "r_draw.h" // R_GetColorByName #include "s_sound.h" // S_StartSound +#include "k_kart.h" // K_IsPLayerLosing +#include "k_grandprix.h" // grandprixinfo +#include "k_battle.h" // battlecapsules +#include "k_specialstage.h" // specialstageinfo #include "k_pwrlv.h" #include "k_profiles.h" @@ -427,7 +431,7 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata) } } -void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) +void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *pendingstring) { condition_t *cond; UINT32 num, wnum; @@ -446,6 +450,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1 cond[wnum].requirement = r; cond[wnum].extrainfo1 = x1; cond[wnum].extrainfo2 = x2; + cond[wnum].pendingstring = pendingstring; } void M_ClearConditionSet(UINT8 set) @@ -490,8 +495,51 @@ void M_ClearSecrets(void) // Condition set checking // ---------------------- +void M_UpdateConditionSetsPending(void) +{ + UINT32 i, j; + conditionset_t *c; + condition_t *cn; + + for (i = 0; i < MAXCONDITIONSETS; ++i) + { + c = &conditionSets[i]; + if (!c->numconditions) + continue; + + for (j = 0; j < c->numconditions; ++j) + { + cn = &c->condition[j]; + if (cn->pendingstring == NULL) + continue; + + switch (cn->type) + { + case UCRP_ISCHARACTER: + { + cn->requirement = R_SkinAvailable(cn->pendingstring); + + if (cn->requirement < 0) + { + CONS_Alert(CONS_WARNING, "UCRP_ISCHARACTER: Invalid character %s for condition ID %d", cn->pendingstring, cn->id+1); + return; + } + break; + } + default: + break; + } + + Z_Free(cn->pendingstring); + cn->pendingstring = NULL; + } + + + } +} + // See also M_GetConditionString -UINT8 M_CheckCondition(condition_t *cn) +boolean M_CheckCondition(condition_t *cn, player_t *player) { switch (cn->type) { @@ -544,16 +592,68 @@ UINT8 M_CheckCondition(condition_t *cn) return gamedata->unlocked[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); + + case UC_AND: // Just for string building + return true; + + case UCRP_PREFIX_GRANDPRIX: + return (grandprixinfo.gp == true); + case UCRP_PREFIX_BONUSROUND: + return ((grandprixinfo.gp == true) && (grandprixinfo.eventmode == GPEVENT_BONUS)); + case UCRP_PREFIX_TIMEATTACK: + return (modeattacking != ATTACKING_NONE); + case UCRP_PREFIX_BREAKTHECAPSULES: + return ((gametyperules & GTR_CAPSULES) && battlecapsules); + case UCRP_PREFIX_SEALEDSTAR: + return (specialstageinfo.valid == true); + + case UCRP_ISMAP: + return (gamemap == cn->requirement+1); + case UCRP_ISCHARACTER: + return (player->skin == cn->requirement); + + case UCRP_FINISHCOOL: + return (player->exiting + && !(player->pflags & PF_NOCONTEST) + && !K_IsPlayerLosing(player)); + case UCRP_FINISHALLCAPSULES: + return (battlecapsules + && !(player->pflags & PF_NOCONTEST) + && numtargets >= maptargets); + case UCRP_NOCONTEST: + return (player->pflags & PF_NOCONTEST); + case UCRP_FINISHPLACE: + return (player->exiting + && !(player->pflags & PF_NOCONTEST) + && player->position <= cn->requirement); + case UCRP_FINISHPLACEEXACT: + return (player->exiting + && !(player->pflags & PF_NOCONTEST) + && player->position == cn->requirement); + case UCRP_FINISHTIME: + return (player->exiting + && !(player->pflags & PF_NOCONTEST) + && player->realtime <= (unsigned)cn->requirement); + case UCRP_FINISHTIMEEXACT: + return (player->exiting + && !(player->pflags & PF_NOCONTEST) + && player->realtime == (unsigned)cn->requirement); + case UCRP_FINISHTIMELEFT: + return (timelimitintics + && player->exiting + && !(player->pflags & PF_NOCONTEST) + && player->realtime < timelimitintics + && (timelimitintics + extratimeintics + secretextratime - player->realtime) <= (unsigned)cn->requirement); } return false; } -static UINT8 M_CheckConditionSet(conditionset_t *c) +static boolean M_CheckConditionSet(conditionset_t *c, player_t *player) { UINT32 i; UINT32 lastID = 0; condition_t *cn; - UINT8 achievedSoFar = true; + boolean achievedSoFar = true; for (i = 0; i < c->numconditions; ++i) { @@ -569,7 +669,16 @@ static UINT8 M_CheckConditionSet(conditionset_t *c) continue; lastID = cn->id; - achievedSoFar = M_CheckCondition(cn); + + if ((player != NULL) != (cn->type >= UCRP_REQUIRESPLAYING)) + { + //CONS_Printf("skipping %s:%u:%u (%s)\n", sizeu1(c-conditionSets), cn->id, i, player ? "player exists" : "player does not exist"); + achievedSoFar = false; + continue; + } + + achievedSoFar = M_CheckCondition(cn, player); + //CONS_Printf("%s:%u:%u - %u is %s\n", sizeu1(c-conditionSets), cn->id, i, cn->type, achievedSoFar ? "true" : "false"); } return achievedSoFar; @@ -599,6 +708,17 @@ static char *M_BuildConditionTitle(UINT16 map) return title; } +static const char *M_GetNthType(UINT8 position) +{ + if (position == 1) + return "st"; + if (position == 2) + return "nd"; + if (position == 3) + return "rd"; + return "th"; +} + // See also M_CheckCondition static const char *M_GetConditionString(condition_t *cn) { @@ -606,6 +726,8 @@ static const char *M_GetConditionString(condition_t *cn) char *title = NULL; const char *work = NULL; + // If this function returns NULL, it stops building the condition and just does ???'s. + #define BUILDCONDITIONTITLE(i) (M_BuildConditionTitle(i)) switch (cn->type) @@ -737,6 +859,60 @@ static const char *M_GetConditionString(condition_t *cn) gamedata->unlocked[cn->requirement-1] ? unlockables[cn->requirement-1].name : "???"); + + case UC_AND: + return "&"; + + case UCRP_PREFIX_GRANDPRIX: + return "GRAND PRIX:"; + case UCRP_PREFIX_BONUSROUND: + return "BONUS ROUND:"; + case UCRP_PREFIX_TIMEATTACK: + if (!M_SecretUnlocked(SECRET_TIMEATTACK, true)) + return NULL; + return "TIME ATTACK:"; + case UCRP_PREFIX_BREAKTHECAPSULES: + return "BREAK THE CAPSULES:"; + case UCRP_PREFIX_SEALEDSTAR: + return "SEALED STAR:"; + + case UCRP_ISMAP: + if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement]) + return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement); + + title = BUILDCONDITIONTITLE(cn->requirement); + work = va("On %s,", title); + Z_Free(title); + return work; + case UCRP_ISCHARACTER: + if (cn->requirement < 0 || !skins[cn->requirement].realname[0]) + return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement); + return va("as %s", skins[cn->requirement].realname); + + case UCRP_FINISHCOOL: + return "finish in good standing"; + case UCRP_FINISHALLCAPSULES: + return "break every capsule"; + case UCRP_NOCONTEST: + return "NO CONTEST"; + case UCRP_FINISHPLACE: + case UCRP_FINISHPLACEEXACT: + return va("finish in %d%s%s", cn->requirement, M_GetNthType(cn->requirement), + ((cn->type == UCRP_FINISHPLACE && cn->requirement > 1) + ? " or better" : "")); + case UCRP_FINISHTIME: + case UCRP_FINISHTIMEEXACT: + return va("finish in %s%i:%02i.%02i", + (cn->type == UCRP_FINISHTIMEEXACT ? "exactly " : ""), + G_TicsToMinutes(cn->requirement, true), + G_TicsToSeconds(cn->requirement), + G_TicsToCentiseconds(cn->requirement)); + case UCRP_FINISHTIMELEFT: + return va("finish with %i:%02i.%02i remaining", + G_TicsToMinutes(cn->requirement, true), + G_TicsToSeconds(cn->requirement), + G_TicsToCentiseconds(cn->requirement)); + default: break; } @@ -746,20 +922,16 @@ static const char *M_GetConditionString(condition_t *cn) #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; + boolean stopasap = false; message[0] = '\0'; @@ -781,43 +953,40 @@ char *M_BuildConditionSetString(UINT8 unlockid) if (i > 0) { - worklen = 3; - if (lastID == cn->id) - { - strncat(message, "\n& ", len); - } - else + if (lastID != cn->id) { + worklen = 4; strncat(message, "\nOR ", len); - worklen++; } + else //if (cn->type >= UCRP_REQUIRESPLAYING) + { + worklen = 1; + strncat(message, " ", len); + } + /*else + { + worklen = 3; + strncat(message, "\n& ", len); + }*/ len -= worklen; } lastID = cn->id; -#ifdef ACHIEVEDBRITE - achieved = M_CheckCondition(cn); - - if (achieved) - { - strncat(message, "\0x82", len); - len--; - } -#endif - work = M_GetConditionString(cn); + if (work == NULL) + { + stopasap = true; + work = "???"; + } worklen = strlen(work); strncat(message, work, len); len -= worklen; -#ifdef ACHIEVEDBRITE - if (achieved) + if (stopasap) { - strncat(message, "\0x80", len); - len--; + break; } -#endif } // Rudementary word wrapping. @@ -854,10 +1023,11 @@ char *M_BuildConditionSetString(UINT8 unlockid) return message; } -static void M_CheckUnlockConditions(void) +static boolean M_CheckUnlockConditions(player_t *player) { INT32 i; conditionset_t *c; + boolean ret; for (i = 0; i < MAXCONDITIONSETS; ++i) { @@ -865,8 +1035,13 @@ static void M_CheckUnlockConditions(void) if (!c->numconditions || gamedata->achieved[i]) continue; - gamedata->achieved[i] = (M_CheckConditionSet(c)); + if ((gamedata->achieved[i] = (M_CheckConditionSet(c, player))) != true) + continue; + + ret = true; } + + return ret; } boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) @@ -882,7 +1057,26 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) M_CompletionEmblems(); } - M_CheckUnlockConditions(); + response = M_CheckUnlockConditions(NULL); + + if (Playing() && (gamestate == GS_LEVEL)) + { + for (i = 0; i <= splitscreen; i++) + { + if (!playeringame[g_localplayers[i]]) + continue; + if (players[g_localplayers[i]].spectator) + continue; + response |= M_CheckUnlockConditions(&players[g_localplayers[i]]); + } + } + + if (!response && loud) + { + return false; + } + + response = 0; // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) diff --git a/src/m_cond.h b/src/m_cond.h index 6918e4771..6a0949f58 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -33,15 +33,39 @@ typedef enum UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] UC_GAMECLEAR, // GAMECLEAR UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] - UC_MAPVISITED, // MAPVISITED [map number] - UC_MAPBEATEN, // MAPBEATEN [map number] - UC_MAPENCORE, // MAPENCORE [map number] - UC_MAPTIME, // MAPTIME [map number] [time to beat, tics] + UC_MAPVISITED, // MAPVISITED [map] + UC_MAPBEATEN, // MAPBEATEN [map] + UC_MAPENCORE, // MAPENCORE [map] + UC_MAPTIME, // MAPTIME [map] [time to beat, tics] UC_TRIGGER, // TRIGGER [trigger number] UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] + + UC_AND, // Just for string building + + UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL). + + UCRP_PREFIX_GRANDPRIX = UCRP_REQUIRESPLAYING, // GRAND PRIX: + UCRP_PREFIX_BONUSROUND, // BONUS ROUND: + UCRP_PREFIX_TIMEATTACK, // TIME ATTACK: + UCRP_PREFIX_BREAKTHECAPSULES, // BREAK THE CAPSULES: + UCRP_PREFIX_SEALEDSTAR, // SEALED STAR: + + UCRP_ISMAP, // gamemap == [map] + UCRP_ISCHARACTER, // character == [skin] + + UCRP_FINISHCOOL, // Finish in good standing + UCRP_FINISHALLCAPSULES, // Break all capsules + UCRP_NOCONTEST, // No Contest + + UCRP_FINISHPLACE, // Finish at least [place] + UCRP_FINISHPLACEEXACT, // Finish at [place] exactly + + UCRP_FINISHTIME, // Finish <= [time, tics] + UCRP_FINISHTIMEEXACT, // Finish == [time, tics] + UCRP_FINISHTIMELEFT, // Finish with at least [time, tics] to spare } conditiontype_t; // Condition Set information @@ -55,6 +79,7 @@ struct condition_t INT32 requirement; /// <- The requirement for this variable. INT16 extrainfo1; /// <- Extra information for the condition when needed. INT16 extrainfo2; /// <- Extra information for the condition when needed. + char *pendingstring; /// oooohhh my god i hate loading order for SOC VS skins }; struct conditionset_t { @@ -217,14 +242,15 @@ 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); +void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *pendingstring); +void M_UpdateConditionSetsPending(void); // Clearing secrets void M_ClearConditionSet(UINT8 set); void M_ClearSecrets(void); // Updating conditions and unlockables -UINT8 M_CheckCondition(condition_t *cn); +boolean M_CheckCondition(condition_t *cn, player_t *player); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); UINT8 M_GetNextAchievedUnlock(void); UINT8 M_CheckLevelEmblems(void); diff --git a/src/p_tick.c b/src/p_tick.c index 70c4a14f3..9a1324f2f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -630,6 +630,10 @@ void P_Ticker(boolean run) #undef PLAYERCONDITION ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; + + // TODO would this be laggy with more conditions in play... + if (M_UpdateUnlockablesAndExtraEmblems(true)) + G_SaveGameData(); } // Keep track of how long they've been playing! diff --git a/src/r_skins.c b/src/r_skins.c index d6294fa0d..1f4daba5f 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -156,6 +156,7 @@ void R_InitSkins(void) R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); } ST_ReloadSkinFaceGraphics(); + M_UpdateConditionSetsPending(); } UINT8 *R_GetSkinAvailabilities(boolean demolock) From b4217e1afd116b0faa177c8b15f16c49ddbb0814 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 2 Mar 2023 23:11:18 +0000 Subject: [PATCH 009/103] UCRP_FINISHTIMELEFT: Flip incorrect less-than to greater-than in condition handling --- 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 76fe45199..b840ffd47 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -643,7 +643,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && player->exiting && !(player->pflags & PF_NOCONTEST) && player->realtime < timelimitintics - && (timelimitintics + extratimeintics + secretextratime - player->realtime) <= (unsigned)cn->requirement); + && (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement); } return false; } From 53ce2e4287b3ff0368991297df5b6a56e5ba7a50 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 13:38:31 +0000 Subject: [PATCH 010/103] Gamedata-related changes + Crash tracking - Combine multiple adjacent saves - Generally could happen during game ticking, combined via gamedata->deferredstate - DEFINITELY happened in splitscreen PWR handling, adjust those loops directly - Write "dirty" state via gamedata->crashflags on everything except safe, intentful unloads - Add UC_CRASH, which unlocks dependent on the above "dirty" state being present at gamedata load - We can use this for something more useful and less funny later. - Play "O_LOSERC" on the menu, starting with the Challenges screen, if a UC_CRASH condition has been met. --- src/d_main.c | 3 ++ src/deh_soc.c | 7 ++++- src/f_finale.c | 2 +- src/g_game.c | 54 +++++++++++++++++++++++------------ src/g_game.h | 2 +- src/k_menufunc.c | 23 ++++++++++++++- src/k_pwrlv.c | 12 ++++++-- src/m_cond.c | 12 ++++++++ src/m_cond.h | 10 +++++++ src/menus/extras-challenges.c | 2 +- src/p_inter.c | 2 +- src/p_setup.c | 3 +- src/p_spec.c | 2 +- src/p_tick.c | 5 ++-- src/sdl/i_system.c | 6 ++-- src/sdl12/i_system.c | 6 ++-- src/win32ce/win_sys.c | 6 ++-- 17 files changed, 117 insertions(+), 40 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index f41d70a31..65d199912 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -977,6 +977,9 @@ void D_ClearState(void) cursongcredit.def = NULL; + if (gamedata->deferredsave) + G_SaveGameData(true); + G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; } diff --git a/src/deh_soc.c b/src/deh_soc.c index 076713297..d904bef02 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2482,6 +2482,11 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + else if (fastcmp(params[0], "CRASH")) + { + //PARAMCHECK(1); + ty = UC_CRASH; + } else if (fastcmp(params[0], "AND")) { //PARAMCHECK(1); @@ -2689,7 +2694,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) if (!GoodDataFileName(word2)) I_Error("Maincfg: bad data file name '%s'\n", word2); - G_SaveGameData(); + G_SaveGameData(false); // undirty your old gamedata strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); strlwr(gamedatafilename); savemoddata = true; diff --git a/src/f_finale.c b/src/f_finale.c index acd923765..80e8e7d4e 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1081,7 +1081,7 @@ void F_GameEvaluationTicker(void) ++gamedata->timesBeaten; M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + G_SaveGameData(true); } else { diff --git a/src/g_game.c b/src/g_game.c index c858bc85e..42d2ddd92 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -649,7 +649,7 @@ void G_UpdateRecords(void) } M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + gamedata->deferredsave = true; } // @@ -2282,19 +2282,6 @@ static inline void G_PlayerFinishLevel(INT32 player) p->starpostnum = 0; memset(&p->respawn, 0, sizeof (p->respawn)); - - // SRB2kart: Increment the "matches played" counter. - if (player == consoleplayer) - { - if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) - { - gamedata->matchesplayed++; - M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); - } - - legitimateexit = false; - } } // @@ -3697,7 +3684,7 @@ static void G_UpdateVisited(void) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + G_SaveGameData(true); } static boolean CanSaveLevel(INT32 mapnum) @@ -4005,6 +3992,19 @@ static void G_DoCompleted(void) if (modeattacking && pausedelay) pausedelay = 0; + if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) + { + // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve + gamedata->matchesplayed++; + M_UpdateUnlockablesAndExtraEmblems(true); + gamedata->deferredsave = true; + } + + if (gamedata->deferredsave) + G_SaveGameData(true); + + legitimateexit = false; + gameaction = ga_nothing; if (metalplayback) @@ -4317,7 +4317,7 @@ void G_LoadGameSettings(void) } #define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual -#define GD_VERSIONMINOR 1 // Change every format update +#define GD_VERSIONMINOR 2 // Change every format update static const char *G_GameDataFolder(void) { @@ -4351,6 +4351,7 @@ void G_LoadGameData(void) gamedata->totalplaytime = 0; // total play time (separate from all) gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished + gamedata->crashflags = 0; if (M_CheckParm("-nodata")) { @@ -4398,6 +4399,13 @@ void G_LoadGameData(void) gamedata->totalplaytime = READUINT32(save.p); gamedata->matchesplayed = READUINT32(save.p); + if (versionMinor > 1) + { + gamedata->crashflags = READUINT8(save.p); + if (gamedata->crashflags & GDCRASH_LAST) + gamedata->crashflags |= GDCRASH_ANY; + } + { // Quick & dirty hash for what mod this save file is for. UINT32 modID = READUINT32(save.p); @@ -4543,13 +4551,15 @@ void G_LoadGameData(void) // G_SaveGameData // Saves the main data file, which stores information such as emblems found, etc. -void G_SaveGameData(void) +void G_SaveGameData(boolean dirty) { size_t length; INT32 i, j; UINT8 btemp; savebuffer_t save = {0}; + gamedata->deferredsave = false; + if (!gamedata->loaded) return; // If never loaded (-nodata), don't save @@ -4561,7 +4571,7 @@ void G_SaveGameData(void) return; } - length = (4+1+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4580,6 +4590,14 @@ void G_SaveGameData(void) WRITEUINT8(save.p, GD_VERSIONMINOR); // 1 WRITEUINT32(save.p, gamedata->totalplaytime); // 4 WRITEUINT32(save.p, gamedata->matchesplayed); // 4 + + { + UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); + if (dirty) + crashflags |= GDCRASH_LAST; + WRITEUINT8(save.p, crashflags); // 1 + } + WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); // To save space, use one bit per collected/achieved/unlocked flag diff --git a/src/g_game.h b/src/g_game.h index fac245c9a..788e2ed9b 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -173,7 +173,7 @@ boolean G_IsTitleCardAvailable(void); // Can be called by the startup code or M_Responder, calls P_SetupLevel. void G_LoadGame(UINT32 slot, INT16 mapoverride); -void G_SaveGameData(void); +void G_SaveGameData(boolean dirty); void G_SaveGame(UINT32 slot, INT16 mapnum); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 4300ef933..16c173d00 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -15,6 +15,7 @@ #include "v_video.h" #include "f_finale.h" #include "m_misc.h" +#include "m_cond.h" #ifdef PC_DOS #include // for snprintf @@ -338,9 +339,17 @@ boolean M_Responder(event_t *ev) void M_PlayMenuJam(void) { menu_t *refMenu = (menuactive ? currentMenu : restoreMenu); + static boolean loserclubpermitted = false; + boolean loserclub = (loserclubpermitted && (gamedata->crashflags & GDCRASH_LOSERCLUB)); if (challengesmenu.pending) + { + S_StopMusic(); + cursongcredit.def = NULL; + + loserclubpermitted = true; return; + } if (Playing()) return; @@ -351,15 +360,27 @@ void M_PlayMenuJam(void) { S_StopMusic(); cursongcredit.def = NULL; + return; } - else + else if (!loserclub) { if (NotCurrentlyPlaying(refMenu->music)) { S_ChangeMusicInternal(refMenu->music, true); S_ShowMusicCredit(); } + return; } + } + + if (loserclub) + { + if (refMenu != NULL && NotCurrentlyPlaying("LOSERC")) + { + S_ChangeMusicInternal("LOSERC", true); + S_ShowMusicCredit(); + } + return; } diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index db46a9266..3bc2466d6 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -397,6 +397,7 @@ void K_CashInPowerLevels(void) { SINT8 powerType = K_UsingPowerLevels(); UINT8 i; + boolean gamedataupdate; //CONS_Printf("\n========\n"); //CONS_Printf("Cashing in power level changes...\n"); @@ -417,14 +418,19 @@ void K_CashInPowerLevels(void) { pr->powerlevels[powerType] = clientpowerlevels[i][powerType]; - M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + gamedataupdate = true; } } clientPowerAdd[i] = 0; } + if (gamedataupdate) + { + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); + } + //CONS_Printf("========\n"); } @@ -638,6 +644,6 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) pr->powerlevels[powerType] = yourPower + inc; M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + G_SaveGameData(true); } } diff --git a/src/m_cond.c b/src/m_cond.c index b840ffd47..73d2310ca 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -486,6 +486,7 @@ void M_ClearSecrets(void) gamedata->challengegridwidth = 0; gamedata->timesBeaten = 0; + gamedata->crashflags = 0; // Re-unlock any always unlocked things M_UpdateUnlockablesAndExtraEmblems(false); @@ -592,6 +593,13 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return gamedata->unlocked[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); + case UC_CRASH: + if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) + { + gamedata->crashflags |= GDCRASH_LOSERCLUB; + return true; + } + return false; case UC_AND: // Just for string building return true; @@ -859,6 +867,10 @@ static const char *M_GetConditionString(condition_t *cn) gamedata->unlocked[cn->requirement-1] ? unlockables[cn->requirement-1].name : "???"); + case UC_CRASH: + if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) + return "Relaunch \"Dr. Robotnik's Ring Racers\" after a crash"; + return NULL; case UC_AND: return "&"; diff --git a/src/m_cond.h b/src/m_cond.h index 6a0949f58..7a4931c94 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -43,6 +43,8 @@ typedef enum UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] + UC_CRASH, // Hee ho ! + UC_AND, // Just for string building UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL). @@ -177,12 +179,17 @@ typedef enum #endif #define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH) +#define GDCRASH_LAST 0x01 +#define GDCRASH_ANY 0x02 +#define GDCRASH_LOSERCLUB 0x04 + // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat struct gamedata_t { // WHENEVER OR NOT WE'RE READY TO SAVE boolean loaded; + boolean deferredsave; // CONDITION SETS ACHIEVED boolean achieved[MAXCONDITIONSETS]; @@ -204,6 +211,9 @@ struct gamedata_t // PLAY TIME UINT32 totalplaytime; UINT32 matchesplayed; + + // Funny + UINT8 crashflags; }; extern gamedata_t *gamedata; diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 17ced0502..9caf144bd 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -324,7 +324,7 @@ void M_ChallengesTick(void) { // All done! Let's save the unlocks we've busted open. challengesmenu.pending = false; - G_SaveGameData(); + G_SaveGameData(true); } } else if (challengesmenu.fade < 5) diff --git a/src/p_inter.c b/src/p_inter.c index 3d69eec25..78ccc8143 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -554,7 +554,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) gamedata->collected[special->health-1] = gotcollected = true; if (!M_UpdateUnlockablesAndExtraEmblems(true)) S_StartSound(NULL, sfx_ncitem); - G_SaveGameData(); + gamedata->deferredsave = true; } if (netgame) diff --git a/src/p_setup.c b/src/p_setup.c index d97696309..a9c4894ad 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7444,6 +7444,7 @@ static void P_InitGametype(void) // Started a game? Move on to the next jam when you go back to the title screen CV_SetValue(&cv_menujam_update, 1); + gamedata->crashflags &= ~GDCRASH_LOSERCLUB; } struct minimapinfo minimapinfo; @@ -7936,7 +7937,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; M_UpdateUnlockablesAndExtraEmblems(true); - G_SaveGameData(); + G_SaveGameData(true); } G_AddMapToBuffer(gamemap-1); diff --git a/src/p_spec.c b/src/p_spec.c index 0317599e4..cc6e3cf0e 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -3310,7 +3310,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha // Unlocked something? if (M_UpdateUnlockablesAndExtraEmblems(true)) { - G_SaveGameData(); // only save if unlocked something + gamedata->deferredsave = true; // only save if unlocked something } } } diff --git a/src/p_tick.c b/src/p_tick.c index 9a1324f2f..8433b4ac3 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -632,8 +632,9 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; // TODO would this be laggy with more conditions in play... - if (M_UpdateUnlockablesAndExtraEmblems(true)) - G_SaveGameData(); + if ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) + || gamedata->deferredsave) + G_SaveGameData(true); } // Keep track of how long they've been playing! diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 9362a540b..cce361bd6 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -1737,7 +1737,7 @@ void I_Quit(void) if (Playing()) K_PlayerForfeit(consoleplayer, true); - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -1821,7 +1821,7 @@ void I_Error(const char *error, ...) if (errorcount == 8) { M_SaveConfig(NULL); - G_SaveGameData(); + G_SaveGameData(true); } if (errorcount > 20) { @@ -1852,7 +1852,7 @@ void I_Error(const char *error, ...) M_SaveConfig(NULL); // save game config, cvars.. D_SaveBan(); // save the ban list - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(true); // Tails 12-08-2002 // Shutdown. Here might be other errors. diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c index b388a9bf5..032c535d6 100644 --- a/src/sdl12/i_system.c +++ b/src/sdl12/i_system.c @@ -2983,7 +2983,7 @@ void I_Quit(void) if (Playing()) K_PlayerForfeit(consoleplayer, true); - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -3078,7 +3078,7 @@ void I_Error(const char *error, ...) if (errorcount == 9) { M_SaveConfig(NULL); - G_SaveGameData(); + G_SaveGameData(true); } if (errorcount > 20) { @@ -3142,7 +3142,7 @@ void I_Error(const char *error, ...) #ifndef NONET D_SaveBan(); // save the ban list #endif - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(true); // Tails 12-08-2002 // Shutdown. Here might be other errors. if (demorecording) diff --git a/src/win32ce/win_sys.c b/src/win32ce/win_sys.c index 091171b52..c4f4f4859 100644 --- a/src/win32ce/win_sys.c +++ b/src/win32ce/win_sys.c @@ -607,7 +607,7 @@ void I_Error(const char *error, ...) if (errorcount == 7) { M_SaveConfig(NULL); - G_SaveGameData(); + G_SaveGameData(true); } if (errorcount > 20) { @@ -636,7 +636,7 @@ void I_Error(const char *error, ...) if (!errorcount) { M_SaveConfig(NULL); // save game config, cvars.. - G_SaveGameData(); + G_SaveGameData(true); } // save demo, could be useful for debug @@ -726,7 +726,7 @@ void I_Quit(void) G_CheckDemoStatus(); M_SaveConfig(NULL); // save game config, cvars.. - G_SaveGameData(); + G_SaveGameData(false); // undirty your save // maybe it needs that the ticcount continues, // or something else that will be finished by I_ShutdownSystem(), From e1c25228b5e7168a6ddad011b5280f6e2dec4450 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 13:43:47 +0000 Subject: [PATCH 011/103] UCRP_REQUIRESPLAYING: Forbid some possible exiting states from happening in Free Play --- src/m_cond.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 73d2310ca..2a9ecbb57 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -539,6 +539,28 @@ void M_UpdateConditionSetsPending(void) } } +static boolean M_NotFreePlay(player_t *player) +{ + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (player == &players[i]) + { + continue; + } + + return true; + } + + return false; +} + // See also M_GetConditionString boolean M_CheckCondition(condition_t *cn, player_t *player) { @@ -622,34 +644,41 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) case UCRP_FINISHCOOL: return (player->exiting - && !(player->pflags & PF_NOCONTEST) - && !K_IsPlayerLosing(player)); + && !(player->pflags & PF_NOCONTEST) + && M_NotFreePlay(player) + && !K_IsPlayerLosing(player)); case UCRP_FINISHALLCAPSULES: return (battlecapsules && !(player->pflags & PF_NOCONTEST) + //&& M_NotFreePlay(player) && numtargets >= maptargets); case UCRP_NOCONTEST: return (player->pflags & PF_NOCONTEST); case UCRP_FINISHPLACE: return (player->exiting && !(player->pflags & PF_NOCONTEST) + && M_NotFreePlay(player) && player->position <= cn->requirement); case UCRP_FINISHPLACEEXACT: return (player->exiting && !(player->pflags & PF_NOCONTEST) + && M_NotFreePlay(player) && player->position == cn->requirement); case UCRP_FINISHTIME: return (player->exiting && !(player->pflags & PF_NOCONTEST) + //&& M_NotFreePlay(player) && player->realtime <= (unsigned)cn->requirement); case UCRP_FINISHTIMEEXACT: return (player->exiting && !(player->pflags & PF_NOCONTEST) + //&& M_NotFreePlay(player) && player->realtime == (unsigned)cn->requirement); case UCRP_FINISHTIMELEFT: return (timelimitintics && player->exiting && !(player->pflags & PF_NOCONTEST) + && !K_CanChangeRules(false) // too easy to change cv_timelimit && player->realtime < timelimitintics && (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement); } From 1f144c31060d5dcd117de541a07e51938272a7db Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 13:46:42 +0000 Subject: [PATCH 012/103] M_UpdateUnlockablesAndExtraEmblems: Do not permit UCRP_REQUIRESPLAYING conditions in demo.playback --- 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 2a9ecbb57..cfaee1e73 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1100,7 +1100,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response = M_CheckUnlockConditions(NULL); - if (Playing() && (gamestate == GS_LEVEL)) + if (!demo.playback && Playing() && (gamestate == GS_LEVEL)) { for (i = 0; i <= splitscreen; i++) { From db52c22a83718c22e7f202eaff380c3d36dd5810 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 13:59:59 +0000 Subject: [PATCH 013/103] More NULL handling for gamedata Partial duplicate of MR 1010, but 1) wanted to guarantee no merge conflicts before I forgot and 2) I added things in previous commits which needed stronger guarding --- src/d_main.c | 2 +- src/g_game.c | 6 +++--- src/m_cond.c | 6 ++++++ src/p_tick.c | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 65d199912..d9eee1b0d 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -977,7 +977,7 @@ void D_ClearState(void) cursongcredit.def = NULL; - if (gamedata->deferredsave) + if (gamedata && gamedata->deferredsave) G_SaveGameData(true); G_SetGamestate(GS_NULL); diff --git a/src/g_game.c b/src/g_game.c index 42d2ddd92..d63de1937 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4558,11 +4558,11 @@ void G_SaveGameData(boolean dirty) UINT8 btemp; savebuffer_t save = {0}; - gamedata->deferredsave = false; - - if (!gamedata->loaded) + if (gamedata == NULL || !gamedata->loaded) return; // If never loaded (-nodata), don't save + gamedata->deferredsave = false; + if (usedCheats) { #ifdef DEVELOP diff --git a/src/m_cond.c b/src/m_cond.c index cfaee1e73..df84c7783 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1090,6 +1090,12 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) INT32 i; UINT8 response = 0; + if (!gamedata) + { + // Don't attempt to write/check anything. + return false; + } + if (!loud) { // Just in case they aren't to sync diff --git a/src/p_tick.c b/src/p_tick.c index 8433b4ac3..bf44ca38a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -633,7 +633,7 @@ void P_Ticker(boolean run) // TODO would this be laggy with more conditions in play... if ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) - || gamedata->deferredsave) + || (gamedata && gamedata->deferredsave)) G_SaveGameData(true); } From bd54a789a28b4bf4e9086a09045a924a2009c06d Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 15:31:36 +0000 Subject: [PATCH 014/103] M_BuildConditionSetString, M_StartMessage: Fix occasionally spurious injected newlines --- src/m_cond.c | 8 ++++---- src/menus/transient/message-box.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index df84c7783..89c685336 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -971,7 +971,7 @@ char *M_BuildConditionSetString(UINT8 unlockid) size_t len = 1024, worklen; static char message[1024] = ""; const char *work = NULL; - size_t max = 0, start = 0, strlines = 0, i; + size_t max = 0, maxatstart = 0, start = 0, i; boolean stopasap = false; message[0] = '\0'; @@ -1038,12 +1038,13 @@ char *M_BuildConditionSetString(UINT8 unlockid) { start = i; max += 4; + maxatstart = max; } else if (message[i] == '\n') { - strlines = i; start = 0; max = 0; + maxatstart = 0; continue; } else if (message[i] & 0x80) @@ -1055,8 +1056,7 @@ char *M_BuildConditionSetString(UINT8 unlockid) if (max >= DESCRIPTIONWIDTH && start > 0) { message[start] = '\n'; - max -= (start-strlines)*8; - strlines = start; + max -= maxatstart; start = 0; } } diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 42591352d..605949b68 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -27,7 +27,7 @@ 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, strlines = 0, i; + size_t max = 0, maxatstart = 0, start = 0, strlines, i; static char *message = NULL; Z_Free(message); message = Z_StrDup(string); @@ -41,12 +41,13 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp { start = i; max += 4; + maxatstart = max; } else if (message[i] == '\n') { - strlines = i; start = 0; max = 0; + maxatstart = 0; continue; } else if (message[i] & 0x80) @@ -58,8 +59,7 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp if (max >= BASEVIDWIDTH && start > 0) { message[start] = '\n'; - max -= (start-strlines)*8; - strlines = start; + max -= maxatstart; start = 0; } } From 06bde8b40e9c8cfd8a3b73e0e0b448e9509de8b3 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 15:40:10 +0000 Subject: [PATCH 015/103] M_CheckCondition, conditiontype_t, etc: More fine-grained control over your condition string - UCRP_PREFIX_ISMAP: a prefix version of UCRP_ISMAP - The condition string for UC_EMBLEM has been updated to match other relevant prefixes. - UC_COMMA: a second string-relevance-only condition, for inserting a comma where only a human could find it natural --- src/deh_soc.c | 10 ++++++---- src/m_cond.c | 37 +++++++++++++++++++++++-------------- src/m_cond.h | 6 +++++- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d904bef02..5ff18f3bf 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2487,10 +2487,11 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) //PARAMCHECK(1); ty = UC_CRASH; } - else if (fastcmp(params[0], "AND")) + else if ((offset=0) || fastcmp(params[0], "AND") + || (++offset && fastcmp(params[0], "COMMA"))) { //PARAMCHECK(1); - ty = UC_AND; + ty = UC_AND + offset; } else if ((offset=0) || fastcmp(params[0], "PREFIX_GRANDPRIX") || (++offset && fastcmp(params[0], "PREFIX_BONUSROUND")) @@ -2501,10 +2502,11 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) //PARAMCHECK(1); ty = UCRP_PREFIX_GRANDPRIX + offset; } - else if (fastcmp(params[0], "ISMAP")) + else if ((offset=0) || fastcmp(params[0], "PREFIX_ISMAP") + || (++offset && fastcmp(params[0], "ISMAP"))) { PARAMCHECK(1); - ty = UCRP_ISMAP; + ty = UCRP_PREFIX_ISMAP + offset; re = G_MapNumber(params[1]); if (re >= nummapheaders) diff --git a/src/m_cond.c b/src/m_cond.c index 89c685336..e1e7f8432 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -623,7 +623,9 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) } return false; - case UC_AND: // Just for string building + // Just for string building + case UC_AND: + case UC_COMMA: return true; case UCRP_PREFIX_GRANDPRIX: @@ -637,6 +639,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) case UCRP_PREFIX_SEALEDSTAR: return (specialstageinfo.valid == true); + case UCRP_PREFIX_ISMAP: case UCRP_ISMAP: return (gamemap == cn->requirement+1); case UCRP_ISCHARACTER: @@ -844,7 +847,7 @@ static const char *M_GetConditionString(condition_t *cn) 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); + work = va("TIME ATTACK: Get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title); break; case ET_GLOBAL: { @@ -870,16 +873,16 @@ static const char *M_GetConditionString(condition_t *cn) if (emblemlocations[i].flags & GE_TIMED) { - work = va("Find %s%s%s in %s before %i:%02i.%02i", - astr, colorstr, medalstr, title, + work = va("%s: Find %s%s%s before %i:%02i.%02i", + title, astr, colorstr, medalstr, 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); + work = va("%s: Find %s%s%s", + title, astr, colorstr, medalstr); } break; } @@ -903,6 +906,8 @@ static const char *M_GetConditionString(condition_t *cn) case UC_AND: return "&"; + case UC_COMMA: + return ","; case UCRP_PREFIX_GRANDPRIX: return "GRAND PRIX:"; @@ -917,12 +922,20 @@ static const char *M_GetConditionString(condition_t *cn) case UCRP_PREFIX_SEALEDSTAR: return "SEALED STAR:"; + case UCRP_PREFIX_ISMAP: + 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:", title); + Z_Free(title); + return work; case UCRP_ISMAP: if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement]) return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement); title = BUILDCONDITIONTITLE(cn->requirement); - work = va("On %s,", title); + work = va("On %s", title); Z_Free(title); return work; case UCRP_ISCHARACTER: @@ -992,25 +1005,21 @@ char *M_BuildConditionSetString(UINT8 unlockid) { cn = &c->condition[i]; - if (i > 0) + if (i > 0 && (cn->type != UC_COMMA)) { if (lastID != cn->id) { worklen = 4; strncat(message, "\nOR ", len); } - else //if (cn->type >= UCRP_REQUIRESPLAYING) + else { worklen = 1; strncat(message, " ", len); } - /*else - { - worklen = 3; - strncat(message, "\n& ", len); - }*/ len -= worklen; } + lastID = cn->id; work = M_GetConditionString(cn); diff --git a/src/m_cond.h b/src/m_cond.h index 7a4931c94..59162f592 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -45,7 +45,9 @@ typedef enum UC_CRASH, // Hee ho ! - UC_AND, // Just for string building + // Just for string building + UC_AND, + UC_COMMA, UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL). @@ -55,7 +57,9 @@ typedef enum UCRP_PREFIX_BREAKTHECAPSULES, // BREAK THE CAPSULES: UCRP_PREFIX_SEALEDSTAR, // SEALED STAR: + UCRP_PREFIX_ISMAP, // name of [map]: UCRP_ISMAP, // gamemap == [map] + UCRP_ISCHARACTER, // character == [skin] UCRP_FINISHCOOL, // Finish in good standing From 4f8df124039cf59c1263f6f6eafcb48568d1d15e Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 15:59:25 +0000 Subject: [PATCH 016/103] M_PopulateChallengeGrid: Remove bad challengegrid when I_Erroring to prevent corrupted save --- src/m_cond.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index e1e7f8432..ce67bfe99 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -214,7 +214,11 @@ quickcheckagain: if (nummajorunlocks > 0) { - I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles (width %d)", nummajorunlocks, gamedata->challengegridwidth); + UINT16 widthtoprint = gamedata->challengegridwidth; + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + + I_Error("M_PopulateChallengeGrid: was not able to populate %d large tiles (width %d)", nummajorunlocks, widthtoprint); } } @@ -232,6 +236,10 @@ quickcheckagain: if (numunlocks > numempty) { + gamedata->challengegridwidth = 0; + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + I_Error("M_PopulateChallengeGrid: %d small unlocks vs %d empty spaces (%d gap)", numunlocks, numempty, (numunlocks-numempty)); } From 002de8c948f50cd92933b42fe390f8bc668a5589 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 16:00:45 +0000 Subject: [PATCH 017/103] G_LoadGameData: Regenerate challengegrid from previous minor versions --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index d63de1937..c9660da52 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4391,7 +4391,7 @@ void G_LoadGameData(void) P_SaveBufferFree(&save); I_Error("Game data is from the future! (expected %d, got %d)\nRename or delete %s (maybe in %s) and try again.", GD_VERSIONMINOR, versionMinor, gamedatafilename, gdfolder); } - if (versionMinor == 0) + if (versionMinor == 0 || versionMinor == 1) { gridunusable = true; } From 86030946647fc40dc3ca7157aafa1476d7550045 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 16:37:35 +0000 Subject: [PATCH 018/103] M_PopulateChallengeGrid: Attempt to recover from one major tile too many for the amount of space left, which is relatively trivial only in the height == 4 case. --- src/m_cond.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index ce67bfe99..0b6be081b 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -212,7 +212,53 @@ quickcheckagain: } } - if (nummajorunlocks > 0) +#if (CHALLENGEGRIDHEIGHT == 4) + if (nummajorunlocks == 1) + { + UINT8 unlocktomoveup = MAXUNLOCKABLES; + + j = gamedata->challengegridwidth-1; + + // Attempt to fix our whoopsie. + for (i = 0; i < j; i++) + { + if (gamedata->challengegrid[1 + (i*CHALLENGEGRIDHEIGHT)] != MAXUNLOCKABLES + && gamedata->challengegrid[(i*CHALLENGEGRIDHEIGHT)] == MAXUNLOCKABLES) + break; + } + + if (i == j) + { + UINT16 widthtoprint = gamedata->challengegridwidth; + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + + I_Error("M_PopulateChallengeGrid: was not able to populate one large tile even after trying again (width %d)", widthtoprint); + } + + unlocktomoveup = gamedata->challengegrid[1 + (i*CHALLENGEGRIDHEIGHT)]; + + if (i == 0 + && challengegridloops + && (gamedata->challengegrid [1 + (j*CHALLENGEGRIDHEIGHT)] + == gamedata->challengegrid[1])) + ; + else + { + j = i + 1; + } + + // Push one pair up. + gamedata->challengegrid[(i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[(j*CHALLENGEGRIDHEIGHT)] = unlocktomoveup; + // Wedge the remaining four underneath. + gamedata->challengegrid[2 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[2 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][0]; + gamedata->challengegrid[3 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[3 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][0]; + + nummajorunlocks = 0; + } + else +#endif + if (nummajorunlocks > 0) { UINT16 widthtoprint = gamedata->challengegridwidth; Z_Free(gamedata->challengegrid); From 74336efc74d3f45a32bfb4054afc676a3da2d865 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 16:38:01 +0000 Subject: [PATCH 019/103] UC_CRASH: Adjust condition string to handle newlines better --- 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 0b6be081b..55dbe98a6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -955,7 +955,7 @@ static const char *M_GetConditionString(condition_t *cn) : "???"); case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) - return "Relaunch \"Dr. Robotnik's Ring Racers\" after a crash"; + return "Launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; return NULL; case UC_AND: From eeb6e4090940600e08a7c5dad5e0c06d23ec3f1e Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 3 Mar 2023 22:35:28 +0000 Subject: [PATCH 020/103] Add totalrings to gamedata - Added to Statistics screen - Caps at "999,999,999+". - Controls UC_TOTALRINGS condition - Improve description for UC_MATCHESPLAYED condition - Says "Rounds" now - Improve Statistics text for playtime - Dynamically changes visible units of time (seconds, minutes, hours, days.......) --- src/deh_soc.c | 12 ++++++++ src/g_game.c | 12 +++++--- src/k_menudraw.c | 50 ++++++++++++++++++++++++++++---- src/m_cond.c | 10 ++++++- src/m_cond.h | 5 ++++ src/menus/options-data-erase-1.c | 2 ++ src/p_user.c | 5 ++++ 7 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 5ff18f3bf..42dde0a2f 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2370,6 +2370,18 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) ty = UC_PLAYTIME + offset; re = atoi(params[1]); } + else if (fastcmp(params[0], "TOTALRINGS")) + { + PARAMCHECK(1); + ty = UC_TOTALRINGS; + re = atoi(params[1]); + + if (re < 2 || re > GDMAX_RINGS) + { + deh_warning("Total Rings requirement %d out of range (%d - %d) for condition ID %d", re, 2, GDMAX_RINGS, id+1); + return; + } + } else if (fastcmp(params[0], "POWERLEVEL")) { PARAMCHECK(2); diff --git a/src/g_game.c b/src/g_game.c index c9660da52..80a187cfc 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4346,12 +4346,13 @@ void G_LoadGameData(void) // Clear things so previously read gamedata doesn't transfer // to new gamedata + // see also M_EraseDataResponse G_ClearRecords(); // records M_ClearSecrets(); // emblems, unlocks, maps visited, etc - gamedata->totalplaytime = 0; // total play time (separate from all) - gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished - gamedata->crashflags = 0; + gamedata->totalplaytime = 0; + gamedata->matchesplayed = 0; + gamedata->totalrings = 0; if (M_CheckParm("-nodata")) { @@ -4401,6 +4402,8 @@ void G_LoadGameData(void) if (versionMinor > 1) { + gamedata->totalrings = READUINT32(save.p); + gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) gamedata->crashflags |= GDCRASH_ANY; @@ -4571,7 +4574,7 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+4+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4590,6 +4593,7 @@ void G_SaveGameData(boolean dirty) WRITEUINT8(save.p, GD_VERSIONMINOR); // 1 WRITEUINT32(save.p, gamedata->totalplaytime); // 4 WRITEUINT32(save.p, gamedata->matchesplayed); // 4 + WRITEUINT32(save.p, gamedata->totalrings); // 4 { UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 49fd7a8d7..bb6786cec 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5471,7 +5471,7 @@ bottomarrow: void M_DrawStatistics(void) { - char beststr[40]; + char beststr[256]; tic_t besttime = 0; @@ -5483,12 +5483,48 @@ void M_DrawStatistics(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } + beststr[0] = 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))); + besttime = G_TicsToHours(gamedata->totalplaytime); + if (besttime) + { + if (besttime >= 24) + { + strcat(beststr, va("%u days, ", besttime/24)); + besttime %= 24; + } + + strcat(beststr, va("%u hours, ", besttime)); + } + besttime = G_TicsToMinutes(gamedata->totalplaytime, false); + if (besttime) + { + strcat(beststr, va("%u minutes, ", besttime)); + } + strcat(beststr, va("%i seconds", G_TicsToSeconds(gamedata->totalplaytime))); + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 22, V_6WIDTHSPACE, beststr); + beststr[0] = 0; + + V_DrawThinString(20, 32, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Rings:"); + if (gamedata->totalrings > GDMAX_RINGS) + { + sprintf(beststr, "%c999,999,999+", '\x82'); + } + else if (gamedata->totalrings >= 1000000) + { + sprintf(beststr, "%u,%u,%u", (gamedata->totalrings/1000000), (gamedata->totalrings/1000)%1000, (gamedata->totalrings%1000)); + } + else if (gamedata->totalrings >= 1000) + { + sprintf(beststr, "%u,%u", (gamedata->totalrings/1000), (gamedata->totalrings%1000)); + } + else + { + sprintf(beststr, "%u", gamedata->totalrings); + } + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 32, V_6WIDTHSPACE, va("%s collected", beststr)); + beststr[0] = 0; + V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, va("%i played", gamedata->matchesplayed)); @@ -5498,6 +5534,8 @@ void M_DrawStatistics(void) return; } + besttime = 0; + for (i = 0; i < nummapheaders; i++) { if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))) diff --git a/src/m_cond.c b/src/m_cond.c index 55dbe98a6..f043d8454 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -624,6 +624,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (gamedata->totalplaytime >= (unsigned)cn->requirement); case UC_MATCHESPLAYED: // Requires any level completed >= x times return (gamedata->matchesplayed >= (unsigned)cn->requirement); + case UC_TOTALRINGS: // Requires grabbing >= x rings + return (gamedata->totalrings >= (unsigned)cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype { UINT8 i; @@ -832,7 +834,13 @@ static const char *M_GetConditionString(condition_t *cn) 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); + return va("Play %d Rounds", cn->requirement); + case UC_TOTALRINGS: // Requires collecting >= x rings + if (cn->requirement >= 1000000) + return va("Collect %u,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); + if (cn->requirement >= 1000) + return va("Collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000)); + return va("Collect %u Rings", 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) diff --git a/src/m_cond.h b/src/m_cond.h index 59162f592..f19f9c93b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -30,6 +30,7 @@ typedef enum { UC_PLAYTIME, // PLAYTIME [tics] UC_MATCHESPLAYED, // SRB2Kart: MATCHESPLAYED [x played] + UC_TOTALRINGS, // TOTALRINGS [x collected] UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] UC_GAMECLEAR, // GAMECLEAR UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] @@ -187,6 +188,9 @@ typedef enum #define GDCRASH_ANY 0x02 #define GDCRASH_LOSERCLUB 0x04 +// This is the largest number of 9s that will fit in UINT32. +#define GDMAX_RINGS 999999999 + // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat struct gamedata_t @@ -215,6 +219,7 @@ struct gamedata_t // PLAY TIME UINT32 totalplaytime; UINT32 matchesplayed; + UINT32 totalrings; // Funny UINT8 crashflags; diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index f40e106c6..d301f29f4 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -53,11 +53,13 @@ static void M_EraseDataResponse(INT32 ch) S_StartSound(NULL, sfx_itrole); // bweh heh heh // Delete the data + // see also G_LoadGameData if (optionsmenu.erasecontext == 2) { // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets gamedata->totalplaytime = 0; gamedata->matchesplayed = 0; + gamedata->totalrings = 0; } if (optionsmenu.erasecontext != 1) G_ClearRecords(); diff --git a/src/p_user.c b/src/p_user.c index b3c605159..ba3481130 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -505,6 +505,11 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode return 0; + if (gamedata && num_rings > 0 && P_IsLocalPlayer(player) && gamedata->totalrings <= GDMAX_RINGS) + { + gamedata->totalrings += num_rings; + } + test = player->rings + num_rings; if (test > 20) // Caps at 20 rings, sorry! num_rings -= (test-20); From 22f9467e71688376dafb2b373c8f324e69e489b4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Mar 2023 17:33:56 +0000 Subject: [PATCH 021/103] M_ClearConditionSet: fix memory leak --- src/m_cond.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index f043d8454..f41f348c6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -511,9 +511,14 @@ void M_ClearConditionSet(UINT8 set) { if (conditionSets[set].numconditions) { + while (conditionSets[set].numconditions > 0) + { + --conditionSets[set].numconditions; + Z_Free(conditionSets[set].condition[conditionSets[set].numconditions].pendingstring); + } + Z_Free(conditionSets[set].condition); conditionSets[set].condition = NULL; - conditionSets[set].numconditions = 0; } gamedata->achieved[set] = false; } From e994b920c69c819a0e62f488f695e00f9ccd5451 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Mar 2023 20:09:29 +0000 Subject: [PATCH 022/103] gamedata->roundsplayed: Split into multiple roughly-gametype-aligned categories - Race, Capsule, Battle, Special, Custom - All categories can now be used for Rounds Played condition - UCRP_SEALEDSTAR now hides if you haven't beaten a single Sealed Star yet In addition, introduce M_ClearStats - As more statistics get added, clearing them manually in G_LoadGameData and M_EraseDataResponse is just going to get annoying - Change around the options on the Erase Data screen to - Make it clear that erasing all game data won't clear your Profiles - Add an option to clear stats by themselves, rather than only permitting via complete gamedata wipe - Move to the "Challenges" terminology over SRB2's "Secrets" terminology - Move some entries that were previously handled in M_ClearSecrets into M_ClearStats --- src/deh_soc.c | 35 +++++++++++++- src/g_game.c | 35 ++++++++++---- src/k_menudraw.c | 26 +++++++++-- src/m_cond.c | 80 ++++++++++++++++++++++++++++---- src/m_cond.h | 14 +++++- src/menus/options-data-erase-1.c | 54 +++++++++++---------- 6 files changed, 197 insertions(+), 47 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 42dde0a2f..7298491e7 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2363,13 +2363,44 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } - if ((offset=0) || fastcmp(params[0], "PLAYTIME") - || (++offset && fastcmp(params[0], "MATCHESPLAYED"))) + if (fastcmp(params[0], "PLAYTIME")) { PARAMCHECK(1); ty = UC_PLAYTIME + offset; re = atoi(params[1]); } + else if (fastcmp(params[0], "ROUNDSPLAYED")) + { + PARAMCHECK(1); + ty = UC_ROUNDSPLAYED; + re = atoi(params[1]); + x1 = GDGT_MAX; + + if (re == 0) + { + deh_warning("Rounds played requirement is %d for condition ID %d", re, id+1); + return; + } + + if (params[2]) + { + if (fastcmp(params[2], "RACE")) + x1 = GDGT_RACE; + else if (fastcmp(params[2], "BATTLE")) + x1 = GDGT_BATTLE; + else if (fastcmp(params[2], "CAPSULE")) + x1 = GDGT_CAPSULES; + else if (fastcmp(params[2], "SPECIAL")) + x1 = GDGT_SPECIAL; + else if (fastcmp(params[2], "CUSTOM")) + x1 = GDGT_CUSTOM; + else + { + deh_warning("gametype requirement \"%s\" invalid for condition ID %d", params[2], id+1); + return; + } + } + } else if (fastcmp(params[0], "TOTALRINGS")) { PARAMCHECK(1); diff --git a/src/g_game.c b/src/g_game.c index 80a187cfc..597624021 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3994,8 +3994,18 @@ static void G_DoCompleted(void) if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { + UINT8 roundtype = GDGT_CUSTOM; + + if (gametype == GT_RACE) + roundtype = GDGT_RACE; + else if (gametype == GT_BATTLE) + roundtype = (battlecapsules ? GDGT_CAPSULES : GDGT_BATTLE); + else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) + roundtype = GDGT_SPECIAL; + + gamedata->roundsplayed[roundtype]++; + // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve - gamedata->matchesplayed++; M_UpdateUnlockablesAndExtraEmblems(true); gamedata->deferredsave = true; } @@ -4348,12 +4358,9 @@ void G_LoadGameData(void) // to new gamedata // see also M_EraseDataResponse G_ClearRecords(); // records + M_ClearStats(); // statistics M_ClearSecrets(); // emblems, unlocks, maps visited, etc - gamedata->totalplaytime = 0; - gamedata->matchesplayed = 0; - gamedata->totalrings = 0; - if (M_CheckParm("-nodata")) { // Don't load at all. @@ -4398,16 +4405,24 @@ void G_LoadGameData(void) } gamedata->totalplaytime = READUINT32(save.p); - gamedata->matchesplayed = READUINT32(save.p); if (versionMinor > 1) { gamedata->totalrings = READUINT32(save.p); + for (i = 0; i < GDGT_MAX; i++) + { + gamedata->roundsplayed[i] = READUINT32(save.p); + } + gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) gamedata->crashflags |= GDCRASH_ANY; } + else + { + save.p += 4; // no direct equivalent to matchesplayed + } { // Quick & dirty hash for what mod this save file is for. @@ -4574,7 +4589,7 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+4+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+(4*GDGT_MAX)+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4592,9 +4607,13 @@ void G_SaveGameData(boolean dirty) 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, gamedata->totalrings); // 4 + for (i = 0; i < GDGT_MAX; i++) // 4 * GDGT_MAX + { + WRITEUINT32(save.p, gamedata->roundsplayed[i]); + } + { UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); if (dirty) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index bb6786cec..750903ee7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5523,10 +5523,30 @@ void M_DrawStatistics(void) sprintf(beststr, "%u", gamedata->totalrings); } V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 32, V_6WIDTHSPACE, va("%s collected", beststr)); - beststr[0] = 0; - V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Matches:"); - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, va("%i played", gamedata->matchesplayed)); + beststr[0] = 0; + V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Rounds:"); + + strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE])); + + if (gamedata->roundsplayed[GDGT_CAPSULES] > 0) + { + strcat(beststr, va(", %u Capsule", gamedata->roundsplayed[GDGT_CAPSULES])); + } + + strcat(beststr, va(", %u Battle", gamedata->roundsplayed[GDGT_BATTLE])); + + if (gamedata->roundsplayed[GDGT_SPECIAL] > 0) + { + strcat(beststr, va(", %u Special", gamedata->roundsplayed[GDGT_SPECIAL])); + } + + if (gamedata->roundsplayed[GDGT_CUSTOM] > 0) + { + strcat(beststr, va(", %u Custom", gamedata->roundsplayed[GDGT_CUSTOM])); + } + + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, beststr); if (!statisticsmenu.maplist) { diff --git a/src/m_cond.c b/src/m_cond.c index f41f348c6..b57908bb1 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -524,6 +524,17 @@ void M_ClearConditionSet(UINT8 set) } // Clear ALL secrets. +void M_ClearStats(void) +{ + UINT8 i; + gamedata->totalplaytime = 0; + gamedata->totalrings = 0; + for (i = 0; i < GDGT_MAX; ++i) + gamedata->roundsplayed[i] = 0; + gamedata->timesBeaten = 0; + gamedata->crashflags = 0; +} + void M_ClearSecrets(void) { INT32 i; @@ -544,9 +555,6 @@ void M_ClearSecrets(void) gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; - gamedata->timesBeaten = 0; - gamedata->crashflags = 0; - // Re-unlock any always unlocked things M_UpdateUnlockablesAndExtraEmblems(false); } @@ -627,8 +635,22 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) { case UC_PLAYTIME: // Requires total playing time >= x return (gamedata->totalplaytime >= (unsigned)cn->requirement); - case UC_MATCHESPLAYED: // Requires any level completed >= x times - return (gamedata->matchesplayed >= (unsigned)cn->requirement); + case UC_ROUNDSPLAYED: // Requires any level completed >= x times + { + if (cn->extrainfo1 == GDGT_MAX) + { + UINT8 i; + UINT32 sum = 0; + + for (i = 0; i < GDGT_MAX; i++) + { + sum += gamedata->roundsplayed[i]; + } + + return (sum >= (unsigned)cn->requirement); + } + return (gamedata->roundsplayed[cn->extrainfo1] >= (unsigned)cn->requirement); + } case UC_TOTALRINGS: // Requires grabbing >= x rings return (gamedata->totalrings >= (unsigned)cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype @@ -834,33 +856,70 @@ static const char *M_GetConditionString(condition_t *cn) 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 Rounds", cn->requirement); + + case UC_ROUNDSPLAYED: // Requires any level completed >= x times + + if (cn->extrainfo1 == GDGT_MAX) + work = ""; + else if (cn->extrainfo1 != GDGT_RACE + && cn->extrainfo1 != GDGT_BATTLE + && cn->extrainfo1 != GDGT_CUSTOM + && gamedata->roundsplayed[cn->extrainfo1] == 0) + work = " ???"; + else switch (cn->extrainfo1) + { + case GDGT_RACE: + work = " Race"; + break; + case GDGT_CAPSULES: + work = " Capsule"; + break; + case GDGT_BATTLE: + work = " Battle"; + break; + case GDGT_SPECIAL: + work = " Special"; + break; + case GDGT_CUSTOM: + work = " custom gametype"; + break; + default: + return va("INVALID GAMETYPE CONDITION \"%d:%d:%d\"", cn->type, cn->extrainfo1, cn->requirement); + } + + return va("Play %d%s Round%s", cn->requirement, work, + (cn->requirement == 1 ? "" : "s")); + case UC_TOTALRINGS: // Requires collecting >= x rings if (cn->requirement >= 1000000) return va("Collect %u,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); if (cn->requirement >= 1000) return va("Collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000)); return va("Collect %u Rings", 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 @@ -876,6 +935,7 @@ static const char *M_GetConditionString(condition_t *cn) Z_Free(title); return work; } + case UC_MAPTIME: // Requires time on map <= x { if (cn->extrainfo1 >= nummapheaders || !mapheaderinfo[cn->extrainfo1]) @@ -890,8 +950,10 @@ static const char *M_GetConditionString(condition_t *cn) Z_Free(title); return work; } + case UC_TOTALMEDALS: // Requires number of emblems >= x return va("Get %d medals", cn->requirement); + case UC_EMBLEM: // Requires emblem x to be obtained { INT32 checkLevel; @@ -987,7 +1049,9 @@ static const char *M_GetConditionString(condition_t *cn) case UCRP_PREFIX_BREAKTHECAPSULES: return "BREAK THE CAPSULES:"; case UCRP_PREFIX_SEALEDSTAR: - return "SEALED STAR:"; + if (gamedata->roundsplayed[GDGT_SPECIAL] == 0) + return NULL; + return "SEALED STARS:"; case UCRP_PREFIX_ISMAP: if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement]) diff --git a/src/m_cond.h b/src/m_cond.h index f19f9c93b..65fee4632 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -29,7 +29,7 @@ extern "C" { typedef enum { UC_PLAYTIME, // PLAYTIME [tics] - UC_MATCHESPLAYED, // SRB2Kart: MATCHESPLAYED [x played] + UC_ROUNDSPLAYED, // ROUNDSPLAYED [x played] UC_TOTALRINGS, // TOTALRINGS [x collected] UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] UC_GAMECLEAR, // GAMECLEAR @@ -191,6 +191,15 @@ typedef enum // This is the largest number of 9s that will fit in UINT32. #define GDMAX_RINGS 999999999 +typedef enum { + GDGT_RACE, + GDGT_BATTLE, + GDGT_CAPSULES, + GDGT_SPECIAL, + GDGT_CUSTOM, + GDGT_MAX +} roundsplayed_t; + // GAMEDATA STRUCTURE // Everything that would get saved in gamedata.dat struct gamedata_t @@ -218,7 +227,7 @@ struct gamedata_t // PLAY TIME UINT32 totalplaytime; - UINT32 matchesplayed; + UINT32 roundsplayed[GDGT_MAX]; UINT32 totalrings; // Funny @@ -267,6 +276,7 @@ void M_UpdateConditionSetsPending(void); // Clearing secrets void M_ClearConditionSet(UINT8 set); void M_ClearSecrets(void); +void M_ClearStats(void); // Updating conditions and unlockables boolean M_CheckCondition(condition_t *cn, player_t *player); diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index d301f29f4..f3a977f7f 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -6,27 +6,31 @@ #include "../m_cond.h" // Condition Sets #include "../f_finale.h" +#define EC_CHALLENGES 0x01 +#define EC_STATISTICS 0x02 +#define EC_TIMEATTACK 0x04 +#define EC_ALLGAME (EC_CHALLENGES|EC_STATISTICS|EC_TIMEATTACK) + menuitem_t OPTIONS_DataErase[] = { + {IT_STRING | IT_CALL, "Erase Challenges Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, EC_CHALLENGES, 0}, + + {IT_STRING | IT_CALL, "Erase Statistics Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, EC_STATISTICS, 0}, {IT_STRING | IT_CALL, "Erase Time Attack Data", "Be careful! What's deleted is gone forever!", - NULL, {.routine = M_EraseData}, 0, 0}, + NULL, {.routine = M_EraseData}, EC_TIMEATTACK, 0}, - {IT_STRING | IT_CALL, "Erase Unlockable Data", "Be careful! What's deleted is gone forever!", - NULL, {.routine = M_EraseData}, 0, 0}, + {IT_STRING | IT_CALL, "\x85\x45rase all Game Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, EC_ALLGAME, 0}, {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CALL, "Erase Profile Data...", "Select a Profile to erase.", + {IT_STRING | IT_CALL, "Erase a Profile...", "Select a Profile to erase.", NULL, {.routine = M_CheckProfileData}, 0, 0}, - {IT_SPACE | IT_NOTHING, NULL, NULL, - NULL, {NULL}, 0, 0}, - - {IT_STRING | IT_CALL, "\x85\x45rase all Data", "Be careful! What's deleted is gone forever!", - NULL, {.routine = M_EraseData}, 0, 0}, - }; menu_t OPTIONS_DataEraseDef = { @@ -54,16 +58,12 @@ static void M_EraseDataResponse(INT32 ch) // Delete the data // see also G_LoadGameData - if (optionsmenu.erasecontext == 2) - { - // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets - gamedata->totalplaytime = 0; - gamedata->matchesplayed = 0; - gamedata->totalrings = 0; - } - if (optionsmenu.erasecontext != 1) + // We do these in backwards order to prevent things from being immediately re-unlocked. + if (optionsmenu.erasecontext & EC_TIMEATTACK) G_ClearRecords(); - if (optionsmenu.erasecontext != 0) + if (optionsmenu.erasecontext & EC_STATISTICS) + M_ClearStats(); + if (optionsmenu.erasecontext & EC_CHALLENGES) M_ClearSecrets(); F_StartIntro(); @@ -73,15 +73,21 @@ static void M_EraseDataResponse(INT32 ch) void M_EraseData(INT32 choice) { const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n"); + (void)choice; - optionsmenu.erasecontext = (UINT8)choice; + optionsmenu.erasecontext = (UINT8)currentMenu->menuitems[itemOn].mvar1; - if (choice == 0) + if (optionsmenu.erasecontext == EC_CHALLENGES) + eschoice = M_GetText("Challenges data"); + else if (optionsmenu.erasecontext == EC_STATISTICS) + eschoice = M_GetText("Statistics data"); + else if (optionsmenu.erasecontext == EC_TIMEATTACK) eschoice = M_GetText("Time Attack data"); - else if (choice == 1) - eschoice = M_GetText("Secrets data"); - else + else if (optionsmenu.erasecontext == EC_ALLGAME) eschoice = M_GetText("ALL game data"); + else + eschoice = va("[misconfigured erasecontext %d]", optionsmenu.erasecontext); + M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO); } From f7617e7530c8d8dab948b44f1723a7dc42855eae Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Mar 2023 21:09:59 +0000 Subject: [PATCH 023/103] Clear records, stats, and secrets on new in-SOC gamedata creation - Pre-emptive sanity check to some impending work - Move M_UpdateUnlockablesAndExtraEmblems out of M_ClearSecrets --- src/deh_soc.c | 4 ++++ src/g_game.c | 10 ++++++---- src/m_cond.c | 3 --- src/menus/options-data-erase-1.c | 2 ++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 7298491e7..86bf76c80 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2768,6 +2768,10 @@ void readmaincfg(MYFILE *f, boolean mainfile) clear_emblems(); //clear_levels(); doClearLevels = true; + + G_ClearRecords(); + M_ClearStats(); + M_ClearSecrets(); } else if (!mainfile && !gamedataadded) { diff --git a/src/g_game.c b/src/g_game.c index 73e60a491..499d8b78e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4371,21 +4371,21 @@ void G_LoadGameData(void) if (M_CheckParm("-nodata")) { // Don't load at all. + // The following used to be in M_ClearSecrets, but that was silly. + M_UpdateUnlockablesAndExtraEmblems(false); return; } if (M_CheckParm("-resetdata")) { // Don't load, but do save. (essentially, reset) - gamedata->loaded = true; - return; + goto finalisegamedata; } if (P_SaveBufferFromFile(&save, va(pandf, srb2home, gamedatafilename)) == false) { // No gamedata. We can save a new one. - gamedata->loaded = true; - return; + goto finalisegamedata; } // Version check @@ -4550,6 +4550,8 @@ void G_LoadGameData(void) // done P_SaveBufferFree(&save); +finalisegamedata: + // Don't consider loaded until it's a success! // 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, diff --git a/src/m_cond.c b/src/m_cond.c index b57908bb1..27a88b10f 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -554,9 +554,6 @@ void M_ClearSecrets(void) Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; - - // Re-unlock any always unlocked things - M_UpdateUnlockablesAndExtraEmblems(false); } // ---------------------- diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index f3a977f7f..b5af7eade 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -66,6 +66,8 @@ static void M_EraseDataResponse(INT32 ch) if (optionsmenu.erasecontext & EC_CHALLENGES) M_ClearSecrets(); + M_UpdateUnlockablesAndExtraEmblems(false); + F_StartIntro(); M_ClearMenus(true); } From 1ae5df651deb3cacc6ad7e1d5cc5889a06e6b86c Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 4 Mar 2023 21:36:58 +0000 Subject: [PATCH 024/103] UC_ADDON - If you add a file (and you've unlocked Addons on non-DEVELOP builds) this gets achieved - Also, Custom # of Rounds Played conditions will show "Custom" only if Addons are loaded --- src/deh_soc.c | 5 +++-- src/g_game.c | 6 +++++- src/m_cond.c | 19 ++++++++++++++++--- src/m_cond.h | 4 +++- src/w_wad.c | 9 +++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 86bf76c80..9f796ec7a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2525,10 +2525,11 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } - else if (fastcmp(params[0], "CRASH")) + else if ((offset=0) || fastcmp(params[0], "ADDON") + || (++offset && fastcmp(params[0], "CRASH"))) { //PARAMCHECK(1); - ty = UC_CRASH; + ty = UC_ADDON + offset; } else if ((offset=0) || fastcmp(params[0], "AND") || (++offset && fastcmp(params[0], "COMMA"))) diff --git a/src/g_game.c b/src/g_game.c index 499d8b78e..4f24a8caa 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4425,6 +4425,8 @@ void G_LoadGameData(void) gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) gamedata->crashflags |= GDCRASH_ANY; + + gamedata->everloadedaddon = (boolean)READUINT8(save.p); } else { @@ -4598,7 +4600,7 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+(4*GDGT_MAX)+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+(4*GDGT_MAX)+1+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4630,6 +4632,8 @@ void G_SaveGameData(boolean dirty) WRITEUINT8(save.p, crashflags); // 1 } + WRITEUINT8(save.p, gamedata->everloadedaddon); // 1 + WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); // To save space, use one bit per collected/achieved/unlocked flag diff --git a/src/m_cond.c b/src/m_cond.c index 27a88b10f..5b69ba1d3 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -532,6 +532,8 @@ void M_ClearStats(void) for (i = 0; i < GDGT_MAX; ++i) gamedata->roundsplayed[i] = 0; gamedata->timesBeaten = 0; + + gamedata->everloadedaddon = false; gamedata->crashflags = 0; } @@ -695,6 +697,13 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return gamedata->unlocked[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved return M_Achieved(cn->requirement-1); + + case UC_ADDON: + return ( +#ifndef DEVELOP + M_SecretUnlocked(SECRET_ADDONS, true) && +#endif + (gamedata->everloadedaddon == true)); case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) { @@ -863,9 +872,8 @@ static const char *M_GetConditionString(condition_t *cn) if (cn->extrainfo1 == GDGT_MAX) work = ""; - else if (cn->extrainfo1 != GDGT_RACE - && cn->extrainfo1 != GDGT_BATTLE - && cn->extrainfo1 != GDGT_CUSTOM + else if (cn->extrainfo1 != GDGT_RACE && cn->extrainfo1 != GDGT_BATTLE // Base gametypes + && (cn->extrainfo1 != GDGT_CUSTOM || M_SecretUnlocked(SECRET_ADDONS, true) == false) // Custom is visible at 0 if addons are unlocked && gamedata->roundsplayed[cn->extrainfo1] == 0) work = " ???"; else switch (cn->extrainfo1) @@ -1025,6 +1033,11 @@ static const char *M_GetConditionString(condition_t *cn) gamedata->unlocked[cn->requirement-1] ? unlockables[cn->requirement-1].name : "???"); + + case UC_ADDON: + if (!M_SecretUnlocked(SECRET_ADDONS, true) && !gamedata->everloadedaddon) + return NULL; + return "Load a custom addon into \"Dr. Robotnik's Ring Racers\""; case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) return "Launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; diff --git a/src/m_cond.h b/src/m_cond.h index 65fee4632..1c1009932 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -44,6 +44,7 @@ typedef enum UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] + UC_ADDON, // Ever loaded a custom file? UC_CRASH, // Hee ho ! // Just for string building @@ -230,7 +231,8 @@ struct gamedata_t UINT32 roundsplayed[GDGT_MAX]; UINT32 totalrings; - // Funny + // SPECIFIC SPECIAL EVENTS + boolean everloadedaddon; UINT8 crashflags; }; diff --git a/src/w_wad.c b/src/w_wad.c index ad630a6fb..e0c19e7e5 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -49,6 +49,7 @@ #include "fastcmp.h" #include "g_game.h" // G_LoadGameData +#include "m_cond.h" // gamedata itself #include "filesrch.h" #include "i_video.h" // rendermode @@ -813,6 +814,14 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) } #endif + // Do this immediately before anything of consequence that invalidates gamedata can happen. + if ((mainfile == false) && (gamedata != NULL) && (gamedata->everloadedaddon == false)) + { + gamedata->everloadedaddon = true; + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); + } + switch(type = ResourceFileDetect(filename)) { case RET_SOC: From 0c75c4006030d9e127b18f6727851ec0e5a506ca Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 18:06:09 +0000 Subject: [PATCH 025/103] Rework how Bots handle their skin availabilities Before we can add extra unlock features, we need to make sure we're not building on a house of sand. - R_SkinUsable: Use Net Unlock system if playernum is -1 - R_BotDefaultSkin: Move to r_skins.c, cache skin search - R_GetSkinAvailabilities: Use Net Unlock when called for bots (and always permit R_BotDefaultSkin) - Got_AddBot: Call R_GetSkinAvailabilities for summoned bots to guarantee sync status of available skins - K_UpdateMatchRaceBots: Tidy up to match grand prix bot skin selection system, hiding server-locked skins and defaulting to R_BotDefaultSkin if you don't have enough unlocked for the remaining player slots --- src/d_clisrv.c | 16 ++++-------- src/g_demo.c | 2 +- src/k_bot.c | 64 ++++++++++++++++++++--------------------------- src/k_grandprix.c | 23 ++--------------- src/k_grandprix.h | 10 -------- src/r_skins.c | 39 ++++++++++++++++++++++++----- src/r_skins.h | 3 ++- 7 files changed, 70 insertions(+), 87 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 76470b865..43df8ade3 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -824,7 +824,7 @@ 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)); + memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false, false), MAXAVAILABILITY*sizeof(UINT8)); return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak)); } @@ -3723,7 +3723,7 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) static void Got_AddBot(UINT8 **p, INT32 playernum) { INT16 newplayernum; - UINT8 i, skinnum = 0; + UINT8 skinnum = 0; UINT8 difficulty = DIFFICULTBOT; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) @@ -3753,14 +3753,8 @@ 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]; - } - } + // this will permit unlocks + memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8)); players[newplayernum].splitscreenindex = 0; players[newplayernum].bot = true; @@ -3936,7 +3930,7 @@ 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); + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false, false); SINT8 node = 0; for (; node < MAXNETNODES; node++) result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring); diff --git a/src/g_demo.c b/src/g_demo.c index 174511e4d..11b1414aa 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2239,7 +2239,7 @@ static void G_SaveDemoSkins(UINT8 **pp) { char skin[16]; UINT8 i; - UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true); + UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true, false); WRITEUINT8((*pp), numskins); for (i = 0; i < numskins; i++) diff --git a/src/k_bot.c b/src/k_bot.c index 56b9a8c1f..fa3635a94 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -108,13 +108,15 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) --------------------------------------------------*/ void K_UpdateMatchRaceBots(void) { + const UINT8 defaultbotskin = R_BotDefaultSkin(); const UINT8 difficulty = cv_kartbot.value; UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value); UINT8 numplayers = 0; UINT8 numbots = 0; UINT8 numwaiting = 0; SINT8 wantedbots = 0; - boolean skinusable[MAXSKINS]; + UINT8 usableskins = 0; + UINT8 grabskins[MAXSKINS+1]; UINT8 i; if (!server) @@ -122,18 +124,12 @@ void K_UpdateMatchRaceBots(void) return; } - // init usable bot skins list - for (i = 0; i < MAXSKINS; i++) + // Init usable bot skins list + for (i = 0; i < numskins; i++) { - if (i < numskins) - { - skinusable[i] = true; - } - else - { - skinusable[i] = false; - } + grabskins[usableskins++] = i; } + grabskins[usableskins] = MAXSKINS; if (cv_maxplayers.value > 0) { @@ -146,7 +142,7 @@ void K_UpdateMatchRaceBots(void) { if (!players[i].spectator) { - skinusable[players[i].skin] = false; + grabskins[players[i].skin] = MAXSKINS; if (players[i].bot) { @@ -185,48 +181,42 @@ void K_UpdateMatchRaceBots(void) { // We require MORE bots! UINT8 newplayernum = 0; - boolean usedallskins = false; if (dedicated) { newplayernum = 1; } + // Rearrange usable bot skins list to prevent gaps for randomised selection + for (i = 0; i < usableskins; i++) + { + if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true))) + continue; + while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true))) + { + usableskins--; + } + grabskins[i] = grabskins[usableskins]; + grabskins[usableskins] = MAXSKINS; + } + while (numbots < wantedbots) { - UINT8 skin = M_RandomKey(numskins); + UINT8 skinnum = defaultbotskin; - if (usedallskins == false) + if (usableskins > 0) { - UINT8 loops = 0; - - while (!skinusable[skin]) - { - if (loops >= numskins) - { - // no more skins, stick to our first choice - usedallskins = true; - break; - } - - skin++; - - if (skin >= numskins) - { - skin = 0; - } - - loops++; - } + UINT8 index = M_RandomKey(usableskins); + skinnum = grabskins[index]; + grabskins[index] = grabskins[--usableskins]; } - if (!K_AddBot(skin, difficulty, &newplayernum)) + if (!K_AddBot(skinnum, difficulty, &newplayernum)) { // Not enough player slots to add the bot, break the loop. break; } - skinusable[skin] = false; numbots++; } } diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 6d879054b..ef7a4cd0f 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -95,25 +95,6 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) return points; } -/*-------------------------------------------------- - UINT8 K_BotDefaultSkin(void) - - See header file for description. ---------------------------------------------------*/ -UINT8 K_BotDefaultSkin(void) -{ - const char *defaultbotskinname = "eggrobo"; - INT32 defaultbotskin = R_SkinAvailable(defaultbotskinname); - - if (defaultbotskin == -1) - { - // This shouldn't happen, but just in case - defaultbotskin = 0; - } - - return (UINT8)defaultbotskin; -} - /*-------------------------------------------------- void K_InitGrandPrixBots(void) @@ -121,7 +102,7 @@ UINT8 K_BotDefaultSkin(void) --------------------------------------------------*/ void K_InitGrandPrixBots(void) { - const UINT8 defaultbotskin = K_BotDefaultSkin(); + const UINT8 defaultbotskin = R_BotDefaultSkin(); const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); UINT8 difficultylevels[MAXPLAYERS]; @@ -519,7 +500,7 @@ void K_IncreaseBotDifficulty(player_t *bot) --------------------------------------------------*/ void K_RetireBots(void) { - const UINT8 defaultbotskin = K_BotDefaultSkin(); + const UINT8 defaultbotskin = R_BotDefaultSkin(); SINT8 newDifficulty; UINT8 usableskins; diff --git a/src/k_grandprix.h b/src/k_grandprix.h index a862245c7..b9d426317 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -71,16 +71,6 @@ UINT8 K_BotStartingDifficulty(SINT8 value); INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); -/*-------------------------------------------------- - UINT8 K_BotDefaultSkin(void); - - Returns the skin number of the skin the game - uses as a fallback option. ---------------------------------------------------*/ - -UINT8 K_BotDefaultSkin(void); - - /*-------------------------------------------------- void K_InitGrandPrixBots(void); diff --git a/src/r_skins.c b/src/r_skins.c index 1f4daba5f..b7fa36ebb 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -129,6 +129,27 @@ static void Sk_SetDefaultValue(skin_t *skin) skin->soundsid[S_sfx[i].skinsound] = i; } +// Grab the default skin +UINT8 R_BotDefaultSkin(void) +{ + static INT32 defaultbotskin = -1; + + if (defaultbotskin == -1) + { + const char *defaultbotskinname = "eggrobo"; + + defaultbotskin = R_SkinAvailable(defaultbotskinname); + + if (defaultbotskin == -1) + { + // This shouldn't happen, but just in case + defaultbotskin = 0; + } + } + + return (UINT8)defaultbotskin; +} + // // Initialize the basic skins // @@ -159,11 +180,12 @@ void R_InitSkins(void) M_UpdateConditionSetsPending(); } -UINT8 *R_GetSkinAvailabilities(boolean demolock) +UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots) { UINT8 i, shif, byte; INT32 skinid; static UINT8 responsebuffer[MAXAVAILABILITY]; + UINT8 defaultbotskin = R_BotDefaultSkin(); memset(&responsebuffer, 0, sizeof(responsebuffer)); @@ -172,15 +194,17 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock) if (unlockables[i].type != SECRET_SKIN) continue; - // NEVER EVER EVER M_CheckNetUnlockByID - if (gamedata->unlocked[i] != true && !demolock) - continue; - skinid = M_UnlockableSkinNum(&unlockables[i]); if (skinid < 0 || skinid >= MAXSKINS) continue; + if ((forbots + ? (M_CheckNetUnlockByID(i) || skinid == defaultbotskin) // Assert the host's lock. + : gamedata->unlocked[i]) // Assert the local lock. + != true && !demolock) + continue; + shif = (skinid % 8); byte = (skinid / 8); @@ -251,8 +275,11 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) return !!(players[playernum].availabilities[byte] & (1 << shif)); } + // Use the host's if it's checking general state + if (playernum == -1) + return M_CheckNetUnlockByID(i); + // 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]); } diff --git a/src/r_skins.h b/src/r_skins.h index fe29282e7..40a95b61a 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -91,8 +91,9 @@ 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); +UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots); INT32 R_SkinAvailable(const char *name); +UINT8 R_BotDefaultSkin(void); void R_PatchSkins(UINT16 wadnum, boolean mainfile); void R_AddSkins(UINT16 wadnum, boolean mainfile); From b005649d718f4dbb20dbef43e8fa45a8270eaf67 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 18:13:39 +0000 Subject: [PATCH 026/103] Copyright notices update regarding the author of this commit - For cross-file consistency in the author of this commit's name and username - To note that the author of this commit performed significant work on code that would eventually be compiled into r_skins.c on several occasions since 2016 up to the present day - To catch work performed by the author of this commit in 2023 --- src/k_boss.c | 4 ++-- src/k_boss.h | 4 ++-- src/m_cond.c | 2 +- src/m_cond.h | 2 +- src/r_skins.c | 1 + src/r_skins.h | 1 + 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/k_boss.c b/src/k_boss.c index ff10231ea..7b0a8fe96 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- -// Copyright (C) 2018-2022 by Viv "toaster" Grannell -// Copyright (C) 2018-2022 by Kart Krew +// Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell +// Copyright (C) 2018-2023 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_boss.h b/src/k_boss.h index 2ce43adea..8e41e0cdf 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- -// Copyright (C) 2018-2022 by Viv "toaster" Grannell -// Copyright (C) 2018-2022 by Kart Krew +// Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell +// Copyright (C) 2018-2023 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_cond.c b/src/m_cond.c index 5b69ba1d3..e0f6edce4 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1,6 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- -// Copyright (C) 2022-2023 by Vivian "toaster" Grannell. +// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. // Copyright (C) 2012-2020 by Sonic Team Junior. // diff --git a/src/m_cond.h b/src/m_cond.h index 1c1009932..4181ff9bf 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -1,6 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- -// Copyright (C) 2022-2023 by Vivian "toaster" Grannell. +// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. // Copyright (C) 2012-2020 by Sonic Team Junior. // diff --git a/src/r_skins.c b/src/r_skins.c index b7fa36ebb..317729b16 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- +// Copyright (C) 2016-2023 by Vivian "toastergrl" Grannell // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2020 by Sonic Team Junior. diff --git a/src/r_skins.h b/src/r_skins.h index 40a95b61a..692c91619 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- +// Copyright (C) 2016-2023 by Vivian "toastergrl" Grannell // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2020 by Sonic Team Junior. From 80555e373830785e480a60efa78c821b08a5fd92 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 19:51:33 +0000 Subject: [PATCH 027/103] P_Ticker: Do not evaluate interesting player conditions during introtime --- src/p_tick.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index bf44ca38a..900cb4ffc 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -632,8 +632,8 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; // TODO would this be laggy with more conditions in play... - if ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) - || (gamedata && gamedata->deferredsave)) + if ((leveltime > introtime) && ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) + || (gamedata && gamedata->deferredsave))) G_SaveGameData(true); } From 99b5fe7bf425d79787a5524866747348d5b89cdb Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 19:56:52 +0000 Subject: [PATCH 028/103] M_EraseData: Fix va() being passed to va() --- src/menus/options-data-erase-1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index b5af7eade..15c727b05 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -88,7 +88,7 @@ void M_EraseData(INT32 choice) else if (optionsmenu.erasecontext == EC_ALLGAME) eschoice = M_GetText("ALL game data"); else - eschoice = va("[misconfigured erasecontext %d]", optionsmenu.erasecontext); + eschoice = "[misconfigured erasecontext]"; M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO); From 87b80a62f2b2963351f9b51a5484dd4f14d8555e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 20:48:31 +0000 Subject: [PATCH 029/103] SPB Attack: Integrate into `conditions-cascading` affairs - All `cv_dummyspbattack`-related material is now gated with the assistance of SECRET_SPBATTACK. - SPB_ATTACK - Add UC_MAPSPBATTACK condition. - Both this and emblems with MV_SPBATTACK have the string "SPB ATTACK: Conquer [LEVEL NAME]", compared to the regular round completion conditions having "Finish a round on [LEVEL NAME]". - If SPBATTACK is not unlocked, shows ???: Conquer [LEVEL NAME] instead --- src/deh_soc.c | 5 +++- src/k_menudraw.c | 28 +++++++++++++++++--- src/m_cond.c | 34 ++++++++++++++++++++++--- src/m_cond.h | 2 ++ src/menus/play-local-race-time-attack.c | 14 +++++++--- src/menus/transient/level-select.c | 5 ++-- 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 9f796ec7a..d3686f6c7 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2290,6 +2290,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_BREAKTHECAPSULES; else if (fastcmp(word2, "SPECIALATTACK")) unlockables[num].type = SECRET_SPECIALATTACK; + else if (fastcmp(word2, "SPBATTACK")) + unlockables[num].type = SECRET_SPBATTACK; else if (fastcmp(word2, "ONLINE")) unlockables[num].type = SECRET_ONLINE; else if (fastcmp(word2, "ADDONS")) @@ -2445,7 +2447,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) } else if ((offset=0) || fastcmp(params[0], "MAPVISITED") || (++offset && fastcmp(params[0], "MAPBEATEN")) - || (++offset && fastcmp(params[0], "MAPENCORE"))) + || (++offset && fastcmp(params[0], "MAPENCORE")) + || (++offset && fastcmp(params[0], "MAPSPBATTACK"))) { PARAMCHECK(1); ty = UC_MAPVISITED + offset; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 2ff0301c1..ebe9a6b17 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2384,7 +2384,7 @@ void M_DrawTimeAttack(void) K_drawKartTimestamp(timerec, 162+t, timeheight+6, 0, 1); // SPB Attack control hint + menu overlay - if (levellist.newgametype == GT_RACE && levellist.levelsearch.timeattack == true) + if (levellist.newgametype == GT_RACE && levellist.levelsearch.timeattack == true && M_SecretUnlocked(SECRET_SPBATTACK, true)) { const UINT8 anim_duration = 16; const UINT8 anim = (timeattackmenu.ticker % (anim_duration * 2)) < anim_duration; @@ -2397,10 +2397,9 @@ void M_DrawTimeAttack(void) else V_DrawScaledPatch(buttonx + 35, buttony - 3, V_SNAPTOLEFT, W_CachePatchName("TLB_IB", PU_CACHE)); - if (timeattackmenu.ticker > (timeattackmenu.spbflicker + TICRATE/6) || timeattackmenu.ticker % 2) + if ((timeattackmenu.spbflicker == 0 || timeattackmenu.ticker % 2) == (cv_dummyspbattack.value == 1)) { - if (cv_dummyspbattack.value) - V_DrawMappedPatch(buttonx + 7, buttony - 1, 0, W_CachePatchName("K_SPBATK", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE)); + V_DrawMappedPatch(buttonx + 7, buttony - 1, 0, W_CachePatchName("K_SPBATK", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE)); } } @@ -4760,6 +4759,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili case SECRET_TIMEATTACK: case SECRET_BREAKTHECAPSULES: case SECRET_SPECIALATTACK: + case SECRET_SPBATTACK: categoryid = '7'; break; } @@ -4845,6 +4845,9 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili case SECRET_SPECIALATTACK: iconid = 9; break; + case SECRET_SPBATTACK: + iconid = 0; // TEMPORARY + break; default: { @@ -5062,6 +5065,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = sscmapcache; break; } + case SECRET_SPBATTACK: + { + static UINT16 spbmapcache = NEXTMAP_INVALID; + if (spbmapcache > nummapheaders) + { + spbmapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL); + } + specialmap = spbmapcache; + break; + } case SECRET_HARDSPEED: { static UINT16 hardmapcache = NEXTMAP_INVALID; @@ -5124,6 +5137,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) V_DrawFixedPatch((x+40)<type == SECRET_SPBATTACK) + { + V_DrawFixedPatch((x+40-25)<type == SECRET_HARDSPEED) { V_DrawFixedPatch((x+40-25)<type == UC_MAPBEATEN) mvtype = MV_BEATEN; else if (cn->type == UC_MAPENCORE) mvtype = MV_ENCORE; + else if (cn->type == UC_MAPSPBATTACK) + mvtype = MV_SPBATTACK; return ((cn->requirement < nummapheaders) && (mapheaderinfo[cn->requirement]) @@ -928,13 +931,29 @@ static const char *M_GetConditionString(condition_t *cn) 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 + case UC_MAPSPBATTACK: // Requires map x to be beaten in SPB Attack { + const char *prefix = ""; + 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" : "Finish a round on", + + if (cn->type == UC_MAPSPBATTACK) + prefix = (M_SecretUnlocked(SECRET_SPBATTACK, true) ? "SPB ATTACK: " : "???: "); + else if (cn->type == UC_MAPENCORE) + prefix = (M_SecretUnlocked(SECRET_ENCORE, true) ? "ENCORE MODE: " : "???: "); + + work = "Finish a round on"; + if (cn->type == UC_MAPVISITED) + work = "Visit"; + else if (cn->type == UC_MAPSPBATTACK) + work = "Conquer"; + + work = va("%s%s %s%s", + prefix, + work, title, (cn->type == UC_MAPENCORE) ? " in Encore Mode" : ""); Z_Free(title); @@ -973,7 +992,16 @@ static const char *M_GetConditionString(condition_t *cn) switch (emblemlocations[i].type) { case ET_MAP: - work = va("Beat %s", title); + work = ""; + if (emblemlocations[i].flags & ME_SPBATTACK) + work = (M_SecretUnlocked(SECRET_SPBATTACK, true) ? "SPB ATTACK: " : "???: "); + else if (emblemlocations[i].flags & ME_ENCORE) + work = (M_SecretUnlocked(SECRET_ENCORE, true) ? "ENCORE MODE: " : "???: "); + + work = va("%s%s %s", + work, + (emblemlocations[i].flags & ME_SPBATTACK) ? "Conquer" : "Finish a round on", + title); break; case ET_TIME: if (emblemlocations[i].color <= 0 || emblemlocations[i].color >= numskincolors) diff --git a/src/m_cond.h b/src/m_cond.h index 0b51b0130..bf826f210 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -37,6 +37,7 @@ typedef enum UC_MAPVISITED, // MAPVISITED [map] UC_MAPBEATEN, // MAPBEATEN [map] UC_MAPENCORE, // MAPENCORE [map] + UC_MAPSPBATTACK, // MAPSPBATTACK [map] UC_MAPTIME, // MAPTIME [map] [time to beat, tics] UC_TRIGGER, // TRIGGER [trigger number] UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] @@ -160,6 +161,7 @@ typedef enum SECRET_TIMEATTACK, // Permit Time attack SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) + SECRET_SPBATTACK, // Permit SPB mode of Time attack // Option restrictions SECRET_ONLINE, // Permit netplay (ankle-high barrier to jumping in the deep end) diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index abdff5762..4c51a2a6c 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -9,6 +9,7 @@ #include "../d_main.h" // srb2home #include "../m_misc.h" // M_MkdirEach #include "../z_zone.h" // Z_StrDup/Z_Free +#include "../m_cond.h" static void CV_SPBAttackChanged(void) { @@ -21,7 +22,11 @@ struct timeattackmenu_s timeattackmenu; void M_TimeAttackTick(void) { - timeattackmenu.ticker++; + timeattackmenu.ticker++; + if (timeattackmenu.spbflicker > 0) + { + timeattackmenu.spbflicker--; + } } boolean M_TimeAttackInputs(INT32 ch) @@ -30,10 +35,10 @@ boolean M_TimeAttackInputs(INT32 ch) const boolean buttonR = M_MenuButtonPressed(pid, MBT_R); (void) ch; - if (buttonR && levellist.newgametype == GT_RACE) + if (buttonR && levellist.newgametype == GT_RACE && M_SecretUnlocked(SECRET_SPBATTACK, true)) { CV_AddValue(&cv_dummyspbattack, 1); - timeattackmenu.spbflicker = timeattackmenu.ticker; + timeattackmenu.spbflicker = TICRATE/6; if (cv_dummyspbattack.value) { S_StartSound(NULL, sfx_s3k9f); @@ -231,6 +236,9 @@ void M_PrepareTimeAttack(INT32 choice) } } + if (levellist.levelsearch.timeattack == false || levellist.newgametype != GT_RACE || !M_SecretUnlocked(SECRET_SPBATTACK, true)) + CV_SetValue(&cv_dummyspbattack, 0); + // Time-sticker Medals G_UpdateTimeStickerMedals(levellist.choosemap, false); diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 20442ceb6..a8f4203af 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -227,12 +227,11 @@ boolean M_LevelListFromGametype(INT16 gt) levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); + CV_SetValue(&cv_dummyspbattack, 0); + first = false; } - if (levellist.levelsearch.timeattack == false || levellist.newgametype != GT_RACE) - CV_SetValue(&cv_dummyspbattack, 0); - // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. From 60968b2f08c68df4ecf371618278e4b1b6adff79 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 20:59:56 +0000 Subject: [PATCH 030/103] UC_AND, UC_COMMA: Fix not combining with UCRP_REQUIRESPLAYING Fixes --- src/m_cond.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 6e0700cc0..5285d5835 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -797,7 +797,11 @@ static boolean M_CheckConditionSet(conditionset_t *c, player_t *player) return true; // Skip future conditions with the same ID if one fails, for obvious reasons - else if (lastID && lastID == cn->id && !achievedSoFar) + if (lastID && lastID == cn->id && !achievedSoFar) + continue; + + // Skip entries that are JUST for string building + if (cn->type == UC_AND || cn->type == UC_COMMA) continue; lastID = cn->id; From 1e67f75f14cd737cb08506a8155f8692429db0a8 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 5 Mar 2023 21:00:27 +0000 Subject: [PATCH 031/103] P_Ticker: Adjust bracketing of Gamedata save conditional to handle deferred saves even during intro turnaround --- src/p_tick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index 900cb4ffc..6fd4dfcd4 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -632,7 +632,7 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; // TODO would this be laggy with more conditions in play... - if ((leveltime > introtime) && ((!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) + if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true)) || (gamedata && gamedata->deferredsave))) G_SaveGameData(true); } From a3554455924ba3a568419a41abddb99b3ba62fd2 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 20:11:45 +0000 Subject: [PATCH 032/103] condition_t: Make `pendingstring` into `stringvar` in anticipation of second, less angry application --- src/deh_soc.c | 6 +++--- src/m_cond.c | 19 ++++++++++--------- src/m_cond.h | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d3686f6c7..8ace8c162 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2338,7 +2338,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) INT32 i; char *params[4]; // condition, requirement, extra info, extra info char *spos; - char *pendingstring = NULL; + char *stringvar = NULL; conditiontype_t ty; INT32 re = 0; @@ -2578,7 +2578,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) } #else { - pendingstring = Z_StrDup(params[1]); + stringvar = Z_StrDup(params[1]); re = -1; } #endif @@ -2623,7 +2623,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } - M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, pendingstring); + M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, stringvar); } void readconditionset(MYFILE *f, UINT8 setnum) diff --git a/src/m_cond.c b/src/m_cond.c index 5285d5835..f1ff2220f 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -485,7 +485,7 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata) } } -void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *pendingstring) +void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar) { condition_t *cond; UINT32 num, wnum; @@ -504,7 +504,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1 cond[wnum].requirement = r; cond[wnum].extrainfo1 = x1; cond[wnum].extrainfo2 = x2; - cond[wnum].pendingstring = pendingstring; + cond[wnum].stringvar = stringvar; } void M_ClearConditionSet(UINT8 set) @@ -514,7 +514,7 @@ void M_ClearConditionSet(UINT8 set) while (conditionSets[set].numconditions > 0) { --conditionSets[set].numconditions; - Z_Free(conditionSets[set].condition[conditionSets[set].numconditions].pendingstring); + Z_Free(conditionSets[set].condition[conditionSets[set].numconditions].stringvar); } Z_Free(conditionSets[set].condition); @@ -577,28 +577,29 @@ void M_UpdateConditionSetsPending(void) for (j = 0; j < c->numconditions; ++j) { cn = &c->condition[j]; - if (cn->pendingstring == NULL) + if (cn->stringvar == NULL) continue; switch (cn->type) { case UCRP_ISCHARACTER: { - cn->requirement = R_SkinAvailable(cn->pendingstring); + cn->requirement = R_SkinAvailable(cn->stringvar); if (cn->requirement < 0) { - CONS_Alert(CONS_WARNING, "UCRP_ISCHARACTER: Invalid character %s for condition ID %d", cn->pendingstring, cn->id+1); + CONS_Alert(CONS_WARNING, "UCRP_ISCHARACTER: Invalid character %s for condition ID %d", cn->stringvar, cn->id+1); return; } + + Z_Free(cn->stringvar); + cn->stringvar = NULL; + break; } default: break; } - - Z_Free(cn->pendingstring); - cn->pendingstring = NULL; } diff --git a/src/m_cond.h b/src/m_cond.h index bf826f210..ef183734a 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -88,7 +88,7 @@ struct condition_t INT32 requirement; /// <- The requirement for this variable. INT16 extrainfo1; /// <- Extra information for the condition when needed. INT16 extrainfo2; /// <- Extra information for the condition when needed. - char *pendingstring; /// oooohhh my god i hate loading order for SOC VS skins + char *stringvar; /// <- Extra z-allocated string for the condition when needed }; struct conditionset_t { @@ -275,7 +275,7 @@ 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, char *pendingstring); +void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar); void M_UpdateConditionSetsPending(void); // Clearing secrets From d540921f783c24693fbd0a36f6ac14b6952e79ff Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 22:18:12 +0000 Subject: [PATCH 033/103] M_PopulateChallengeGrid: Since I actually got a scenario where two tiles weren't able to be populated, just do the rejiggering fix in a while loop. --- src/m_cond.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index f1ff2220f..aedc2583e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -213,7 +213,7 @@ quickcheckagain: } #if (CHALLENGEGRIDHEIGHT == 4) - if (nummajorunlocks == 1) + while (nummajorunlocks > 0) { UINT8 unlocktomoveup = MAXUNLOCKABLES; @@ -229,11 +229,7 @@ quickcheckagain: if (i == j) { - UINT16 widthtoprint = gamedata->challengegridwidth; - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = NULL; - - I_Error("M_PopulateChallengeGrid: was not able to populate one large tile even after trying again (width %d)", widthtoprint); + break; } unlocktomoveup = gamedata->challengegrid[1 + (i*CHALLENGEGRIDHEIGHT)]; @@ -248,17 +244,17 @@ quickcheckagain: j = i + 1; } + nummajorunlocks--; + // Push one pair up. gamedata->challengegrid[(i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[(j*CHALLENGEGRIDHEIGHT)] = unlocktomoveup; // Wedge the remaining four underneath. - gamedata->challengegrid[2 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[2 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][0]; - gamedata->challengegrid[3 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[3 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][0]; - - nummajorunlocks = 0; + gamedata->challengegrid[2 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[2 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][nummajorunlocks]; + gamedata->challengegrid[3 + (i*CHALLENGEGRIDHEIGHT)] = gamedata->challengegrid[3 + (j*CHALLENGEGRIDHEIGHT)] = selection[1][nummajorunlocks]; } - else #endif - if (nummajorunlocks > 0) + + if (nummajorunlocks > 0) { UINT16 widthtoprint = gamedata->challengegridwidth; Z_Free(gamedata->challengegrid); From baeb48ca1f9168ea7e85a6a6093f7730894d5e81 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 22:31:35 +0000 Subject: [PATCH 034/103] roundconditions_t - State tracking for events which occour mid-match and don't stay that way - Exists on every player struct to simplify writes, but A) not netsynced and B) only checked for local players - Updated in the relevant locations - no centralised ticking at the moment - Has a number of new associated conditions that require playing (UCRP's). - The following require [True/False] as supplementary information. - FallOff - TouchOffroad - TouchSneakerPanel - RingDebt - The following have no supplementary information because they're universally a specific achievement. - TripwireHyuu - SPBNeuter - LandmineDunk - HitMidair - The following has specific requirements that can be set. - WetPlayer [name of fluid] - Append "Strict" to forbid even skimming the surface of the map's fluid. --- src/d_player.h | 16 +++++++++++++ src/deh_soc.c | 39 +++++++++++++++++++++++++++++++ src/g_game.c | 11 +++++++++ src/k_collide.c | 5 ++++ src/k_kart.c | 10 ++++++++ src/m_cond.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ src/m_cond.h | 12 ++++++++++ src/p_inter.c | 20 +++++++++++++++- src/p_mobj.c | 4 ++++ src/p_user.c | 5 ++++ src/typedef.h | 1 + 11 files changed, 184 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index 98dee8e9a..303013da1 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -333,6 +333,21 @@ struct botvars_t tic_t spindashconfirm; // When high enough, they will try spindashing }; +// player_t struct for round-specific condition tracking + +struct roundconditions_t +{ + // Trivial Yes/no events across multiple UCRP's + boolean fell_off; + boolean touched_offroad; + boolean touched_sneakerpanel; + boolean debt_rings; + boolean tripwire_hyuu; + boolean spb_neuter; + boolean landmine_dunk; + boolean hit_midair; +}; + // player_t struct for all skybox variables struct skybox_t { mobj_t * viewpoint; @@ -682,6 +697,7 @@ struct player_t #endif sonicloopvars_t loop; + roundconditions_t roundconditions; }; #ifdef __cplusplus diff --git a/src/deh_soc.c b/src/deh_soc.c index 8ace8c162..9c853e0ab 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2617,6 +2617,45 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + else if ((offset=0) || fastcmp(params[0], "FALLOFF") + || (++offset && fastcmp(params[0], "TOUCHOFFROAD")) + || (++offset && fastcmp(params[0], "TOUCHSNEAKERPANEL")) + || (++offset && fastcmp(params[0], "RINGDEBT"))) + { + PARAMCHECK(1); + ty = UCRP_FALLOFF + offset; + re = 1; + + if (params[1][0] == 'F' || params[1][0] == 'N' || params[1][0] == '0') + re = 0; + } + else if ((offset=0) || fastcmp(params[0], "TRIPWIREHYUU") + || (++offset && fastcmp(params[0], "SPBNEUTER")) + || (++offset && fastcmp(params[0], "LANDMINEDUNK")) + || (++offset && fastcmp(params[0], "HITMIDAIR"))) + { + //PARAMCHECK(1); + ty = UCRP_TRIPWIREHYUU + offset; + } + else if (fastcmp(params[0], "WETPLAYER")) + { + PARAMCHECK(1); + ty = UCRP_WETPLAYER; + re = MFE_UNDERWATER; + x1 = 1; + stringvar = Z_StrDup(params[1]); + + if (params[2]) + { + if (fastcmp(params[2], "STRICT")) + re |= MFE_TOUCHWATER; + else + { + deh_warning("liquid strictness requirement \"%s\" invalid for condition ID %d", params[2], id+1); + return; + } + } + } else { deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1); diff --git a/src/g_game.c b/src/g_game.c index adc0efd48..86efdd11c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2371,6 +2371,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 khudfault; INT32 kickstartaccel; + roundconditions_t roundconditions; + boolean saveroundconditions; + score = players[player].score; lives = players[player].lives; ctfteam = players[player].ctfteam; @@ -2461,6 +2464,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) khudfinish = 0; khudcardanimation = 0; starpostnum = 0; + + saveroundconditions = false; } else { @@ -2509,6 +2514,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) starpostnum = players[player].starpostnum; pflags |= (players[player].pflags & (PF_STASIS|PF_ELIMINATED|PF_NOCONTEST|PF_FAULT|PF_LOSTLIFE)); + + memcpy(&roundconditions, &players[player].roundconditions, sizeof (roundconditions)); + saveroundconditions = true; } if (!betweenmaps) @@ -2592,6 +2600,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); + if (saveroundconditions) + memcpy(&p->roundconditions, &roundconditions, sizeof (p->roundconditions)); + if (follower) P_RemoveMobj(follower); diff --git a/src/k_collide.c b/src/k_collide.c index ee773dfa3..cea3497c4 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -411,7 +411,12 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) // Banana snipe! if (t1->health > 1) + { + if (t1->target && t1->target->player) + t1->target->player->roundconditions.landmine_dunk = true; + S_StartSound(t2, sfx_bsnipe); + } if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) { diff --git a/src/k_kart.c b/src/k_kart.c index 5e9982648..05755dcbe 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1118,6 +1118,9 @@ static void K_UpdateOffroad(player_t *player) if (player->offroad > offroadstrength) player->offroad = offroadstrength; + + if (player->offroad > (2*offroadstrength) / TICRATE) + player->roundconditions.touched_offroad = true; } else player->offroad = 0; @@ -4065,7 +4068,11 @@ void K_ApplyTripWire(player_t *player, tripwirestate_t state) K_TumblePlayer(player, NULL, NULL); if (state == TRIPSTATE_PASSED) + { S_StartSound(player->mo, sfx_ssa015); + if (player->hyudorotimer > 0) + player->roundconditions.tripwire_hyuu = true; + } else if (state == TRIPSTATE_BLOCKED) { S_StartSound(player->mo, sfx_kc40); @@ -5731,6 +5738,9 @@ void K_DoSneaker(player_t *player, INT32 type) { const fixed_t intendedboost = FRACUNIT/2; + if (player->floorboost != 0) + player->roundconditions.touched_sneakerpanel = true; + if (player->floorboost == 0 || player->floorboost == 3) { const sfxenum_t normalsfx = sfx_cdfm01; diff --git a/src/m_cond.c b/src/m_cond.c index aedc2583e..c06fa5cc0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -593,6 +593,23 @@ void M_UpdateConditionSetsPending(void) break; } + + case UCRP_WETPLAYER: + { + if (cn->extrainfo1) + { + char *l; + + for (l = cn->stringvar; *l != '\0'; l++) + { + *l = tolower(*l); + } + + cn->extrainfo1 = 0; + } + break; + } + default: break; } @@ -773,6 +790,28 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && !K_CanChangeRules(false) // too easy to change cv_timelimit && player->realtime < timelimitintics && (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement); + + case UCRP_FALLOFF: + return (player->roundconditions.fell_off == (cn->requirement == 1)); + case UCRP_TOUCHOFFROAD: + return (player->roundconditions.touched_offroad == (cn->requirement == 1)); + case UCRP_TOUCHSNEAKERPANEL: + return (player->roundconditions.touched_sneakerpanel == (cn->requirement == 1)); + case UCRP_RINGDEBT: + return (!(gametyperules & GTR_SPHERES) && (player->roundconditions.debt_rings == (cn->requirement == 1))); + + case UCRP_TRIPWIREHYUU: + return (player->roundconditions.tripwire_hyuu); + case UCRP_SPBNEUTER: + return (player->roundconditions.spb_neuter); + case UCRP_LANDMINEDUNK: + return (player->roundconditions.landmine_dunk); + case UCRP_HITMIDAIR: + return (player->roundconditions.hit_midair); + + case UCRP_WETPLAYER: + return (((player->roundconditions.wet_player & cn->requirement) == 0) + || player->roundconditions.fell_off); // Levels with water tend to texture their pits as water too } return false; } @@ -1137,6 +1176,29 @@ static const char *M_GetConditionString(condition_t *cn) G_TicsToSeconds(cn->requirement), G_TicsToCentiseconds(cn->requirement)); + case UCRP_FALLOFF: + return (cn->requirement == 1) ? "fall off the course" : "without falling off"; + case UCRP_TOUCHOFFROAD: + return (cn->requirement == 1) ? "touch offroad" : "without touching any offroad"; + case UCRP_TOUCHSNEAKERPANEL: + return (cn->requirement == 1) ? "touch a Sneaker Panel" : "without touching any Sneaker Panels"; + case UCRP_RINGDEBT: + return (cn->requirement == 1) ? "go into Ring debt" : "without going into Ring debt"; + + case UCRP_TRIPWIREHYUU: + return "go through Tripwire after getting snared by Hyudoro"; + case UCRP_SPBNEUTER: + return "shock a Self Propelled Bomb into submission"; + case UCRP_LANDMINEDUNK: + return "dunk a Landmine on another racer's head"; + case UCRP_HITMIDAIR: + return "hit another racer with a projectile while you're both in the air"; + + case UCRP_WETPLAYER: + return va("without %s %s", + (cn->requirement & MFE_TOUCHWATER) ? "touching any" : "going into", + cn->stringvar); + default: break; } diff --git a/src/m_cond.h b/src/m_cond.h index ef183734a..e5e9e0412 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -75,6 +75,18 @@ typedef enum UCRP_FINISHTIME, // Finish <= [time, tics] UCRP_FINISHTIMEEXACT, // Finish == [time, tics] UCRP_FINISHTIMELEFT, // Finish with at least [time, tics] to spare + + UCRP_FALLOFF, // Fall off (or don't) + UCRP_TOUCHOFFROAD, // Touch offroad (or don't) + UCRP_TOUCHSNEAKERPANEL, // Either touch sneaker panel (or don't) + UCRP_RINGDEBT, // Go into debt (or don't) + + UCRP_TRIPWIREHYUU, // Go through tripwire with Hyudoro + UCRP_SPBNEUTER, // Kill an SPB with Lightning + UCRP_LANDMINEDUNK, // huh? you died? that's weird. all i did was try to hug you... + UCRP_HITMIDAIR, // Hit another player mid-air with a kartfielditem + + UCRP_WETPLAYER, // Touch [fluid] } conditiontype_t; // Condition Set information diff --git a/src/p_inter.c b/src/p_inter.c index 75f0ab487..576825002 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1940,6 +1940,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, { case DMG_DEATHPIT: // Respawn kill types + player->roundconditions.fell_off = true; K_DoIngameRespawn(player); return false; case DMG_SPECTATOR: @@ -2025,6 +2026,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { player_t *player; boolean force = false; + boolean spbpop = false; INT32 laglength = 6; @@ -2058,6 +2060,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da Obj_MonitorOnDamage(target, inflictor, damage); break; + case MT_SPB: + spbpop = (damagetype & DMG_TYPEMASK) == DMG_VOLTAGE; + if (spbpop && source && source->player) + { + source->player->roundconditions.spb_neuter = true; + } + break; + default: break; } @@ -2076,7 +2086,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (!force) { - if (!(target->type == MT_SPB && (damagetype & DMG_TYPEMASK) == DMG_VOLTAGE)) + if (!spbpop) { if (!(target->flags & MF_SHOOTABLE)) return false; // shouldn't happen... @@ -2130,6 +2140,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } } + if (inflictor && source && source->player) + { + if (P_IsKartFieldItem(source->type) + && target->player->airtime > TICRATE/2 + && source->player->airtime > TICRATE/2) + source->player->roundconditions.hit_midair = true; + } + // Instant-Death if ((damagetype & DMG_DEATHMASK)) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 8fe24ffed..9594935bc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3421,6 +3421,10 @@ void P_MobjCheckWater(mobj_t *mobj) { return; } + + p->roundconditions.wet_player |= (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER|MFE_GOOWATER)); + if (p->roundconditions.wet_player) + CONS_Printf("%u\n", p->roundconditions.wet_player); } if (mobj->flags & MF_APPLYTERRAIN) diff --git a/src/p_user.c b/src/p_user.c index 94e96a45b..2d374e060 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -518,6 +518,11 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) player->rings += num_rings; + if (player->rings < 0) + { + player->roundconditions.debt_rings = true; + } + return num_rings; } diff --git a/src/typedef.h b/src/typedef.h index 632684d1e..c3b404b03 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -44,6 +44,7 @@ TYPEDEF (discordRequest_t); // d_player.h TYPEDEF (respawnvars_t); TYPEDEF (botvars_t); +TYPEDEF (roundconditions_t); TYPEDEF (skybox_t); TYPEDEF (itemroulette_t); TYPEDEF (player_t); From f6a83d21614e5bf9d068990e0925ecffc832f7cc Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 22:32:02 +0000 Subject: [PATCH 035/103] M_GetConditionString: All non-allcaps messages have had their first letter lowercased Instead, M_BuildConditionSetString uppercases the first lowercase letter, so it's always (approaching) grammatical correctness. --- src/m_cond.c | 56 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index c06fa5cc0..0f42e0576 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -906,7 +906,7 @@ static const char *M_GetConditionString(condition_t *cn) { case UC_PLAYTIME: // Requires total playing time >= x - return va("Play for %i:%02i:%02i", + return va("play for %i:%02i:%02i", G_TicsToHours(cn->requirement), G_TicsToMinutes(cn->requirement, false), G_TicsToSeconds(cn->requirement)); @@ -940,30 +940,30 @@ static const char *M_GetConditionString(condition_t *cn) return va("INVALID GAMETYPE CONDITION \"%d:%d:%d\"", cn->type, cn->extrainfo1, cn->requirement); } - return va("Play %d%s Round%s", cn->requirement, work, + return va("play %d%s Round%s", cn->requirement, work, (cn->requirement == 1 ? "" : "s")); case UC_TOTALRINGS: // Requires collecting >= x rings if (cn->requirement >= 1000000) - return va("Collect %u,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); + return va("collect %u,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); if (cn->requirement >= 1000) - return va("Collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000)); - return va("Collect %u Rings", cn->requirement); + return va("collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000)); + return va("collect %u Rings", cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype - return va("Get a PWR of %d in %s", cn->requirement, + 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); + return va("beat game %d times", cn->requirement); else - return va("Beat the game"); + return va("beat the game"); case UC_OVERALLTIME: // Requires overall time <= x - return va("Get overall time of %i:%02i:%02i", + return va("get overall time of %i:%02i:%02i", G_TicsToHours(cn->requirement), G_TicsToMinutes(cn->requirement, false), G_TicsToSeconds(cn->requirement)); @@ -985,11 +985,11 @@ static const char *M_GetConditionString(condition_t *cn) else if (cn->type == UC_MAPENCORE) prefix = (M_SecretUnlocked(SECRET_ENCORE, true) ? "ENCORE MODE: " : "???: "); - work = "Finish a round on"; + work = "finish a round on"; if (cn->type == UC_MAPVISITED) - work = "Visit"; + work = "visit"; else if (cn->type == UC_MAPSPBATTACK) - work = "Conquer"; + work = "conquer"; work = va("%s%s %s%s", prefix, @@ -1006,7 +1006,7 @@ static const char *M_GetConditionString(condition_t *cn) 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, + work = va("beat %s in %i:%02i.%02i", title, G_TicsToMinutes(cn->requirement, true), G_TicsToSeconds(cn->requirement), G_TicsToCentiseconds(cn->requirement)); @@ -1016,7 +1016,7 @@ static const char *M_GetConditionString(condition_t *cn) } case UC_TOTALMEDALS: // Requires number of emblems >= x - return va("Get %d medals", cn->requirement); + return va("get %d medals", cn->requirement); case UC_EMBLEM: // Requires emblem x to be obtained { @@ -1040,7 +1040,7 @@ static const char *M_GetConditionString(condition_t *cn) work = va("%s%s %s", work, - (emblemlocations[i].flags & ME_SPBATTACK) ? "Conquer" : "Finish a round on", + (emblemlocations[i].flags & ME_SPBATTACK) ? "conquer" : "finish a round on", title); break; case ET_TIME: @@ -1049,7 +1049,7 @@ static const char *M_GetConditionString(condition_t *cn) Z_Free(title); return va("INVALID MEDAL COLOR \"%d:%d\"", cn->requirement, checkLevel); } - work = va("TIME ATTACK: Get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title); + work = va("TIME ATTACK: get the %s Medal for %s", skincolors[emblemlocations[i].color].name, title); break; case ET_GLOBAL: { @@ -1075,7 +1075,7 @@ static const char *M_GetConditionString(condition_t *cn) if (emblemlocations[i].flags & GE_TIMED) { - work = va("%s: Find %s%s%s before %i:%02i.%02i", + work = va("%s: find %s%s%s before %i:%02i.%02i", title, astr, colorstr, medalstr, G_TicsToMinutes(emblemlocations[i].var, true), G_TicsToSeconds(emblemlocations[i].var), @@ -1083,13 +1083,13 @@ static const char *M_GetConditionString(condition_t *cn) } else { - work = va("%s: Find %s%s%s", + work = va("%s: find %s%s%s", title, astr, colorstr, medalstr); } break; } default: - work = va("Find a secret in %s", title); + work = va("find a secret in %s", title); break; } @@ -1097,7 +1097,7 @@ static const char *M_GetConditionString(condition_t *cn) return work; } case UC_UNLOCKABLE: // Requires unlockable x to be obtained - return va("Get \"%s\"", + return va("get \"%s\"", gamedata->unlocked[cn->requirement-1] ? unlockables[cn->requirement-1].name : "???"); @@ -1105,10 +1105,10 @@ static const char *M_GetConditionString(condition_t *cn) case UC_ADDON: if (!M_SecretUnlocked(SECRET_ADDONS, true) && !gamedata->everloadedaddon) return NULL; - return "Load a custom addon into \"Dr. Robotnik's Ring Racers\""; + return "load a custom addon into \"Dr. Robotnik's Ring Racers\""; case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) - return "Launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; + return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; return NULL; case UC_AND: @@ -1133,7 +1133,7 @@ static const char *M_GetConditionString(condition_t *cn) case UCRP_PREFIX_ISMAP: if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement]) - return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement); + return va("INVALID MAP CONDITION \"%d:%d\":", cn->type, cn->requirement); title = BUILDCONDITIONTITLE(cn->requirement); work = va("%s:", title); @@ -1144,7 +1144,7 @@ static const char *M_GetConditionString(condition_t *cn) return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement); title = BUILDCONDITIONTITLE(cn->requirement); - work = va("On %s", title); + work = va("on %s", title); Z_Free(title); return work; case UCRP_ISCHARACTER: @@ -1302,6 +1302,14 @@ char *M_BuildConditionSetString(UINT8 unlockid) } } + for (i = 0; message[i]; i++) + { + if (message[i] == toupper(message[i])) + continue; + message[i] = toupper(message[i]); + break; + } + return message; } From ff926440d5598c0a9cedcc73a8b70cff46e26fb7 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 22:38:37 +0000 Subject: [PATCH 036/103] UCRP_WETPLAYER: Adjust SOC load to not Z_StrDup before confirming other data is valid, to prevent memory leak --- src/deh_soc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 9c853e0ab..08b1771e6 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2643,7 +2643,6 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) ty = UCRP_WETPLAYER; re = MFE_UNDERWATER; x1 = 1; - stringvar = Z_StrDup(params[1]); if (params[2]) { @@ -2655,6 +2654,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + + stringvar = Z_StrDup(params[1]); } else { From 98423a2196fa453c7a657ddf48b3ec40e4c46f20 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 22:57:53 +0000 Subject: [PATCH 037/103] UNSTAGED CHANGE: Missing `mobjeflag_t wet_player` from `roundconditions_t` --- src/d_player.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/d_player.h b/src/d_player.h index 303013da1..7b0911e45 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -346,6 +346,8 @@ struct roundconditions_t boolean spb_neuter; boolean landmine_dunk; boolean hit_midair; + + mobjeflag_t wet_player; }; // player_t struct for all skybox variables From 42a985d06182875b9d67b28e6909d201f66e9161 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 6 Mar 2023 23:18:22 +0000 Subject: [PATCH 038/103] readcondition: Support stringvar for extended condition instead of always having to not fit one in --- src/deh_soc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 08b1771e6..34b9ebefc 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2336,7 +2336,7 @@ void readunlockable(MYFILE *f, INT32 num) static void readcondition(UINT8 set, UINT32 id, char *word2) { INT32 i; - char *params[4]; // condition, requirement, extra info, extra info + char *params[5]; // condition, requirement, extra info, extra info, stringvar char *spos; char *stringvar = NULL; @@ -2348,7 +2348,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) spos = strtok(word2, " "); - for (i = 0; i < 4; ++i) + for (i = 0; i < 5; ++i) { if (spos != NULL) { From 4802f96249a1ef4b2cf0898268315f6044c214a1 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 7 Mar 2023 00:03:41 +0000 Subject: [PATCH 039/103] UCRP_TRIGGER: Convert to roundconditions_t - Previously was extern scope UINT32 for all players, but this permits one player in a netgame taking a secret path while others don't. - Attempted to make user-specifiable string, but while I can undo the effects of strtok for the condition, I cannot undo the effects of strupr - so it's disabled for now until we come up with a more robust and hopefully direct system. - Also removed some old SRB2-originated assumptions that you couldn't unlock anything in multiplayer from the unlocktrigger system. --- src/acs/call-funcs.cpp | 7 ++++-- src/d_player.h | 3 +++ src/deh_soc.c | 51 +++++++++++++++++++++++++++++++----------- src/g_game.c | 3 --- src/m_cond.c | 12 +++++----- src/m_cond.h | 5 ++--- src/p_spec.c | 18 +++++++-------- 7 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index b6e1ce3a8..6c17767a5 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1162,6 +1162,7 @@ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *ar { UINT8 id = 0; bool unlocked = false; + auto info = &static_cast(thread)->info; (void)argC; @@ -1171,9 +1172,11 @@ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *ar { CONS_Printf("Bad unlockable trigger ID %d\n", id); } - else + else if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) { - unlocked = (unlocktriggers & (1 << id)); + unlocked = (info->mo->player->roundconditions.unlocktriggers & (1 << id)); } thread->dataStk.push(unlocked); diff --git a/src/d_player.h b/src/d_player.h index 7b0911e45..12a0ca6eb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -348,6 +348,9 @@ struct roundconditions_t boolean hit_midair; mobjeflag_t wet_player; + + // 32 triggers, one bit each, for map execution + UINT32 unlocktriggers; }; // player_t struct for all skybox variables diff --git a/src/deh_soc.c b/src/deh_soc.c index 34b9ebefc..5519e7d67 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2346,6 +2346,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) INT32 offset = 0; +#if 0 + char *endpos = word2 + strlen(word2); +#endif + spos = strtok(word2, " "); for (i = 0; i < 5; ++i) @@ -2473,19 +2477,6 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } - else if (fastcmp(params[0], "TRIGGER")) - { - PARAMCHECK(1); - ty = UC_TRIGGER; - re = atoi(params[1]); - - // 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+1); - return; - } - } else if (fastcmp(params[0], "TOTALMEDALS")) { PARAMCHECK(1); @@ -2617,6 +2608,40 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + else if (fastcmp(params[0], "TRIGGER")) + { + PARAMCHECK(2); // strictly speaking at LEAST two + ty = UCRP_TRIGGER; + re = atoi(params[1]); + + // 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+1); + return; + } + + // The following undid the effects of strtok. + // Unfortunately, there is no way it can reasonably undo the effects of strupr. + // If we want custom descriptions for map execution triggers, we're gonna need a different method. +#if 0 + // undo affect of strtok + i = 5; + // so spos will still be the strtok from earlier + while (i >= 2) + { + if (!spos) + continue; + while (*spos != '\0') + spos++; + if (spos < endpos) + *spos = ' '; + spos = params[--i]; + } +#endif + + stringvar = Z_StrDup(params[2]); + } else if ((offset=0) || fastcmp(params[0], "FALLOFF") || (++offset && fastcmp(params[0], "TOUCHOFFROAD")) || (++offset && fastcmp(params[0], "TOUCHSNEAKERPANEL")) diff --git a/src/g_game.c b/src/g_game.c index 86efdd11c..59948388c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5096,9 +5096,6 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr } } - // Reset unlockable triggers - unlocktriggers = 0; - // clear itemfinder, just in case if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds CV_StealthSetValue(&cv_itemfinder, 0); diff --git a/src/m_cond.c b/src/m_cond.c index 0f42e0576..0f19f9d8f 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -35,10 +35,6 @@ gamedata_t *gamedata = NULL; boolean netUnlocked[MAXUNLOCKABLES]; -// Map triggers for linedef executors -// 32 triggers, one bit each -UINT32 unlocktriggers; - // The meat of this system lies in condition sets conditionset_t conditionSets[MAXCONDITIONSETS]; @@ -704,8 +700,6 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) } case UC_MAPTIME: // Requires time on map <= x return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); - case UC_TRIGGER: // requires map trigger set - return !!(unlocktriggers & (1 << cn->requirement)); case UC_TOTALMEDALS: // Requires number of emblems >= x return (M_GotEnoughMedals(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained @@ -791,6 +785,9 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && player->realtime < timelimitintics && (timelimitintics + extratimeintics + secretextratime - player->realtime) >= (unsigned)cn->requirement); + case UCRP_TRIGGER: // requires map trigger set + return !!(player->roundconditions.unlocktriggers & (1 << cn->requirement)); + case UCRP_FALLOFF: return (player->roundconditions.fell_off == (cn->requirement == 1)); case UCRP_TOUCHOFFROAD: @@ -1176,6 +1173,9 @@ static const char *M_GetConditionString(condition_t *cn) G_TicsToSeconds(cn->requirement), G_TicsToCentiseconds(cn->requirement)); + case UCRP_TRIGGER: + return cn->stringvar; + case UCRP_FALLOFF: return (cn->requirement == 1) ? "fall off the course" : "without falling off"; case UCRP_TOUCHOFFROAD: diff --git a/src/m_cond.h b/src/m_cond.h index e5e9e0412..6c1f131d4 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -39,7 +39,6 @@ typedef enum UC_MAPENCORE, // MAPENCORE [map] UC_MAPSPBATTACK, // MAPSPBATTACK [map] UC_MAPTIME, // MAPTIME [map] [time to beat, tics] - UC_TRIGGER, // TRIGGER [trigger number] UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] @@ -76,6 +75,8 @@ typedef enum UCRP_FINISHTIMEEXACT, // Finish == [time, tics] UCRP_FINISHTIMELEFT, // Finish with at least [time, tics] to spare + UCRP_TRIGGER, // Map execution trigger [id] + UCRP_FALLOFF, // Fall off (or don't) UCRP_TOUCHOFFROAD, // Touch offroad (or don't) UCRP_TOUCHSNEAKERPANEL, // Either touch sneaker panel (or don't) @@ -262,8 +263,6 @@ extern unlockable_t unlockables[MAXUNLOCKABLES]; extern INT32 numemblems; -extern UINT32 unlocktriggers; - void M_NewGameDataStruct(void); // Challenges menu stuff diff --git a/src/p_spec.c b/src/p_spec.c index 75ad1be38..c7452db2d 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1541,17 +1541,18 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller return false; break; case 317: + if (actor && actor->player) { // Unlockable triggers required INT32 trigid = triggerline->args[1]; - if ((modifiedgame && !savemoddata) || (netgame || multiplayer)) - return false; - else if (trigid < 0 || trigid > 31) // limited by 32 bit variable + if (trigid < 0 || trigid > 31) // limited by 32 bit variable { CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid); return false; } - else if (!(unlocktriggers & (1 << trigid))) + else if (!(actor && actor->player)) + return false; + else if (!(actor->player->roundconditions.unlocktriggers & (1 << trigid))) return false; } break; @@ -3296,26 +3297,25 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha break; case 441: // Trigger unlockable - if (!(demo.playback || netgame || multiplayer)) { INT32 trigid = args[0]; if (trigid < 0 || trigid > 31) // limited by 32 bit variable CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger: bad trigger ID %d\n", trigid); - else + else if (mo && mo->player) { UINT32 flag = 1 << trigid; - if (unlocktriggers & flag) + if (mo->player->roundconditions.unlocktriggers & flag) { // Execute one time only break; } - unlocktriggers |= flag; + mo->player->roundconditions.unlocktriggers |= flag; // Unlocked something? - if (M_UpdateUnlockablesAndExtraEmblems(true)) + if (!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) { gamedata->deferredsave = true; // only save if unlocked something } From 84742e0376bc3c6ab1c27faae92738aaa8a6570f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 7 Mar 2023 20:02:23 +0000 Subject: [PATCH 040/103] G_LoadGameData: Remove print from recorded map record --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 133b23e45..23714313a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4678,7 +4678,7 @@ void G_LoadGameData(void) G_AllocMainRecordData((INT16)i); mapheaderinfo[i]->mainrecord->time = rectime; mapheaderinfo[i]->mainrecord->lap = reclap; - CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35); + //CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35); } } else From 40786a006c4aa051da5c65fafdea5fe3c4027672 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 9 Mar 2023 16:40:39 +0000 Subject: [PATCH 041/103] P_MobjCheckWater: Remove wet player test print --- src/p_mobj.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 4d5c68fec..a0f7b268b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3427,8 +3427,6 @@ void P_MobjCheckWater(mobj_t *mobj) } p->roundconditions.wet_player |= (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER|MFE_GOOWATER)); - if (p->roundconditions.wet_player) - CONS_Printf("%u\n", p->roundconditions.wet_player); } if (mobj->flags & MF_APPLYTERRAIN) From 328ab0059a7fde706a5952ebcd89d6b3f154ef87 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 9 Mar 2023 21:47:16 +0000 Subject: [PATCH 042/103] F_StartIntro: Disable sound credit (was relevant during routine gamedata deletion) --- src/f_finale.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/f_finale.c b/src/f_finale.c index b591a77a4..6e936c9d4 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -309,6 +309,8 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset void F_StartIntro(void) { + cursongcredit.def = NULL; + if (gamestate) { F_WipeStartScreen(); From 8b437d5a326c39feaec3c553c8a1fefe28094ffe Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 9 Mar 2023 22:31:34 +0000 Subject: [PATCH 043/103] Minimum viable product of Chao Keys condition bypass - Start with 3, per Sakurai's prior art. - Earn them per certain number of rounds - DEVELOP builds: once every 4 rounds - Release builds: once every 50 rounds - Has an internal cap based on the maximum number of unlockables supported. - Possible future work could adjust this to restrict based on the maximum number of unlockables unlocks.pk3 actually has set. - Use on the Challenges screen to bust open small tiles with hints (or the very first tile, if you haven't unlocked anything yet). - Will do a funny shake if you try anything else. - Interrupts menu flow just like getting an unlock. - The matches you've played will tick upwards, giving you keys as they loop over. --- src/g_game.c | 20 +++- src/k_menu.h | 6 +- src/k_menudraw.c | 22 ++++ src/m_cond.c | 30 ++++- src/m_cond.h | 19 ++- src/menus/extras-challenges.c | 218 +++++++++++++++++++++++----------- 6 files changed, 237 insertions(+), 78 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 23714313a..c06ad9c61 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4141,6 +4141,7 @@ static void G_DoCompleted(void) roundtype = GDGT_SPECIAL; gamedata->roundsplayed[roundtype]++; + gamedata->pendingkeyrounds++; // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve M_UpdateUnlockablesAndExtraEmblems(true); @@ -4562,6 +4563,12 @@ void G_LoadGameData(void) gamedata->roundsplayed[i] = READUINT32(save.p); } + gamedata->pendingkeyrounds = READUINT32(save.p); + gamedata->pendingkeyroundoffset = READUINT8(save.p); + gamedata->keyspending = READUINT8(save.p); + gamedata->chaokeys = READUINT8(save.p); + gamedata->usedkeys = READUINT8(save.p); + gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) gamedata->crashflags |= GDCRASH_ANY; @@ -4740,7 +4747,12 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+(4*GDGT_MAX)+1+1+4+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2); + length = (4+1+4+4+ + (4*GDGT_MAX)+ + 4+1+1+1+1+ + 1+1+4+ + (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ + 4+4+2); if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; @@ -4765,6 +4777,12 @@ void G_SaveGameData(boolean dirty) WRITEUINT32(save.p, gamedata->roundsplayed[i]); } + WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4 + WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1 + WRITEUINT8(save.p, gamedata->keyspending); // 1 + WRITEUINT8(save.p, gamedata->chaokeys); // 1 + WRITEUINT8(save.p, gamedata->usedkeys); // 1 + { UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); if (dirty) diff --git a/src/k_menu.h b/src/k_menu.h index 75bd392e2..cb7e79a10 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1144,7 +1144,9 @@ void M_DrawAddons(void); #define CC_UNLOCKED 1 #define CC_TALLY 2 #define CC_ANIM 3 -#define CC_MAX 4 +#define CC_CHAOANIM 4 +#define CC_CHAONOPE 5 +#define CC_MAX 6 #define TILEFLIP_MAX 16 @@ -1155,7 +1157,6 @@ extern struct timeattackmenu_s { } timeattackmenu; - // Keep track of some pause menu data for visual goodness. extern struct challengesmenu_s { @@ -1174,6 +1175,7 @@ extern struct challengesmenu_s { boolean pending; boolean requestnew; + boolean chaokeyadd; boolean requestflip; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ebe9a6b17..ba48c2a30 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5170,6 +5170,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) #define challengetransparentstrength 8 #define challengesgridstep 22 +#define challengekeybarwidth 50 void M_DrawChallenges(void) { @@ -5293,6 +5294,26 @@ void M_DrawChallenges(void) challengedesc: + // Chao Keys + { + patch_t *key = W_CachePatchName("UN_CHA00", PU_CACHE); + INT32 offs = challengesmenu.unlockcount[CC_CHAONOPE]; + if (offs & 1) + offs = -offs; + offs /= 2; + V_DrawFixedPatch((6+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL); + V_DrawKartString((25+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); + + offs = challengekeybarwidth; + if ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS) + offs = ((gamedata->pendingkeyroundoffset * challengekeybarwidth)/GDCONVERT_ROUNDSTOKEY); + + if (offs > 0) + V_DrawFill(1, 25, offs, 2, 0); + if (offs < challengekeybarwidth) + V_DrawFadeFill(1+offs, 25, challengekeybarwidth-offs, 2, 0, 31, challengetransparentstrength); + } + // Tally { str = va("%d/%d", @@ -5347,6 +5368,7 @@ challengedesc: #undef challengetransparentstrength #undef challengesgridstep +#undef challengekeybarwidth // Statistics menu diff --git a/src/m_cond.c b/src/m_cond.c index 0f19f9d8f..36f575317 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -548,6 +548,12 @@ void M_ClearSecrets(void) Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; + + gamedata->pendingkeyrounds = 0; + gamedata->pendingkeyroundoffset = 0; + gamedata->keyspending = 0; + gamedata->chaokeys = 3; // Start with 3 !! + gamedata->usedkeys = 0; } // ---------------------- @@ -1336,8 +1342,7 @@ static boolean M_CheckUnlockConditions(player_t *player) boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) { - INT32 i; - UINT8 response = 0; + UINT16 i = 0, response = 0, newkeys = 0; if (!gamedata) { @@ -1355,6 +1360,14 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response = M_CheckUnlockConditions(NULL); + while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS + && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) + { + gamedata->keyspending++; + newkeys++; + response |= true; + } + if (!demo.playback && Playing() && (gamestate == GS_LEVEL)) { for (i = 0; i <= splitscreen; i++) @@ -1367,7 +1380,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) } } - if (!response && loud) + if (loud && response == 0) { return false; } @@ -1397,8 +1410,10 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response++; } + response += newkeys; + // Announce - if (response) + if (response != 0) { if (loud) { @@ -1409,7 +1424,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) return false; } -UINT8 M_GetNextAchievedUnlock(void) +UINT16 M_GetNextAchievedUnlock(void) { UINT8 i; @@ -1434,6 +1449,11 @@ UINT8 M_GetNextAchievedUnlock(void) return i; } + if (gamedata->keyspending > 0) + { + return PENDING_CHAOKEYS; + } + return MAXUNLOCKABLES; } diff --git a/src/m_cond.h b/src/m_cond.h index 6c1f131d4..83d64ac9b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -207,6 +207,13 @@ typedef enum // This is the largest number of 9s that will fit in UINT32. #define GDMAX_RINGS 999999999 +#define GDMAX_CHAOKEYS MAXUNLOCKABLES + +#ifdef DEVELOP +#define GDCONVERT_ROUNDSTOKEY 4 +#else +#define GDCONVERT_ROUNDSTOKEY 50 +#endif typedef enum { GDGT_RACE, @@ -247,6 +254,13 @@ struct gamedata_t UINT32 roundsplayed[GDGT_MAX]; UINT32 totalrings; + // Chao Key condition bypass + UINT32 pendingkeyrounds; + UINT8 pendingkeyroundoffset; + UINT8 keyspending; + UINT8 chaokeys; + UINT8 usedkeys; + // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; UINT8 crashflags; @@ -297,7 +311,10 @@ void M_ClearStats(void); // Updating conditions and unlockables boolean M_CheckCondition(condition_t *cn, player_t *player); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); -UINT8 M_GetNextAchievedUnlock(void); + +#define PENDING_CHAOKEYS (UINT16_MAX-1) +UINT16 M_GetNextAchievedUnlock(void); + UINT8 M_CheckLevelEmblems(void); UINT8 M_CompletionEmblems(void); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 9caf144bd..30229499d 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -49,17 +49,59 @@ menu_t MISC_StatisticsDef = { struct challengesmenu_s challengesmenu; -static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) +static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh) { UINT8 i; SINT8 work; + if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 + && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + challengesmenu.chaokeyadd = true; + + if (fresh && unlockid >= MAXUNLOCKABLES) + { + 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; + } + } + + unlockid = selection[M_RandomKey(numunlocks)]; + } + if (unlockid >= MAXUNLOCKABLES) return; challengesmenu.currentunlock = unlockid; challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); - challengesmenu.unlockanim = 0; + challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME); if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) return; @@ -161,12 +203,13 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { - UINT8 i; - UINT16 newunlock = M_GetNextAchievedUnlock(); + UINT16 i, newunlock; M_UpdateUnlockablesAndExtraEmblems(false); - if ((challengesmenu.pending = (newunlock < MAXUNLOCKABLES))) + newunlock = M_GetNextAchievedUnlock(); + + if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES))) { S_StopMusic(); MISC_ChallengesDef.prevMenu = desiredmenu; @@ -177,6 +220,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) challengesmenu.ticker = 0; challengesmenu.requestflip = false; challengesmenu.requestnew = false; + challengesmenu.chaokeyadd = false; challengesmenu.currentunlock = MAXUNLOCKABLES; challengesmenu.unlockcondition = NULL; @@ -210,6 +254,9 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending) M_ChallengesAutoFocus(newunlock, true); + else if (newunlock >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 + && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + challengesmenu.chaokeyadd = true; return &MISC_ChallengesDef; } @@ -219,7 +266,6 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) void M_Challenges(INT32 choice) { - UINT8 i; (void)choice; M_InterruptMenuWithChallenges(NULL); @@ -227,40 +273,7 @@ void M_Challenges(INT32 choice) if (gamedata->challengegrid != NULL && !challengesmenu.pending) { - 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; - } - } - - M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true); + M_ChallengesAutoFocus(UINT16_MAX, true); } M_SetupNextMenu(&MISC_ChallengesDef, false); @@ -270,7 +283,7 @@ void M_ChallengesTick(void) { const UINT8 pid = 0; UINT16 i; - UINT8 newunlock = MAXUNLOCKABLES; + UINT16 newunlock = MAXUNLOCKABLES; // Ticking challengesmenu.ticker++; @@ -280,8 +293,11 @@ void M_ChallengesTick(void) if (setup_explosions[i].tics > 0) setup_explosions[i].tics--; } - if (challengesmenu.unlockcount[CC_ANIM] > 0) - challengesmenu.unlockcount[CC_ANIM]--; + for (i = CC_ANIM; i < CC_MAX; i++) + { + if (challengesmenu.unlockcount[i] > 0) + challengesmenu.unlockcount[i]--; + } M_CupSelectTick(); // Update tile flip state. @@ -307,27 +323,61 @@ void M_ChallengesTick(void) } } - if (challengesmenu.pending) + if (challengesmenu.chaokeyadd == true) { - // Pending mode. - - if (challengesmenu.requestnew) + if (challengesmenu.ticker <= 5) + ; // recreate the slight delay the unlock fades provide + else if (gamedata->pendingkeyrounds == 0) { - // The menu apparatus is requesting a new unlock. - challengesmenu.requestnew = false; - if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) + challengesmenu.chaokeyadd = false; + challengesmenu.requestnew = true; + } + else if ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS) + { + gamedata->keyspending = 0; + gamedata->pendingkeyrounds = 0; + } + else + { + if (!(--gamedata->pendingkeyrounds & 1)) { - // We got one! - M_ChallengesAutoFocus(newunlock, false); + S_StartSound(NULL, sfx_ptally); } - else + + if (++gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY) { - // All done! Let's save the unlocks we've busted open. - challengesmenu.pending = false; - G_SaveGameData(true); + gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY; + + if (gamedata->keyspending > 0) + { + S_StartSound(NULL, sfx_achiev); + gamedata->keyspending--; + gamedata->chaokeys++; + challengesmenu.unlockcount[CC_CHAOANIM]++; + } } } - else if (challengesmenu.fade < 5) + } + else if (challengesmenu.requestnew) + { + // The menu apparatus is requesting a new unlock. + challengesmenu.requestnew = false; + if ((newunlock = M_GetNextAchievedUnlock()) != MAXUNLOCKABLES) + { + // We got one! + M_ChallengesAutoFocus(newunlock, false); + } + else + { + // All done! Let's save the unlocks we've busted open. + challengesmenu.pending = challengesmenu.chaokeyadd = false; + G_SaveGameData(true); + } + } + else if (challengesmenu.pending) + { + // Pending mode. + if (challengesmenu.fade < 5) { // Fade increase. challengesmenu.fade++; @@ -441,28 +491,58 @@ boolean M_ChallengesInputs(INT32 ch) const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); (void) ch; - if (challengesmenu.fade) + if (challengesmenu.fade || challengesmenu.chaokeyadd) { ; } -#ifdef DEVELOP - else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging + else if (M_MenuExtraPressed(pid) + && challengesmenu.extradata) { - if (challengesmenu.currentunlock < MAXUNLOCKABLES) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = NULL; - gamedata->challengegridwidth = 0; - M_PopulateChallengeGrid(); - M_UpdateChallengeGridExtraData(challengesmenu.extradata); + i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; - M_ChallengesAutoFocus(challengesmenu.currentunlock, true); + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && !gamedata->unlocked[challengesmenu.currentunlock] + && !unlockables[challengesmenu.currentunlock].majorunlock + && ((challengesmenu.extradata[i].flags & CHE_HINT) + || (challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY] == 0)) + && gamedata->chaokeys > 0) + { + gamedata->chaokeys--; + gamedata->usedkeys++; + challengesmenu.unlockcount[CC_CHAOANIM]++; + + S_StartSound(NULL, sfx_chchng); challengesmenu.pending = true; + M_ChallengesAutoFocus(challengesmenu.currentunlock, false); + } + else + { + challengesmenu.unlockcount[CC_CHAONOPE] = 6; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2 +#if 0 // debugging + if (challengesmenu.currentunlock < MAXUNLOCKABLES) + { + if (gamedata->unlocked[challengesmenu.currentunlock] && challengesmenu.unlockanim >= UNLOCKTIME) + { + if (challengesmenu.unlockcount[CC_TALLY] > 0) + challengesmenu.unlockcount[CC_TALLY]--; + else + challengesmenu.unlockcount[CC_UNLOCKED]--; + } + + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + M_PopulateChallengeGrid(); + M_UpdateChallengeGridExtraData(challengesmenu.extradata); + challengesmenu.pending = true; + M_ChallengesAutoFocus(challengesmenu.currentunlock, true); + } +#endif } return true; } -#endif else { if (M_MenuBackPressed(pid) || start) From 304a7dce72933fa893d7bbb4e15914d477c99bc9 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 20:14:48 +0000 Subject: [PATCH 044/103] K_InitGrandPrixRank: Accomodate cv_gptest --- src/k_rank.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/k_rank.c b/src/k_rank.c index 0e07c33ec..84d98519d 100644 --- a/src/k_rank.c +++ b/src/k_rank.c @@ -302,7 +302,12 @@ void K_InitGrandPrixRank(gpRank_t *rankData) const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i]; if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL) { - laps += mapheaderinfo[cupLevelNum]->numlaps; + if (!cv_gptest.value) + { + laps += mapheaderinfo[cupLevelNum]->numlaps; + continue; + } + laps++; } } From f3cde6140a7de8e326fb28d1815b9ffac8558b8f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 20:15:18 +0000 Subject: [PATCH 045/103] G_GetNextMap: Do not permit entering GPEVENT_SPECIAL if you're on Easy --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index c06ad9c61..e22b5d659 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3911,7 +3911,7 @@ static void G_GetNextMap(void) { gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank); - if (grade >= GRADE_A) // On A rank pace? Then you get a chance for S rank! + if (grade >= GRADE_A && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) // On A rank pace? Then you get a chance for S rank! { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) From dfe75726dfda35bc5a4130375897b866c24f8cb9 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 20:20:48 +0000 Subject: [PATCH 046/103] cupwindata_t Save your best GP stats across sessions. - Saved into gamedata - Deliniated per difficulty option - Draw onto cupgrid in GP cup select - Best grade - Whether you've ever gotten the Emerald - TODO: Always shows Chaos Emerald, will need updating when Super Emerald graphics are created - Monitor status changes depending on recorded position - 1st: Gold, shiny - 2nd: Silver, shiny - 3rd: Bronze, shiny - 4th and downwards: Beige, barely different - Wiped with G_ClearRecords Also, to avoid circular dependencies: - KARTSPEED/KARTGP constants moved from command.h to doomstat.h - gp_rank_e enums moved from k_rank.h to doomstat.h --- src/command.h | 6 +-- src/doomstat.h | 25 ++++++++++++ src/g_game.c | 99 +++++++++++++++++++++++++++++++++++++++++------- src/k_menudraw.c | 87 ++++++++++++++++++++++++++++++++++++++---- src/k_podium.c | 34 +++++++++++++++++ src/k_rank.h | 10 +---- src/typedef.h | 1 + 7 files changed, 226 insertions(+), 36 deletions(-) diff --git a/src/command.h b/src/command.h index d1ddfa337..8ad27bfbc 100644 --- a/src/command.h +++ b/src/command.h @@ -174,11 +174,7 @@ extern CV_PossibleValue_t CV_Unsigned[]; extern CV_PossibleValue_t CV_Natural[]; // SRB2kart -#define KARTSPEED_AUTO -1 -#define KARTSPEED_EASY 0 -#define KARTSPEED_NORMAL 1 -#define KARTSPEED_HARD 2 -#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots +// the KARTSPEED and KARTGP were previously defined here, but moved to doomstat to avoid circular dependencies extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[]; extern consvar_t cv_execversion; diff --git a/src/doomstat.h b/src/doomstat.h index 33c200dbf..ba12196c9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -120,6 +120,30 @@ struct recorddata_t //UINT16 rings; ///< Rings when the level was finished. }; +#define KARTSPEED_AUTO -1 +#define KARTSPEED_EASY 0 +#define KARTSPEED_NORMAL 1 +#define KARTSPEED_HARD 2 +#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots +#define KARTGP_MAX 4 + +typedef enum +{ + GRADE_E, + GRADE_D, + GRADE_C, + GRADE_B, + GRADE_A, + GRADE_S +} gp_rank_e; + +struct cupwindata_t +{ + UINT8 best_placement; + gp_rank_e best_grade; + boolean got_emerald; +}; + // mapvisited is now a set of flags that says what we've done in the map. #define MV_VISITED (1) #define MV_BEATEN (1<<1) @@ -358,6 +382,7 @@ 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) + cupwindata_t windata[4]; ///< Data for cup visitation cupheader_t *next; ///< Next cup in linked list }; diff --git a/src/g_game.c b/src/g_game.c index e22b5d659..b48ee700b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -463,6 +463,8 @@ void G_AllocMainRecordData(INT16 i) void G_ClearRecords(void) { INT16 i; + cupheader_t *cup; + for (i = 0; i < nummapheaders; ++i) { if (mapheaderinfo[i]->mainrecord) @@ -471,6 +473,11 @@ void G_ClearRecords(void) mapheaderinfo[i]->mainrecord = NULL; } } + + for (cup = kartcupheaders; cup; cup = cup->next) + { + memset(&cup->windata, 0, sizeof(cup->windata)); + } } // For easy retrieval of records @@ -4498,6 +4505,7 @@ void G_LoadGameData(void) //For records UINT32 numgamedatamapheaders; + UINT32 numgamedatacups; // Stop saving, until we successfully load it again. gamedata->loaded = false; @@ -4696,21 +4704,57 @@ void G_LoadGameData(void) } } + if (versionMinor > 1) + { + numgamedatacups = READUINT32(save.p); + + for (i = 0; i < numgamedatacups; i++) + { + char cupname[16]; + cupheader_t *cup; + + READSTRINGN(save.p, cupname, sizeof(cupname)); + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (strcmp(cup->name, cupname)) + continue; + + for (j = 0; j < KARTGP_MAX; j++) + { + rtemp = READUINT8(save.p); + + cup->windata[j].best_placement = (rtemp & 0x0F); + cup->windata[j].best_grade = (rtemp & 0x70)>>4; + if (rtemp & 0x80) + { + if (j == 0) + goto datacorrupt; + + cup->windata[j].got_emerald = true; + } + } + + break; + } + } + } + // done P_SaveBufferFree(&save); -finalisegamedata: + finalisegamedata: + { + // Don't consider loaded until it's a success! + // 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! + gamedata->loaded = true; - // Don't consider loaded until it's a success! - // 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! - gamedata->loaded = true; + // Silent update unlockables in case they're out of sync with conditions + M_UpdateUnlockablesAndExtraEmblems(false); - // Silent update unlockables in case they're out of sync with conditions - M_UpdateUnlockablesAndExtraEmblems(false); - - return; + return; + } // Landing point for corrupt gamedata datacorrupt: @@ -4730,7 +4774,8 @@ finalisegamedata: void G_SaveGameData(boolean dirty) { size_t length; - INT32 i, j; + INT32 i, j, numcups; + cupheader_t *cup; UINT8 btemp; savebuffer_t save = {0}; @@ -4752,12 +4797,20 @@ void G_SaveGameData(boolean dirty) 4+1+1+1+1+ 1+1+4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ - 4+4+2); + 4+2); + if (gamedata->challengegrid) { length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT; } - length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); + length += 4 + (nummapheaders * (MAXMAPLUMPNAME+1+4+4)); + + numcups = 0; + for (cup = kartcupheaders; cup; cup = cup->next) + { + numcups++; + } + length += 4 + (numcups * 4); if (P_SaveBufferAlloc(&save, length) == false) { @@ -4851,7 +4904,7 @@ void G_SaveGameData(boolean dirty) for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4) { - // For figuring out which header to assing it to on load + // For figuring out which header to assign it to on load WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX)); @@ -4868,6 +4921,24 @@ void G_SaveGameData(boolean dirty) } } + WRITEUINT32(save.p, numcups); // 4 + + for (cup = kartcupheaders; cup; cup = cup->next) + { + // For figuring out which header to assign it to on load + WRITESTRINGN(save.p, cup->name, 16); + + for (i = 0; i < KARTGP_MAX; i++) + { + btemp = min(cup->windata[i].best_placement, 0x0F); + btemp |= (cup->windata[i].best_grade<<4); + if (i != 0 && cup->windata[i].got_emerald == true) + btemp |= 0x80; + + WRITEUINT8(save.p, btemp); // 4 * numcups + } + } + length = save.p - save.buffer; FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ba48c2a30..eb0cf5e00 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2113,6 +2113,8 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) void M_DrawCupSelect(void) { UINT8 i, j, temp = 0; + UINT8 *colormap = NULL; + cupwindata_t *windata = NULL; levelsearch_t templevelsearch = levellist.levelsearch; // full copy for (i = 0; i < CUPMENU_COLUMNS; i++) @@ -2123,26 +2125,62 @@ void M_DrawCupSelect(void) patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; + char status = 'A'; + UINT8 monitor = 1; + INT32 rankx = 0; if (!cupgrid.builtgrid[id]) break; templevelsearch.cup = cupgrid.builtgrid[id]; - /*if (templevelsearch.cup->emeraldnum == 0) - patch = W_CachePatchName("CUPMON3A", PU_CACHE); - else*/ if (templevelsearch.cup->emeraldnum > 7) + if (cupgrid.grandprix + && (cv_dummygpdifficulty.value >= 0 && cv_dummygpdifficulty.value < KARTGP_MAX)) { - patch = W_CachePatchName("CUPMON2A", PU_CACHE); - icony = 5; + UINT16 col = SKINCOLOR_NONE; + + windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value]; + + switch (windata->best_placement) + { + case 0: + break; + case 1: + col = SKINCOLOR_GOLD; + status = 'B'; + break; + case 2: + col = SKINCOLOR_SILVER; + status = 'B'; + break; + case 3: + col = SKINCOLOR_BRONZE; + status = 'B'; + break; + default: + col = SKINCOLOR_BEIGE; + break; + } + + if (col != SKINCOLOR_NONE) + colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE); + else + colormap = NULL; } - else - patch = W_CachePatchName("CUPMON1A", PU_CACHE); + + if (templevelsearch.cup->emeraldnum > 7) + { + monitor = 2; + icony = 5; + rankx = 2; + } + + patch = W_CachePatchName(va("CUPMON%d%c", monitor, status), PU_CACHE); x = 14 + (i*42); y = 20 + (j*44) - (30*menutransition.tics); - V_DrawScaledPatch(x, y, 0, patch); + V_DrawFixedPatch((x)*FRACUNIT, (y)<icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); + + if (!windata) + ; + else if (windata->best_placement != 0) + { + char gradeChar = '?'; + + switch (windata->best_grade) + { + case GRADE_E: { gradeChar = 'E'; break; } + case GRADE_D: { gradeChar = 'D'; break; } + case GRADE_C: { gradeChar = 'C'; break; } + case GRADE_B: { gradeChar = 'B'; break; } + case GRADE_A: { gradeChar = 'A'; break; } + case GRADE_S: { gradeChar = 'S'; break; } + default: { break; } + } + + V_DrawCharacter(x + 5 + rankx, y + icony + 14, gradeChar, false); // rank + + if (windata->got_emerald == true) + { + if (templevelsearch.cup->emeraldnum == 0) + V_DrawCharacter(x + 26 - rankx, y + icony + 14, '*', false); // rank + else + { + UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7; + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + + V_DrawFixedPatch((x + 26 - rankx)*FRACUNIT, (y + icony + 13)*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_EMERC", PU_CACHE), colormap); + } + } + } } } } diff --git a/src/k_podium.c b/src/k_podium.c index 7f00834d9..0d6b23e5b 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -264,6 +264,10 @@ void K_FinishCeremony(void) } podiumData.ranking = true; + + // Play the noise now + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); } /*-------------------------------------------------- @@ -273,6 +277,8 @@ void K_FinishCeremony(void) --------------------------------------------------*/ void K_ResetCeremony(void) { + UINT8 i; + memset(&podiumData, 0, sizeof(struct podiumData_s)); if (K_PodiumSequence() == false) @@ -280,8 +286,36 @@ void K_ResetCeremony(void) return; } + // Establish rank and grade for this play session. podiumData.rank = grandprixinfo.rank; podiumData.grade = K_CalculateGPGrade(&podiumData.rank); + + if (!grandprixinfo.cup) + { + return; + } + + // Write grade, position, and emerald-having-ness for later sessions! + i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; + + if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run + || (podiumData.rank.position < grandprixinfo.cup->windata[i].best_placement)) // Later, better run + { + grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position; + + // The following will not occour in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day + if (grandprixinfo.cup->windata[i].best_placement > 0x0F) + grandprixinfo.cup->windata[i].best_placement = 0x0F; + } + + if (podiumData.grade > grandprixinfo.cup->windata[i].best_grade) + grandprixinfo.cup->windata[i].best_grade = podiumData.grade; + + if (i != KARTSPEED_EASY && podiumData.rank.specialWon == true) + grandprixinfo.cup->windata[i].got_emerald = true; + + // Save before playing the noise + G_SaveGameData(true); } /*-------------------------------------------------- diff --git a/src/k_rank.h b/src/k_rank.h index cc675db17..7903de708 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -44,15 +44,7 @@ struct gpRank_t boolean specialWon; }; -typedef enum -{ - GRADE_E, - GRADE_D, - GRADE_C, - GRADE_B, - GRADE_A, - GRADE_S -} gp_rank_e; +// gp_rank_e was once defined here, but moved to doomstat.h to prevent circular dependency // 3rd place is neutral, anything below is a penalty #define RANK_NEUTRAL_POSITION (3) diff --git a/src/typedef.h b/src/typedef.h index 5071ff01f..0615e7034 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -108,6 +108,7 @@ TYPEDEF (skincolor_t); // doomstat.h TYPEDEF (precipprops_t); TYPEDEF (recorddata_t); +TYPEDEF (cupwindata_t); TYPEDEF (scene_t); TYPEDEF (cutscene_t); TYPEDEF (textpage_t); From fea235d8a75403be6ba6798dccd2e2efdf809261 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 21:44:48 +0000 Subject: [PATCH 047/103] UC_REPLAY Save a replay after finishing a round. Basically another tutorial unlock condition like UC_ADDON --- src/deh_soc.c | 1 + src/g_demo.c | 9 +++++++++ src/g_game.c | 4 +++- src/m_cond.c | 5 +++++ src/m_cond.h | 7 +++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index d6c5e8fbf..0fed9db9f 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2520,6 +2520,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) } } else if ((offset=0) || fastcmp(params[0], "ADDON") + || (++offset && fastcmp(params[0], "REPLAY")) || (++offset && fastcmp(params[0], "CRASH"))) { //PARAMCHECK(1); diff --git a/src/g_demo.c b/src/g_demo.c index ad965e670..f9b088ea4 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -26,6 +26,7 @@ #include "g_game.h" #include "g_demo.h" #include "m_misc.h" +#include "m_cond.h" #include "k_menu.h" #include "m_argv.h" #include "hu_stuff.h" @@ -4188,7 +4189,15 @@ void G_SaveDemo(void) if (!modeattacking) { if (demo.savemode == DSM_SAVED) + { CONS_Printf(M_GetText("Demo %s recorded\n"), demoname); + if (gamedata->eversavedreplay == false) + { + gamedata->eversavedreplay = true; + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); + } + } else CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname); } diff --git a/src/g_game.c b/src/g_game.c index b48ee700b..4b36b1f2c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4582,6 +4582,7 @@ void G_LoadGameData(void) gamedata->crashflags |= GDCRASH_ANY; gamedata->everloadedaddon = (boolean)READUINT8(save.p); + gamedata->eversavedreplay = (boolean)READUINT8(save.p); } else { @@ -4795,7 +4796,7 @@ void G_SaveGameData(boolean dirty) length = (4+1+4+4+ (4*GDGT_MAX)+ 4+1+1+1+1+ - 1+1+4+ + 1+1+1+4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ 4+2); @@ -4844,6 +4845,7 @@ void G_SaveGameData(boolean dirty) } WRITEUINT8(save.p, gamedata->everloadedaddon); // 1 + WRITEUINT8(save.p, gamedata->eversavedreplay); // 1 WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); diff --git a/src/m_cond.c b/src/m_cond.c index 36f575317..5d0e26df9 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -526,6 +526,7 @@ void M_ClearStats(void) gamedata->timesBeaten = 0; gamedata->everloadedaddon = false; + gamedata->eversavedreplay = false; gamedata->crashflags = 0; } @@ -721,6 +722,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) M_SecretUnlocked(SECRET_ADDONS, true) && #endif (gamedata->everloadedaddon == true)); + case UC_REPLAY: + return (gamedata->eversavedreplay == true); case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) { @@ -1109,6 +1112,8 @@ static const char *M_GetConditionString(condition_t *cn) if (!M_SecretUnlocked(SECRET_ADDONS, true) && !gamedata->everloadedaddon) return NULL; return "load a custom addon into \"Dr. Robotnik's Ring Racers\""; + case UC_REPLAY: + return "save a replay after finishing a round"; case UC_CRASH: if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; diff --git a/src/m_cond.h b/src/m_cond.h index 83d64ac9b..47bc45488 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -31,20 +31,26 @@ typedef enum UC_PLAYTIME, // PLAYTIME [tics] UC_ROUNDSPLAYED, // ROUNDSPLAYED [x played] UC_TOTALRINGS, // TOTALRINGS [x collected] + UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] + UC_GAMECLEAR, // GAMECLEAR UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] + UC_MAPVISITED, // MAPVISITED [map] UC_MAPBEATEN, // MAPBEATEN [map] UC_MAPENCORE, // MAPENCORE [map] UC_MAPSPBATTACK, // MAPSPBATTACK [map] UC_MAPTIME, // MAPTIME [map] [time to beat, tics] + UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] + UC_UNLOCKABLE, // UNLOCKABLE [unlockable number] UC_CONDITIONSET, // CONDITIONSET [condition set number] UC_ADDON, // Ever loaded a custom file? + UC_REPLAY, // Save a replay UC_CRASH, // Hee ho ! // Just for string building @@ -263,6 +269,7 @@ struct gamedata_t // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; + boolean eversavedreplay; UINT8 crashflags; }; From cbebfe5a62eb115191344a83e6c0a30b4cf7d4f9 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 22:41:03 +0000 Subject: [PATCH 048/103] UC_POWERLEVEL: Do not iterate over your profiles in GS_LEVEL --- src/m_cond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/m_cond.c b/src/m_cond.c index 5d0e26df9..6ea8e37e0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -672,6 +672,10 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) case UC_POWERLEVEL: // Requires power level >= x on a certain gametype { UINT8 i; + + if (gamestate == GS_LEVEL) + return false; // this one could be laggy with many profiles available + for (i = PROFILE_GUEST; i < PR_GetNumProfiles(); i++) { profile_t *p = PR_GetProfile(i); From b3aa2520bcd4c72898ef5a0b57ef43e4128f4cd6 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 22:43:06 +0000 Subject: [PATCH 049/103] gamedata->everseenspecial Record whether you've ever successfully entered a special stage Used for conditions which were previously checking for whether you'd completed a special stage - which I think is a bit too restrictive for someone figuring out what next to lab --- src/g_game.c | 12 +++++++++++- src/m_cond.c | 3 ++- src/m_cond.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 4b36b1f2c..72e396d1f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3926,6 +3926,13 @@ static void G_GetNextMap(void) grandprixinfo.eventmode = GPEVENT_SPECIAL; nextmap = cupLevelNum; newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); + + if (gamedata->everseenspecial == false) + { + gamedata->everseenspecial = true; + M_UpdateUnlockablesAndExtraEmblems(true); + G_SaveGameData(true); + } } } } @@ -4583,6 +4590,7 @@ void G_LoadGameData(void) gamedata->everloadedaddon = (boolean)READUINT8(save.p); gamedata->eversavedreplay = (boolean)READUINT8(save.p); + gamedata->everseenspecial = (boolean)READUINT8(save.p); } else { @@ -4796,7 +4804,8 @@ void G_SaveGameData(boolean dirty) length = (4+1+4+4+ (4*GDGT_MAX)+ 4+1+1+1+1+ - 1+1+1+4+ + 1+1+1+1+ + 4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ 4+2); @@ -4846,6 +4855,7 @@ void G_SaveGameData(boolean dirty) WRITEUINT8(save.p, gamedata->everloadedaddon); // 1 WRITEUINT8(save.p, gamedata->eversavedreplay); // 1 + WRITEUINT8(save.p, gamedata->everseenspecial); // 1 WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); diff --git a/src/m_cond.c b/src/m_cond.c index 6ea8e37e0..84c0472f6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -527,6 +527,7 @@ void M_ClearStats(void) gamedata->everloadedaddon = false; gamedata->eversavedreplay = false; + gamedata->everseenspecial = false; gamedata->crashflags = 0; } @@ -1139,7 +1140,7 @@ static const char *M_GetConditionString(condition_t *cn) case UCRP_PREFIX_BREAKTHECAPSULES: return "BREAK THE CAPSULES:"; case UCRP_PREFIX_SEALEDSTAR: - if (gamedata->roundsplayed[GDGT_SPECIAL] == 0) + if (!gamedata->everseenspecial) return NULL; return "SEALED STARS:"; diff --git a/src/m_cond.h b/src/m_cond.h index 47bc45488..693a285e3 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -270,6 +270,7 @@ struct gamedata_t // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; boolean eversavedreplay; + boolean everseenspecial; UINT8 crashflags; }; From 6129d810ccfc8ab731bddb78109fb0a43804ccf6 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 10 Mar 2023 22:50:08 +0000 Subject: [PATCH 050/103] UC_ALLCHAOS, UC_ALLSUPER, and UC_ALLEMERALDS Measures whether you have all 7 Chaos Emeralds, 7 Super Emeralds, or 14 Emeralds - Hidden if you haven't entered a special stage yet - Checks all cups and all relevant difficulties outside of GS_LEVEL - You can specify a difficulty of Normal, Hard, or Master --- src/deh_soc.c | 22 ++++++++++++++++ src/m_cond.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/m_cond.h | 4 +++ 3 files changed, 99 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index 0fed9db9f..c787437c9 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2477,6 +2477,28 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) return; } } + else if ((offset=0) || fastcmp(params[0], "ALLCHAOS") + || (++offset && fastcmp(params[0], "ALLSUPER")) + || (++offset && fastcmp(params[0], "ALLEMERALDS"))) + { + //PARAMCHECK(1); + ty = UC_ALLCHAOS + offset; + re = 1; + if (params[1]) + { + if (fastcmp(params[1], "NORMAL")) + ; + else if (fastcmp(params[1], "HARD")) + x1 = 2; + else if (fastcmp(params[1], "MASTER")) + x1 = 3; + else + { + deh_warning("gamespeed requirement \"%s\" invalid for condition ID %d", params[1], id+1); + return; + } + } + } else if (fastcmp(params[0], "TOTALMEDALS")) { PARAMCHECK(1); diff --git a/src/m_cond.c b/src/m_cond.c index 84c0472f6..7fce4101c 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -712,6 +712,43 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) } case UC_MAPTIME: // Requires time on map <= x return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); + + case UC_ALLCHAOS: + case UC_ALLSUPER: + case UC_ALLEMERALDS: + { + cupheader_t *cup; + UINT16 ret = 0; + UINT8 i; + + if (gamestate == GS_LEVEL) + return false; // this one could be laggy with many cups available + + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (cup->emeraldnum == 0) + continue; + + i = cn->requirement; + for (i = cn->requirement; i < KARTGP_MAX; i++) + { + if (cup->windata[i].got_emerald == true) + break; + } + + if (i == KARTGP_MAX) + continue; + + ret |= 1<<(cup->emeraldnum-1); + } + + if (cn->type == UC_ALLCHAOS) + return ALLCHAOSEMERALDS(ret); + if (cn->type == UC_ALLSUPER) + return ALLSUPEREMERALDS(ret); + return ALLEMERALDS(ret); + } + case UC_TOTALMEDALS: // Requires number of emblems >= x return (M_GotEnoughMedals(cn->requirement)); case UC_EMBLEM: // Requires emblem x to be obtained @@ -1026,6 +1063,42 @@ static const char *M_GetConditionString(condition_t *cn) return work; } + case UC_ALLCHAOS: + case UC_ALLSUPER: + case UC_ALLEMERALDS: + { + const char *chaostext, *speedtext = "", *orbetter = ""; + + if (!gamedata->everseenspecial) + return NULL; + + if (cn->type == UC_ALLCHAOS) + chaostext = "7 Chaos"; + else if (cn->type == UC_ALLSUPER) + chaostext = "7 Super"; + else + chaostext = "14"; + + if (cn->requirement == 1) + { + speedtext = " on Normal difficulty"; + //if (M_SecretUnlocked(SECRET_HARDSPEED, true)) + orbetter = " or better"; + } + else if (cn->requirement == 2) + { + speedtext = " on Hard difficulty"; + if (M_SecretUnlocked(SECRET_MASTERMODE, true)) + orbetter = " or better"; + } + else if (cn->requirement == 3) + { + speedtext = " on Master difficulty"; + } + + return va("collect all %s Emeralds%s%s", chaostext, speedtext, orbetter); + } + case UC_TOTALMEDALS: // Requires number of emblems >= x return va("get %d medals", cn->requirement); diff --git a/src/m_cond.h b/src/m_cond.h index 693a285e3..4cbe8a678 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -43,6 +43,10 @@ typedef enum UC_MAPSPBATTACK, // MAPSPBATTACK [map] UC_MAPTIME, // MAPTIME [map] [time to beat, tics] + UC_ALLCHAOS, // ALLCHAOS [minimum difficulty] + UC_ALLSUPER, // ALLSUPER [minimum difficulty] + UC_ALLEMERALDS, // ALLEMERALDS [minimum difficulty] + UC_TOTALMEDALS, // TOTALMEDALS [number of emblems] UC_EMBLEM, // EMBLEM [emblem number] From f411ca1e8549b099070d62d4a9bc78e691383c89 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 11 Mar 2023 19:42:20 +0000 Subject: [PATCH 051/103] UC_ALLCHAOS, UC_ALLSUPER, UC_ALLEMERALDS: Move to using KARTSPEED/KARTGP constants directly --- src/deh_soc.c | 6 +++--- src/m_cond.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index c787437c9..57ff7e6a9 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2483,15 +2483,15 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) { //PARAMCHECK(1); ty = UC_ALLCHAOS + offset; - re = 1; + re = KARTSPEED_NORMAL; if (params[1]) { if (fastcmp(params[1], "NORMAL")) ; else if (fastcmp(params[1], "HARD")) - x1 = 2; + x1 = KARTSPEED_HARD; else if (fastcmp(params[1], "MASTER")) - x1 = 3; + x1 = KARTGP_MASTER; else { deh_warning("gamespeed requirement \"%s\" invalid for condition ID %d", params[1], id+1); diff --git a/src/m_cond.c b/src/m_cond.c index 7fce4101c..bd92b97e0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1079,19 +1079,19 @@ static const char *M_GetConditionString(condition_t *cn) else chaostext = "14"; - if (cn->requirement == 1) + if (cn->requirement == KARTSPEED_NORMAL) { speedtext = " on Normal difficulty"; //if (M_SecretUnlocked(SECRET_HARDSPEED, true)) orbetter = " or better"; } - else if (cn->requirement == 2) + else if (cn->requirement == KARTSPEED_HARD) { speedtext = " on Hard difficulty"; if (M_SecretUnlocked(SECRET_MASTERMODE, true)) orbetter = " or better"; } - else if (cn->requirement == 3) + else if (cn->requirement == KARTGP_MASTER) { speedtext = " on Master difficulty"; } From 6f62abc1efdebc966af8606265a1ef2102425c5b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 11 Mar 2023 20:12:47 +0000 Subject: [PATCH 052/103] UCRP_FINISHPLACE: Don't permit messed up position of 0 --- src/m_cond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/m_cond.c b/src/m_cond.c index bd92b97e0..0013a8398 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -812,6 +812,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (player->exiting && !(player->pflags & PF_NOCONTEST) && M_NotFreePlay(player) + && player->position != 0 && player->position <= cn->requirement); case UCRP_FINISHPLACEEXACT: return (player->exiting From 4d889231739619e8588cf4773ffcd8aa40f7c86f Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 11 Mar 2023 21:07:29 +0000 Subject: [PATCH 053/103] UC_ALLCHAOS, UC_ALLSUPER, UC_ALLEMERALDS: Provide a ??? tease for Master difficulty if not unlocked --- src/m_cond.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/m_cond.c b/src/m_cond.c index 0013a8398..39b760ffa 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1094,7 +1094,10 @@ static const char *M_GetConditionString(condition_t *cn) } else if (cn->requirement == KARTGP_MASTER) { - speedtext = " on Master difficulty"; + if (M_SecretUnlocked(SECRET_MASTERMODE, true)) + speedtext = " on Master difficulty"; + else + speedtext = " on ???"; } return va("collect all %s Emeralds%s%s", chaostext, speedtext, orbetter); From 3abba2bd26d4b1e8ec0eebda9f89cdebb53bef85 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 11 Mar 2023 21:19:42 +0000 Subject: [PATCH 054/103] G_LoadGameData: Add -resetchallengegrid command line parameter for DEVELOP builds only --- src/g_game.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 72e396d1f..066f96909 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4562,7 +4562,11 @@ void G_LoadGameData(void) P_SaveBufferFree(&save); I_Error("Game data is from the future! (expected %d, got %d)\nRename or delete %s (maybe in %s) and try again.", GD_VERSIONMINOR, versionMinor, gamedatafilename, gdfolder); } - if (versionMinor == 0 || versionMinor == 1) + if ((versionMinor == 0 || versionMinor == 1) +#ifdef DEVELOP + || M_CheckParm("-resetchallengegrid") +#endif + ) { gridunusable = true; } From 5a9281ecfb88d182c452ead17ad78d79cd2b26c1 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 11 Mar 2023 23:56:58 +0000 Subject: [PATCH 055/103] Three new UCRP's - UCRP_ISDIFFICULTY - Example: IsDifficulty Hard - "on Hard difficulty" - Does what it says on the tin - You can't specify Easy because there is nothing easier than Easy - Does it based on the GPsetting if in grand prix, or the level setting otherwise - UCRP_PODIUMCUP - Example: PodiumCup Ring - "complete RING CUP" - Example: PodiumCup Barrier Silver - "get Silver or better on BARRIER CUP" - Example: PodiumCup Goggles S - "get grade S on GOGGLES CUP" - Basically a monolithic cup completion handler. - Only happens after rankings begins in Podium ceremony. - UCRP_PODIUMEMERALD - Example: PodiumEmerald - "collect the Emerald" - Get the Emerald to the ceremony and this is yours. - UCRP_PODIUMPRIZE - Example: PodiumPrize - "collect the prize" - LITERALLY identical to PodiumEmerald except the string - Doing it seperately from PodiumCup means we can't check whether that cup uses an Emerald or another Catcher Prize automagically --- src/deh_soc.c | 81 ++++++++++++++++++++++++++++++++++++++ src/k_podium.c | 26 +++++++++++- src/k_podium.h | 32 +++++++++++++++ src/m_cond.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++- src/m_cond.h | 5 +++ 5 files changed, 247 insertions(+), 2 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 57ff7e6a9..e1318a429 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2597,6 +2597,87 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) } #endif } + else if (fastcmp(params[0], "ISDIFFICULTY")) + { + //PARAMCHECK(1); + ty = UCRP_ISDIFFICULTY; + re = KARTSPEED_NORMAL; + if (params[1]) + { + if (fastcmp(params[1], "NORMAL")) + ; + else if (fastcmp(params[1], "HARD")) + x1 = KARTSPEED_HARD; + else if (fastcmp(params[1], "MASTER")) + x1 = KARTGP_MASTER; + else + { + deh_warning("gamespeed requirement \"%s\" invalid for condition ID %d", params[1], id+1); + return; + } + } + } + else if (fastcmp(params[0], "PODIUMCUP")) + { + PARAMCHECK(1); + ty = UCRP_PODIUMCUP; + { + cupheader_t *cup = kartcupheaders; + while (cup) + { + if (!strcmp(cup->name, params[1])) + break; + cup = cup->next; + } + + if (!cup) + { + deh_warning("Invalid cup %s for condition ID %d", params[1], id+1); + return; + } + + re = cup->id; + } + + if (params[2]) + { + if (params[2][0] && !params[2][1]) + { + x2 = 1; + + switch (params[2][0]) + { + case 'E': { x1 = GRADE_E; break; } + case 'D': { x1 = GRADE_D; break; } + case 'C': { x1 = GRADE_C; break; } + case 'B': { x1 = GRADE_B; break; } + case 'A': { x1 = GRADE_A; break; } + case 'S': { x1 = GRADE_S; break; } + default: + deh_warning("Invalid grade %s for condition ID %d", params[2], id+1); + return; + } + } + else if ((offset=0) || fastcmp(params[2], "GOLD") + || (++offset && fastcmp(params[2], "SILVER")) + || (++offset && fastcmp(params[2], "BRONZE"))) + { + x1 = offset + 1; + } + else + { + deh_warning("Invalid cup result %s for condition ID %d", params[2], id+1); + return; + } + + } + } + else if ((offset=0) || fastcmp(params[0], "PODIUMEMERALD") + || (++offset && fastcmp(params[0], "PODIUMPRIZE"))) + { + //PARAMCHECK(1); + ty = UCRP_PODIUMEMERALD + offset; + } else if ((offset=0) || fastcmp(params[0], "FINISHCOOL") || (++offset && fastcmp(params[0], "FINISHALLCAPSULES")) || (++offset && fastcmp(params[0], "NOCONTEST"))) diff --git a/src/k_podium.c b/src/k_podium.c index 0d6b23e5b..807b7f5a4 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -13,7 +13,6 @@ #include "k_podium.h" #include "doomdef.h" -#include "doomstat.h" #include "d_main.h" #include "d_netcmd.h" #include "f_finale.h" @@ -69,6 +68,31 @@ boolean K_PodiumSequence(void) return (gamestate == GS_CEREMONY); } +/*-------------------------------------------------- + boolean K_PodiumRanking(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumRanking(void) +{ + return (gamestate == GS_CEREMONY && podiumData.ranking == true); +} + +/*-------------------------------------------------- + boolean K_PodiumGrade(void) + + See header file for description. +--------------------------------------------------*/ +gp_rank_e K_PodiumGrade(void) +{ + if (K_PodiumSequence() == false) + { + return 0; + } + + return podiumData.grade; +} + /*-------------------------------------------------- UINT8 K_GetPodiumPosition(player_t *player) diff --git a/src/k_podium.h b/src/k_podium.h index 08357dd02..d7b04e3e4 100644 --- a/src/k_podium.h +++ b/src/k_podium.h @@ -14,6 +14,7 @@ #define __K_PODIUM__ #include "doomtype.h" +#include "doomstat.h" // gp_rank_e #include "d_event.h" #include "p_mobj.h" @@ -37,6 +38,37 @@ extern "C" { boolean K_PodiumSequence(void); +/*-------------------------------------------------- + boolean K_PodiumRanking(void); + + Returns whenver or not we are in the podium + final state. + + Input Arguments:- + N/A + + Return:- + true if we're in GS_CEREMONY, otherwise false. +--------------------------------------------------*/ + +boolean K_PodiumRanking(void); + + +/*-------------------------------------------------- + boolean K_PodiumGrade(void) + + Returns the podium grade. + + Input Arguments:- + N/A + + Return:- + gp_rank_e constant if we're in GS_CEREMONY, otherwise 0. +--------------------------------------------------*/ + +gp_rank_e K_PodiumGrade(void); + + /*-------------------------------------------------- UINT8 K_GetPodiumPosition(player_t *player); diff --git a/src/m_cond.c b/src/m_cond.c index 39b760ffa..26e43d797 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -29,6 +29,7 @@ #include "k_grandprix.h" // grandprixinfo #include "k_battle.h" // battlecapsules #include "k_specialstage.h" // specialstageinfo +#include "k_podium.h" #include "k_pwrlv.h" #include "k_profiles.h" @@ -795,6 +796,29 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (gamemap == cn->requirement+1); case UCRP_ISCHARACTER: return (player->skin == cn->requirement); + case UCRP_ISDIFFICULTY: + if (grandprixinfo.gp == false) + return (gamespeed >= cn->requirement); + if (cn->requirement == KARTGP_MASTER) + return (grandprixinfo.masterbots == true); + return (grandprixinfo.gamespeed >= cn->requirement); + + case UCRP_PODIUMCUP: + if (K_PodiumRanking() == false) + return false; + if (grandprixinfo.cup == NULL + || grandprixinfo.cup->id != cn->requirement) + return false; + if (cn->extrainfo2) + return (K_PodiumGrade() >= (unsigned)cn->requirement); + if (cn->extrainfo1 != 0) + return (player->position != 0 + && player->position <= cn->extrainfo1); + return true; + case UCRP_PODIUMEMERALD: + case UCRP_PODIUMPRIZE: + return (K_PodiumRanking() == true + && grandprixinfo.rank.specialWon == true); case UCRP_FINISHCOOL: return (player->exiting @@ -1241,6 +1265,85 @@ static const char *M_GetConditionString(condition_t *cn) if (cn->requirement < 0 || !skins[cn->requirement].realname[0]) return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement); return va("as %s", skins[cn->requirement].realname); + case UCRP_ISDIFFICULTY: + { + const char *speedtext = "", *orbetter = ""; + + if (cn->requirement == KARTSPEED_NORMAL) + { + speedtext = "on Normal difficulty"; + //if (M_SecretUnlocked(SECRET_HARDSPEED, true)) + orbetter = " or better"; + } + else if (cn->requirement == KARTSPEED_HARD) + { + speedtext = "on Hard difficulty"; + if (M_SecretUnlocked(SECRET_MASTERMODE, true)) + orbetter = " or better"; + } + else if (cn->requirement == KARTGP_MASTER) + { + if (M_SecretUnlocked(SECRET_MASTERMODE, true)) + speedtext = "on Master difficulty"; + else + speedtext = "on ???"; + } + + return va("%s%s", speedtext, orbetter); + } + + case UCRP_PODIUMCUP: + { + cupheader_t *cup; + const char *completetype = "complete", *orbetter = ""; + + if (cn->extrainfo2) + { + switch (cn->requirement) + { + case GRADE_E: { completetype = "get grade E"; break; } + case GRADE_D: { completetype = "get grade D"; break; } + case GRADE_C: { completetype = "get grade C"; break; } + case GRADE_B: { completetype = "get grade B"; break; } + case GRADE_A: { completetype = "get grade A"; break; } + case GRADE_S: { completetype = "get grade S"; break; } + default: { break; } + } + + if (cn->requirement < GRADE_S) + orbetter = " or better in"; + else + orbetter = " in"; + } + else if (cn->extrainfo1 == 0) + ; + else if (cn->extrainfo1 == 1) + completetype = "get Gold in"; + else + { + if (cn->extrainfo1 == 2) + completetype = "get Silver"; + else if (cn->extrainfo1 == 3) + completetype = "get Bronze"; + orbetter = " or better in"; + } + + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (cup->id != cn->requirement) + continue; + return va("%s%s %s CUP", completetype, orbetter, cup->name); + } + return va("INVALID CUP CONDITION \"%d:%d\"", cn->type, cn->requirement); + } + case UCRP_PODIUMEMERALD: + if (!gamedata->everseenspecial) + return "???"; + return "collect the Emerald"; + case UCRP_PODIUMPRIZE: + if (!gamedata->everseenspecial) + return "???"; + return "collect the prize"; case UCRP_FINISHCOOL: return "finish in good standing"; @@ -1455,7 +1558,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) response |= true; } - if (!demo.playback && Playing() && (gamestate == GS_LEVEL)) + if (!demo.playback && Playing() && (gamestate == GS_LEVEL || K_PodiumRanking() == true)) { for (i = 0; i <= splitscreen; i++) { diff --git a/src/m_cond.h b/src/m_cond.h index 4cbe8a678..f908cb1d0 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -73,6 +73,11 @@ typedef enum UCRP_ISMAP, // gamemap == [map] UCRP_ISCHARACTER, // character == [skin] + UCRP_ISDIFFICULTY, // difficulty >= [difficulty] + + UCRP_PODIUMCUP, // cup == [cup] [optional: >= grade OR place] + UCRP_PODIUMEMERALD, // Get to podium sequence with that cup's emerald + UCRP_PODIUMPRIZE, // Get to podium sequence with that cup's bonus (alternate string version of UCRP_PODIUMEMERALD UCRP_FINISHCOOL, // Finish in good standing UCRP_FINISHALLCAPSULES, // Break all capsules From 01f6eb71f53b202745f189f7121003be6490caa7 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 13:52:28 +0000 Subject: [PATCH 056/103] Optimisation: reduce the number of calls to M_CheckUnlockConditions - For P_Ticker()'s calls to M_UpdateUnlockablesAndExtraEmblems - Do not check non-UCRP_REQUIRESPLAYING conditions - Controlled by a new `boolean doall` parameter to M_UpdateUnlockablesAndExtraEmblems - Most other contexts have this as true - Forced true if update is meant to be silent - Only check UCRP_REQUIRESPLAYING conditions if a relevant property has been touched - Controlled by a new `boolean checkthisframe` property on roundcondition_t - Set in all contexts where roundcondition_t is modified - Would also be set on lap change, but that case is already covered by the following - Check all conditions, both UCRP_REQUIRESPLAYING and not, on: - local player K_HandleLapIncrement - local player P_DoPlayerExit - local player P_DoTimeOver - Controlled by a new `boolean deferredconditioncheck` property on gamedata_t --- src/d_player.h | 3 +++ src/f_finale.c | 2 +- src/g_demo.c | 2 +- src/g_game.c | 12 ++++++------ src/k_collide.c | 3 +++ src/k_kart.c | 18 +++++++++++++++--- src/k_podium.c | 2 +- src/k_pwrlv.c | 4 ++-- src/m_cond.c | 30 ++++++++++++++++++++++-------- src/m_cond.h | 3 ++- src/menus/extras-challenges.c | 4 ++-- src/menus/options-data-erase-1.c | 2 +- src/p_inter.c | 22 ++++++++++++++++------ src/p_mobj.c | 14 +++++++++++++- src/p_setup.c | 2 +- src/p_spec.c | 13 +++++++------ src/p_tick.c | 2 +- src/p_user.c | 9 ++++++++- src/w_wad.c | 2 +- 19 files changed, 106 insertions(+), 43 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index a96102ecd..0bc1d43bc 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -337,6 +337,9 @@ struct botvars_t struct roundconditions_t { + // Reduce the number of checks by only updating when this is true + boolean checkthisframe; + // Trivial Yes/no events across multiple UCRP's boolean fell_off; boolean touched_offroad; diff --git a/src/f_finale.c b/src/f_finale.c index 6e936c9d4..0a802459d 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1083,7 +1083,7 @@ void F_GameEvaluationTicker(void) { ++gamedata->timesBeaten; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } else diff --git a/src/g_demo.c b/src/g_demo.c index f9b088ea4..4c9a80d94 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -4194,7 +4194,7 @@ void G_SaveDemo(void) if (gamedata->eversavedreplay == false) { gamedata->eversavedreplay = true; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } } diff --git a/src/g_game.c b/src/g_game.c index 066f96909..67a971614 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -667,7 +667,7 @@ void G_UpdateRecords(void) S_StartSound(NULL, sfx_ncitem); } - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); gamedata->deferredsave = true; } @@ -3838,7 +3838,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" : ""); - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } @@ -3930,7 +3930,7 @@ static void G_GetNextMap(void) if (gamedata->everseenspecial == false) { gamedata->everseenspecial = true; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } } @@ -4158,7 +4158,7 @@ static void G_DoCompleted(void) gamedata->pendingkeyrounds++; // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); gamedata->deferredsave = true; } @@ -4528,7 +4528,7 @@ void G_LoadGameData(void) { // Don't load at all. // The following used to be in M_ClearSecrets, but that was silly. - M_UpdateUnlockablesAndExtraEmblems(false); + M_UpdateUnlockablesAndExtraEmblems(false, true); return; } @@ -4764,7 +4764,7 @@ void G_LoadGameData(void) gamedata->loaded = true; // Silent update unlockables in case they're out of sync with conditions - M_UpdateUnlockablesAndExtraEmblems(false); + M_UpdateUnlockablesAndExtraEmblems(false, true); return; } diff --git a/src/k_collide.c b/src/k_collide.c index 43e2c04ed..0116f0d49 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -414,7 +414,10 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if (t1->health > 1) { if (t1->target && t1->target->player) + { t1->target->player->roundconditions.landmine_dunk = true; + t1->target->player->roundconditions.checkthisframe = true; + } S_StartSound(t2, sfx_bsnipe); } diff --git a/src/k_kart.c b/src/k_kart.c index deb1227ab..c32a67091 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1133,8 +1133,12 @@ static void K_UpdateOffroad(player_t *player) if (player->offroad > offroadstrength) player->offroad = offroadstrength; - if (player->offroad > (2*offroadstrength) / TICRATE) + if (player->roundconditions.touched_offroad == false + && player->offroad > (2*offroadstrength) / TICRATE) + { player->roundconditions.touched_offroad = true; + player->roundconditions.checkthisframe = true; + } } else player->offroad = 0; @@ -4233,8 +4237,12 @@ void K_ApplyTripWire(player_t *player, tripwirestate_t state) if (state == TRIPSTATE_PASSED) { S_StartSound(player->mo, sfx_ssa015); - if (player->hyudorotimer > 0) + if (player->roundconditions.tripwire_hyuu == false + && player->hyudorotimer > 0) + { player->roundconditions.tripwire_hyuu = true; + player->roundconditions.checkthisframe = true; + } } else if (state == TRIPSTATE_BLOCKED) { @@ -5903,8 +5911,12 @@ void K_DoSneaker(player_t *player, INT32 type) { const fixed_t intendedboost = FRACUNIT/2; - if (player->floorboost != 0) + if (player->roundconditions.touched_sneakerpanel == false + && player->floorboost != 0) + { player->roundconditions.touched_sneakerpanel = true; + player->roundconditions.checkthisframe = true; + } if (player->floorboost == 0 || player->floorboost == 3) { diff --git a/src/k_podium.c b/src/k_podium.c index 807b7f5a4..1bb5d9968 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -290,7 +290,7 @@ void K_FinishCeremony(void) podiumData.ranking = true; // Play the noise now - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 3bc2466d6..f4b307cfd 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -427,7 +427,7 @@ void K_CashInPowerLevels(void) if (gamedataupdate) { - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } @@ -643,7 +643,7 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) { pr->powerlevels[powerType] = yourPower + inc; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } } diff --git a/src/m_cond.c b/src/m_cond.c index 26e43d797..eb5fbc537 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1530,7 +1530,7 @@ static boolean M_CheckUnlockConditions(player_t *player) return ret; } -boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) +boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall) { UINT16 i = 0, response = 0, newkeys = 0; @@ -1546,16 +1546,27 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) // Done first so that emblems are ready before check M_CheckLevelEmblems(); M_CompletionEmblems(); + doall = true; } - response = M_CheckUnlockConditions(NULL); - - while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS - && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) + if (gamedata->deferredconditioncheck == true) { - gamedata->keyspending++; - newkeys++; - response |= true; + // Handle deferred all-condition checks + gamedata->deferredconditioncheck = false; + doall = true; + } + + if (doall) + { + response = M_CheckUnlockConditions(NULL); + + while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS + && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) + { + gamedata->keyspending++; + newkeys++; + response |= true; + } } if (!demo.playback && Playing() && (gamestate == GS_LEVEL || K_PodiumRanking() == true)) @@ -1566,7 +1577,10 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud) continue; if (players[g_localplayers[i]].spectator) continue; + if (!doall && players[g_localplayers[i]].roundconditions.checkthisframe == false) + continue; response |= M_CheckUnlockConditions(&players[g_localplayers[i]]); + players[g_localplayers[i]].roundconditions.checkthisframe = false; } } diff --git a/src/m_cond.h b/src/m_cond.h index f908cb1d0..d3b2b73dc 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -246,6 +246,7 @@ struct gamedata_t // WHENEVER OR NOT WE'RE READY TO SAVE boolean loaded; boolean deferredsave; + boolean deferredconditioncheck; // CONDITION SETS ACHIEVED boolean achieved[MAXCONDITIONSETS]; @@ -327,7 +328,7 @@ void M_ClearStats(void); // Updating conditions and unlockables boolean M_CheckCondition(condition_t *cn, player_t *player); -boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud); +boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall); #define PENDING_CHAOKEYS (UINT16_MAX-1) UINT16 M_GetNextAchievedUnlock(void); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 30229499d..5289d3972 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -205,7 +205,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { UINT16 i, newunlock; - M_UpdateUnlockablesAndExtraEmblems(false); + M_UpdateUnlockablesAndExtraEmblems(false, true); newunlock = M_GetNextAchievedUnlock(); @@ -397,7 +397,7 @@ void M_ChallengesTick(void) { // Unlock animation... also tied directly to the actual unlock! gamedata->unlocked[challengesmenu.currentunlock] = true; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); // Update shown description just in case..? challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index 15c727b05..3a40ed70f 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -66,7 +66,7 @@ static void M_EraseDataResponse(INT32 ch) if (optionsmenu.erasecontext & EC_CHALLENGES) M_ClearSecrets(); - M_UpdateUnlockablesAndExtraEmblems(false); + M_UpdateUnlockablesAndExtraEmblems(false, true); F_StartIntro(); M_ClearMenus(true); diff --git a/src/p_inter.c b/src/p_inter.c index 05579b61c..f4f612101 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -552,7 +552,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (P_IsLocalPlayer(player) && !gamedata->collected[special->health-1]) { gamedata->collected[special->health-1] = gotcollected = true; - if (!M_UpdateUnlockablesAndExtraEmblems(true)) + if (!M_UpdateUnlockablesAndExtraEmblems(true, true)) S_StartSound(NULL, sfx_ncitem); gamedata->deferredsave = true; } @@ -1938,7 +1938,11 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, { case DMG_DEATHPIT: // Respawn kill types - player->roundconditions.fell_off = true; + if (player->roundconditions.fell_off == true) + { + player->roundconditions.fell_off = true; + player->roundconditions.checkthisframe = true; + } K_DoIngameRespawn(player); player->mo->health -= K_DestroyBumpers(player, 1); return false; @@ -2062,9 +2066,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da case MT_SPB: spbpop = (damagetype & DMG_TYPEMASK) == DMG_VOLTAGE; - if (spbpop && source && source->player) + if (spbpop && source && source->player + && source->player->roundconditions.spb_neuter == false) { source->player->roundconditions.spb_neuter = true; + source->player->roundconditions.checkthisframe = true; } break; @@ -2142,10 +2148,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (inflictor && source && source->player) { - if (P_IsKartFieldItem(source->type) - && target->player->airtime > TICRATE/2 - && source->player->airtime > TICRATE/2) + if (source->player->roundconditions.hit_midair == false + && P_IsKartFieldItem(source->type) + && target->player->airtime > TICRATE/2 + && source->player->airtime > TICRATE/2) + { source->player->roundconditions.hit_midair = true; + source->player->roundconditions.checkthisframe = true; + } } // Instant-Death diff --git a/src/p_mobj.c b/src/p_mobj.c index a0f7b268b..a456234e4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3426,7 +3426,19 @@ void P_MobjCheckWater(mobj_t *mobj) return; } - p->roundconditions.wet_player |= (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER|MFE_GOOWATER)); + if (!(p->roundconditions.wet_player & MFE_TOUCHWATER) + && (mobj->eflags & MFE_TOUCHWATER)) + { + p->roundconditions.wet_player |= MFE_TOUCHWATER; + p->roundconditions.checkthisframe = true; + } + + if (!(p->roundconditions.wet_player & MFE_UNDERWATER) + && (mobj->eflags & MFE_UNDERWATER)) + { + p->roundconditions.wet_player |= MFE_UNDERWATER; + p->roundconditions.checkthisframe = true; + } } if (mobj->flags & MF_APPLYTERRAIN) diff --git a/src/p_setup.c b/src/p_setup.c index 64b217225..bf3a0f358 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7965,7 +7965,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) { mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } diff --git a/src/p_spec.c b/src/p_spec.c index 2ebda0d68..e9ad66d67 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2115,6 +2115,12 @@ static void K_HandleLapIncrement(player_t *player) } lastLowestLap = lowestLap; + + if (P_IsLocalPlayer(player)) + { + player->roundconditions.checkthisframe = true; + gamedata->deferredconditioncheck = true; + } } else if (player->starpostnum) { @@ -3427,12 +3433,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha } mo->player->roundconditions.unlocktriggers |= flag; - - // Unlocked something? - if (!demo.playback && M_UpdateUnlockablesAndExtraEmblems(true)) - { - gamedata->deferredsave = true; // only save if unlocked something - } + mo->player->roundconditions.checkthisframe = true; } } break; diff --git a/src/p_tick.c b/src/p_tick.c index f45eecf4a..1a5ae508c 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -618,7 +618,7 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; // TODO would this be laggy with more conditions in play... - if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true)) + if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false)) || (gamedata && gamedata->deferredsave))) G_SaveGameData(true); } diff --git a/src/p_user.c b/src/p_user.c index f957eabf9..29ffbd94a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -521,9 +521,10 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) player->rings += num_rings; - if (player->rings < 0) + if (player->roundconditions.debt_rings == false && player->rings < 0) { player->roundconditions.debt_rings = true; + player->roundconditions.checkthisframe = true; } return num_rings; @@ -1281,7 +1282,11 @@ void P_DoPlayerExit(player_t *player) return; if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) + { legitimateexit = true; + player->roundconditions.checkthisframe = true; + gamedata->deferredconditioncheck = true; + } if (G_GametypeUsesLives() && losing) { @@ -3819,6 +3824,8 @@ void P_DoTimeOver(player_t *player) if (P_IsLocalPlayer(player) && !demo.playback) { legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p + player->roundconditions.checkthisframe = true; + gamedata->deferredconditioncheck = true; } if (netgame && !player->bot && !(gametyperules & GTR_BOSS)) diff --git a/src/w_wad.c b/src/w_wad.c index e0c19e7e5..481ef672e 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -818,7 +818,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) if ((mainfile == false) && (gamedata != NULL) && (gamedata->everloadedaddon == false)) { gamedata->everloadedaddon = true; - M_UpdateUnlockablesAndExtraEmblems(true); + M_UpdateUnlockablesAndExtraEmblems(true, true); G_SaveGameData(true); } From 65a4f33b6fd84b976f63e5d48da3c88bb84075a0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 14:41:15 +0000 Subject: [PATCH 057/103] R_GetEngineClass Before unlockable conditions dependent on Engine Class are added, standardise the system. - enum constant in r_skins.h from A-I + J - Engine Class J is for SF_IRONMAN - The Joker in the pack of cards - Also immediately after I in the alphabet - It's a Jape that works on multiple levels - Integrate into K_UpdateEngineSounds - Ignores Engine Class J - Show the Engine Class in character select extrainfo mode, even in situations character name would have been the only thing shown --- src/k_kart.c | 14 ++------------ src/k_menudraw.c | 18 +++++++++++++----- src/r_skins.c | 19 +++++++++++++++++++ src/r_skins.h | 19 +++++++++++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index c32a67091..ebb85f2dc 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7152,7 +7152,7 @@ static void K_UpdateEngineSounds(player_t *player) const UINT16 buttons = K_GetKartButtons(player); - INT32 class, s, w; // engine class number + INT32 class; // engine class number UINT8 volume = 255; fixed_t volumedampen = FRACUNIT; @@ -7167,17 +7167,7 @@ static void K_UpdateEngineSounds(player_t *player) return; } - s = (player->kartspeed - 1) / 3; - w = (player->kartweight - 1) / 3; - -#define LOCKSTAT(stat) \ - if (stat < 0) { stat = 0; } \ - if (stat > 2) { stat = 2; } - LOCKSTAT(s); - LOCKSTAT(w); -#undef LOCKSTAT - - class = s + (3*w); + class = R_GetEngineClass(player->kartspeed, player->kartweight, 0); // there are no unique sounds for ENGINECLASS_J #if 0 if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! diff --git a/src/k_menudraw.c b/src/k_menudraw.c index eb0cf5e00..4a2ed788c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1488,23 +1488,31 @@ static void M_DrawCharSelectPreview(UINT8 num) if (p->showextra == true) { + INT32 randomskin = 0; switch (p->mdepth) { - case CSSTEP_CHARS: // Character Select grid - V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("Speed %u - Weight %u", p->gridx+1, p->gridy+1)); - break; case CSSTEP_ALTS: // Select clone case CSSTEP_READY: if (p->clonenum < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum] < numskins) { - V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, + V_DrawThinString(x-3, y+12, V_6WIDTHSPACE, skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].name); + randomskin = (skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].flags & SF_IRONMAN); } else { - V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("BAD CLONENUM %u", p->clonenum)); + V_DrawThinString(x-3, y+12, V_6WIDTHSPACE, va("BAD CLONENUM %u", p->clonenum)); } + /* FALLTHRU */ + case CSSTEP_CHARS: // Character Select grid + V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("Class %c (s %c - w %c)", + ('A' + R_GetEngineClass(p->gridx+1, p->gridy+1, randomskin)), + (randomskin + ? '?' : ('1'+p->gridx)), + (randomskin + ? '?' : ('1'+p->gridy)) + )); break; case CSSTEP_COLORS: // Select color if (p->color < numskincolors) diff --git a/src/r_skins.c b/src/r_skins.c index 317729b16..7623f2232 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -299,6 +299,25 @@ INT32 R_SkinAvailable(const char *name) return -1; } +// Returns engine class dependent on skin properties +engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags) +{ + if (flags & SF_IRONMAN) + return ENGINECLASS_J; + + speed = (speed - 1) / 3; + weight = (weight - 1) / 3; + +#define LOCKSTAT(stat) \ + if (stat < 0) { stat = 0; } \ + if (stat > 2) { stat = 2; } + LOCKSTAT(speed); + LOCKSTAT(weight); +#undef LOCKSTAT + + return (speed + (3*weight)); +} + // Auxillary function that actually sets the skin static void SetSkin(player_t *player, INT32 skinnum) { diff --git a/src/r_skins.h b/src/r_skins.h index 692c91619..2319326fd 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -75,6 +75,25 @@ enum facepatches { NUMFACES }; +typedef enum { + ENGINECLASS_A, + ENGINECLASS_B, + ENGINECLASS_C, + + ENGINECLASS_D, + ENGINECLASS_E, + ENGINECLASS_F, + + ENGINECLASS_G, + ENGINECLASS_H, + ENGINECLASS_I, + + ENGINECLASS_J + +} engineclass_t; + +engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags); + /// Externs extern INT32 numskins; extern skin_t skins[MAXSKINS]; From 84e807cfa0ec6be569e7b83e460ee01976615367 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 15:55:14 +0000 Subject: [PATCH 058/103] UCRP_ISENGINECLASS Checks for the player's character's engine class As before, A-I and J are valid classes --- src/deh_soc.c | 15 +++++++++++++++ src/m_cond.c | 9 +++++++++ src/m_cond.h | 1 + 3 files changed, 25 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index e1318a429..5dd4d2138 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2597,6 +2597,21 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) } #endif } + else if (fastcmp(params[0], "ISENGINECLASS")) + { + PARAMCHECK(1); + ty = UCRP_ISENGINECLASS; + if (!params[1][1] + && params[1][0] >= 'A' && params[1][0] <= 'J') + { + re = params[1][0] - 'A'; + } + else + { + deh_warning("engine class requirement \"%s\" invalid for condition ID %d", params[1], id+1); + return; + } + } else if (fastcmp(params[0], "ISDIFFICULTY")) { //PARAMCHECK(1); diff --git a/src/m_cond.c b/src/m_cond.c index eb5fbc537..c2cd117a8 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -796,6 +796,13 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (gamemap == cn->requirement+1); case UCRP_ISCHARACTER: return (player->skin == cn->requirement); + case UCRP_ISENGINECLASS: + return (player->skin < numskins + && R_GetEngineClass( + skins[player->skin].kartspeed, + skins[player->skin].kartweight, + skins[player->skin].flags + ) == (unsigned)cn->requirement); case UCRP_ISDIFFICULTY: if (grandprixinfo.gp == false) return (gamespeed >= cn->requirement); @@ -1265,6 +1272,8 @@ static const char *M_GetConditionString(condition_t *cn) if (cn->requirement < 0 || !skins[cn->requirement].realname[0]) return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement); return va("as %s", skins[cn->requirement].realname); + case UCRP_ISENGINECLASS: + return va("with engine class %c", 'A' + cn->requirement); case UCRP_ISDIFFICULTY: { const char *speedtext = "", *orbetter = ""; diff --git a/src/m_cond.h b/src/m_cond.h index d3b2b73dc..b8e84ddf0 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -73,6 +73,7 @@ typedef enum UCRP_ISMAP, // gamemap == [map] UCRP_ISCHARACTER, // character == [skin] + UCRP_ISENGINECLASS, // engine class [class] UCRP_ISDIFFICULTY, // difficulty >= [difficulty] UCRP_PODIUMCUP, // cup == [cup] [optional: >= grade OR place] From 03b6f50ab86e85358cec476075ce78999e997e21 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 18:10:14 +0000 Subject: [PATCH 059/103] M_DrawChallengePreview: Selection assistance icons - SECRET_SKIN: - Shows the character icon you'd have to pick (Eggman for Eggrobo, etc) - Shows the Engine Class section of the charsel grid you'd have to pick - SECRET_FOLLOWER: - Shows the category icon you'd have to pick (3DB for Whirl, etc) - SECRET_CUP: - Shows the position on the cup grid, INCLUDING previous pages --- src/k_menudraw.c | 102 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 4a2ed788c..fb1977c2e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5004,6 +5004,8 @@ drawborder: } } +#define challengetransparentstrength 8 + static void M_DrawChallengePreview(INT32 x, INT32 y) { unlockable_t *ref = NULL; @@ -5050,12 +5052,57 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) { case SECRET_SKIN: { - INT32 skin = M_UnlockableSkinNum(ref); + INT32 skin = M_UnlockableSkinNum(ref), i; // Draw our character! if (skin != -1) { colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap); + + for (i = 0; i < skin; i++) + { + if (!R_SkinUsable(-1, i, false)) + continue; + if (skins[i].kartspeed != skins[skin].kartspeed) + continue; + if (skins[i].kartweight != skins[skin].kartweight) + continue; + + colormap = R_GetTranslationColormap(i, skins[i].prefcolor, GTC_MENUCACHE); + break; + } + + V_DrawFixedPatch(4*FRACUNIT, (BASEVIDHEIGHT-(4+16))*FRACUNIT, + FRACUNIT, + 0, faceprefix[i][FACE_RANK], + colormap); + + if (i != skin) + { + V_DrawScaledPatch(4, (11 + BASEVIDHEIGHT-(4+16)), 0, W_CachePatchName("ALTSDOT", PU_CACHE)); + } + + V_DrawFadeFill(4+16, (BASEVIDHEIGHT-(4+16)), 16, 16, 0, 31, challengetransparentstrength); + + V_DrawFill(4+16+5, (BASEVIDHEIGHT-(4+16))+1, 1, 14, 0); + V_DrawFill(4+16+5+5, (BASEVIDHEIGHT-(4+16))+1, 1, 14, 0); + V_DrawFill(4+16+1, (BASEVIDHEIGHT-(4+16))+5, 14, 1, 0); + V_DrawFill(4+16+1, (BASEVIDHEIGHT-(4+16))+5+5, 14, 1, 0); + + // The following is a partial duplication of R_GetEngineClass + { + INT32 s = (skins[skin].kartspeed - 1)/3; + INT32 w = (skins[skin].kartweight - 1)/3; + + #define LOCKSTAT(stat) \ + if (stat < 0) { stat = 0; } \ + if (stat > 2) { stat = 2; } + LOCKSTAT(s); + LOCKSTAT(w); + #undef LOCKSTAT + + V_DrawFill(4+16+1 + (s*5), (BASEVIDHEIGHT-(4+16))+1 + (w*5), 4, 4, 0); + } } break; } @@ -5076,14 +5123,27 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) UINT16 col = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, cv_playercolor[0].value); colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); M_DrawFollowerSprite(x - 16, y, fskin, false, 0, colormap, NULL); + + if (followers[fskin].category < numfollowercategories) + { + V_DrawFixedPatch(4*FRACUNIT, (BASEVIDHEIGHT-(4+16))*FRACUNIT, + FRACUNIT, + 0, W_CachePatchName(followercategories[followers[fskin].category].icon, PU_CACHE), + NULL); + } } break; } case SECRET_CUP: { levelsearch_t templevelsearch; + UINT32 i, id, maxid, offset; + cupheader_t *temp = M_UnlockableCup(ref); - templevelsearch.cup = M_UnlockableCup(ref); + if (!temp) + break; + + templevelsearch.cup = temp; templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE); templevelsearch.cupmode = true; templevelsearch.timeattack = false; @@ -5091,6 +5151,43 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) M_DrawCupPreview(146, &templevelsearch); + maxid = id = (temp->id % 14); + offset = (temp->id - id) * 2; + while (temp && maxid < 14) + { + maxid++; + temp = temp->next; + } + + V_DrawFadeFill(4, (BASEVIDHEIGHT-(4+16)), 28 + offset, 16, 0, 31, challengetransparentstrength); + + for (i = 0; i < offset; i += 4) + { + V_DrawFill(4+1 + i, (BASEVIDHEIGHT-(4+16))+3, 2, 2, 15); + V_DrawFill(4+1 + i, (BASEVIDHEIGHT-(4+16))+8+3, 2, 2, 15); + } + + for (i = 0; i < 7; i++) + { + if (templevelsearch.cup && id == i) + { + V_DrawFill(offset + 4 + (i*4), (BASEVIDHEIGHT-(4+16)), 4, 8, 0); + } + else if (i < maxid) + { + V_DrawFill(offset + 4+1 + (i*4), (BASEVIDHEIGHT-(4+16))+3, 2, 2, 0); + } + + if (templevelsearch.cup && (templevelsearch.cup->id % 14) == i+7) + { + V_DrawFill(offset + 4 + (i*4), (BASEVIDHEIGHT-(4+16))+8, 4, 8, 0); + } + else if (i+7 < maxid) + { + V_DrawFill(offset + 4+1 + (i*4), (BASEVIDHEIGHT-(4+16))+8+3, 2, 2, 0); + } + } + break; } case SECRET_MAP: @@ -5247,7 +5344,6 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } } -#define challengetransparentstrength 8 #define challengesgridstep 22 #define challengekeybarwidth 50 From 0b71b2f71fc075611c26eee9d6e2598399612a17 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 20:16:03 +0000 Subject: [PATCH 060/103] M_DrawStatistics: Fix "Hours/Minutes/Seconds" plurality if there's only 1 --- src/k_menudraw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index fb1977c2e..1d848e974 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5729,18 +5729,19 @@ void M_DrawStatistics(void) { if (besttime >= 24) { - strcat(beststr, va("%u days, ", besttime/24)); + strcat(beststr, va("%u day%s, ", besttime/24, (besttime < 48 ? "" : "s"))); besttime %= 24; } - strcat(beststr, va("%u hours, ", besttime)); + strcat(beststr, va("%u hour%s, ", besttime, (besttime == 1 ? "" : "s"))); } besttime = G_TicsToMinutes(gamedata->totalplaytime, false); if (besttime) { - strcat(beststr, va("%u minutes, ", besttime)); + strcat(beststr, va("%u minute%s, ", besttime, (besttime == 1 ? "" : "s"))); } - strcat(beststr, va("%i seconds", G_TicsToSeconds(gamedata->totalplaytime))); + besttime = G_TicsToSeconds(gamedata->totalplaytime); + strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s"))); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 22, V_6WIDTHSPACE, beststr); beststr[0] = 0; From 241475155b96c005b5f7f3dcd261ceef13788a6f Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 20:17:46 +0000 Subject: [PATCH 061/103] Rename "Break the Capsules" to "Prison Break" We had this collective consciousness bigbrain moment in VC together, and it can literally only happen in this branch because unlocks.pk3 is the only main-game asset that needs to change for it Solves the big problem we had with mixing up Item Capsules and ~~Battle Capsules~~ PRISON EGGS --- src/deh_soc.c | 10 +++++----- src/deh_tables.c | 2 +- src/doomstat.h | 2 +- src/g_game.c | 6 +++--- src/hu_stuff.c | 8 ++++---- src/k_battle.c | 14 +++++++------- src/k_battle.h | 2 +- src/k_hud.c | 10 +++++----- src/k_hud_track.cpp | 2 +- src/k_kart.c | 14 +++++++------- src/k_menudraw.c | 10 +++++----- src/k_podium.c | 2 +- src/k_rank.c | 10 +++++----- src/k_rank.h | 4 ++-- src/k_roulette.c | 2 +- src/m_cond.c | 14 +++++++------- src/m_cond.h | 6 +++--- src/menus/play-local-1.c | 4 ++-- src/p_inter.c | 2 +- src/p_mobj.c | 2 +- src/p_saveg.c | 4 ++-- src/p_setup.c | 2 +- 22 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 5dd4d2138..14544c2fc 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2286,8 +2286,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_ENCORE; else if (fastcmp(word2, "TIMEATTACK")) unlockables[num].type = SECRET_TIMEATTACK; - else if (fastcmp(word2, "BREAKTHECAPSULES")) - unlockables[num].type = SECRET_BREAKTHECAPSULES; + else if (fastcmp(word2, "PRISONBREAK")) + unlockables[num].type = SECRET_PRISONBREAK; else if (fastcmp(word2, "SPECIALATTACK")) unlockables[num].type = SECRET_SPECIALATTACK; else if (fastcmp(word2, "SPBATTACK")) @@ -2394,8 +2394,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) x1 = GDGT_RACE; else if (fastcmp(params[2], "BATTLE")) x1 = GDGT_BATTLE; - else if (fastcmp(params[2], "CAPSULE")) - x1 = GDGT_CAPSULES; + else if (fastcmp(params[2], "PRISONS")) + x1 = GDGT_PRISONS; else if (fastcmp(params[2], "SPECIAL")) x1 = GDGT_SPECIAL; else if (fastcmp(params[2], "CUSTOM")) @@ -2557,7 +2557,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) else if ((offset=0) || fastcmp(params[0], "PREFIX_GRANDPRIX") || (++offset && fastcmp(params[0], "PREFIX_BONUSROUND")) || (++offset && fastcmp(params[0], "PREFIX_TIMEATTACK")) - || (++offset && fastcmp(params[0], "PREFIX_BREAKTHECAPSULES")) + || (++offset && fastcmp(params[0], "PREFIX_PRISONBREAK")) || (++offset && fastcmp(params[0], "PREFIX_SEALEDSTAR"))) { //PARAMCHECK(1); diff --git a/src/deh_tables.c b/src/deh_tables.c index 9cae9323e..3e72a1900 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5836,7 +5836,7 @@ const char *const GAMETYPERULE_LIST[] = { "KARMA", "ITEMARROWS", - "CAPSULES", + "PRISONS", "CATCHER", "ROLLINGSTART", "SPECIALSTART", diff --git a/src/doomstat.h b/src/doomstat.h index ba12196c9..8496c27b5 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -552,7 +552,7 @@ enum GameTypeRules GTR_ITEMARROWS = 1<<9, // Show item box arrows above players // Bonus gametype rules - GTR_CAPSULES = 1<<10, // Can enter Break The Capsules mode + GTR_PRISONS = 1<<10, // Can enter Prison Break mode GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) GTR_SPECIALSTART = 1<<13, // White fade instant start diff --git a/src/g_game.c b/src/g_game.c index 67a971614..3a1280347 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3274,7 +3274,7 @@ static gametype_t defaultgametypes[] = { "Battle", "GT_BATTLE", - GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, + GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_PRISONS|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, TOL_BATTLE, int_scoreortimeattack, 0, @@ -4150,7 +4150,7 @@ static void G_DoCompleted(void) if (gametype == GT_RACE) roundtype = GDGT_RACE; else if (gametype == GT_BATTLE) - roundtype = (battlecapsules ? GDGT_CAPSULES : GDGT_BATTLE); + roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) roundtype = GDGT_SPECIAL; @@ -4177,7 +4177,7 @@ static void G_DoCompleted(void) G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; - grandprixinfo.rank.capsules += numtargets; + grandprixinfo.rank.prisons += numtargets; grandprixinfo.rank.position = MAXPLAYERS; for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 44264b48f..5a8069414 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2474,8 +2474,8 @@ static void HU_DrawRankings(void) // draw the current gametype in the lower right if (grandprixinfo.gp == true) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Grand Prix"); - else if (battlecapsules) - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Capsules"); + else if (battleprisons) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Prisons"); else if (gametype >= 0 && gametype < numgametypes) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); @@ -2533,11 +2533,11 @@ static void HU_DrawRankings(void) } // Right hand side - if (battlecapsules == true) + if (battleprisons == true) { if (numtargets < maptargets) { - V_DrawCenteredString(256, 8, 0, "CAPSULES"); + V_DrawCenteredString(256, 8, 0, "PRISONS"); V_DrawCenteredString(256, 16, hilicol, va("%d", maptargets - numtargets)); } } diff --git a/src/k_battle.c b/src/k_battle.c index 38b86abaf..3f333180d 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -26,7 +26,7 @@ struct battleovertime battleovertime; // Capsules mode enabled for this map? -boolean battlecapsules = false; +boolean battleprisons = false; // box respawning in battle mode INT32 nummapboxes = 0; @@ -38,7 +38,7 @@ UINT8 numtargets = 0; // Capsules busted INT32 K_StartingBumperCount(void) { - if (battlecapsules) + if (battleprisons) return 1; // always 1 hit in Break the Capsules return cv_kartbumpers.value; @@ -95,7 +95,7 @@ void K_CheckBumpers(void) UINT8 nobumpers = 0; UINT8 eliminated = 0; - const boolean singleplayer = (battlecapsules || bossinfo.valid); + const boolean singleplayer = (battleprisons || bossinfo.valid); if (!(gametyperules & GTR_BUMPERS)) return; @@ -145,7 +145,7 @@ void K_CheckBumpers(void) if (numingame <= 1) { - if ((gametyperules & GTR_CAPSULES) && (K_CanChangeRules(true) == true)) + if ((gametyperules & GTR_PRISONS) && (K_CanChangeRules(true) == true)) { // Reset map to turn on battle capsules if (server) @@ -335,7 +335,7 @@ void K_RunPaperItemSpawners(void) UINT8 pcount = 0; INT16 i; - if (battlecapsules) + if (battleprisons) { // Gametype uses paper items, but this specific expression doesn't return; @@ -770,7 +770,7 @@ void K_BattleInit(boolean singleplayercontext) { size_t i; - if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules) + if ((gametyperules & GTR_PRISONS) && singleplayercontext && !battleprisons) { mapthing_t *mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) @@ -779,7 +779,7 @@ void K_BattleInit(boolean singleplayercontext) P_SpawnMapThing(mt); } - battlecapsules = true; + battleprisons = true; } if (gametyperules & GTR_BUMPERS) diff --git a/src/k_battle.h b/src/k_battle.h index f64cfa967..b1957d9b1 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -18,7 +18,7 @@ extern struct battleovertime fixed_t x, y, z; ///< Position to center on } battleovertime; -extern boolean battlecapsules; +extern boolean battleprisons; extern INT32 nummapboxes, numgotboxes; // keep track of spawned battle mode items extern UINT8 maptargets, numtargets; diff --git a/src/k_hud.c b/src/k_hud.c index 7eeb9f596..720b205a5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2839,7 +2839,7 @@ static void K_drawKartBumpersOrKarma(void) V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash); - if (battlecapsules) + if (battleprisons) { V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankcapsule, NULL); @@ -2893,7 +2893,7 @@ static void K_drawKartBumpersOrKarma(void) } else { - if (battlecapsules) + if (battleprisons) { if (numtargets > 9 && maptargets > 9) V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulestickerwide, NULL); @@ -4152,7 +4152,7 @@ static void K_drawBattleFullscreen(void) if (K_IsPlayerLosing(stplyr)) p = kp_battlelose; - else if (stplyr->position == 1 && (!battlecapsules || numtargets >= maptargets)) + else if (stplyr->position == 1 && (!battleprisons || numtargets >= maptargets)) p = kp_battlewin; V_DrawFixedPatch(x<pflags & PF_NOCONTEST) return true; - if (battlecapsules && numtargets == 0) + if (battleprisons && numtargets == 0) return true; // Didn't even TRY? - if (battlecapsules || (gametyperules & GTR_BOSS)) + if (battleprisons || (gametyperules & GTR_BOSS)) return (player->bumpers <= 0); // anything short of DNF is COOL if (player->position == 1) @@ -561,7 +561,7 @@ boolean K_TimeAttackRules(void) return true; } - if (battlecapsules == true) + if (battleprisons == true) { // Break the Capsules always uses Time Attack // rules, since you can bring 2-4 players in @@ -4362,7 +4362,7 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) } else if (player->bumpers == 0 && prevBumpers > 0) { - if (battlecapsules || bossinfo.valid) + if (battleprisons || bossinfo.valid) { player->pflags |= (PF_NOCONTEST|PF_ELIMINATED); } @@ -11681,7 +11681,7 @@ tic_t K_TimeLimitForGametype(void) // Grand Prix if (!K_CanChangeRules(true)) { - if (battlecapsules) + if (battleprisons) { return 20*TICRATE; } @@ -11695,7 +11695,7 @@ tic_t K_TimeLimitForGametype(void) } // No time limit for Break the Capsules FREE PLAY - if (battlecapsules) + if (battleprisons) { return 0; } @@ -11720,7 +11720,7 @@ UINT32 K_PointLimitForGametype(void) return cv_pointlimit.value; } - if (battlecapsules || bossinfo.valid) + if (battleprisons || bossinfo.valid) { return 0; } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 1d848e974..273ffdf95 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4836,7 +4836,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili categoryid = '6'; break; case SECRET_TIMEATTACK: - case SECRET_BREAKTHECAPSULES: + case SECRET_PRISONBREAK: case SECRET_SPECIALATTACK: case SECRET_SPBATTACK: categoryid = '7'; @@ -4918,7 +4918,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili case SECRET_TIMEATTACK: iconid = 7; break; - case SECRET_BREAKTHECAPSULES: + case SECRET_PRISONBREAK: iconid = 8; break; case SECRET_SPECIALATTACK: @@ -5221,7 +5221,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = tamapcache; break; } - case SECRET_BREAKTHECAPSULES: + case SECRET_PRISONBREAK: { static UINT16 btcmapcache = NEXTMAP_INVALID; if (btcmapcache > nummapheaders) @@ -5769,9 +5769,9 @@ void M_DrawStatistics(void) strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE])); - if (gamedata->roundsplayed[GDGT_CAPSULES] > 0) + if (gamedata->roundsplayed[GDGT_PRISONS] > 0) { - strcat(beststr, va(", %u Capsule", gamedata->roundsplayed[GDGT_CAPSULES])); + strcat(beststr, va(", %u Prisons", gamedata->roundsplayed[GDGT_PRISONS])); } strcat(beststr, va(", %u Battle", gamedata->roundsplayed[GDGT_BATTLE])); diff --git a/src/k_podium.c b/src/k_podium.c index 1bb5d9968..b9c6ca831 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -516,7 +516,7 @@ void K_CeremonyDrawer(void) case 5: { V_DrawString(x, y, V_ALLOWLOWERCASE, - va("CAPSULES: %d / %d", podiumData.rank.capsules, podiumData.rank.totalCapsules) + va("PRISONS: %d / %d", podiumData.rank.prisons, podiumData.rank.totalPrisons) ); break; } diff --git a/src/k_rank.c b/src/k_rank.c index 84d98519d..76812564c 100644 --- a/src/k_rank.c +++ b/src/k_rank.c @@ -338,7 +338,7 @@ void K_InitGrandPrixRank(gpRank_t *rankData) continue; } - rankData->totalCapsules += RankCapsules_CountFromMap(virt); + rankData->totalPrisons += RankCapsules_CountFromMap(virt); vres_Free(virt); } } @@ -363,9 +363,9 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) const INT32 positionWeight = 150; const INT32 pointsWeight = 100; const INT32 lapsWeight = 100; - const INT32 capsulesWeight = 100; + const INT32 prisonsWeight = 100; const INT32 ringsWeight = 50; - const INT32 total = positionWeight + pointsWeight + lapsWeight + capsulesWeight + ringsWeight; + const INT32 total = positionWeight + pointsWeight + lapsWeight + prisonsWeight + ringsWeight; const INT32 continuesPenalty = 20; INT32 ours = 0; @@ -388,9 +388,9 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) ours += (rankData->laps * lapsWeight) / rankData->totalLaps; } - if (rankData->totalCapsules > 0) + if (rankData->totalPrisons > 0) { - ours += (rankData->capsules * capsulesWeight) / rankData->totalCapsules; + ours += (rankData->prisons * prisonsWeight) / rankData->totalPrisons; } if (rankData->totalRings > 0) diff --git a/src/k_rank.h b/src/k_rank.h index 7903de708..eb2c420cd 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -35,8 +35,8 @@ struct gpRank_t UINT32 continuesUsed; - UINT32 capsules; - UINT32 totalCapsules; + UINT32 prisons; + UINT32 totalPrisons; UINT32 rings; UINT32 totalRings; diff --git a/src/k_roulette.c b/src/k_roulette.c index 57f948880..bd50dced8 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1240,7 +1240,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet kartitems_t *presetlist = K_KartItemReelTimeAttack; // If the objective is not to go fast, it's to cause serious damage. - if (gametyperules & GTR_CAPSULES) + if (gametyperules & GTR_PRISONS) { presetlist = K_KartItemReelBreakTheCapsules; } diff --git a/src/m_cond.c b/src/m_cond.c index c2cd117a8..40d38e7c8 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -27,7 +27,7 @@ #include "k_kart.h" // K_IsPLayerLosing #include "k_grandprix.h" // grandprixinfo -#include "k_battle.h" // battlecapsules +#include "k_battle.h" // battleprisons #include "k_specialstage.h" // specialstageinfo #include "k_podium.h" #include "k_pwrlv.h" @@ -786,8 +786,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return ((grandprixinfo.gp == true) && (grandprixinfo.eventmode == GPEVENT_BONUS)); case UCRP_PREFIX_TIMEATTACK: return (modeattacking != ATTACKING_NONE); - case UCRP_PREFIX_BREAKTHECAPSULES: - return ((gametyperules & GTR_CAPSULES) && battlecapsules); + case UCRP_PREFIX_PRISONBREAK: + return ((gametyperules & GTR_PRISONS) && battleprisons); case UCRP_PREFIX_SEALEDSTAR: return (specialstageinfo.valid == true); @@ -833,7 +833,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && M_NotFreePlay(player) && !K_IsPlayerLosing(player)); case UCRP_FINISHALLCAPSULES: - return (battlecapsules + return (battleprisons && !(player->pflags & PF_NOCONTEST) //&& M_NotFreePlay(player) && numtargets >= maptargets); @@ -1004,7 +1004,7 @@ static const char *M_GetConditionString(condition_t *cn) case GDGT_RACE: work = " Race"; break; - case GDGT_CAPSULES: + case GDGT_PRISONS: work = " Capsule"; break; case GDGT_BATTLE: @@ -1245,8 +1245,8 @@ static const char *M_GetConditionString(condition_t *cn) if (!M_SecretUnlocked(SECRET_TIMEATTACK, true)) return NULL; return "TIME ATTACK:"; - case UCRP_PREFIX_BREAKTHECAPSULES: - return "BREAK THE CAPSULES:"; + case UCRP_PREFIX_PRISONBREAK: + return "PRISON BREAK:"; case UCRP_PREFIX_SEALEDSTAR: if (!gamedata->everseenspecial) return NULL; diff --git a/src/m_cond.h b/src/m_cond.h index b8e84ddf0..3dafef72f 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -66,7 +66,7 @@ typedef enum UCRP_PREFIX_GRANDPRIX = UCRP_REQUIRESPLAYING, // GRAND PRIX: UCRP_PREFIX_BONUSROUND, // BONUS ROUND: UCRP_PREFIX_TIMEATTACK, // TIME ATTACK: - UCRP_PREFIX_BREAKTHECAPSULES, // BREAK THE CAPSULES: + UCRP_PREFIX_PRISONBREAK, // PRISON BREAK: UCRP_PREFIX_SEALEDSTAR, // SEALED STAR: UCRP_PREFIX_ISMAP, // name of [map]: @@ -188,7 +188,7 @@ typedef enum // Menu restrictions SECRET_TIMEATTACK, // Permit Time attack - SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack + SECRET_PRISONBREAK, // Permit SP Prison attack SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) SECRET_SPBATTACK, // Permit SPB mode of Time attack @@ -234,7 +234,7 @@ typedef enum typedef enum { GDGT_RACE, GDGT_BATTLE, - GDGT_CAPSULES, + GDGT_PRISONS, GDGT_SPECIAL, GDGT_CUSTOM, GDGT_MAX diff --git a/src/menus/play-local-1.c b/src/menus/play-local-1.c index 3d0e2c22f..21f127e0a 100644 --- a/src/menus/play-local-1.c +++ b/src/menus/play-local-1.c @@ -12,7 +12,7 @@ menuitem_t PLAY_GamemodesMenu[] = {IT_STRING | IT_CALL, "Battle", "It's last kart standing in this free-for-all!", "MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, - {IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!", + {IT_STRING | IT_CALL, "Prisons", "Bust up all of the Prison Eggs in record time!", NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE}, {IT_STRING | IT_CALL, "Special", "Strike your target and secure the prize!", @@ -42,7 +42,7 @@ void M_SetupGametypeMenu(INT32 choice) { boolean anyunlocked = false; - if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) + if (M_SecretUnlocked(SECRET_PRISONBREAK, true)) { // Re-add Capsules PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; diff --git a/src/p_inter.c b/src/p_inter.c index f4f612101..6b4ce2810 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -808,7 +808,7 @@ void P_CheckPointLimit(void) if (!(gametyperules & GTR_POINTLIMIT)) return; - if (battlecapsules) + if (battleprisons) return; // pointlimit is nonzero, check if it's been reached by this player diff --git a/src/p_mobj.c b/src/p_mobj.c index a456234e4..5a11045c4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9446,7 +9446,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { if (gametyperules & GTR_PAPERITEMS) { - if (battlecapsules == true) + if (battleprisons == true) { ; } diff --git a/src/p_saveg.c b/src/p_saveg.c index e0db5fdb0..4e4f5a483 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4999,7 +4999,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) // SRB2kart WRITEINT32(save->p, numgotboxes); WRITEUINT8(save->p, numtargets); - WRITEUINT8(save->p, battlecapsules); + WRITEUINT8(save->p, battleprisons); WRITEUINT8(save->p, gamespeed); WRITEUINT8(save->p, numlaps); @@ -5168,7 +5168,7 @@ static inline boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) // SRB2kart numgotboxes = READINT32(save->p); numtargets = READUINT8(save->p); - battlecapsules = (boolean)READUINT8(save->p); + battleprisons = (boolean)READUINT8(save->p); gamespeed = READUINT8(save->p); numlaps = READUINT8(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index bf3a0f358..e2058b7ae 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7040,7 +7040,7 @@ static void P_InitLevelSettings(void) nummaprings = 0; nummapboxes = numgotboxes = 0; maptargets = numtargets = 0; - battlecapsules = false; + battleprisons = false; // emerald hunt hunt1 = hunt2 = hunt3 = NULL; From 425f26091411abcef70803ff681599c54b887b13 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 20:41:00 +0000 Subject: [PATCH 062/103] Never consider TEST RUN locked - Repairs access to TEST RUN cup - We want to make it an unlockable later, so... - M_MapLocked data types corrected --- src/m_cond.c | 4 ++-- src/m_cond.h | 2 +- src/menus/transient/level-select.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 40d38e7c8..6ddbed1f9 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1838,7 +1838,7 @@ boolean M_CupLocked(cupheader_t *cup) return false; } -boolean M_MapLocked(INT32 mapnum) +boolean M_MapLocked(UINT16 mapnum) { UINT8 i; @@ -1851,7 +1851,7 @@ boolean M_MapLocked(INT32 mapnum) if (marathonmode) return false; - if (!mapnum || mapnum > nummapheaders) + if (mapnum <= 1 || mapnum > nummapheaders) return false; if (!mapheaderinfo[mapnum-1]) diff --git a/src/m_cond.h b/src/m_cond.h index 3dafef72f..a8c66c98e 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -341,7 +341,7 @@ UINT8 M_CompletionEmblems(void); boolean M_CheckNetUnlockByID(UINT8 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_CupLocked(cupheader_t *cup); -boolean M_MapLocked(INT32 mapnum); +boolean M_MapLocked(UINT16 mapnum); INT32 M_CountMedals(boolean all, boolean extraonly); // Emblem shit diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index a8f4203af..19f7ca18e 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -58,7 +58,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) return false; // Check for TOL - if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) + if (mapheaderinfo[mapnum]->typeoflevel && !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; // Should the map be hidden? From 45891ac22f4f80be77686ffade68e0efb0948b97 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 12 Mar 2023 20:53:46 +0000 Subject: [PATCH 063/103] G_GetNextMap: Go to TEST RUN when the next Race map in a GP is invalid Resolves #366 --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 3a1280347..c406addd6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3980,7 +3980,7 @@ static void G_GetNextMap(void) } else { - nextmap = prevmap; // Prevent uninitialised use + nextmap = 0; // Prevent uninitialised use -- go to TEST RUN, it's very obvious } grandprixinfo.roundnum++; From 756feaa20b5d4eaccca80669de3796ee93101701 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 13:22:57 +0000 Subject: [PATCH 064/103] M_CanShowLevelInList: Do not permit Test Run in time attack Also improves the associated comment and indentation --- src/menus/transient/level-select.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 19f7ca18e..8550339e9 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -57,8 +57,9 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - // Check for TOL - if (mapheaderinfo[mapnum]->typeoflevel && !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) + // Check for TOL (permits TEST RUN outside of time attack) + if ((levelsearch->timeattack || mapheaderinfo[mapnum]->typeoflevel) + && !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; // Should the map be hidden? From b5be97cbfc1c4a6ef89f265f51e114ea5c547086 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 14:25:44 +0000 Subject: [PATCH 065/103] level-select.c: Fix incorrect responses for level-finding functions - Fix the case where invalid levelsearch_t were providing values of false instead of NEXTMAP_INVALID - Fix the case where M_GetFirstLevelInList was not returning NEXTMAP_INVALID for rearching nummapheaders --- src/menus/transient/level-select.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 8550339e9..6cede5901 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -98,7 +98,7 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) INT16 i, count = 0; if (!levelsearch) - return false; + return 0; if (levelsearch->cup) { @@ -127,7 +127,7 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) INT16 mapnum = NEXTMAP_INVALID; if (!levelsearch) - return false; + return NEXTMAP_INVALID; if (levelsearch->cup) { @@ -152,6 +152,9 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) for (mapnum = 0; mapnum < nummapheaders; mapnum++) if (M_CanShowLevelInList(mapnum, levelsearch)) break; + + if (mapnum >= nummapheaders) + mapnum = NEXTMAP_INVALID; } return mapnum; @@ -160,7 +163,7 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) { if (!levelsearch) - return false; + return NEXTMAP_INVALID; if (levelsearch->cup) { From 07f7be03a1c406b09ecb5b3a543d787dbf6ab307 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 15:11:14 +0000 Subject: [PATCH 066/103] M_CupSelectHandler: Fix data types --- src/menus/transient/cup-select.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index 23a85e4e0..b4ef37493 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -84,7 +84,7 @@ void M_CupSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { - INT16 count; + UINT16 count; cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; cupheader_t *oldcup = levellist.levelsearch.cup; @@ -94,7 +94,7 @@ void M_CupSelectHandler(INT32 choice) count = M_CountLevelsToShowInList(&levellist.levelsearch); if ((!newcup) - || (count <= 0) + || (count == 0) || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); From 1d3b5adfdf0a8015017a7591a1eeb329a319d02b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 16:18:07 +0000 Subject: [PATCH 067/103] "Monitor" parameter for cups - Defaults to 1, AKA Sonic 1/2 monitor - Set to 2 for Sonic 3k monitor - Supports a range of 1-9 and A-Z - Permits fine-grained rapid-prototype stretch goal Chaotix monitor stuff later in development --- src/deh_soc.c | 11 ++++++++++- src/dehacked.c | 1 + src/doomstat.h | 1 + src/k_menudraw.c | 22 ++++++++++++++++------ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 14544c2fc..764d08628 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3432,7 +3432,16 @@ void readcupheader(MYFILE *f, cupheader_t *cup) i = atoi(word2); // used for numerical settings strupr(word2); - if (fastcmp(word, "ICON")) + if (fastcmp(word, "MONITOR")) + { + if (i > 0 && i < 10) + cup->monitor = i; + else if (!word2[0] || word2[1] != '\0' || word2[0] == '0') + deh_warning("%s Cup: Invalid monitor type \"%s\" (should be 1-9 or A-Z)\n", cup->name, word2); + else + cup->monitor = (word2[0] - 'A') + 10; + } + else if (fastcmp(word, "ICON")) { deh_strlcpy(cup->icon, word2, sizeof(cup->icon), va("%s Cup: icon", cup->name)); diff --git a/src/dehacked.c b/src/dehacked.c index 7a916c950..9e50a9f42 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -486,6 +486,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); cup->id = numkartcupheaders; + cup->monitor = 1; 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 8496c27b5..ad4bf3b14 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -375,6 +375,7 @@ struct customoption_t struct cupheader_t { UINT16 id; ///< Cup ID + UINT8 monitor; ///< Monitor graphic 1-9 or A-Z char name[15]; ///< Cup title (14 chars) char icon[9]; ///< Name of the icon patch char *levellist[CUPCACHE_MAX]; ///< List of levels that belong to this cup diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 273ffdf95..01089958e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2134,7 +2134,7 @@ void M_DrawCupSelect(void) INT16 x, y; INT16 icony = 7; char status = 'A'; - UINT8 monitor = 1; + char monitor; INT32 rankx = 0; if (!cupgrid.builtgrid[id]) @@ -2176,14 +2176,24 @@ void M_DrawCupSelect(void) colormap = NULL; } - if (templevelsearch.cup->emeraldnum > 7) { - monitor = 2; - icony = 5; - rankx = 2; + if (templevelsearch.cup->monitor < 10) + { + monitor = '0' + templevelsearch.cup->monitor; + + if (templevelsearch.cup->monitor == '2') + { + icony = 5; + rankx = 2; + } + } + else + { + monitor = 'A' + (templevelsearch.cup->monitor - 10); + } } - patch = W_CachePatchName(va("CUPMON%d%c", monitor, status), PU_CACHE); + patch = W_CachePatchName(va("CUPMON%c%c", monitor, status), PU_CACHE); x = 14 + (i*42); y = 20 + (j*44) - (30*menutransition.tics); From 1701662b6be6439d401023ead5a375919b85f9ed Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 16:44:21 +0000 Subject: [PATCH 068/103] Lost and Found - When selecting levels: - If the gametype uses cups - and a map has no cup - and you're not in Grand Prix mode - show those maps in a quasi-cup called "Lost and Found". - Implementation details: - a few == checks for the pointer to `cupheader_t dummy_lostandfound` - Otherwise most of the apparatus was built as part of prior art! --- src/k_menu.h | 2 + src/k_menudraw.c | 16 ++++- src/menus/transient/level-select.c | 101 ++++++++++++++++++++--------- 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index cb7e79a10..8fbaed1bf 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -743,6 +743,8 @@ extern struct levellist_s { boolean netgame; // Start the game in an actual server } levellist; +extern cupheader_t dummy_lostandfound; + boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch); UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch); UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 01089958e..94a390e62 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2092,7 +2092,11 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); - if (levelsearch->cup) + if (levelsearch->cup == &dummy_lostandfound) + { + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, "Lost and Found"); + } + else if (levelsearch->cup) { boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); @@ -2176,6 +2180,12 @@ void M_DrawCupSelect(void) colormap = NULL; } + if (templevelsearch.cup == &dummy_lostandfound) + { + // No cup? Lost and found! + monitor = '0'; + } + else { if (templevelsearch.cup->monitor < 10) { @@ -2200,7 +2210,9 @@ void M_DrawCupSelect(void) V_DrawFixedPatch((x)*FRACUNIT, (y)<cupmode - && (levelsearch->timeattack || !levelsearch->cup) - && mapheaderinfo[mapnum]->cup != levelsearch->cup) - return false; + if (levelsearch->cupmode) + { + cupheader_t *cup = (levelsearch->cup == &dummy_lostandfound) ? NULL : levelsearch->cup; + + if ((!cup || levelsearch->timeattack) + && mapheaderinfo[mapnum]->cup != cup) + return false; + } // Finally, the most complex check: does the map have lock conditions? if (levelsearch->checklocked) @@ -100,7 +106,7 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) if (!levelsearch) return 0; - if (levelsearch->cup) + if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound) { if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) return 0; @@ -129,7 +135,7 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) if (!levelsearch) return NEXTMAP_INVALID; - if (levelsearch->cup) + if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound) { if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) { @@ -165,7 +171,7 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) if (!levelsearch) return NEXTMAP_INVALID; - if (levelsearch->cup) + if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound) { mapnum = NEXTMAP_INVALID; (*i)++; @@ -213,6 +219,9 @@ boolean M_LevelListFromGametype(INT16 gt) { cupgrid.cappages = 0; cupgrid.builtgrid = NULL; + dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID; + + first = false; } levellist.newgametype = gt; @@ -232,8 +241,6 @@ boolean M_LevelListFromGametype(INT16 gt) levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); CV_SetValue(&cv_dummyspbattack, 0); - - first = false; } // Obviously go to Cup Select in gametypes that have cups. @@ -269,6 +276,31 @@ boolean M_LevelListFromGametype(INT16 gt) } memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); + // The following doubles the size of the buffer if necessary. +#define GRID_INSERTCUP \ + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) \ + { \ + const size_t firstlen = cupgrid.cappages * pagelen; \ + cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, \ + firstlen * 2, \ + PU_STATIC, NULL); \ + \ + if (!cupgrid.builtgrid) \ + { \ + I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); \ + } \ + \ + cupgrid.cappages *= 2; \ + } \ + \ + cupgrid.builtgrid[currentid] = templevelsearch.cup; + +#define GRID_FOCUSCUP \ + cupgrid.x = currentid % CUPMENU_COLUMNS; \ + cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; \ + cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); \ + currentvalid = true; + while (templevelsearch.cup) { templevelsearch.checklocked = false; @@ -281,23 +313,7 @@ boolean M_LevelListFromGametype(INT16 gt) foundany = true; - if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) - { - // Double the size of the buffer, and clear the other stuff. - const size_t firstlen = cupgrid.cappages * pagelen; - cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, - firstlen * 2, - PU_STATIC, NULL); - - if (!cupgrid.builtgrid) - { - I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); - } - - cupgrid.cappages *= 2; - } - - cupgrid.builtgrid[currentid] = templevelsearch.cup; + GRID_INSERTCUP; templevelsearch.checklocked = true; if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) @@ -308,10 +324,7 @@ boolean M_LevelListFromGametype(INT16 gt) ? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) : (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup)) { - cupgrid.x = currentid % CUPMENU_COLUMNS; - cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; - cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); - currentvalid = true; + GRID_FOCUSCUP; } } @@ -319,6 +332,34 @@ boolean M_LevelListFromGametype(INT16 gt) templevelsearch.cup = templevelsearch.cup->next; } + // Lost and found, a simplified version of the above loop. + if (cupgrid.grandprix == false) + { + templevelsearch.cup = &dummy_lostandfound; + templevelsearch.checklocked = true; + + if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) != NEXTMAP_INVALID) + { + foundany = true; + GRID_INSERTCUP; + highestunlockedid = currentid; + + if (Playing() + ? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == NULL) + : (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup)) + { + GRID_FOCUSCUP; + } + + currentid++; + } + + templevelsearch.cup = NULL; + } + +#undef GRID_INSERTCUP +#undef GRID_FOCUSCUP + if (foundany == false) { return false; From bfb939161aa7beb31eb047f42e5c49de4daf189b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 17:22:25 +0000 Subject: [PATCH 069/103] M_DrawChallengePreview: Improve SECRET_MAP - Change scale and offsets of map icon to match other unlock types that show a single map - Show a gametype string - Fudges GT_VERSUS into GT_SPECIAL just like the menu does - Shows "Match Race/Online" for a level with no preferred gametype like TEST RUN --- src/k_menudraw.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 94a390e62..72b26c3b6 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5214,13 +5214,46 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } case SECRET_MAP: { + const char *gtname = "INVALID HEADER"; UINT16 mapnum = M_UnlockableMapNum(ref); + K_DrawMapThumbnail( - (x-30)<typeoflevel); + + if (guessgt == -1) + { + // No Time Attack support, so specify... + gtname = "Match Race/Online"; + } + else + { + if (guessgt == GT_VERSUS) + { + // Fudge since there's no Versus-specific menu right now... + guessgt = GT_SPECIAL; + } + + if (guessgt == GT_SPECIAL && !M_SecretUnlocked(SECRET_SPECIALATTACK, true)) + { + gtname = "???"; + } + else + { + gtname = gametypes[guessgt]->name; + } + } + } + + V_DrawThinString(1, BASEVIDHEIGHT-(9+3), V_ALLOWLOWERCASE|V_6WIDTHSPACE, gtname); + break; } case SECRET_ENCORE: From 60fee59c54ce24e041269bec0773e0f8293b056b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 17:28:55 +0000 Subject: [PATCH 070/103] Adjust Test Run's exceptions for lock checks - Revert the blanket exception so Lost and Found won't always have it available - Won't be considered cheating to go to Test Run on game boot even if not unlocked - Made the lack of exception for Test Run in Command_Map_f's cheat check more intentional --- src/d_main.c | 2 +- src/d_netcmd.c | 2 +- src/m_cond.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 4b450eb06..23d4562b5 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1908,7 +1908,7 @@ void D_SRB2Main(void) I_Error("Can't get first map of gametype\n"); } - if (M_MapLocked(pstartmap)) + if (pstartmap != 1 && M_MapLocked(pstartmap)) { G_SetUsedCheats(); } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 258521d27..8fddc2794 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2821,7 +2821,7 @@ static void Command_Map_f(void) return; } - if (M_MapLocked(newmapnum)) + if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum)) { ischeating = true; } diff --git a/src/m_cond.c b/src/m_cond.c index 6ddbed1f9..c0d40ed5e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1851,7 +1851,7 @@ boolean M_MapLocked(UINT16 mapnum) if (marathonmode) return false; - if (mapnum <= 1 || mapnum > nummapheaders) + if (mapnum == 0 || mapnum > nummapheaders) return false; if (!mapheaderinfo[mapnum-1]) From 0f1407955507336da9144b523ca83212a24f7e96 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 17:34:14 +0000 Subject: [PATCH 071/103] levellist: Fix type of choosemap --- src/k_menu.h | 2 +- src/k_menudraw.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 8fbaed1bf..84fcba3f6 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -736,7 +736,7 @@ extern struct levellist_s { SINT8 cursor; UINT16 y; UINT16 dest; - INT16 choosemap; + UINT16 choosemap; UINT8 newgametype; UINT8 guessgt; levelsearch_t levelsearch; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 72b26c3b6..9fd148935 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2439,7 +2439,7 @@ void M_DrawLevelSelect(void) void M_DrawTimeAttack(void) { - INT16 map = levellist.choosemap; + UINT16 map = levellist.choosemap; INT16 t = (48*menutransition.tics); INT16 leftedge = 149+t+16; INT16 rightedge = 149+t+155; From 492babd73dc6a5dabc644d619d5adc705c5439e8 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 13 Mar 2023 18:20:50 +0000 Subject: [PATCH 072/103] Linear Level Select polish - Even if there's only one level in the group, only skip over the mini linear list in a Time Attack mode - Improves some of the jumpscare of looking into Lost and Found and being blasted to TEST RUN - Fix lists of one map being wedged against the bottom of the screen - Precache valid map count --- src/k_menu.h | 1 + src/menus/transient/cup-select.c | 3 ++- src/menus/transient/level-select.c | 10 +++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 84fcba3f6..6ee320f92 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -737,6 +737,7 @@ extern struct levellist_s { UINT16 y; UINT16 dest; UINT16 choosemap; + UINT16 mapcount; UINT8 newgametype; UINT8 guessgt; levelsearch_t levelsearch; diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index b4ef37493..cca546f9e 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -161,7 +161,7 @@ void M_CupSelectHandler(INT32 choice) restoreMenu = &PLAY_CupSelectDef; } - else if (count == 1) + else if (count == 1 && levellist.levelsearch.timeattack == true) { PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; M_LevelSelected(0); @@ -174,6 +174,7 @@ void M_CupSelectHandler(INT32 choice) levellist.cursor = 0; } + levellist.mapcount = count; M_LevelSelectScrollDest(); levellist.y = levellist.dest; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index f0e012855..11a62e944 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -195,14 +195,14 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) void M_LevelSelectScrollDest(void) { - UINT16 m = M_CountLevelsToShowInList(&levellist.levelsearch)-1; + UINT16 m = levellist.mapcount-1; levellist.dest = (6*levellist.cursor); if (levellist.dest < 3) levellist.dest = 3; - if (levellist.dest > (6*m)-3) + if (m && levellist.dest > (6*m)-3) levellist.dest = (6*m)-3; } @@ -400,6 +400,7 @@ boolean M_LevelListFromGametype(INT16 gt) levellist.levelsearch.cup = NULL; } + levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch); M_LevelSelectScrollDest(); levellist.y = levellist.dest; @@ -562,7 +563,6 @@ void M_LevelSelected(INT16 add) void M_LevelSelectHandler(INT32 choice) { - INT16 maxlevels = M_CountLevelsToShowInList(&levellist.levelsearch); const UINT8 pid = 0; (void)choice; @@ -575,7 +575,7 @@ void M_LevelSelectHandler(INT32 choice) if (menucmd[pid].dpad_ud > 0) { levellist.cursor++; - if (levellist.cursor >= maxlevels) + if (levellist.cursor >= levellist.mapcount) levellist.cursor = 0; S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(pid); @@ -584,7 +584,7 @@ void M_LevelSelectHandler(INT32 choice) { levellist.cursor--; if (levellist.cursor < 0) - levellist.cursor = maxlevels-1; + levellist.cursor = levellist.mapcount-1; S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(pid); } From db1b3dd7c92af6a7c6c402e9d936a66b75d7f893 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 11:31:09 +0000 Subject: [PATCH 073/103] GDCONVERT_ROUNDSTOKEY: Make 20 in DEVELOP builds instead of 4, for a closer-to-realistic progression --- src/m_cond.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_cond.h b/src/m_cond.h index a8c66c98e..9483810b1 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -226,7 +226,7 @@ typedef enum #define GDMAX_CHAOKEYS MAXUNLOCKABLES #ifdef DEVELOP -#define GDCONVERT_ROUNDSTOKEY 4 +#define GDCONVERT_ROUNDSTOKEY 20 #else #define GDCONVERT_ROUNDSTOKEY 50 #endif From 1fb0e0454fde156d658781e7392032a5691d0bd3 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 11:34:07 +0000 Subject: [PATCH 074/103] M_ChallengesTick: Fix no in-fade if only Chao Keys are pending --- src/menus/extras-challenges.c | 139 ++++++++++++++++------------------ 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 5289d3972..70ac69eb6 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -323,7 +323,12 @@ void M_ChallengesTick(void) } } - if (challengesmenu.chaokeyadd == true) + if (challengesmenu.pending && challengesmenu.fade < 5) + { + // Fade increase. + challengesmenu.fade++; + } + else if (challengesmenu.chaokeyadd == true) { if (challengesmenu.ticker <= 5) ; // recreate the slight delay the unlock fades provide @@ -376,87 +381,77 @@ void M_ChallengesTick(void) } else if (challengesmenu.pending) { - // Pending mode. - if (challengesmenu.fade < 5) + tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; + + if (++challengesmenu.unlockanim >= nexttime) { - // Fade increase. - challengesmenu.fade++; + challengesmenu.requestnew = true; } - else + + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && challengesmenu.unlockanim == UNLOCKTIME) { - // Unlock sequence. - tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; + // Unlock animation... also tied directly to the actual unlock! + gamedata->unlocked[challengesmenu.currentunlock] = true; + M_UpdateUnlockablesAndExtraEmblems(true, true); - if (++challengesmenu.unlockanim >= nexttime) + // Update shown description just in case..? + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + + challengesmenu.unlockcount[CC_TALLY]++; + challengesmenu.unlockcount[CC_ANIM]++; + + if (challengesmenu.extradata) { - challengesmenu.requestnew = true; - } + unlockable_t *ref; + UINT16 bombcolor; - if (challengesmenu.currentunlock < MAXUNLOCKABLES - && challengesmenu.unlockanim == UNLOCKTIME) - { - // Unlock animation... also tied directly to the actual unlock! - gamedata->unlocked[challengesmenu.currentunlock] = true; - M_UpdateUnlockablesAndExtraEmblems(true, true); + M_UpdateChallengeGridExtraData(challengesmenu.extradata); - // Update shown description just in case..? - challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + ref = &unlockables[challengesmenu.currentunlock]; + bombcolor = SKINCOLOR_NONE; - challengesmenu.unlockcount[CC_TALLY]++; - challengesmenu.unlockcount[CC_ANIM]++; - - if (challengesmenu.extradata) + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) { - unlockable_t *ref; - UINT16 bombcolor; - - M_UpdateChallengeGridExtraData(challengesmenu.extradata); - - ref = &unlockables[challengesmenu.currentunlock]; - 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.hilix, challengesmenu.hiliy+i, bombcolor); - if (ref->majorunlock) - { - M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor); - } - - S_StartSound(NULL, sfx_s3k4e); + 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.hilix, challengesmenu.hiliy+i, bombcolor); + if (ref->majorunlock) + { + M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor); + } + + S_StartSound(NULL, sfx_s3k4e); } } } From 276b19e87118b436f32c1825228407f26a7b5843 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 11:35:08 +0000 Subject: [PATCH 075/103] M_ChallengesTick: Speed up digesting pending rounds into Chao Keys if there's lots of them remaining --- src/menus/extras-challenges.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 70ac69eb6..66093e0c2 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -344,12 +344,28 @@ void M_ChallengesTick(void) } else { - if (!(--gamedata->pendingkeyrounds & 1)) + UINT32 keyexchange = gamedata->keyspending; + + if (keyexchange > gamedata->pendingkeyrounds) + { + keyexchange = 1; + } + else if (keyexchange >= GDCONVERT_ROUNDSTOKEY/2) + { + keyexchange = GDCONVERT_ROUNDSTOKEY/2; + } + + keyexchange |= 1; // guarantee an odd delta for the sake of the sound + + gamedata->pendingkeyrounds -= keyexchange; + gamedata->pendingkeyroundoffset += keyexchange; + + if (!(gamedata->pendingkeyrounds & 1)) { S_StartSound(NULL, sfx_ptally); } - if (++gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY) + if (gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY) { gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY; From 5b48c52b65e95e65e93c6d5383cb2d9b25965237 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 11:52:54 +0000 Subject: [PATCH 076/103] G_DoCompleted: Call S_StopSounds earlier so legitimate exit condition sound isn't insta-wiped --- src/g_game.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index c406addd6..efa48408a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4143,6 +4143,9 @@ static void G_DoCompleted(void) if (modeattacking && pausedelay) pausedelay = 0; + // We do this here so Challenges-related sounds aren't insta-killed + S_StopSounds(); + if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { UINT8 roundtype = GDGT_CUSTOM; @@ -4221,8 +4224,6 @@ static void G_DoCompleted(void) if (automapactive) AM_Stop(); - S_StopSounds(); - prevmap = (INT16)(gamemap-1); if (!demo.playback) From 107acf34d3db7382ec0b5b29ebb846b8f39db4f0 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 12:28:52 +0000 Subject: [PATCH 077/103] Fix some softlock circumstances caused by the Chao Key system - Nonzero keys pending, but zero pending rounds - Nonzero keys pending, but too many keys already earned --- src/m_cond.c | 9 +++++++-- src/menus/extras-challenges.c | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index c0d40ed5e..7404bb0ac 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1569,7 +1569,12 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall) { response = M_CheckUnlockConditions(NULL); - while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS + if (gamedata->pendingkeyrounds == 0 + || ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS)) + { + gamedata->keyspending = 0; + } + else while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) { gamedata->keyspending++; @@ -1662,7 +1667,7 @@ UINT16 M_GetNextAchievedUnlock(void) return i; } - if (gamedata->keyspending > 0) + if (gamedata->keyspending != 0) { return PENDING_CHAOKEYS; } diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 66093e0c2..05481d8e5 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -334,13 +334,17 @@ void M_ChallengesTick(void) ; // recreate the slight delay the unlock fades provide else if (gamedata->pendingkeyrounds == 0) { + gamedata->keyspending = 0; + gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY; + challengesmenu.chaokeyadd = false; challengesmenu.requestnew = true; } else if ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS) { - gamedata->keyspending = 0; + // The above condition will run on the next tic because of this set gamedata->pendingkeyrounds = 0; + gamedata->pendingkeyroundoffset = 0; } else { From 558d3dc8426224d05aa59db2fe99c5e13e9dda99 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 12:48:34 +0000 Subject: [PATCH 078/103] M_DrawChallenges: Change offset of Chao Keys --- src/k_menudraw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9fd148935..e8ade83a9 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5531,8 +5531,12 @@ challengedesc: if (offs & 1) offs = -offs; offs /= 2; - V_DrawFixedPatch((6+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL); - V_DrawKartString((25+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); + + if (gamedata->chaokeys > 9) + offs -= 6; + + V_DrawFixedPatch((8+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL); + V_DrawKartString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); offs = challengekeybarwidth; if ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS) From 4db0affd2bbafb43b48fbc9d5088201b1d4c1a97 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 17:04:46 +0000 Subject: [PATCH 079/103] Addons menu: Show unlocks after backing out - For the Addons unlock condition. - Also forbids having menu flow interrupted with challenges if you're in-game --- src/menus/extras-addons.c | 2 +- src/menus/extras-challenges.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index 0d2dbd85b..da76abc09 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -351,7 +351,7 @@ void M_HandleAddons(INT32 choice) //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); + M_SetupNextMenu(M_InterruptMenuWithChallenges(currentMenu->prevMenu), false); else M_ClearMenus(true); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 05481d8e5..ad896b49d 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -205,6 +205,9 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { UINT16 i, newunlock; + if (Playing()) + return desiredmenu; + M_UpdateUnlockablesAndExtraEmblems(false, true); newunlock = M_GetNextAchievedUnlock(); From 22e17fd881eddaaffc62bd342f4ab0ad16d8548c Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 20:27:58 +0000 Subject: [PATCH 080/103] Statistics respects cups now - Adds headers to the list - Adds indentation - Doesn't show extra medals if there are none available - Cleans up some of the undesired duplication in the drawer --- src/k_menu.h | 1 + src/k_menudraw.c | 58 +++++++++++++++++-------- src/menus/extras-statistics.c | 79 ++++++++++++++++++++++++++--------- 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 6ee320f92..52a60ac42 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1196,6 +1196,7 @@ boolean M_ChallengesInputs(INT32 ch); extern struct statisticsmenu_s { INT32 location; INT32 nummaps; + INT32 numextramedals; INT32 maxscroll; UINT16 *maplist; } statisticsmenu; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index e8ade83a9..92acf9805 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5662,27 +5662,53 @@ static void M_DrawStatsMaps(void) V_DrawCharacter(10, y-(skullAnimCounter/5), '\x1A' | highlightflags, false); // up arrow - while (statisticsmenu.maplist[++i] != NEXTMAP_INVALID) + while ((mnum = statisticsmenu.maplist[++i]) != NEXTMAP_INVALID) { if (location) { --location; continue; } - else if (dotopname) + + if (dotopname || mnum >= nummapheaders) { - V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME"); - V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, V_6WIDTHSPACE|highlightflags, "MEDALS"); + if (mnum >= nummapheaders) + { + mnum = statisticsmenu.maplist[1+i]; + if (mnum >= nummapheaders) + mnum = statisticsmenu.maplist[i-1]; + } + + if (mnum < nummapheaders) + { + const char *str; + + if (mapheaderinfo[mnum]->cup) + str = va("%s CUP", mapheaderinfo[mnum]->cup->name); + else + str = "LOST AND FOUND"; + + V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, str); + } + + if (dotopname) + { + V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, V_6WIDTHSPACE|highlightflags, "MEDALS"); + dotopname = false; + } + y += STATSSTEP; - dotopname = false; + if (y >= BASEVIDHEIGHT-STATSSTEP) + goto bottomarrow; + + continue; } - mnum = statisticsmenu.maplist[i]+1; - M_DrawMapMedals(mnum, 291, y); + M_DrawMapMedals(mnum+1, 291, y); { - char *title = G_BuildMapTitle(mnum); - V_DrawThinString(20, y, V_6WIDTHSPACE, title); + char *title = G_BuildMapTitle(mnum+1); + V_DrawThinString(24, y, V_6WIDTHSPACE, title); Z_Free(title); } @@ -5691,15 +5717,12 @@ static void M_DrawStatsMaps(void) if (y >= BASEVIDHEIGHT-STATSSTEP) goto bottomarrow; } - if (dotopname && !location) - { - V_DrawString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME"); - V_DrawString(256, y, V_6WIDTHSPACE|highlightflags, "MEDALS"); - y += STATSSTEP; - } - else if (location) + if (location) --location; + if (statisticsmenu.numextramedals == 0) + goto bottomarrow; + // Extra Emblem headers for (i = 0; i < 2; ++i) { @@ -5738,7 +5761,6 @@ static void M_DrawStatsMaps(void) continue; } - if (i >= 0) { if (gamedata->unlocked[i]) { @@ -5753,7 +5775,7 @@ static void M_DrawStatsMaps(void) V_DrawSmallScaledPatch(291, y+1, V_6WIDTHSPACE, W_CachePatchName("NEEDIT", PU_CACHE)); } - V_DrawThinString(20, y, V_6WIDTHSPACE, va("%s", unlockables[i].name)); + V_DrawThinString(24, y, V_6WIDTHSPACE, va("%s", unlockables[i].name)); } y += STATSSTEP; diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.c index 97f4b3192..d34a5d9f4 100644 --- a/src/menus/extras-statistics.c +++ b/src/menus/extras-statistics.c @@ -8,37 +8,76 @@ struct statisticsmenu_s statisticsmenu; +static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headerexists) +{ + if (!mapheaderinfo[map]) + return false; + + if (mapheaderinfo[map]->cup != cup) + return false; + + // Check for no visibility + if (mapheaderinfo[map]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) + return false; + + // Check for completion + if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[map]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(map+1)) + return false; + + if (*headerexists == false) + { + statisticsmenu.maplist[statisticsmenu.nummaps++] = NEXTMAP_TITLE; // cheeky hack + *headerexists = true; + } + + statisticsmenu.maplist[statisticsmenu.nummaps++] = map; + return true; +} + void M_Statistics(INT32 choice) { - UINT16 i = 0; + cupheader_t *cup; + UINT16 i; + boolean headerexists; (void)choice; - statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL); + statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (nummapheaders+1 + numkartcupheaders), PU_STATIC, NULL); statisticsmenu.nummaps = 0; + for (cup = kartcupheaders; cup; cup = cup->next) + { + headerexists = false; + + if (M_CupLocked(cup)) + continue; + + for (i = 0; i < CUPCACHE_MAX; i++) + { + if (cup->cachedlevels[i] >= nummapheaders) + continue; + + M_StatisticsAddMap(cup->cachedlevels[i], cup, &headerexists); + } + } + + headerexists = false; + for (i = 0; i < nummapheaders; i++) { - if (!mapheaderinfo[i]) - continue; - - // Check for no visibility + legacy box - if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) - continue; - - // Check for completion - if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[i]->mapvisited & MV_BEATEN)) - continue; - - // Check for unlock - if (M_MapLocked(i+1)) - continue; - - statisticsmenu.maplist[statisticsmenu.nummaps++] = i; + M_StatisticsAddMap(i, NULL, &headerexists); } + + if ((i = statisticsmenu.numextramedals = M_CountMedals(true, true)) != 0) + i += 2; + statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID; - statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10; + statisticsmenu.maxscroll = (statisticsmenu.nummaps + i) - 11; statisticsmenu.location = 0; if (statisticsmenu.maxscroll < 0) From 967ad3662daea41136c619d89d36b91994d342db Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 20:28:36 +0000 Subject: [PATCH 081/103] M_DrawMapMedals: Only shows ET_MAP medals with ME_ENCORE and/or ME_SPBATTACK if you've unlocked those two things --- src/k_menudraw.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 92acf9805..623d97116 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5630,6 +5630,17 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y) curtype = 2; break; } + case ET_MAP: + { + if (((emblem->flags & ME_ENCORE) && !M_SecretUnlocked(SECRET_ENCORE, true)) + || ((emblem->flags & ME_SPBATTACK) && !M_SecretUnlocked(SECRET_SPBATTACK, true))) + { + emblem = M_GetLevelEmblems(-1); + continue; + } + curtype = 0; + break; + } default: curtype = 0; break; From 0124cf7356fc35a593e2e6b94f6dfb93434c8dc4 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 20:36:54 +0000 Subject: [PATCH 082/103] No TEST RUN in statistics as that's also forbidden in Time Attack --- src/menus/extras-statistics.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.c index d34a5d9f4..356bf46b2 100644 --- a/src/menus/extras-statistics.c +++ b/src/menus/extras-statistics.c @@ -20,6 +20,10 @@ static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headere if (mapheaderinfo[map]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) return false; + // No TEST RUN, as that's another exception to Time Attack too + if (!mapheaderinfo[map]->typeoflevel) + return false; + // Check for completion if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) && !(mapheaderinfo[map]->mapvisited & MV_BEATEN)) From 2d7ef00e29f48faa214ca9efae83434a9b04bc94 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 20:59:51 +0000 Subject: [PATCH 083/103] Discoveries while self-reviewing during description creation - Rename "FinishAllPrisons" and correct text to refer to Prisons - Do not hint at greater speeds for UCRP_ISDIFFICULTY --- src/deh_soc.c | 2 +- src/m_cond.c | 16 ++++++---------- src/m_cond.h | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 764d08628..13c5b2703 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2694,7 +2694,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) ty = UCRP_PODIUMEMERALD + offset; } else if ((offset=0) || fastcmp(params[0], "FINISHCOOL") - || (++offset && fastcmp(params[0], "FINISHALLCAPSULES")) + || (++offset && fastcmp(params[0], "FINISHALLPRISONS")) || (++offset && fastcmp(params[0], "NOCONTEST"))) { //PARAMCHECK(1); diff --git a/src/m_cond.c b/src/m_cond.c index 7404bb0ac..1f0f4f2b8 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -832,7 +832,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && !(player->pflags & PF_NOCONTEST) && M_NotFreePlay(player) && !K_IsPlayerLosing(player)); - case UCRP_FINISHALLCAPSULES: + case UCRP_FINISHALLPRISONS: return (battleprisons && !(player->pflags & PF_NOCONTEST) //&& M_NotFreePlay(player) @@ -1276,19 +1276,15 @@ static const char *M_GetConditionString(condition_t *cn) return va("with engine class %c", 'A' + cn->requirement); case UCRP_ISDIFFICULTY: { - const char *speedtext = "", *orbetter = ""; + const char *speedtext = ""; if (cn->requirement == KARTSPEED_NORMAL) { - speedtext = "on Normal difficulty"; - //if (M_SecretUnlocked(SECRET_HARDSPEED, true)) - orbetter = " or better"; + speedtext = "on Normal difficulty or better"; } else if (cn->requirement == KARTSPEED_HARD) { speedtext = "on Hard difficulty"; - if (M_SecretUnlocked(SECRET_MASTERMODE, true)) - orbetter = " or better"; } else if (cn->requirement == KARTGP_MASTER) { @@ -1298,7 +1294,7 @@ static const char *M_GetConditionString(condition_t *cn) speedtext = "on ???"; } - return va("%s%s", speedtext, orbetter); + return speedtext; } case UCRP_PODIUMCUP: @@ -1356,8 +1352,8 @@ static const char *M_GetConditionString(condition_t *cn) case UCRP_FINISHCOOL: return "finish in good standing"; - case UCRP_FINISHALLCAPSULES: - return "break every capsule"; + case UCRP_FINISHALLPRISONS: + return "break every prison"; case UCRP_NOCONTEST: return "NO CONTEST"; case UCRP_FINISHPLACE: diff --git a/src/m_cond.h b/src/m_cond.h index 9483810b1..354721407 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -81,7 +81,7 @@ typedef enum UCRP_PODIUMPRIZE, // Get to podium sequence with that cup's bonus (alternate string version of UCRP_PODIUMEMERALD UCRP_FINISHCOOL, // Finish in good standing - UCRP_FINISHALLCAPSULES, // Break all capsules + UCRP_FINISHALLPRISONS, // Break all prisons UCRP_NOCONTEST, // No Contest UCRP_FINISHPLACE, // Finish at least [place] From edddb26f9821f351731f7a8c97b030db16f96528 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 14 Mar 2023 22:04:38 +0000 Subject: [PATCH 084/103] P_Net(Un)ArchivePlayers: Send roundconditions.unlocktriggers over the wire Discovered while self-reviewing that ACS has read access to this, not just write, so it has to be saved and loaded. It is the only roundcondition which should be netsynced. Everything else is truly per-player progression. --- src/p_saveg.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/p_saveg.c b/src/p_saveg.c index 544c43624..f28efeb15 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -487,6 +487,10 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEFIXED(save->p, players[i].loop.shift.x); WRITEFIXED(save->p, players[i].loop.shift.y); WRITEUINT8(save->p, players[i].loop.flip); + + // ACS has read access to this, so it has to be net-communicated. + // It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way. + WRITEUINT32(save->p, players[i].roundconditions.unlocktriggers); } } @@ -874,6 +878,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].loop.shift.y = READFIXED(save->p); players[i].loop.flip = READUINT8(save->p); + // ACS has read access to this, so it has to be net-communicated. + // It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way. + players[i].roundconditions.unlocktriggers = READUINT32(save->p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } From 8c19477ad8a0bc07d437b3c365d37d8ef44df147 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 15 Mar 2023 13:12:19 +0000 Subject: [PATCH 085/103] Self-review: Correct to say "GP & Time Attack data" --- src/menus/options-data-erase-1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index 3a40ed70f..faf893d32 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -19,7 +19,7 @@ menuitem_t OPTIONS_DataErase[] = {IT_STRING | IT_CALL, "Erase Statistics Data", "Be careful! What's deleted is gone forever!", NULL, {.routine = M_EraseData}, EC_STATISTICS, 0}, - {IT_STRING | IT_CALL, "Erase Time Attack Data", "Be careful! What's deleted is gone forever!", + {IT_STRING | IT_CALL, "Erase GP & Time Attack Data", "Be careful! What's deleted is gone forever!", NULL, {.routine = M_EraseData}, EC_TIMEATTACK, 0}, {IT_STRING | IT_CALL, "\x85\x45rase all Game Data", "Be careful! What's deleted is gone forever!", @@ -84,7 +84,7 @@ void M_EraseData(INT32 choice) else if (optionsmenu.erasecontext == EC_STATISTICS) eschoice = M_GetText("Statistics data"); else if (optionsmenu.erasecontext == EC_TIMEATTACK) - eschoice = M_GetText("Time Attack data"); + eschoice = M_GetText("GP & Time Attack data"); else if (optionsmenu.erasecontext == EC_ALLGAME) eschoice = M_GetText("ALL game data"); else From bb98db6fce4ff49660b610f366eb037f04f13961 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 15 Mar 2023 13:14:12 +0000 Subject: [PATCH 086/103] Ring count formatting: Correct to always have leading 0s for up to 3 digits --- src/k_menudraw.c | 4 ++-- src/m_cond.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 826281bc7..32f3cb522 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5844,11 +5844,11 @@ void M_DrawStatistics(void) } else if (gamedata->totalrings >= 1000000) { - sprintf(beststr, "%u,%u,%u", (gamedata->totalrings/1000000), (gamedata->totalrings/1000)%1000, (gamedata->totalrings%1000)); + sprintf(beststr, "%u,%03u,%03u", (gamedata->totalrings/1000000), (gamedata->totalrings/1000)%1000, (gamedata->totalrings%1000)); } else if (gamedata->totalrings >= 1000) { - sprintf(beststr, "%u,%u", (gamedata->totalrings/1000), (gamedata->totalrings%1000)); + sprintf(beststr, "%u,%03u", (gamedata->totalrings/1000), (gamedata->totalrings%1000)); } else { diff --git a/src/m_cond.c b/src/m_cond.c index 1f0f4f2b8..aa704bd17 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1025,9 +1025,9 @@ static const char *M_GetConditionString(condition_t *cn) case UC_TOTALRINGS: // Requires collecting >= x rings if (cn->requirement >= 1000000) - return va("collect %u,%u,%u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); + return va("collect %u,%03u,%03u Rings", (cn->requirement/1000000), (cn->requirement/1000)%1000, (cn->requirement%1000)); if (cn->requirement >= 1000) - return va("collect %u,%u Rings", (cn->requirement/1000), (cn->requirement%1000)); + return va("collect %u,%03u Rings", (cn->requirement/1000), (cn->requirement%1000)); return va("collect %u Rings", cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype From 2ec3cf498dfa2d21a6544a68b35b2895d097cfde Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 15 Mar 2023 13:19:01 +0000 Subject: [PATCH 087/103] K_DrawChallenges: Technically possible to have 3-digit Chao Keys, so scooch just a hair further --- src/k_menudraw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 32f3cb522..91d85a674 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5533,7 +5533,11 @@ challengedesc: offs /= 2; if (gamedata->chaokeys > 9) + { offs -= 6; + if (gamedata->chaokeys > 99) + offs -= 2; // as far as we can go + } V_DrawFixedPatch((8+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL); V_DrawKartString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); From 7f3836f91689b8b2a23f87fa687132a0583a77c6 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 15 Mar 2023 14:49:22 +0000 Subject: [PATCH 088/103] Chao Key changes - Chao Keys are now UINT16 instead of UINT8 - The maximum number of Chao Keys is now 9999, which is the largest number of 9s that can fit in a UINT16 - Used keys no longer count towards your total Will mildly corrupt gamedatas made with previous conditions-cascading builds, but only in a way that gives you extra keys than you've earned. --- src/g_game.c | 8 +++----- src/k_menudraw.c | 2 +- src/m_cond.c | 5 ++--- src/m_cond.h | 7 +++---- src/menus/extras-challenges.c | 7 +++---- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index d0d55d964..3b2e4aafc 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4581,8 +4581,7 @@ void G_LoadGameData(void) gamedata->pendingkeyrounds = READUINT32(save.p); gamedata->pendingkeyroundoffset = READUINT8(save.p); gamedata->keyspending = READUINT8(save.p); - gamedata->chaokeys = READUINT8(save.p); - gamedata->usedkeys = READUINT8(save.p); + gamedata->chaokeys = READUINT16(save.p); gamedata->crashflags = READUINT8(save.p); if (gamedata->crashflags & GDCRASH_LAST) @@ -4803,7 +4802,7 @@ void G_SaveGameData(boolean dirty) length = (4+1+4+4+ (4*GDGT_MAX)+ - 4+1+1+1+1+ + 4+1+1+2+ 1+1+1+1+ 4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ @@ -4843,8 +4842,7 @@ void G_SaveGameData(boolean dirty) WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4 WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1 WRITEUINT8(save.p, gamedata->keyspending); // 1 - WRITEUINT8(save.p, gamedata->chaokeys); // 1 - WRITEUINT8(save.p, gamedata->usedkeys); // 1 + WRITEUINT16(save.p, gamedata->chaokeys); // 2 { UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 91d85a674..40d6f96b1 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5543,7 +5543,7 @@ challengedesc: V_DrawKartString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys)); offs = challengekeybarwidth; - if ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS) + if (gamedata->chaokeys < GDMAX_CHAOKEYS) offs = ((gamedata->pendingkeyroundoffset * challengekeybarwidth)/GDCONVERT_ROUNDSTOKEY); if (offs > 0) diff --git a/src/m_cond.c b/src/m_cond.c index aa704bd17..faff792c6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -556,7 +556,6 @@ void M_ClearSecrets(void) gamedata->pendingkeyroundoffset = 0; gamedata->keyspending = 0; gamedata->chaokeys = 3; // Start with 3 !! - gamedata->usedkeys = 0; } // ---------------------- @@ -1566,11 +1565,11 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall) response = M_CheckUnlockConditions(NULL); if (gamedata->pendingkeyrounds == 0 - || ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS)) + || (gamedata->chaokeys >= GDMAX_CHAOKEYS)) { gamedata->keyspending = 0; } - else while ((gamedata->keyspending + gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS + else while ((gamedata->keyspending + gamedata->chaokeys) < GDMAX_CHAOKEYS && ((gamedata->pendingkeyrounds + gamedata->pendingkeyroundoffset)/GDCONVERT_ROUNDSTOKEY) > gamedata->keyspending) { gamedata->keyspending++; diff --git a/src/m_cond.h b/src/m_cond.h index 354721407..3ba54e7c4 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -221,9 +221,9 @@ typedef enum #define GDCRASH_ANY 0x02 #define GDCRASH_LOSERCLUB 0x04 -// This is the largest number of 9s that will fit in UINT32. +// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively. #define GDMAX_RINGS 999999999 -#define GDMAX_CHAOKEYS MAXUNLOCKABLES +#define GDMAX_CHAOKEYS 9999 #ifdef DEVELOP #define GDCONVERT_ROUNDSTOKEY 20 @@ -275,8 +275,7 @@ struct gamedata_t UINT32 pendingkeyrounds; UINT8 pendingkeyroundoffset; UINT8 keyspending; - UINT8 chaokeys; - UINT8 usedkeys; + UINT16 chaokeys; // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index ad896b49d..5e462287b 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -55,7 +55,7 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh) SINT8 work; if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 - && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + && (gamedata->chaokeys < GDMAX_CHAOKEYS)) challengesmenu.chaokeyadd = true; if (fresh && unlockid >= MAXUNLOCKABLES) @@ -258,7 +258,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) if (challengesmenu.pending) M_ChallengesAutoFocus(newunlock, true); else if (newunlock >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0 - && ((gamedata->chaokeys + gamedata->usedkeys) < GDMAX_CHAOKEYS)) + && (gamedata->chaokeys < GDMAX_CHAOKEYS)) challengesmenu.chaokeyadd = true; return &MISC_ChallengesDef; @@ -343,7 +343,7 @@ void M_ChallengesTick(void) challengesmenu.chaokeyadd = false; challengesmenu.requestnew = true; } - else if ((gamedata->chaokeys + gamedata->usedkeys) >= GDMAX_CHAOKEYS) + else if (gamedata->chaokeys >= GDMAX_CHAOKEYS) { // The above condition will run on the next tic because of this set gamedata->pendingkeyrounds = 0; @@ -526,7 +526,6 @@ boolean M_ChallengesInputs(INT32 ch) && gamedata->chaokeys > 0) { gamedata->chaokeys--; - gamedata->usedkeys++; challengesmenu.unlockcount[CC_CHAOANIM]++; S_StartSound(NULL, sfx_chchng); From e943ba75488bf005165d65f9eba1aeaa2446e18b Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 15 Mar 2023 14:51:13 +0000 Subject: [PATCH 089/103] Challenge condition string: Grey out if you've unlocked the space with a Chao Key and haven't achieved its associated condition yet You can still achieve it and make it full-bright at a later date --- src/m_cond.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/m_cond.c b/src/m_cond.c index faff792c6..227fcf48e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1431,6 +1431,13 @@ char *M_BuildConditionSetString(UINT8 unlockid) return NULL; } + if (gamedata->unlocked[unlockid] == true && M_Achieved(unlockables[unlockid].conditionset - 1) == false) + { + message[0] = '\x86'; // the following text will be grey + message[1] = '\0'; + len--; + } + c = &conditionSets[unlockables[unlockid].conditionset-1]; for (i = 0; i < c->numconditions; ++i) From f2f8a10bd0ad5bdc1194c5678b447c3a9466ab78 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Mar 2023 12:54:59 +0000 Subject: [PATCH 090/103] M_DrawCupSelect: Fix regression for S3K monitor graphic alignment --- 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 40d6f96b1..89d34e5c8 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2184,7 +2184,7 @@ void M_DrawCupSelect(void) { monitor = '0' + templevelsearch.cup->monitor; - if (templevelsearch.cup->monitor == '2') + if (monitor == '2') { icony = 5; rankx = 2; From 680416946a52064381ca39145bb14e6616ef43be Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Mar 2023 13:01:26 +0000 Subject: [PATCH 091/103] UCRP_FINISHTIMEEXACT: Round to the nearest second --- src/m_cond.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 227fcf48e..566021897 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -858,7 +858,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (player->exiting && !(player->pflags & PF_NOCONTEST) //&& M_NotFreePlay(player) - && player->realtime == (unsigned)cn->requirement); + && player->realtime/TICRATE == (unsigned)cn->requirement/TICRATE); case UCRP_FINISHTIMELEFT: return (timelimitintics && player->exiting @@ -1361,12 +1361,14 @@ static const char *M_GetConditionString(condition_t *cn) ((cn->type == UCRP_FINISHPLACE && cn->requirement > 1) ? " or better" : "")); case UCRP_FINISHTIME: - case UCRP_FINISHTIMEEXACT: - return va("finish in %s%i:%02i.%02i", - (cn->type == UCRP_FINISHTIMEEXACT ? "exactly " : ""), + return va("finish in %i:%02i.%02i", G_TicsToMinutes(cn->requirement, true), G_TicsToSeconds(cn->requirement), G_TicsToCentiseconds(cn->requirement)); + case UCRP_FINISHTIMEEXACT: + return va("finish in exactly %i:%02i", + G_TicsToMinutes(cn->requirement, true), + G_TicsToSeconds(cn->requirement)); case UCRP_FINISHTIMELEFT: return va("finish with %i:%02i.%02i remaining", G_TicsToMinutes(cn->requirement, true), From 412107c1401b74dd026ac9896165af756156d42d Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Mar 2023 13:04:01 +0000 Subject: [PATCH 092/103] Adjust string for UCRP_FINISHTIMEEXACT to match the same number of visible digits by using X as a wildcard millisecond --- 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 566021897..9d6535fb0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1366,7 +1366,7 @@ static const char *M_GetConditionString(condition_t *cn) G_TicsToSeconds(cn->requirement), G_TicsToCentiseconds(cn->requirement)); case UCRP_FINISHTIMEEXACT: - return va("finish in exactly %i:%02i", + return va("finish in exactly %i:%02i.XX", G_TicsToMinutes(cn->requirement, true), G_TicsToSeconds(cn->requirement)); case UCRP_FINISHTIMELEFT: From 7b917930d433f8abbfc5d49b5918a32db0a08185 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Mar 2023 14:05:54 +0000 Subject: [PATCH 093/103] M_BuildConditionSetString: Fix capitalisation rules to prevent "NO CONTEST On GREEN HILLS" (The desired version of the string is "NO CONTEST on GREEN HILLS".) Instead, searches for the first ':'. - If found, goes to the first non-whitespace character afterwards and toupper's it. - If not, goes to the first non-whitespace character at all and toupper's it. --- src/m_cond.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 9d6535fb0..21cf628ad 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1511,12 +1511,30 @@ char *M_BuildConditionSetString(UINT8 unlockid) } } - for (i = 0; message[i]; i++) + // Valid sentence capitalisation handling. { - if (message[i] == toupper(message[i])) - continue; - message[i] = toupper(message[i]); - break; + // Finds the first : character, indicating the end of the prefix. + for (i = 0; message[i]; i++) + { + if (message[i] != ':') + continue; + i++; + break; + } + + // If we didn't find a prefix, just start from the first character again. + if (!message[i]) + i = 0; + + // Okay, now make the first non-whitespace character after the prefix a capital. + // Doesn't matter if !isalpha() - toupper is a no-op. + for (; message[i]; i++) + { + if ((message[i] & 0x80) || isspace(message[i])) + continue; + message[i] = toupper(message[i]); + break; + } } return message; From e5f88dd2c60469eb85b6aa3294cab2d6f984bacb Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Mar 2023 23:27:21 +0000 Subject: [PATCH 094/103] UC_ADDON: On second thoughts, don't have seperate conditions for DEVELOP and non-DEVELOP Any difference in behaviour between these is a possible avenue for bugs which take some time to be discovered --- 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 21cf628ad..d121b3db1 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -759,11 +759,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return M_Achieved(cn->requirement-1); case UC_ADDON: - return ( -#ifndef DEVELOP - M_SecretUnlocked(SECRET_ADDONS, true) && -#endif - (gamedata->everloadedaddon == true)); + return ((gamedata->everloadedaddon == true) + && M_SecretUnlocked(SECRET_ADDONS, true)); case UC_REPLAY: return (gamedata->eversavedreplay == true); case UC_CRASH: @@ -1221,7 +1218,7 @@ static const char *M_GetConditionString(condition_t *cn) : "???"); case UC_ADDON: - if (!M_SecretUnlocked(SECRET_ADDONS, true) && !gamedata->everloadedaddon) + if (!M_SecretUnlocked(SECRET_ADDONS, true)) return NULL; return "load a custom addon into \"Dr. Robotnik's Ring Racers\""; case UC_REPLAY: From 3ead0d09c4500566c815a1ac9e89d442715586a9 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 17 Mar 2023 13:00:17 +0000 Subject: [PATCH 095/103] Jartha review: Issues with activation and comments of UCRP_WETPLAYER and UCRP_FALLOFF --- src/m_cond.c | 2 +- src/m_cond.h | 2 +- src/p_inter.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index d121b3db1..518c7780e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -887,7 +887,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) case UCRP_WETPLAYER: return (((player->roundconditions.wet_player & cn->requirement) == 0) - || player->roundconditions.fell_off); // Levels with water tend to texture their pits as water too + && !player->roundconditions.fell_off); // Levels with water tend to texture their pits as water too } return false; } diff --git a/src/m_cond.h b/src/m_cond.h index 3ba54e7c4..968d5c38e 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -103,7 +103,7 @@ typedef enum UCRP_LANDMINEDUNK, // huh? you died? that's weird. all i did was try to hug you... UCRP_HITMIDAIR, // Hit another player mid-air with a kartfielditem - UCRP_WETPLAYER, // Touch [fluid] + UCRP_WETPLAYER, // Don't touch [fluid] } conditiontype_t; // Condition Set information diff --git a/src/p_inter.c b/src/p_inter.c index 3e04b5c31..3acc52cdd 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1929,7 +1929,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, { case DMG_DEATHPIT: // Fell off the stage - if (player->roundconditions.fell_off == true) + if (player->roundconditions.fell_off == false) { player->roundconditions.fell_off = true; player->roundconditions.checkthisframe = true; From b0a028c756e87429a4eebe9439a4ef3295aed58c Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 17 Mar 2023 13:03:19 +0000 Subject: [PATCH 096/103] Jartha review: Use P_IsMissileOrKartItem for UCRP_HITMIDAIR instead of P_IsKartFieldItem directly --- src/p_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index 3acc52cdd..d31f06e6c 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2181,7 +2181,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (inflictor && source && source->player) { if (source->player->roundconditions.hit_midair == false - && P_IsKartFieldItem(source->type) + && K_IsMissileOrKartItem(source) && target->player->airtime > TICRATE/2 && source->player->airtime > TICRATE/2) { From c33ae4ae62753f7a4c93fbffd8b9f64e486210f2 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 17 Mar 2023 13:09:58 +0000 Subject: [PATCH 097/103] Gamedata: Fix issues with cup windata handling - G_SaveGameData: correctly increase length of buffer to account for cup name - G_LoadGameData: digest the cup's associated windata even if it's not loaded --- src/g_game.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 3b2e4aafc..26c0bc2af 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4721,16 +4721,23 @@ void G_LoadGameData(void) char cupname[16]; cupheader_t *cup; + // Find the relevant cup. READSTRINGN(save.p, cupname, sizeof(cupname)); for (cup = kartcupheaders; cup; cup = cup->next) { if (strcmp(cup->name, cupname)) continue; + break; + } - for (j = 0; j < KARTGP_MAX; j++) + // Digest its data... + for (j = 0; j < KARTGP_MAX; j++) + { + rtemp = READUINT8(save.p); + + // ...but only record it if we actually found the associated cup. + if (cup) { - rtemp = READUINT8(save.p); - cup->windata[j].best_placement = (rtemp & 0x0F); cup->windata[j].best_grade = (rtemp & 0x70)>>4; if (rtemp & 0x80) @@ -4741,8 +4748,6 @@ void G_LoadGameData(void) cup->windata[j].got_emerald = true; } } - - break; } } } @@ -4819,7 +4824,7 @@ void G_SaveGameData(boolean dirty) { numcups++; } - length += 4 + (numcups * 4); + length += 4 + (numcups * (4+16)); if (P_SaveBufferAlloc(&save, length) == false) { From 3e900d7f57877af23df466bdb9425703a8f0f46f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 17 Mar 2023 14:34:39 +0000 Subject: [PATCH 098/103] G_DirtyGameData: Dirty bit only applied in I_Error and signal handlers, nowhere else - Unfortunately, the way this system previously worked, the unlock was given to you for free if you accidentially opened two copies of the game at once. - Instead, open the file in r+ mode, shimmy along 5 bytes, and write a `true` to be read later. - Far more memory safe than rewriting the entire gamedata out on crash. ALSO: - crashflags has been split into boolean evercrashed and UINT8 musicflags. - We don't need to track if the LAST session was a crash, at least not right now. - Opens the floor up to other music like Loser Club happening on the Challenges menu. --- src/d_main.c | 2 +- src/deh_soc.c | 2 +- src/f_finale.c | 2 +- src/g_demo.c | 2 +- src/g_game.c | 61 +++++++++++++++++++++++++---------- src/g_game.h | 3 +- src/k_menufunc.c | 2 +- src/k_podium.c | 4 +-- src/k_pwrlv.c | 4 +-- src/m_cond.c | 9 +++--- src/m_cond.h | 7 ++-- src/menus/extras-challenges.c | 2 +- src/p_setup.c | 4 +-- src/p_tick.c | 2 +- src/sdl/i_system.c | 9 ++++-- src/sdl12/i_system.c | 9 ++++-- src/w_wad.c | 2 +- src/win32ce/win_sys.c | 9 ++++-- 18 files changed, 86 insertions(+), 49 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 508b42712..027cd2461 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1051,7 +1051,7 @@ void D_ClearState(void) cursongcredit.def = NULL; if (gamedata && gamedata->deferredsave) - G_SaveGameData(true); + G_SaveGameData(); G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; diff --git a/src/deh_soc.c b/src/deh_soc.c index 13c5b2703..8a6c2955d 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2931,7 +2931,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) if (!GoodDataFileName(word2)) I_Error("Maincfg: bad data file name '%s'\n", word2); - G_SaveGameData(false); // undirty your old gamedata + G_SaveGameData(); strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); strlwr(gamedatafilename); savemoddata = true; diff --git a/src/f_finale.c b/src/f_finale.c index 0a802459d..4e17c8785 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1084,7 +1084,7 @@ void F_GameEvaluationTicker(void) ++gamedata->timesBeaten; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } else { diff --git a/src/g_demo.c b/src/g_demo.c index 312d0f3ca..a8632e126 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -4196,7 +4196,7 @@ void G_SaveDemo(void) { gamedata->eversavedreplay = true; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } } else diff --git a/src/g_game.c b/src/g_game.c index 26c0bc2af..c3eac0990 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3834,7 +3834,7 @@ static void G_UpdateVisited(void) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } static boolean CanSaveLevel(INT32 mapnum) @@ -3926,7 +3926,7 @@ static void G_GetNextMap(void) { gamedata->everseenspecial = true; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } } } @@ -4161,7 +4161,7 @@ static void G_DoCompleted(void) } if (gamedata->deferredsave) - G_SaveGameData(true); + G_SaveGameData(); legitimateexit = false; @@ -4567,6 +4567,11 @@ void G_LoadGameData(void) gridunusable = true; } + if (versionMinor > 1) + { + gamedata->evercrashed = (boolean)READUINT8(save.p); + } + gamedata->totalplaytime = READUINT32(save.p); if (versionMinor > 1) @@ -4583,10 +4588,6 @@ void G_LoadGameData(void) gamedata->keyspending = READUINT8(save.p); gamedata->chaokeys = READUINT16(save.p); - gamedata->crashflags = READUINT8(save.p); - if (gamedata->crashflags & GDCRASH_LAST) - gamedata->crashflags |= GDCRASH_ANY; - gamedata->everloadedaddon = (boolean)READUINT8(save.p); gamedata->eversavedreplay = (boolean)READUINT8(save.p); gamedata->everseenspecial = (boolean)READUINT8(save.p); @@ -4782,9 +4783,34 @@ void G_LoadGameData(void) } } +// G_DirtyGameData +// Modifies the gamedata as little as possible to maintain safety in a crash event, while still recording it. +void G_DirtyGameData(void) +{ + FILE *handle = NULL; + const UINT8 writebytesource = true; + + if (gamedata) + gamedata->evercrashed = true; + + //if (FIL_WriteFileOK(name)) + handle = fopen(va(pandf, srb2home, gamedatafilename), "r+"); + + if (!handle) + return; + + // Write a dirty byte immediately after the gamedata check + minor version. + if (fseek(handle, 5, SEEK_SET) != -1) + fwrite(&writebytesource, 1, 1, handle); + + fclose(handle); + + return; +} + // G_SaveGameData // Saves the main data file, which stores information such as emblems found, etc. -void G_SaveGameData(boolean dirty) +void G_SaveGameData(void) { size_t length; INT32 i, j, numcups; @@ -4805,10 +4831,11 @@ void G_SaveGameData(boolean dirty) return; } - length = (4+1+4+4+ + length = (4+1+1+ + 4+4+ (4*GDGT_MAX)+ 4+1+1+2+ - 1+1+1+1+ + 1+1+1+ 4+ (MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+ 4+2); @@ -4836,6 +4863,13 @@ void G_SaveGameData(boolean dirty) WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 WRITEUINT8(save.p, GD_VERSIONMINOR); // 1 + + // Crash dirtiness + // cannot move, see G_DirtyGameData + WRITEUINT8(save.p, gamedata->evercrashed); // 1 + + // Statistics + WRITEUINT32(save.p, gamedata->totalplaytime); // 4 WRITEUINT32(save.p, gamedata->totalrings); // 4 @@ -4849,13 +4883,6 @@ void G_SaveGameData(boolean dirty) WRITEUINT8(save.p, gamedata->keyspending); // 1 WRITEUINT16(save.p, gamedata->chaokeys); // 2 - { - UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY); - if (dirty) - crashflags |= GDCRASH_LAST; - WRITEUINT8(save.p, crashflags); // 1 - } - WRITEUINT8(save.p, gamedata->everloadedaddon); // 1 WRITEUINT8(save.p, gamedata->eversavedreplay); // 1 WRITEUINT8(save.p, gamedata->everseenspecial); // 1 diff --git a/src/g_game.h b/src/g_game.h index d85b98187..3f122f6c4 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -175,7 +175,8 @@ boolean G_IsTitleCardAvailable(void); // Can be called by the startup code or M_Responder, calls P_SetupLevel. void G_LoadGame(UINT32 slot, INT16 mapoverride); -void G_SaveGameData(boolean dirty); +void G_SaveGameData(void); +void G_DirtyGameData(void); void G_SaveGame(UINT32 slot, INT16 mapnum); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6602f74cd..2561238f2 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -373,7 +373,7 @@ void M_PlayMenuJam(void) { menu_t *refMenu = (menuactive ? currentMenu : restoreMenu); static boolean loserclubpermitted = false; - boolean loserclub = (loserclubpermitted && (gamedata->crashflags & GDCRASH_LOSERCLUB)); + boolean loserclub = (loserclubpermitted && (gamedata->musicflags & GDMUSIC_LOSERCLUB)); if (challengesmenu.pending) { diff --git a/src/k_podium.c b/src/k_podium.c index b9c6ca831..519c654e3 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -291,7 +291,7 @@ void K_FinishCeremony(void) // Play the noise now M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } /*-------------------------------------------------- @@ -339,7 +339,7 @@ void K_ResetCeremony(void) grandprixinfo.cup->windata[i].got_emerald = true; // Save before playing the noise - G_SaveGameData(true); + G_SaveGameData(); } /*-------------------------------------------------- diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index f4b307cfd..aa98cbcc3 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -428,7 +428,7 @@ void K_CashInPowerLevels(void) if (gamedataupdate) { M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } //CONS_Printf("========\n"); @@ -644,6 +644,6 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) pr->powerlevels[powerType] = yourPower + inc; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } } diff --git a/src/m_cond.c b/src/m_cond.c index 518c7780e..55eeeb300 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -529,7 +529,8 @@ void M_ClearStats(void) gamedata->everloadedaddon = false; gamedata->eversavedreplay = false; gamedata->everseenspecial = false; - gamedata->crashflags = 0; + gamedata->evercrashed = false; + gamedata->musicflags = 0; } void M_ClearSecrets(void) @@ -764,9 +765,9 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) case UC_REPLAY: return (gamedata->eversavedreplay == true); case UC_CRASH: - if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) + if (gamedata->evercrashed) { - gamedata->crashflags |= GDCRASH_LOSERCLUB; + gamedata->musicflags |= GDMUSIC_LOSERCLUB; return true; } return false; @@ -1224,7 +1225,7 @@ static const char *M_GetConditionString(condition_t *cn) case UC_REPLAY: return "save a replay after finishing a round"; case UC_CRASH: - if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY)) + if (gamedata->evercrashed) return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; return NULL; diff --git a/src/m_cond.h b/src/m_cond.h index 968d5c38e..f65bba15b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -217,9 +217,7 @@ typedef enum #endif #define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH) -#define GDCRASH_LAST 0x01 -#define GDCRASH_ANY 0x02 -#define GDCRASH_LOSERCLUB 0x04 +#define GDMUSIC_LOSERCLUB 0x01 // This is the largest number of 9s that will fit in UINT32 and UINT16 respectively. #define GDMAX_RINGS 999999999 @@ -281,7 +279,8 @@ struct gamedata_t boolean everloadedaddon; boolean eversavedreplay; boolean everseenspecial; - UINT8 crashflags; + boolean evercrashed; + UINT8 musicflags; }; extern gamedata_t *gamedata; diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 5e462287b..c5cda4bc8 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -399,7 +399,7 @@ void M_ChallengesTick(void) { // All done! Let's save the unlocks we've busted open. challengesmenu.pending = challengesmenu.chaokeyadd = false; - G_SaveGameData(true); + G_SaveGameData(); } } else if (challengesmenu.pending) diff --git a/src/p_setup.c b/src/p_setup.c index 939e8cd22..d2cd02ede 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7501,7 +7501,7 @@ static void P_InitGametype(void) // Started a game? Move on to the next jam when you go back to the title screen CV_SetValue(&cv_menujam_update, 1); - gamedata->crashflags &= ~GDCRASH_LOSERCLUB; + gamedata->musicflags = 0; } struct minimapinfo minimapinfo; @@ -7995,7 +7995,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } G_AddMapToBuffer(gamemap-1); diff --git a/src/p_tick.c b/src/p_tick.c index 1a5ae508c..26741e8a5 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -620,7 +620,7 @@ void P_Ticker(boolean run) // TODO would this be laggy with more conditions in play... if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false)) || (gamedata && gamedata->deferredsave))) - G_SaveGameData(true); + G_SaveGameData(); } // Keep track of how long they've been playing! diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index b60e30269..9d593115b 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -328,6 +328,7 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num) { D_QuitNetGame(); // Fix server freezes CL_AbortDownloadResume(); + G_DirtyGameData(); #ifdef UNIXBACKTRACE write_backtrace(num); #endif @@ -1448,7 +1449,7 @@ void I_Quit(void) if (Playing()) K_PlayerForfeit(consoleplayer, true); - G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save + G_SaveGameData(); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -1532,7 +1533,8 @@ void I_Error(const char *error, ...) if (errorcount == 8) { M_SaveConfig(NULL); - G_SaveGameData(true); + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); } if (errorcount > 20) { @@ -1563,7 +1565,8 @@ void I_Error(const char *error, ...) M_SaveConfig(NULL); // save game config, cvars.. D_SaveBan(); // save the ban list - G_SaveGameData(true); // Tails 12-08-2002 + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); // Tails 12-08-2002 // Shutdown. Here might be other errors. diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c index 032c535d6..2af8f4cf9 100644 --- a/src/sdl12/i_system.c +++ b/src/sdl12/i_system.c @@ -357,6 +357,7 @@ static void signal_handler(INT32 num) sigmsg = sigdef; } + G_DirtyGameData(); I_OutputMsg("signal_handler() error: %s\n", sigmsg); signal(num, SIG_DFL); //default signal action raise(num); @@ -2983,7 +2984,7 @@ void I_Quit(void) if (Playing()) K_PlayerForfeit(consoleplayer, true); - G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save + G_SaveGameData(); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -3078,7 +3079,8 @@ void I_Error(const char *error, ...) if (errorcount == 9) { M_SaveConfig(NULL); - G_SaveGameData(true); + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); } if (errorcount > 20) { @@ -3142,7 +3144,8 @@ void I_Error(const char *error, ...) #ifndef NONET D_SaveBan(); // save the ban list #endif - G_SaveGameData(true); // Tails 12-08-2002 + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); // Tails 12-08-2002 // Shutdown. Here might be other errors. if (demorecording) diff --git a/src/w_wad.c b/src/w_wad.c index 481ef672e..bf2a3f640 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -819,7 +819,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) { gamedata->everloadedaddon = true; M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(true); + G_SaveGameData(); } switch(type = ResourceFileDetect(filename)) diff --git a/src/win32ce/win_sys.c b/src/win32ce/win_sys.c index c4f4f4859..2d2a64f02 100644 --- a/src/win32ce/win_sys.c +++ b/src/win32ce/win_sys.c @@ -469,6 +469,7 @@ static void signal_handler(int num) char sigdef[64]; D_QuitNetGame(); // Fix server freezes + G_DirtyGameData(); I_ShutdownSystem(); switch (num) @@ -607,7 +608,8 @@ void I_Error(const char *error, ...) if (errorcount == 7) { M_SaveConfig(NULL); - G_SaveGameData(true); + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); } if (errorcount > 20) { @@ -636,7 +638,8 @@ void I_Error(const char *error, ...) if (!errorcount) { M_SaveConfig(NULL); // save game config, cvars.. - G_SaveGameData(true); + G_DirtyGameData(); // done first in case an error is in G_SaveGameData + G_SaveGameData(); } // save demo, could be useful for debug @@ -726,7 +729,7 @@ void I_Quit(void) G_CheckDemoStatus(); M_SaveConfig(NULL); // save game config, cvars.. - G_SaveGameData(false); // undirty your save + G_SaveGameData(); // undirty your save // maybe it needs that the ticcount continues, // or something else that will be finished by I_ShutdownSystem(), From 8285f0a636c3f3c3c9338231eaed2d6db7d7354c Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 17 Mar 2023 14:49:30 +0000 Subject: [PATCH 099/103] M_DrawChallengePreview for SECRET_SKIN: Keep the engine class indicator square no matter where it is on the grid --- 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 89d34e5c8..b9eb6acce 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5123,7 +5123,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) LOCKSTAT(w); #undef LOCKSTAT - V_DrawFill(4+16+1 + (s*5), (BASEVIDHEIGHT-(4+16))+1 + (w*5), 4, 4, 0); + V_DrawFill(4+16 + (s*5), (BASEVIDHEIGHT-(4+16)) + (w*5), 6, 6, 0); } } break; From a08c0d478c1b1bfea1c7f71c4fe4c86be4a67b14 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 17 Mar 2023 22:27:35 -0700 Subject: [PATCH 100/103] signal_handler_child: call G_DirtyGameData for NEWSIGNALHANDLER too --- src/sdl/i_system.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 9d593115b..91aaa1eb2 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -725,6 +725,8 @@ static void I_RegisterSignals (void) #ifdef NEWSIGNALHANDLER static void signal_handler_child(INT32 num) { + G_DirtyGameData(); + #ifdef UNIXBACKTRACE write_backtrace(num); #endif From 8d78e798a81bb13bccd4cc38370d4590c8faf0ff Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 18 Mar 2023 04:46:59 -0700 Subject: [PATCH 101/103] FINISHTIME, FINISHTIMEEXACT, FINISHTIMELEFT: use get_number for parameter Lets you use 2*TICRATE for instance, to represent 2 seconds. --- 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 8a6c2955d..f286a7ce1 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2719,7 +2719,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2) { PARAMCHECK(1); ty = UCRP_FINISHTIME + offset; - re = atoi(params[1]); + re = get_number(params[1]); if (re < 0) { From a50f665d6815d9739efd9df44ff1a37aaaba61d2 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 18 Mar 2023 04:48:51 -0700 Subject: [PATCH 102/103] UC_ROUNDSPLAYED: correct wording to "Prison" --- 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 55eeeb300..9fc0d0805 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1002,7 +1002,7 @@ static const char *M_GetConditionString(condition_t *cn) work = " Race"; break; case GDGT_PRISONS: - work = " Capsule"; + work = " Prison"; break; case GDGT_BATTLE: work = " Battle"; From 7d8fe8576756a02e787d74a63baed81cd779b17b Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 19 Mar 2023 13:06:55 +0000 Subject: [PATCH 103/103] battleprisons rename compilation repair --- src/acs/call-funcs.cpp | 2 +- src/k_hud.c | 2 +- src/k_hud_track.cpp | 2 +- src/p_inter.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index fa5da2c1c..119238cbe 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1357,7 +1357,7 @@ bool CallFunc_BreakTheCapsules(ACSVM::Thread *thread, const ACSVM::Word *argV, A (void)argV; (void)argC; - thread->dataStk.push(battlecapsules); + thread->dataStk.push(battleprisons); return false; } diff --git a/src/k_hud.c b/src/k_hud.c index 1712838fd..52e54f5ce 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -3746,7 +3746,7 @@ static void K_drawKartMinimap(void) workingPic = kp_capsuleminimap[(mobj->extravalue1 != 0 ? 1 : 0)]; break; case MT_CDUFO: - if (battlecapsules) //!battleprisons + if (battleprisons) workingPic = kp_capsuleminimap[2]; break; default: diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index d28f32337..9d2e23f23 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -365,7 +365,7 @@ bool is_object_tracking_target(const mobj_t* mobj) { case MT_BATTLECAPSULE: case MT_CDUFO: - return battlecapsules; // battleprisons + return battleprisons; case MT_SPECIAL_UFO: return true; diff --git a/src/p_inter.c b/src/p_inter.c index c7914922c..0486da414 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -636,7 +636,7 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *source) { (void)target; - if (!battlecapsules) // !battleprisons + if (!battleprisons) return; if ((gametyperules & GTR_POINTLIMIT) && (source && source->player))