Merge branch 'battle-fuckers-thursday' into 'master'

Battle Fuckers Thursday Night Funkin' (but it's Monday-Sunday)

Closes #941, #950, #928, #951, #953, and #948

See merge request KartKrew/Kart!1863
This commit is contained in:
James R. 2024-01-23 00:46:16 +00:00
commit a4c6b3a077
29 changed files with 913 additions and 209 deletions

View file

@ -157,6 +157,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_dialogue.cpp k_dialogue.cpp
k_tally.cpp k_tally.cpp
k_bans.cpp k_bans.cpp
k_endcam.cpp
music.cpp music.cpp
music_manager.cpp music_manager.cpp
) )

133
src/archive_wrapper.hpp Normal file
View file

@ -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 <type_traits>
#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 <typename T, typename U>
void read(U& n) = delete;
template <typename T, typename U>
void write(const U& n) = delete;
private:
UINT8*& p_;
};
#define READ_SPEC(T, arg) \
template <> void ArchiveWrapperBase::read<T, T>(T& arg)
#define WRITE_SPEC(T, arg) \
template <> void ArchiveWrapperBase::write<T, T>(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<fixed_t>(n.x);
read<fixed_t>(n.y);
}
WRITE_SPEC(vector2_t, n)
{
write<fixed_t>(n.x);
write<fixed_t>(n.y);
}
READ_SPEC(vector3_t, n)
{
read<fixed_t>(n.x);
read<fixed_t>(n.y);
read<fixed_t>(n.z);
}
WRITE_SPEC(vector3_t, n)
{
write<fixed_t>(n.x);
write<fixed_t>(n.y);
write<fixed_t>(n.z);
}
// Specializations for boolean, so it can stored as char
// instead of int.
template <>
void ArchiveWrapperBase::read<bool, boolean>(boolean& n)
{
SRB2_ASSERT(n == true || n == false);
n = READUINT8(p_);
}
template <>
void ArchiveWrapperBase::write<bool, boolean>(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 <typename T, typename U>
void operator()(const U& n) { write<T, U>(n); }
};
struct UnArchiveWrapper : ArchiveWrapperBase
{
using ArchiveWrapperBase::ArchiveWrapperBase;
template <typename T, typename U>
void operator()(U& n) { read<T, U>(n); }
};
template <typename T>
struct is_archive_wrapper : std::is_base_of<ArchiveWrapperBase, T> {};
template <typename T>
inline constexpr bool is_archive_wrapper_v = is_archive_wrapper<T>::value;
#define SRB2_ARCHIVE_WRAPPER_CALL(ar, T, var) ((ar). template operator()<T>(var))
}; // namespace
#endif/*archive_wrapper_hpp*/

View file

