From 840c8d497ed8153cff35bd5fd4d6991f0629b0f4 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 29 Feb 2024 21:46:29 -0700 Subject: [PATCH 01/32] Bot behavior for charged instawhip --- src/cvars.cpp | 2 ++ src/d_netcmd.h | 1 + src/k_botitem.cpp | 55 ++++++++++++++++++++++++++++++++++++----------- src/k_kart.c | 12 +++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 2b439d89e..6fefb14f8 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -814,6 +814,8 @@ consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().descriptio consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off().description("Show items that the roulette can roll"); consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way"); +consvar_t cv_kartdebugbotwhip = OnlineCheat("debugbotwhip", "Off").on_off().description("Disable bot ring and item pickups"); + extern CV_PossibleValue_t kartdebugitem_cons_t[]; consvar_t cv_kartdebugitem = OnlineCheat("debugitem", "None").values(kartdebugitem_cons_t).description("Force item boxes to only roll one kind of item"); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index d1d0fa643..00c7c3322 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -94,6 +94,7 @@ extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution, extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_spbtest, cv_reducevfx, cv_screenshake; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbots; +extern consvar_t cv_kartdebugbotwhip; extern consvar_t cv_kartdebugstart; extern consvar_t cv_debugrank; extern consvar_t cv_battletest; diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 84baaab6b..db9cd35f7 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -1479,13 +1479,19 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd) const fixed_t radius = FixedMul(mobjinfo[MT_INSTAWHIP].radius, player->mo->scale); size_t i = SIZE_MAX; - if (K_ItemButtonWasDown(player) == true) - { - // Release the button, dude. - return; - } + boolean nearbyThreat = false; // Someone's near enough to worry about, start charging. + boolean attackOpportunity = false; // Someone's close enough to hit! + boolean coastIsClear = true; // Nobody is nearby, let any pending charge go. - if (player->instaWhipCharge || leveltime < starttime || player->spindash) + UINT8 stupidRating = MAXBOTDIFFICULTY - player->botvars.difficulty; + // Weak bots take a second to react on offense. + UINT8 reactiontime = stupidRating; + // Weak bots misjudge their attack range. Purely accurate at Lv.MAX, 250% overestimate at Lv.1 + fixed_t radiusWithError = radius + 3*(radius * stupidRating / MAXBOTDIFFICULTY)/2; + + // Future work: Expand threat range versus fast pursuers. + + if (leveltime < starttime || player->spindash || player->defenseLockout) { // Instashield is on cooldown. return; @@ -1517,18 +1523,41 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd) (player->mo->z - target->mo->z) / 4 ); - if (dist <= radius) + if (dist <= 8 * radius) { - K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * 2); + coastIsClear = false; + } + + if (dist <= 5 * radius) + { + nearbyThreat = true; + } + + if (dist <= (radiusWithError + target->mo->radius)) + { + attackOpportunity = true; + K_ItemConfirmForTarget(player, cmd, target, 1); } } - if (player->botvars.itemconfirm > 10*TICRATE) + if (player->instaWhipCharge) // Already charging, do we stay committed? { - // Use it!! - cmd->buttons |= BT_ATTACK; - //player->botvars.itemconfirm = 0; + cmd->buttons |= BT_ATTACK; // Keep holding, unless... + + // ...there are no attackers that are even distantly threatening... + if (coastIsClear) + cmd->buttons &= ~BT_ATTACK; + + // ...or we're ready to rock. + if (attackOpportunity && player->instaWhipCharge >= (INSTAWHIP_CHARGETIME + reactiontime) && player->botvars.itemconfirm >= reactiontime) + cmd->buttons &= ~BT_ATTACK; } + else // When should we get spooked and start a charge? + { + if (nearbyThreat) + cmd->buttons |= BT_ATTACK; + } + } /*-------------------------------------------------- @@ -1800,7 +1829,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player) --------------------------------------------------*/ void K_UpdateBotGameplayVarsItemUsage(player_t *player) { - if (player->itemflags & IF_USERINGS) + if (player->itemflags & IF_USERINGS && !player->instaWhipCharge) { return; } diff --git a/src/k_kart.c b/src/k_kart.c index 0444d3c11..15bdf2629 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8497,6 +8497,17 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) else if (player->rings < -20) player->rings = -20; + if (cv_kartdebugbotwhip.value) + { + if (player->bot) + { + player->rings = 0; + player->itemtype = 0; + player->itemamount = 0; + player->itemRoulette.active = false; + } + } + if (player->spheres > 40) player->spheres = 40; // where's the < 0 check? see below the following block! @@ -11914,6 +11925,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { S_StartSound(player->mo, sfx_kc50); player->instaWhipCharge = 0; + player->botvars.itemconfirm = 0; } else { From 1f3b5dea0ad0a7df674f8439e61cf7dda9da7125 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:16:25 -0800 Subject: [PATCH 02/32] Remove cv_showinputjoy (unused) --- src/cvars.cpp | 1 - src/doomstat.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 2b439d89e..b7216fe41 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -445,7 +445,6 @@ consvar_t cv_showfocuslost = Player("showfocuslost", "Yes").yes_no(); void R_SetViewSize(void); consvar_t cv_showhud = Player("showhud", "Yes").yes_no().onchange(R_SetViewSize).dont_save(); -consvar_t cv_showinputjoy = Player("showinputjoy", "Off").on_off().dont_save(); consvar_t cv_skybox = Player("skybox", "On").on_off(); // Display song credits diff --git a/src/doomstat.h b/src/doomstat.h index 2515d9e1d..53ac819d9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -927,7 +927,6 @@ extern tic_t g_fast_forward; #include "d_clisrv.h" -extern consvar_t cv_showinputjoy; // display joystick in time attack extern consvar_t cv_forceskin; // force clients to use the server's skin extern consvar_t cv_downloading; // allow clients to downloading WADs. extern consvar_t cv_nettimeout; // SRB2Kart: Advanced server options menu From e44d1759dd4a822cf76479fcfa19054ca1c0971c Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:18:33 -0800 Subject: [PATCH 03/32] Menus/Accessibility: add Input Display option --- src/cvars.cpp | 2 +- src/menus/options-profiles-edit-accessibility.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index b7216fe41..ab121527b 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -359,6 +359,7 @@ consvar_t cv_drawdist_precip = Player("drawdist_precip", "1024").values({ {0, "None"}, }); +consvar_t cv_drawinput = Player("drawinput", "No").yes_no(); consvar_t cv_ffloorclip = Player("ffloorclip", "On").on_off(); consvar_t cv_fpscap = Player("fpscap", "Match refresh rate").values({ @@ -889,7 +890,6 @@ consvar_t cv_debugrender_spriteclip = PlayerCheat("debugrender_spriteclip", "Off consvar_t cv_debugrender_visplanes = PlayerCheat("debugrender_visplanes", "Off").on_off().description("Highlight the number of visplanes"); consvar_t cv_devmode_screen = PlayerCheat("devmode_screen", "1").min_max(1, 4).description("Choose which splitscreen player devmode applies to"); consvar_t cv_drawpickups = PlayerCheat("drawpickups", "Yes").yes_no().description("Hide rings, spheres, item capsules, prison capsules (visual only)"); -consvar_t cv_drawinput = PlayerCheat("drawinput", "No").yes_no().description("Draw turn inputs outside of Record Attack (turn solver debugging)"); void lua_profile_OnChange(void); consvar_t cv_lua_profile = PlayerCheat("lua_profile", "0").values(CV_Unsigned).onchange(lua_profile_OnChange).description("Show hook timings over an average of N tics"); diff --git a/src/menus/options-profiles-edit-accessibility.cpp b/src/menus/options-profiles-edit-accessibility.cpp index 8ace78f24..ca031279c 100644 --- a/src/menus/options-profiles-edit-accessibility.cpp +++ b/src/menus/options-profiles-edit-accessibility.cpp @@ -8,7 +8,7 @@ #include "../m_easing.h" #include "../p_local.h" // cv_tilting -extern "C" consvar_t cv_mindelay; +extern "C" consvar_t cv_mindelay, cv_drawinput; using srb2::Draw; @@ -119,6 +119,9 @@ menuitem_t OPTIONS_ProfileAccessibility[] = { {IT_STRING | IT_CVAR, "Screenshake", "Adjust shake intensity from hazards and offroad.", NULL, {.cvar = &cv_screenshake}, 0, 0}, + + {IT_STRING | IT_CVAR, "Input Display", "Show virtual controller on the HUD.", + NULL, {.cvar = &cv_drawinput}, 0, 0}, }; menu_t OPTIONS_ProfileAccessibilityDef = { From bf65fb0cbfcd0d881605d89f256b6fad35bce5f5 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:20:30 -0800 Subject: [PATCH 04/32] Input Display: fade in quickly at level start --- src/hud/input-display.cpp | 19 ++++++++++++++++++- src/k_hud.cpp | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp index 1acc0c068..864b5dbf8 100644 --- a/src/hud/input-display.cpp +++ b/src/hud/input-display.cpp @@ -7,6 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- +#include #include #include @@ -18,6 +19,8 @@ #include "../i_joy.h" #include "../k_hud.h" #include "../k_kart.h" +#include "../m_easing.h" +#include "../p_tick.h" #include "../v_draw.hpp" using srb2::Draw; @@ -61,6 +64,19 @@ const char* dpad_suffix(const Vec2& v) void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent) { + auto fade_in = [] + { + constexpr tic_t kStart = TICRATE; + constexpr tic_t kDuration = TICRATE/2; + fixed_t f = std::min(std::max(leveltime, kStart) - kStart, kDuration) * FRACUNIT / kDuration; + return Easing_Linear(f, 0, 9); + }; + auto alpha_to_flag = [](int alpha) { return (9 - alpha) << V_ALPHASHIFT; }; + + int alpha = fade_in(); + if (alpha == 0) + return; + const ticcmd_t& cmd = players[displayplayers[pid]].cmd; const boolean analog = (mode == '4' || mode == '5') ? players[displayplayers[pid]].analoginput : false; const std::string prefix = fmt::format("PR{}", mode); @@ -73,7 +89,8 @@ void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boo Draw box = Draw(x, y).flags(flags); - box.flags(transparent ? V_TRANSLUCENT : 0).patch(gfx("CONT")); + box.flags(alpha_to_flag(alpha / (transparent ? 2 : 1))).patch(gfx("CONT")); + box = box.flags(alpha_to_flag(alpha)); Vec2 dpad = local ? Vec2 { diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 2cb721138..848edbc36 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5170,7 +5170,7 @@ static void K_drawInput(void) {282 - BASEVIDWIDTH/2, 52, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 4p right }; INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (R_GetViewNumber() & 1); - INT32 flags = def[k][2] | V_SPLITSCREEN | V_SLIDEIN; + INT32 flags = def[k][2] | V_SPLITSCREEN; char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1); bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr); K_DrawInputDisplay( From bf41f3ec2931cbe7153465de0462abbca986c6f1 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:22:44 -0800 Subject: [PATCH 05/32] Input Display: slide with dialogue box --- src/hud/input-display.cpp | 2 +- src/k_hud.cpp | 5 +++-- src/k_hud.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp index 864b5dbf8..8d82799ac 100644 --- a/src/hud/input-display.cpp +++ b/src/hud/input-display.cpp @@ -62,7 +62,7 @@ const char* dpad_suffix(const Vec2& v) }; // namespace -void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent) +void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent) { auto fade_in = [] { diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 848edbc36..5009f8009 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -51,6 +51,7 @@ #include "g_party.h" #include "k_hitlag.h" #include "g_input.h" +#include "k_dialogue.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -5174,8 +5175,8 @@ static void K_drawInput(void) char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1); bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr); K_DrawInputDisplay( - def[k][0], - def[k][1], + def[k][0] - FixedToFloat(K_GetDialogueSlide(34 * FRACUNIT)), + def[k][1] - FixedToFloat(K_GetDialogueSlide(51 * FRACUNIT)), flags, mode, (local ? G_LocalSplitscreenPartyPosition : G_PartyPosition)(stplyr - players), diff --git a/src/k_hud.h b/src/k_hud.h index 422f3559f..94ee18343 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -63,7 +63,7 @@ void K_DrawKartPositionNumXY( boolean exit, boolean lastLap, boolean losing ); -void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent); +void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent); extern patch_t *kp_capsuletarget_arrow[2][2]; extern patch_t *kp_capsuletarget_icon[2]; From 6988fbcf3e0fb10f859ea946625ad7fd8429c031 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:23:07 -0800 Subject: [PATCH 06/32] Input Display: draw over everything else on the HUD --- src/k_hud.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 5009f8009..7995ef787 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5164,13 +5164,22 @@ static void K_drawKartFirstPerson(void) static void K_drawInput(void) { + UINT8 viewnum = R_GetViewNumber(); + boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam + + if (!cv_drawinput.value && !modeattacking) + return; + + if (stplyr->spectator || freecam || demo.attract) + return; + INT32 def[4][3] = { {247, 156, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 1p {247, 56, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 2p {6, 52, V_SNAPTOBOTTOM | V_SNAPTOLEFT}, // 4p left {282 - BASEVIDWIDTH/2, 52, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 4p right }; - INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (R_GetViewNumber() & 1); + INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (viewnum & 1); INT32 flags = def[k][2] | V_SPLITSCREEN; char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1); bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr); @@ -6063,13 +6072,6 @@ void K_drawKartHUD(void) K_drawRingCounter(gametypeinfoshown); } - if ((modeattacking && !bossinfo.valid) || cv_drawinput.value) - { - // Draw the input UI - if (LUA_HudEnabled(hud_position)) - K_drawInput(); - } - // Draw the item window if (LUA_HudEnabled(hud_item) && !freecam) { @@ -6129,7 +6131,10 @@ void K_drawKartHUD(void) K_drawEmeraldWin(true); if (modeattacking || freecam) // everything after here is MP and debug only + { + K_drawInput(); return; + } if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); @@ -6148,6 +6153,10 @@ void K_drawKartHUD(void) { K_drawSpectatorHUD(true); } + else + { + K_drawInput(); + } if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); From e8523b69f0646038541e8bab23d178601b670487 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Mar 2024 23:22:28 +0000 Subject: [PATCH 07/32] Sealed Star re-ordering (resolves #606) - If emerald not yet collected on that cup, pick the first uncollected emerald, then get the cup's CUPCACHE_SPECIAL with that ID to pick the stage - Already collected emeralds retain their swappage across gamedata saves - Returns to normal order if you get all 7 OR Special Mode is unlocked (chao key? debug? password in modded games? sky's the limit) - Pops up a Message from the Stars telling you the gems have been returned to their natural place - Add-ons will always use their dedicated sealed star, since it's unordered material If it weren't so last minute I could have a better solution for GP Backups, but right now what I've gone for is it always trusts whatever G_GPCupIntoRoundQueue does AS LONG AS THE COURSE ISN'T THE ONE YOU'RE RELOADING INTO. If it IS, then it checks to see if it's exactly what's been saved, and complains (with the generic error message, unfortunately) if it isn't. --- src/g_game.c | 69 +++++++++++++++++++++++++++---- src/g_gamedata.cpp | 42 ++++++++++++++++++- src/g_gamedata.h | 15 ++++++- src/k_menu.h | 4 ++ src/k_menudraw.c | 76 +++++++++++++++++++++++++++++------ src/k_menufunc.c | 34 ++++++++++++++++ src/k_podium.cpp | 75 +++++++++++++++++++++++++++------- src/m_cheat.c | 2 + src/m_cond.c | 3 ++ src/m_cond.h | 7 +++- src/menus/extras-challenges.c | 17 ++++++-- src/p_saveg.c | 8 ++++ src/p_user.c | 24 ++++++++++- 13 files changed, 333 insertions(+), 43 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index aa60e5b1d..5c889ccc3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3932,15 +3932,68 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor // At the end of the Cup is a Rank-restricted treat. // So we append it to the end of the roundqueue. // (as long as it exists, of course!) - cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders) { - G_MapIntoRoundQueue( - cupLevelNum, - G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), - setencore, // if this isn't correct, Got_Mapcmd will fix it - true // Rank-restricted! - ); + // Of course, this last minute game design tweak + // has to make things a little complicated. We + // basically just make sure they're dispensed + // at the intended difficulty sequence until + // you've got them all, at which point they + // become their intended order permanently. + // ~toast 010324 + cupheader_t *emeraldcup = NULL; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + // Standard order. + emeraldcup = cup; + } + else + { + // Determine order from sealedswaps. + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + // If there's pending stars, get them from the associated cup order. + if (i < GDMAX_SEALEDSWAPS) + { + emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + break; + + emeraldcup = emeraldcup->next; + } + } + } + + if (emeraldcup) + { + cupLevelNum = emeraldcup->cachedlevels[CUPCACHE_SPECIAL]; + if (cupLevelNum < nummapheaders) + { + G_MapIntoRoundQueue( + cupLevelNum, + G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel), + setencore, // if this isn't correct, Got_Mapcmd will fix it + true // Rank-restricted! + ); + } + } } if (roundqueue.size == 0) diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 105f997ad..d522ce7ab 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -64,6 +64,7 @@ void srb2::save_ng_gamedata() ng.milestones.majorkeyskipattempted = gamedata->majorkeyskipattempted; ng.milestones.finishedtutorialchallenge = gamedata->finishedtutorialchallenge; ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge; + ng.milestones.sealedswapalerted = gamedata->sealedswapalerted; ng.milestones.gonerlevel = gamedata->gonerlevel; ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup; ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup; @@ -176,7 +177,7 @@ void srb2::save_ng_gamedata() } for (auto cup = kartcupheaders; cup; cup = cup->next) { - if (cup->windata[0].best_placement == 0) + if (cup->windata[0].best_placement == 0 && cup->windata[1].got_emerald == false) { continue; } @@ -229,6 +230,17 @@ void srb2::save_ng_gamedata() ng.cups[cupdata.name] = std::move(cupdata); } + for (int i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + srb2::GamedataSealedSwapJson sealedswap {}; + + cupheader_t* cup = gamedata->sealedswaps[i]; + + sealedswap.name = std::string(cup->name); + + ng.sealedswaps.emplace_back(std::move(sealedswap)); + } + std::string gamedataname_s {gamedatafilename}; fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)}; fs::path tmpsavepath {fmt::format("{}/{}.tmp", srb2home, gamedataname_s)}; @@ -418,6 +430,7 @@ void srb2::load_ng_gamedata() gamedata->majorkeyskipattempted = js.milestones.majorkeyskipattempted; gamedata->finishedtutorialchallenge = js.milestones.finishedtutorialchallenge; gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge; + gamedata->sealedswapalerted = js.milestones.sealedswapalerted; gamedata->gonerlevel = js.milestones.gonerlevel; gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup; gamedata->prisoneggstothispickup = js.prisons.prisoneggstothispickup; @@ -720,6 +733,33 @@ void srb2::load_ng_gamedata() } } + size_t sealedswaps_size = js.sealedswaps.size(); + for (size_t i = 0; i < std::min((size_t)GDMAX_SEALEDSWAPS, sealedswaps_size); i++) + { + cupheader_t* cup = nullptr; + + // Find BASE cups only + for (cup = kartcupheaders; cup; cup = cup->next) + { + if (cup->id >= basenumkartcupheaders) + { + cup = NULL; + break; + } + + std::string cupname = std::string(cup->name); + if (cupname == js.sealedswaps[i].name) + { + break; + } + } + + if (cup) + { + gamedata->sealedswaps[i] = cup; + } + } + M_FinaliseGameData(); } diff --git a/src/g_gamedata.h b/src/g_gamedata.h index d476f2e46..f479da427 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -70,6 +70,7 @@ struct GamedataMilestonesJson final bool majorkeyskipattempted; bool finishedtutorialchallenge; bool enteredtutorialchallenge; + bool sealedswapalerted; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, @@ -81,7 +82,8 @@ struct GamedataMilestonesJson final chaokeytutorial, majorkeyskipattempted, finishedtutorialchallenge, - enteredtutorialchallenge + enteredtutorialchallenge, + sealedswapalerted ) }; @@ -184,6 +186,13 @@ struct GamedataCupJson final NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) }; +struct GamedataSealedSwapJson final +{ + std::string name; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) +}; + struct GamedataJson final { GamedataPlaytimeJson playtime; @@ -203,6 +212,7 @@ struct GamedataJson final std::unordered_map maps; std::vector spraycans; std::unordered_map cups; + std::vector sealedswaps; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataJson, @@ -222,7 +232,8 @@ struct GamedataJson final skins, maps, spraycans, - cups + cups, + sealedswaps ) }; diff --git a/src/k_menu.h b/src/k_menu.h index d84d97414..049559ce4 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -680,6 +680,8 @@ void M_Init(void); void M_PlayMenuJam(void); +boolean M_ConsiderSealedSwapAlert(void); + void M_OpenVirtualKeyboard(boolean gamepad); void M_MenuTypingInput(INT32 key); @@ -1347,6 +1349,8 @@ extern struct challengesmenu_s { boolean chaokeyadd, keywasadded; UINT8 chaokeyhold; + boolean considersealedswapalert; + boolean requestflip; UINT16 unlockcount[CMC_MAX]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index bb9205fe2..0929e714e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2823,6 +2823,57 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi rankx += 19 - (rankw / 2); cupwindata_t *windata = &(cup->windata[difficulty]); + UINT8 emeraldnum = UINT8_MAX; + + if (!noemerald) + { + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order + { + // Standard order. + if (windata->got_emerald == true) + { + emeraldnum = cup->emeraldnum; + } + } + else + { + // Determine order from sealedswaps. + UINT8 i; + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != cup) + continue; + + break; + } + + // If there's pending stars, get them from the associated cup order. + if (i < GDMAX_SEALEDSWAPS) + { + cupheader_t *emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + { + if (emeraldcup->windata[difficulty].got_emerald == true) + emeraldnum = i+1; + break; + } + + emeraldcup = emeraldcup->next; + } + } + } + } + if (windata->best_placement == 0) { if (statsmode) @@ -2832,14 +2883,13 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi V_DrawCharacter((14-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); rankx += 14 + 1; V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); - - if (!noemerald) - { - rankx += 12 + 1; - V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false); - } } - return rankw; + else + { + rankx += 14 + 1; + } + + goto windataemeraldmaybe; } UINT8 *colormap = NULL; @@ -2934,13 +2984,15 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi if (charPat) V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap); +windataemeraldmaybe: + rankx += 12 + 1; if (noemerald) ; - else if (windata->got_emerald == true) + else if (emeraldnum != UINT8_MAX) { - if (cup->emeraldnum == 0) + if (emeraldnum == 0) V_DrawCharacter(rankx+2, ranky+2, '+', false); else { @@ -2948,14 +3000,14 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi if (!flash) { - UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (cup->emeraldnum-1) % 7; + UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (emeraldnum-1) % 7; colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); } const char *emname = va( "%sMAP%c", - (cup->emeraldnum > 7) ? "SUP" : "EME", + (emeraldnum > 7) ? "SUP" : "EME", colormap ? '\0' : 'B' ); @@ -3116,7 +3168,7 @@ void M_DrawCupSelect(void) y += 44; //(8 + 100) - (20 + 44) } - if (windata && windata->best_placement != 0) + if (windata) { M_DrawCupWinData( x, diff --git a/src/k_menufunc.c b/src/k_menufunc.c index efb78969d..7833da1e3 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -560,6 +560,35 @@ void M_PlayMenuJam(void) #undef IsCurrentlyPlaying +boolean M_ConsiderSealedSwapAlert(void) +{ + if (gamedata->sealedswapalerted == true) + return false; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order + { + gamedata->sealedswapalerted = true; + + // Don't make a message if no Sealed Stars have yet been found. + if (gamedata->everseenspecial == false) + return false; + + M_StartMessage( + "Message from the Stars", + "As if called by fate, the Emeralds you've\n" + "collected return to their rightful places...\n" + "\n" + "The Sealed Stars are now ordered via Cups!\n", + NULL, MM_NOTHING, NULL, NULL + ); + + return true; + } + + return false; +} + void M_ValidateRestoreMenu(void) { if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) @@ -629,6 +658,11 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore) M_SetupPlayMenu(-1); PLAY_CharSelectDef.prevMenu = &MainDef; + if (torestore != &MISC_ChallengesDef) + { + M_ConsiderSealedSwapAlert(); + } + return torestore; } diff --git a/src/k_podium.cpp b/src/k_podium.cpp index 103640d02..30e863298 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -83,6 +83,7 @@ static struct podiumData_s sfxenum_t gradeVoice; cupheader_t *cup; + UINT8 emeraldnum; boolean fastForward; @@ -102,6 +103,7 @@ void podiumData_s::Init(void) { rank = grandprixinfo.rank; cup = grandprixinfo.cup; + emeraldnum = cup->emeraldnum; } else { @@ -119,6 +121,7 @@ void podiumData_s::Init(void) cup = cup->next; } + emeraldnum = 0; memset(&rank, 0, sizeof(gpRank_t)); rank.skin = players[consoleplayer].skin; @@ -628,12 +631,7 @@ void podiumData_s::Draw(void) case GPEVENT_SPECIAL: { srb2::Draw drawer_emerald = drawer_gametype; - UINT8 emeraldNum = 0; - - if (cup != nullptr) - { - emeraldNum = cup->emeraldnum; - } + UINT8 emeraldNum = g_podiumData.emeraldnum; boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize); patch_t *emeraldPatch = nullptr; @@ -844,12 +842,7 @@ void podiumData_s::Draw(void) if (rank.specialWon == true) { - UINT8 emeraldNum = 0; - - if (cup != nullptr) - { - emeraldNum = cup->emeraldnum; - } + UINT8 emeraldNum = g_podiumData.emeraldnum; const boolean emeraldBlink = (leveltime & 1); patch_t *emeraldOverlay = nullptr; @@ -1263,6 +1256,60 @@ void K_ResetCeremony(void) return; } + cupheader_t *emeraldcup = NULL; + + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || grandprixinfo.cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + // Standard order. + emeraldcup = grandprixinfo.cup; + } + else + { + // Determine order from sealedswaps. + for (i = 0; i < GDMAX_SEALEDSWAPS; i++) + { + if (gamedata->sealedswaps[i] == NULL) + { + if (g_podiumData.rank.specialWon == true) + { + // First visit! Mark it off. + gamedata->sealedswaps[i] = grandprixinfo.cup; + } + + break; + } + + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + // If there's pending stars, apply them to the new cup order. + if (i < GDMAX_SEALEDSWAPS) + { + emeraldcup = kartcupheaders; + while (emeraldcup) + { + if (emeraldcup->id >= basenumkartcupheaders) + { + emeraldcup = NULL; + break; + } + + if (emeraldcup->emeraldnum == i+1) + break; + + emeraldcup = emeraldcup->next; + } + + g_podiumData.emeraldnum = i+1; + } + } + // Write grade, position, and emerald-having-ness for later sessions! i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; @@ -1292,9 +1339,9 @@ void K_ResetCeremony(void) anymerit = true; } - if (g_podiumData.rank.specialWon == true) + if (g_podiumData.rank.specialWon == true && emeraldcup) { - grandprixinfo.cup->windata[i].got_emerald = true; + emeraldcup->windata[i].got_emerald = true; anymerit = true; } diff --git a/src/m_cheat.c b/src/m_cheat.c index 779d6b526..7a0f1e063 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -96,6 +96,7 @@ static UINT8 cheatf_warp(void) if (success) { gamedata->gonerlevel = GDGONER_DONE; + gamedata->sealedswapalerted = true; G_SetUsedCheats(); } @@ -231,6 +232,7 @@ static UINT8 cheatf_devmode(void) } gamedata->gonerlevel = GDGONER_DONE; + gamedata->sealedswapalerted = true; M_ClearMenus(true); diff --git a/src/m_cond.c b/src/m_cond.c index 345641454..4e35471ed 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -666,6 +666,7 @@ void M_ClearStats(void) gamedata->majorkeyskipattempted = false; gamedata->enteredtutorialchallenge = false; gamedata->finishedtutorialchallenge = false; + gamedata->sealedswapalerted = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -720,6 +721,8 @@ void M_ClearSecrets(void) skincolors[i].cache_spraycan = UINT16_MAX; } + memset(gamedata->sealedswaps, 0, sizeof(gamedata->sealedswaps)); + Z_Free(gamedata->challengegrid); gamedata->challengegrid = NULL; gamedata->challengegridwidth = 0; diff --git a/src/m_cond.h b/src/m_cond.h index 393d046c5..210a09767 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -291,6 +291,7 @@ typedef enum { // This is the largest number of 9s that will fit in UINT32 and UINT16 respectively. #define GDMAX_RINGS 999999999 #define GDMAX_CHAOKEYS 9999 +#define GDMAX_SEALEDSWAPS 7 #define GDCONVERT_ROUNDSTOKEY 14 @@ -371,12 +372,15 @@ struct gamedata_t UINT32 totalrings; UINT32 totaltumbletime; - // Chao Key condition bypass + // CHAO KEYS AND THEIR GENERATION UINT32 pendingkeyrounds; UINT8 pendingkeyroundoffset; UINT16 keyspending; UINT16 chaokeys; + // EMERALD REMAPPING + cupheader_t *sealedswaps[GDMAX_SEALEDSWAPS]; + // SPECIFIC SPECIAL EVENTS boolean everloadedaddon; boolean everfinishedcredits; @@ -387,6 +391,7 @@ struct gamedata_t boolean majorkeyskipattempted; boolean enteredtutorialchallenge; boolean finishedtutorialchallenge; + boolean sealedswapalerted; gdmusic_t musicstate; UINT8 gonerlevel; diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 6fe8ac330..e1df3066b 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -335,6 +335,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) challengesmenu.requestnew = false; challengesmenu.chaokeyadd = false; challengesmenu.keywasadded = false; + challengesmenu.considersealedswapalert = false; challengesmenu.chaokeyhold = 0; challengesmenu.currentunlock = MAXUNLOCKABLES; challengesmenu.unlockcondition = NULL; @@ -668,10 +669,15 @@ void M_ChallengesTick(void) if (challengesmenu.currentunlock < MAXUNLOCKABLES && challengesmenu.unlockanim == UNLOCKTIME) { + unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; + // Unlock animation... also tied directly to the actual unlock! gamedata->unlocked[challengesmenu.currentunlock] = true; M_UpdateUnlockablesAndExtraEmblems(true, true); + if (ref->type == SECRET_SPECIALATTACK) + challengesmenu.considersealedswapalert = true; + // Update shown description just in case..? if (challengesmenu.unlockcondition) Z_Free(challengesmenu.unlockcondition); @@ -682,12 +688,10 @@ void M_ChallengesTick(void) if (challengesmenu.extradata) { - unlockable_t *ref; UINT16 bombcolor; M_UpdateChallengeGridExtraData(challengesmenu.extradata); - ref = &unlockables[challengesmenu.currentunlock]; bombcolor = SKINCOLOR_NONE; if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) @@ -747,7 +751,14 @@ void M_ChallengesTick(void) // Play music the moment control returns. M_PlayMenuJam(); - if (gamedata->chaokeytutorial == false + if (challengesmenu.considersealedswapalert == true + && M_ConsiderSealedSwapAlert() == true) + { + // No keygen tutorial in this case... + // not ideal but at least unlikely to + // get at same time?? :V + } + else if (gamedata->chaokeytutorial == false && challengesmenu.keywasadded == true) { M_ChallengesTutorial(CCTUTORIAL_KEYGEN); diff --git a/src/p_saveg.c b/src/p_saveg.c index e516d809c..8d3b51da8 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6280,6 +6280,14 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) { UINT32 val = READUINT32(save->p); + if (roundqueue.entries[i].rankrestricted && roundqueue.position != i+1) + { + // If this is a Sealed Star that hasn't yet been + // reached, don't be picky about divergance. Just + // use the base game without question. ~toast 010324 + continue; + } + mapnum = roundqueue.entries[i].mapnum; if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL) { diff --git a/src/p_user.c b/src/p_user.c index 815551626..7adfa49ea 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -296,9 +296,29 @@ UINT8 P_GetNextEmerald(void) { cupheader_t *cup = NULL; - if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true && grandprixinfo.cup) { - cup = grandprixinfo.cup; + if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found + || grandprixinfo.cup->id >= basenumkartcupheaders // custom content + || M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order + { + cup = grandprixinfo.cup; + } + else + { + // Determine order from sealedswaps. + UINT8 i; + for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++) + { + if (gamedata->sealedswaps[i] != grandprixinfo.cup) + continue; + + // Repeat visit, grab the same ID. + break; + } + + return i+1; + } } if (cup == NULL) From 616d2bb87a51c6cfb594b693dd8cfeb87f4b2a48 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 3 Mar 2024 00:22:22 +0000 Subject: [PATCH 08/32] Menu Messages only animate when menuwipe is stopped Reduces the amount of moving parts we have to worry about. Fire M_StartMessage at the same time you change a menu without worry! --- src/menus/transient/message-box.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 72bc50731..ad901ef11 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -116,6 +116,9 @@ void M_StopMessage(INT32 choice) boolean M_MenuMessageTick(void) { + if (menuwipe) + return false; + if (menumessage.closing) { if (menumessage.closing > MENUMESSAGECLOSE) From d4ab1a32725854d14d97c43561085f055f39ac7f Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 00:27:25 -0800 Subject: [PATCH 09/32] Input Display: force on in Tutorials --- src/k_hud.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 7995ef787..e7b2f2c6b 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5167,7 +5167,7 @@ static void K_drawInput(void) UINT8 viewnum = R_GetViewNumber(); boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam - if (!cv_drawinput.value && !modeattacking) + if (!cv_drawinput.value && !modeattacking && gametype != GT_TUTORIAL) return; if (stplyr->spectator || freecam || demo.attract) From 8524401c03dafd27f235803a5b021d267e6f890c Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 18:39:29 -0800 Subject: [PATCH 10/32] Scale Gachabom and Ballhog with Grow/Shrink --- src/k_kart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 3f9dfa551..8be8c9d35 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12406,6 +12406,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { INT32 numhogs = min((player->ballhogcharge / BALLHOGINCREMENT), player->itemamount); + K_SetItemOut(player); // need this to set itemscale + if (numhogs <= 0) { // no tapfire scams @@ -12434,6 +12436,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) K_PlayAttackTaunt(player->mo); } + K_UnsetItemOut(player); player->ballhogcharge = 0; player->itemflags &= ~IF_HOLDREADY; player->botvars.itemconfirm = 0; @@ -12772,7 +12775,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) case KITEM_GACHABOM: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { + K_SetItemOut(player); // need this to set itemscale K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0); + K_UnsetItemOut(player); K_PlayAttackTaunt(player->mo); player->itemamount--; player->roundconditions.gachabom_miser = ( From 1ebac1f553239166c16992cd3dda8f58b8bc3c9f Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 18:46:56 -0800 Subject: [PATCH 11/32] Scale SPB with Grow/Shrink --- src/k_kart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 8be8c9d35..6096c03a9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12448,7 +12448,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; + K_SetItemOut(player); K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0); + K_UnsetItemOut(player); K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } From f917fbd199414d3a6417a0e7e2108e348521e5eb Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 20:41:25 -0800 Subject: [PATCH 12/32] Ignore patch X offset when drawing fonts We don't typically use offsets for font graphics anyway. But I had to add offsets to the console font specifically, because the console doesn't render text with the normal drawing functions. --- src/v_video.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v_video.cpp b/src/v_video.cpp index 1db7bd01c..06991d0e5 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -2702,9 +2702,11 @@ void V_DrawStringScaled( c -= font->start; if (V_CharacterValid(font, c) == true) { + // Remove offsets from patch + fixed_t patchxofs = SHORT (font->font[c]->leftoffset) * dupx * FRACUNIT; cw = SHORT (font->font[c]->width) * dupx; cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); - V_DrawFixedPatch(cx + cxoff, cy + cyoff, scale, + V_DrawFixedPatch(cx + cxoff + patchxofs, cy + cyoff, scale, flags, font->font[c], colormap); cx += cw; } From ffa698cc6eb3eee3c67df1f677e3f27e03b8f0e1 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 20:47:33 -0800 Subject: [PATCH 13/32] Set Gachabom explosion color to player skincolor --- src/objects/gachabom-rebound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objects/gachabom-rebound.cpp b/src/objects/gachabom-rebound.cpp index 91e1f23c5..35c2bf7e7 100644 --- a/src/objects/gachabom-rebound.cpp +++ b/src/objects/gachabom-rebound.cpp @@ -228,7 +228,7 @@ void Obj_SpawnGachaBomRebound(mobj_t* source, mobj_t* target) { mobj_t *x = P_SpawnMobjFromMobjUnscaled(source, 0, 0, target->height / 2, MT_GACHABOM_REBOUND); - x->color = target->color; + x->color = target->player ? target->player->skincolor : target->color; x->angle = angle; if (!(gametyperules & GTR_BUMPERS) || battleprisons) From cd081ecc47f0758c5be1532cc540cf07ea5098f1 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 21:11:24 -0800 Subject: [PATCH 14/32] SPB explosion ignores Grow --- src/p_inter.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index 11e32dbff..1889946e8 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2935,7 +2935,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { sfx = sfx_invind; } - else if (K_IsBigger(target, inflictor) == true) + else if (K_IsBigger(target, inflictor) == true && + // SPB bypasses grow (K_IsBigger handles NULL check) + (type != DMG_EXPLODE || inflictor->type != MT_SPBEXPLOSION || !inflictor->movefactor)) { sfx = sfx_grownd; } From 3ea57bdf2ff2eea76e6619851bfec7dc1dfa8a4a Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 22:23:42 -0800 Subject: [PATCH 15/32] CONS_Printf: lock more of the function behind mutex --- src/console.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/console.c b/src/console.c index bf2dd069f..282f833a4 100644 --- a/src/console.c +++ b/src/console.c @@ -1512,6 +1512,8 @@ void CONS_Printf(const char *fmt, ...) vsprintf(txt, fmt, argptr); va_end(argptr); + Lock_state(); + // echo console prints to log file DEBFILE(txt); @@ -1521,8 +1523,6 @@ void CONS_Printf(const char *fmt, ...) CON_LogMessage(txt); - Lock_state(); - // make sure new text is visible con_scrollup = 0; From 45aef83fc1be48cb971f8b544daa6ad8c5c724e1 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 22:24:27 -0800 Subject: [PATCH 16/32] debugrender_portal, debugrender_visplanes: fix in splitscreen - Fix debugrender_visplanes crashing - Fix debugrender_portal drawing everything on P1 viewport --- src/r_main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/r_main.cpp b/src/r_main.cpp index 365bd6674..e82da5cc6 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -1617,7 +1617,11 @@ void R_RenderPlayerView(void) { if (top > bot) std::swap(top, bot); - UINT8* p = &screens[0][x + top * vid.width]; + if (top < 0) + top = 0; + if (bot > viewheight-1) + bot = viewheight-1; + UINT8* p = &topleft[x + top * vid.width]; while (top <= bot) { *p = 35; @@ -1634,7 +1638,7 @@ void R_RenderPlayerView(void) INT32 bottom = pl->bottom[pl->minx]; span(pl->minx, top, bottom); span(pl->maxx, pl->top[pl->maxx], pl->bottom[pl->maxx]); - for (INT32 x = pl->minx + 1; x < pl->maxx; ++x) + for (INT32 x = pl->minx + 1; x < std::min(pl->maxx, viewwidth); ++x) { INT32 new_top = pl->top[x]; INT32 new_bottom = pl->bottom[x]; @@ -1668,14 +1672,14 @@ void R_RenderPlayerView(void) INT32 width = (portal->end - portal->start); INT32 i; - for (i = 0; i < width; ++i) + for (i = 0; i < std::min(width, viewwidth); ++i) { INT32 yl = std::max(portal->ceilingclip[i] + 1, 0); INT32 yh = std::min(static_cast(portal->floorclip[i]), viewheight); for (; yl < yh; ++yl) { - screens[0][portal->start + i + (yl * vid.width)] = pal; + topleft[portal->start + i + (yl * vid.width)] = pal; } } From d2d9edb7e3c1e128b0776ef789dbd47f26b3560b Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 2 Mar 2024 22:25:46 -0800 Subject: [PATCH 17/32] R_MapPlane, R_MapTiltedPlane: return on invalid coordinates, debug print in devmode render --- src/r_plane.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/r_plane.cpp b/src/r_plane.cpp index fba7de8a3..0d0dd68e8 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -126,6 +126,18 @@ static void R_UpdatePlaneRipple(drawspandata_t* ds) static void R_SetSlopePlaneVectors(drawspandata_t* ds, visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff); +static bool R_CheckMapPlane(const char* funcname, INT32 y, INT32 x1, INT32 x2) +{ + if (x1 == x2) + return true; + + if (x1 < x2 && x1 >= 0 && x2 < viewwidth && y >= 0 && y < viewheight) + return true; + + CONS_Debug(DBG_RENDER, "%s: x1=%d, x2=%d at y=%d\n", funcname, x1, x2, y); + return false; +} + static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, INT32 x1, INT32 x2, boolean allow_parallel) { ZoneScoped; @@ -133,13 +145,8 @@ static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, IN fixed_t distance = 0, span; size_t pindex; -#ifdef RANGECHECK - if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight) - I_Error("R_MapPlane: %d, %d at %d", x1, x2, y); -#endif - - if (x1 >= vid.width) - x1 = vid.width - 1; + if (!R_CheckMapPlane(__func__, y, x1, x2)) + return; angle = (ds->currentplane->viewangle + ds->currentplane->plangle)>>ANGLETOFINESHIFT; planecos = FINECOSINE(angle); @@ -216,13 +223,9 @@ static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, IN static void R_MapTiltedPlane(drawspandata_t *ds, void(*spanfunc)(drawspandata_t*), INT32 y, INT32 x1, INT32 x2, boolean allow_parallel) { ZoneScoped; -#ifdef RANGECHECK - if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y >= viewheight || y < 0) - I_Error("R_MapTiltedPlane: %d, %d at %d", x1, x2, y); -#endif - if (x1 >= vid.width) - x1 = vid.width - 1; + if (!R_CheckMapPlane(__func__, y, x1, x2)) + return; // Water ripple effect if (ds->planeripple.active) From ba0a40c1b1709769f4a010567b207c782a6d75ba Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 3 Mar 2024 01:22:46 -0700 Subject: [PATCH 18/32] Don't allow spindash above max power --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index a4ef754e0..3eda8a567 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11364,7 +11364,7 @@ static void K_KartSpindash(player_t *player) if (player->spindash > 0 && (buttons & (BT_DRIFT|BT_BRAKE|BT_ACCELERATE)) != (BT_DRIFT|BT_BRAKE|BT_ACCELERATE)) { - player->spindashspeed = (player->spindash * FRACUNIT) / MAXCHARGETIME; + player->spindashspeed = (min(player->spindash, MAXCHARGETIME) * FRACUNIT) / MAXCHARGETIME; player->spindashboost = TICRATE; // if spindash was charged enough, give a small thrust. From db7892d15a1b33189d891392413da2acad1a1569 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 3 Mar 2024 00:32:38 -0800 Subject: [PATCH 19/32] Comment out SCRAMBLE_REMOVED - Scramble can help point out memory leaks from mobjs never being freed - However it can also cause false positive crashes, since there is sometimes a 1 or 2 tic window between when P_RemoveMobj is called and all pointers to that mobj are set to NULL - Presumably we have code that NULL checks but does not check P_MobjWasRemoved and such instances may be hard to find so it's just easier to not scramble --- src/p_mobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 12bf519bf..a686fa609 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11290,7 +11290,7 @@ tic_t itemrespawntime[ITEMQUESIZE]; size_t iquehead, iquetail; #ifdef PARANOIA -#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed +//#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed #endif void P_RemoveMobj(mobj_t *mobj) { From 0d4f42cf809dccc9327a331a5029815b68b47ccb Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 3 Mar 2024 03:03:23 -0800 Subject: [PATCH 20/32] Add debuglapcheat command, disables lap cheat prevention and prints a console message --- src/cvars.cpp | 1 + src/k_kart.c | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 809216600..1aa0d9a0b 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -804,6 +804,7 @@ consvar_t cv_capsuletest = OnlineCheat("capsuletest", "Off").values(capsuletest_ consvar_t cv_debugcheese = OnlineCheat("debugcheese", "Off").on_off().description("Disable checks that prevent farming item boxes"); consvar_t cv_debugencorevote = OnlineCheat("debugencorevote", "Off").on_off().description("Force encore choice to appear on vote screen"); +consvar_t cv_debuglapcheat = OnlineCheat("debuglapcheat", "Off").on_off().description("Permit far waypoint jumps and disable lap cheat prevention"); consvar_t cv_forcebots = OnlineCheat("forcebots", "No").yes_no().description("Force bots to appear, even in wrong game modes"); void ForceSkin_OnChange(void); diff --git a/src/k_kart.c b/src/k_kart.c index a4ef754e0..f9e3a17ea 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9935,18 +9935,27 @@ static void K_UpdatePlayerWaypoints(player_t *const player) UINT32 delta = u32_delta(player->distancetofinish, player->distancetofinishprev); if (player->respawn.state == RESPAWNST_NONE && delta > distance_threshold && old_currentwaypoint != NULL) { - CONS_Debug(DBG_GAMELOGIC, "Player %s: waypoint ID %d too far away (%u > %u)\n", - sizeu1(player - players), K_GetWaypointID(player->nextwaypoint), delta, distance_threshold); + extern consvar_t cv_debuglapcheat; +#define debug_args "Player %s: waypoint ID %d too far away (%u > %u)\n", \ + sizeu1(player - players), K_GetWaypointID(player->nextwaypoint), delta, distance_threshold + if (cv_debuglapcheat.value) + CONS_Printf(debug_args); + else + CONS_Debug(DBG_GAMELOGIC, debug_args); +#undef debug_args - // Distance jump is too great, keep the old waypoints and old distance. - player->currentwaypoint = old_currentwaypoint; - player->nextwaypoint = old_nextwaypoint; - player->distancetofinish = player->distancetofinishprev; - - // Start the auto respawn timer when the distance jumps. - if (!player->bigwaypointgap) + if (!cv_debuglapcheat.value) { - player->bigwaypointgap = 35; + // Distance jump is too great, keep the old waypoints and old distance. + player->currentwaypoint = old_currentwaypoint; + player->nextwaypoint = old_nextwaypoint; + player->distancetofinish = player->distancetofinishprev; + + // Start the auto respawn timer when the distance jumps. + if (!player->bigwaypointgap) + { + player->bigwaypointgap = 35; + } } } else From 9f982a574ed1063fd378641461d360d2be808227 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 3 Mar 2024 03:04:24 -0800 Subject: [PATCH 21/32] Add LapsPerSection property to map headers, fixes finish line distance in hybrid section-circuit maps - It is possible to have a circuit map separated into sections, where there are multiple "laps" before making a full loop - Normally the lap count is used to multiply the finish line distance, but it fails for these maps - The LapsPerSection property fixes the calculation by specifying how many laps make up a full loop - Default = 1 lap (most maps) --- src/deh_soc.c | 2 ++ src/doomstat.h | 1 + src/k_kart.c | 5 +++-- src/lua_maplib.c | 2 ++ src/p_setup.cpp | 1 + 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 88755ead8..8817ad40b 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1299,6 +1299,8 @@ void readlevelheader(MYFILE *f, char * name) mapheaderinfo[num]->encorepal = (UINT16)i; else if (fastcmp(word, "NUMLAPS")) mapheaderinfo[num]->numlaps = (UINT8)i; + else if (fastcmp(word, "LAPSPERSECTION")) + mapheaderinfo[num]->lapspersection = max((UINT8)i, 1u); else if (fastcmp(word, "SKYBOXSCALE")) mapheaderinfo[num]->skybox_scalex = mapheaderinfo[num]->skybox_scaley = mapheaderinfo[num]->skybox_scalez = (INT16)i; else if (fastcmp(word, "SKYBOXSCALEX")) diff --git a/src/doomstat.h b/src/doomstat.h index 53ac819d9..c74dda7ac 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -509,6 +509,7 @@ struct mapheader_t UINT16 levelflags; ///< LF_flags: merged booleans into one UINT16 for space, see below UINT32 typeoflevel; ///< Combination of typeoflevel flags. UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden. + UINT8 lapspersection; ///< Number of laps per section in hybrid section-circuit maps. fixed_t gravity; ///< Map-wide gravity. char relevantskin[SKINNAMESIZE+1]; ///< Skin to use for tutorial (if not provided, uses Eggman.) diff --git a/src/k_kart.c b/src/k_kart.c index f9e3a17ea..66865c8b4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -9891,9 +9891,10 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player) // correct we need to add to it the length of the entire circuit multiplied by the number of laps // left after this one. This will give us the total distance to the finish line, and allow item // distance calculation to work easily - if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) + const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; + if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { - const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps); + const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 7ec807c87..9117858d1 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2535,6 +2535,8 @@ static int mapheaderinfo_get(lua_State *L) lua_pushinteger(L, header->palette); else if (fastcmp(field,"numlaps")) lua_pushinteger(L, header->numlaps); + else if (fastcmp(field,"lapspersection")) + lua_pushinteger(L, header->lapspersection); else if (fastcmp(field,"levelselect")) lua_pushinteger(L, header->levelselect); else if (fastcmp(field,"levelflags")) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 27dd80043..f73355636 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -458,6 +458,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->palette = UINT16_MAX; mapheaderinfo[num]->encorepal = UINT16_MAX; mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT; + mapheaderinfo[num]->lapspersection = 1; mapheaderinfo[num]->levelselect = 0; mapheaderinfo[num]->levelflags = 0; mapheaderinfo[num]->menuflags = 0; From f270ac5579c6e3ba4cb441c82dd8db47219f872e Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 19:41:42 -0800 Subject: [PATCH 22/32] Virtual Keyboard: use callback function to get/set cvar string --- src/k_menu.h | 4 +++- src/k_menufunc.c | 11 ++++++++++- src/menus/transient/virtual-keyboard.c | 7 ++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 049559ce4..dc5b5d095 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -542,6 +542,7 @@ extern INT32 menuKey; // keyboard key pressed for menu extern INT16 virtualKeyboard[5][NUMVIRTUALKEYSINROW]; extern INT16 shift_virtualKeyboard[5][NUMVIRTUALKEYSINROW]; +typedef const char *(*vkb_query_fn_t)(const char *replace); extern struct menutyping_s { boolean active; // Active @@ -554,6 +555,7 @@ extern struct menutyping_s boolean keyboardcapslock; boolean keyboardshift; + vkb_query_fn_t queryfn; // callback on open and close char cache[MAXSTRINGLENGTH]; // cached string } menutyping; @@ -682,7 +684,7 @@ void M_PlayMenuJam(void); boolean M_ConsiderSealedSwapAlert(void); -void M_OpenVirtualKeyboard(boolean gamepad); +void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn); void M_MenuTypingInput(INT32 key); void M_QuitResponse(INT32 ch); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 7833da1e3..aa3c3b674 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -188,6 +188,14 @@ static void M_ChangeCvar(INT32 choice) M_ChangeCvarDirect(choice, currentMenu->menuitems[itemOn].itemaction.cvar); } +static const char *M_QueryCvarAction(const char *replace) +{ + consvar_t *cvar = currentMenu->menuitems[itemOn].itemaction.cvar; + if (replace) + CV_Set(cvar, replace); + return cvar->string; +} + boolean M_NextOpt(void) { INT16 oldItemOn = itemOn; // prevent infinite loop @@ -1124,7 +1132,8 @@ static void M_HandleMenuInput(void) // If we're hovering over a IT_CV_STRING option, pressing A/X opens the typing submenu if (M_MenuConfirmPressed(pid)) { - M_OpenVirtualKeyboard(thisMenuKey == -1); // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller. + // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller. + M_OpenVirtualKeyboard(thisMenuKey == -1, M_QueryCvarAction); return; } else if (M_MenuExtraPressed(pid)) diff --git a/src/menus/transient/virtual-keyboard.c b/src/menus/transient/virtual-keyboard.c index 81afaed8e..6722e48ca 100644 --- a/src/menus/transient/virtual-keyboard.c +++ b/src/menus/transient/virtual-keyboard.c @@ -180,7 +180,7 @@ static void M_ToggleVirtualShift(void) static void M_CloseVirtualKeyboard(void) { menutyping.menutypingclose = true; // close menu. - CV_Set(currentMenu->menuitems[itemOn].itemaction.cvar, menutyping.cache); + menutyping.queryfn(menutyping.cache); } static boolean M_IsTypingKey(INT32 key) @@ -405,11 +405,12 @@ void M_MenuTypingInput(INT32 key) } } -void M_OpenVirtualKeyboard(boolean gamepad) +void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn) { menutyping.keyboardtyping = !gamepad; menutyping.active = true; menutyping.menutypingclose = false; - strlcpy(menutyping.cache, currentMenu->menuitems[itemOn].itemaction.cvar->string, MAXSTRINGLENGTH); + menutyping.queryfn = queryfn; + strlcpy(menutyping.cache, queryfn(NULL), MAXSTRINGLENGTH); } From 7d01c3046b1e81d224ae319e6d71f9eaec54b80a Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:22:56 -0800 Subject: [PATCH 23/32] M_OpenVirtualKeyboard: add optional dummymenu argument - Virtual Keyboard can switch to a temporary menu while typing - It will switch back to the previous menu after it's closed - This functionality can be used even if the menu isn't already open - It will close the menu and return to gameplay when the Virtual Keyboard is closed --- src/k_menu.h | 4 +++- src/k_menufunc.c | 4 ++-- src/menus/transient/virtual-keyboard.c | 28 ++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index dc5b5d095..fdc7c2c88 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -556,6 +556,7 @@ extern struct menutyping_s boolean keyboardshift; vkb_query_fn_t queryfn; // callback on open and close + menu_t *dummymenu; char cache[MAXSTRINGLENGTH]; // cached string } menutyping; @@ -684,7 +685,8 @@ void M_PlayMenuJam(void); boolean M_ConsiderSealedSwapAlert(void); -void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn); +void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn, menu_t *dummymenu); +void M_AbortVirtualKeyboard(void); void M_MenuTypingInput(INT32 key); void M_QuitResponse(INT32 ch); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index aa3c3b674..e7507aec3 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -839,7 +839,7 @@ void M_ClearMenus(boolean callexitmenufunc) D_StartTitle(); } - menutyping.active = false; + M_AbortVirtualKeyboard(); menumessage.active = false; menuactive = false; @@ -1133,7 +1133,7 @@ static void M_HandleMenuInput(void) if (M_MenuConfirmPressed(pid)) { // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller. - M_OpenVirtualKeyboard(thisMenuKey == -1, M_QueryCvarAction); + M_OpenVirtualKeyboard(thisMenuKey == -1, M_QueryCvarAction, NULL); return; } else if (M_MenuExtraPressed(pid)) diff --git a/src/menus/transient/virtual-keyboard.c b/src/menus/transient/virtual-keyboard.c index 6722e48ca..15e0e9485 100644 --- a/src/menus/transient/virtual-keyboard.c +++ b/src/menus/transient/virtual-keyboard.c @@ -183,6 +183,16 @@ static void M_CloseVirtualKeyboard(void) menutyping.queryfn(menutyping.cache); } +void M_AbortVirtualKeyboard(void) +{ + if (!menutyping.active) + return; + + menutyping.active = false; + if (currentMenu == menutyping.dummymenu) + M_GoBack(0); +} + static boolean M_IsTypingKey(INT32 key) { return key == KEY_BACKSPACE || key == KEY_ENTER @@ -202,7 +212,7 @@ void M_MenuTypingInput(INT32 key) // Closing menutyping.menutypingfade--; if (!menutyping.menutypingfade) - menutyping.active = false; + M_AbortVirtualKeyboard(); return; // prevent inputs while closing the menu. } @@ -405,12 +415,26 @@ void M_MenuTypingInput(INT32 key) } } -void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn) +void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn, menu_t *dummymenu) { menutyping.keyboardtyping = !gamepad; menutyping.active = true; menutyping.menutypingclose = false; menutyping.queryfn = queryfn; + menutyping.dummymenu = dummymenu; strlcpy(menutyping.cache, queryfn(NULL), MAXSTRINGLENGTH); + + if (dummymenu) + { + if (!menuactive) + { + M_StartControlPanel(); + dummymenu->prevMenu = NULL; + } + else + dummymenu->prevMenu = currentMenu; + + M_SetupNextMenu(dummymenu, true); + } } From 8fa01ee558a05f9e35f8e68abbb679f6e11d84fb Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:30:32 -0800 Subject: [PATCH 24/32] Replays: use Virtual Keyboard for title entry - This shortens the maximum title length from 64 to 31 characters --- src/d_main.cpp | 6 ---- src/g_demo.cpp | 88 +++++++++++++++++++++---------------------------- src/g_demo.h | 3 -- src/st_stuff.c | 34 ------------------- src/st_stuff.h | 3 -- src/y_inter.cpp | 4 --- 6 files changed, 38 insertions(+), 100 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 793c14013..19fd9c1db 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -278,12 +278,6 @@ void D_ProcessEvents(void) HandleGamepadDeviceEvents(ev); - if (demo.savemode == demovars_s::DSM_TITLEENTRY) - { - if (G_DemoTitleResponder(ev)) - continue; - } - // console input #ifdef HAVE_THREADS I_lock_mutex(&con_mutex); diff --git a/src/g_demo.cpp b/src/g_demo.cpp index eac0b1989..71cef6e81 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -65,6 +65,30 @@ #include "k_credits.h" #include "k_grandprix.h" +static menuitem_t TitleEntry[] = +{ + {IT_NOTHING | IT_SPACE, "Save Replay", NULL, + NULL, {NULL}, 0, 0}, +}; + +static menu_t TitleEntryDef = { + sizeof (TitleEntry) / sizeof (menuitem_t), + NULL, + 0, + TitleEntry, + 0, 0, + 0, 0, + MBF_SOUNDLESS, + NULL, + 0, 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + boolean nodrawers; // for comparative timing purposes boolean noblit; // for comparative timing purposes tic_t demostarttime; // for comparative timing purposes @@ -3992,6 +4016,9 @@ void G_SaveDemo(void) UINT8 i; #endif + if (currentMenu == &TitleEntryDef) + M_ClearMenus(true); + // Ensure extrainfo pointer is always available, even if no info is present. if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) { @@ -4076,55 +4103,6 @@ void G_SaveDemo(void) } } -boolean G_DemoTitleResponder(event_t *ev) -{ - size_t len; - INT32 ch; - - if (ev->type != ev_keydown) - return false; - - ch = (INT32)ev->data1; - - // Only ESC and non-keyboard keys abort connection - if (ch == KEY_ESCAPE) - { - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? demovars_s::DSM_WILLAUTOSAVE : demovars_s::DSM_NOTSAVING; - return true; - } - - if (ch == KEY_ENTER || ch >= NUMKEYS) - { - demo.savemode = demovars_s::DSM_WILLSAVE; - return true; - } - - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) - || ch == ' ') // Allow spaces, of course - { - len = strlen(demo.titlename); - if (len < 64) - { - demo.titlename[len+1] = 0; - demo.titlename[len] = CON_ShiftChar(ch); - } - } - else if (ch == KEY_BACKSPACE) - { - if (shiftdown) - memset(demo.titlename, 0, sizeof(demo.titlename)); - else - { - len = strlen(demo.titlename); - - if (len > 0) - demo.titlename[len-1] = 0; - } - } - - return true; -} - boolean G_CheckDemoTitleEntry(void) { if (menuactive || chat_on) @@ -4133,7 +4111,17 @@ boolean G_CheckDemoTitleEntry(void) if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0)) return false; - demo.savemode = demovars_s::DSM_TITLEENTRY; + demo.savemode = demovars_s::DSM_WILLSAVE; + M_OpenVirtualKeyboard( + false, + [](const char* replace) -> const char* + { + if (replace) + strlcpy(demo.titlename, replace, sizeof demo.titlename); + return demo.titlename; + }, + &TitleEntryDef + ); return true; } diff --git a/src/g_demo.h b/src/g_demo.h index 4e89b24ef..85b4d65f7 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -93,7 +93,6 @@ struct demovars_s { enum { DSM_NOTSAVING, DSM_WILLAUTOSAVE, - DSM_TITLEENTRY, DSM_WILLSAVE, DSM_SAVED } savemode; @@ -232,8 +231,6 @@ void G_DeferedPlayDemo(const char *demo); void G_SaveDemo(void); -boolean G_DemoTitleResponder(event_t *ev); - boolean G_CheckDemoTitleEntry(void); typedef enum diff --git a/src/st_stuff.c b/src/st_stuff.c index d65d19ea1..df86acbb8 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1303,36 +1303,6 @@ static void ST_overlayDrawer(void) K_DrawMidVote(); } -void ST_DrawDemoTitleEntry(void) -{ - static UINT8 anim = 0; - char *nametodraw; - - anim++; - anim %= 8; - - nametodraw = demo.titlename; - while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8) - nametodraw++; - -#define x (BASEVIDWIDTH/2 - 139) -#define y (BASEVIDHEIGHT/2) - M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); - V_DrawString(x + 8, y + 12, 0, nametodraw); - if (anim < 4) - V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12, - '_' | 0x80, false); - - M_DrawTextBox(x + 30, y - 24, 26, 1); - V_DrawString(x + 38, y - 16, 0, "Enter the name of the replay."); - - M_DrawTextBox(x + 50, y + 20, 20, 1); - V_DrawThinString(x + 58, y + 28, 0, "Escape - Cancel"); - V_DrawRightAlignedThinString(x + 220, y + 28, 0, "Enter - Confirm"); -#undef x -#undef y -} - // MayonakaStatic: draw Midnight Channel's TV-like borders static void ST_MayonakaStatic(void) { @@ -1640,10 +1610,6 @@ void ST_Drawer(void) V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved."); break; - case DSM_TITLEENTRY: - ST_DrawDemoTitleEntry(); - break; - default: // Don't render anything break; } diff --git a/src/st_stuff.h b/src/st_stuff.h index a48eaf213..72289d652 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -33,9 +33,6 @@ extern "C" { // Called by main loop. void ST_Ticker(boolean run); -// Called when naming a replay. -void ST_DrawDemoTitleEntry(void); - #ifdef HAVE_DISCORDRPC // Called when you have Discord asks void ST_AskToJoinEnvelope(void); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 1822debde..545627e9f 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1710,10 +1710,6 @@ finalcounter: V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!"); break; - case demovars_s::DSM_TITLEENTRY: - ST_DrawDemoTitleEntry(); - break; - default: // Don't render any text here break; } From e05daf0f1163699d15eb8aa5bdb2b8627de26ffe Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:31:30 -0800 Subject: [PATCH 25/32] M_GoBack: do not play sound effect on MBF_SOUNDLESS menus --- src/k_menufunc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index e7507aec3..b5335a87c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -926,6 +926,8 @@ void M_SetupNextMenu(menu_t *menudef, boolean notransition) void M_GoBack(INT32 choice) { + const INT16 behaviourflags = currentMenu->behaviourflags; + (void)choice; noFurtherInput = true; @@ -948,7 +950,8 @@ void M_GoBack(INT32 choice) else // No returning to the title screen. M_QuitSRB2(-1); - S_StartSound(NULL, sfx_s3k5b); + if (!(behaviourflags & MBF_SOUNDLESS)) + S_StartSound(NULL, sfx_s3k5b); } // From 153ffa471799366c0a272d42216a70aaae775a9d Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:33:08 -0800 Subject: [PATCH 26/32] Replays: do not save file at intermission start - Postpone saving until intermission ends --- src/y_inter.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 545627e9f..fe380119b 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1756,9 +1756,6 @@ void Y_Ticker(void) replayprompttic++; G_CheckDemoTitleEntry(); } - - if (demo.savemode == demovars_s::DSM_WILLSAVE || demo.savemode == demovars_s::DSM_WILLAUTOSAVE) - G_SaveDemo(); } // Check for pause or menu up in single player From 983fb4c53c6b43f02254293d63771404cf591d83 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:38:55 -0800 Subject: [PATCH 27/32] Replays: merge HUD and intermission drawing functions for "Save replay" prompt --- src/st_stuff.c | 42 +++++++++++++++++++++++++----------------- src/st_stuff.h | 1 + src/y_inter.cpp | 21 +-------------------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/st_stuff.c b/src/st_stuff.c index df86acbb8..b2a2bbc41 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1457,6 +1457,30 @@ void ST_DrawServerSplash(boolean timelimited) } } +void ST_DrawSaveReplayHint(INT32 flags) +{ + const char *text = ""; + switch (demo.savemode) + { + case DSM_NOTSAVING: + text = "\xAB" "or " "\xAE" "Save replay"; + break; + + case DSM_WILLAUTOSAVE: + text = "Replay will be saved. \xAB" "Change title"; + break; + + case DSM_WILLSAVE: + text = "Replay will be saved."; + break; + + case DSM_SAVED: + text = "Replay saved!"; + break; + } + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, text); +} + static fixed_t ST_CalculateFadeIn(player_t *player) { const tic_t length = TICRATE/4; @@ -1596,22 +1620,6 @@ void ST_Drawer(void) INT32 flags = V_SNAPTOTOP | V_SNAPTORIGHT | (Easing_Linear(min(t, fadeLength) * FRACUNIT / fadeLength, 9, 0) << V_ALPHASHIFT); - switch (demo.savemode) - { - case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "\xAB" "or " "\xAE" "Save replay"); - break; - - case DSM_WILLAUTOSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved. \xAB" "Change title"); - break; - - case DSM_WILLSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved."); - break; - - default: // Don't render anything - break; - } + ST_DrawSaveReplayHint(flags); } } diff --git a/src/st_stuff.h b/src/st_stuff.h index 72289d652..23eecb034 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -76,6 +76,7 @@ extern tic_t lt_exitticker, lt_endtime; extern tic_t lt_fade; void ST_DrawServerSplash(boolean timelimited); +void ST_DrawSaveReplayHint(INT32 flags); // return if player a is in the same team as player b boolean ST_SameTeam(player_t *a, player_t *b); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index fe380119b..13a5e6ba1 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1693,26 +1693,7 @@ finalcounter: { if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == demovars_s::DSM_SAVED) && !demo.playback) { - switch (demo.savemode) - { - case demovars_s::DSM_NOTSAVING: - { - INT32 buttonx = BASEVIDWIDTH; - INT32 buttony = 2; - - K_drawButtonAnim(buttonx - 76, buttony, 0, kp_button_b[1], replayprompttic); - V_DrawRightAlignedThinString(buttonx - 55, buttony, highlightflags, "or"); - K_drawButtonAnim(buttonx - 55, buttony, 0, kp_button_x[1], replayprompttic); - V_DrawRightAlignedThinString(buttonx - 2, buttony, highlightflags, "Save replay"); - break; - } - case demovars_s::DSM_SAVED: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!"); - break; - - default: // Don't render any text here - break; - } + ST_DrawSaveReplayHint(0); } } From 38e1ae1c53cdfff9009a9c66b16ea01374e653b2 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 21:40:22 -0800 Subject: [PATCH 28/32] Replays: let name be changed any time until the file is saved --- src/d_netcmd.c | 2 +- src/g_demo.cpp | 9 ++++----- src/g_demo.h | 7 +------ src/g_game.c | 2 +- src/p_tick.c | 5 ++--- src/st_stuff.c | 25 +++++-------------------- src/y_inter.cpp | 17 +++-------------- 7 files changed, 17 insertions(+), 50 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index cc16907fa..fdda510dd 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2878,7 +2878,7 @@ static void Got_Mapcmd(const UINT8 **cp, INT32 playernum) if (demo.playback && !demo.timing) precache = false; - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; + demo.willsave = (cv_recordmultiplayerdemos.value == 2); demo.savebutton = 0; G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene); diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 71cef6e81..28e92b027 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -3992,7 +3992,7 @@ boolean G_CheckDemoStatus(void) if (!demo.recording) return false; - if (modeattacking || demo.savemode != demovars_s::DSM_NOTSAVING) + if (modeattacking || demo.willsave) { if (demobuf.p) { @@ -4081,14 +4081,13 @@ void G_SaveDemo(void) md5_buffer((char *)p+16, (demobuf.buffer + length) - (p+16), p); #endif - if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. - demo.savemode = demovars_s::DSM_SAVED; + bool saved = FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. Z_Free(demobuf.buffer); demo.recording = false; if (!modeattacking) { - if (demo.savemode == demovars_s::DSM_SAVED) + if (saved) { CONS_Printf(M_GetText("Demo %s recorded\n"), demoname); if (gamedata->eversavedreplay == false) @@ -4111,7 +4110,7 @@ boolean G_CheckDemoTitleEntry(void) if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0)) return false; - demo.savemode = demovars_s::DSM_WILLSAVE; + demo.willsave = true; M_OpenVirtualKeyboard( false, [](const char* replace) -> const char* diff --git a/src/g_demo.h b/src/g_demo.h index 85b4d65f7..91b2c7ee7 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -90,12 +90,7 @@ struct demovars_s { boolean netgame; // multiplayer netgame tic_t savebutton; // Used to determine when the local player can choose to save the replay while the race is still going - enum { - DSM_NOTSAVING, - DSM_WILLAUTOSAVE, - DSM_WILLSAVE, - DSM_SAVED - } savemode; + boolean willsave; boolean freecam; diff --git a/src/g_game.c b/src/g_game.c index 79e5bd91e..3ca13d642 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4549,7 +4549,7 @@ void G_AfterIntermission(void) M_PlaybackQuit(0); return; } - else if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING)) + else if (demo.recording && (modeattacking || demo.willsave)) G_SaveDemo(); if (modeattacking) // End the run. diff --git a/src/p_tick.c b/src/p_tick.c index 1d273e328..3e1e2bb1c 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -1178,9 +1178,8 @@ void P_Ticker(boolean run) { G_WriteAllGhostTics(); - if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE)) - if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) - G_CheckDemoTitleEntry(); + if (cv_recordmultiplayerdemos.value && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) + G_CheckDemoTitleEntry(); } else if (demo.playback) // Use Ghost data for consistency checks. { diff --git a/src/st_stuff.c b/src/st_stuff.c index b2a2bbc41..65f048b7c 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1459,26 +1459,11 @@ void ST_DrawServerSplash(boolean timelimited) void ST_DrawSaveReplayHint(INT32 flags) { - const char *text = ""; - switch (demo.savemode) - { - case DSM_NOTSAVING: - text = "\xAB" "or " "\xAE" "Save replay"; - break; - - case DSM_WILLAUTOSAVE: - text = "Replay will be saved. \xAB" "Change title"; - break; - - case DSM_WILLSAVE: - text = "Replay will be saved."; - break; - - case DSM_SAVED: - text = "Replay saved!"; - break; - } - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, text); + V_DrawRightAlignedThinString( + BASEVIDWIDTH - 2, 2, + flags|V_YELLOWMAP, + demo.willsave ? "Replay will be saved. \xAB" "Change title" : "\xAB" "or " "\xAE" "Save replay" + ); } static fixed_t ST_CalculateFadeIn(player_t *player) diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 13a5e6ba1..688afbd8c 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -80,7 +80,6 @@ static INT32 powertype = PWRLV_DISABLED; static INT32 intertic; static INT32 endtic = -1; static INT32 sorttic = -1; -static INT32 replayprompttic; static fixed_t mqscroll = 0; static fixed_t chkscroll = 0; @@ -1690,12 +1689,8 @@ skiptallydrawer: } finalcounter: - { - if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == demovars_s::DSM_SAVED) && !demo.playback) - { - ST_DrawSaveReplayHint(0); - } - } + if ((modeattacking == ATTACKING_NONE) && demo.recording) + ST_DrawSaveReplayHint(0); if (Y_CanSkipIntermission()) { @@ -1731,13 +1726,7 @@ void Y_Ticker(void) return; if (demo.recording) - { - if (demo.savemode == demovars_s::DSM_NOTSAVING) - { - replayprompttic++; - G_CheckDemoTitleEntry(); - } - } + G_CheckDemoTitleEntry(); // Check for pause or menu up in single player if (paused || P_AutoPause()) From 8c76dee523eba5691346eb8b071f76c73400b4e6 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 22:51:22 -0800 Subject: [PATCH 29/32] Virtual Keyboard: dynamically allocate cache; allow full size of replay title buffer --- src/g_demo.cpp | 1 + src/k_menu.h | 5 +++-- src/k_menudraw.c | 17 ++++++++++++++++- src/k_menufunc.c | 2 +- src/menus/transient/virtual-keyboard.c | 15 ++++++++++----- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 28e92b027..c84b650bc 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -4113,6 +4113,7 @@ boolean G_CheckDemoTitleEntry(void) demo.willsave = true; M_OpenVirtualKeyboard( false, + sizeof demo.titlename, [](const char* replace) -> const char* { if (replace) diff --git a/src/k_menu.h b/src/k_menu.h index fdc7c2c88..ae093bf1c 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -557,7 +557,8 @@ extern struct menutyping_s vkb_query_fn_t queryfn; // callback on open and close menu_t *dummymenu; - char cache[MAXSTRINGLENGTH]; // cached string + size_t cachelen; + char *cache; // cached string } menutyping; // While typing, we'll have a fade strongly darken the screen to overlay the typing menu instead @@ -685,7 +686,7 @@ void M_PlayMenuJam(void); boolean M_ConsiderSealedSwapAlert(void); -void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn, menu_t *dummymenu); +void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu); void M_AbortVirtualKeyboard(void); void M_MenuTypingInput(INT32 key); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0929e714e..b0a125478 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -495,6 +495,21 @@ static void M_DrawMenuTooltips(void) } } +static const char *M_MenuTypingCroppedString(void) +{ + static char buf[36]; + const char *p = menutyping.cache; + size_t n = strlen(p); + if (n > sizeof buf) + { + p += n - sizeof buf; + n = sizeof buf; + } + memcpy(buf, p, n); + buf[n] = '\0'; + return buf; +} + // Draws the typing submenu static void M_DrawMenuTyping(void) { @@ -534,7 +549,7 @@ static void M_DrawMenuTyping(void) V_DrawFill(x + 4, y + 4 + 5, 1, 8+6, 121); V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121); - INT32 textwidth = M_DrawCaretString(x + 8, y + 12, menutyping.cache, true); + INT32 textwidth = M_DrawCaretString(x + 8, y + 12, M_MenuTypingCroppedString(), true); if (skullAnimCounter < 4 && menutyping.menutypingclose == false && menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18)) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index b5335a87c..dc86b141c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1136,7 +1136,7 @@ static void M_HandleMenuInput(void) if (M_MenuConfirmPressed(pid)) { // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller. - M_OpenVirtualKeyboard(thisMenuKey == -1, M_QueryCvarAction, NULL); + M_OpenVirtualKeyboard(thisMenuKey == -1, MAXSTRINGLENGTH, M_QueryCvarAction, NULL); return; } else if (M_MenuExtraPressed(pid)) diff --git a/src/menus/transient/virtual-keyboard.c b/src/menus/transient/virtual-keyboard.c index 15e0e9485..4204dce09 100644 --- a/src/menus/transient/virtual-keyboard.c +++ b/src/menus/transient/virtual-keyboard.c @@ -5,6 +5,7 @@ #include "../../s_sound.h" #include "../../console.h" // CON_ShiftChar #include "../../i_system.h" // I_Clipboard funcs +#include "../../z_zone.h" // Typing "sub"-menu struct menutyping_s menutyping; @@ -91,9 +92,9 @@ boolean M_ChangeStringCvar(INT32 choice) const char *paste = I_ClipboardPaste(); if (paste == NULL || paste[0] == '\0') ; - else if (len < MAXSTRINGLENGTH - 1) + else if (len < menutyping.cachelen) { - strlcat(menutyping.cache, paste, MAXSTRINGLENGTH); + strlcat(menutyping.cache, paste, menutyping.cachelen + 1); S_StartSound(NULL, sfx_tmxbdn); // Tails } @@ -146,7 +147,7 @@ boolean M_ChangeStringCvar(INT32 choice) if (choice >= 32 && choice <= 127) { len = strlen(menutyping.cache); - if (len < MAXSTRINGLENGTH - 1) + if (len < menutyping.cachelen) { menutyping.cache[len++] = (char)choice; menutyping.cache[len] = 0; @@ -189,6 +190,8 @@ void M_AbortVirtualKeyboard(void) return; menutyping.active = false; + Z_Free(menutyping.cache); + if (currentMenu == menutyping.dummymenu) M_GoBack(0); } @@ -415,7 +418,7 @@ void M_MenuTypingInput(INT32 key) } } -void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn, menu_t *dummymenu) +void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu) { menutyping.keyboardtyping = !gamepad; menutyping.active = true; @@ -423,7 +426,9 @@ void M_OpenVirtualKeyboard(boolean gamepad, vkb_query_fn_t queryfn, menu_t *dumm menutyping.queryfn = queryfn; menutyping.dummymenu = dummymenu; - strlcpy(menutyping.cache, queryfn(NULL), MAXSTRINGLENGTH); + menutyping.cachelen = cachelen; + Z_Malloc(cachelen + 1, PU_STATIC, &menutyping.cache); + strlcpy(menutyping.cache, queryfn(NULL), cachelen + 1); if (dummymenu) { From e563f6271fc3fb86e41424d717b1acc566c8938f Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 29 Feb 2024 23:06:44 -0800 Subject: [PATCH 30/32] Replays: auto-save when new recording starts - Saves if the map changes --- src/g_demo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index c84b650bc..e727eacba 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -1776,6 +1776,9 @@ void G_ConfirmRewind(tic_t rewindtime) // void G_RecordDemo(const char *name) { + if (demo.recording) + G_CheckDemoStatus(); + extern consvar_t cv_netdemosize; INT32 maxsize; From 71ccf7c9685c65727df3a53df996ab2a6156dbb5 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 3 Mar 2024 19:29:48 -0700 Subject: [PATCH 31/32] Fix out-of-bounds hitlag sounds --- src/k_hitlag.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/k_hitlag.c b/src/k_hitlag.c index 5a81cfd5a..5b40ac9cd 100644 --- a/src/k_hitlag.c +++ b/src/k_hitlag.c @@ -20,6 +20,7 @@ #include "p_local.h" #include "r_main.h" #include "s_sound.h" +#include "m_easing.h" /*-------------------------------------------------- void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage) @@ -152,7 +153,11 @@ static void K_PlayHitLagSFX(mobj_t *victim, UINT8 tics) soundID = sfx_dmgb1; } - soundID += ((tics * (NUM_HITLAG_SOUNDS - 1)) + (MAXHITLAGTICS >> 1)) / MAXHITLAGTICS; + soundID += Easing_Linear( + min(FRACUNIT, FRACUNIT*tics/MAXHITLAGTICS), + 0, + NUM_HITLAG_SOUNDS-1 + ); S_StartSound(victim, soundID); } From 1019e535a8369ab9809814c723674b4cdbac475a Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 3 Mar 2024 19:32:49 -0700 Subject: [PATCH 32/32] Fix out-of-bounds hitlag sounds --- src/k_hitlag.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/k_hitlag.c b/src/k_hitlag.c index 5a81cfd5a..5b40ac9cd 100644 --- a/src/k_hitlag.c +++ b/src/k_hitlag.c @@ -20,6 +20,7 @@ #include "p_local.h" #include "r_main.h" #include "s_sound.h" +#include "m_easing.h" /*-------------------------------------------------- void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage) @@ -152,7 +153,11 @@ static void K_PlayHitLagSFX(mobj_t *victim, UINT8 tics) soundID = sfx_dmgb1; } - soundID += ((tics * (NUM_HITLAG_SOUNDS - 1)) + (MAXHITLAGTICS >> 1)) / MAXHITLAGTICS; + soundID += Easing_Linear( + min(FRACUNIT, FRACUNIT*tics/MAXHITLAGTICS), + 0, + NUM_HITLAG_SOUNDS-1 + ); S_StartSound(victim, soundID); }