From f7e3ea76d1b9ef3533acdd977d2e874891df312e Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 20:27:57 -0800 Subject: [PATCH 01/28] Fix Mine omega combo (instant death) --- src/p_inter.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/p_inter.c b/src/p_inter.c index 79e8fda62..a2954e8ca 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2830,6 +2830,15 @@ static boolean P_FlashingException(const player_t *player, const mobj_t *inflict return false; } + if (inflictor->type == MT_SSMINE) + { + // Mine's first hit is DMG_EXPLODE. + // Afterward, it leaves a spinout hitbox which remains for a short period. + // If the spinout hitbox ignored flashing tics, you would be combod every tic and die instantly. + // DMG_EXPLODE already ignores flashing tics (correct behavior). + return false; + } + if (!P_IsKartItem(inflictor->type) && inflictor->type != MT_PLAYER) { // Exception only applies to player items. From 30d7e94ecefdf2f061909869f7b3a090d3cbd594 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 20:35:15 -0800 Subject: [PATCH 02/28] Fix rare Bubble Shield crash Just NULL-checking --- src/p_map.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_map.c b/src/p_map.c index b700a4b5e..6884ad2e3 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1031,7 +1031,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } // Bubble Shield reflect - if ((thing->type == MT_BUBBLESHIELD && thing->target->player && thing->target->player->bubbleblowup) + if ((thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(thing->target) && thing->target->player && thing->target->player->bubbleblowup) || (thing->player && thing->player->bubbleblowup)) { // see if it went over / under @@ -1053,7 +1053,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } } - else if ((tm.thing->type == MT_BUBBLESHIELD && tm.thing->target->player && tm.thing->target->player->bubbleblowup) + else if ((tm.thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(tm.thing->target) && tm.thing->target->player && tm.thing->target->player->bubbleblowup) || (tm.thing->player && tm.thing->player->bubbleblowup)) { // see if it went over / under From 07ecf949996e341eb173b5964a654618fe6dae33 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:09:04 -0800 Subject: [PATCH 03/28] Bubble: refactor collision code, goes in k_collide --- src/k_collide.cpp | 81 +++++++++++++++++++++++++++++---------------- src/k_collide.h | 3 ++ src/p_map.c | 83 ++++++++++++++--------------------------------- 3 files changed, 80 insertions(+), 87 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 98dc46325..994e0890a 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -744,6 +744,49 @@ void K_LightningShieldAttack(mobj_t *actor, fixed_t size) P_BlockThingsIterator(bx, by, PIT_LightningShieldAttack); } +boolean K_BubbleShieldCanReflect(mobj_t *t1, mobj_t *t2) +{ + return (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_GACHABOM + || t2->type == MT_BANANA || t2->type == MT_EGGMANITEM || t2->type == MT_BALLHOG + || t2->type == MT_SSMINE || t2->type == MT_LANDMINE || t2->type == MT_SINK + || t2->type == MT_GARDENTOP + || t2->type == MT_DROPTARGET + || t2->type == MT_KART_LEFTOVER + || (t2->type == MT_PLAYER && t1->target != t2)); +} + +boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2) +{ + mobj_t *owner = t1->player ? t1 : t1->target; + + if (t2->target != owner || !t2->threshold || t2->type == MT_DROPTARGET) + { + if (t1->player && K_PlayerGuard(t1->player)) + { + K_KartSolidBounce(t1, t2); + K_DoPowerClash(t1, t2); + } + if (!t2->momx && !t2->momy) + { + t2->momz += (24*t2->scale) * P_MobjFlip(t2); + } + else + { + t2->momx = -6*t2->momx; + t2->momy = -6*t2->momy; + t2->momz = -6*t2->momz; + t2->angle += ANGLE_180; + } + if (t2->type == MT_JAWZ) + P_SetTarget(&t2->tracer, t2->target); // Back to the source! + P_SetTarget(&t2->target, owner); // Let the source reflect it back again! + t2->threshold = 10; + S_StartSound(t1, sfx_s3k44); + } + + return true; +} + boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) { if (t2->type == MT_PLAYER) @@ -767,38 +810,20 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) // Don't play from t1 else it gets cut out... for some reason. S_StartSound(t2, sfx_s3k44); } + + return true; } - else + + if (K_BubbleShieldCanReflect(t1, t2)) { - mobj_t *owner = t1->player ? t1 : t1->target; - - if (t2->target != owner || !t2->threshold || t2->type == MT_DROPTARGET) - { - if (t1->player && K_PlayerGuard(t1->player)) - { - K_KartSolidBounce(t1, t2); - K_DoPowerClash(t1, t2); - } - if (!t2->momx && !t2->momy) - { - t2->momz += (24*t2->scale) * P_MobjFlip(t2); - } - else - { - t2->momx = -6*t2->momx; - t2->momy = -6*t2->momy; - t2->momz = -6*t2->momz; - t2->angle += ANGLE_180; - } - if (t2->type == MT_JAWZ) - P_SetTarget(&t2->tracer, t2->target); // Back to the source! - P_SetTarget(&t2->target, owner); // Let the source reflect it back again! - t2->threshold = 10; - S_StartSound(t1, sfx_s3k44); - } + return K_BubbleShieldReflect(t1, t2); + } + + if (t2->flags & MF_SHOOTABLE) + { + P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL); } - // no interaction return true; } diff --git a/src/k_collide.h b/src/k_collide.h index 525e5b9c3..2b205bffb 100644 --- a/src/k_collide.h +++ b/src/k_collide.h @@ -22,6 +22,9 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2); boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2); void K_LightningShieldAttack(mobj_t *actor, fixed_t size); + +boolean K_BubbleShieldCanReflect(mobj_t *t1, mobj_t *t2); +boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2); boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2); boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim); diff --git a/src/p_map.c b/src/p_map.c index 6884ad2e3..dbbbdc0bb 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -522,17 +522,6 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object) } } -static boolean P_BubbleCanReflect(mobj_t *t1, mobj_t *t2) -{ - return (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_GACHABOM - || t2->type == MT_BANANA || t2->type == MT_EGGMANITEM || t2->type == MT_BALLHOG - || t2->type == MT_SSMINE || t2->type == MT_LANDMINE || t2->type == MT_SINK - || t2->type == MT_GARDENTOP - || t2->type == MT_DROPTARGET - || t2->type == MT_KART_LEFTOVER - || (t2->type == MT_PLAYER && t1->target != t2)); -} - // // PIT_CheckThing // @@ -1009,7 +998,29 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) if (tm.thing->type == MT_RANDOMITEM) return BMIT_CONTINUE; - if (tm.thing->type != MT_PLAYER && thing->player && K_PlayerGuard(thing->player) && P_BubbleCanReflect(thing, tm.thing)) + if (tm.thing->type != MT_PLAYER && thing->player && K_PlayerGuard(thing->player) && K_BubbleShieldCanReflect(thing, tm.thing)) + { + // see if it went over / under + if (tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (tm.thing->z + tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + + return K_BubbleShieldReflect(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; + } + else if (thing->type != MT_PLAYER && tm.thing->player && K_PlayerGuard(tm.thing->player) && K_BubbleShieldCanReflect(tm.thing, thing)) + { + // see if it went over / under + if (tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (tm.thing->z + tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + + return K_BubbleShieldReflect(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; + } + + // Bubble Shield reflect + if (thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(thing->target) && thing->target->player && thing->target->player->bubbleblowup) { // see if it went over / under if (tm.thing->z > thing->z + thing->height) @@ -1019,7 +1030,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return K_BubbleShieldCollide(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; } - else if (thing->type != MT_PLAYER && tm.thing->player && K_PlayerGuard(tm.thing->player) && P_BubbleCanReflect(tm.thing, thing)) + else if (tm.thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(tm.thing->target) && tm.thing->target->player && tm.thing->target->player->bubbleblowup) { // see if it went over / under if (tm.thing->z > thing->z + thing->height) @@ -1030,52 +1041,6 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return K_BubbleShieldCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; } - // Bubble Shield reflect - if ((thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(thing->target) && thing->target->player && thing->target->player->bubbleblowup) - || (thing->player && thing->player->bubbleblowup)) - { - // see if it went over / under - if (tm.thing->z > thing->z + thing->height) - return BMIT_CONTINUE; // overhead - if (tm.thing->z + tm.thing->height < thing->z) - return BMIT_CONTINUE; // underneath - - if (P_BubbleCanReflect(thing, tm.thing)) - { - // don't let player hitbox touch it too - if (thing->player) - return BMIT_CONTINUE; - return K_BubbleShieldCollide(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; - } - else if ((tm.thing->flags & MF_SHOOTABLE) && !thing->player) - { - P_DamageMobj(tm.thing, thing, thing->target, 1, DMG_NORMAL); - return BMIT_CONTINUE; - } - } - else if ((tm.thing->type == MT_BUBBLESHIELD && !P_MobjWasRemoved(tm.thing->target) && tm.thing->target->player && tm.thing->target->player->bubbleblowup) - || (tm.thing->player && tm.thing->player->bubbleblowup)) - { - // see if it went over / under - if (tm.thing->z > thing->z + thing->height) - return BMIT_CONTINUE; // overhead - if (tm.thing->z + tm.thing->height < thing->z) - return BMIT_CONTINUE; // underneath - - if (P_BubbleCanReflect(tm.thing, thing)) - { - // don't let player hitbox touch it too - if (tm.thing->player) - return BMIT_CONTINUE; - return K_BubbleShieldCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; - } - else if ((thing->flags & MF_SHOOTABLE) && !tm.thing->player) - { - P_DamageMobj(thing, tm.thing, tm.thing->target, 1, DMG_NORMAL); - return BMIT_CONTINUE; - } - } - // double make sure bubbles won't collide with anything else if (thing->type == MT_BUBBLESHIELD || tm.thing->type == MT_BUBBLESHIELD) return BMIT_CONTINUE; From 4dc1a404f630a964a05fa79d102d5f8c5b0086b9 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:11:59 -0800 Subject: [PATCH 04/28] Battle UFO: fix Bubble blow-up collision --- src/p_map.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/p_map.c b/src/p_map.c index dbbbdc0bb..6ad4cc239 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -768,13 +768,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } - if (thing->type == MT_BATTLEUFO) + if (thing->type == MT_BATTLEUFO && tm.thing->player) { - if (!tm.thing->player) - { - return BMIT_CONTINUE; // not a player - } - if (thing->health <= 0) { return BMIT_CONTINUE; // dead From ab575deaa2589bf6cc7366783f85afe2b5f031dd Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:17:50 -0800 Subject: [PATCH 05/28] Bubble blow-up: hitlag is based on player's speed, not Bubble's speed --- src/k_hitlag.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/k_hitlag.c b/src/k_hitlag.c index e211e90c0..5a81cfd5a 100644 --- a/src/k_hitlag.c +++ b/src/k_hitlag.c @@ -260,6 +260,12 @@ void K_SetHitLagForObjects(mobj_t *victim, mobj_t *inflictor, mobj_t *source, IN return; } + if (P_MobjWasRemoved(inflictor) == false && inflictor->type == MT_BUBBLESHIELD) + { + // Bubble blow-up: hitlag is based on player's speed + inflictor = source; + } + if (P_MobjWasRemoved(victim) == false && P_MobjWasRemoved(inflictor) == false) { const fixed_t speedTicFactor = (mapobjectscale * 8); From fd175f4a5ce5a5cdb66f30763d4d5961aa0e9d65 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:25:43 -0800 Subject: [PATCH 06/28] Insta-Whip: scale knockback to map scale --- src/k_collide.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 994e0890a..f889a8334 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -863,8 +863,8 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) attacker->renderflags &= ~RF_DONTDRAW; angle_t thrangle = R_PointToAngle2(attacker->x, attacker->y, victim->x, victim->y); - P_Thrust(victim, thrangle, FRACUNIT*7); - P_Thrust(attacker, ANGLE_180 + thrangle, FRACUNIT*7); + P_Thrust(victim, thrangle, mapobjectscale*28); + P_Thrust(attacker, ANGLE_180 + thrangle, mapobjectscale*28); return false; } @@ -932,7 +932,7 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) K_DropPowerUps(victimPlayer); angle_t thrangle = ANGLE_180 + R_PointToAngle2(victim->x, victim->y, shield->x, shield->y); - P_Thrust(victim, thrangle, FRACUNIT*10); + P_Thrust(victim, thrangle, mapobjectscale*40); K_AddHitLag(victim, victimHitlag, true); K_AddHitLag(attacker, attackerHitlag, false); From 20bd1ddf3500e672b36a314b600eef113844a4ca Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:32:35 -0800 Subject: [PATCH 07/28] Guard Break: fix knockback direction --- 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 8fd2a8154..2af7cee2e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3851,7 +3851,7 @@ void K_DoGuardBreak(mobj_t *t1, mobj_t *t2) { K_AddMessageForPlayer(t2->player, "Smashed 'em!", false, false); K_AddMessageForPlayer(t1->player, "BARRIER BREAK!!", false, false); - angle_t thrangle = R_PointToAngle2(t1->x, t1->y, t2->x, t2->y); + angle_t thrangle = R_PointToAngle2(t2->x, t2->y, t1->x, t1->y); P_Thrust(t1, thrangle, 7*mapobjectscale); P_DamageMobj(t1, t2, t2, 1, DMG_TUMBLE); From 8baf3cd28e189c178305e21eea510d250910df28 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 21:47:22 -0800 Subject: [PATCH 08/28] Battle: number of monitors that can spawn scales with lobby size - Duel: 1 - Small: 3 - Medium: 5 - Large: 8 --- src/k_battle.c | 6 +++++- src/k_battle.h | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index dce59c87c..544490ee7 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -540,7 +540,11 @@ void K_RunPaperItemSpawners(void) //CONS_Printf("leveltime = %d ", leveltime); - if (spotAvailable > 0 && monitorsSpawned < BATTLE_MONITOR_SPAWN_LIMIT) + // Duel = 2 + 1 = 3 / 2 = 1 + // Small = 5 + 1 = 6 / 2 = 3 + // Medium = 10 + 1 = 11 / 2 = 5 + // Large = 16 + 1 = 17 / 2 = 8 + if (spotAvailable > 0 && monitorsSpawned < (mapheaderinfo[gamemap - 1]->playerLimit + 1) / 2) { const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)]; diff --git a/src/k_battle.h b/src/k_battle.h index 645821920..623553b63 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -13,8 +13,6 @@ extern "C" { #define BATTLE_POWERUP_TIME (30*TICRATE) #define BATTLE_UFO_TIME (20*TICRATE) -#define BATTLE_MONITOR_SPAWN_LIMIT (3) - extern struct battleovertime { UINT16 enabled; ///< Has this been initalized yet? From eccc0a69247d224c81bc91a1c59eaa33318061f1 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 18 Jan 2024 15:32:40 -0800 Subject: [PATCH 09/28] Fix battlespawn cheat --- src/g_game.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 9e2c7d946..7c2dc274c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2607,22 +2607,12 @@ mapthing_t *G_FindBattleStart(INT32 playernum) if (numdmstarts) { - extern consvar_t cv_battlespawn; - if (cv_battlespawn.value) + for (j = 0; j < 64; j++) { - i = cv_battlespawn.value - 1; - if (i < numdmstarts) + i = P_RandomKey(PR_PLAYERSTARTS, numdmstarts); + if (G_CheckSpot(playernum, deathmatchstarts[i])) return deathmatchstarts[i]; } - else - { - for (j = 0; j < 64; j++) - { - i = P_RandomKey(PR_PLAYERSTARTS, numdmstarts); - if (G_CheckSpot(playernum, deathmatchstarts[i])) - return deathmatchstarts[i]; - } - } if (doprints) CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Deathmatch starts!\n")); return NULL; @@ -2796,14 +2786,20 @@ static inline mapthing_t *G_FindTeamStartOrFallback(INT32 playernum) mapthing_t *G_FindMapStart(INT32 playernum) { + extern consvar_t cv_battlespawn; mapthing_t *spawnpoint; if (!playeringame[playernum]) return NULL; + // -- battlespawn cheat -- + // Everyone spawns at the same spot + if ((gametyperules & GTR_BATTLESTARTS) && cv_battlespawn.value > 0 && cv_battlespawn.value <= numdmstarts) + spawnpoint = deathmatchstarts[cv_battlespawn.value - 1]; + // -- Podium -- // Single special behavior - if (K_PodiumSequence() == true) + else if (K_PodiumSequence() == true) spawnpoint = G_FindPodiumStart(playernum); // -- Time Attack / Battle duels -- From e06e0dcb3a1c55bcb5ca8c89e56dcec9805cfe59 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 18:53:14 -0800 Subject: [PATCH 10/28] Battle: emerald win HUD polish - Bigger - Aligned to the Tally boxes - Each emerald slides in one-by-one over the duration of the Tally - Flash after the final emerald slides into place --- src/hud/emerald-win.cpp | 85 ++++++++++++++++++++++++++++++++--------- src/k_battle.c | 10 +++-- src/k_battle.h | 2 +- src/k_hud.cpp | 18 ++++++--- src/k_hud.h | 2 +- src/p_saveg.c | 4 +- 6 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/hud/emerald-win.cpp b/src/hud/emerald-win.cpp index f304b0f4e..df7ee7319 100644 --- a/src/hud/emerald-win.cpp +++ b/src/hud/emerald-win.cpp @@ -1,41 +1,90 @@ +#include + #include "../v_draw.hpp" #include "../doomdef.h" #include "../i_time.h" +#include "../k_battle.h" #include "../k_hud.h" +#include "../m_easing.h" +#include "../m_fixed.h" +#include "../p_tick.h" #include "../screen.h" using srb2::Draw; -void K_drawEmeraldWin(void) +namespace { - constexpr float kScale = 0.25; - constexpr int kH = 72 * kScale; - constexpr int kYPad = 12; - constexpr int kWidth = 34 + 4; - if (I_GetTime() % 3) +fixed_t interval(tic_t t, tic_t s, tic_t d) +{ + return (std::min(std::max(t, s) - s, d) * FRACUNIT) / d; +} + +}; // namespace + +void K_drawEmeraldWin(boolean overlay) +{ + if (leveltime < g_emeraldWin) { return; } - Draw row = Draw(BASEVIDWIDTH / 2, BASEVIDHEIGHT / 2).scale(kScale).flags(V_ADD); + constexpr float kScale = 0.5; + constexpr int kWidth = (69 + 4) * 2 * kScale; + constexpr int kMid = (72 * kScale) / 2; + constexpr int kYOffset = (68 * kScale) - kMid; + + constexpr int kTop = 86; + constexpr int kBot = 129; + + constexpr tic_t kDelay = 24; + constexpr tic_t kSlide = 12; + + constexpr tic_t kFlashStart = (6 * kDelay) + kSlide; + constexpr tic_t kFlash = 10; + + INT32 flags = 0; + + tic_t t = leveltime - g_emeraldWin; + + if (overlay) + { + if (t >= kFlashStart && t - kFlashStart <= kFlash) + { + flags = V_ADD | (Easing_InOutSine(interval(t, kFlashStart, kFlash), 0, 9) << V_ALPHASHIFT); + } + else + { + return; + } + } + else + { + flags = (I_GetTime() & 1) ? V_ADD : 0; + } + + patch_t* emer = Draw::cache_patch("EMRCA0"); + Draw row = Draw(BASEVIDWIDTH / 2, kYOffset).scale(kScale).flags(flags); //Draw(0, row.y()).size(BASEVIDWIDTH, 1).fill(35); - Draw top = row.y(-kYPad); - Draw bot = row.xy(-kWidth / 2, kH + kYPad); + Draw top = row.y(kTop); + Draw bot = row.xy(-kWidth / 2, kBot); - auto put = [](Draw& row, int x, int n) + auto put = [&](Draw& row, int offscreen, int x, int n) { - row.x(x * kWidth).colormap(static_cast(SKINCOLOR_CHAOSEMERALD1 + n)).patch("EMRCA0"); + row + .xy(x * kWidth, Easing_OutSine(interval(t, kDelay * n, kSlide), offscreen, 0)) + .colormap(static_cast(SKINCOLOR_CHAOSEMERALD1 + n)) + .patch(emer); }; - put(top, -1, 3); - put(top, 0, 0); - put(top, 1, 4); + put(top, -kTop - kMid, -1, 3); + put(top, -kTop - kMid, 0, 0); + put(top, -kTop - kMid, 1, 4); - put(bot, -1, 5); - put(bot, 0, 1); - put(bot, 1, 2); - put(bot, 2, 6); + put(bot, 200 - kBot + kMid, -1, 5); + put(bot, 200 - kBot + kMid, 0, 1); + put(bot, 200 - kBot + kMid, 1, 2); + put(bot, 200 - kBot + kMid, 2, 6); } diff --git a/src/k_battle.c b/src/k_battle.c index 544490ee7..811c9fc37 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -43,7 +43,7 @@ UINT8 maptargets = 0; // Capsules in map UINT8 numtargets = 0; // Capsules busted // Battle: someone won by collecting all 7 Chaos Emeralds -boolean g_emeraldWin = false; +tic_t g_emeraldWin = 0; INT32 K_StartingBumperCount(void) { @@ -210,7 +210,11 @@ void K_CheckEmeralds(player_t *player) player->roundscore = 100; // lmao P_DoAllPlayersExit(0, false); - g_emeraldWin = true; + + // TODO: this would be better if the timing lived in + // Tally code. But I didn't do it that, so this just + // shittily approximates syncing up with Tally. + g_emeraldWin = leveltime + (3*TICRATE); } UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) @@ -884,7 +888,7 @@ void K_BattleInit(boolean singleplayercontext) g_battleufo.due = starttime; g_battleufo.previousId = Obj_RandomBattleUFOSpawnerID() - 1; - g_emeraldWin = false; + g_emeraldWin = 0; } UINT8 K_Bumpers(player_t *player) diff --git a/src/k_battle.h b/src/k_battle.h index 623553b63..cb15921ae 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -31,7 +31,7 @@ extern struct battleufo extern boolean battleprisons; extern INT32 nummapboxes, numgotboxes; // keep track of spawned battle mode items extern UINT8 maptargets, numtargets; -extern boolean g_emeraldWin; +extern tic_t g_emeraldWin; INT32 K_StartingBumperCount(void); boolean K_IsPlayerWanted(player_t *player); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index e1eb0a4b8..e3ed12f52 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5851,10 +5851,16 @@ void K_drawKartHUD(void) K_drawKartFirstPerson(); // Draw full screen stuff that turns off the rest of the HUD - if (mapreset && R_GetViewNumber() == 0) + if (R_GetViewNumber() == 0) { - K_drawChallengerScreen(); - return; + if (mapreset) + { + K_drawChallengerScreen(); + return; + } + + if (g_emeraldWin) + K_drawEmeraldWin(false); } if (!demo.title) @@ -6069,15 +6075,15 @@ void K_drawKartHUD(void) K_drawSpectatorHUD(false); } + if (R_GetViewNumber() == 0 && g_emeraldWin) + K_drawEmeraldWin(true); + if (modeattacking || freecam) // everything after here is MP and debug only 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); - if (g_emeraldWin) - K_drawEmeraldWin(); - // Draw FREE PLAY. K_drawKartFreePlay(); diff --git a/src/k_hud.h b/src/k_hud.h index 839827d3d..d91d0c5c0 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -47,7 +47,7 @@ void K_drawSpectatorHUD(boolean director); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT32 splitflags, UINT8 mode); void K_drawKart2PTimestamp(void); void K_drawKart4PTimestamp(void); -void K_drawEmeraldWin(void); +void K_drawEmeraldWin(boolean overlay); void K_DrawMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, UINT16 map, const UINT8 *colormap); void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, patch_t *patch, const UINT8 *colormap); void K_drawTargetHUD(const vector3_t *origin, player_t *player); diff --git a/src/p_saveg.c b/src/p_saveg.c index 712a7d9d4..64659573a 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6471,7 +6471,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEINT32(save->p, numgotboxes); WRITEUINT8(save->p, numtargets); WRITEUINT8(save->p, battleprisons); - WRITEUINT8(save->p, g_emeraldWin); + WRITEUINT32(save->p, g_emeraldWin); WRITEUINT8(save->p, gamespeed); WRITEUINT8(save->p, numlaps); @@ -6657,7 +6657,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) numgotboxes = READINT32(save->p); numtargets = READUINT8(save->p); battleprisons = (boolean)READUINT8(save->p); - g_emeraldWin = (boolean)READUINT8(save->p); + g_emeraldWin = (tic_t)READUINT32(save->p); gamespeed = READUINT8(save->p); numlaps = READUINT8(save->p); From 8a882c55e5020d2d3ba4898d7c686ddddc97bb1b Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:31:08 -0800 Subject: [PATCH 11/28] Battle: have 6 emeralds and touch the 7th to win instantly Win instantly instead of waiting for the emerald to finish swirling into your body. --- src/k_battle.c | 5 +++++ src/p_inter.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/k_battle.c b/src/k_battle.c index 811c9fc37..7e330b545 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -207,6 +207,11 @@ void K_CheckEmeralds(player_t *player) return; } + if (player->exiting) + { + return; + } + player->roundscore = 100; // lmao P_DoAllPlayersExit(0, false); diff --git a/src/p_inter.c b/src/p_inter.c index a2954e8ca..97b166def 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -560,6 +560,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_SetEmeraldAwardee(special, toucher); } + // You have 6 emeralds and you touch the 7th: win instantly! + if (ALLCHAOSEMERALDS((player->emeralds | special->extravalue1))) + { + player->emeralds |= special->extravalue1; + K_CheckEmeralds(player); + } + return; case MT_SPECIAL_UFO: if (Obj_UFOEmeraldCollect(special, toucher) == false) From 446f5d23f36ce8692d5d6c964328b279ebe60e6c Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:41:51 -0800 Subject: [PATCH 12/28] Add srb2::ArchiveWrapper and srb2::UnArchiveWrapper, helper class to simplify p_saveg I/O --- src/archive_wrapper.hpp | 133 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/archive_wrapper.hpp diff --git a/src/archive_wrapper.hpp b/src/archive_wrapper.hpp new file mode 100644 index 000000000..e615695b9 --- /dev/null +++ b/src/archive_wrapper.hpp @@ -0,0 +1,133 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef archive_wrapper_hpp +#define archive_wrapper_hpp + +#include + +#include "typedef.h" + +#include "cxxutil.hpp" +#include "byteptr.h" +#include "doomdef.h" // p_saveg.h +#include "doomtype.h" +#include "m_fixed.h" +#include "p_mobj.h" // p_saveg.h +#include "p_saveg.h" + +namespace srb2 +{ + +struct ArchiveWrapperBase +{ + explicit ArchiveWrapperBase(savebuffer_t* save) : p_(save->p) {} + +protected: + template + void read(U& n) = delete; + + template + void write(const U& n) = delete; + +private: + UINT8*& p_; +}; + +#define READ_SPEC(T, arg) \ + template <> void ArchiveWrapperBase::read(T& arg) + +#define WRITE_SPEC(T, arg) \ + template <> void ArchiveWrapperBase::write(const T& arg) + +#define MACRO_PAIR(T) \ + READ_SPEC(T, n) { n = READ ## T(p_); } \ + WRITE_SPEC(T, n) { WRITE ## T(p_, n); } + +MACRO_PAIR(UINT8) +MACRO_PAIR(SINT8) +MACRO_PAIR(UINT16) +MACRO_PAIR(INT16) +MACRO_PAIR(UINT32) +MACRO_PAIR(INT32) + +READ_SPEC(vector2_t, n) +{ + read(n.x); + read(n.y); +} + +WRITE_SPEC(vector2_t, n) +{ + write(n.x); + write(n.y); +} + +READ_SPEC(vector3_t, n) +{ + read(n.x); + read(n.y); + read(n.z); +} + +WRITE_SPEC(vector3_t, n) +{ + write(n.x); + write(n.y); + write(n.z); +} + +// Specializations for boolean, so it can stored as char +// instead of int. + +template <> +void ArchiveWrapperBase::read(boolean& n) +{ + SRB2_ASSERT(n == true || n == false); + n = READUINT8(p_); +} + +template <> +void ArchiveWrapperBase::write(const boolean& n) +{ + SRB2_ASSERT(n == true || n == false); + WRITEUINT8(p_, n); +} + +#undef MACRO_PAIR +#undef WRITE_SPEC +#undef READ_SPEC + +struct ArchiveWrapper : ArchiveWrapperBase +{ + using ArchiveWrapperBase::ArchiveWrapperBase; + + template + void operator()(const U& n) { write(n); } +}; + +struct UnArchiveWrapper : ArchiveWrapperBase +{ + using ArchiveWrapperBase::ArchiveWrapperBase; + + template + void operator()(U& n) { read(n); } +}; + +template +struct is_archive_wrapper : std::is_base_of {}; + +template +inline constexpr bool is_archive_wrapper_v = is_archive_wrapper::value; + +#define SRB2_ARCHIVE_WRAPPER_CALL(ar, T, var) ((ar). template operator()(var)) + +}; // namespace + +#endif/*archive_wrapper_hpp*/ From 7d6239e06c6624be129d7dddd39361036be4bd89 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:54:11 -0800 Subject: [PATCH 13/28] Add round end camera system - Spins and zooms around a center point - Freezes the level while spinning - Pans over to follow and object after spinning ends - Timing and speed completely customizable --- src/CMakeLists.txt | 1 + src/k_endcam.cpp | 197 +++++++++++++++++++++++++++++++++++++++++++++ src/k_endcam.h | 74 +++++++++++++++++ src/p_saveg.c | 13 +++ src/p_setup.cpp | 3 + src/p_tick.c | 9 ++- src/p_user.c | 14 +++- src/typedef.h | 3 + 8 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 src/k_endcam.cpp create mode 100644 src/k_endcam.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4099f965..c46dbf3ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -157,6 +157,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_dialogue.cpp k_tally.cpp k_bans.cpp + k_endcam.cpp music.cpp music_manager.cpp ) diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp new file mode 100644 index 000000000..91f87d35b --- /dev/null +++ b/src/k_endcam.cpp @@ -0,0 +1,197 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include + +#include "archive_wrapper.hpp" + +#include "byteptr.h" +#include "doomdef.h" +#include "doomtype.h" +#include "g_game.h" +#include "k_endcam.h" +#include "m_easing.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_saveg.h" +#include "p_tick.h" +#include "r_fps.h" +#include "r_main.h" + +endcam_t g_endcam; + +namespace +{ + +fixed_t interval(tic_t t, tic_t d) +{ + return (std::min(t, d) * FRACUNIT) / std::max(d, 1u); +} + +fixed_t interval(tic_t t, tic_t s, tic_t d) +{ + return interval(std::max(t, s) - s, d); +} + +INT32 lerp(fixed_t f, INT32 a, INT32 b) +{ + return a + FixedMul(f, b - a); +} + +struct Camera : camera_t +{ + void pos(const vector3_t& p) + { + x = p.x; + y = p.y; + z = p.z; + subsector = R_PointInSubsector(x, y); + } +}; + +struct EndCam : endcam_t +{ + tic_t Time() const { return leveltime - begin; } + bool Freezing() const { return Time() <= swirlDuration; } + + void GC() + { + if (P_MobjWasRemoved(panMobj)) + { + P_SetTarget(&panMobj, nullptr); + } + } + + void Start() + { + active = true; + begin = leveltime; + + // Reset all viewpoints + for (int i = 0; i < MAXSPLITSCREENPLAYERS; ++i) + { + Move(static_cast(camera[i])); + } + + R_ResetViewInterpolation(0); + } + + void Move(Camera& cam) + { + tic_t t = Time(); + fixed_t pan = Easing_OutQuint(interval(t, swirlDuration, panDuration), FRACUNIT, 0); + + auto aim = [&](vector3_t& p, vector3_t& q) + { + cam.aiming = lerp(pan, cam.aiming, R_PointToAngle2(0, p.z, FixedHypot(q.x - p.x, q.y - p.y), q.z)); + cam.pos(p); + }; + + if (t <= swirlDuration) + { + fixed_t swirl = interval(t, swirlDuration); + + angle_t ang = FixedAngle(swirl < FRACUNIT/2 ? + Easing_InOutQuint(swirl, startAngle, endAngle) : + Easing_InOutQuad(swirl, startAngle, endAngle)); + + fixed_t hDist = Easing_OutQuad(swirl, startRadius.x, endRadius.x); + + vector3_t p = { + origin.x - FixedMul(FCOS(ang), hDist), + origin.y - FixedMul(FSIN(ang), hDist), + origin.z + Easing_OutQuad(swirl, startRadius.y, endRadius.y) + }; + + aim(p, origin); + cam.angle = ang; + } + else if (!P_MobjWasRemoved(panMobj)) + { + vector3_t q = {panMobj->x, panMobj->y, P_GetMobjHead(panMobj)}; + vector3_t p = {cam.x, cam.y, cam.z}; + Follow(FixedMul(pan, panSpeed), p, q); // modifies p + + aim(p, q); + cam.angle = lerp( + Easing_Linear(pan, FRACUNIT/4, FRACUNIT), + cam.angle, + R_PointToAngle2(p.x, p.y, q.x, q.y) + ); + } + + } + + template + void Archive(T&& ar) + { + static_assert(srb2::is_archive_wrapper_v); +#define X(T, var) SRB2_ARCHIVE_WRAPPER_CALL(ar, T, var) + X(vector3_t, origin); + X(vector2_t, startRadius); + X(vector2_t, endRadius); + X(tic_t, swirlDuration); + X(fixed_t, startAngle); + X(fixed_t, endAngle); + // panMobj is handled in p_saveg.c + X(tic_t, panDuration); + X(fixed_t, panSpeed); + X(bool, active); + X(tic_t, begin); +#undef X + } + +private: + void Follow(fixed_t f, vector3_t& p, vector3_t q) const + { + FV3_Sub(&q, &p); + q.x = FixedMul(q.x, f); + q.y = FixedMul(q.y, f); + q.z = FixedMul(q.z, f); + + FV3_Add(&p, &q); + } +}; + +EndCam& endcam_cast() +{ + return static_cast(g_endcam); +} + +}; // namespace + +void K_CommitEndCamera(void) +{ + endcam_cast().Start(); +} + +void K_MoveEndCamera(camera_t *thiscam) +{ + endcam_cast().Move(static_cast(*thiscam)); +} + +void K_EndCameraGC(void) +{ + endcam_cast().GC(); +} + +boolean K_EndCameraIsFreezing(void) +{ + return endcam_cast().Freezing(); +} + +void K_SaveEndCamera(savebuffer_t *save) +{ + endcam_cast().Archive(srb2::ArchiveWrapper(save)); +} + +void K_LoadEndCamera(savebuffer_t *save) +{ + endcam_cast().Archive(srb2::UnArchiveWrapper(save)); +} diff --git a/src/k_endcam.h b/src/k_endcam.h new file mode 100644 index 000000000..e9ea3ed7a --- /dev/null +++ b/src/k_endcam.h @@ -0,0 +1,74 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef k_endcam_h +#define k_endcam_h + +#include "typedef.h" + +#include "doomtype.h" +#include "m_fixed.h" +#include "tables.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct endcam_t +{ + // + // Configurable properties + // + + vector3_t origin; // center point + vector2_t startRadius; // X = horizontal, Y = vertical + vector2_t endRadius; + + tic_t swirlDuration; + fixed_t startAngle; // 180*FRACUNIT, NOT ANGLE_180 + fixed_t endAngle; + + // 1) Camera pans vertically to keep this object centered + // 2) After swirling ends, pan horizontally too + mobj_t *panMobj; + tic_t panDuration; // dropoff after swirling ends + fixed_t panSpeed; // 0-FRACUNIT + + /// ... + + // You should not set these yourself. + // Use K_CommitEndCamera. + boolean active; + tic_t begin; // leveltime +}; + +extern endcam_t g_endcam; + +// Sets endcam_t.active and endcam_t.begin. +// +// VERY IMPORTANT: +// +// Set the OTHER fields in endcam_t BEFORE calling this +// function, so the camera can cut away cleanly. +void K_CommitEndCamera(void); + +/// ... + +// Low-level functions +void K_MoveEndCamera(camera_t *thiscam); +void K_EndCameraGC(void); +boolean K_EndCameraIsFreezing(void); +void K_SaveEndCamera(savebuffer_t *save); +void K_LoadEndCamera(savebuffer_t *save); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif/*k_endcam_h*/ diff --git a/src/p_saveg.c b/src/p_saveg.c index 64659573a..2cf06d1e2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -47,6 +47,7 @@ #include "g_party.h" #include "k_vote.h" #include "k_zvote.h" +#include "k_endcam.h" #include @@ -5745,6 +5746,12 @@ static void P_RelinkPointers(void) P_LoadMobjPointers(RelinkMobjVoid); + if (g_endcam.panMobj) + { + if (!RelinkMobj(&g_endcam.panMobj)) + CONS_Debug(DBG_GAMELOGIC, "g_endcam.panMobj not found\n"); + } + // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; currentthinker = currentthinker->next) @@ -6868,6 +6875,9 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) } } + K_SaveEndCamera(save); + WriteMobjPointer(g_endcam.panMobj); + P_NetArchivePlayers(save); P_NetArchiveParties(save); P_NetArchiveRoundQueue(save); @@ -6933,6 +6943,9 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) if (!P_NetUnArchiveMisc(save, reloading)) return false; + K_LoadEndCamera(save); + ReadMobjPointer(&g_endcam.panMobj); + P_NetUnArchivePlayers(save); P_NetUnArchiveParties(save); P_NetUnArchiveRoundQueue(save); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index aaa9389c2..f70f552ca 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -115,6 +115,7 @@ #include "music.h" #include "k_dialogue.h" #include "k_hud.h" // K_ClearPersistentMessages +#include "k_endcam.h" // Replay names have time #if !defined (UNDER_CE) @@ -7696,6 +7697,8 @@ static void P_InitLevelSettings(void) K_ResetSpecialStage(); K_ResetBossInfo(); + + memset(&g_endcam, 0, sizeof g_endcam); } #if 0 diff --git a/src/p_tick.c b/src/p_tick.c index c8bb7c4ec..309979cab 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -48,6 +48,7 @@ #include "k_dialogue.h" #include "m_easing.h" #include "k_hud.h" // messagetimer +#include "k_endcam.h" #include "lua_profile.h" @@ -65,12 +66,12 @@ static boolean g_freezeLevel; boolean P_LevelIsFrozen(void) { - return (g_freezeLevel || g_freezeCheat); + return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing()); } boolean P_FreezeCheat(void) { - return (g_freezeLevel || g_freezeCheat); + return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing()); } void P_SetFreezeCheat(boolean value) @@ -101,7 +102,7 @@ boolean P_MobjIsFrozen(mobj_t *mobj) } } - if (g_freezeLevel == true) + if (g_freezeLevel == true || K_EndCameraIsFreezing()) { // level totally frozen return true; @@ -767,6 +768,8 @@ void P_RunChaseCameras(void) P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false); } } + + K_EndCameraGC(); } static fixed_t P_GetDarkness(tic_t start, tic_t end) diff --git a/src/p_user.c b/src/p_user.c index 350ded74c..b9c1911f4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -67,6 +67,7 @@ #include "music.h" #include "k_tally.h" #include "k_objects.h" +#include "k_endcam.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -3062,6 +3063,11 @@ void P_ToggleDemoCamera(UINT8 viewnum) void P_ResetCamera(player_t *player, camera_t *thiscam) { + if (g_endcam.active) + { + return; + } + tic_t tries = 0; fixed_t x, y, z; @@ -3155,7 +3161,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall num = 0; } - if (thiscam->freecam || player->spectator) + if ((thiscam->freecam || player->spectator) && !g_endcam.active) { P_DemoCameraMovement(thiscam, num); return true; @@ -3164,6 +3170,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (paused || P_AutoPause()) return true; + if (g_endcam.active) + { + K_MoveEndCamera(thiscam); + return true; + } + playerScale = FixedDiv(player->mo->scale, mapobjectscale); scaleDiff = playerScale - FRACUNIT; diff --git a/src/typedef.h b/src/typedef.h index 77625d4cd..605103166 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -190,6 +190,9 @@ TYPEDEF (botcontroller_t); // k_brightmap.h TYPEDEF (brightmapStorage_t); +// k_endcam.h +TYPEDEF (endcam_t); + // k_follower.h TYPEDEF (follower_t); TYPEDEF (followercategory_t); From 3cfd50ef2bc3abfe4e58134b149037e0af9c7026 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:56:25 -0800 Subject: [PATCH 14/28] Battle: don't let players respawn after round ends --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index b9c1911f4..3a649503e 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2838,7 +2838,7 @@ static void P_DeathThink(player_t *player) } } - if ((player->pflags & PF_ELIMINATED) /*&& (gametyperules & GTR_BUMPERS)*/) + if ((player->pflags & PF_ELIMINATED) || exitcountdown) { playerGone = true; } From b1c9de32c6ac4993b3f470857d4cfaff4aac8478 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:48:54 -0800 Subject: [PATCH 15/28] Battle: use round end camera when player wins with final KO --- src/k_endcam.cpp | 19 +++++++++++++++++++ src/k_endcam.h | 3 +++ src/k_kart.c | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp index 91f87d35b..ec04351cb 100644 --- a/src/k_endcam.cpp +++ b/src/k_endcam.cpp @@ -195,3 +195,22 @@ void K_LoadEndCamera(savebuffer_t *save) { endcam_cast().Archive(srb2::UnArchiveWrapper(save)); } + +void K_StartRoundWinCamera(mobj_t *origin, angle_t focusAngle, fixed_t finalRadius, tic_t panDuration, fixed_t panSpeed) +{ + const fixed_t angF = AngleFixed(focusAngle); + + g_endcam.origin = {origin->x, origin->y, P_GetMobjHead(origin)}; + g_endcam.startRadius = {2400*mapobjectscale, 800*mapobjectscale}; + g_endcam.endRadius = {finalRadius, finalRadius / 2}; + + g_endcam.swirlDuration = 3*TICRATE; + g_endcam.startAngle = angF + (90*FRACUNIT); + g_endcam.endAngle = angF + (720*FRACUNIT); + + P_SetTarget(&g_endcam.panMobj, origin); + g_endcam.panDuration = panDuration; + g_endcam.panSpeed = panSpeed; + + K_CommitEndCamera(); +} diff --git a/src/k_endcam.h b/src/k_endcam.h index e9ea3ed7a..d6d976883 100644 --- a/src/k_endcam.h +++ b/src/k_endcam.h @@ -58,6 +58,9 @@ extern endcam_t g_endcam; // function, so the camera can cut away cleanly. void K_CommitEndCamera(void); +// Automatically set up a cool camera in one-shot. +void K_StartRoundWinCamera(mobj_t *origin, angle_t focusAngle, fixed_t finalRadius, tic_t panDuration, fixed_t panSpeed); + /// ... // Low-level functions diff --git a/src/k_kart.c b/src/k_kart.c index 2af7cee2e..be7213d43 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -50,6 +50,7 @@ #include "k_tally.h" #include "music.h" #include "m_easing.h" +#include "k_endcam.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -3918,6 +3919,16 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN { player->roundscore = 100; // Make sure you win! P_DoAllPlayersExit(0, false); + + mobj_t *source = !P_MobjWasRemoved(inflictor) ? inflictor : player->mo; + + K_StartRoundWinCamera( + victim->mo, + R_PointToAngle2(source->x, source->y, victim->mo->x, victim->mo->y) + ANGLE_135, + 200*mapobjectscale, + 8*TICRATE, + FRACUNIT/512 + ); } P_AddPlayerScore(player, points); From a012b90c89b051557670fcbffcf1193df3c2f89b Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:49:07 -0800 Subject: [PATCH 16/28] Battle: use round end camera when player wins with emeralds --- src/k_battle.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/k_battle.c b/src/k_battle.c index 7e330b545..67baa7d69 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -24,6 +24,7 @@ #include "music.h" #include "hu_stuff.h" #include "m_easing.h" +#include "k_endcam.h" #define BARRIER_MIN_RADIUS (768 * mapobjectscale) @@ -220,6 +221,19 @@ void K_CheckEmeralds(player_t *player) // Tally code. But I didn't do it that, so this just // shittily approximates syncing up with Tally. g_emeraldWin = leveltime + (3*TICRATE); + + if (!P_MobjWasRemoved(player->mo)) + { + K_StartRoundWinCamera( + player->mo, + player->angleturn + ANGLE_180, + 400*mapobjectscale, + 6*TICRATE, + FRACUNIT/16 + ); + + g_emeraldWin += g_endcam.swirlDuration; + } } UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) From 418f2a14c4a37e56500db5ef749528bc22179433 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 20 Jan 2024 21:51:06 -0800 Subject: [PATCH 17/28] Battle: delay Tally a little bit to let round end camera finish beforehand --- src/g_game.c | 2 +- src/k_tally.cpp | 7 ++++++- src/k_tally.h | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 7c2dc274c..88effe9d8 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1975,7 +1975,7 @@ void G_Ticker(boolean run) { Music_Play("intermission"); } - else if (musiccountdown == MUSIC_COUNTDOWN_MAX - TALLY_TIME) + else if (musiccountdown == MUSIC_COUNTDOWN_MAX - K_TallyDelay()) { P_EndingMusic(); } diff --git a/src/k_tally.cpp b/src/k_tally.cpp index ef6d0a112..3beafcfc2 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -525,7 +525,7 @@ void level_tally_t::Init(player_t *player) } } - delay = TALLY_TIME; // sync up with musiccountdown + delay = K_TallyDelay(); // sync up with musiccountdown if (game_over == true) { @@ -1404,3 +1404,8 @@ boolean K_PlayerTallyActive(player_t *player) { return player->tally.active; //(player->exiting || (player->pflags & PF_NOCONTEST)); } + +tic_t K_TallyDelay(void) +{ + return ((gametyperules & GTR_BUMPERS) ? 4 : 3) * TICRATE; +} diff --git a/src/k_tally.h b/src/k_tally.h index ff4870184..d763e54e2 100644 --- a/src/k_tally.h +++ b/src/k_tally.h @@ -20,8 +20,7 @@ #define TALLY_WINDOW_SIZE (2) -#define TALLY_TIME (3*TICRATE) -#define MUSIC_COUNTDOWN_MAX (TALLY_TIME + 8*TICRATE) +#define MUSIC_COUNTDOWN_MAX (K_TallyDelay() + 8*TICRATE) typedef enum { @@ -119,6 +118,7 @@ void K_InitPlayerTally(player_t *player); void K_TickPlayerTally(player_t *player); void K_DrawPlayerTally(void); boolean K_PlayerTallyActive(player_t *player); +tic_t K_TallyDelay(void); #ifdef __cplusplus } // extern "C" From 8f53d34325d3f2df1ad6fa280c3e61516cb9409c Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 18:25:52 -0800 Subject: [PATCH 18/28] Battle: make sure Overtime lasers are visible during round end camera --- src/k_battle.c | 55 +++++++++++++++++++++++++++++------------------- src/k_battle.h | 1 + src/k_endcam.cpp | 5 +++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index 67baa7d69..21f4961bb 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -25,6 +25,7 @@ #include "hu_stuff.h" #include "m_easing.h" #include "k_endcam.h" +#include "p_tick.h" #define BARRIER_MIN_RADIUS (768 * mapobjectscale) @@ -727,6 +728,36 @@ static void K_SpawnOvertimeLaser(fixed_t x, fixed_t y, fixed_t scale) } } +void K_SpawnOvertimeBarrier(void) +{ + if (battleovertime.radius <= 0) + { + return; + } + + const INT32 orbs = 32; + const angle_t angoff = ANGLE_MAX / orbs; + const UINT8 spriteSpacing = 128; + + fixed_t circumference = FixedMul(M_PI_FIXED, battleovertime.radius * 2); + fixed_t scale = max(circumference / spriteSpacing / orbs, mapobjectscale); + + fixed_t size = FixedMul(mobjinfo[MT_OVERTIME_PARTICLE].radius, scale); + fixed_t posOffset = max(battleovertime.radius - size, 0); + + INT32 i; + + for (i = 0; i < orbs; i++) + { + angle_t ang = (i * angoff) + FixedAngle((leveltime * FRACUNIT) / 4); + + fixed_t x = battleovertime.x + P_ReturnThrustX(NULL, ang, posOffset); + fixed_t y = battleovertime.y + P_ReturnThrustY(NULL, ang, posOffset); + + K_SpawnOvertimeLaser(x, y, scale); + } +} + void K_RunBattleOvertime(void) { if (battleovertime.enabled < 10*TICRATE) @@ -784,29 +815,9 @@ void K_RunBattleOvertime(void) } } - if (battleovertime.radius > 0) + if (!P_LevelIsFrozen()) { - const INT32 orbs = 32; - const angle_t angoff = ANGLE_MAX / orbs; - const UINT8 spriteSpacing = 128; - - fixed_t circumference = FixedMul(M_PI_FIXED, battleovertime.radius * 2); - fixed_t scale = max(circumference / spriteSpacing / orbs, mapobjectscale); - - fixed_t size = FixedMul(mobjinfo[MT_OVERTIME_PARTICLE].radius, scale); - fixed_t posOffset = max(battleovertime.radius - size, 0); - - INT32 i; - - for (i = 0; i < orbs; i++) - { - angle_t ang = (i * angoff) + FixedAngle((leveltime * FRACUNIT) / 4); - - fixed_t x = battleovertime.x + P_ReturnThrustX(NULL, ang, posOffset); - fixed_t y = battleovertime.y + P_ReturnThrustY(NULL, ang, posOffset); - - K_SpawnOvertimeLaser(x, y, scale); - } + K_SpawnOvertimeBarrier(); } } diff --git a/src/k_battle.h b/src/k_battle.h index cb15921ae..bc894c150 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -45,6 +45,7 @@ mobj_t *K_SpawnSphereBox(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 f void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType); UINT8 K_NumEmeralds(player_t *player); void K_RunPaperItemSpawners(void); +void K_SpawnOvertimeBarrier(void); void K_RunBattleOvertime(void); void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj); void K_SpawnPlayerBattleBumpers(player_t *p); diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp index ec04351cb..802e0e263 100644 --- a/src/k_endcam.cpp +++ b/src/k_endcam.cpp @@ -15,6 +15,7 @@ #include "doomdef.h" #include "doomtype.h" #include "g_game.h" +#include "k_battle.h" #include "k_endcam.h" #include "m_easing.h" #include "p_local.h" @@ -168,6 +169,10 @@ EndCam& endcam_cast() void K_CommitEndCamera(void) { + // Level will be frozen, so make sure the lasers are + // spawned before that happens. + K_SpawnOvertimeBarrier(); + endcam_cast().Start(); } From d0057126e6d7436fedcdb83542a224d19b1e1887 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 22:06:21 -0800 Subject: [PATCH 19/28] Battle: make level darkness more dark by default --- src/p_tick.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index 309979cab..80559eddc 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -789,7 +789,8 @@ static fixed_t P_GetDarkness(tic_t start, tic_t end) t = end - leveltime; } - return Easing_Linear((t * FRACUNIT) / DARKNESS_FADE_TIME, 0, FRACUNIT/7); + return Easing_Linear((t * FRACUNIT) / DARKNESS_FADE_TIME, 0, + (gametyperules & GTR_BUMPERS) ? FRACUNIT/3 : FRACUNIT/7); } return 0; From 6f89dd9cc9d9c85833e0f04f07e5092f0b4a5175 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 18:37:42 -0800 Subject: [PATCH 20/28] End camera: darken level while spinning --- src/k_endcam.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp index 802e0e263..20cdf2848 100644 --- a/src/k_endcam.cpp +++ b/src/k_endcam.cpp @@ -13,6 +13,7 @@ #include "byteptr.h" #include "doomdef.h" +#include "doomstat.h" #include "doomtype.h" #include "g_game.h" #include "k_battle.h" @@ -218,4 +219,7 @@ void K_StartRoundWinCamera(mobj_t *origin, angle_t focusAngle, fixed_t finalRadi g_endcam.panSpeed = panSpeed; K_CommitEndCamera(); + + g_darkness.start = leveltime; + g_darkness.end = leveltime + g_endcam.swirlDuration + DARKNESS_FADE_TIME; } From 255552d14164bbfaf2c5b537ee26637273628098 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 20:01:21 -0800 Subject: [PATCH 21/28] Battle: make battle bumper burst animation fly a little lower --- src/p_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index 97b166def..349847180 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2338,7 +2338,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget P_Thrust(target, R_PointToAngle2(owner->x, owner->y, target->x, target->y), 4 * target->scale); } - target->momz += (24 * target->scale) * P_MobjFlip(target); + target->momz += (18 * target->scale) * P_MobjFlip(target); target->fuse = 8; overlay = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_OVERLAY); From 6b27a656e8f1ef3a8a300e5610fd9c348a72e29f Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 20:02:17 -0800 Subject: [PATCH 22/28] Polish player corpse physics - Flung a little higher into the air - Takes momentum of whatever killed the player - Bounces off walls - Bounces off the floor once - After bouncing off the floor once, corpse noclips through walls and floors --- src/p_inter.c | 17 ++++++++++++++--- src/p_map.c | 12 +++++++++++- src/p_mobj.c | 7 ++++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/p_inter.c b/src/p_inter.c index 349847180..b3433479f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1764,7 +1764,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block P_UnsetThingPosition(target); - target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; + target->flags |= MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOGRAVITY; P_SetThingPosition(target); target->standingslope = NULL; target->terrain = NULL; @@ -1967,6 +1967,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_PLAYER: if (damagetype != DMG_SPECTATOR) { + fixed_t flingSpeed = FixedHypot(target->momx, target->momy); angle_t flingAngle; mobj_t *kart; @@ -2019,8 +2020,18 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget // so make sure that this draws at the correct angle. target->rollangle = 0; - P_InstaThrust(target, flingAngle, 14 * target->scale); - P_SetObjectMomZ(target, 14*FRACUNIT, false); + fixed_t inflictorSpeed = 0; + if (!P_MobjWasRemoved(inflictor)) + { + inflictorSpeed = FixedHypot(inflictor->momx, inflictor->momy); + if (inflictorSpeed > flingSpeed) + { + flingSpeed = inflictorSpeed; + } + } + + P_InstaThrust(target, flingAngle, max(flingSpeed, 14 * target->scale)); + P_SetObjectMomZ(target, 20*FRACUNIT, false); P_PlayDeathSound(target); } diff --git a/src/p_map.c b/src/p_map.c index 6ad4cc239..7c7593cad 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -4140,7 +4140,12 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) ); } - if (mo->eflags & MFE_JUSTBOUNCEDWALL) // Stronger push-out + if (mo->health <= 0) + { + tmxmove = mo->momx; + tmymove = mo->momy; + } + else if (mo->eflags & MFE_JUSTBOUNCEDWALL) // Stronger push-out { tmxmove = mmomx; tmymove = mmomy; @@ -4177,6 +4182,11 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) mo->player->cmomx = tmxmove; mo->player->cmomy = tmymove; + if (mo->health <= 0) + { + mo->momz = 16 * mapobjectscale; + } + if (!bestslideline || !P_IsLineTripWire(bestslideline)) { if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL)) diff --git a/src/p_mobj.c b/src/p_mobj.c index dfe9008b8..1e7f4a65c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1969,7 +1969,7 @@ void P_XYMovement(mobj_t *mo) return; //} - if (((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz)) + if (((!(mo->eflags & MFE_VERTICALFLIP) && (mo->momz > 0 || mo->z > mo->floorz)) || (mo->eflags & MFE_VERTICALFLIP && (mo->momz < 0 || mo->z+mo->height < mo->ceilingz))) && !(player && player->carry == CR_SLIDING)) return; // no friction when airborne @@ -2501,6 +2501,11 @@ boolean P_ZMovement(mobj_t *mo) mom.x = mom.y = 0; mom.z = -mom.z/2; } + else if (mo->type == MT_PLAYER) // only DEAD players + { + mom.z = -mom.z; + mo->flags |= MF_NOCLIP | MF_NOCLIPHEIGHT; // fall through floor next time + } else if (mo->flags & MF_MISSILE) { if (!(mo->flags & MF_NOCLIP)) From 311cf5ceea161115b0ed0d33d764fff06b108a8d Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 21 Jan 2024 22:26:08 -0800 Subject: [PATCH 23/28] Battle: darken map for players who die during Overtime --- src/k_kart.c | 21 ++++++++++++--------- src/p_inter.c | 5 +++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index be7213d43..7334567a9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8921,19 +8921,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER); } - if (leveltime < player->darkness_end) + if (!player->exiting && !(player->pflags & PF_ELIMINATED)) { - if (leveltime > player->darkness_end - DARKNESS_FADE_TIME) + if (leveltime < player->darkness_end) { - player->darkness_start = leveltime - (player->darkness_end - leveltime); + if (leveltime > player->darkness_end - DARKNESS_FADE_TIME) + { + player->darkness_start = leveltime - (player->darkness_end - leveltime); + } + } + else + { + player->darkness_start = leveltime; } - } - else - { - player->darkness_start = leveltime; - } - player->darkness_end = leveltime + (2 * DARKNESS_FADE_TIME); + player->darkness_end = leveltime + (2 * DARKNESS_FADE_TIME); + } } } diff --git a/src/p_inter.c b/src/p_inter.c index b3433479f..6a7164745 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1799,6 +1799,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (battleovertime.enabled >= 10*TICRATE) // Overtime Barrier is armed { target->player->pflags |= PF_ELIMINATED; + if (target->player->darkness_end < leveltime) + { + target->player->darkness_start = leveltime; + } + target->player->darkness_end = INFTICS; } K_CheckBumpers(); From 6347afb63fe3660a4ca41f580e03e46506e4d92c Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 18 Jan 2024 02:01:18 -0800 Subject: [PATCH 24/28] srb2::Draw: add cache_patch static class method --- src/v_draw.cpp | 10 +++++----- src/v_draw.hpp | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/v_draw.cpp b/src/v_draw.cpp index edff06380..bd6aaf8f9 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -132,11 +132,6 @@ void Chain::patch(patch_t* patch) const V_DrawStretchyFixedPatch(FloatToFixed(x_), FloatToFixed(y_), h * scale_, v * scale_, flags_, patch, colormap_); } -void Chain::patch(const char* name) const -{ - patch(static_cast(W_CachePatchName(name, PU_CACHE))); -} - void Chain::thumbnail(UINT16 mapnum) const { const auto _ = Clipper(*this); @@ -240,6 +235,11 @@ Chain::Clipper::~Clipper() V_ClearClipRect(); } +patch_t* Draw::cache_patch(const char* name) +{ + return static_cast(W_CachePatchName(name, PU_CACHE)); +} + int Draw::font_to_fontno(Font font) { switch (font) diff --git a/src/v_draw.hpp b/src/v_draw.hpp index c21c3356b..a713b8d15 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -171,7 +171,7 @@ public: TextElement text() const { return TextElement().font(font_).flags(flags_); } void patch(patch_t* patch) const; - void patch(const char* name) const; + void patch(const char* name) const { patch(Draw::cache_patch(name)); } void thumbnail(UINT16 mapnum) const; @@ -217,6 +217,8 @@ public: friend Draw; }; + static patch_t* cache_patch(const char* name); + constexpr Draw() {} explicit Draw(float x, float y) : chain_(x, y) {} Draw(const Chain& chain) : chain_(chain) {} From 892d2c73a831bebc0fc4c618dc75680f84a9f07f Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 18 Jan 2024 02:03:34 -0800 Subject: [PATCH 25/28] srb2::Draw: add sticker and small_sticker methods - Can be aligned to place a wing on only the left or right sides, or both - Width is adjustable - Custom sticker support --- src/v_draw.cpp | 30 ++++++++++++++++++++++++++++++ src/v_draw.hpp | 6 ++++++ 2 files changed, 36 insertions(+) diff --git a/src/v_draw.cpp b/src/v_draw.cpp index bd6aaf8f9..0653e8941 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -219,6 +219,36 @@ void Chain::button_(Button type, int ver, std::optional press) const } } +void Chain::sticker(patch_t* end_graphic, UINT8 color) const +{ + const auto _ = Clipper(*this); + + INT32 x = x_; + INT32 y = y_; + INT32 width = width_; + INT32 flags = flags_ | V_FLIP; + + auto fill = [&](int x, int width) { V_DrawFill(x, y, width, SHORT(end_graphic->height), color | (flags_ & ~0xFF)); }; + + if (align_ == Align::kRight) + { + width = -(width); + flags ^= V_FLIP; + fill(x + width, -(width)); + } + else + { + fill(x, width); + } + + V_DrawScaledPatch(x + width, y, flags, end_graphic); + + if (align_ == Align::kCenter) + { + V_DrawScaledPatch(x, y, flags ^ V_FLIP, end_graphic); + } +} + Chain::Clipper::Clipper(const Chain& chain) { V_SetClipRect( diff --git a/src/v_draw.hpp b/src/v_draw.hpp index a713b8d15..468695e7e 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -180,6 +180,10 @@ public: void button(Button type, std::optional press = {}) const { button_(type, 0, press); } void small_button(Button type, std::optional press = {}) const { button_(type, 1, press); } + void sticker(patch_t* end_graphic, UINT8 color) const; + void sticker() const { sticker(Draw::cache_patch("K_STIKEN"), 24); } + void small_sticker() const { sticker(Draw::cache_patch("K_STIKE2"), 24); } + private: constexpr Chain() {} explicit Chain(float x, float y) : x_(x), y_(y) {} @@ -261,6 +265,8 @@ public: VOID_METHOD(fill); VOID_METHOD(button); VOID_METHOD(small_button); + VOID_METHOD(sticker); + VOID_METHOD(small_sticker); #undef VOID_METHOD From 3a8a7f3cf598fb5224d29fa03d1938752b1aa062 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 22 Jan 2024 16:37:35 -0800 Subject: [PATCH 26/28] Add thin timer font --- src/hu_stuff.c | 3 +++ src/hu_stuff.h | 1 + src/v_draw.cpp | 3 +++ src/v_draw.hpp | 1 + src/v_video.cpp | 3 +++ 5 files changed, 11 insertions(+) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 2c428df43..96acfd80f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -327,6 +327,9 @@ void HU_Init(void) PR ("TMFNT"); REG; + PR ("TMFNS"); + REG; + ADIM (LT); PR ("GAMEM"); REG; diff --git a/src/hu_stuff.h b/src/hu_stuff.h index f9efdbf1c..d7384f9f8 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -87,6 +87,7 @@ enum X (KART), X (TIMER), + X (TINYTIMER), X (GM), X (LSHI), X (LSLOW), diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 0653e8941..6a72639f3 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -295,6 +295,9 @@ int Draw::font_to_fontno(Font font) case Font::kTimer: return TIMER_FONT; + case Font::kThinTimer: + return TINYTIMER_FONT; + case Font::kMenu: return MENU_FONT; } diff --git a/src/v_draw.hpp b/src/v_draw.hpp index 468695e7e..9a9459d2e 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -38,6 +38,7 @@ public: kZVote, kPing, kTimer, + kThinTimer, kMenu, }; diff --git a/src/v_video.cpp b/src/v_video.cpp index 0e0e4477a..dd2ee68e9 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -2273,6 +2273,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) } break; case TINY_FONT: + case TINYTIMER_FONT: result->spacew = 2; switch (spacing) { @@ -2331,6 +2332,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) case HU_FONT: case MENU_FONT: case TINY_FONT: + case TINYTIMER_FONT: case KART_FONT: result->lfh = 12; break; @@ -2381,6 +2383,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) result->dim_fn = BunchedCharacterDim; break; case TINY_FONT: + case TINYTIMER_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else From 1cb306ac875b830f5fec092ffab6e5a4b9913d9c Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 18 Jan 2024 02:06:04 -0800 Subject: [PATCH 27/28] HUD: replace 4P bumpers/prisons sticker with srb2::Draw version The color is one shade off, but we'll see if it matters. --- src/k_hud.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index e3ed12f52..c1d01a725 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -3356,7 +3356,15 @@ static void K_drawKartBumpersOrKarma(void) } } - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); + { + using srb2::Draw; + int width = 39; + Draw(fx-1 + (flipflag ? width + 3 : 0), fy+1) + .flags(V_HUDTRANS|V_SLIDEIN|splitflags) + .align(flipflag ? Draw::Align::kRight : Draw::Align::kLeft) + .width(width) + .small_sticker(); + } fx += 2; From 1f4bebe6daaf4e6cb0722128daa35076a23743f8 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 17 Jan 2024 22:45:17 -0800 Subject: [PATCH 28/28] HUD: Battle points HUD (1P and 4P versions), displays right next to bumpers --- src/k_hud.cpp | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index c1d01a725..8931f7732 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -114,6 +114,7 @@ static patch_t *kp_rankcapsule; static patch_t *kp_rankemerald; static patch_t *kp_rankemeraldflash; static patch_t *kp_rankemeraldback; +static patch_t *kp_pts[2]; static patch_t *kp_goal[2][2]; // [skull][4p] static patch_t *kp_goalrod[2]; // [4p] @@ -451,7 +452,7 @@ void K_LoadKartHUDGraphics(void) // Extra ranking icons HU_UpdatePatch(&kp_rankbumper, "K_BLNICO"); - HU_UpdatePatch(&kp_bigbumper, "K_BLNBIG"); + HU_UpdatePatch(&kp_bigbumper, "K_BLNREG"); HU_UpdatePatch(&kp_tinybumper[0], "K_BLNA"); HU_UpdatePatch(&kp_tinybumper[1], "K_BLNB"); HU_UpdatePatch(&kp_ranknobumpers, "K_NOBLNS"); @@ -459,6 +460,8 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_rankemerald, "K_EMERC"); HU_UpdatePatch(&kp_rankemeraldflash, "K_EMERW"); HU_UpdatePatch(&kp_rankemeraldback, "K_EMERBK"); + HU_UpdatePatch(&kp_pts[0], "K_POINTS"); + HU_UpdatePatch(&kp_pts[1], "K_POINT4"); // Battle goal HU_UpdatePatch(&kp_goal[0][0], "K_ST1GLA"); @@ -3359,6 +3362,13 @@ static void K_drawKartBumpersOrKarma(void) { using srb2::Draw; int width = 39; + if (!battleprisons) + { + constexpr int kPad = 16; + if (flipflag) + fx -= kPad; + width += kPad; + } Draw(fx-1 + (flipflag ? width + 3 : 0), fy+1) .flags(V_HUDTRANS|V_SLIDEIN|splitflags) .align(flipflag ? Draw::Align::kRight : Draw::Align::kLeft) @@ -3400,12 +3410,18 @@ static void K_drawKartBumpersOrKarma(void) V_DrawMappedPatch(fx-1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap); - UINT8 ln[2]; - ln[0] = (bumpers / 10 % 10); - ln[1] = (bumpers % 10); - - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[ln[0]]); - V_DrawScaledPatch(fx+19, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[ln[1]]); + using srb2::Draw; + Draw row = Draw(fx+12, fy).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kPing); + row.text("{:02}", bumpers); + if (g_pointlimit <= stplyr->roundscore && leveltime % 8 < 4) + { + row = row.colorize(SKINCOLOR_TANGERINE); + } + row.xy(10, -2).patch(kp_pts[1]); + row + .x(31) + .flags(g_pointlimit <= stplyr->roundscore ? V_STRINGDANCE : 0) + .text("{:02}", stplyr->roundscore); } } else @@ -3429,9 +3445,21 @@ static void K_drawKartBumpersOrKarma(void) fy += 2; } - K_DrawSticker(LAPS_X+12, fy+5, bumpers > 9 ? 64 : 52, V_HUDTRANS|V_SLIDEIN|splitflags, false); - V_DrawMappedPatch(LAPS_X+15, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bigbumper, colormap); - V_DrawTimerString(LAPS_X+47, fy+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d", bumpers)); + K_DrawSticker(LAPS_X+12, fy+5, 75, V_HUDTRANS|V_SLIDEIN|splitflags, false); + V_DrawMappedPatch(LAPS_X+12, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bigbumper, colormap); + + using srb2::Draw; + Draw row = Draw(LAPS_X+12+23+1, fy+3).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer); + row.text("{:02}", bumpers); + if (g_pointlimit <= stplyr->roundscore && leveltime % 8 < 4) + { + row = row.colorize(SKINCOLOR_TANGERINE); + } + row.xy(12, -2).patch(kp_pts[0]); + row + .x(12+27) + .flags(g_pointlimit <= stplyr->roundscore ? V_STRINGDANCE : 0) + .text("{:02}", stplyr->roundscore); } } }