@ -1975,7 +1975,7 @@ void G_Ticker(boolean run)
{ {
Music_Play("intermission"); Music_Play("intermission");
} }
else if (musiccountdown == MUSIC_COUNTDOWN_MAX - TALLY_TIME) else if (musiccountdown == MUSIC_COUNTDOWN_MAX - K_TallyDelay())
{ {
P_EndingMusic(); P_EndingMusic();
} }
@ -2607,22 +2607,12 @@ mapthing_t *G_FindBattleStart(INT32 playernum)
if (numdmstarts) if (numdmstarts)
{ {
extern consvar_t cv_battlespawn; for (j = 0; j < 64; j++)
if (cv_battlespawn.value)
{ {
i = cv_battlespawn.value - 1; i = P_RandomKey(PR_PLAYERSTARTS, numdmstarts);
if (i < numdmstarts) if (G_CheckSpot(playernum, deathmatchstarts[i]))
return 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) if (doprints)
CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Deathmatch starts!\n")); CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Deathmatch starts!\n"));
return NULL; return NULL;
@ -2796,14 +2786,20 @@ static inline mapthing_t *G_FindTeamStartOrFallback(INT32 playernum)
mapthing_t *G_FindMapStart(INT32 playernum) mapthing_t *G_FindMapStart(INT32 playernum)
{ {
extern consvar_t cv_battlespawn;
mapthing_t *spawnpoint; mapthing_t *spawnpoint;
if (!playeringame[playernum]) if (!playeringame[playernum])
return NULL; 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 -- // -- Podium --
// Single special behavior // Single special behavior
if (K_PodiumSequence() == true) else if (K_PodiumSequence() == true)
spawnpoint = G_FindPodiumStart(playernum); spawnpoint = G_FindPodiumStart(playernum);
// -- Time Attack / Battle duels -- // -- Time Attack / Battle duels --

View file

@ -327,6 +327,9 @@ void HU_Init(void)
PR ("TMFNT"); PR ("TMFNT");
REG; REG;
PR ("TMFNS");
REG;
ADIM (LT); ADIM (LT);
PR ("GAMEM"); PR ("GAMEM");
REG; REG;

View file

@ -87,6 +87,7 @@ enum
X (KART), X (KART),
X (TIMER), X (TIMER),
X (TINYTIMER),
X (GM), X (GM),
X (LSHI), X (LSHI),
X (LSLOW), X (LSLOW),

View file

@ -1,41 +1,90 @@
#include <algorithm>
#include "../v_draw.hpp" #include "../v_draw.hpp"
#include "../doomdef.h" #include "../doomdef.h"
#include "../i_time.h" #include "../i_time.h"
#include "../k_battle.h"
#include "../k_hud.h" #include "../k_hud.h"
#include "../m_easing.h"
#include "../m_fixed.h"
#include "../p_tick.h"
#include "../screen.h" #include "../screen.h"
using srb2::Draw; 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; 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(0, row.y()).size(BASEVIDWIDTH, 1).fill(35);
Draw top = row.y(-kYPad); Draw top = row.y(kTop);
Draw bot = row.xy(-kWidth / 2, kH + kYPad); 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<skincolornum_t>(SKINCOLOR_CHAOSEMERALD1 + n)).patch("EMRCA0"); row
.xy(x * kWidth, Easing_OutSine(interval(t, kDelay * n, kSlide), offscreen, 0))
.colormap(static_cast<skincolornum_t>(SKINCOLOR_CHAOSEMERALD1 + n))
.patch(emer);
}; };
put(top, -1, 3); put(top, -kTop - kMid, -1, 3);
put(top, 0, 0); put(top, -kTop - kMid, 0, 0);
put(top, 1, 4); put(top, -kTop - kMid, 1, 4);
put(bot, -1, 5); put(bot, 200 - kBot + kMid, -1, 5);
put(bot, 0, 1); put(bot, 200 - kBot + kMid, 0, 1);
put(bot, 1, 2); put(bot, 200 - kBot + kMid, 1, 2);
put(bot, 2, 6); put(bot, 200 - kBot + kMid, 2, 6);
} }

View file

@ -24,6 +24,8 @@
#include "music.h" #include "music.h"
#include "hu_stuff.h" #include "hu_stuff.h"
#include "m_easing.h" #include "m_easing.h"
#include "k_endcam.h"
#include "p_tick.h"
#define BARRIER_MIN_RADIUS (768 * mapobjectscale) #define BARRIER_MIN_RADIUS (768 * mapobjectscale)
@ -43,7 +45,7 @@ UINT8 maptargets = 0; // Capsules in map
UINT8 numtargets = 0; // Capsules busted UINT8 numtargets = 0; // Capsules busted
// Battle: someone won by collecting all 7 Chaos Emeralds // Battle: someone won by collecting all 7 Chaos Emeralds
boolean g_emeraldWin = false; tic_t g_emeraldWin = 0;
INT32 K_StartingBumperCount(void) INT32 K_StartingBumperCount(void)
{ {
@ -207,10 +209,32 @@ void K_CheckEmeralds(player_t *player)
return; return;
} }
if (player->exiting)
{
return;
}
player->roundscore = 100; // lmao player->roundscore = 100; // lmao
P_DoAllPlayersExit(0, false); 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);
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) UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType)
@ -540,7 +564,11 @@ void K_RunPaperItemSpawners(void)
//CONS_Printf("leveltime = %d ", leveltime); //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)]; const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)];
@ -700,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) void K_RunBattleOvertime(void)
{ {
if (battleovertime.enabled < 10*TICRATE) if (battleovertime.enabled < 10*TICRATE)
@ -757,29 +815,9 @@ void K_RunBattleOvertime(void)
} }
} }
if (battleovertime.radius > 0) if (!P_LevelIsFrozen())
{ {
const INT32 orbs = 32; K_SpawnOvertimeBarrier();
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);
}
} }
} }
@ -880,7 +918,7 @@ void K_BattleInit(boolean singleplayercontext)
g_battleufo.due = starttime; g_battleufo.due = starttime;
g_battleufo.previousId = Obj_RandomBattleUFOSpawnerID() - 1; g_battleufo.previousId = Obj_RandomBattleUFOSpawnerID() - 1;
g_emeraldWin = false; g_emeraldWin = 0;
} }
UINT8 K_Bumpers(player_t *player) UINT8 K_Bumpers(player_t *player)

View file

