From 9be607e6e2a85ec009b9440faa6350e9f8f6e29e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 7 Jun 2023 00:51:06 -0700 Subject: [PATCH 01/44] Make SPB respond to player physics scale --- src/objects/spb.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/objects/spb.c b/src/objects/spb.c index 722fc4eee..fb000c12f 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -678,6 +678,12 @@ static void SPBChase(mobj_t *spb, mobj_t *bestMobj) UINT8 spark = ((10 - chasePlayer->kartspeed) + chasePlayer->kartweight) / 2; fixed_t easiness = ((chasePlayer->kartspeed + (10 - spark)) << FRACBITS) / 2; + fixed_t scaleAdjust = FRACUNIT; + if (chase->scale > mapobjectscale) + scaleAdjust = GROW_PHYSICS_SCALE; + if (chase->scale < mapobjectscale) + scaleAdjust = SHRINK_PHYSICS_SCALE; + spb_lastplayer(spb) = chasePlayer - players; // Save the player num for death scumming... spbplace = chasePlayer->position; @@ -693,7 +699,7 @@ static void SPBChase(mobj_t *spb, mobj_t *bestMobj) // 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel baseSpeed = FixedMul( ((fracmax+1) << FRACBITS) - easiness, - K_GetKartSpeed(chasePlayer, false, false) + FixedMul(K_GetKartSpeed(chasePlayer, false, false), scaleAdjust) ) / fracmax; } From ccad5bba12e4551452fda0f3254a2be0b7e1cbe3 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 14 Jun 2023 07:58:41 -0400 Subject: [PATCH 02/44] Small bird slope improvements - Bird slopes can be made from UDMF sectors - Group is now argument 3 on the line/sector action and argument 1 on the anchor, instead of using tags. - The terminology no longer swaps between "tag" and "paramater" -- it's just called "group" now. --- src/p_setup.c | 4 ++- src/p_slopes.c | 2 ++ src/slope_anchors.c | 77 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index 8ef4aaa76..693607993 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6278,6 +6278,8 @@ static void P_ConvertBinaryLinedefTypes(void) if (lines[i].flags & ML_BLOCKMONSTERS) lines[i].args[1] |= TMSAF_MIRROR; + lines[i].args[2] = tag; + lines[i].special = LT_SLOPE_ANCHORS; break; } @@ -7132,7 +7134,7 @@ static void P_ConvertBinaryThingTypes(void) break; case FLOOR_SLOPE_THING: case CEILING_SLOPE_THING: - mapthings[i].tid = mapthings[i].extrainfo; + mapthings[i].args[0] = mapthings[i].extrainfo; break; default: break; diff --git a/src/p_slopes.c b/src/p_slopes.c index 38a173baa..d8cbee616 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -855,6 +855,8 @@ void P_SpawnSlopes(const boolean fromsave) { /// Setup anchor based slopes. P_SetupAnchoredSlopes(); + // end of jart + /// Copies slopes from tagged sectors via line specials. /// \note Doesn't actually copy, but instead they share the same pointers. for (i = 0; i < numlines; i++) diff --git a/src/slope_anchors.c b/src/slope_anchors.c index 1ee1286dd..f5bbdccdb 100644 --- a/src/slope_anchors.c +++ b/src/slope_anchors.c @@ -190,7 +190,7 @@ get_anchor mapthing_t ** anchors, fixed_t distances[3], const struct anchor_list * list, - const mtag_t tag, + const INT32 group, const vertex_t * v ){ size_t i; @@ -199,7 +199,7 @@ get_anchor for (i = 0; i < list->count; ++i) { - if (list->points[i] == v && list->anchors[i]->tid == tag) + if (list->points[i] == v && list->anchors[i]->args[0] == group) { for (k = 0; k < 3; ++k) { @@ -240,15 +240,15 @@ get_sector_anchors mapthing_t ** anchors, fixed_t distances[3], const struct anchor_list * list, - const mtag_t tag, + const INT32 group, const sector_t * sector ){ size_t i; for (i = 0; i < sector->linecount; ++i) { - get_anchor(anchors, distances, list, tag, sector->lines[i]->v1); - get_anchor(anchors, distances, list, tag, sector->lines[i]->v2); + get_anchor(anchors, distances, list, group, sector->lines[i]->v1); + get_anchor(anchors, distances, list, group, sector->lines[i]->v2); } } @@ -257,7 +257,7 @@ find_closest_anchors ( const sector_t * sector, const struct anchor_list * list, - const mtag_t tag + const INT32 group ){ fixed_t distances[3] = { INT32_MAX, INT32_MAX, INT32_MAX }; @@ -279,12 +279,12 @@ find_closest_anchors for (i = 0; i < sector->numattached; ++i) { get_sector_anchors - (anchors, distances, list, tag, §ors[sector->attached[i]]); + (anchors, distances, list, group, §ors[sector->attached[i]]); } } else { - get_sector_anchors(anchors, distances, list, tag, sector); + get_sector_anchors(anchors, distances, list, group, sector); } if (distances[2] < INT32_MAX) @@ -310,13 +310,13 @@ find_closest_anchors I_Error( "(Control Sector #%s)" - " Slope requires anchors (with Parameter %d)" + " Slope requires anchors (with group ID %d)" " near 3 of its target sectors' vertices (%d found)" "\n\nCheck the log to see which sectors were searched.", sizeu1 (sector - sectors), - tag, + group, last ); } @@ -324,11 +324,11 @@ find_closest_anchors { I_Error( "(Sector #%s)" - " Slope requires anchors (with Parameter %d)" + " Slope requires anchors (with group ID %d)" " near 3 of its vertices (%d found)", sizeu1 (sector - sectors), - tag, + group, last ); } @@ -402,9 +402,9 @@ slope_sector sector_t * sector, const INT16 flags, const struct anchor_list * list, - const mtag_t tag + const INT32 group ){ - mapthing_t ** anchors = find_closest_anchors(sector, list, tag); + mapthing_t ** anchors = find_closest_anchors(sector, list, group); if (anchors != NULL) { @@ -432,7 +432,7 @@ make_anchored_slope sector_t * s; - mtag_t tag = Tag_FGet(&line->tags); + INT32 group = line->args[2]; if (side == 0 || (line->flags & ML_TWOSIDED)) { @@ -446,17 +446,44 @@ make_anchored_slope if (plane & TMSA_FLOOR) { slope_sector - (&s->f_slope, &s->c_slope, s, flags, &floor_anchors, tag); + (&s->f_slope, &s->c_slope, s, flags, &floor_anchors, group); } if (plane & TMSA_CEILING) { slope_sector - (&s->c_slope, &s->f_slope, s, flags, &ceiling_anchors, tag); + (&s->c_slope, &s->f_slope, s, flags, &ceiling_anchors, group); } } } +static void +make_anchored_slope_from_sector +( + sector_t * s, + const int plane +){ + INT16 flags = s->args[1]; + INT32 group = s->args[2]; + + if (plane == (TMSA_FLOOR|TMSA_CEILING)) + { + flags &= ~TMSAF_MIRROR; + } + + if (plane & TMSA_FLOOR) + { + slope_sector + (&s->f_slope, &s->c_slope, s, flags, &floor_anchors, group); + } + + if (plane & TMSA_CEILING) + { + slope_sector + (&s->c_slope, &s->f_slope, s, flags, &ceiling_anchors, group); + } +} + static void P_BuildSlopeAnchorList (void) { allocate_anchors(); build_anchors(); @@ -480,4 +507,20 @@ static void P_SetupAnchoredSlopes (void) { make_anchored_slope(&lines[i], plane); } } + + for (i = 0; i < numsectors; ++i) + { + if (sectors[i].action == LT_SLOPE_ANCHORS) + { + int plane = (sectors[i].args[0] & (TMSA_FLOOR|TMSA_CEILING)); + + if (plane == 0) + { + CONS_Alert(CONS_WARNING, "Slope anchor sector %s has no planes set.\n", sizeu1(i)); + continue; + } + + make_anchored_slope_from_sector(§ors[i], plane); + } + } } From e6e7056aae76bf1356a1e7f966314709b93171f9 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 14 Jun 2023 09:16:38 -0400 Subject: [PATCH 03/44] Add bot styles & bot spawn ACS function --- src/acs/call-funcs.cpp | 46 ++++++++++++++++++++ src/acs/call-funcs.hpp | 1 + src/acs/environment.cpp | 1 + src/acs/thread.cpp | 2 - src/acs/thread.hpp | 3 +- src/d_clisrv.c | 3 ++ src/d_player.h | 11 +++++ src/k_bot.c | 95 +++++++++++++++++++++++++++-------------- src/k_bot.h | 5 ++- src/k_grandprix.c | 2 +- 10 files changed, 132 insertions(+), 37 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index cfb84c7b8..54fc987bd 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -41,6 +41,7 @@ #include "../r_skins.h" #include "../k_battle.h" #include "../k_podium.h" +#include "../k_bot.h" #include "../z_zone.h" #include "call-funcs.hpp" @@ -1710,6 +1711,51 @@ bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wor return false; } +/*-------------------------------------------------- + bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Inserts a bot, if there's room for them. +--------------------------------------------------*/ +bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + + ACSVM::String *skinStr = nullptr; + INT32 skin = -1; + + UINT8 difficulty = 0; + botStyle_e style = BOT_STYLE_NORMAL; + + UINT8 newplayernum = 0; + + (void)argC; + + map = thread->scopeMap; + + skinStr = map->getString(argV[0]); + if (skinStr->len != 0) + { + skin = R_SkinAvailable(skinStr->str); + } + + if (skin == -1) + { + skin = R_BotDefaultSkin(); + } + + difficulty = std::clamp(static_cast(argV[1]), 1, MAXBOTDIFFICULTY); + + style = static_cast(argV[2]); + if (style < BOT_STYLE_NORMAL || style >= BOT_STYLE__MAX) + { + style = BOT_STYLE_NORMAL; + } + + K_AddBot(skin, difficulty, style, &newplayernum); + thread->dataStk.push(newplayernum); + return false; +} + /*-------------------------------------------------- bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index a59b4d916..9bc46d0ef 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -84,6 +84,7 @@ bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index ea0aaf5f2..0843773f8 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -166,6 +166,7 @@ Environment::Environment() addFuncDataACS0( 502, addCallFunc(CallFunc_PodiumFinish)); addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle)); addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp)); + addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot)); } ACSVM::Thread *Environment::allocThread() diff --git a/src/acs/thread.cpp b/src/acs/thread.cpp index 88494e5b3..35d088e22 100644 --- a/src/acs/thread.cpp +++ b/src/acs/thread.cpp @@ -15,7 +15,6 @@ #include "thread.hpp" -extern "C" { #include "../doomtype.h" #include "../doomdef.h" #include "../doomstat.h" @@ -26,7 +25,6 @@ extern "C" { #include "../r_defs.h" #include "../r_state.h" #include "../p_polyobj.h" -} using namespace srb2::acs; diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index 61c0d8fe2..3c146ddf6 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -16,6 +16,7 @@ #include "acsvm.hpp" +extern "C" { #include "../doomtype.h" #include "../doomdef.h" #include "../doomstat.h" @@ -23,7 +24,7 @@ #include "../r_defs.h" #include "../r_state.h" #include "../p_spec.h" - +} namespace srb2::acs { diff --git a/src/d_clisrv.c b/src/d_clisrv.c index c36bc9d8c..445545322 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4010,6 +4010,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) INT16 newplayernum; UINT8 skinnum = 0; UINT8 difficulty = DIFFICULTBOT; + botStyle_e style = BOT_STYLE_NORMAL; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -4025,6 +4026,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) newplayernum = READUINT8(*p); skinnum = READUINT8(*p); difficulty = READUINT8(*p); + style = READUINT8(*p); CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); @@ -4045,6 +4047,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) players[newplayernum].splitscreenindex = 0; players[newplayernum].bot = true; players[newplayernum].botvars.difficulty = difficulty; + players[newplayernum].botvars.style = style; players[newplayernum].lives = 9; players[newplayernum].skincolor = skins[skinnum].prefcolor; diff --git a/src/d_player.h b/src/d_player.h index 877f65c59..9a0a7e7b5 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -313,9 +313,20 @@ struct respawnvars_t boolean init; }; +typedef enum +{ + BOT_STYLE_NORMAL, + BOT_STYLE_STAY, + //BOT_STYLE_CHASE, + //BOT_STYLE_ESCAPE, + BOT_STYLE__MAX +} botStyle_e; + // player_t struct for all bot variables struct botvars_t { + botStyle_e style; // Training mode-style CPU mode + UINT8 difficulty; // Bot's difficulty setting UINT8 diffincrease; // In GP: bot difficulty will increase this much next round boolean rival; // If true, they're the GP rival diff --git a/src/k_bot.c b/src/k_bot.c index 8c588b37f..02f5a4af1 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -37,11 +37,11 @@ #endif /*-------------------------------------------------- - boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) + boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) See header file for description. --------------------------------------------------*/ -boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) +boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) { UINT8 buf[3]; UINT8 *buf_p = buf; @@ -96,6 +96,7 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) } WRITEUINT8(buf_p, difficulty); + WRITEUINT8(buf_p, style); SendNetXCmd(XD_ADDBOT, buf, buf_p - buf); @@ -156,6 +157,9 @@ void K_UpdateMatchRaceBots(void) // While we're here, we should update bot difficulty to the proper value. players[i].botvars.difficulty = difficulty; + + // Enforce normal style for Match Race + players[i].botvars.style = BOT_STYLE_NORMAL; } else { @@ -169,7 +173,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || !(gametyperules & GTR_BOTS)) + if (difficulty == 0 || (gametyperules & GTR_BOTS) == 0) { wantedbots = 0; } @@ -217,7 +221,7 @@ void K_UpdateMatchRaceBots(void) grabskins[index] = grabskins[--usableskins]; } - if (!K_AddBot(skinnum, difficulty, &newplayernum)) + if (!K_AddBot(skinnum, difficulty, BOT_STYLE_NORMAL, &newplayernum)) { // Not enough player slots to add the bot, break the loop. break; @@ -231,7 +235,6 @@ void K_UpdateMatchRaceBots(void) UINT8 buf[2]; i = MAXPLAYERS; - while (numbots > wantedbots && i > 0) { i--; @@ -1473,11 +1476,11 @@ static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd) } /*-------------------------------------------------- - void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) + static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd) - See header file for description. + Build ticcmd for bots with a style of BOT_STYLE_NORMAL --------------------------------------------------*/ -void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) +static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd) { precise_t t = 0; botprediction_t *predict = NULL; @@ -1487,29 +1490,6 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) INT32 turnamt = 0; const line_t *botController = player->botvars.controller != UINT16_MAX ? &lines[player->botvars.controller] : NULL; - // Remove any existing controls - memset(cmd, 0, sizeof(ticcmd_t)); - - if (player->mo == NULL - || player->spectator == true - || G_GamestateUsesLevel() == false) - { - // Not in the level. - return; - } - - // Complete override of all ticcmd functionality - if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true) - { - return; - } - - if (K_PodiumSequence() == true) - { - K_BuildBotPodiumTiccmd(player, cmd); - return; - } - if (!(gametyperules & GTR_BOTS) // No bot behaviors || K_GetNumWaypoints() == 0 // No waypoints || leveltime <= introtime // During intro camera @@ -1762,6 +1742,59 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) } } +/*-------------------------------------------------- + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) + + See header file for description. +--------------------------------------------------*/ +void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) +{ + precise_t t = 0; + botprediction_t *predict = NULL; + boolean trySpindash = true; + angle_t destangle = 0; + UINT8 spindash = 0; + INT32 turnamt = 0; + const line_t *botController = player->botvars.controller != UINT16_MAX ? &lines[player->botvars.controller] : NULL; + + // Remove any existing controls + memset(cmd, 0, sizeof(ticcmd_t)); + + if (player->mo == NULL + || player->spectator == true + || G_GamestateUsesLevel() == false) + { + // Not in the level. + return; + } + + // Complete override of all ticcmd functionality + if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true) + { + return; + } + + if (K_PodiumSequence() == true) + { + K_BuildBotPodiumTiccmd(player, cmd); + return; + } + + switch (player->botvars.style) + { + case BOT_STYLE_STAY: + { + // Hey, this one's pretty easy :P + break; + } + default: + { + K_BuildBotTiccmdNormal(player, cmd); + break; + } + } +} + /*-------------------------------------------------- void K_UpdateBotGameplayVars(player_t *player); diff --git a/src/k_bot.h b/src/k_bot.h index ab75034ee..8eca06316 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -142,13 +142,14 @@ fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t /*-------------------------------------------------- - boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); + boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *newplayernum); Returns the waypoint actually being used as the finish line. Input Arguments:- skin - Skin number that the bot will use. difficulty - Difficulty level this bot will use. + style - Bot style to spawn this bot with, see botStyle_e. newplayernum - Pointer to the last valid player slot number. Is a pointer so that this function can be called multiple times to add more than one bot. @@ -156,7 +157,7 @@ fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t true if a bot packet can be sent, otherwise false. --------------------------------------------------*/ -boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); +boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p); /*-------------------------------------------------- diff --git a/src/k_grandprix.c b/src/k_grandprix.c index dff88ceab..073ad7362 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -256,7 +256,7 @@ void K_InitGrandPrixBots(void) for (i = 0; i < wantedbots; i++) { - if (!K_AddBot(botskinlist[i], difficultylevels[i], &newplayernum)) + if (!K_AddBot(botskinlist[i], difficultylevels[i], BOT_STYLE_NORMAL, &newplayernum)) { break; } From 80d0231e8afe3477952252c31deccaaaf79b2df8 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 14 Jun 2023 09:30:34 -0400 Subject: [PATCH 04/44] ACS AddBot: Push -1 on fail instead of MAXPLAYERS --- src/acs/call-funcs.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 54fc987bd..6987f65c9 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1727,6 +1727,7 @@ bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word botStyle_e style = BOT_STYLE_NORMAL; UINT8 newplayernum = 0; + bool success = false; (void)argC; @@ -1751,8 +1752,8 @@ bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word style = BOT_STYLE_NORMAL; } - K_AddBot(skin, difficulty, style, &newplayernum); - thread->dataStk.push(newplayernum); + success = K_AddBot(skin, difficulty, style, &newplayernum); + thread->dataStk.push(success ? newplayernum : -1); return false; } From d936c7aed9c242b219bf6a33bb23cb6631b7e8b7 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 14 Jun 2023 10:29:19 -0400 Subject: [PATCH 05/44] Milky Way terrain `OutRun 48.0` on Terrain block will make that texture add 48 units to driving top speed. --- src/d_player.h | 2 ++ src/k_kart.c | 30 ++++++++++++++++++++---------- src/k_terrain.c | 7 +++++++ src/k_terrain.h | 1 + src/p_user.c | 5 +++++ 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 877f65c59..be2b96a70 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -752,6 +752,8 @@ struct player_t boolean markedfordeath; + fixed_t outrun; // Milky Way road effect + uint8_t public_key[PUBKEYLENGTH]; #ifdef HWRENDER diff --git a/src/k_kart.c b/src/k_kart.c index 33fcd8819..31646d16a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3337,6 +3337,11 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb finalspeed = FixedMul(finalspeed, mapobjectscale); + if (dorubberband == true && K_PlayerUsesBotMovement(player) == true) + { + finalspeed = FixedMul(finalspeed, player->botvars.rubberband); + } + if (doboostpower == true) { if (mobjValid == true) @@ -3346,11 +3351,12 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb } finalspeed = FixedMul(finalspeed, player->boostpower + player->speedboost); - } - if (dorubberband == true && K_PlayerUsesBotMovement(player) == true) - { - finalspeed = FixedMul(finalspeed, player->botvars.rubberband); + if (mobjValid == true && player->outrun != 0) + { + // Milky Way's roads + finalspeed += FixedMul(player->outrun, K_GrowShrinkSpeedMul(player)); + } } return finalspeed; @@ -3369,18 +3375,22 @@ fixed_t K_GetKartAccel(player_t *player) k_accel += 17 * stat; // 121 - 257 - if (K_PodiumSequence() == true) - { - return FixedMul(k_accel, FRACUNIT / 4); - } - // Marble Garden Top gets 1200% accel if (player->curshield == KSHIELD_TOP) { k_accel *= 12; } - return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4); + if (K_PodiumSequence() == true) + { + k_accel = FixedMul(k_accel, FRACUNIT / 4); + } + else + { + k_accel = FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4); + } + + return k_accel; } UINT16 K_GetKartFlashing(player_t *player) diff --git a/src/k_terrain.c b/src/k_terrain.c index 55a6be6a4..00bf76b08 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -455,6 +455,9 @@ void K_ProcessTerrainEffect(mobj_t *mo) return; } + // Milky Way road effect + player->outrun = terrain->outrun + (48*FRACUNIT); + // Damage effects if (terrain->damageType > 0) { @@ -1688,6 +1691,10 @@ static void K_ParseTerrainParameter(size_t i, char *param, char *val) { terrain->springStarColor = get_number(val); } + else if (stricmp(param, "outrun") == 0 || stricmp(param, "speedIncrease") == 0) + { + terrain->outrun = FLOAT_TO_FIXED(atof(val)); + } else if (stricmp(param, "floorClip") == 0) { terrain->floorClip = FLOAT_TO_FIXED(atof(val)); diff --git a/src/k_terrain.h b/src/k_terrain.h index e3cce83ad..5a9dcb25c 100644 --- a/src/k_terrain.h +++ b/src/k_terrain.h @@ -122,6 +122,7 @@ struct terrain_t angle_t speedPadAngle; // Speed pad angle fixed_t springStrength; // Spring strength UINT16 springStarColor; // Spring star color + fixed_t outrun; // Raise top speed by this amount, for super fast road. fixed_t floorClip; // Offset for sprites on this ground UINT32 flags; // Flag values (see: terrain_flags_t) }; diff --git a/src/p_user.c b/src/p_user.c index a415d6c98..c4535efb8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4609,6 +4609,11 @@ void P_PlayerAfterThink(player_t *player) } #endif + if (P_IsObjectOnGround(player->mo) == true) + { + player->outrun = 0; + } + #ifdef SECTORSPECIALSAFTERTHINK if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo)) player->onconveyor = 0; From 6d1a3de24394a9886e8431ddc17e1d9528824739 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 14 Jun 2023 10:37:05 -0400 Subject: [PATCH 06/44] Add outrun to netsave --- src/k_terrain.c | 2 +- src/p_saveg.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/k_terrain.c b/src/k_terrain.c index 00bf76b08..f9114f75c 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -456,7 +456,7 @@ void K_ProcessTerrainEffect(mobj_t *mo) } // Milky Way road effect - player->outrun = terrain->outrun + (48*FRACUNIT); + player->outrun = terrain->outrun; // Damage effects if (terrain->damageType > 0) diff --git a/src/p_saveg.c b/src/p_saveg.c index ba096a480..368ec5742 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -433,6 +433,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].markedfordeath); + WRITEFIXED(save->p, players[i].outrun); + // respawnvars_t WRITEUINT8(save->p, players[i].respawn.state); WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].respawn.wp)); @@ -828,6 +830,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].markedfordeath = READUINT8(save->p); + players[i].outrun = READFIXED(save->p); + // respawnvars_t players[i].respawn.state = READUINT8(save->p); players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save->p); From 6b125ea5759cd45cb1d7f76cace351ccadc4cbb5 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 5 Jun 2023 18:53:12 -0700 Subject: [PATCH 07/44] Sprite directional lighting, add SpriteBacklight option to level header - Sprites have directional lighting, like walls - For normal sprites: contrast is much stronger than walls - Papersprites look the same as walls - SpriteBacklight option in level header weakens the contrast for sprites only - SpriteBacklight subtracts from LightContrast - E.g. SpriteBacklight = 0 would let it match LightContrast - E.g. SpriteBacklight = 60 would make the contrast much weaker - Negative values make the contrast stronger --- src/deh_soc.c | 4 ++++ src/doomstat.h | 2 ++ src/p_setup.c | 1 + src/p_spec.c | 1 + src/r_things.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index f6ae6a285..d0ca37e45 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1278,6 +1278,10 @@ void readlevelheader(MYFILE *f, char * name) { mapheaderinfo[num]->light_contrast = (UINT8)i; } + else if (fastcmp(word, "SPRITEBACKLIGHT")) + { + mapheaderinfo[num]->sprite_backlight = (SINT8)i; + } else if (fastcmp(word, "LIGHTANGLE")) { if (fastcmp(word2, "EVEN")) diff --git a/src/doomstat.h b/src/doomstat.h index 950645ef6..53071a319 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -503,6 +503,7 @@ struct mapheader_t UINT16 palette; ///< PAL lump to use on this map UINT16 encorepal; ///< PAL for encore mode UINT8 light_contrast; ///< Range of wall lighting. 0 is no lighting. + SINT8 sprite_backlight; ///< Subtract from wall lighting for sprites only. boolean use_light_angle; ///< When false, wall lighting is evenly distributed. When true, wall lighting is directional. angle_t light_angle; ///< Angle of directional wall lighting. @@ -762,6 +763,7 @@ extern fixed_t mapobjectscale; extern struct maplighting { UINT8 contrast; + SINT8 backlight; boolean directional; angle_t angle; } maplighting; diff --git a/src/p_setup.c b/src/p_setup.c index 02e19702d..c9a79c812 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -438,6 +438,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->mobj_scale = FRACUNIT; mapheaderinfo[num]->default_waypoint_radius = 0; mapheaderinfo[num]->light_contrast = 16; + mapheaderinfo[num]->sprite_backlight = 0; mapheaderinfo[num]->use_light_angle = false; mapheaderinfo[num]->light_angle = 0; #if 1 // equivalent to "Followers = DEFAULT" diff --git a/src/p_spec.c b/src/p_spec.c index 1a794c3ef..51a62332c 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6732,6 +6732,7 @@ void P_InitSpecials(void) // Set map lighting settings. maplighting.contrast = mapheaderinfo[gamemap-1]->light_contrast; + maplighting.backlight = mapheaderinfo[gamemap-1]->sprite_backlight; maplighting.directional = mapheaderinfo[gamemap-1]->use_light_angle; maplighting.angle = mapheaderinfo[gamemap-1]->light_angle; diff --git a/src/r_things.c b/src/r_things.c index ca164a398..4d1c17263 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1630,6 +1630,26 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) box->x2test = 0; } +static fixed_t R_GetSpriteDirectionalLighting(angle_t angle) +{ + // Copied from P_UpdateSegLightOffset + const UINT8 contrast = min(max(0, maplighting.contrast - maplighting.backlight), UINT8_MAX); + const fixed_t contrastFixed = ((fixed_t)contrast) * FRACUNIT; + + fixed_t light = FRACUNIT; + fixed_t extralight = 0; + + light = FixedMul(FINECOSINE(angle >> ANGLETOFINESHIFT), FINECOSINE(maplighting.angle >> ANGLETOFINESHIFT)) + + FixedMul(FINESINE(angle >> ANGLETOFINESHIFT), FINESINE(maplighting.angle >> ANGLETOFINESHIFT)); + light = (light + FRACUNIT) / 2; + + light = FixedMul(light, FRACUNIT - FSIN(abs(AngleDeltaSigned(angle, maplighting.angle)) / 2)); + + extralight = -contrastFixed + FixedMul(light, contrastFixed * 2); + + return extralight; +} + // // R_ProjectSprite // Generates a vissprite for a thing @@ -2275,6 +2295,38 @@ static void R_ProjectSprite(mobj_t *thing) lightnum = (lightnum + R_ThingLightLevel(oldthing)) >> LIGHTSEGSHIFT; + if (maplighting.directional == true) + { + fixed_t extralight = R_GetSpriteDirectionalLighting(papersprite + ? interp.angle + (ang >= ANGLE_180 ? -ANGLE_90 : ANGLE_90) + : R_PointToAngle(interp.x, interp.y)); + + // Less change in contrast in dark sectors + extralight = FixedMul(extralight, min(max(0, lightnum), LIGHTLEVELS - 1) * FRACUNIT / (LIGHTLEVELS - 1)); + + if (papersprite) + { + // Papersprite contrast should match walls + lightnum += FixedFloor((extralight / 8) + (FRACUNIT / 2)) / FRACUNIT; + } + else + { + fixed_t n = FixedDiv(FixedMul(xscale, LIGHTRESOLUTIONFIX), ((MAXLIGHTSCALE-1) << LIGHTSCALESHIFT)); + + // Less change in contrast at further distances, to counteract DOOM diminished light + extralight = FixedMul(extralight, min(n, FRACUNIT)); + + // Contrast is stronger for normal sprites, stronger than wall lighting is at the same distance + lightnum += FixedFloor((extralight / 4) + (FRACUNIT / 2)) / FRACUNIT; + } + + // Semibright objects will be made slightly brighter to compensate contrast + if (R_ThingIsSemiBright(oldthing)) + { + lightnum += 2; + } + } + if (lightnum < 0) lights_array = scalelight[0]; else if (lightnum >= LIGHTLEVELS) From 86be5f7354658fd1f5d2d407a3e3b3581349e837 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 14 Jun 2023 20:09:32 -0700 Subject: [PATCH 08/44] SaveMobjThinker: fix diff2 uninitialized --- src/p_saveg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index ba096a480..f022feedf 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2355,6 +2355,8 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 if (mobj->type == MT_SPARK) return; + diff2 = 0; + if (mobj->spawnpoint) { // spawnpoint is not modified but we must save it since it is an identifier @@ -2408,8 +2410,6 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 } } - diff2 = 0; - // not the default but the most probable if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz != 0) diff |= MD_MOM; From 54e6efc79825bfb9b4689a4268ac3240ab6b75e3 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Jun 2023 12:06:21 -0400 Subject: [PATCH 09/44] K_AddBot is direct No longer sends a packet from the server, now all clients do it. Old method is kept just in case. --- src/d_clisrv.c | 36 +-------- src/k_bot.c | 195 +++++++++++++++++++++++++++++++++------------- src/k_bot.h | 53 +++++++++++-- src/k_grandprix.c | 17 +++- src/m_random.h | 4 + src/p_setup.c | 2 +- 6 files changed, 210 insertions(+), 97 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 445545322..67718cc4b 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4028,41 +4028,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) difficulty = READUINT8(*p); style = READUINT8(*p); - CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); - - // Clear player before joining, lest some things get set incorrectly - CL_ClearPlayer(newplayernum); - G_DestroyParty(newplayernum); - - playeringame[newplayernum] = true; - G_AddPlayer(newplayernum); - if (newplayernum+1 > doomcom->numslots) - doomcom->numslots = (INT16)(newplayernum+1); - - playernode[newplayernum] = servernode; - - // this will permit unlocks - memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8)); - - players[newplayernum].splitscreenindex = 0; - players[newplayernum].bot = true; - players[newplayernum].botvars.difficulty = difficulty; - players[newplayernum].botvars.style = style; - players[newplayernum].lives = 9; - - players[newplayernum].skincolor = skins[skinnum].prefcolor; - sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); - SetPlayerSkinByNum(newplayernum, skinnum); - - playerconsole[newplayernum] = newplayernum; - G_BuildLocalSplitscreenParty(newplayernum); - - if (netgame) - { - HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); - } - - LUA_HookInt(newplayernum, HOOK(PlayerJoin)); + K_SetBot(newplayernum, skinnum, difficulty, style); } static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, diff --git a/src/k_bot.c b/src/k_bot.c index 02f5a4af1..2ce99b6bd 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -31,11 +31,61 @@ #include "k_podium.h" #include "k_respawn.h" #include "m_easing.h" +#include "d_clisrv.h" +#include "g_party.h" +#include "k_grandprix.h" // K_CanChangeRules +#include "hu_stuff.h" // HU_AddChatText +#include "discord.h" // DRPC_UpdatePresence +#include "i_net.h" // doomcom #ifdef DEVELOP consvar_t cv_botcontrol = CVAR_INIT ("botcontrol", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); #endif +/*-------------------------------------------------- + void K_SetBot(UINT8 playerNum, UINT8 skinnum, UINT8 difficulty, botStyle_e style) + + See header file for description. +--------------------------------------------------*/ +void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style) +{ + CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); + + // Clear player before joining, lest some things get set incorrectly + CL_ClearPlayer(newplayernum); + G_DestroyParty(newplayernum); + + playeringame[newplayernum] = true; + G_AddPlayer(newplayernum); + if (newplayernum+1 > doomcom->numslots) + doomcom->numslots = (INT16)(newplayernum+1); + + playernode[newplayernum] = servernode; + + // this will permit unlocks + memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8)); + + players[newplayernum].splitscreenindex = 0; + players[newplayernum].bot = true; + players[newplayernum].botvars.difficulty = difficulty; + players[newplayernum].botvars.style = style; + players[newplayernum].lives = 9; + + players[newplayernum].skincolor = skins[skinnum].prefcolor; + sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); + SetPlayerSkinByNum(newplayernum, skinnum); + + playerconsole[newplayernum] = newplayernum; + G_BuildLocalSplitscreenParty(newplayernum); + + if (netgame) + { + HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); + } + + LUA_HookInt(newplayernum, HOOK(PlayerJoin)); +} + /*-------------------------------------------------- boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) @@ -43,8 +93,40 @@ --------------------------------------------------*/ boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) { - UINT8 buf[3]; - UINT8 *buf_p = buf; + UINT8 newplayernum = *p; + + for (; newplayernum < MAXPLAYERS; newplayernum++) + { + if (playeringame[newplayernum] == false) + { + // free player slot + break; + } + } + + if (newplayernum >= MAXPLAYERS) + { + // nothing is free + *p = MAXPLAYERS; + return false; + } + + K_SetBot(newplayernum, skin, difficulty, style); + DEBFILE(va("Everyone added bot %d\n", newplayernum)); + + // use the next free slot + *p = newplayernum+1; + + return true; +} + +/*-------------------------------------------------- + boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) + + See header file for description. +--------------------------------------------------*/ +boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) +{ UINT8 newplayernum = *p; // search for a free playernum @@ -54,57 +136,66 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) UINT8 n; for (n = 0; n < MAXNETNODES; n++) + { if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum || nodetoplayer3[n] == newplayernum || nodetoplayer4[n] == newplayernum) break; + } if (n == MAXNETNODES) break; } - while (playeringame[newplayernum] - && players[newplayernum].bot - && newplayernum < MAXPLAYERS) + for (; newplayernum < MAXPLAYERS; newplayernum++) { - newplayernum++; + if (playeringame[newplayernum] == false) + { + // free player slot + break; + } } if (newplayernum >= MAXPLAYERS) { - *p = newplayernum; + // nothing is free + *p = MAXPLAYERS; return false; } - WRITEUINT8(buf_p, newplayernum); - - if (skin > numskins) + if (server) { - skin = numskins; + UINT8 buf[4]; + UINT8 *buf_p = buf; + + WRITEUINT8(buf_p, newplayernum); + + if (skin > numskins) + { + skin = numskins; + } + + WRITEUINT8(buf_p, skin); + + if (difficulty < 1) + { + difficulty = 1; + } + else if (difficulty > MAXBOTDIFFICULTY) + { + difficulty = MAXBOTDIFFICULTY; + } + + WRITEUINT8(buf_p, difficulty); + WRITEUINT8(buf_p, style); + + SendNetXCmd(XD_ADDBOT, buf, buf_p - buf); + DEBFILE(va("Server added bot %d\n", newplayernum)); } - WRITEUINT8(buf_p, skin); - - if (difficulty < 1) - { - difficulty = 1; - } - else if (difficulty > MAXBOTDIFFICULTY) - { - difficulty = MAXBOTDIFFICULTY; - } - - WRITEUINT8(buf_p, difficulty); - WRITEUINT8(buf_p, style); - - SendNetXCmd(XD_ADDBOT, buf, buf_p - buf); - - DEBFILE(va("Server added bot %d\n", newplayernum)); // use the next free slot (we can't put playeringame[newplayernum] = true here) - newplayernum++; - - *p = newplayernum; + *p = newplayernum+1; return true; } @@ -126,11 +217,6 @@ void K_UpdateMatchRaceBots(void) UINT8 grabskins[MAXSKINS+1]; UINT8 i; - if (!server) - { - return; - } - // Init usable bot skins list for (i = 0; i < numskins; i++) { @@ -173,12 +259,16 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || (gametyperules & GTR_BOTS) == 0) + if (K_CanChangeRules(true) == false + || (gametyperules & GTR_BOTS) == 0 + || difficulty == 0) { + // Remove bots if there are any. wantedbots = 0; } else { + // Add bots to fill up MAXPLAYERS wantedbots = pmax - numplayers - numwaiting; if (wantedbots < 0) @@ -201,11 +291,15 @@ void K_UpdateMatchRaceBots(void) 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; } @@ -216,7 +310,7 @@ void K_UpdateMatchRaceBots(void) if (usableskins > 0) { - UINT8 index = M_RandomKey(usableskins); + UINT8 index = P_RandomKey(PR_BOTS, usableskins); skinnum = grabskins[index]; grabskins[index] = grabskins[--usableskins]; } @@ -232,8 +326,6 @@ void K_UpdateMatchRaceBots(void) } else if (numbots > wantedbots) { - UINT8 buf[2]; - i = MAXPLAYERS; while (numbots > wantedbots && i > 0) { @@ -241,16 +333,18 @@ void K_UpdateMatchRaceBots(void) if (playeringame[i] && players[i].bot) { - buf[0] = i; - buf[1] = KR_LEAVE; - SendNetXCmd(XD_REMOVEPLAYER, &buf, 2); - + CL_RemovePlayer(i, KR_LEAVE); numbots--; } } } // We should have enough bots now :) + +#ifdef HAVE_DISCORDRPC + // Player count change was possible, so update presence + DRPC_UpdatePresence(); +#endif } /*-------------------------------------------------- @@ -1749,14 +1843,6 @@ static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) { - precise_t t = 0; - botprediction_t *predict = NULL; - boolean trySpindash = true; - angle_t destangle = 0; - UINT8 spindash = 0; - INT32 turnamt = 0; - const line_t *botController = player->botvars.controller != UINT16_MAX ? &lines[player->botvars.controller] : NULL; - // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); @@ -1768,7 +1854,10 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) return; } - // Complete override of all ticcmd functionality + // Complete override of all ticcmd functionality. + // May add more hooks to individual pieces of bot ticcmd, + // but this should always be here so anyone can roll + // their own :) if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true) { return; diff --git a/src/k_bot.h b/src/k_bot.h index 8eca06316..eb83cbddf 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -138,13 +138,10 @@ fixed_t K_UpdateRubberband(player_t *player); fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); -// NOT AVAILABLE FOR LUA - - /*-------------------------------------------------- - boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *newplayernum); + boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p); - Returns the waypoint actually being used as the finish line. + Adds a new bot, using code intended to run on all clients. Input Arguments:- skin - Skin number that the bot will use. @@ -154,12 +151,56 @@ fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t Is a pointer so that this function can be called multiple times to add more than one bot. Return:- - true if a bot packet can be sent, otherwise false. + true if a bot was added, otherwise false. --------------------------------------------------*/ boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p); +// NOT AVAILABLE FOR LUA + + +/*-------------------------------------------------- + void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style); + + Sets a player ID to be a new bot directly. Invoked directly + by K_AddBot, and indirectly by K_AddBotFromServer by sending + a packet. + + Input Arguments:- + newplayernum - Player slot number to set as a bot. + skin - Skin number that the bot will use. + difficulty - Difficulty level this bot will use. + style - Bot style to spawn this bot with, see botStyle_e. + + Return:- + None +--------------------------------------------------*/ + +void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style); + + +/*-------------------------------------------------- + boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *newplayernum); + + Adds a new bot, using a server-sided packet sent to all clients. + Using regular K_AddBot wherever possible is better, but this is kept + as a back-up measure if this is the only option. + + Input Arguments:- + skin - Skin number that the bot will use. + difficulty - Difficulty level this bot will use. + style - Bot style to spawn this bot with, see botStyle_e. + newplayernum - Pointer to the last valid player slot number. + Is a pointer so that this function can be called multiple times to add more than one bot. + + Return:- + true if a bot can be added via a packet later, otherwise false. +--------------------------------------------------*/ + +boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p); + + /*-------------------------------------------------- void K_UpdateMatchRaceBots(void); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 073ad7362..47b4c22ab 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -186,6 +186,13 @@ void K_InitGrandPrixBots(void) { if (playeringame[i]) { + if (players[i].bot == true) + { + // Remove existing bots. + CL_RemovePlayer(i, KR_LEAVE); + continue; + } + if (numplayers < MAXSPLITSCREENPLAYERS && !players[i].spectator) { competitors[numplayers] = i; @@ -227,11 +234,15 @@ void K_InitGrandPrixBots(void) 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; } @@ -245,7 +256,7 @@ void K_InitGrandPrixBots(void) if (usableskins > 0) { - UINT8 index = M_RandomKey(usableskins); + UINT8 index = P_RandomKey(PR_BOTS, usableskins); skinnum = grabskins[index]; grabskins[index] = grabskins[--usableskins]; } @@ -327,8 +338,10 @@ void K_UpdateGrandPrixBots(void) UINT16 newrivalscore = 0; UINT8 i; - if (K_PodiumSequence()) + if (K_PodiumSequence() == true) + { return; + } for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/m_random.h b/src/m_random.h index febfd4689..039dcb6ad 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -40,6 +40,8 @@ typedef enum // However each instance of RNG being used for // gameplay should be split up as much as possible. + // Place new ones at the end for demo compatibility. + PR_EXECUTOR, // Linedef executor PR_ACS, // ACS scripts @@ -72,6 +74,8 @@ typedef enum PR_MOVINGTARGET, // Randomised moving targets + PR_BOTS, // Bot spawning + PRNUMCLASS } pr_class_t; diff --git a/src/p_setup.c b/src/p_setup.c index 02e19702d..b1e221e67 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7739,7 +7739,7 @@ static void P_InitGametype(void) grandprixinfo.wonround = false; } } - else if (!modeattacking) + else { // We're in a Match Race, use simplistic randomized bots. K_UpdateMatchRaceBots(); From a8467a22ce257c293e6abbf9a50add967b5ee1a4 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Jun 2023 12:12:41 -0400 Subject: [PATCH 10/44] Fix speedometer in outrun mode --- src/k_kart.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 31646d16a..595d1f2b8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3301,6 +3301,7 @@ fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed) fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberband) { const boolean mobjValid = (player->mo != NULL && P_MobjWasRemoved(player->mo) == false); + const fixed_t physicsScale = mobjValid ? K_GrowShrinkSpeedMul(player) : FRACUNIT; fixed_t finalspeed = 0; if (K_PodiumSequence() == true) @@ -3344,19 +3345,17 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberb if (doboostpower == true) { - if (mobjValid == true) - { - // Scale with the player. - finalspeed = FixedMul(finalspeed, K_GrowShrinkSpeedMul(player)); - } + // Scale with the player. + finalspeed = FixedMul(finalspeed, physicsScale); + // Add speed boosts. finalspeed = FixedMul(finalspeed, player->boostpower + player->speedboost); + } - if (mobjValid == true && player->outrun != 0) - { - // Milky Way's roads - finalspeed += FixedMul(player->outrun, K_GrowShrinkSpeedMul(player)); - } + if (player->outrun != 0) + { + // Milky Way's roads + finalspeed += FixedMul(player->outrun, physicsScale); } return finalspeed; From d63c1d8ab4afdb92f51ecd43f2b27832442a92d5 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 15 Jun 2023 12:44:19 -0400 Subject: [PATCH 11/44] Reorder stuff in P_SpawnMobjFromMapThing Fixes the loops issue Tyron ran into. Way, way too much stuff was being initialized after P_SetupSpawnedMapThing instead of before. Also get rid of weird doangle pointer crap, I don't understand why it did it that way to begin with? The only thing that I didn't write that needed to set it was P_SetupMace, which just seemed to be a weird hack to get around the weird ordering, instead of just changing the ordering? A lot of objects even double-calculate the angle just because it hasn't been set yet... --- src/p_mobj.c | 67 ++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index f0e529a0d..d2fd861cf 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12305,7 +12305,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) return true; } -static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) +static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj) { fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor; angle_t mspokeangle; @@ -12350,7 +12350,6 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) mobj->lastlook = mspeed; mobj->movecount = mobj->lastlook; mobj->angle = FixedAngle(myaw << FRACBITS); - *doangle = false; mobj->threshold = (FixedAngle(mpitch << FRACBITS) >> ANGLETOFINESHIFT); mobj->movefactor = mpinch; mobj->movedir = 0; @@ -12745,7 +12744,7 @@ static boolean P_MapAlreadyHasStarPost(mobj_t *mobj) return false; } -static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) +static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) { boolean override = LUA_HookMapThingSpawn(mobj, mthing); @@ -12857,7 +12856,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean case MT_CHAINPOINT: case MT_FIREBARPOINT: case MT_CUSTOMMACEPOINT: - if (!P_SetupMace(mthing, mobj, doangle)) + if (!P_SetupMace(mthing, mobj)) return false; break; case MT_PARTICLEGEN: @@ -13350,23 +13349,18 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } case MT_DUELBOMB: { - // Duel Bomb needs init to match real map thing's angle - mobj->angle = FixedAngle(mthing->angle << FRACBITS); Obj_DuelBombInit(mobj); if (mthing->args[1]) { Obj_DuelBombReverse(mobj); } - - *doangle = false; break; } case MT_BANANA: { // Give Duel bananas a random angle mobj->angle = FixedMul(P_RandomFixed(PR_DECORATION), ANGLE_MAX); - *doangle = false; break; } case MT_HYUDORO_CENTER: @@ -13398,37 +13392,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj->flags2 |= MF2_BOSSNOTRAP; } - return true; -} - -static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i) -{ - mobj_t *mobj = NULL; - boolean doangle = true; - size_t arg = SIZE_MAX; - - mobj = P_SpawnMobj(x, y, z, i); - mobj->spawnpoint = mthing; - - P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); - mobj->destscale = FixedMul(mobj->destscale, mthing->scale); - - if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) - { - if (P_MobjWasRemoved(mobj)) - return NULL; - - return mobj; - } - - if (doangle) - { - mobj->angle = FixedAngle(mthing->angle << FRACBITS); - } - if ((mobj->flags & MF_SPRING) - && mobj->info->damage != 0 - && mobj->info->mass == 0) + && mobj->info->damage != 0 + && mobj->info->mass == 0) { // Offset sprite of horizontal springs angle_t a = mobj->angle + ANGLE_180; @@ -13436,9 +13402,24 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, mobj->spryoff = FixedMul(mobj->radius, FINESINE(a >> ANGLETOFINESHIFT)); } + return true; +} + +static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i) +{ + mobj_t *mobj = NULL; + size_t arg = SIZE_MAX; + + mobj = P_SpawnMobj(x, y, z, i); + mobj->spawnpoint = mthing; + + mobj->angle = FixedAngle(mthing->angle << FRACBITS); mobj->pitch = FixedAngle(mthing->pitch << FRACBITS); mobj->roll = FixedAngle(mthing->roll << FRACBITS); + P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); + mobj->destscale = FixedMul(mobj->destscale, mthing->scale); + P_SetThingTID(mobj, mthing->tid); mobj->special = mthing->special; @@ -13468,6 +13449,14 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, M_Memcpy(mobj->stringargs[arg], mthing->stringargs[arg], len + 1); } + if (!P_SetupSpawnedMapThing(mthing, mobj)) + { + if (P_MobjWasRemoved(mobj)) + return NULL; + + return mobj; + } + mthing->mobj = mobj; // Generic reverse gravity for individual objects flag. From 1fee9f65fb68722772960793033e68d05e03d287 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 15 Jun 2023 18:46:44 +0100 Subject: [PATCH 12/44] Servant Hand - Points in the direction of the best waypoint to take - Vwoops in and out like a drop target squash-n-stretch - Shows WRONG WAY only on debugwaypoints - Flexible enough to be used for custom purposes and other gametypes, the only caveat being if those gametypes use GTR_CIRCUIT conflicting with the other purpose of PF_WRONGWAY --- src/d_player.h | 4 ++ src/deh_tables.c | 4 ++ src/g_game.c | 1 + src/info.c | 31 +++++++++++++ src/info.h | 6 +++ src/k_hud.c | 7 +-- src/k_kart.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++- src/k_kart.h | 1 + src/p_mobj.c | 16 +++++++ src/p_saveg.c | 25 +++++++++++ 10 files changed, 198 insertions(+), 7 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 877f65c59..7775a70f4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -744,10 +744,14 @@ struct player_t mobj_t *stumbleIndicator; mobj_t *sliptideZipIndicator; mobj_t *whip; + mobj_t *hand; UINT8 instaShieldCooldown; UINT8 guardCooldown; + UINT8 handtimer; + angle_t besthanddirection; + INT16 incontrol; // -1 to -175 when spinning out or tumbling, 1 to 175 when not. Use to check for combo hits or emergency inputs. boolean markedfordeath; diff --git a/src/deh_tables.c b/src/deh_tables.c index 3cce64a49..e44c6cb52 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3291,6 +3291,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BLOCKRING", "S_BLOCKBODY", + "S_SERVANTHAND", + // Signpost sparkles "S_SIGNSPARK1", "S_SIGNSPARK2", @@ -5337,6 +5339,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BLOCKRING", "MT_BLOCKBODY", + "MT_SERVANTHAND", + "MT_SIGNSPARKLE", "MT_FASTLINE", diff --git a/src/g_game.c b/src/g_game.c index 5c3e3ca4a..8d8610741 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2689,6 +2689,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) P_SetTarget(&players[player].awayview.mobj, NULL); P_SetTarget(&players[player].stumbleIndicator, NULL); P_SetTarget(&players[player].whip, NULL); + P_SetTarget(&players[player].hand, NULL); P_SetTarget(&players[player].ringShooter, NULL); P_SetTarget(&players[player].followmobj, NULL); diff --git a/src/info.c b/src/info.c index 05ce6aa36..cbe5556a3 100644 --- a/src/info.c +++ b/src/info.c @@ -559,6 +559,8 @@ char sprnames[NUMSPRITES + 1][5] = "GRNG", // Guard ring "GBDY", // Guard body + "DHND", // Servant Hand + "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks "BDRF", // Brake drift sparks @@ -3956,6 +3958,8 @@ state_t states[NUMSTATES] = {SPR_GRNG, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_BLOCKRING {SPR_GBDY, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_BLOCKBODY + {SPR_DHND, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SERVANTHAND + {SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3 @@ -22760,6 +22764,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags S_NULL // raisestate }, + + { // MT_SERVANTHAND + -1, // doomednum + S_SERVANTHAND, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 40*FRACUNIT, // radius + 40*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, { // MT_SIGNSPARKLE -1, // doomednum diff --git a/src/info.h b/src/info.h index ea28b56c8..566e23eab 100644 --- a/src/info.h +++ b/src/info.h @@ -1112,6 +1112,8 @@ typedef enum sprite SPR_GRNG, // Guard ring SPR_GBDY, // Guard body + SPR_DHND, // Servant Hand + SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks SPR_BDRF, // Brake drift sparks @@ -4367,6 +4369,8 @@ typedef enum state S_BLOCKRING, S_BLOCKBODY, + S_SERVANTHAND, + // Signpost sparkles S_SIGNSPARK1, S_SIGNSPARK2, @@ -6448,6 +6452,8 @@ typedef enum mobj_type MT_BLOCKRING, MT_BLOCKBODY, + MT_SERVANTHAND, + MT_SIGNSPARKLE, MT_FASTLINE, diff --git a/src/k_hud.c b/src/k_hud.c index f958ff6c9..c0098b016 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5005,7 +5005,7 @@ static void K_DrawWaypointDebugger(void) } V_DrawString(8, 156, 0, va("Current Waypoint ID: %d", K_GetWaypointID(stplyr->currentwaypoint))); - V_DrawString(8, 166, 0, va("Next Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); + V_DrawString(8, 166, 0, va("Next Waypoint ID: %d%s", K_GetWaypointID(stplyr->nextwaypoint), ((stplyr->pflags & PF_WRONGWAY) ? " (WRONG WAY)" : ""))); V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); if (numstarposts > 0) @@ -5365,11 +5365,6 @@ void K_drawKartHUD(void) // Draw FREE PLAY. K_drawKartFreePlay(); - if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) - { - V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); - } - if ((netgame || cv_mindelay.value) && r_splitscreen && Playing()) { K_drawMiniPing(); diff --git a/src/k_kart.c b/src/k_kart.c index 33fcd8819..8ff8e25c3 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8275,9 +8275,14 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->tripwireState = TRIPSTATE_NONE; } + if (player->hand && P_MobjWasRemoved(player->hand)) + P_SetTarget(&player->hand, NULL); + if (player->spectator == false) { K_KartEbrakeVisuals(player); + + K_KartServantHandVisuals(player); } if (K_GetKartButtons(player) & BT_BRAKE && @@ -8596,6 +8601,7 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) { angle_t nextbestdelta = ANGLE_90; angle_t nextbestmomdelta = ANGLE_90; + angle_t nextbestanydelta = ANGLE_MAX; size_t i = 0U; if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) @@ -8637,8 +8643,14 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) momdelta = InvAngle(momdelta); } - if (angledelta < nextbestdelta || momdelta < nextbestmomdelta) + if (angledelta < nextbestanydelta || momdelta < nextbestanydelta) { + nextbestanydelta = min(angledelta, momdelta); + player->besthanddirection = angletowaypoint; + + if (nextbestanydelta >= ANGLE_90) + continue; + // Wanted to use a next waypoint, so remove WRONG WAY flag. // Done here instead of when set, because of finish line // hacks meaning we might not actually use this one, but @@ -10158,6 +10170,102 @@ void K_KartEbrakeVisuals(player_t *p) } } +void K_KartServantHandVisuals(player_t *player) +{ + if (player->pflags & PF_WRONGWAY) + { + if (player->handtimer < TICRATE) + { + player->handtimer++; + if (player->hand == NULL && player->handtimer == TICRATE) + { + mobj_t *hand = P_SpawnMobj( + player->mo->x, + player->mo->y, + player->mo->z + player->mo->height + 30*mapobjectscale, + MT_SERVANTHAND + ); + + if (hand) + { + K_FlipFromObject(hand, player->mo); + hand->old_z = hand->z; + + P_SetTarget(&hand->target, player->mo); + P_SetTarget(&player->hand, hand); + + hand->fuse = 8; + } + } + } + + if (player->hand) + { + player->hand->destscale = mapobjectscale; + } + } + else if (player->handtimer != 0) + { + player->handtimer--; + } + + if (player->hand) + { + const fixed_t handpokespeed = 4; + const fixed_t looping = handpokespeed - abs((player->hand->threshold % (handpokespeed*2)) - handpokespeed); + fixed_t xoffs = 0, yoffs = 0; + + player->hand->color = player->skincolor; + player->hand->angle = player->besthanddirection; + + if (player->hand->fuse != 0) + { + ; + } + else if (looping != 0) + { + xoffs = FixedMul(2 * looping * mapobjectscale, FINECOSINE(player->hand->angle >> ANGLETOFINESHIFT)), + yoffs = FixedMul(2 * looping * mapobjectscale, FINESINE(player->hand->angle >> ANGLETOFINESHIFT)), + + player->hand->threshold++; + } + else if (player->handtimer == 0) + { + player->hand->fuse = 8; + } + else + { + player->hand->threshold++; + } + + if (player->hand->fuse != 0) + { + if ((player->hand->fuse > 4) ^ (player->handtimer < TICRATE/2)) + { + player->hand->spritexscale = FRACUNIT/3; + player->hand->spriteyscale = 3*FRACUNIT; + } + else + { + player->hand->spritexscale = 2*FRACUNIT; + player->hand->spriteyscale = FRACUNIT/2; + } + } + + P_MoveOrigin(player->hand, + player->mo->x + xoffs, + player->mo->y + yoffs, + player->mo->z + player->mo->height + 30*mapobjectscale + ); + K_FlipFromObject(player->hand, player->mo); + + player->hand->sprzoff = player->mo->sprzoff; + + player->hand->renderflags &= ~RF_DONTDRAW; + player->hand->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player)); + } +} + static void K_KartSpindashDust(mobj_t *parent) { fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); diff --git a/src/k_kart.h b/src/k_kart.h index af7d142d2..379d8c3ea 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -196,6 +196,7 @@ UINT8 K_GetInvincibilityItemFrame(void); UINT8 K_GetOrbinautItemFrame(UINT8 count); boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); +void K_KartServantHandVisuals(player_t *player); void K_HandleDirectionalInfluence(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player); diff --git a/src/p_mobj.c b/src/p_mobj.c index f0e529a0d..57880f350 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9776,6 +9776,22 @@ static boolean P_FuseThink(mobj_t *mobj) Obj_SPBExplode(mobj); break; } + case MT_SERVANTHAND: + { + if (!mobj->target + || P_MobjWasRemoved(mobj->target) + || !mobj->target->player + || mobj->target->player->handtimer == 0) + { + P_RemoveMobj(mobj); + return false; + } + + mobj->spritexscale = FRACUNIT; + mobj->spriteyscale = FRACUNIT; + + break; + } case MT_PLAYER: break; // don't remove default: diff --git a/src/p_saveg.c b/src/p_saveg.c index f022feedf..f9d019050 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -76,6 +76,7 @@ typedef enum SLIPTIDEZIP = 0x0080, RINGSHOOTER = 0x0100, WHIP = 0x0200, + HAND = 0x0400, } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -229,6 +230,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].whip) flags |= WHIP; + if (players[i].hand) + flags |= HAND; + if (players[i].ringShooter) flags |= RINGSHOOTER; @@ -258,6 +262,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (flags & WHIP) WRITEUINT32(save->p, players[i].whip->mobjnum); + if (flags & HAND) + WRITEUINT32(save->p, players[i].hand->mobjnum); + if (flags & RINGSHOOTER) WRITEUINT32(save->p, players[i].ringShooter->mobjnum); @@ -429,6 +436,10 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].instaShieldCooldown); WRITEUINT8(save->p, players[i].guardCooldown); + + WRITEUINT8(save->p, players[i].handtimer); + WRITEANGLE(save->p, players[i].besthanddirection); + WRITEINT16(save->p, players[i].incontrol); WRITEUINT8(save->p, players[i].markedfordeath); @@ -652,6 +663,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) if (flags & WHIP) players[i].whip = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & HAND) + players[i].hand = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & RINGSHOOTER) players[i].ringShooter = (mobj_t *)(size_t)READUINT32(save->p); @@ -824,6 +838,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].instaShieldCooldown = READUINT8(save->p); players[i].guardCooldown = READUINT8(save->p); + + players[i].handtimer = READUINT8(save->p); + players[i].besthanddirection = READANGLE(save->p); + players[i].incontrol = READINT16(save->p); players[i].markedfordeath = READUINT8(save->p); @@ -5136,6 +5154,13 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&players[i].whip, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "whip not found on player %d\n", i); } + if (players[i].hand) + { + temp = (UINT32)(size_t)players[i].hand; + players[i].hand = NULL; + if (!P_SetTarget(&players[i].hand, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "hand not found on player %d\n", i); + } if (players[i].ringShooter) { temp = (UINT32)(size_t)players[i].ringShooter; From fa10ff629f358165be8cc99a2ed1e8a8a1783393 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 15 Jun 2023 19:39:55 +0100 Subject: [PATCH 13/44] servant-hand.c --- src/k_kart.c | 98 +---------------------------------- src/k_kart.h | 1 - src/k_objects.h | 3 ++ src/objects/CMakeLists.txt | 1 + src/objects/servant-hand.c | 103 +++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 98 deletions(-) create mode 100644 src/objects/servant-hand.c diff --git a/src/k_kart.c b/src/k_kart.c index 8ff8e25c3..bb05f7adc 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8282,7 +8282,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { K_KartEbrakeVisuals(player); - K_KartServantHandVisuals(player); + Obj_ServantHandHandling(player); } if (K_GetKartButtons(player) & BT_BRAKE && @@ -10170,102 +10170,6 @@ void K_KartEbrakeVisuals(player_t *p) } } -void K_KartServantHandVisuals(player_t *player) -{ - if (player->pflags & PF_WRONGWAY) - { - if (player->handtimer < TICRATE) - { - player->handtimer++; - if (player->hand == NULL && player->handtimer == TICRATE) - { - mobj_t *hand = P_SpawnMobj( - player->mo->x, - player->mo->y, - player->mo->z + player->mo->height + 30*mapobjectscale, - MT_SERVANTHAND - ); - - if (hand) - { - K_FlipFromObject(hand, player->mo); - hand->old_z = hand->z; - - P_SetTarget(&hand->target, player->mo); - P_SetTarget(&player->hand, hand); - - hand->fuse = 8; - } - } - } - - if (player->hand) - { - player->hand->destscale = mapobjectscale; - } - } - else if (player->handtimer != 0) - { - player->handtimer--; - } - - if (player->hand) - { - const fixed_t handpokespeed = 4; - const fixed_t looping = handpokespeed - abs((player->hand->threshold % (handpokespeed*2)) - handpokespeed); - fixed_t xoffs = 0, yoffs = 0; - - player->hand->color = player->skincolor; - player->hand->angle = player->besthanddirection; - - if (player->hand->fuse != 0) - { - ; - } - else if (looping != 0) - { - xoffs = FixedMul(2 * looping * mapobjectscale, FINECOSINE(player->hand->angle >> ANGLETOFINESHIFT)), - yoffs = FixedMul(2 * looping * mapobjectscale, FINESINE(player->hand->angle >> ANGLETOFINESHIFT)), - - player->hand->threshold++; - } - else if (player->handtimer == 0) - { - player->hand->fuse = 8; - } - else - { - player->hand->threshold++; - } - - if (player->hand->fuse != 0) - { - if ((player->hand->fuse > 4) ^ (player->handtimer < TICRATE/2)) - { - player->hand->spritexscale = FRACUNIT/3; - player->hand->spriteyscale = 3*FRACUNIT; - } - else - { - player->hand->spritexscale = 2*FRACUNIT; - player->hand->spriteyscale = FRACUNIT/2; - } - } - - P_MoveOrigin(player->hand, - player->mo->x + xoffs, - player->mo->y + yoffs, - player->mo->z + player->mo->height + 30*mapobjectscale - ); - K_FlipFromObject(player->hand, player->mo); - - player->hand->sprzoff = player->mo->sprzoff; - - player->hand->renderflags &= ~RF_DONTDRAW; - player->hand->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player)); - } -} - static void K_KartSpindashDust(mobj_t *parent) { fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); diff --git a/src/k_kart.h b/src/k_kart.h index 379d8c3ea..af7d142d2 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -196,7 +196,6 @@ UINT8 K_GetInvincibilityItemFrame(void); UINT8 K_GetOrbinautItemFrame(UINT8 count); boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); -void K_KartServantHandVisuals(player_t *player); void K_HandleDirectionalInfluence(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player); diff --git a/src/k_objects.h b/src/k_objects.h index 5deb7749f..3ea63b8ce 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -137,6 +137,9 @@ void Obj_RandomItemSpawn(mobj_t *mobj); void Obj_GachaBomReboundThink(mobj_t *mobj); void Obj_SpawnGachaBomRebound(mobj_t *source, mobj_t *target); +/* Servant Hand */ +void Obj_ServantHandHandling(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 0c6fd74e9..6ec83ac0c 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -20,4 +20,5 @@ target_sources(SRB2SDL2 PRIVATE instawhip.c block.c gachabom-rebound.cpp + servant-hand.c ) diff --git a/src/objects/servant-hand.c b/src/objects/servant-hand.c new file mode 100644 index 000000000..3a94ab69a --- /dev/null +++ b/src/objects/servant-hand.c @@ -0,0 +1,103 @@ +#include "../doomdef.h" +#include "../p_local.h" +#include "../p_mobj.h" +#include "../d_player.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../v_video.h" + +void Obj_ServantHandHandling(player_t *player) +{ + if (player->pflags & PF_WRONGWAY) + { + if (player->handtimer < TICRATE) + { + player->handtimer++; + if (player->hand == NULL && player->handtimer == TICRATE) + { + mobj_t *hand = P_SpawnMobj( + player->mo->x, + player->mo->y, + player->mo->z + player->mo->height + 30*mapobjectscale, + MT_SERVANTHAND + ); + + if (hand) + { + K_FlipFromObject(hand, player->mo); + hand->old_z = hand->z; + + P_SetTarget(&hand->target, player->mo); + P_SetTarget(&player->hand, hand); + + hand->fuse = 8; + } + } + } + + if (player->hand) + { + player->hand->destscale = mapobjectscale; + } + } + else if (player->handtimer != 0) + { + player->handtimer--; + } + + if (player->hand) + { + const fixed_t handpokespeed = 4; + const fixed_t looping = handpokespeed - abs((player->hand->threshold % (handpokespeed*2)) - handpokespeed); + fixed_t xoffs = 0, yoffs = 0; + + player->hand->color = player->skincolor; + player->hand->angle = player->besthanddirection; + + if (player->hand->fuse != 0) + { + ; + } + else if (looping != 0) + { + xoffs = FixedMul(2 * looping * mapobjectscale, FINECOSINE(player->hand->angle >> ANGLETOFINESHIFT)), + yoffs = FixedMul(2 * looping * mapobjectscale, FINESINE(player->hand->angle >> ANGLETOFINESHIFT)), + + player->hand->threshold++; + } + else if (player->handtimer == 0) + { + player->hand->fuse = 8; + } + else + { + player->hand->threshold++; + } + + if (player->hand->fuse != 0) + { + if ((player->hand->fuse > 4) ^ (player->handtimer < TICRATE/2)) + { + player->hand->spritexscale = FRACUNIT/3; + player->hand->spriteyscale = 3*FRACUNIT; + } + else + { + player->hand->spritexscale = 2*FRACUNIT; + player->hand->spriteyscale = FRACUNIT/2; + } + } + + P_MoveOrigin(player->hand, + player->mo->x + xoffs, + player->mo->y + yoffs, + player->mo->z + player->mo->height + 30*mapobjectscale + ); + K_FlipFromObject(player->hand, player->mo); + + player->hand->sprzoff = player->mo->sprzoff; + + player->hand->renderflags &= ~RF_DONTDRAW; + player->hand->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player)); + } +} From f72bd7ff328cd7ee1db4716ddbdd9bf6bc365cc4 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 15 Jun 2023 21:10:06 +0100 Subject: [PATCH 14/44] Make name of the item "Eggmark" Cvar was previously "eggmanmonitor" inherited from a completely different implementation of the item Menu was previously "Eggman Mark", which was good inspiration but too unwieldy Has been blended together per Gardentop and VC discussion --- src/d_netcmd.c | 2 +- src/menus/options-gameplay-item-toggles.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 712c01a3f..f391e83c1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -373,7 +373,7 @@ consvar_t cv_items[NUMKARTRESULTS-1] = { CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL), CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL), CVAR_INIT ("banana", "On", CV_NETVAR, CV_OnOff, NULL), - CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("eggmark", "On", CV_NETVAR, CV_OnOff, NULL), CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, NULL), CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL), CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL), diff --git a/src/menus/options-gameplay-item-toggles.c b/src/menus/options-gameplay-item-toggles.c index fd4bc5818..a87d62577 100644 --- a/src/menus/options-gameplay-item-toggles.c +++ b/src/menus/options-gameplay-item-toggles.c @@ -19,7 +19,7 @@ menuitem_t OPTIONS_GameplayItems[] = {IT_KEYHANDLER | IT_NOTHING, NULL, "Banana", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Banana x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Mark", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggmark", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Gachabom", NULL, {.routine = M_HandleItemToggles}, KITEM_GACHABOM, 0}, {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinaut", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0}, From f20a7e4db2d6287815bda99d6cb91f233d329292 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 18 Jun 2023 00:46:32 -0700 Subject: [PATCH 15/44] A_PlaySound: var2 lower 16 bits == 2, play from actor target --- src/p_enemy.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/p_enemy.c b/src/p_enemy.c index 17aa6ea71..c21afb34d 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -7221,13 +7221,14 @@ void A_ChangeRollAngleAbsolute(mobj_t *actor) // // var1 = sound # to play // var2: -// lower 16 bits = If 1, play sound using calling object as origin. If 0, play sound without an origin +// lower 16 bits = If 1, play sound using calling object as origin. If 2, use target. If 0, play sound without an origin // upper 16 bits = If 1, do not play sound during preticker. // void A_PlaySound(mobj_t *actor) { INT32 locvar1 = var1; INT32 locvar2 = var2; + mobj_t *origin = NULL; if (LUA_CallAction(A_PLAYSOUND, actor)) return; @@ -7235,7 +7236,18 @@ void A_PlaySound(mobj_t *actor) if (leveltime < 2 && (locvar2 >> 16)) return; - S_StartSound((locvar2 & 65535) ? actor : NULL, locvar1); + switch (locvar2 & 65535) + { + case 1: + origin = actor; + break; + + case 2: + origin = actor->target; + break; + } + + S_StartSound(origin, locvar1); } // Function: A_FindTarget From f98319a98cc6812bb14cca6ca40d4df09f715cf8 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 18 Jun 2023 00:11:01 -0700 Subject: [PATCH 16/44] Add R_SplatSlope, function to set slope of splat during rendering Slope set by R_SplatSlope is only effective if floorspriteslope or standingslope is not already being used. --- src/r_splats.c | 4 ++++ src/r_spritefx.cpp | 15 +++++++++++++++ src/r_things.h | 1 + 3 files changed, 20 insertions(+) diff --git a/src/r_splats.c b/src/r_splats.c index bfd0197ca..c2aae1a93 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -141,6 +141,7 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 void R_DrawFloorSplat(vissprite_t *spr) { floorsplat_t splat; + pslope_t localslope; mobj_t *mobj = spr->mobj; fixed_t tr_x, tr_y, rot_x, rot_y, rot_z; @@ -258,6 +259,9 @@ void R_DrawFloorSplat(vissprite_t *spr) if (standingslope && (renderflags & RF_OBJECTSLOPESPLAT)) splat.slope = standingslope; + + if (!splat.slope && R_SplatSlope(mobj, (vector3_t){splat.x, splat.y, splat.z}, &localslope)) + splat.slope = &localslope; } // Translate diff --git a/src/r_spritefx.cpp b/src/r_spritefx.cpp index 448c65974..098cc14e8 100644 --- a/src/r_spritefx.cpp +++ b/src/r_spritefx.cpp @@ -10,6 +10,7 @@ #include "d_player.h" #include "p_tick.h" +#include "r_splats.h" #include "r_things.h" INT32 R_ThingLightLevel(mobj_t* thing) @@ -29,3 +30,17 @@ INT32 R_ThingLightLevel(mobj_t* thing) return lightlevel; } + +// Use this function to set the slope of a splat sprite. +// +// slope->o, slope->d and slope->zdelta must be set, none of +// the other fields on pslope_t are used. +// +// Return true if you want the slope to be used. The object +// must have RF_SLOPESPLAT and mobj_t.floorspriteslope must be +// NULL. (If RF_OBJECTSLOPESPLAT is set, then +// mobj_t.standingslope must also be NULL.) +boolean R_SplatSlope(mobj_t* mobj, vector3_t position, pslope_t* slope) +{ + return false; +} diff --git a/src/r_things.h b/src/r_things.h index 78b6c013e..2737ded62 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -94,6 +94,7 @@ boolean R_ThingIsFullDark (mobj_t *thing); boolean R_ThingIsFlashing(mobj_t *thing); INT32 R_ThingLightLevel(mobj_t *thing); +boolean R_SplatSlope(mobj_t *thing, vector3_t position, pslope_t *slope); // -------------- // MASKED DRAWING From 96a3221dd3d9a127aeb7dfc6764dcdfde7f2bd73 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 17 Jun 2023 23:41:00 -0700 Subject: [PATCH 17/44] Add Instawhip Recharge states --- src/deh_tables.c | 5 +++++ src/info.c | 32 ++++++++++++++++++++++++++++++++ src/info.h | 6 ++++++ 3 files changed, 43 insertions(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index e44c6cb52..ddbae5cbf 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3288,6 +3288,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_SLIPTIDEZIP", "S_INSTAWHIP", + "S_INSTAWHIP_RECHARGE1", + "S_INSTAWHIP_RECHARGE2", + "S_INSTAWHIP_RECHARGE3", + "S_INSTAWHIP_RECHARGE4", "S_BLOCKRING", "S_BLOCKBODY", @@ -5336,6 +5340,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SLIPTIDEZIP", "MT_INSTAWHIP", + "MT_INSTAWHIP_RECHARGE", "MT_BLOCKRING", "MT_BLOCKBODY", diff --git a/src/info.c b/src/info.c index cbe5556a3..7d1c0023c 100644 --- a/src/info.c +++ b/src/info.c @@ -556,6 +556,7 @@ char sprnames[NUMSPRITES + 1][5] = "SLPT", // Sliptide zip indicator "IWHP", // Instawhip + "WPRE", // Instawhip Recharge "GRNG", // Guard ring "GBDY", // Guard body @@ -3955,6 +3956,10 @@ state_t states[NUMSTATES] = {SPR_SLPT, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_SLIPTIDEZIP {SPR_IWHP, FF_FLOORSPRITE|FF_ANIMATE|0, -1, {NULL}, 6, 2, S_NULL}, // S_INSTAWHIP + {SPR_NULL, 0, 1, {NULL}, 0, 0, S_INSTAWHIP_RECHARGE2}, // S_INSTAWHIP_RECHARGE1 + {SPR_NULL, 0, 0, {A_PlaySound}, sfx_s3ka0, 2, S_INSTAWHIP_RECHARGE3}, // S_INSTAWHIP_RECHARGE2 + {SPR_WPRE, FF_FULLBRIGHT|FF_FLOORSPRITE|FF_ANIMATE|0, 36, {NULL}, 17, 2, S_INSTAWHIP_RECHARGE4}, // S_INSTAWHIP_RECHARGE3 + {SPR_NULL, 0, 0, {A_PlaySound}, sfx_s3k7c, 2, S_NULL}, // S_INSTAWHIP_RECHARGE4 {SPR_GRNG, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_BLOCKRING {SPR_GBDY, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_BLOCKBODY @@ -22711,6 +22716,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_INSTAWHIP_RECHARGE + -1, // doomednum + S_INSTAWHIP_RECHARGE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 90*FRACUNIT, // radius + 90*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING|MF_DONTENCOREMAP|MF_NOSQUISH, // flags + S_INSTAWHIP_RECHARGE3 // raisestate + }, + { // MT_BLOCKRING -1, // doomednum S_BLOCKRING, // spawnstate diff --git a/src/info.h b/src/info.h index 566e23eab..a203dbc3d 100644 --- a/src/info.h +++ b/src/info.h @@ -1109,6 +1109,7 @@ typedef enum sprite SPR_SLPT, // Sliptide zip indicator SPR_IWHP, // Instawhip + SPR_WPRE, // Instawhip Recharge SPR_GRNG, // Guard ring SPR_GBDY, // Guard body @@ -4366,6 +4367,10 @@ typedef enum state S_SLIPTIDEZIP, S_INSTAWHIP, + S_INSTAWHIP_RECHARGE1, + S_INSTAWHIP_RECHARGE2, + S_INSTAWHIP_RECHARGE3, + S_INSTAWHIP_RECHARGE4, S_BLOCKRING, S_BLOCKBODY, @@ -6449,6 +6454,7 @@ typedef enum mobj_type MT_SLIPTIDEZIP, MT_INSTAWHIP, + MT_INSTAWHIP_RECHARGE, MT_BLOCKRING, MT_BLOCKBODY, From aa82d8da77d39ce6a912d9c108fbce9ef9967062 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 18 Jun 2023 00:32:33 -0700 Subject: [PATCH 18/44] Instawhip Recharge VFX - 3 splats spawn before the instawhip cooldown runs out - Splats angle steeply outward in a triangle formation - VFX is animated, animation runs out right when instawhip cooldown completely runs out --- src/k_kart.c | 5 +++++ src/k_objects.h | 3 +++ src/objects/instawhip.c | 32 ++++++++++++++++++++++++++++++++ src/p_mobj.c | 8 ++++++++ src/r_spritefx.cpp | 15 +++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 6e0974105..1a6ba0d06 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10780,6 +10780,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) whip->fuse = 12; // Changing instawhip animation duration? Look here player->flashing = max(player->flashing, 12); player->mo->momz += 4*mapobjectscale; + + // Spawn in triangle formation + Obj_SpawnInstaWhipRecharge(player, 0); + Obj_SpawnInstaWhipRecharge(player, ANGLE_120); + Obj_SpawnInstaWhipRecharge(player, ANGLE_240); } } diff --git a/src/k_objects.h b/src/k_objects.h index 3ea63b8ce..a20798dac 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -2,6 +2,7 @@ #ifndef k_objects_H #define k_objects_H +#include "tables.h" // angle_t #include "taglist.h" #ifdef __cplusplus @@ -109,6 +110,8 @@ boolean Obj_DropTargetMorphThink(mobj_t *morph); /* Instawhip */ void Obj_InstaWhipThink(mobj_t *whip); +void Obj_SpawnInstaWhipRecharge(player_t *player, angle_t angleOffset); +void Obj_InstaWhipRechargeThink(mobj_t *mobj); /* Block VFX */ void Obj_BlockRingThink(mobj_t *ring); diff --git a/src/objects/instawhip.c b/src/objects/instawhip.c index d2b38044f..64ddd8ae5 100644 --- a/src/objects/instawhip.c +++ b/src/objects/instawhip.c @@ -3,6 +3,9 @@ #include "../k_objects.h" #include "../p_local.h" +#define recharge_target(o) ((o)->target) +#define recharge_offset(o) ((o)->movedir) + void Obj_InstaWhipThink (mobj_t *whip) { if (P_MobjWasRemoved(whip->target)) @@ -40,3 +43,32 @@ void Obj_InstaWhipThink (mobj_t *whip) whip->renderflags |= RF_DONTDRAW; } } + +void Obj_SpawnInstaWhipRecharge(player_t *player, angle_t angleOffset) +{ + mobj_t *x = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_INSTAWHIP_RECHARGE); + + x->tics = max(player->instaShieldCooldown - states[x->info->raisestate].tics, 0); + x->renderflags |= RF_SLOPESPLAT | RF_NOSPLATBILLBOARD; + + P_SetTarget(&recharge_target(x), player->mo); + recharge_offset(x) = angleOffset; +} + +void Obj_InstaWhipRechargeThink(mobj_t *x) +{ + mobj_t *target = recharge_target(x); + + if (P_MobjWasRemoved(target)) + { + P_RemoveMobj(x); + return; + } + + P_MoveOrigin(x, target->x, target->y, target->z + (target->height / 2)); + P_InstaScale(x, 2 * target->scale); + x->angle = target->angle + recharge_offset(x); + + // Flickers every other frame + x->renderflags ^= RF_DONTDRAW; +} diff --git a/src/p_mobj.c b/src/p_mobj.c index f28575cf7..a4aa1d128 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6681,6 +6681,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } break; + case MT_INSTAWHIP_RECHARGE: + Obj_InstaWhipRechargeThink(mobj); + + if (P_MobjWasRemoved(mobj)) + { + return; + } + break; case MT_VWREF: case MT_VWREB: { diff --git a/src/r_spritefx.cpp b/src/r_spritefx.cpp index 098cc14e8..4db957880 100644 --- a/src/r_spritefx.cpp +++ b/src/r_spritefx.cpp @@ -9,6 +9,7 @@ /// \brief Special effects for sprite rendering #include "d_player.h" +#include "info.h" #include "p_tick.h" #include "r_splats.h" #include "r_things.h" @@ -42,5 +43,19 @@ INT32 R_ThingLightLevel(mobj_t* thing) // mobj_t.standingslope must also be NULL.) boolean R_SplatSlope(mobj_t* mobj, vector3_t position, pslope_t* slope) { + switch (mobj->type) + { + case MT_INSTAWHIP_RECHARGE: { + // Create an acute angle + slope->o = position; + FV2_Load(&slope->d, FCOS(mobj->angle) / 2, FSIN(mobj->angle) / 2); + slope->zdelta = FRACUNIT; + return true; + } + + default: + break; + } + return false; } From bebf9ebed7d61628c88eb77772b8d3f5965941fa Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 18 Jun 2023 01:34:30 -0700 Subject: [PATCH 19/44] A_PlaySound: var2 lower 16 bits == 2, play from actor if actor target is NULL --- src/p_enemy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_enemy.c b/src/p_enemy.c index c21afb34d..9f6f5c490 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -7243,7 +7243,7 @@ void A_PlaySound(mobj_t *actor) break; case 2: - origin = actor->target; + origin = actor->target ? actor->target : actor; break; } From 164f72a6cbae1bb627ab26882b503bed046ef438 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 20 Jun 2023 12:41:42 +0100 Subject: [PATCH 20/44] "Bored" follower audience toggle (for Chaclon) - If follower audience member has a certain flag, the bob/jump height is forced to zero - UDMF: args[2] & 2 - Binary: The "Extra" flag - If a follower audience member (or MT_EMBLEM with GE_FOLLOWER) has a bob/jump height of 0, use the idlestate instead of the movement state --- src/objects/audience.c | 35 +++++++++++++++++++++-------------- src/p_mobj.c | 7 ++++++- src/p_setup.c | 6 +++++- src/p_spec.h | 6 ++++++ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/objects/audience.c b/src/objects/audience.c index ca9098b2b..6cb2d8dfe 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -101,25 +101,32 @@ Obj_AudienceInit mobj->destscale = FixedMul(3*mobj->destscale, followers[followerpick].scale); P_SetScale(mobj, mobj->destscale); - audience_mainstate(mobj) = followers[followerpick].followstate; + if (mobj->flags2 & MF2_BOSSNOTRAP) + { + audience_bobamp(mobj) = 0; + } + else + { + // The following is derived from the default bobamp + if (mobj->type != MT_EMBLEM && !(mobj->flags & MF_NOGRAVITY) && followers[followerpick].bobamp < 4*FRACUNIT) + { + audience_bobamp(mobj) = 4*mobj->scale; + } + else + { + audience_bobamp(mobj) = FixedMul(mobj->scale, followers[followerpick].bobamp); + } + } + + audience_mainstate(mobj) = + audience_bobamp(mobj) != 0 + ? followers[followerpick].followstate + : followers[followerpick].idlestate; P_SetMobjState(mobj, audience_mainstate(mobj)); if (P_MobjWasRemoved(mobj)) return; - // The following is derived from the default bobamp - if (mobj->type != MT_EMBLEM && !(mobj->flags & MF_NOGRAVITY) && followers[followerpick].bobamp < 4*FRACUNIT) - { - audience_bobamp(mobj) = 4*mobj->scale; - } - else - { - audience_bobamp(mobj) = FixedMul(mobj->scale, followers[followerpick].bobamp); - } - - audience_bobspeed(mobj) = followers[followerpick].bobspeed; - audience_focusplayer(mobj) = MAXPLAYERS; - if (P_RandomChance(PR_RANDOMAUDIENCE, FRACUNIT/2)) { audience_animoffset(mobj) = 5; diff --git a/src/p_mobj.c b/src/p_mobj.c index a4aa1d128..054efcbb1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13192,11 +13192,16 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) } case MT_RANDOMAUDIENCE: { - if (mthing->args[2] != 0) + if (mthing->args[2] & TMAUDIM_FLOAT) { mobj->flags |= MF_NOGRAVITY; } + if (mthing->args[2] & TMAUDIM_BORED) + { + mobj->flags2 |= MF2_BOSSNOTRAP; + } + if (mthing->args[3] != 0) { mobj->flags2 |= MF2_AMBUSH; diff --git a/src/p_setup.c b/src/p_setup.c index 4cf537526..c9ca10345 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6959,7 +6959,11 @@ static void P_ConvertBinaryThingTypes(void) mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH); break; case 1488: // Follower Audience (unfortunately numbered) - mapthings[i].args[2] = !!(mapthings[i].options & MTF_OBJECTSPECIAL); + if (mapthings[i].options & MTF_OBJECTSPECIAL) + mapthings[i].args[2] |= TMAUDIM_FLOAT; + if (mapthings[i].options & MTF_EXTRA) + mapthings[i].args[2] |= TMAUDIM_BORED; + mapthings[i].args[3] = !!(mapthings[i].options & MTF_AMBUSH); break; case 1500: //Glaregoyle diff --git a/src/p_spec.h b/src/p_spec.h index 9714d3fc8..af0b8e7fe 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -135,6 +135,12 @@ typedef enum TMWPF_FINISHLINE = 1<<3, } textmapwaypointflags_t; +typedef enum +{ + TMAUDIM_FLOAT = 1, + TMAUDIM_BORED = 1<<1, +} textmapaudiencemovementflags_t; + typedef enum { TMBCF_BACKANDFORTH = 1, From a9361020bf4b9c2cc7b0394e7c354431f224802f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 20 Jun 2023 21:52:50 +0100 Subject: [PATCH 21/44] Re-add missing bobspeed/focusplayer initialisation --- src/objects/audience.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/objects/audience.c b/src/objects/audience.c index 6cb2d8dfe..a43dbcc94 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -118,6 +118,9 @@ Obj_AudienceInit } } + audience_bobspeed(mobj) = followers[followerpick].bobspeed; + audience_focusplayer(mobj) = MAXPLAYERS; + audience_mainstate(mobj) = audience_bobamp(mobj) != 0 ? followers[followerpick].followstate From 3cbbcfd2531e231554ff241f1c23358ffd37f649 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 00:51:04 +0100 Subject: [PATCH 22/44] M_StringHeight: Guarantee that there will be enough vertical space in a Menu Message for the relevant lines --- src/menus/transient/message-box.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index f677ba992..f463bfcef 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -14,11 +14,14 @@ struct menumessage_s menumessage; // static inline size_t M_StringHeight(const char *string) { - size_t h = 8, i; + size_t h = 8, i, len = strlen(string); - for (i = 0; i < strlen(string); i++) - if (string[i] == '\n') - h += 8; + for (i = 0; i < len; i++) + { + if (string[i] != '\n' && i != len-1) + continue; + h += 8; + } return h; } From e372d348cd5d3f891eb4136f81e757c6c7e1a617 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 01:11:34 +0100 Subject: [PATCH 23/44] Live Event Backup Associated menu stuff is rough and unpolished, but it's 1am for the author of this commit. - Activated for this session by either of the following Passwords. - `savetheframes` - `savetheanimals` - The idea is that if Ring Racers were ever ran at a GDQ, runners would swear their alliegance to a particular Metroid routing at the startup stage, because I think it's funny and cute. - A live event backup is created/overwritten on non-cheated grandprix level change. - It's wiped on reaching the podium. - When hitting PLAY on the main menu, if a live event backup exists, make a Menu Message. - If you hit A, turn live event backups on for this session, and load the backup. - If you hit B/X, delete the backup and proceed to character select. - Done this way to avoid cheating a different character to the end of GP. - Unlike the (maybe a little over-)engineering of ringdata.dat, liveringbak.bkp is streamlined as all hells and has very little recovery for differing file lists. --- src/d_main.c | 2 +- src/doomdef.h | 2 +- src/doomstat.h | 4 +- src/g_game.c | 235 +++++-------------------------- src/g_game.h | 9 +- src/k_grandprix.c | 38 +++++ src/k_grandprix.h | 10 ++ src/k_rank.h | 1 + src/m_cheat.c | 32 +++++ src/menus/play-char-select.c | 89 ++++++++++++ src/p_saveg.c | 259 +++++++++++++++++++++++++++-------- src/p_saveg.h | 26 +++- src/p_setup.c | 34 ++--- 13 files changed, 441 insertions(+), 300 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index a4036cbb5..610acbd11 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -982,7 +982,7 @@ void D_ClearState(void) cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); - lastmaploaded = 0; + lastqueuesaved = 0; // In case someone exits out at the same time they start a time attack run, // reset modeattacking diff --git a/src/doomdef.h b/src/doomdef.h index 0e09a6aa6..b653f926a 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -517,7 +517,7 @@ void CONS_Debug(UINT32 debugflags, const char *fmt, ...) FUNCDEBUG; #include "m_swap.h" // Things that used to be in dstrings.h -#define SAVEGAMENAME "srb2sav" +#define SAVEGAMENAME "ringsav" extern char savegamename[256]; extern char liveeventbackup[256]; diff --git a/src/doomstat.h b/src/doomstat.h index 53071a319..827eec3fb 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -51,8 +51,8 @@ extern UINT8 mapmusrng; extern UINT32 maptol; extern INT32 cursaveslot; -//extern INT16 lastmapsaved; -extern INT16 lastmaploaded; +extern UINT8 lastqueuesaved; +extern boolean makelivebackup; extern UINT8 gamecomplete; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/g_game.c b/src/g_game.c index 8d8610741..9633a2e23 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -123,8 +123,8 @@ precipprops_t precipprops[MAXPRECIP] = preciptype_t precip_freeslot = PRECIP_FIRSTFREESLOT; INT32 cursaveslot = 0; // Auto-save 1p savegame slot -//INT16 lastmapsaved = 0; // Last map we auto-saved at -INT16 lastmaploaded = 0; // Last map the game loaded +UINT8 lastqueuesaved = 0; +boolean makelivebackup = false; UINT8 gamecomplete = 0; marathonmode_t marathonmode = 0; @@ -4018,39 +4018,26 @@ void G_UpdateVisited(void) G_SaveGameData(); } -static boolean CanSaveLevel(INT32 mapnum) +void G_HandleSaveLevel(void) { - // SRB2Kart: No save files yet - (void)mapnum; - return false; -} + if (!grandprixinfo.gp || !grandprixinfo.cup + || splitscreen || netgame + || !(makelivebackup || cursaveslot > 0)) + return; -static void G_HandleSaveLevel(void) -{ - // do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c - if (nextmap >= NEXTMAP_SPECIAL) + if (gamestate == GS_CEREMONY && makelivebackup) { - if (!gamecomplete) - gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission - if (cursaveslot > 0) - { - if (marathonmode) - { - // don't keep a backup around when the run is done! - if (FIL_FileExists(liveeventbackup)) - remove(liveeventbackup); - cursaveslot = 0; - } - else if (!usedCheats && !(netgame || multiplayer || ultimatemode || demo.recording || metalrecording || modeattacking)) - G_SaveGame((UINT32)cursaveslot, 0); // TODO when we readd a campaign one day - } - } - // and doing THIS here means you don't lose your progress if you close the game mid-intermission - else if (!(ultimatemode || demo.playback || demo.recording || metalrecording || modeattacking) - && cursaveslot > 0 && CanSaveLevel(lastmap+1)) - { - G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages + if (FIL_FileExists(liveeventbackup)) + remove(liveeventbackup); + return; } + + if (gamestate != GS_LEVEL + || roundqueue.size == 0 + || lastqueuesaved == roundqueue.position) + return; + + G_SaveGame(); } // Next map apparatus @@ -4645,9 +4632,6 @@ static void G_DoContinued(void) // Reset score pl->score = 0; - if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0) - G_SaveGameOver((UINT32)cursaveslot, true); - // Reset # of lives pl->lives = 3; @@ -5673,7 +5657,7 @@ void G_SaveGameData(void) // G_InitFromSavegame // Can be called by the startup code or the menu task. // -void G_LoadGame(UINT32 slot, INT16 mapoverride) +void G_LoadGame(void) { char vcheck[VERSIONSIZE]; char savename[255]; @@ -5682,15 +5666,10 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); -#ifdef SAVEGAME_OTHERVERSIONS - //Oh christ. The force load response needs access to mapoverride too... - startonmapnum = mapoverride; -#endif - - if (marathonmode) + if (makelivebackup) strcpy(savename, liveeventbackup); else - sprintf(savename, savegamename, slot); + sprintf(savename, savegamename, cursaveslot); if (P_SaveBufferFromFile(&save, savename) == false) { @@ -5699,22 +5678,11 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); + sprintf(vcheck, (makelivebackup ? "back-up %d" : "version %d"), VERSION); if (strcmp((const char *)save.p, (const char *)vcheck)) { -#ifdef SAVEGAME_OTHERVERSIONS - M_StartMessage("Savegame Load", M_GetText("Save game from different version.\nYou can load this savegame, but\nsaving afterwards will be disabled.\n\nDo you want to continue anyway?\n"), - M_ForceLoadGameResponse, MM_YESNO, NULL, NULL); - //Freeing done by the callback function of the above message -#else - M_ClearMenus(true); // so ESC backs out to title - M_StartMessage("Savegame Load", M_GetText("Save game from different version\n\n"), NULL, MM_NOTHING, NULL, "Return to Menu"); - Command_ExitGame_f(); + M_StartMessage("Savegame Load", va(M_GetText("Save game %s from different version"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); P_SaveBufferFree(&save); - - // no cheating! - memset(&savedata, 0, sizeof(savedata)); -#endif return; // bad version } save.p += VERSIONSIZE; @@ -5726,39 +5694,18 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // automapactive = false; // dearchive all the modifications - if (!P_LoadGame(&save, mapoverride)) + if (!P_LoadGame(&save)) { - M_ClearMenus(true); // so ESC backs out to title - M_StartMessage("Savegame Load", M_GetText("Savegame file corrupted\n"), NULL, MM_NOTHING, NULL, "Return to Menu"); - Command_ExitGame_f(); + M_StartMessage("Savegame Load", va(M_GetText("Savegame %s corrupted\n"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); Z_Free(save.buffer); save.p = save.buffer = NULL; - // no cheating! - memset(&savedata, 0, sizeof(savedata)); return; } - if (marathonmode) - { - marathontime = READUINT32(save.p); - marathonmode |= READUINT8(save.p); - } // done P_SaveBufferFree(&save); -// gameaction = ga_nothing; -// G_SetGamestate(GS_LEVEL); - displayplayers[0] = consoleplayer; - multiplayer = false; - splitscreen = 0; - SplitScreen_OnChange(); // not needed? - -// G_DeferedInitNew(sk_medium, G_BuildMapName(1), 0, 0, 1); - if (setsizeneeded) - R_ExecuteSetViewSize(); - - M_ClearMenus(true); CON_ToggleOff(); } @@ -5766,18 +5713,16 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // G_SaveGame // Saves your game. // -void G_SaveGame(UINT32 slot, INT16 mapnum) +void G_SaveGame(void) { boolean saved; char savename[256] = ""; - const char *backup; savebuffer_t save = {0}; - if (marathonmode) + if (makelivebackup) strcpy(savename, liveeventbackup); else - sprintf(savename, savegamename, slot); - backup = va("%s",savename); + sprintf(savename, savegamename, cursaveslot); gameaction = ga_nothing; { @@ -5791,138 +5736,24 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) } memset(name, 0, sizeof (name)); - sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); + sprintf(name, (makelivebackup ? "back-up %d" : "version %d"), VERSION); WRITEMEM(save.p, name, VERSIONSIZE); - P_SaveGame(&save, mapnum); - if (marathonmode) - { - UINT32 writetime = marathontime; - if (!(marathonmode & MA_INGAME)) - writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map - WRITEUINT32(save.p, writetime); - WRITEUINT8(save.p, (marathonmode & ~MA_INIT)); - } + P_SaveGame(&save); length = save.p - save.buffer; - saved = FIL_WriteFile(backup, save.buffer, length); + saved = FIL_WriteFile(savename, save.buffer, length); P_SaveBufferFree(&save); } gameaction = ga_nothing; if (cht_debug && saved) - CONS_Printf(M_GetText("Game saved.\n")); + CONS_Printf(M_GetText("%s saved.\n"), savename); else if (!saved) - CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); + CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s\n"), savename); } -#define BADSAVE goto cleanup; -#define CHECKPOS if (save.p >= save.end) BADSAVE -void G_SaveGameOver(UINT32 slot, boolean modifylives) -{ - boolean saved = false; - size_t length; - char vcheck[VERSIONSIZE]; - char savename[255]; - const char *backup; - savebuffer_t save = {0}; - - if (marathonmode) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, slot); - backup = va("%s",savename); - - if (P_SaveBufferFromFile(&save, savename) == false) - { - CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); - return; - } - - length = save.size; - - { - char temp[sizeof(timeattackfolder)]; - UINT8 *lives_p; - SINT8 pllives; - - // Version check - memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); - if (strcmp((const char *)save.p, (const char *)vcheck)) BADSAVE - save.p += VERSIONSIZE; - - // P_UnArchiveMisc() - (void)READINT16(save.p); - CHECKPOS - (void)READUINT16(save.p); // emeralds - CHECKPOS - READSTRINGN(save.p, temp, sizeof(temp)); // mod it belongs to - if (strcmp(temp, timeattackfolder)) BADSAVE - - // P_UnArchivePlayer() - CHECKPOS - (void)READUINT16(save.p); - CHECKPOS - - WRITEUINT8(save.p, numgameovers); - CHECKPOS - - lives_p = save.p; - pllives = READSINT8(save.p); // lives - CHECKPOS - if (modifylives && pllives < startinglivesbalance[numgameovers]) - { - pllives = startinglivesbalance[numgameovers]; - WRITESINT8(lives_p, pllives); - } - - (void)READINT32(save.p); // Score - CHECKPOS - (void)READINT32(save.p); // continues - - // File end marker check - CHECKPOS - switch (READUINT8(save.p)) - { - case 0xb7: - { - UINT8 i, banksinuse; - CHECKPOS - banksinuse = READUINT8(save.p); - CHECKPOS - if (banksinuse > NUM_LUABANKS) - BADSAVE - for (i = 0; i < banksinuse; i++) - { - (void)READINT32(save.p); - CHECKPOS - } - if (READUINT8(save.p) != 0x1d) - BADSAVE - } - case 0x1d: - break; - default: - BADSAVE - } - - // done - saved = FIL_WriteFile(backup, save.buffer, length); - } - -cleanup: - if (cht_debug && saved) - CONS_Printf(M_GetText("Game saved.\n")); - else if (!saved) - CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); - - P_SaveBufferFree(&save); -} -#undef CHECKPOS -#undef BADSAVE - // // G_DeferedInitNew // Can be called by the startup code or the menu task, diff --git a/src/g_game.h b/src/g_game.h index 65eb78aa3..949bcd3c7 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -187,16 +187,13 @@ void G_StartTitleCard(void); void G_PreLevelTitleCard(void); 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_HandleSaveLevel(void); +void G_SaveGame(void); +void G_LoadGame(void); void G_SaveGameData(void); void G_DirtyGameData(void); -void G_SaveGame(UINT32 slot, INT16 mapnum); - -void G_SaveGameOver(UINT32 slot, boolean modifylives); - void G_SetGametype(INT16 gametype); char *G_PrepareGametypeConstant(const char *newgtconst); void G_AddTOL(UINT32 newtol, const char *tolname); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 47b4c22ab..dfbb65669 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -274,6 +274,44 @@ void K_InitGrandPrixBots(void) } } +/*-------------------------------------------------- + void K_LoadGrandPrixSaveGame(void) + + See header file for description. +---------------------------------------------------*/ + +void K_LoadGrandPrixSaveGame(void) +{ + if (splitscreen) + { + // You're not doing splitscreen runs at GDQ. + // We are literally 14 days from code freeze + // and I am not accomodating weird setup this + // second in my last minute QoL feature. + // I will *actually* fight you + return; + } + + players[consoleplayer].lives = savedata.lives; + players[consoleplayer].score = savedata.score; + players[consoleplayer].totalring = savedata.totalring; + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (savedata.bots[i].valid == false) + continue; + + K_SetBot(i, savedata.bots[i].skin, savedata.bots[i].difficulty, BOT_STYLE_NORMAL); + + players[i].botvars.rival = savedata.bots[i].rival; + players[i].score = savedata.bots[i].score; + + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); + } +} + /*-------------------------------------------------- static INT16 K_RivalScore(player_t *bot) diff --git a/src/k_grandprix.h b/src/k_grandprix.h index ba19bdb61..34315ba48 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -28,6 +28,7 @@ typedef enum GPEVENT_SPECIAL, } gpEvent_e; +// Please also see P_ArchiveMisc extern struct grandprixinfo { boolean gp; ///< If true, then we are in a Grand Prix. @@ -100,6 +101,15 @@ UINT8 K_GetGPPlayerCount(UINT8 humans); void K_InitGrandPrixBots(void); +/*-------------------------------------------------- + void K_LoadGrandPrixSaveGame(void) + + Handles loading savedata_t info for Grand Prix context. +---------------------------------------------------*/ + +void K_LoadGrandPrixSaveGame(void); + + /*-------------------------------------------------- void K_UpdateGrandPrixBots(void); diff --git a/src/k_rank.h b/src/k_rank.h index 701c01b7c..6d4887218 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -20,6 +20,7 @@ extern "C" { #endif +// Please also see P_ArchiveMisc struct gpRank_t { UINT8 players; diff --git a/src/m_cheat.c b/src/m_cheat.c index a8ac759c0..e4bac635a 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -116,6 +116,26 @@ static UINT8 cheatf_wrongwarp(void) return 1; } +static UINT8 cheatf_backup(void) +{ + /*if (modifiedgame) + return 0;*/ + + makelivebackup = true; + + M_ClearMenus(true); + S_StartSound(0, sfx_kc42); + + M_StartMessage("Live Event Mode", + M_GetText( + "Your progression in GP cups will be.\n" + "backed up whenever you complete a\n" + "round, in case of game crashes.\n" + ), NULL, MM_NOTHING, NULL, NULL); + + return 1; +} + #ifdef DEVELOP static UINT8 cheatf_devmode(void) { @@ -152,6 +172,16 @@ static cheatseq_t cheat_wrongwarp = { (UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff } }; +static cheatseq_t cheat_backup1 = { + NULL, cheatf_backup, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('f'), SCRAMBLE('r'), SCRAMBLE('a'), SCRAMBLE('m'), SCRAMBLE('e'), SCRAMBLE('s'), 0xff } +}; + +static cheatseq_t cheat_backup2 = { + NULL, cheatf_backup, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('l'), SCRAMBLE('s'), 0xff } +}; + #ifdef DEVELOP static cheatseq_t cheat_devmode = { NULL, cheatf_devmode, @@ -163,6 +193,8 @@ cheatseq_t *cheatseqlist[] = { &cheat_warp, &cheat_wrongwarp, + &cheat_backup1, + &cheat_backup2, #ifdef DEVELOP &cheat_devmode, #endif diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 9e7d4ca60..ac208e8c7 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -6,6 +6,8 @@ #include "../s_sound.h" #include "../k_grandprix.h" // K_CanChangeRules #include "../m_cond.h" // Condition Sets +#include "../r_local.h" // SplitScreen_OnChange +#include "../m_misc.h" // FIL_FileExists //#define CHARSELECT_DEVICEDEBUG @@ -484,9 +486,96 @@ void M_CharacterSelectInit(void) setup_page = 0; } + +static void M_MarathonLiveEventBackup(INT32 choice) +{ + if (choice == MA_YES) + { + makelivebackup = true; + G_LoadGame(); + + if (savedata.lives != 0) + { + // Only do this after confirming savegame is ok + const UINT8 ssplayers = 0; + + { + CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor); + + // follower + if (savedata.followerskin < 0 || savedata.followerskin > numfollowers) + CV_StealthSet(&cv_follower[0], "None"); + else + CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name); + + // finally, call the skin[x] console command. + // This will call SendNameAndColor which will synch everything we sent here and apply the changes! + + CV_StealthSet(&cv_skin[0], skins[savedata.skin].name); + + // ...actually, let's do this last - Skin_OnChange has some return-early occasions + // follower color + CV_SetValue(&cv_followercolor[0], savedata.followercolor); + } + + paused = false; + + S_StopMusicCredit(); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + const UINT8 entry = lastqueuesaved-1; + + SV_StartSinglePlayerServer(roundqueue.entries[entry].gametype, false); + + D_MapChange( + roundqueue.entries[entry].mapnum + 1, + roundqueue.entries[entry].gametype, + roundqueue.entries[entry].encore, + true, + 1, + false, + roundqueue.entries[lastqueuesaved-1].rankrestricted + ); + + M_ClearMenus(true); + } + } + else if (choice == MA_NO) + { + if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking. + remove(liveeventbackup); + + M_CharacterSelect(0); + } +} + void M_CharacterSelect(INT32 choice) { (void)choice; + + if (currentMenu == &MainDef + && FIL_FileExists(liveeventbackup)) + { + M_StartMessage( + "Live Event Backup", + "A Live Event Backup was found.\n" + "Do you want to resurrect the last run?\n" + "(Fs in chat if we crashed on stream.)\n", + M_MarathonLiveEventBackup, + MM_YESNO, + "Resume the last run", + "Delete, play another way"); + return; + } + PLAY_CharSelectDef.music = currentMenu->music; PLAY_CharSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_CharSelectDef, false); diff --git a/src/p_saveg.c b/src/p_saveg.c index 1e443a962..728ae1f16 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -38,6 +38,7 @@ #include "m_cond.h" // netUnlocked // SRB2Kart +#include "k_grandprix.h" #include "k_battle.h" #include "k_pwrlv.h" #include "k_terrain.h" @@ -82,25 +83,66 @@ typedef enum static inline void P_ArchivePlayer(savebuffer_t *save) { const player_t *player = &players[consoleplayer]; - INT16 skininfo = player->skin; - SINT8 pllives = player->lives; - if (pllives < startinglivesbalance[numgameovers]) // Bump up to 3 lives if the player - pllives = startinglivesbalance[numgameovers]; // has less than that. - WRITEUINT16(save->p, skininfo); - WRITEUINT8(save->p, numgameovers); - WRITESINT8(save->p, pllives); + WRITESINT8(save->p, player->lives); WRITEUINT32(save->p, player->score); + WRITEUINT16(save->p, player->totalring); + + WRITEUINT8(save->p, player->skin); + WRITEUINT16(save->p, player->skincolor); + WRITEINT32(save->p, player->followerskin); + WRITEUINT16(save->p, player->followercolor); + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false) + continue; + if (players[i].bot == false) + continue; + + WRITEUINT8(save->p, i); + + WRITEUINT8(save->p, players[i].skin); + + WRITEUINT8(save->p, players[i].botvars.difficulty); + WRITEUINT8(save->p, (UINT8)players[i].botvars.rival); + + WRITEUINT32(save->p, players[i].score); + } + + WRITEUINT8(save->p, 0xFE); } -static inline void P_UnArchivePlayer(savebuffer_t *save) +static boolean P_UnArchivePlayer(savebuffer_t *save) { - INT16 skininfo = READUINT16(save->p); - savedata.skin = skininfo; - - savedata.numgameovers = READUINT8(save->p); savedata.lives = READSINT8(save->p); savedata.score = READUINT32(save->p); + savedata.totalring = READUINT16(save->p); + + savedata.skin = READUINT8(save->p); + savedata.skincolor = READUINT16(save->p); + savedata.followerskin = READINT32(save->p); + savedata.followercolor = READUINT16(save->p); + + if (savedata.skin >= numskins) + return false; + + memset(&savedata.bots, 0, sizeof(savedata.bots)); + + UINT8 pid; + + while ((pid = READUINT8(save->p)) < MAXPLAYERS) + { + savedata.bots[pid].valid = true; + savedata.bots[pid].skin = READUINT8(save->p); + savedata.bots[pid].difficulty = READUINT8(save->p); + savedata.bots[pid].rival = (boolean)READUINT8(save->p); + savedata.bots[pid].score = READUINT32(save->p); + } + + return (pid == 0xFE); } static void P_NetArchivePlayers(savebuffer_t *save) @@ -1039,6 +1081,9 @@ static void P_NetUnArchiveRoundQueue(savebuffer_t *save) roundqueue.position = READUINT8(save->p); roundqueue.size = READUINT8(save->p); + if (roundqueue.size > ROUNDQUEUE_MAX) + I_Error("Bad $$$.sav at illegitimate roundqueue size"); + roundqueue.roundnum = READUINT8(save->p); for (i = 0; i < roundqueue.size; i++) @@ -5257,55 +5302,148 @@ static void P_NetUnArchiveSpecials(savebuffer_t *save) // ======================================================================= // Misc // ======================================================================= -static inline void P_ArchiveMisc(savebuffer_t *save, INT16 mapnum) +static inline void P_ArchiveMisc(savebuffer_t *save) { - //lastmapsaved = mapnum; - lastmaploaded = mapnum; + UINT8 i; - if (gamecomplete) - mapnum |= 8192; - - WRITEINT16(save->p, mapnum); - WRITEUINT16(save->p, emeralds+357); WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); + + WRITEUINT8(save->p, grandprixinfo.gamespeed); + WRITEUINT8(save->p, (UINT8)grandprixinfo.encore); + WRITEUINT8(save->p, (UINT8)grandprixinfo.masterbots); + + WRITEUINT16(save->p, grandprixinfo.cup->id); + + { + WRITEUINT8(save->p, grandprixinfo.rank.players); + WRITEUINT8(save->p, grandprixinfo.rank.totalPlayers); + + WRITEUINT8(save->p, grandprixinfo.rank.position); + WRITEUINT8(save->p, grandprixinfo.rank.skin); + + WRITEUINT32(save->p, grandprixinfo.rank.winPoints); + WRITEUINT32(save->p, grandprixinfo.rank.totalPoints); + + WRITEUINT32(save->p, grandprixinfo.rank.laps); + WRITEUINT32(save->p, grandprixinfo.rank.totalLaps); + + WRITEUINT32(save->p, grandprixinfo.rank.continuesUsed); + + WRITEUINT32(save->p, grandprixinfo.rank.prisons); + WRITEUINT32(save->p, grandprixinfo.rank.totalPrisons); + + WRITEUINT32(save->p, grandprixinfo.rank.rings); + WRITEUINT32(save->p, grandprixinfo.rank.totalRings); + + WRITEUINT8(save->p, (UINT8)grandprixinfo.rank.specialWon); + } + + WRITEUINT8(save->p, roundqueue.position); + WRITEUINT8(save->p, roundqueue.size); + WRITEUINT8(save->p, roundqueue.roundnum); + + for (i = 0; i < roundqueue.size; i++) + { + WRITEUINT16(save->p, roundqueue.entries[i].mapnum); + WRITEUINT8(save->p, roundqueue.entries[i].gametype); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].encore); + WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].rankrestricted); + } + + WRITEUINT8(save->p, (marathonmode & ~MA_INIT)); + + UINT32 writetime = marathontime; + if (!(marathonmode & MA_INGAME)) + writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map + WRITEUINT32(save->p, writetime); } -static inline void P_UnArchiveSPGame(savebuffer_t *save, INT16 mapoverride) +static boolean P_UnArchiveSPGame(savebuffer_t *save) { + UINT8 i; char testname[sizeof(timeattackfolder)]; - gamemap = READINT16(save->p); - - if (mapoverride != 0) - { - gamemap = mapoverride; - gamecomplete = 1; - } - else - gamecomplete = 0; - - // gamemap changed; we assume that its map header is always valid, - // so make it so - if (!gamemap || gamemap > nummapheaders || !mapheaderinfo[gamemap-1]) - I_Error("P_UnArchiveSPGame: Internal map ID %d not found (nummapheaders = %d)", gamemap-1, nummapheaders); - - //lastmapsaved = gamemap; - lastmaploaded = gamemap; - - savedata.emeralds = READUINT16(save->p)-357; - READSTRINGN(save->p, testname, sizeof(testname)); if (strcmp(testname, timeattackfolder)) { - if (modifiedgame) - I_Error("Save game not for this modification."); - else - I_Error("This save file is for a particular mod, it cannot be used with the regular game."); + return false; } - memset(playeringame, 0, sizeof(*playeringame)); - playeringame[consoleplayer] = true; + // TODO do not work off grandprixinfo/roundqueue directly + // This is only strictly necessary if we ever re-add a save + // select screen or something, for live event backup only + // it's *fine* and, more importantly, shippable + + memset(&grandprixinfo, 0, sizeof(grandprixinfo)); + + grandprixinfo.gp = true; + + grandprixinfo.gamespeed = READUINT8(save->p); + grandprixinfo.encore = (boolean)READUINT8(save->p); + grandprixinfo.masterbots = (boolean)READUINT8(save->p); + + UINT16 cupid = READUINT16(save->p); + grandprixinfo.cup = kartcupheaders; + while (grandprixinfo.cup) + { + if (grandprixinfo.cup->id == cupid) + break; + grandprixinfo.cup = grandprixinfo.cup->next; + } + + if (!grandprixinfo.cup) + return false; + + { + grandprixinfo.rank.players = READUINT8(save->p); + grandprixinfo.rank.totalPlayers = READUINT8(save->p); + + grandprixinfo.rank.position = READUINT8(save->p); + grandprixinfo.rank.skin = READUINT8(save->p); + + grandprixinfo.rank.winPoints = READUINT32(save->p); + grandprixinfo.rank.totalPoints = READUINT32(save->p); + + grandprixinfo.rank.laps = READUINT32(save->p); + grandprixinfo.rank.totalLaps = READUINT32(save->p); + + grandprixinfo.rank.continuesUsed = READUINT32(save->p); + + grandprixinfo.rank.prisons = READUINT32(save->p); + grandprixinfo.rank.totalPrisons = READUINT32(save->p); + + grandprixinfo.rank.rings = READUINT32(save->p); + grandprixinfo.rank.totalRings = READUINT32(save->p); + + grandprixinfo.rank.specialWon = (boolean)READUINT8(save->p); + } + + memset(&roundqueue, 0, sizeof(roundqueue)); + + roundqueue.position = READUINT8(save->p); + roundqueue.size = READUINT8(save->p); + if (roundqueue.size > ROUNDQUEUE_MAX + || roundqueue.position > roundqueue.size) + return false; + + roundqueue.roundnum = READUINT8(save->p); + + for (i = 0; i < roundqueue.size; i++) + { + roundqueue.entries[i].mapnum = READUINT16(save->p); + if (roundqueue.entries[i].mapnum >= nummapheaders) + return false; + + roundqueue.entries[i].gametype = READUINT8(save->p); + roundqueue.entries[i].encore = (boolean)READUINT8(save->p); + roundqueue.entries[i].rankrestricted = (boolean)READUINT8(save->p); + } + + marathonmode = READUINT8(save->p); + marathontime = READUINT32(save->p); + + return true; } static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) @@ -5718,9 +5856,9 @@ static inline void P_NetUnArchiveRNG(savebuffer_t *save) } } -void P_SaveGame(savebuffer_t *save, INT16 mapnum) +void P_SaveGame(savebuffer_t *save) { - P_ArchiveMisc(save, mapnum); + P_ArchiveMisc(save); P_ArchivePlayer(save); P_ArchiveLuabanksAndConsistency(save); } @@ -5775,7 +5913,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_ArchiveLuabanksAndConsistency(save); } -boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride) +boolean P_LoadGame(savebuffer_t *save) { if (gamestate == GS_INTERMISSION) Y_EndIntermission(); @@ -5783,17 +5921,26 @@ boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride) Y_EndVote(); G_SetGamestate(GS_NULL); // should be changed in P_UnArchiveMisc - P_UnArchiveSPGame(save, mapoverride); - P_UnArchivePlayer(save); + if (!P_UnArchiveSPGame(save)) + goto badloadgame; + if (!P_UnArchivePlayer(save)) + goto badloadgame; if (!P_UnArchiveLuabanksAndConsistency(save)) - return false; + goto badloadgame; - // Only do this after confirming savegame is ok - G_DeferedInitNew(false, gamemap, savedata.skin, 0, true); - COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this + lastqueuesaved = roundqueue.position; return true; + +badloadgame: + // these are the side effects of P_UnarchiveSPGame + savedata.lives = 0; + roundqueue.size = 0; + grandprixinfo.gp = false; + marathonmode = 0; + + return false; } boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) diff --git a/src/p_saveg.h b/src/p_saveg.h index 30eeaef66..b8e5bcf0d 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -31,20 +31,34 @@ extern "C" { // Persistent storage/archiving. // These are the load / save game routines. -void P_SaveGame(savebuffer_t *save, INT16 mapnum); +void P_SaveGame(savebuffer_t *save); void P_SaveNetGame(savebuffer_t *save, boolean resending); -boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride); +boolean P_LoadGame(savebuffer_t *save); boolean P_LoadNetGame(savebuffer_t *save, boolean reloading); mobj_t *P_FindNewPosition(UINT32 oldposition); +struct savedata_bot_s +{ + boolean valid; + UINT8 skin; + UINT8 difficulty; + boolean rival; + UINT32 score; +}; + struct savedata_t { + UINT32 score; + SINT8 lives; + UINT16 totalring; + UINT8 skin; - INT32 score; - INT32 lives; - UINT16 emeralds; - UINT8 numgameovers; + UINT16 skincolor; + INT32 followerskin; + UINT16 followercolor; + + struct savedata_bot_s bots[MAXPLAYERS]; }; extern savedata_t savedata; diff --git a/src/p_setup.c b/src/p_setup.c index c9ca10345..16e58a109 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7734,7 +7734,12 @@ static void P_InitGametype(void) if (grandprixinfo.gp == true) { - if (grandprixinfo.initalize == true) + if (savedata.lives > 0) + { + K_LoadGrandPrixSaveGame(); + savedata.lives = 0; + } + else if (grandprixinfo.initalize == true) { K_InitGrandPrixRank(&grandprixinfo.rank); K_InitGrandPrixBots(); @@ -8152,15 +8157,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) R_InitMobjInterpolators(); P_InitCachedActions(); - if (!fromnetsave && savedata.lives > 0) - { - numgameovers = savedata.numgameovers; - players[consoleplayer].lives = savedata.lives; - players[consoleplayer].score = savedata.score; - emeralds = savedata.emeralds; - savedata.lives = 0; - } - // internal game map maplumpname = mapheaderinfo[gamemap-1]->lumpname; lastloadedmaplumpnum = mapheaderinfo[gamemap-1]->lumpnum; @@ -8311,22 +8307,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_MapEnd(); // tm.thing is no longer needed from this point onwards - // Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap... - if (gamestate == GS_LEVEL) - { - if (!lastmaploaded) // Start a new game? - { - // I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020 - if (!(ultimatemode || netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking || marathonmode) - && !usedCheats && cursaveslot > 0) - { - G_SaveGame((UINT32)cursaveslot, gamemap); - } - // If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted. - } - lastmaploaded = gamemap; // HAS to be set after saving!! - } - if (!fromnetsave) { INT32 buf = gametic % BACKUPTICS; @@ -8382,6 +8362,8 @@ void P_PostLoadLevel(void) P_RunCachedActions(); + G_HandleSaveLevel(); + if (marathonmode & MA_INGAME) { marathonmode &= ~MA_INIT; From 304bd383deb41f5340144c5f86ae07260fd38eb8 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 12:38:09 +0100 Subject: [PATCH 24/44] Do not call G_HandleSaveLevel between maps --- src/g_game.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 9633a2e23..f7baab1ac 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4527,7 +4527,6 @@ void G_AfterIntermission(void) if (gamestate != GS_VOTING) { G_GetNextMap(); - G_HandleSaveLevel(); } if ((grandprixinfo.gp == true) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. From 12ff43687743a715e4bb19fdeb661807259f85db Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 12:53:38 +0100 Subject: [PATCH 25/44] Make a first-class feature instead of a passworded secret - Now called "Grand Prix Backup" - Filename is now `gpringsav.bkp` - Since available in standard contexts, do a little extra guarding against unsporting behaviour: - In non-DEVELOP builds, delete Grand Prix Backup when returning to the titlescreen/menus. - "undo your extra work and make it more generic" - Tyron --- src/d_main.c | 8 ++++---- src/d_netcmd.c | 5 +++++ src/deh_soc.c | 4 ++-- src/doomdef.h | 2 +- src/doomstat.h | 1 - src/g_game.c | 32 +++++++++++++++----------------- src/g_game.h | 2 +- src/m_cheat.c | 32 -------------------------------- src/menus/play-char-select.c | 18 ++++++++---------- src/p_setup.c | 2 +- 10 files changed, 37 insertions(+), 69 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 610acbd11..7889d41d7 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -140,7 +140,7 @@ UINT16 numskincolors; menucolor_t *menucolorhead, *menucolortail; char savegamename[256]; -char liveeventbackup[256]; +char gpbackup[256]; char srb2home[256] = "."; char srb2path[256] = "."; @@ -1327,7 +1327,7 @@ void D_SRB2Main(void) // default savegame strcpy(savegamename, SAVEGAMENAME"%u.ssg"); - strcpy(liveeventbackup, "live"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg + strcpy(gpbackup, "gp"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg // Init the joined IP table for quick rejoining of past games. M_InitJoinedIPArray(); @@ -1358,7 +1358,7 @@ void D_SRB2Main(void) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, srb2home, PATHSEP); - strcatbf(liveeventbackup, srb2home, PATHSEP); + strcatbf(gpbackup, srb2home, PATHSEP); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home); #else // DEFAULTDIR @@ -1370,7 +1370,7 @@ void D_SRB2Main(void) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, userhome, PATHSEP); - strcatbf(liveeventbackup, userhome, PATHSEP); + strcatbf(gpbackup, userhome, PATHSEP); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome); #endif // DEFAULTDIR diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f391e83c1..c24b3cb9e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6241,6 +6241,11 @@ void Command_ExitGame_f(void) { INT32 i; +#ifndef DEVELOP + // Wipes gp backup if appropriate + G_HandleSaveLevel(true); +#endif + LUA_HookBool(false, HOOK(GameQuit)); D_QuitNetGame(); diff --git a/src/deh_soc.c b/src/deh_soc.c index d0ca37e45..eb30c33ed 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2997,8 +2997,8 @@ void readmaincfg(MYFILE *f, boolean mainfile) // can't use sprintf since there is %u in savegamename strcatbf(savegamename, srb2home, PATHSEP); - strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder)); - strcatbf(liveeventbackup, srb2home, PATHSEP); + strcpy(gpbackup, va("gp%s.bkp", timeattackfolder)); + strcatbf(gpbackup, srb2home, PATHSEP); refreshdirmenu |= REFRESHDIR_GAMEDATA; gamedataadded = true; diff --git a/src/doomdef.h b/src/doomdef.h index b653f926a..8eac43761 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -519,7 +519,7 @@ void CONS_Debug(UINT32 debugflags, const char *fmt, ...) FUNCDEBUG; // Things that used to be in dstrings.h #define SAVEGAMENAME "ringsav" extern char savegamename[256]; -extern char liveeventbackup[256]; +extern char gpbackup[256]; // m_misc.h #ifdef GETTEXT diff --git a/src/doomstat.h b/src/doomstat.h index 827eec3fb..e5788f824 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -52,7 +52,6 @@ extern UINT32 maptol; extern INT32 cursaveslot; extern UINT8 lastqueuesaved; -extern boolean makelivebackup; extern UINT8 gamecomplete; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/g_game.c b/src/g_game.c index f7baab1ac..f6df3b86d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -124,7 +124,6 @@ preciptype_t precip_freeslot = PRECIP_FIRSTFREESLOT; INT32 cursaveslot = 0; // Auto-save 1p savegame slot UINT8 lastqueuesaved = 0; -boolean makelivebackup = false; UINT8 gamecomplete = 0; marathonmode_t marathonmode = 0; @@ -4018,17 +4017,16 @@ void G_UpdateVisited(void) G_SaveGameData(); } -void G_HandleSaveLevel(void) +void G_HandleSaveLevel(boolean removecondition) { if (!grandprixinfo.gp || !grandprixinfo.cup - || splitscreen || netgame - || !(makelivebackup || cursaveslot > 0)) + || splitscreen || netgame) return; - if (gamestate == GS_CEREMONY && makelivebackup) + if (removecondition) { - if (FIL_FileExists(liveeventbackup)) - remove(liveeventbackup); + if (FIL_FileExists(gpbackup)) + remove(gpbackup); return; } @@ -5665,10 +5663,10 @@ void G_LoadGame(void) // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); - if (makelivebackup) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, cursaveslot); + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); if (P_SaveBufferFromFile(&save, savename) == false) { @@ -5677,7 +5675,7 @@ void G_LoadGame(void) } memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, (makelivebackup ? "back-up %d" : "version %d"), VERSION); + sprintf(vcheck, "version %d", VERSION); if (strcmp((const char *)save.p, (const char *)vcheck)) { M_StartMessage("Savegame Load", va(M_GetText("Save game %s from different version"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); @@ -5718,10 +5716,10 @@ void G_SaveGame(void) char savename[256] = ""; savebuffer_t save = {0}; - if (makelivebackup) - strcpy(savename, liveeventbackup); - else - sprintf(savename, savegamename, cursaveslot); + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); gameaction = ga_nothing; { @@ -5735,7 +5733,7 @@ void G_SaveGame(void) } memset(name, 0, sizeof (name)); - sprintf(name, (makelivebackup ? "back-up %d" : "version %d"), VERSION); + sprintf(name, "version %d", VERSION); WRITEMEM(save.p, name, VERSIONSIZE); P_SaveGame(&save); diff --git a/src/g_game.h b/src/g_game.h index 949bcd3c7..55876e5df 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -187,7 +187,7 @@ void G_StartTitleCard(void); void G_PreLevelTitleCard(void); boolean G_IsTitleCardAvailable(void); -void G_HandleSaveLevel(void); +void G_HandleSaveLevel(boolean removecondition); void G_SaveGame(void); void G_LoadGame(void); diff --git a/src/m_cheat.c b/src/m_cheat.c index e4bac635a..a8ac759c0 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -116,26 +116,6 @@ static UINT8 cheatf_wrongwarp(void) return 1; } -static UINT8 cheatf_backup(void) -{ - /*if (modifiedgame) - return 0;*/ - - makelivebackup = true; - - M_ClearMenus(true); - S_StartSound(0, sfx_kc42); - - M_StartMessage("Live Event Mode", - M_GetText( - "Your progression in GP cups will be.\n" - "backed up whenever you complete a\n" - "round, in case of game crashes.\n" - ), NULL, MM_NOTHING, NULL, NULL); - - return 1; -} - #ifdef DEVELOP static UINT8 cheatf_devmode(void) { @@ -172,16 +152,6 @@ static cheatseq_t cheat_wrongwarp = { (UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff } }; -static cheatseq_t cheat_backup1 = { - NULL, cheatf_backup, - (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('f'), SCRAMBLE('r'), SCRAMBLE('a'), SCRAMBLE('m'), SCRAMBLE('e'), SCRAMBLE('s'), 0xff } -}; - -static cheatseq_t cheat_backup2 = { - NULL, cheatf_backup, - (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('l'), SCRAMBLE('s'), 0xff } -}; - #ifdef DEVELOP static cheatseq_t cheat_devmode = { NULL, cheatf_devmode, @@ -193,8 +163,6 @@ cheatseq_t *cheatseqlist[] = { &cheat_warp, &cheat_wrongwarp, - &cheat_backup1, - &cheat_backup2, #ifdef DEVELOP &cheat_devmode, #endif diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index ac208e8c7..44d1287e5 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -487,11 +487,10 @@ void M_CharacterSelectInit(void) } -static void M_MarathonLiveEventBackup(INT32 choice) +static void M_GPBackup(INT32 choice) { if (choice == MA_YES) { - makelivebackup = true; G_LoadGame(); if (savedata.lives != 0) @@ -550,8 +549,8 @@ static void M_MarathonLiveEventBackup(INT32 choice) } else if (choice == MA_NO) { - if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking. - remove(liveeventbackup); + if (FIL_FileExists(gpbackup)) // just in case someone deleted it while we weren't looking. + remove(gpbackup); M_CharacterSelect(0); } @@ -562,14 +561,13 @@ void M_CharacterSelect(INT32 choice) (void)choice; if (currentMenu == &MainDef - && FIL_FileExists(liveeventbackup)) + && FIL_FileExists(gpbackup)) { M_StartMessage( - "Live Event Backup", - "A Live Event Backup was found.\n" - "Do you want to resurrect the last run?\n" - "(Fs in chat if we crashed on stream.)\n", - M_MarathonLiveEventBackup, + "Grand Prix Backup", + "A Grand Prix Backup was found.\n" + "Do you want to resurrect the last session?\n", + M_GPBackup, MM_YESNO, "Resume the last run", "Delete, play another way"); diff --git a/src/p_setup.c b/src/p_setup.c index 16e58a109..ee07ccf14 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8362,7 +8362,7 @@ void P_PostLoadLevel(void) P_RunCachedActions(); - G_HandleSaveLevel(); + G_HandleSaveLevel(gamestate == GS_CEREMONY); if (marathonmode & MA_INGAME) { From 3b272ddb97ed543a582fbee8e658890f221effa3 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 12:56:55 +0100 Subject: [PATCH 26/44] M_StringHeight: Handle correct height more cleanly --- src/menus/transient/message-box.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index f463bfcef..ec34a68a1 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -14,11 +14,11 @@ struct menumessage_s menumessage; // static inline size_t M_StringHeight(const char *string) { - size_t h = 8, i, len = strlen(string); + size_t h = 16, i, len = strlen(string); - for (i = 0; i < len; i++) + for (i = 0; i < len-1; i++) { - if (string[i] != '\n' && i != len-1) + if (string[i] != '\n') continue; h += 8; } From e9f9cfd051056e661f0e003d9b7ae8dc1dc8bf24 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 13:30:03 +0100 Subject: [PATCH 27/44] Menu Messages: Call the routine after it closes, not the moment you press the button Slicker, prevents the issue where a transition after pressing accept on a prompt would be instant. Also guarantees M_StopMessage is called even for MM_NOTHING, even with a custom routine. --- src/k_menu.h | 1 + src/menus/extras-addons.c | 3 ++- src/menus/play-local-race-time-attack.c | 1 - src/menus/transient/message-box.c | 31 +++++++++---------------- src/menus/transient/pause-game.c | 4 +++- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index bca035b59..4b10d0eac 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -533,6 +533,7 @@ extern struct menumessage_s void (*routine)(INT32 choice); // Normal routine //void (*eroutine)(event_t *ev); // Event routine (MM_EVENTHANDLER) + INT32 answer; const char *defaultstr; const char *confirmstr; diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index dfa21b876..8610992d3 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -101,11 +101,12 @@ static boolean prevmajormods = false; static void M_AddonsClearName(INT32 choice) { + (void)choice; + if (!majormods || prevmajormods) { CLEARNAME; } - M_StopMessage(choice); } // Handles messages for addon errors. diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 454080573..ed5990587 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -414,7 +414,6 @@ static void M_WriteGuestReplay(INT32 ch) if (FIL_FileExists(rguest)) { - //M_StopMessage(0); remove(rguest); } diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index ec34a68a1..5f88dd832 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -71,6 +71,7 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 menumessage.header = header; menumessage.flags = itemtype; menumessage.routine = routine; + menumessage.answer = MA_NONE; menumessage.fadetimer = 1; menumessage.timer = 0; menumessage.closing = false; @@ -82,7 +83,6 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 if (!routine) { menumessage.flags = MM_NOTHING; - menumessage.routine = M_StopMessage; } if (menumessage.flags == MM_YESNO && !defaultstr) @@ -156,6 +156,11 @@ boolean M_MenuMessageTick(void) if (menumessage.fadetimer == 0) { menumessage.active = false; + + if (menumessage.routine) + { + menumessage.routine(menumessage.answer); + } } return false; @@ -183,38 +188,24 @@ void M_HandleMenuMessage(void) switch (menumessage.flags) { - // Send 1 to the routine if we're pressing A/B/X - case MM_NOTHING: - { - if (btok || btnok) - menumessage.routine(0); - - break; - } // Send 1 to the routine if we're pressing A, 2 if B/X, 0 otherwise. case MM_YESNO: { - INT32 answer = MA_NONE; if (btok) - answer = MA_YES; + menumessage.answer = MA_YES; else if (btnok) - answer = MA_NO; - - // send 1 if btok is pressed, 2 if nok is pressed, 0 otherwise. - if (answer) - { - menumessage.routine(answer); - M_StopMessage(0); - } + menumessage.answer = MA_NO; break; } - // MM_EVENTHANDLER: In M_Responder to allow full event compat. default: break; } // if we detect any keypress, don't forget to set the menu delay regardless. if (btok || btnok) + { + M_StopMessage(0); M_SetMenuDelay(pid); + } } diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 3f638aebd..c3b640ea8 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -358,6 +358,8 @@ void M_ConfirmEnterGame(INT32 choice) static void M_ExitGameResponse(INT32 ch) { + const UINT8 pid = 0; + if (ch != MA_YES) return; @@ -368,7 +370,7 @@ static void M_ExitGameResponse(INT32 ch) else { G_SetExitGameFlag(); - M_ClearMenus(true); + M_SetMenuDelay(pid); // prevent another input } } From 2eef39e079dde5ec72063475eb5d59444fb4c54f Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 19:56:46 +0100 Subject: [PATCH 28/44] Fix valid followerskin range on GP Backup load --- src/menus/play-char-select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 44d1287e5..cb19a9351 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -502,7 +502,7 @@ static void M_GPBackup(INT32 choice) CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor); // follower - if (savedata.followerskin < 0 || savedata.followerskin > numfollowers) + if (savedata.followerskin < 0 || savedata.followerskin >= numfollowers) CV_StealthSet(&cv_follower[0], "None"); else CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name); From 0d827ab05e5babe42231d3d8204488c7c9bf1fd0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 20:05:15 +0100 Subject: [PATCH 29/44] Improve GP Backup handling to be more flexible Since it's a user-facing feature and not a speedrunner's assistant... - If an issue is discovered, make a CONS_Alert instead of a complete failure and let the user know - Replaces the unhelpful error of "Savegame %s corrupted" - Store skins and cups as strings, not IDs - Permits playing with differing mod loadouts if everything important is still loaded - Won't error for bots having not-loaded skins - those are converted into Eggrobos. - Instead of storing full roundqueue data, rebuild from the cup and check if the important details match - Prevents having to include map name strings - Add minor version to GP backups - Permits more flexibility in future --- src/g_game.c | 21 ++++++-- src/p_saveg.c | 131 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index f6df3b86d..422c124a1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5654,10 +5654,14 @@ void G_SaveGameData(void) // G_InitFromSavegame // Can be called by the startup code or the menu task. // + +#define SAV_VERSIONMINOR 1 + void G_LoadGame(void) { - char vcheck[VERSIONSIZE]; + char vcheck[VERSIONSIZE+1]; char savename[255]; + UINT8 versionMinor; savebuffer_t save = {0}; // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots @@ -5674,11 +5678,15 @@ void G_LoadGame(void) return; } + versionMinor = READUINT8(save.p); + memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, "version %d", VERSION); - if (strcmp((const char *)save.p, (const char *)vcheck)) + + if (versionMinor != SAV_VERSIONMINOR + || memcmp(save.p, vcheck, VERSIONSIZE)) { - M_StartMessage("Savegame Load", va(M_GetText("Save game %s from different version"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); + M_StartMessage("Savegame Load", va(M_GetText("Savegame %s is from\na different version."), savename), NULL, MM_NOTHING, NULL, NULL); P_SaveBufferFree(&save); return; // bad version } @@ -5693,7 +5701,8 @@ void G_LoadGame(void) // dearchive all the modifications if (!P_LoadGame(&save)) { - M_StartMessage("Savegame Load", va(M_GetText("Savegame %s corrupted\n"), savename), NULL, MM_NOTHING, NULL, "Return to Menu"); + M_StartMessage("Savegame Load", va(M_GetText("Savegame %s could not be loaded.\n" + "Check the console log for more info.\n"), savename), NULL, MM_NOTHING, NULL, NULL); Z_Free(save.buffer); save.p = save.buffer = NULL; @@ -5723,7 +5732,7 @@ void G_SaveGame(void) gameaction = ga_nothing; { - char name[VERSIONSIZE]; + char name[VERSIONSIZE+1]; size_t length; if (P_SaveBufferAlloc(&save, SAVEGAMESIZE) == false) @@ -5732,6 +5741,8 @@ void G_SaveGame(void) return; } + WRITEUINT8(save.p, SAV_VERSIONMINOR); + memset(name, 0, sizeof (name)); sprintf(name, "version %d", VERSION); WRITEMEM(save.p, name, VERSIONSIZE); diff --git a/src/p_saveg.c b/src/p_saveg.c index 728ae1f16..054058d93 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -88,9 +88,18 @@ static inline void P_ArchivePlayer(savebuffer_t *save) WRITEUINT32(save->p, player->score); WRITEUINT16(save->p, player->totalring); - WRITEUINT8(save->p, player->skin); + INT32 skin = player->skin; + if (skin > numskins) + skin = 0; + + WRITESTRINGN(save->p, skins[skin].name, SKINNAMESIZE); + + if (player->followerskin < 0 || player->followerskin >= numfollowers) + WRITESTRINGN(save->p, "None", SKINNAMESIZE); + else + WRITESTRINGN(save->p, followers[player->followerskin].name, SKINNAMESIZE); + WRITEUINT16(save->p, player->skincolor); - WRITEINT32(save->p, player->followerskin); WRITEUINT16(save->p, player->followercolor); UINT8 i; @@ -104,7 +113,11 @@ static inline void P_ArchivePlayer(savebuffer_t *save) WRITEUINT8(save->p, i); - WRITEUINT8(save->p, players[i].skin); + skin = players[i].skin; + if (skin > numskins) + skin = 0; + + WRITESTRINGN(save->p, skins[skin].name, SKINNAMESIZE); WRITEUINT8(save->p, players[i].botvars.difficulty); WRITEUINT8(save->p, (UINT8)players[i].botvars.rival); @@ -121,22 +134,48 @@ static boolean P_UnArchivePlayer(savebuffer_t *save) savedata.score = READUINT32(save->p); savedata.totalring = READUINT16(save->p); - savedata.skin = READUINT8(save->p); - savedata.skincolor = READUINT16(save->p); - savedata.followerskin = READINT32(save->p); - savedata.followercolor = READUINT16(save->p); + char skinname[SKINNAMESIZE+1]; + INT32 skin; - if (savedata.skin >= numskins) + READSTRINGN(save->p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); + + if (skin == -1) + { + CONS_Alert(CONS_ERROR, "P_UnArchivePlayer: Character \"%s\" is not currently loaded.\n", skinname); return false; + } + + savedata.skin = skin; + + READSTRINGN(save->p, skinname, SKINNAMESIZE); + savedata.followerskin = K_FollowerAvailable(skinname); + + savedata.skincolor = READUINT16(save->p); + savedata.followercolor = READUINT16(save->p); memset(&savedata.bots, 0, sizeof(savedata.bots)); UINT8 pid; + const UINT8 defaultbotskin = R_BotDefaultSkin(); while ((pid = READUINT8(save->p)) < MAXPLAYERS) { savedata.bots[pid].valid = true; - savedata.bots[pid].skin = READUINT8(save->p); + + READSTRINGN(save->p, skinname, SKINNAMESIZE); + skin = R_SkinAvailable(skinname); + + if (skin == -1) + { + // It is not worth destroying an otherwise good savedata over extra added skins. + // Let's just say they didn't show up to the rematch, so some Eggrobos subbed in. + CONS_Alert(CONS_WARNING, "P_UnArchivePlayer: Bot's character \"%s\" was not loaded, replacing with default \"%s\".\n", skinname, skins[defaultbotskin].name); + skin = defaultbotskin; + } + + savedata.bots[pid].skin = skin; + savedata.bots[pid].difficulty = READUINT8(save->p); savedata.bots[pid].rival = (boolean)READUINT8(save->p); savedata.bots[pid].score = READUINT32(save->p); @@ -5304,16 +5343,12 @@ static void P_NetUnArchiveSpecials(savebuffer_t *save) // ======================================================================= static inline void P_ArchiveMisc(savebuffer_t *save) { - UINT8 i; - WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); WRITEUINT8(save->p, grandprixinfo.gamespeed); WRITEUINT8(save->p, (UINT8)grandprixinfo.encore); WRITEUINT8(save->p, (UINT8)grandprixinfo.masterbots); - WRITEUINT16(save->p, grandprixinfo.cup->id); - { WRITEUINT8(save->p, grandprixinfo.rank.players); WRITEUINT8(save->p, grandprixinfo.rank.totalPlayers); @@ -5338,18 +5373,12 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT8(save->p, (UINT8)grandprixinfo.rank.specialWon); } + WRITESTRINGL(save->p, grandprixinfo.cup->name, MAXCUPNAME); + WRITEUINT8(save->p, roundqueue.position); WRITEUINT8(save->p, roundqueue.size); WRITEUINT8(save->p, roundqueue.roundnum); - for (i = 0; i < roundqueue.size; i++) - { - WRITEUINT16(save->p, roundqueue.entries[i].mapnum); - WRITEUINT8(save->p, roundqueue.entries[i].gametype); - WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].encore); - WRITEUINT8(save->p, (UINT8)roundqueue.entries[i].rankrestricted); - } - WRITEUINT8(save->p, (marathonmode & ~MA_INIT)); UINT32 writetime = marathontime; @@ -5360,13 +5389,13 @@ static inline void P_ArchiveMisc(savebuffer_t *save) static boolean P_UnArchiveSPGame(savebuffer_t *save) { - UINT8 i; char testname[sizeof(timeattackfolder)]; READSTRINGN(save->p, testname, sizeof(testname)); if (strcmp(testname, timeattackfolder)) { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Corrupt mod ID.\n"); return false; } @@ -5383,18 +5412,6 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) grandprixinfo.encore = (boolean)READUINT8(save->p); grandprixinfo.masterbots = (boolean)READUINT8(save->p); - UINT16 cupid = READUINT16(save->p); - grandprixinfo.cup = kartcupheaders; - while (grandprixinfo.cup) - { - if (grandprixinfo.cup->id == cupid) - break; - grandprixinfo.cup = grandprixinfo.cup->next; - } - - if (!grandprixinfo.cup) - return false; - { grandprixinfo.rank.players = READUINT8(save->p); grandprixinfo.rank.totalPlayers = READUINT8(save->p); @@ -5419,25 +5436,47 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) grandprixinfo.rank.specialWon = (boolean)READUINT8(save->p); } + char cupname[MAXCUPNAME]; + + // Find the relevant cup. + READSTRINGL(save->p, cupname, sizeof(cupname)); + UINT32 hash = quickncasehash(cupname, MAXCUPNAME); + + for (grandprixinfo.cup = kartcupheaders; grandprixinfo.cup; grandprixinfo.cup = grandprixinfo.cup->next) + { + if (grandprixinfo.cup->namehash != hash) + continue; + + if (strcmp(grandprixinfo.cup->name, cupname)) + continue; + + break; + } + + if (!grandprixinfo.cup) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\" is not currently loaded.\n", cupname); + return false; + } + memset(&roundqueue, 0, sizeof(roundqueue)); - roundqueue.position = READUINT8(save->p); - roundqueue.size = READUINT8(save->p); - if (roundqueue.size > ROUNDQUEUE_MAX - || roundqueue.position > roundqueue.size) - return false; + G_GPCupIntoRoundQueue(grandprixinfo.cup, GT_RACE, grandprixinfo.encore); + roundqueue.position = READUINT8(save->p); + UINT8 size = READUINT8(save->p); roundqueue.roundnum = READUINT8(save->p); - for (i = 0; i < roundqueue.size; i++) + if (roundqueue.size != size) { - roundqueue.entries[i].mapnum = READUINT16(save->p); - if (roundqueue.entries[i].mapnum >= nummapheaders) - return false; + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches.\n", cupname); + return false; + } - roundqueue.entries[i].gametype = READUINT8(save->p); - roundqueue.entries[i].encore = (boolean)READUINT8(save->p); - roundqueue.entries[i].rankrestricted = (boolean)READUINT8(save->p); + if (roundqueue.position == 0 || roundqueue.position > size) + { + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Position in the round queue is invalid.\n"); + return false; } marathonmode = READUINT8(save->p); From 281002f2ee6a53a0dd60f60caed11a95d5880815 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 20:14:07 +0100 Subject: [PATCH 30/44] Improve wording for A/B options for GP Backup prompt --- src/menus/play-char-select.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index cb19a9351..346c7beca 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -569,8 +569,8 @@ void M_CharacterSelect(INT32 choice) "Do you want to resurrect the last session?\n", M_GPBackup, MM_YESNO, - "Resume the last run", - "Delete, play another way"); + "Yes, let's try again", + "No, play another way"); return; } From 45bff5f0d8a8cd04eb72ad49b14a5f360f5f8258 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 17 Jun 2023 23:54:26 +0100 Subject: [PATCH 31/44] Further polish - Set a restoreMenu for M_GPBackup - Improved newlines on associated message --- src/menus/play-char-select.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 346c7beca..733c37848 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -545,6 +545,9 @@ static void M_GPBackup(INT32 choice) ); M_ClearMenus(true); + + // We can't put it deeper in the menuflow due to lack of guaranteed setup + restoreMenu = &MainDef; } } else if (choice == MA_NO) @@ -565,8 +568,9 @@ void M_CharacterSelect(INT32 choice) { M_StartMessage( "Grand Prix Backup", - "A Grand Prix Backup was found.\n" - "Do you want to resurrect the last session?\n", + "A progress backup was found.\n" + "Do you want to resurrect your\n" + "last Grand Prix session?\n", M_GPBackup, MM_YESNO, "Yes, let's try again", From 17f23e897497fdfde7ca0368ec04d83f64a794cf Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 13:41:26 +0100 Subject: [PATCH 32/44] Tournament Mode cheat: Improve handling - Have special message if cheats are enabled (it previously falsely said you could still save) - Cleaner internal handling - Always take you back to the title screen --- src/m_cheat.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/m_cheat.c b/src/m_cheat.c index a8ac759c0..1f81c370f 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -80,31 +80,45 @@ static UINT8 cheatf_warp(void) } } + M_ClearMenus(true); + + const char *text; + if (success) { G_SetUsedCheats(); - M_ClearMenus(true); S_StartSound(0, sfx_kc42); - M_StartMessage("Tournament Mode", - M_GetText( + text = M_GetText( "All challenges temporarily unlocked.\n" "Saving is disabled - the game will\n" "return to normal on next launch.\n" - ), NULL, MM_NOTHING, NULL, NULL); + ); } else { S_StartSound(0, sfx_s3k7b); - M_StartMessage("Tournament Mode", - M_GetText( - "This is the correct password, but\n" - "you already have every challenge\n" - "unlocked, so saving is still allowed!\n" - ), NULL, MM_NOTHING, NULL, NULL); + if (usedCheats) + { + text = M_GetText( + "This is the correct password, but\n" + "you already have every challenge\n" + "unlocked, so nothing has changed.\n" + ); + } + else + { + text = M_GetText( + "This is the correct password, but\n" + "you already have every challenge\n" + "unlocked, so saving is still allowed!\n" + ); + } } + M_StartMessage("Tournament Mode", text, NULL, MM_NOTHING, NULL, NULL); + return 1; } From d450faeaaf816a7130296ef3af11546ba9ec465e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 14:01:58 +0100 Subject: [PATCH 33/44] General Menu Message input cleanup - M_StopMessage is now in charge of setting the answer to a given prompt. - Menu Messages can now be dismissed on the title screen, instead of carried into the top-level menu transition. --- src/d_clisrv.c | 5 ++++- src/f_finale.c | 6 ++++-- src/k_menufunc.c | 17 +++++++++++++++-- src/menus/transient/message-box.c | 21 +++++++++++---------- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 67718cc4b..ddf33f091 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1676,6 +1676,8 @@ static boolean M_ConfirmConnect(void) if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) { cl_requestmode = CL_ABORTED; + + M_StopMessage(MA_NO); return true; } @@ -1704,6 +1706,7 @@ static boolean M_ConfirmConnect(void) else cl_requestmode = CL_LOADFILES; + M_StopMessage(MA_YES); return true; } @@ -2174,7 +2177,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic I_lock_mutex(&k_menu_mutex); #endif if (M_MenuMessageTick() && M_ConfirmConnect()) - M_StopMessage(0); + ; else if (menumessage.active == false) cl_mode = cl_requestmode; #ifdef HAVE_THREADS diff --git a/src/f_finale.c b/src/f_finale.c index 14011b1c9..a6f4fb900 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -2131,8 +2131,10 @@ void F_TitleScreenTicker(boolean run) // Now start the music S_ChangeMusicInternal("_title", looptitle); } - else if (menumessage.fadetimer < 9) - menumessage.fadetimer++; + else if (menumessage.active) + { + M_MenuMessageTick(); + } finalecount++; } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 2e7800be0..4f3ce4f3d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -539,6 +539,21 @@ void M_StartControlPanel(void) // (We can change this timer later when extra animation is added.) if (finalecount < 1) return; + + if (menumessage.active) + { + if (!menumessage.closing && menumessage.fadetimer == 9) + { + // The following doesn't work with MM_YESNO. + // However, because there's no guarantee a profile + // is selected or controls set up to our liking, + // we can't call M_HandleMenuMessage. + + M_StopMessage(MA_NONE); + } + + return; + } } menuactive = true; @@ -549,8 +564,6 @@ void M_StartControlPanel(void) } else if (!Playing()) { - M_StopMessage(0); // Doesn't work with MM_YESNO or MM_EVENTHANDLER... but good enough to get the game as it is currently functional again - if (gamestate != GS_MENU) { G_SetGamestate(GS_MENU); diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 5f88dd832..bcb90293d 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -136,11 +136,14 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 void M_StopMessage(INT32 choice) { + if (!menumessage.active || menumessage.closing) + return; + const char pid = 0; - (void) choice; menumessage.closing = true; menumessage.timer = 0; + menumessage.answer = choice; M_SetMenuDelay(pid); } @@ -192,20 +195,18 @@ void M_HandleMenuMessage(void) case MM_YESNO: { if (btok) - menumessage.answer = MA_YES; + M_StopMessage(MA_YES); else if (btnok) - menumessage.answer = MA_NO; + M_StopMessage(MA_NO); break; } default: - break; - } + { + if (btok || btnok) + M_StopMessage(MA_NONE); - // if we detect any keypress, don't forget to set the menu delay regardless. - if (btok || btnok) - { - M_StopMessage(0); - M_SetMenuDelay(pid); + break; + } } } From 3391b0e9a512dabd6746a30a83defbb0cc0a2963 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 14:03:52 +0100 Subject: [PATCH 34/44] Flash button prompt before closing Menu Message, for improved conveyance Needs a sound, we can do that later --- src/k_menu.h | 3 +- src/k_menudraw.c | 55 ++++++++++++++++++++++++++----- src/menus/transient/message-box.c | 35 +++++++++++++------- 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 4b10d0eac..29c0734ea 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -517,10 +517,11 @@ typedef enum } manswer_e; #define MAXMENUMESSAGE 256 +#define MENUMESSAGECLOSE 2 extern struct menumessage_s { boolean active; - boolean closing; + UINT8 closing; INT32 flags; // MM_ const char *header; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b08e251b0..0b56ae847 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -606,15 +606,15 @@ static void M_DrawMenuTyping(void) // Draw the message popup submenu void M_DrawMenuMessage(void) { + if (!menumessage.active) + return; + INT32 x = (BASEVIDWIDTH - menumessage.x)/2; INT32 y = (BASEVIDHEIGHT - menumessage.y)/2 + floor(pow(2, (double)(9 - menumessage.fadetimer))); size_t i, start = 0; char string[MAXMENUMESSAGE]; const char *msg = menumessage.message; - if (!menumessage.active) - return; - V_DrawFadeScreen(31, menumessage.fadetimer); V_DrawFill(0, y, BASEVIDWIDTH, menumessage.y, 159); @@ -629,25 +629,64 @@ void M_DrawMenuMessage(void) INT32 workx = x + menumessage.x; INT32 worky = y + menumessage.y; + boolean push; + + if (menumessage.closing) + push = (menumessage.answer != MA_YES); + else + { + const UINT8 anim_duration = 16; + push = ((menumessage.timer % (anim_duration * 2)) < anim_duration); + } + workx -= V_ThinStringWidth(menumessage.defaultstr, V_6WIDTHSPACE|V_ALLOWLOWERCASE); - V_DrawThinString(workx, worky + 1, V_6WIDTHSPACE|V_ALLOWLOWERCASE, menumessage.defaultstr); + V_DrawThinString( + workx, worky + 1, + V_6WIDTHSPACE|V_ALLOWLOWERCASE + | ((push && (menumessage.closing & MENUMESSAGECLOSE)) ? highlightflags : 0), + menumessage.defaultstr + ); + + workx -= 2; workx -= SHORT(kp_button_x[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_x[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_x[1], + push + ); workx -= SHORT(kp_button_b[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_b[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_b[1], + push + ); if (menumessage.confirmstr) { workx -= 12; + if (menumessage.closing) + push = !push; + workx -= V_ThinStringWidth(menumessage.confirmstr, V_6WIDTHSPACE|V_ALLOWLOWERCASE); - V_DrawThinString(workx, worky + 1, V_6WIDTHSPACE|V_ALLOWLOWERCASE, menumessage.confirmstr); + V_DrawThinString( + workx, worky + 1, + V_6WIDTHSPACE|V_ALLOWLOWERCASE + | ((push && (menumessage.closing & MENUMESSAGECLOSE)) ? highlightflags : 0), + menumessage.confirmstr + ); + + workx -= 2; } workx -= SHORT(kp_button_a[1][0]->width); - K_drawButtonAnim(workx, worky, 0, kp_button_a[1], menumessage.timer); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_a[1], + push + ); } x -= 4; diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index bcb90293d..873d5838e 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -74,7 +74,7 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 menumessage.answer = MA_NONE; menumessage.fadetimer = 1; menumessage.timer = 0; - menumessage.closing = false; + menumessage.closing = 0; menumessage.active = true; start = 0; @@ -141,9 +141,15 @@ void M_StopMessage(INT32 choice) const char pid = 0; - menumessage.closing = true; - menumessage.timer = 0; + // Set the answer. menumessage.answer = choice; + + // Intended length of time. + menumessage.closing = (TICRATE/2); + + // This weird operation is necessary so the text flash is consistently timed. + menumessage.closing |= ((2*MENUMESSAGECLOSE) - 1); + M_SetMenuDelay(pid); } @@ -151,18 +157,25 @@ boolean M_MenuMessageTick(void) { if (menumessage.closing) { - if (menumessage.fadetimer > 0) + if (menumessage.closing > MENUMESSAGECLOSE) { - menumessage.fadetimer--; + menumessage.closing--; } - - if (menumessage.fadetimer == 0) + else { - menumessage.active = false; - - if (menumessage.routine) + if (menumessage.fadetimer > 0) { - menumessage.routine(menumessage.answer); + menumessage.fadetimer--; + } + + if (menumessage.fadetimer == 0) + { + menumessage.active = false; + + if (menumessage.routine) + { + menumessage.routine(menumessage.answer); + } } } From 6b1fadd9d20a46d99352a9b3e5b6322528ae8d95 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 17:54:23 +0100 Subject: [PATCH 35/44] Make the Server Connection Ticker use the MenuCMD system - Permits the use of the existing K_HandleMenuMessage MM_YESNO system, instead of the faked previous implementation - Removes the "cl_requestmode" hack - The only area of the game that fakes handling Menu Messages is the title screen, now, due to the lack of guarantees for menu inputs. - *Also* permits Screenshots and video/lossless Recordings to be started/ended on this menu. --- src/d_clisrv.c | 69 +++++++++++++++++++++--------------------------- src/d_main.c | 2 +- src/k_menu.h | 2 +- src/k_menufunc.c | 7 ++++- 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index ddf33f091..2849589ba 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -653,7 +653,6 @@ typedef enum static void GetPackets(void); static cl_mode_t cl_mode = CL_SEARCHING; -static cl_mode_t cl_requestmode = CL_ABORTED; #ifdef HAVE_CURL char http_source[MAX_MIRROR_LENGTH]; @@ -1671,46 +1670,37 @@ void CL_UpdateServerList (void) SendAskInfo(BROADCASTADDR); } -static boolean M_ConfirmConnect(void) +static void M_ConfirmConnect(INT32 choice) { - if (G_PlayerInputDown(0, gc_b, 1) || G_PlayerInputDown(0, gc_x, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) - { - cl_requestmode = CL_ABORTED; - - M_StopMessage(MA_NO); - return true; - } - - if (G_PlayerInputDown(0, gc_a, 1) || G_GetDeviceGameKeyDownArray(0)[KEY_ENTER]) + if (choice == MA_YES) { if (totalfilesrequestednum > 0) { -#ifdef HAVE_CURL + #ifdef HAVE_CURL if (http_source[0] == '\0' || curl_failedwebdownload) -#endif + #endif { if (CL_SendFileRequest()) { - cl_requestmode = CL_DOWNLOADFILES; + cl_mode = CL_DOWNLOADFILES; } else { - cl_requestmode = CL_DOWNLOADFAILED; + cl_mode = CL_DOWNLOADFAILED; } } -#ifdef HAVE_CURL + #ifdef HAVE_CURL else - cl_requestmode = CL_PREPAREHTTPFILES; -#endif + cl_mode = CL_PREPAREHTTPFILES; + #endif } else - cl_requestmode = CL_LOADFILES; + cl_mode = CL_LOADFILES; - M_StopMessage(MA_YES); - return true; + return; } - return false; + cl_mode = CL_ABORTED; } static boolean CL_FinishedFileList(void) @@ -1762,7 +1752,7 @@ static boolean CL_FinishedFileList(void) "This server is full!\n" "\n" "You may load server addons (if any), and wait for a slot.\n" - ), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); cl_mode = CL_CONFIRMCONNECT; } else @@ -1825,13 +1815,13 @@ static boolean CL_FinishedFileList(void) "\n" "You may download, load server addons,\n" "and wait for a slot.\n" - ), downloadsize), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), downloadsize), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); else M_StartMessage("Server Connection", va(M_GetText( "Download of %s additional content\n" "is required to join.\n" - ), downloadsize), NULL, MM_NOTHING, "Continue", "Back to Menu"); + ), downloadsize), &M_ConfirmConnect, MM_YESNO, "Continue", "Back to Menu"); Z_Free(downloadsize); cl_mode = CL_CONFIRMCONNECT; @@ -1972,6 +1962,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { boolean waitmore; INT32 i; + const UINT8 pid = 0; switch (cl_mode) { @@ -2171,26 +2162,27 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic G_MapEventsToControls(&events[eventtail]); } +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + M_UpdateMenuCMD(0, true); + if (cl_mode == CL_CONFIRMCONNECT) { -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - if (M_MenuMessageTick() && M_ConfirmConnect()) - ; - else if (menumessage.active == false) - cl_mode = cl_requestmode; -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif + if (menumessage.active) + M_HandleMenuMessage(); } else { - if (G_PlayerInputDown(0, gc_b, 1) - || G_PlayerInputDown(0, gc_x, 1) - || G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) + if (M_MenuBackPressed(pid)) cl_mode = CL_ABORTED; } + + M_ScreenshotTicker(); + +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif } if (cl_mode == CL_ABORTED) @@ -2268,7 +2260,6 @@ static void CL_ConnectToServer(void) lastfilenum = -1; cl_mode = CL_SEARCHING; - cl_requestmode = CL_ABORTED; // sane default // Don't get a corrupt savegame error because tmpsave already exists if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) diff --git a/src/d_main.c b/src/d_main.c index 7889d41d7..22ca0ef88 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -295,7 +295,7 @@ void D_ProcessEvents(void) // Update menu CMD for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - M_UpdateMenuCMD(i); + M_UpdateMenuCMD(i, false); } } diff --git a/src/k_menu.h b/src/k_menu.h index 29c0734ea..6fb5a2571 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -601,7 +601,7 @@ void M_SetMenuDelay(UINT8 i); void M_SortServerList(void); -void M_UpdateMenuCMD(UINT8 i); +void M_UpdateMenuCMD(UINT8 i, boolean bailrequired); boolean M_Responder(event_t *ev); boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt); boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 4f3ce4f3d..5acbb0e7d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -767,7 +767,7 @@ void M_SetMenuDelay(UINT8 i) } } -void M_UpdateMenuCMD(UINT8 i) +void M_UpdateMenuCMD(UINT8 i, boolean bailrequired) { UINT8 mp = max(1, setup_numplayers); @@ -805,6 +805,11 @@ void M_UpdateMenuCMD(UINT8 i) if (G_PlayerInputDown(i, gc_start, mp)) { menucmd[i].buttons |= MBT_START; } + if (bailrequired && i == 0) + { + if (G_GetDeviceGameKeyDownArray(0)[KEY_ESCAPE]) { menucmd[i].buttons |= MBT_B; } + } + if (menucmd[i].dpad_ud == 0 && menucmd[i].dpad_lr == 0 && menucmd[i].buttons == 0) { // Reset delay count with no buttons. From 3e6a5fda21a8226cff7b9f1c2fbfa8b5afe861b4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 18:25:55 +0100 Subject: [PATCH 36/44] Change the rules of when GP Backup occours/is deleted - Re-saved every time you lose a life, to prevent scumming - Force-deletion of your backup has changed - Don't when saying NO to the GP Backup load prompt - Don't when returning to titlescreen under non-DEVELOP - DO when starting a new Grand Prix session of any cup --- src/d_main.c | 1 - src/d_netcmd.c | 5 ----- src/doomstat.h | 1 - src/g_game.c | 19 +++++++++++-------- src/menus/play-char-select.c | 16 ++++++---------- src/p_saveg.c | 2 -- 6 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 22ca0ef88..aa79e6584 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -982,7 +982,6 @@ void D_ClearState(void) cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); - lastqueuesaved = 0; // In case someone exits out at the same time they start a time attack run, // reset modeattacking diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c24b3cb9e..f391e83c1 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -6241,11 +6241,6 @@ void Command_ExitGame_f(void) { INT32 i; -#ifndef DEVELOP - // Wipes gp backup if appropriate - G_HandleSaveLevel(true); -#endif - LUA_HookBool(false, HOOK(GameQuit)); D_QuitNetGame(); diff --git a/src/doomstat.h b/src/doomstat.h index e5788f824..e6787116e 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -51,7 +51,6 @@ extern UINT8 mapmusrng; extern UINT32 maptol; extern INT32 cursaveslot; -extern UINT8 lastqueuesaved; extern UINT8 gamecomplete; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/g_game.c b/src/g_game.c index 422c124a1..bffba112a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -123,7 +123,6 @@ precipprops_t precipprops[MAXPRECIP] = preciptype_t precip_freeslot = PRECIP_FIRSTFREESLOT; INT32 cursaveslot = 0; // Auto-save 1p savegame slot -UINT8 lastqueuesaved = 0; UINT8 gamecomplete = 0; marathonmode_t marathonmode = 0; @@ -3350,6 +3349,7 @@ void G_ExitLevel(void) else { // Back to the menu with you. + G_HandleSaveLevel(true); D_QuitNetGame(); CL_Reset(); D_ClearState(); @@ -4024,18 +4024,21 @@ void G_HandleSaveLevel(boolean removecondition) return; if (removecondition) - { - if (FIL_FileExists(gpbackup)) - remove(gpbackup); - return; - } + goto doremove; if (gamestate != GS_LEVEL - || roundqueue.size == 0 - || lastqueuesaved == roundqueue.position) + || roundqueue.size == 0) return; + if (roundqueue.position == 1) + goto doremove; + G_SaveGame(); + return; + +doremove: + if (FIL_FileExists(gpbackup)) + remove(gpbackup); } // Next map apparatus diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 733c37848..c88512926 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -530,7 +530,7 @@ static void M_GPBackup(INT32 choice) SplitScreen_OnChange(); } - const UINT8 entry = lastqueuesaved-1; + const UINT8 entry = roundqueue.position-1; SV_StartSinglePlayerServer(roundqueue.entries[entry].gametype, false); @@ -541,7 +541,7 @@ static void M_GPBackup(INT32 choice) true, 1, false, - roundqueue.entries[lastqueuesaved-1].rankrestricted + roundqueue.entries[entry].rankrestricted ); M_ClearMenus(true); @@ -549,21 +549,17 @@ static void M_GPBackup(INT32 choice) // We can't put it deeper in the menuflow due to lack of guaranteed setup restoreMenu = &MainDef; } - } - else if (choice == MA_NO) - { - if (FIL_FileExists(gpbackup)) // just in case someone deleted it while we weren't looking. - remove(gpbackup); - M_CharacterSelect(0); + return; } + + M_CharacterSelect(-1); } void M_CharacterSelect(INT32 choice) { - (void)choice; - if (currentMenu == &MainDef + && choice != -1 && FIL_FileExists(gpbackup)) { M_StartMessage( diff --git a/src/p_saveg.c b/src/p_saveg.c index 054058d93..6474e5ed9 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5968,8 +5968,6 @@ boolean P_LoadGame(savebuffer_t *save) if (!P_UnArchiveLuabanksAndConsistency(save)) goto badloadgame; - lastqueuesaved = roundqueue.position; - return true; badloadgame: From d6a3e1446fd7973e613b1632815b9dbfe31fe80e Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 18 Jun 2023 19:08:42 +0100 Subject: [PATCH 37/44] Improve handling of M_StartMessage strings The author of this commit ran into an issue while testing previous changes where a Menu Message had MM_NOTHING but a Yes and No string, leading to misleading appearances. This prevents that footgun from happening again. --- src/menus/transient/message-box.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 873d5838e..1e5de84b9 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -85,15 +85,19 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3 menumessage.flags = MM_NOTHING; } - if (menumessage.flags == MM_YESNO && !defaultstr) + // Set action strings + switch (menumessage.flags) { - menumessage.defaultstr = "No"; - menumessage.confirmstr = "Yes"; - } - else - { - menumessage.defaultstr = defaultstr ? defaultstr : "OK"; - menumessage.confirmstr = confirmstr; + // Send 1 to the routine if we're pressing A, 2 if B/X, 0 otherwise. + case MM_YESNO: + menumessage.defaultstr = defaultstr ? defaultstr : "No"; + menumessage.confirmstr = confirmstr ? confirmstr : "Yes"; + break; + + default: + menumessage.defaultstr = defaultstr ? defaultstr : "OK"; + menumessage.confirmstr = NULL; + break; } // event routine From 8199437e0ded17eebc27239d66db22b8f47e61c3 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 23 Jun 2023 13:39:08 +0100 Subject: [PATCH 38/44] Coming back to the branch, closing menu messages felt annoyingly unresponsive. Let's disable the flashing animation for now and just retain the slideaway. --- src/menus/transient/message-box.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 1e5de84b9..50ef95fb7 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -148,11 +148,16 @@ void M_StopMessage(INT32 choice) // Set the answer. menumessage.answer = choice; +#if 1 + // The below was cool, but it felt annoyingly unresponsive. + menumessage.closing = MENUMESSAGECLOSE+1; +#else // Intended length of time. menumessage.closing = (TICRATE/2); // This weird operation is necessary so the text flash is consistently timed. menumessage.closing |= ((2*MENUMESSAGECLOSE) - 1); +#endif M_SetMenuDelay(pid); } From 3474fd6e83c265099f4565721d0bb41aa36e8465 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 20:13:15 +0100 Subject: [PATCH 39/44] Grand Prix Backup: Store lumpnamehashes of all maps in queue for extra confirmation that the cup's contents are the same between gameboots. --- src/p_saveg.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 6474e5ed9..124285bd1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5379,6 +5379,17 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT8(save->p, roundqueue.size); WRITEUINT8(save->p, roundqueue.roundnum); + UINT8 i; + for (i = 0; i < roundqueue.size; i++) + { + UINT16 mapnum = roundqueue.entries[i].mapnum; + UINT32 val = 0; // no good default, will all-but-guarantee bad save + if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) + val = mapheaderinfo[mapnum]->lumpnamehash; + + WRITEUINT32(save->p, val); + } + WRITEUINT8(save->p, (marathonmode & ~MA_INIT)); UINT32 writetime = marathontime; @@ -5469,7 +5480,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) if (roundqueue.size != size) { - CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches.\n", cupname); + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches (differs in level count).\n", cupname); return false; } @@ -5479,6 +5490,22 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) return false; } + UINT8 i; + for (i = 0; i < roundqueue.size; i++) + { + UINT32 val = READUINT32(save->p); + UINT16 mapnum = roundqueue.entries[i].mapnum; + + if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) + { + if (mapheaderinfo[mapnum]->lumpnamehash == val) + continue; + } + + CONS_Alert(CONS_ERROR, "P_UnArchiveSPGame: Cup \"%s\"'s level composition has changed between game launches (differs at level %u).\n", cupname, i); + return false; + } + marathonmode = READUINT8(save->p); marathontime = READUINT32(save->p); From 87c26f0fe9d7c690992655b1e07f1315c3be9869 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 20:23:44 +0100 Subject: [PATCH 40/44] Skip Bonus Rounds when loading up Live Event Backups Prevents the exploit Tape discussed from being used for Battle. Also attempts to recover to Podium if relevant. --- src/g_game.c | 2 +- src/g_game.h | 1 + src/k_podium.c | 7 +++++ src/menus/play-char-select.c | 51 +++++++++++++++++++++++++++++------- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index bffba112a..ee4125077 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4145,7 +4145,7 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor } } -static void G_GetNextMap(void) +void G_GetNextMap(void) { INT32 i; boolean setalready = false; diff --git a/src/g_game.h b/src/g_game.h index 55876e5df..a05d7ef0c 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -206,6 +206,7 @@ boolean G_GametypeHasSpectators(void); INT16 G_SometimesGetDifferentEncore(void); void G_ExitLevel(void); void G_NextLevel(void); +void G_GetNextMap(void); void G_Continue(void); void G_UseContinue(void); void G_AfterIntermission(void); diff --git a/src/k_podium.c b/src/k_podium.c index 7b658c944..630048e98 100644 --- a/src/k_podium.c +++ b/src/k_podium.c @@ -35,6 +35,7 @@ #include "y_inter.h" #include "m_cond.h" #include "p_local.h" +#include "p_saveg.h" #include "p_setup.h" #include "st_stuff.h" // hud hiding #include "fastcmp.h" @@ -252,6 +253,12 @@ boolean K_StartCeremony(void) maptol = mapheaderinfo[gamemap-1]->typeoflevel; globalweather = mapheaderinfo[gamemap-1]->weather; + if (savedata.lives > 0) + { + K_LoadGrandPrixSaveGame(); + savedata.lives = 0; + } + // Make sure all of the GAME OVER'd players can spawn // and be present for the podium for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index c88512926..837f81527 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 "../k_podium.h" // K_StartCeremony #include "../m_cond.h" // Condition Sets #include "../r_local.h" // SplitScreen_OnChange #include "../m_misc.h" // FIL_FileExists @@ -530,19 +531,49 @@ static void M_GPBackup(INT32 choice) SplitScreen_OnChange(); } - const UINT8 entry = roundqueue.position-1; + UINT8 entry = roundqueue.position-1; SV_StartSinglePlayerServer(roundqueue.entries[entry].gametype, false); - D_MapChange( - roundqueue.entries[entry].mapnum + 1, - roundqueue.entries[entry].gametype, - roundqueue.entries[entry].encore, - true, - 1, - false, - roundqueue.entries[entry].rankrestricted - ); + // Skip Bonus rounds. + if (roundqueue.entries[entry].gametype != roundqueue.entries[0].gametype + && roundqueue.entries[entry].rankrestricted == false) + { + G_GetNextMap(); // updates position in the roundqueue + entry = roundqueue.position-1; + } + + if (entry < roundqueue.size) + { + D_MapChange( + roundqueue.entries[entry].mapnum + 1, + roundqueue.entries[entry].gametype, + roundqueue.entries[entry].encore, + true, + 1, + false, + roundqueue.entries[entry].rankrestricted + ); + } + else + { + if (K_StartCeremony() == false) + { + // Accomodate our buffoonery with the artificial fade. + wipegamestate = -1; + + M_StartMessage( + "Grand Prix Backup", + "The session is concluded!\n" + "You exited a final Bonus Round,\n" + "and the Podium failed to load.\n", + NULL, MM_NOTHING, NULL, NULL); + + G_HandleSaveLevel(true); + + return; + } + } M_ClearMenus(true); From b1b23da51b73cb4ec82e4053c18f1ac672d7d859 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 20:28:28 +0100 Subject: [PATCH 41/44] Prevent the other half of tape's exploit Save one less life than you actually have, so you're discouraged from constantly reloading from backup. --- src/g_game.c | 3 ++- src/p_saveg.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index ee4125077..47ae6479f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4030,7 +4030,8 @@ void G_HandleSaveLevel(boolean removecondition) || roundqueue.size == 0) return; - if (roundqueue.position == 1) + if (roundqueue.position == 1 + || players[consoleplayer].lives <= 1) // because a life is lost on reload goto doremove; G_SaveGame(); diff --git a/src/p_saveg.c b/src/p_saveg.c index 124285bd1..57b7e140f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -84,7 +84,8 @@ static inline void P_ArchivePlayer(savebuffer_t *save) { const player_t *player = &players[consoleplayer]; - WRITESINT8(save->p, player->lives); + // Prevent an exploit from occuring. + WRITESINT8(save->p, (player->lives - 1)); WRITEUINT32(save->p, player->score); WRITEUINT16(save->p, player->totalring); From e9df563826c71db3fbe4c705cdaf3e3b5ae01afb Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 21:48:18 +0100 Subject: [PATCH 42/44] Rearrange contents of GP Backups for future purposes Also increments minor version for backups --- src/g_game.c | 2 +- src/p_saveg.c | 99 +++++++++++++++++++++++++++++---------------------- src/p_saveg.h | 5 ++- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 47ae6479f..64424da4f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5659,7 +5659,7 @@ void G_SaveGameData(void) // Can be called by the startup code or the menu task. // -#define SAV_VERSIONMINOR 1 +#define SAV_VERSIONMINOR 2 void G_LoadGame(void) { diff --git a/src/p_saveg.c b/src/p_saveg.c index 57b7e140f..729b38d42 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5346,10 +5346,33 @@ static inline void P_ArchiveMisc(savebuffer_t *save) { WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); + // Grand Prix information + WRITEUINT8(save->p, grandprixinfo.gamespeed); WRITEUINT8(save->p, (UINT8)grandprixinfo.encore); WRITEUINT8(save->p, (UINT8)grandprixinfo.masterbots); + WRITESTRINGL(save->p, grandprixinfo.cup->name, MAXCUPNAME); + + // Round Queue information + + WRITEUINT8(save->p, roundqueue.position); + WRITEUINT8(save->p, roundqueue.size); + WRITEUINT8(save->p, roundqueue.roundnum); + + UINT8 i; + for (i = 0; i < roundqueue.size; i++) + { + UINT16 mapnum = roundqueue.entries[i].mapnum; + UINT32 val = 0; // no good default, will all-but-guarantee bad save + if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) + val = mapheaderinfo[mapnum]->lumpnamehash; + + WRITEUINT32(save->p, val); + } + + // Rank information + { WRITEUINT8(save->p, grandprixinfo.rank.players); WRITEUINT8(save->p, grandprixinfo.rank.totalPlayers); @@ -5374,22 +5397,7 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT8(save->p, (UINT8)grandprixinfo.rank.specialWon); } - WRITESTRINGL(save->p, grandprixinfo.cup->name, MAXCUPNAME); - - WRITEUINT8(save->p, roundqueue.position); - WRITEUINT8(save->p, roundqueue.size); - WRITEUINT8(save->p, roundqueue.roundnum); - - UINT8 i; - for (i = 0; i < roundqueue.size; i++) - { - UINT16 mapnum = roundqueue.entries[i].mapnum; - UINT32 val = 0; // no good default, will all-but-guarantee bad save - if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) - val = mapheaderinfo[mapnum]->lumpnamehash; - - WRITEUINT32(save->p, val); - } + // Marathon information WRITEUINT8(save->p, (marathonmode & ~MA_INIT)); @@ -5420,37 +5428,14 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) grandprixinfo.gp = true; + // Grand Prix information + grandprixinfo.gamespeed = READUINT8(save->p); grandprixinfo.encore = (boolean)READUINT8(save->p); grandprixinfo.masterbots = (boolean)READUINT8(save->p); - { - grandprixinfo.rank.players = READUINT8(save->p); - grandprixinfo.rank.totalPlayers = READUINT8(save->p); - - grandprixinfo.rank.position = READUINT8(save->p); - grandprixinfo.rank.skin = READUINT8(save->p); - - grandprixinfo.rank.winPoints = READUINT32(save->p); - grandprixinfo.rank.totalPoints = READUINT32(save->p); - - grandprixinfo.rank.laps = READUINT32(save->p); - grandprixinfo.rank.totalLaps = READUINT32(save->p); - - grandprixinfo.rank.continuesUsed = READUINT32(save->p); - - grandprixinfo.rank.prisons = READUINT32(save->p); - grandprixinfo.rank.totalPrisons = READUINT32(save->p); - - grandprixinfo.rank.rings = READUINT32(save->p); - grandprixinfo.rank.totalRings = READUINT32(save->p); - - grandprixinfo.rank.specialWon = (boolean)READUINT8(save->p); - } - - char cupname[MAXCUPNAME]; - // Find the relevant cup. + char cupname[MAXCUPNAME]; READSTRINGL(save->p, cupname, sizeof(cupname)); UINT32 hash = quickncasehash(cupname, MAXCUPNAME); @@ -5471,6 +5456,8 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) return false; } + // Round Queue information + memset(&roundqueue, 0, sizeof(roundqueue)); G_GPCupIntoRoundQueue(grandprixinfo.cup, GT_RACE, grandprixinfo.encore); @@ -5507,6 +5494,34 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) return false; } + // Rank information + + { + grandprixinfo.rank.players = READUINT8(save->p); + grandprixinfo.rank.totalPlayers = READUINT8(save->p); + + grandprixinfo.rank.position = READUINT8(save->p); + grandprixinfo.rank.skin = READUINT8(save->p); + + grandprixinfo.rank.winPoints = READUINT32(save->p); + grandprixinfo.rank.totalPoints = READUINT32(save->p); + + grandprixinfo.rank.laps = READUINT32(save->p); + grandprixinfo.rank.totalLaps = READUINT32(save->p); + + grandprixinfo.rank.continuesUsed = READUINT32(save->p); + + grandprixinfo.rank.prisons = READUINT32(save->p); + grandprixinfo.rank.totalPrisons = READUINT32(save->p); + + grandprixinfo.rank.rings = READUINT32(save->p); + grandprixinfo.rank.totalRings = READUINT32(save->p); + + grandprixinfo.rank.specialWon = (boolean)READUINT8(save->p); + } + + // Marathon information + marathonmode = READUINT8(save->p); marathontime = READUINT32(save->p); diff --git a/src/p_saveg.h b/src/p_saveg.h index b8e5bcf0d..ad2537087 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -31,9 +31,12 @@ extern "C" { // Persistent storage/archiving. // These are the load / save game routines. +// Local Play void P_SaveGame(savebuffer_t *save); -void P_SaveNetGame(savebuffer_t *save, boolean resending); boolean P_LoadGame(savebuffer_t *save); + +// Online +void P_SaveNetGame(savebuffer_t *save, boolean resending); boolean P_LoadNetGame(savebuffer_t *save, boolean reloading); mobj_t *P_FindNewPosition(UINT32 oldposition); From a016a54e5298f4afd9d1f9fb376f5596fc369dea Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 23:20:33 +0100 Subject: [PATCH 43/44] Rework how GP Backups are accessed - Now actually from the relevant GP difficulty's Cupgrid, instead of the top-level Play choice - Permits a much cleaner M_StartCup, combining two of the previously four copypasted, slightly modified level startup regions (which could be further combined for sanity's sake, but would take a LITTLE more work right now than I have in me) - Shows a funny exclamation mark from Sonic Rush on the relevant cup on the grid - Selected by default when loading the menu, if appropriate --- src/g_game.c | 51 +++++++ src/g_game.h | 1 + src/k_menu.h | 1 + src/k_menudraw.c | 24 +++- src/menus/play-char-select.c | 119 +--------------- src/menus/transient/cup-select.c | 220 +++++++++++++++++++++-------- src/menus/transient/level-select.c | 10 +- src/p_saveg.c | 41 ++++++ src/p_saveg.h | 10 ++ src/typedef.h | 1 + 10 files changed, 296 insertions(+), 182 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 64424da4f..1b3dfa770 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5719,6 +5719,57 @@ void G_LoadGame(void) CON_ToggleOff(); } +void G_GetBackupCupData(boolean actuallygetdata) +{ + if (actuallygetdata == false) + { + cupsavedata.cup = NULL; + return; + } + + char vcheck[VERSIONSIZE+1]; + char savename[255]; + UINT8 versionMinor; + savebuffer_t save = {0}; + + //if (makelivebackup) + strcpy(savename, gpbackup); + //else + //sprintf(savename, savegamename, cursaveslot); + + if (P_SaveBufferFromFile(&save, savename) == false) + { + cupsavedata.cup = NULL; + return; + } + + versionMinor = READUINT8(save.p); + + memset(vcheck, 0, sizeof (vcheck)); + sprintf(vcheck, "version %d", VERSION); + + if (versionMinor != SAV_VERSIONMINOR + || memcmp(save.p, vcheck, VERSIONSIZE)) + { + cupsavedata.cup = NULL; + P_SaveBufferFree(&save); + return; // bad version + } + save.p += VERSIONSIZE; + + P_GetBackupCupData(&save); + + if (cv_dummygpdifficulty.value != cupsavedata.difficulty + || !!cv_dummygpencore.value != cupsavedata.encore) + { + // Still not compatible. + cupsavedata.cup = NULL; + } + + // done + P_SaveBufferFree(&save); +} + // // G_SaveGame // Saves your game. diff --git a/src/g_game.h b/src/g_game.h index a05d7ef0c..71dd4ada8 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -190,6 +190,7 @@ boolean G_IsTitleCardAvailable(void); void G_HandleSaveLevel(boolean removecondition); void G_SaveGame(void); void G_LoadGame(void); +void G_GetBackupCupData(boolean actuallygetdata); void G_SaveGameData(void); void G_DirtyGameData(void); diff --git a/src/k_menu.h b/src/k_menu.h index 6fb5a2571..7eefd06ea 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -19,6 +19,7 @@ #include "command.h" #include "doomstat.h" // MAXSPLITSCREENPLAYERS #include "g_demo.h" //menudemo_t +#include "p_saveg.h" // savedata_cup_t #include "k_profiles.h" // profile data & functions #include "g_input.h" // gc_ #include "i_threads.h" diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0b56ae847..361e61d46 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2368,6 +2368,7 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) void M_DrawCupSelect(void) { UINT8 i, j, temp = 0; + INT16 x, y; UINT8 *colormap = NULL; cupwindata_t *windata = NULL; levelsearch_t templevelsearch = levellist.levelsearch; // full copy @@ -2378,7 +2379,6 @@ void M_DrawCupSelect(void) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); patch_t *patch = NULL; - INT16 x, y; INT16 icony = 7; char status = 'A'; char monitor; @@ -2463,6 +2463,13 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); + if (cupgrid.grandprix == true + && templevelsearch.cup == cupsavedata.cup + && id != CUPMENU_CURSORID) + { + V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP1", PU_CACHE)); + } + if (!windata) ; else if (windata->best_placement != 0) @@ -2562,13 +2569,20 @@ void M_DrawCupSelect(void) } } - V_DrawScaledPatch(14 + (cupgrid.x*42) - 4, - 20 + (cupgrid.y*44) - 1 - (24*menutransition.tics), - 0, W_CachePatchName("CUPCURS", PU_CACHE) - ); + x = 14 + (cupgrid.x*42); + y = 20 + (cupgrid.y*44) - (30*menutransition.tics); + + V_DrawScaledPatch(x - 4, y - 1, 0, W_CachePatchName("CUPCURS", PU_CACHE)); templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + if (cupgrid.grandprix == true + && templevelsearch.cup != NULL + && templevelsearch.cup == cupsavedata.cup) + { + V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP2", PU_CACHE)); + } + V_DrawFill(0, 146 + (24*menutransition.tics), BASEVIDWIDTH, 54, 31); M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 837f81527..edd3c99c8 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -5,10 +5,7 @@ #include "../r_skins.h" #include "../s_sound.h" #include "../k_grandprix.h" // K_CanChangeRules -#include "../k_podium.h" // K_StartCeremony #include "../m_cond.h" // Condition Sets -#include "../r_local.h" // SplitScreen_OnChange -#include "../m_misc.h" // FIL_FileExists //#define CHARSELECT_DEVICEDEBUG @@ -488,123 +485,9 @@ void M_CharacterSelectInit(void) } -static void M_GPBackup(INT32 choice) -{ - if (choice == MA_YES) - { - G_LoadGame(); - - if (savedata.lives != 0) - { - // Only do this after confirming savegame is ok - const UINT8 ssplayers = 0; - - { - CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor); - - // follower - if (savedata.followerskin < 0 || savedata.followerskin >= numfollowers) - CV_StealthSet(&cv_follower[0], "None"); - else - CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name); - - // finally, call the skin[x] console command. - // This will call SendNameAndColor which will synch everything we sent here and apply the changes! - - CV_StealthSet(&cv_skin[0], skins[savedata.skin].name); - - // ...actually, let's do this last - Skin_OnChange has some return-early occasions - // follower color - CV_SetValue(&cv_followercolor[0], savedata.followercolor); - } - - paused = false; - - S_StopMusicCredit(); - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - UINT8 entry = roundqueue.position-1; - - SV_StartSinglePlayerServer(roundqueue.entries[entry].gametype, false); - - // Skip Bonus rounds. - if (roundqueue.entries[entry].gametype != roundqueue.entries[0].gametype - && roundqueue.entries[entry].rankrestricted == false) - { - G_GetNextMap(); // updates position in the roundqueue - entry = roundqueue.position-1; - } - - if (entry < roundqueue.size) - { - D_MapChange( - roundqueue.entries[entry].mapnum + 1, - roundqueue.entries[entry].gametype, - roundqueue.entries[entry].encore, - true, - 1, - false, - roundqueue.entries[entry].rankrestricted - ); - } - else - { - if (K_StartCeremony() == false) - { - // Accomodate our buffoonery with the artificial fade. - wipegamestate = -1; - - M_StartMessage( - "Grand Prix Backup", - "The session is concluded!\n" - "You exited a final Bonus Round,\n" - "and the Podium failed to load.\n", - NULL, MM_NOTHING, NULL, NULL); - - G_HandleSaveLevel(true); - - return; - } - } - - M_ClearMenus(true); - - // We can't put it deeper in the menuflow due to lack of guaranteed setup - restoreMenu = &MainDef; - } - - return; - } - - M_CharacterSelect(-1); -} - void M_CharacterSelect(INT32 choice) { - if (currentMenu == &MainDef - && choice != -1 - && FIL_FileExists(gpbackup)) - { - M_StartMessage( - "Grand Prix Backup", - "A progress backup was found.\n" - "Do you want to resurrect your\n" - "last Grand Prix session?\n", - M_GPBackup, - MM_YESNO, - "Yes, let's try again", - "No, play another way"); - return; - } - + (void)choice; PLAY_CharSelectDef.music = currentMenu->music; PLAY_CharSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_CharSelectDef, false); diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index c97246e04..f146cd74f 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -7,6 +7,9 @@ #include "../../v_video.h" #include "../../k_grandprix.h" #include "../../r_local.h" // SplitScreen_OnChange +#include "../../k_podium.h" // K_StartCeremony +#include "../../m_misc.h" // FIL_FileExists +#include "../../d_main.h" // D_ClearState menuitem_t PLAY_CupSelect[] = { @@ -32,6 +35,150 @@ menu_t PLAY_CupSelectDef = { struct cupgrid_s cupgrid; +static void M_StartCup(UINT8 entry) +{ + UINT8 ssplayers = cv_splitplayers.value-1; + + if (ssplayers > 0) + { + // Splitscreen is not accomodated with this recovery feature. + entry = 0; + } + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + S_StopMusicCredit(); + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + if (entry == 0) + { + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + // read our dummy cvars + + grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); + grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); + + grandprixinfo.gp = true; + grandprixinfo.initalize = true; + grandprixinfo.cup = levellist.levelsearch.cup; + + // Populate the roundqueue + memset(&roundqueue, 0, sizeof(struct roundqueue)); + G_GPCupIntoRoundQueue(levellist.levelsearch.cup, levellist.newgametype, (boolean)cv_dummygpencore.value); + roundqueue.position = roundqueue.roundnum = 1; + roundqueue.netcommunicate = true; // relevant for future Online GP + } + else + { + // Silently change player setup + { + CV_StealthSetValue(&cv_playercolor[0], savedata.skincolor); + + // follower + if (savedata.followerskin < 0 || savedata.followerskin >= numfollowers) + CV_StealthSet(&cv_follower[0], "None"); + else + CV_StealthSet(&cv_follower[0], followers[savedata.followerskin].name); + + // finally, call the skin[x] console command. + // This will call SendNameAndColor which will synch everything we sent here and apply the changes! + + CV_StealthSet(&cv_skin[0], skins[savedata.skin].name); + + // ...actually, let's do this last - Skin_OnChange has some return-early occasions + // follower color + CV_SetValue(&cv_followercolor[0], savedata.followercolor); + } + + // Skip Bonus rounds. + if (roundqueue.entries[entry].gametype != roundqueue.entries[0].gametype + && roundqueue.entries[entry].rankrestricted == false) + { + G_GetNextMap(); // updates position in the roundqueue + entry = roundqueue.position-1; + } + } + + paused = false; + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + + M_ClearMenus(true); + restoreMenu = &PLAY_CupSelectDef; + + if (entry < roundqueue.size) + { + D_MapChange( + roundqueue.entries[entry].mapnum + 1, + roundqueue.entries[entry].gametype, + roundqueue.entries[entry].encore, + true, + 1, + false, + roundqueue.entries[entry].rankrestricted + ); + } + else if (entry == 0) + { + I_Error("M_StartCup: roundqueue is empty on startup!!"); + } + else + { + if (K_StartCeremony() == false) + { + // Accomodate our buffoonery + D_ClearState(); + M_StartControlPanel(); + + M_StartMessage( + "Grand Prix Backup", + "The session is concluded!\n" + "You exited a final Bonus Round,\n" + "and the Podium failed to load.\n", + NULL, MM_NOTHING, NULL, NULL + ); + + G_HandleSaveLevel(true); + + return; + } + } +} + +static void M_GPBackup(INT32 choice) +{ + if (choice == MA_YES) + { + G_LoadGame(); + + if (savedata.lives != 0) + { + M_StartCup(roundqueue.position-1); + } + + return; + } + + M_StartCup(0); +} + void M_CupSelectHandler(INT32 choice) { const UINT8 pid = 0; @@ -104,67 +251,24 @@ void M_CupSelectHandler(INT32 choice) if (cupgrid.grandprix == true) { - UINT8 ssplayers = cv_splitplayers.value-1; - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - S_StopMusicCredit(); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) + if (newcup == cupsavedata.cup + && FIL_FileExists(gpbackup)) { - splitscreen = ssplayers; - SplitScreen_OnChange(); + M_StartMessage( + "Grand Prix Backup", + "A progress backup was found.\n" + "Do you want to resurrect your\n" + "last Grand Prix session?\n", + M_GPBackup, + MM_YESNO, + "Yes, let's try again", + "No, start from Round 1" + ); + + return; } - // read our dummy cvars - - grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); - grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); - - grandprixinfo.gp = true; - grandprixinfo.initalize = true; - grandprixinfo.cup = newcup; - - // Populate the roundqueue - memset(&roundqueue, 0, sizeof(struct roundqueue)); - G_GPCupIntoRoundQueue(newcup, levellist.newgametype, (boolean)cv_dummygpencore.value); - roundqueue.position = roundqueue.roundnum = 1; - roundqueue.netcommunicate = true; // relevant for future Online GP - - paused = false; - - // Don't restart the server if we're already in a game lol - if (gamestate == GS_MENU) - { - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - } - - D_MapChange( - roundqueue.entries[0].mapnum + 1, - roundqueue.entries[0].gametype, - roundqueue.entries[0].encore, - true, - 1, - false, - roundqueue.entries[0].rankrestricted - ); - - M_ClearMenus(true); - - restoreMenu = &PLAY_CupSelectDef; + M_StartCup(0); } else if (count == 1 && levellist.levelsearch.timeattack == true) { diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index a54096b1b..a759f7c0b 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -8,6 +8,8 @@ #include "../../r_local.h" // SplitScreen_OnChange #include "../../f_finale.h" // F_WipeStartScreen #include "../../v_video.h" +#include "../../g_game.h" // G_GetBackupCupData +#include "../../p_saveg.h" // cupsavedata cupheader_t dummy_lostandfound; @@ -262,6 +264,11 @@ boolean M_LevelListFromGametype(INT16 gt) const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); boolean foundany = false, currentvalid = false; + G_GetBackupCupData( + cupgrid.grandprix == true + || cv_splitplayers.value <= 1 + ); + templevelsearch.cup = kartcupheaders; #if 0 @@ -331,7 +338,8 @@ boolean M_LevelListFromGametype(INT16 gt) if (Playing() ? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) - : (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup)) + : (cupsavedata.cup == templevelsearch.cup + || (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup))) { GRID_FOCUSCUP; } diff --git a/src/p_saveg.c b/src/p_saveg.c index 729b38d42..f80ffff7b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -48,6 +48,7 @@ #include "k_zvote.h" savedata_t savedata; +savedata_cup_t cupsavedata; // Block UINT32s to attempt to ensure that the correct data is // being sent and received @@ -5407,6 +5408,46 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT32(save->p, writetime); } +void P_GetBackupCupData(savebuffer_t *save) +{ + char testname[sizeof(timeattackfolder)]; + + READSTRINGN(save->p, testname, sizeof(testname)); + + if (strcmp(testname, timeattackfolder)) + { + cupsavedata.cup = NULL; + return; + } + + // Grand Prix information + + cupsavedata.difficulty = READUINT8(save->p); + cupsavedata.encore = (boolean)READUINT8(save->p); + boolean masterbots = (boolean)READUINT8(save->p); + + if (masterbots == true) + cupsavedata.difficulty = KARTGP_MASTER; + + // Find the relevant cup. + char cupname[MAXCUPNAME]; + READSTRINGL(save->p, cupname, sizeof(cupname)); + UINT32 hash = quickncasehash(cupname, MAXCUPNAME); + + for (cupsavedata.cup = kartcupheaders; cupsavedata.cup; cupsavedata.cup = cupsavedata.cup->next) + { + if (cupsavedata.cup->namehash != hash) + continue; + + if (strcmp(cupsavedata.cup->name, cupname)) + continue; + + break; + } + + // Okay, no further! We've got everything we need. +} + static boolean P_UnArchiveSPGame(savebuffer_t *save) { char testname[sizeof(timeattackfolder)]; diff --git a/src/p_saveg.h b/src/p_saveg.h index ad2537087..66345367d 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -34,6 +34,7 @@ extern "C" { // Local Play void P_SaveGame(savebuffer_t *save); boolean P_LoadGame(savebuffer_t *save); +void P_GetBackupCupData(savebuffer_t *save); // Online void P_SaveNetGame(savebuffer_t *save, boolean resending); @@ -66,6 +67,15 @@ struct savedata_t extern savedata_t savedata; +struct savedata_cup_t +{ + cupheader_t *cup; + UINT8 difficulty; + boolean encore; +}; + +extern savedata_cup_t cupsavedata; + struct savebuffer_t { UINT8 *buffer; diff --git a/src/typedef.h b/src/typedef.h index a54ae82e5..275a30da4 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -297,6 +297,7 @@ TYPEDEF (polyfadedata_t); // p_saveg.h TYPEDEF (savedata_t); +TYPEDEF (savedata_cup_t); TYPEDEF (savebuffer_t); // p_setup.h From 78404af6fe0825bf35c8de74608ad279678ced90 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 25 Jun 2023 23:43:46 +0100 Subject: [PATCH 44/44] Arrows for cupgrid page indication My biggest bugbear for a while and we are literally almost out of time, can polish gfx later --- src/k_menudraw.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 361e61d46..d01bd593d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2587,6 +2587,20 @@ void M_DrawCupSelect(void) M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); + + if (cupgrid.numpages > 1) + { + x = 3 - (skullAnimCounter/5); + y = 20 + (44 - 1) - (30*menutransition.tics); + + patch_t *cuparrow = W_CachePatchName("CUPARROW", PU_CACHE); + + if (cupgrid.pageno != 0) + V_DrawScaledPatch(x, y, 0, cuparrow); + + if (cupgrid.pageno != cupgrid.numpages-1) + V_DrawScaledPatch(BASEVIDWIDTH-x, y, V_FLIP, cuparrow); + } } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map)