@ -13,8 +13,6 @@ extern "C" {
#define BATTLE_POWERUP_TIME (30*TICRATE) #define BATTLE_POWERUP_TIME (30*TICRATE)
#define BATTLE_UFO_TIME (20*TICRATE) #define BATTLE_UFO_TIME (20*TICRATE)
#define BATTLE_MONITOR_SPAWN_LIMIT (3)
extern struct battleovertime extern struct battleovertime
{ {
UINT16 enabled; ///< Has this been initalized yet? UINT16 enabled; ///< Has this been initalized yet?
@ -33,7 +31,7 @@ extern struct battleufo
extern boolean battleprisons; extern boolean battleprisons;
extern INT32 nummapboxes, numgotboxes; // keep track of spawned battle mode items extern INT32 nummapboxes, numgotboxes; // keep track of spawned battle mode items
extern UINT8 maptargets, numtargets; extern UINT8 maptargets, numtargets;
extern boolean g_emeraldWin; extern tic_t g_emeraldWin;
INT32 K_StartingBumperCount(void); INT32 K_StartingBumperCount(void);
boolean K_IsPlayerWanted(player_t *player); boolean K_IsPlayerWanted(player_t *player);
@ -47,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); void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType);
UINT8 K_NumEmeralds(player_t *player); UINT8 K_NumEmeralds(player_t *player);
void K_RunPaperItemSpawners(void); void K_RunPaperItemSpawners(void);
void K_SpawnOvertimeBarrier(void);
void K_RunBattleOvertime(void); void K_RunBattleOvertime(void);
void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj); void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj);
void K_SpawnPlayerBattleBumpers(player_t *p); void K_SpawnPlayerBattleBumpers(player_t *p);

View file

@ -744,6 +744,49 @@ void K_LightningShieldAttack(mobj_t *actor, fixed_t size)
P_BlockThingsIterator(bx, by, PIT_LightningShieldAttack); 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) boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2)
{ {
if (t2->type == MT_PLAYER) 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. // Don't play from t1 else it gets cut out... for some reason.
S_StartSound(t2, sfx_s3k44); S_StartSound(t2, sfx_s3k44);
} }
return true;
} }
else
if (K_BubbleShieldCanReflect(t1, t2))
{ {
mobj_t *owner = t1->player ? t1 : t1->target; return K_BubbleShieldReflect(t1, t2);
}
if (t2->target != owner || !t2->threshold || t2->type == MT_DROPTARGET)
{ if (t2->flags & MF_SHOOTABLE)
if (t1->player && K_PlayerGuard(t1->player)) {
{ P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
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);
}
} }
// no interaction
return true; return true;
} }
@ -838,8 +863,8 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
attacker->renderflags &= ~RF_DONTDRAW; attacker->renderflags &= ~RF_DONTDRAW;
angle_t thrangle = R_PointToAngle2(attacker->x, attacker->y, victim->x, victim->y); angle_t thrangle = R_PointToAngle2(attacker->x, attacker->y, victim->x, victim->y);
P_Thrust(victim, thrangle, FRACUNIT*7); P_Thrust(victim, thrangle, mapobjectscale*28);
P_Thrust(attacker, ANGLE_180 + thrangle, FRACUNIT*7); P_Thrust(attacker, ANGLE_180 + thrangle, mapobjectscale*28);
return false; return false;
} }
@ -907,7 +932,7 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
K_DropPowerUps(victimPlayer); K_DropPowerUps(victimPlayer);
angle_t thrangle = ANGLE_180 + R_PointToAngle2(victim->x, victim->y, shield->x, shield->y); 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(victim, victimHitlag, true);
K_AddHitLag(attacker, attackerHitlag, false); K_AddHitLag(attacker, attackerHitlag, false);

View file

@ -22,6 +22,9 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2);
boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2); boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2);
void K_LightningShieldAttack(mobj_t *actor, fixed_t size); 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_BubbleShieldCollide(mobj_t *t1, mobj_t *t2);
boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim); boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim);

225
src/k_endcam.cpp Normal file
View file

@ -0,0 +1,225 @@
// 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 <algorithm>
#include "archive_wrapper.hpp"
#include "byteptr.h"
#include "doomdef.h"
#include "doomstat.h"
#include "doomtype.h"
#include "g_game.h"
#include "k_battle.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&>(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 <typename T>
void Archive(T&& ar)
{
static_assert(srb2::is_archive_wrapper_v<T>);
#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<EndCam&>(g_endcam);
}
}; // namespace
void K_CommitEndCamera(void)
{
// Level will be frozen, so make sure the lasers are
// spawned before that happens.
K_SpawnOvertimeBarrier();
endcam_cast().Start();
}
void K_MoveEndCamera(camera_t *thiscam)
{
endcam_cast().Move(static_cast<Camera&>(*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));
}
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();
g_darkness.start = leveltime;
g_darkness.end = leveltime + g_endcam.swirlDuration + DARKNESS_FADE_TIME;
}

77
src/k_endcam.h Normal file
View file

@ -0,0 +1,77 @@
// 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);
// 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
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*/

View file

@ -260,6 +260,12 @@ void K_SetHitLagForObjects(mobj_t *victim, mobj_t *inflictor, mobj_t *source, IN
return; 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) if (P_MobjWasRemoved(victim) == false && P_MobjWasRemoved(inflictor) == false)
{ {
const fixed_t speedTicFactor = (mapobjectscale * 8); const fixed_t speedTicFactor = (mapobjectscale * 8);

View file

@ -114,6 +114,7 @@ static patch_t *kp_rankcapsule;
static patch_t *kp_rankemerald; static patch_t *kp_rankemerald;
static patch_t *kp_rankemeraldflash; static patch_t *kp_rankemeraldflash;
static patch_t *kp_rankemeraldback; static patch_t *kp_rankemeraldback;
static patch_t *kp_pts[2];
static patch_t *kp_goal[2][2]; // [skull][4p] static patch_t *kp_goal[2][2]; // [skull][4p]
static patch_t *kp_goalrod[2]; // [4p] static patch_t *kp_goalrod[2]; // [4p]
@ -451,7 +452,7 @@ void K_LoadKartHUDGraphics(void)
// Extra ranking icons // Extra ranking icons
HU_UpdatePatch(&kp_rankbumper, "K_BLNICO"); 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[0], "K_BLNA");
HU_UpdatePatch(&kp_tinybumper[1], "K_BLNB"); HU_UpdatePatch(&kp_tinybumper[1], "K_BLNB");
HU_UpdatePatch(&kp_ranknobumpers, "K_NOBLNS"); HU_UpdatePatch(&kp_ranknobumpers, "K_NOBLNS");
@ -459,6 +460,8 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_rankemerald, "K_EMERC"); HU_UpdatePatch(&kp_rankemerald, "K_EMERC");
HU_UpdatePatch(&kp_rankemeraldflash, "K_EMERW"); HU_UpdatePatch(&kp_rankemeraldflash, "K_EMERW");
HU_UpdatePatch(&kp_rankemeraldback, "K_EMERBK"); HU_UpdatePatch(&kp_rankemeraldback, "K_EMERBK");
HU_UpdatePatch(&kp_pts[0], "K_POINTS");
HU_UpdatePatch(&kp_pts[1], "K_POINT4");
// Battle goal // Battle goal
HU_UpdatePatch(&kp_goal[0][0], "K_ST1GLA"); HU_UpdatePatch(&kp_goal[0][0], "K_ST1GLA");
@ -3356,7 +3359,22 @@ 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;
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)
.width(width)
.small_sticker();
}
fx += 2; fx += 2;
@ -3392,12 +3410,18 @@ static void K_drawKartBumpersOrKarma(void)
V_DrawMappedPatch(fx-1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap); V_DrawMappedPatch(fx-1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap);
UINT8 ln[2]; using srb2::Draw;
ln[0] = (bumpers / 10 % 10); Draw row = Draw(fx+12, fy).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kPing);
ln[1] = (bumpers % 10); row.text("{:02}", bumpers);
if (g_pointlimit <= stplyr->roundscore && leveltime % 8 < 4)
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]]); 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 else
@ -3421,9 +3445,21 @@ static void K_drawKartBumpersOrKarma(void)
fy += 2; fy += 2;
} }
K_DrawSticker(LAPS_X+12, fy+5, bumpers > 9 ? 64 : 52, V_HUDTRANS|V_SLIDEIN|splitflags, false); K_DrawSticker(LAPS_X+12, fy+5, 75, V_HUDTRANS|V_SLIDEIN|splitflags, false);
V_DrawMappedPatch(LAPS_X+15, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bigbumper, colormap); V_DrawMappedPatch(LAPS_X+12, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bigbumper, colormap);
V_DrawTimerString(LAPS_X+47, fy+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d", bumpers));
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);
} }
} }
} }
@ -5851,10 +5887,16 @@ void K_drawKartHUD(void)
K_drawKartFirstPerson(); K_drawKartFirstPerson();
// Draw full screen stuff that turns off the rest of the HUD // Draw full screen stuff that turns off the rest of the HUD
if (mapreset && R_GetViewNumber() == 0) if (R_GetViewNumber() == 0)
{ {
K_drawChallengerScreen(); if (mapreset)
return; {
K_drawChallengerScreen();
return;
}
if (g_emeraldWin)
K_drawEmeraldWin(false);
} }
if (!demo.title) if (!demo.title)
@ -6069,15 +6111,15 @@ void K_drawKartHUD(void)
K_drawSpectatorHUD(false); K_drawSpectatorHUD(false);
} }
if (R_GetViewNumber() == 0 && g_emeraldWin)
K_drawEmeraldWin(true);
if (modeattacking || freecam) // everything after here is MP and debug only if (modeattacking || freecam) // everything after here is MP and debug only
return; return;
if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * 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); V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
if (g_emeraldWin)
K_drawEmeraldWin();
// Draw FREE PLAY. // Draw FREE PLAY.
K_drawKartFreePlay(); K_drawKartFreePlay();

View file

@ -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_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT32 splitflags, UINT8 mode);
void K_drawKart2PTimestamp(void); void K_drawKart2PTimestamp(void);
void K_drawKart4PTimestamp(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_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_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); void K_drawTargetHUD(const vector3_t *origin, player_t *player);

View file

@ -50,6 +50,7 @@
#include "k_tally.h" #include "k_tally.h"
#include "music.h" #include "music.h"
#include "m_easing.h" #include "m_easing.h"
#include "k_endcam.h"
// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
// gamespeed is cc (0 for easy, 1 for normal, 2 for hard) // gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
@ -3851,7 +3852,7 @@ void K_DoGuardBreak(mobj_t *t1, mobj_t *t2) {
K_AddMessageForPlayer(t2->player, "Smashed 'em!", false, false); K_AddMessageForPlayer(t2->player, "Smashed 'em!", false, false);
K_AddMessageForPlayer(t1->player, "BARRIER BREAK!!", 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_Thrust(t1, thrangle, 7*mapobjectscale);
P_DamageMobj(t1, t2, t2, 1, DMG_TUMBLE); P_DamageMobj(t1, t2, t2, 1, DMG_TUMBLE);
@ -3918,6 +3919,16 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN
{ {
player->roundscore = 100; // Make sure you win! player->roundscore = 100; // Make sure you win!
P_DoAllPlayersExit(0, false); 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); P_AddPlayerScore(player, points);
@ -8910,19 +8921,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER); 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);
}
} }
} }

View file

@ -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) if (game_over == true)
{ {
@ -1404,3 +1404,8 @@ boolean K_PlayerTallyActive(player_t *player)
{ {
return player->tally.active; //(player->exiting || (player->pflags & PF_NOCONTEST)); return player->tally.active; //(player->exiting || (player->pflags & PF_NOCONTEST));
} }
tic_t K_TallyDelay(void)
{
return ((gametyperules & GTR_BUMPERS) ? 4 : 3) * TICRATE;
}

View file

@ -20,8 +20,7 @@
#define TALLY_WINDOW_SIZE (2) #define TALLY_WINDOW_SIZE (2)
#define TALLY_TIME (3*TICRATE) #define MUSIC_COUNTDOWN_MAX (K_TallyDelay() + 8*TICRATE)
#define MUSIC_COUNTDOWN_MAX (TALLY_TIME + 8*TICRATE)
typedef enum typedef enum
{ {
@ -119,6 +118,7 @@ void K_InitPlayerTally(player_t *player);
void K_TickPlayerTally(player_t *player); void K_TickPlayerTally(player_t *player);
void K_DrawPlayerTally(void); void K_DrawPlayerTally(void);
boolean K_PlayerTallyActive(player_t *player); boolean K_PlayerTallyActive(player_t *player);
tic_t K_TallyDelay(void);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -560,6 +560,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
Obj_SetEmeraldAwardee(special, toucher); 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; return;
case MT_SPECIAL_UFO: case MT_SPECIAL_UFO:
if (Obj_UFOEmeraldCollect(special, toucher) == false) if (Obj_UFOEmeraldCollect(special, toucher) == false)
@ -1757,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 target->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block
P_UnsetThingPosition(target); P_UnsetThingPosition(target);
target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; target->flags |= MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOGRAVITY;
P_SetThingPosition(target); P_SetThingPosition(target);
target->standingslope = NULL; target->standingslope = NULL;
target->terrain = NULL; target->terrain = NULL;
@ -1792,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 if (battleovertime.enabled >= 10*TICRATE) // Overtime Barrier is armed
{ {
target->player->pflags |= PF_ELIMINATED; target->player->pflags |= PF_ELIMINATED;
if (target->player->darkness_end < leveltime)
{
target->player->darkness_start = leveltime;
}
target->player->darkness_end = INFTICS;
} }
K_CheckBumpers(); K_CheckBumpers();
@ -1960,6 +1972,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
case MT_PLAYER: case MT_PLAYER:
if (damagetype != DMG_SPECTATOR) if (damagetype != DMG_SPECTATOR)
{ {
fixed_t flingSpeed = FixedHypot(target->momx, target->momy);
angle_t flingAngle; angle_t flingAngle;
mobj_t *kart; mobj_t *kart;
@ -2012,8 +2025,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. // so make sure that this draws at the correct angle.
target->rollangle = 0; target->rollangle = 0;
P_InstaThrust(target, flingAngle, 14 * target->scale); fixed_t inflictorSpeed = 0;
P_SetObjectMomZ(target, 14*FRACUNIT, false); 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); P_PlayDeathSound(target);
} }
@ -2331,7 +2354,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); 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; target->fuse = 8;
overlay = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_OVERLAY); overlay = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_OVERLAY);
@ -2830,6 +2853,15 @@ static boolean P_FlashingException(const player_t *player, const mobj_t *inflict
return false; 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) if (!P_IsKartItem(inflictor->type) && inflictor->type != MT_PLAYER)
{ {
// Exception only applies to player items. // Exception only applies to player items.

View file

@ -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 // PIT_CheckThing
// //
@ -779,13 +768,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return BMIT_CONTINUE; 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) if (thing->health <= 0)
{ {
return BMIT_CONTINUE; // dead return BMIT_CONTINUE; // dead
@ -1009,7 +993,29 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if (tm.thing->type == MT_RANDOMITEM) if (tm.thing->type == MT_RANDOMITEM)
return BMIT_CONTINUE; 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 // see if it went over / under
if (tm.thing->z > thing->z + thing->height) if (tm.thing->z > thing->z + thing->height)
@ -1019,7 +1025,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return K_BubbleShieldCollide(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; 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 // see if it went over / under
if (tm.thing->z > thing->z + thing->height) if (tm.thing->z > thing->z + thing->height)
@ -1030,52 +1036,6 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return K_BubbleShieldCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; return K_BubbleShieldCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT;
} }
// Bubble Shield reflect
if ((thing->type == MT_BUBBLESHIELD && 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 && 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 // double make sure bubbles won't collide with anything else
if (thing->type == MT_BUBBLESHIELD || tm.thing->type == MT_BUBBLESHIELD) if (thing->type == MT_BUBBLESHIELD || tm.thing->type == MT_BUBBLESHIELD)
return BMIT_CONTINUE; return BMIT_CONTINUE;
@ -4180,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; tmxmove = mmomx;
tmymove = mmomy; tmymove = mmomy;
@ -4217,6 +4182,11 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
mo->player->cmomx = tmxmove; mo->player->cmomx = tmxmove;
mo->player->cmomy = tmymove; mo->player->cmomy = tmymove;
if (mo->health <= 0)
{
mo->momz = 16 * mapobjectscale;
}
if (!bestslideline || !P_IsLineTripWire(bestslideline)) if (!bestslideline || !P_IsLineTripWire(bestslideline))
{ {
if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL)) if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL))

View file

@ -1969,7 +1969,7 @@ void P_XYMovement(mobj_t *mo)
return; 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)) && !(player && player->carry == CR_SLIDING))
return; // no friction when airborne return; // no friction when airborne
@ -2501,6 +2501,11 @@ boolean P_ZMovement(mobj_t *mo)
mom.x = mom.y = 0; mom.x = mom.y = 0;
mom.z = -mom.z/2; 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) else if (mo->flags & MF_MISSILE)
{ {
if (!(mo->flags & MF_NOCLIP)) if (!(mo->flags & MF_NOCLIP))

View file

@ -47,6 +47,7 @@
#include "g_party.h" #include "g_party.h"
#include "k_vote.h" #include "k_vote.h"
#include "k_zvote.h" #include "k_zvote.h"
#include "k_endcam.h"
#include <tracy/tracy/TracyC.h> #include <tracy/tracy/TracyC.h>
@ -5745,6 +5746,12 @@ static void P_RelinkPointers(void)
P_LoadMobjPointers(RelinkMobjVoid); 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 // use info field (value = oldposition) to relink mobjs
for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
currentthinker = currentthinker->next) currentthinker = currentthinker->next)
@ -6471,7 +6478,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
WRITEINT32(save->p, numgotboxes); WRITEINT32(save->p, numgotboxes);
WRITEUINT8(save->p, numtargets); WRITEUINT8(save->p, numtargets);
WRITEUINT8(save->p, battleprisons); WRITEUINT8(save->p, battleprisons);
WRITEUINT8(save->p, g_emeraldWin); WRITEUINT32(save->p, g_emeraldWin);
WRITEUINT8(save->p, gamespeed); WRITEUINT8(save->p, gamespeed);
WRITEUINT8(save->p, numlaps); WRITEUINT8(save->p, numlaps);
@ -6657,7 +6664,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
numgotboxes = READINT32(save->p); numgotboxes = READINT32(save->p);
numtargets = READUINT8(save->p); numtargets = READUINT8(save->p);
battleprisons = (boolean)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); gamespeed = READUINT8(save->p);
numlaps = READUINT8(save->p); numlaps = READUINT8(save->p);
@ -6868,6 +6875,9 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending)
} }
} }
K_SaveEndCamera(save);
WriteMobjPointer(g_endcam.panMobj);
P_NetArchivePlayers(save); P_NetArchivePlayers(save);
P_NetArchiveParties(save); P_NetArchiveParties(save);
P_NetArchiveRoundQueue(save); P_NetArchiveRoundQueue(save);
@ -6933,6 +6943,9 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
if (!P_NetUnArchiveMisc(save, reloading)) if (!P_NetUnArchiveMisc(save, reloading))
return false; return false;
K_LoadEndCamera(save);
ReadMobjPointer(&g_endcam.panMobj);
P_NetUnArchivePlayers(save); P_NetUnArchivePlayers(save);
P_NetUnArchiveParties(save); P_NetUnArchiveParties(save);
P_NetUnArchiveRoundQueue(save); P_NetUnArchiveRoundQueue(save);

View file

@ -115,6 +115,7 @@
#include "music.h" #include "music.h"
#include "k_dialogue.h" #include "k_dialogue.h"
#include "k_hud.h" // K_ClearPersistentMessages #include "k_hud.h" // K_ClearPersistentMessages
#include "k_endcam.h"
// Replay names have time // Replay names have time
#if !defined (UNDER_CE) #if !defined (UNDER_CE)
@ -7696,6 +7697,8 @@ static void P_InitLevelSettings(void)
K_ResetSpecialStage(); K_ResetSpecialStage();
K_ResetBossInfo(); K_ResetBossInfo();
memset(&g_endcam, 0, sizeof g_endcam);
} }
#if 0 #if 0

View file

@ -48,6 +48,7 @@
#include "k_dialogue.h" #include "k_dialogue.h"
#include "m_easing.h" #include "m_easing.h"
#include "k_hud.h" // messagetimer #include "k_hud.h" // messagetimer
#include "k_endcam.h"
#include "lua_profile.h" #include "lua_profile.h"
@ -65,12 +66,12 @@ static boolean g_freezeLevel;
boolean P_LevelIsFrozen(void) boolean P_LevelIsFrozen(void)
{ {
return (g_freezeLevel || g_freezeCheat); return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing());
} }
boolean P_FreezeCheat(void) boolean P_FreezeCheat(void)
{ {
return (g_freezeLevel || g_freezeCheat); return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing());
} }
void P_SetFreezeCheat(boolean value) 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 // level totally frozen
return true; return true;
@ -767,6 +768,8 @@ void P_RunChaseCameras(void)
P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false); P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false);
} }
} }
K_EndCameraGC();
} }
static fixed_t P_GetDarkness(tic_t start, tic_t end) static fixed_t P_GetDarkness(tic_t start, tic_t end)
@ -786,7 +789,8 @@ static fixed_t P_GetDarkness(tic_t start, tic_t end)
t = end - leveltime; 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; return 0;

View file

@ -67,6 +67,7 @@
#include "music.h" #include "music.h"
#include "k_tally.h" #include "k_tally.h"
#include "k_objects.h" #include "k_objects.h"
#include "k_endcam.h"
#ifdef HWRENDER #ifdef HWRENDER
#include "hardware/hw_light.h" #include "hardware/hw_light.h"
@ -2837,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; playerGone = true;
} }
@ -3062,6 +3063,11 @@ void P_ToggleDemoCamera(UINT8 viewnum)
void P_ResetCamera(player_t *player, camera_t *thiscam) void P_ResetCamera(player_t *player, camera_t *thiscam)
{ {
if (g_endcam.active)
{
return;
}
tic_t tries = 0; tic_t tries = 0;
fixed_t x, y, z; fixed_t x, y, z;
@ -3155,7 +3161,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
num = 0; num = 0;
} }
if (thiscam->freecam || player->spectator) if ((thiscam->freecam || player->spectator) && !g_endcam.active)
{ {
P_DemoCameraMovement(thiscam, num); P_DemoCameraMovement(thiscam, num);
return true; return true;
@ -3164,6 +3170,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
if (paused || P_AutoPause()) if (paused || P_AutoPause())
return true; return true;
if (g_endcam.active)
{
K_MoveEndCamera(thiscam);
return true;
}
playerScale = FixedDiv(player->mo->scale, mapobjectscale); playerScale = FixedDiv(player->mo->scale, mapobjectscale);
scaleDiff = playerScale - FRACUNIT; scaleDiff = playerScale - FRACUNIT;

View file

@ -190,6 +190,9 @@ TYPEDEF (botcontroller_t);
// k_brightmap.h // k_brightmap.h
TYPEDEF (brightmapStorage_t); TYPEDEF (brightmapStorage_t);
// k_endcam.h
TYPEDEF (endcam_t);
// k_follower.h // k_follower.h
TYPEDEF (follower_t); TYPEDEF (follower_t);
TYPEDEF (followercategory_t); TYPEDEF (followercategory_t);

View file

@ -132,11 +132,6 @@ void Chain::patch(patch_t* patch) const
V_DrawStretchyFixedPatch(FloatToFixed(x_), FloatToFixed(y_), h * scale_, v * scale_, flags_, patch, colormap_); V_DrawStretchyFixedPatch(FloatToFixed(x_), FloatToFixed(y_), h * scale_, v * scale_, flags_, patch, colormap_);
} }
void Chain::patch(const char* name) const
{
patch(static_cast<patch_t*>(W_CachePatchName(name, PU_CACHE)));
}
void Chain::thumbnail(UINT16 mapnum) const void Chain::thumbnail(UINT16 mapnum) const
{ {
const auto _ = Clipper(*this); const auto _ = Clipper(*this);
@ -224,6 +219,36 @@ void Chain::button_(Button type, int ver, std::optional<bool> 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) Chain::Clipper::Clipper(const Chain& chain)
{ {
V_SetClipRect( V_SetClipRect(
@ -240,6 +265,11 @@ Chain::Clipper::~Clipper()
V_ClearClipRect(); V_ClearClipRect();
} }
patch_t* Draw::cache_patch(const char* name)
{
return static_cast<patch_t*>(W_CachePatchName(name, PU_CACHE));
}
int Draw::font_to_fontno(Font font) int Draw::font_to_fontno(Font font)
{ {
switch (font) switch (font)
@ -265,6 +295,9 @@ int Draw::font_to_fontno(Font font)
case Font::kTimer: case Font::kTimer:
return TIMER_FONT; return TIMER_FONT;
case Font::kThinTimer:
return TINYTIMER_FONT;
case Font::kMenu: case Font::kMenu:
return MENU_FONT; return MENU_FONT;
} }

View file

@ -38,6 +38,7 @@ public:
kZVote, kZVote,
kPing, kPing,
kTimer, kTimer,
kThinTimer,
kMenu, kMenu,
}; };
@ -171,7 +172,7 @@ public:
TextElement text() const { return TextElement().font(font_).flags(flags_); } TextElement text() const { return TextElement().font(font_).flags(flags_); }
void patch(patch_t* patch) const; 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; void thumbnail(UINT16 mapnum) const;
@ -180,6 +181,10 @@ public:
void button(Button type, std::optional<bool> press = {}) const { button_(type, 0, press); } void button(Button type, std::optional<bool> press = {}) const { button_(type, 0, press); }
void small_button(Button type, std::optional<bool> press = {}) const { button_(type, 1, press); } void small_button(Button type, std::optional<bool> 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: private:
constexpr Chain() {} constexpr Chain() {}
explicit Chain(float x, float y) : x_(x), y_(y) {} explicit Chain(float x, float y) : x_(x), y_(y) {}
@ -217,6 +222,8 @@ public:
friend Draw; friend Draw;
}; };
static patch_t* cache_patch(const char* name);
constexpr Draw() {} constexpr Draw() {}
explicit Draw(float x, float y) : chain_(x, y) {} explicit Draw(float x, float y) : chain_(x, y) {}
Draw(const Chain& chain) : chain_(chain) {} Draw(const Chain& chain) : chain_(chain) {}
@ -259,6 +266,8 @@ public:
VOID_METHOD(fill); VOID_METHOD(fill);
VOID_METHOD(button); VOID_METHOD(button);
VOID_METHOD(small_button); VOID_METHOD(small_button);
VOID_METHOD(sticker);
VOID_METHOD(small_sticker);
#undef VOID_METHOD #undef VOID_METHOD

View file

@ -2273,6 +2273,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result)
} }
break; break;
case TINY_FONT: case TINY_FONT:
case TINYTIMER_FONT:
result->spacew = 2; result->spacew = 2;
switch (spacing) switch (spacing)
{ {
@ -2331,6 +2332,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result)
case HU_FONT: case HU_FONT:
case MENU_FONT: case MENU_FONT:
case TINY_FONT: case TINY_FONT:
case TINYTIMER_FONT:
case KART_FONT: case KART_FONT:
result->lfh = 12; result->lfh = 12;
break; break;
@ -2381,6 +2383,7 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result)
result->dim_fn = BunchedCharacterDim; result->dim_fn = BunchedCharacterDim;
break; break;
case TINY_FONT: case TINY_FONT:
case TINYTIMER_FONT:
if (result->chw) if (result->chw)
result->dim_fn = FixedCharacterDim; result->dim_fn = FixedCharacterDim;
else else