mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Merge branch 'checkpoint' into 'master'
Checkpoint Star Posts See merge request KartKrew/Kart!1444
This commit is contained in:
commit
954fb272dd
15 changed files with 798 additions and 29 deletions
|
|
@ -742,6 +742,7 @@ struct player_t
|
||||||
UINT8 latestlap;
|
UINT8 latestlap;
|
||||||
UINT32 lapPoints; // Points given from laps
|
UINT32 lapPoints; // Points given from laps
|
||||||
INT32 cheatchecknum; // The number of the last cheatcheck you hit
|
INT32 cheatchecknum; // The number of the last cheatcheck you hit
|
||||||
|
INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp
|
||||||
|
|
||||||
UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue
|
UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4653,6 +4653,22 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
|
||||||
"S_BATTLEUFO_BEAM2",
|
"S_BATTLEUFO_BEAM2",
|
||||||
|
|
||||||
"S_POWERUP_AURA",
|
"S_POWERUP_AURA",
|
||||||
|
|
||||||
|
"S_CHECKPOINT",
|
||||||
|
"S_CHECKPOINT_ARM",
|
||||||
|
"S_CHECKPOINT_ORB_DEAD",
|
||||||
|
"S_CHECKPOINT_ORB_LIVE",
|
||||||
|
"S_CHECKPOINT_SPARK1",
|
||||||
|
"S_CHECKPOINT_SPARK2",
|
||||||
|
"S_CHECKPOINT_SPARK3",
|
||||||
|
"S_CHECKPOINT_SPARK4",
|
||||||
|
"S_CHECKPOINT_SPARK5",
|
||||||
|
"S_CHECKPOINT_SPARK6",
|
||||||
|
"S_CHECKPOINT_SPARK7",
|
||||||
|
"S_CHECKPOINT_SPARK8",
|
||||||
|
"S_CHECKPOINT_SPARK9",
|
||||||
|
"S_CHECKPOINT_SPARK10",
|
||||||
|
"S_CHECKPOINT_SPARK11",
|
||||||
};
|
};
|
||||||
|
|
||||||
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
|
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
|
||||||
|
|
@ -5803,6 +5819,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
|
||||||
|
|
||||||
"MT_POWERUP_AURA",
|
"MT_POWERUP_AURA",
|
||||||
|
|
||||||
|
"MT_CHECKPOINT_END",
|
||||||
"MT_SCRIPT_THING",
|
"MT_SCRIPT_THING",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -5969,6 +5986,7 @@ const char *const GAMETYPERULE_LIST[] = {
|
||||||
"KARMA",
|
"KARMA",
|
||||||
"ITEMARROWS",
|
"ITEMARROWS",
|
||||||
|
|
||||||
|
"CHECKPOINTS",
|
||||||
"PRISONS",
|
"PRISONS",
|
||||||
"CATCHER",
|
"CATCHER",
|
||||||
"ROLLINGSTART",
|
"ROLLINGSTART",
|
||||||
|
|
|
||||||
|
|
@ -622,25 +622,26 @@ enum GameTypeRules
|
||||||
GTR_ITEMARROWS = 1<<9, // Show item box arrows above players
|
GTR_ITEMARROWS = 1<<9, // Show item box arrows above players
|
||||||
|
|
||||||
// Bonus gametype rules
|
// Bonus gametype rules
|
||||||
GTR_PRISONS = 1<<10, // Can enter Prison Break mode
|
GTR_CHECKPOINTS = 1<<10, // Player respawns at specific checkpoints
|
||||||
GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT)
|
GTR_PRISONS = 1<<11, // Can enter Prison Break mode
|
||||||
GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT)
|
GTR_CATCHER = 1<<12, // UFO Catcher (only works with GTR_CIRCUIT)
|
||||||
GTR_SPECIALSTART = 1<<13, // White fade instant start
|
GTR_ROLLINGSTART = 1<<13, // Rolling start (only works with GTR_CIRCUIT)
|
||||||
GTR_BOSS = 1<<14, // Boss intro and spawning
|
GTR_SPECIALSTART = 1<<14, // White fade instant start
|
||||||
|
GTR_BOSS = 1<<15, // Boss intro and spawning
|
||||||
|
|
||||||
// General purpose rules
|
// General purpose rules
|
||||||
GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round
|
GTR_POINTLIMIT = 1<<16, // Reaching point limit ends the round
|
||||||
GTR_TIMELIMIT = 1<<16, // Reaching time limit ends the round
|
GTR_TIMELIMIT = 1<<17, // Reaching time limit ends the round
|
||||||
GTR_OVERTIME = 1<<17, // Allow overtime behavior
|
GTR_OVERTIME = 1<<18, // Allow overtime behavior
|
||||||
GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping
|
GTR_ENCORE = 1<<19, // Alternate Encore mirroring, scripting, and texture remapping
|
||||||
|
|
||||||
GTR_TEAMS = 1<<19, // Teams are forced on
|
GTR_TEAMS = 1<<20, // Teams are forced on
|
||||||
GTR_NOTEAMS = 1<<20, // Teams are forced off
|
GTR_NOTEAMS = 1<<21, // Teams are forced off
|
||||||
GTR_TEAMSTARTS = 1<<21, // Use team-based start positions
|
GTR_TEAMSTARTS = 1<<22, // Use team-based start positions
|
||||||
|
|
||||||
GTR_NOMP = 1<<22, // No multiplayer
|
GTR_NOMP = 1<<23, // No multiplayer
|
||||||
GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup.
|
GTR_NOCUPSELECT = 1<<24, // Your maps are not selected via cup.
|
||||||
GTR_NOPOSITION = 1<<24, // No POSITION
|
GTR_NOPOSITION = 1<<25, // No POSITION
|
||||||
|
|
||||||
// free: to and including 1<<31
|
// free: to and including 1<<31
|
||||||
};
|
};
|
||||||
|
|
|
||||||
49
src/g_game.c
49
src/g_game.c
|
|
@ -71,6 +71,7 @@
|
||||||
#include "k_zvote.h"
|
#include "k_zvote.h"
|
||||||
#include "music.h"
|
#include "music.h"
|
||||||
#include "k_roulette.h"
|
#include "k_roulette.h"
|
||||||
|
#include "k_objects.h"
|
||||||
|
|
||||||
#ifdef HAVE_DISCORDRPC
|
#ifdef HAVE_DISCORDRPC
|
||||||
#include "discord.h"
|
#include "discord.h"
|
||||||
|
|
@ -2044,6 +2045,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
UINT16 nocontrol;
|
UINT16 nocontrol;
|
||||||
INT32 khudfault;
|
INT32 khudfault;
|
||||||
INT32 kickstartaccel;
|
INT32 kickstartaccel;
|
||||||
|
INT32 checkpointId;
|
||||||
boolean enteredGame;
|
boolean enteredGame;
|
||||||
|
|
||||||
roundconditions_t roundconditions;
|
roundconditions_t roundconditions;
|
||||||
|
|
@ -2249,6 +2251,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
skyboxviewpoint = skyboxcenterpoint = NULL;
|
skyboxviewpoint = skyboxcenterpoint = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkpointId = players[player].checkpointId;
|
||||||
|
|
||||||
enteredGame = players[player].enteredGame;
|
enteredGame = players[player].enteredGame;
|
||||||
|
|
||||||
p = &players[player];
|
p = &players[player];
|
||||||
|
|
@ -2305,6 +2309,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
p->karthud[khud_fault] = khudfault;
|
p->karthud[khud_fault] = khudfault;
|
||||||
p->nocontrol = nocontrol;
|
p->nocontrol = nocontrol;
|
||||||
p->kickstartaccel = kickstartaccel;
|
p->kickstartaccel = kickstartaccel;
|
||||||
|
p->checkpointId = checkpointId;
|
||||||
|
|
||||||
p->ringvolume = 255;
|
p->ringvolume = 255;
|
||||||
|
|
||||||
|
|
@ -2453,19 +2458,40 @@ void G_SpawnPlayer(INT32 playernum)
|
||||||
|
|
||||||
void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum)
|
void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum)
|
||||||
{
|
{
|
||||||
#if 0
|
|
||||||
if (leveltime <= introtime && !players[playernum].spectator)
|
|
||||||
P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
|
|
||||||
else
|
|
||||||
P_MovePlayerToCheatcheck(playernum);
|
|
||||||
#else
|
|
||||||
// Player's first spawn should be at the "map start".
|
// Player's first spawn should be at the "map start".
|
||||||
// I.e. level load or join mid game.
|
// I.e. level load or join mid game.
|
||||||
if (leveltime > starttime && players[playernum].jointime > 1 && K_PodiumSequence() == false)
|
if (leveltime > starttime && players[playernum].jointime > 1 && K_PodiumSequence() == false)
|
||||||
|
{
|
||||||
P_MovePlayerToCheatcheck(playernum);
|
P_MovePlayerToCheatcheck(playernum);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
|
{
|
||||||
#endif
|
mobj_t *checkpoint;
|
||||||
|
vector3_t pos;
|
||||||
|
|
||||||
|
if ((gametyperules & GTR_CHECKPOINTS)
|
||||||
|
&& players[playernum].checkpointId
|
||||||
|
&& (checkpoint = Obj_FindCheckpoint(players[playernum].checkpointId))
|
||||||
|
&& Obj_GetCheckpointRespawnPosition(checkpoint, &pos))
|
||||||
|
{
|
||||||
|
respawnvars_t *rsp = &players[playernum].respawn;
|
||||||
|
|
||||||
|
rsp->wp = NULL;
|
||||||
|
rsp->pointx = pos.x;
|
||||||
|
rsp->pointy = pos.y;
|
||||||
|
rsp->pointz = pos.z;
|
||||||
|
|
||||||
|
players[playernum].mo->angle = Obj_GetCheckpointRespawnAngle(checkpoint);
|
||||||
|
|
||||||
|
Obj_ActivateCheckpointInstantly(checkpoint);
|
||||||
|
|
||||||
|
P_MovePlayerToCheatcheck(playernum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapthing_t *G_FindTeamStart(INT32 playernum)
|
mapthing_t *G_FindTeamStart(INT32 playernum)
|
||||||
|
|
@ -3032,7 +3058,7 @@ static gametype_t defaultgametypes[] =
|
||||||
{
|
{
|
||||||
"Tutorial",
|
"Tutorial",
|
||||||
"GT_TUTORIAL",
|
"GT_TUTORIAL",
|
||||||
GTR_NOMP|GTR_NOCUPSELECT|GTR_NOPOSITION,
|
GTR_CHECKPOINTS|GTR_NOMP|GTR_NOCUPSELECT|GTR_NOPOSITION,
|
||||||
TOL_TUTORIAL,
|
TOL_TUTORIAL,
|
||||||
int_none,
|
int_none,
|
||||||
0,
|
0,
|
||||||
|
|
@ -5669,6 +5695,11 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
|
||||||
players[i].totalring = 0;
|
players[i].totalring = 0;
|
||||||
players[i].score = 0;
|
players[i].score = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resetplayer || map != gamemap)
|
||||||
|
{
|
||||||
|
players[i].checkpointId = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear itemfinder, just in case
|
// clear itemfinder, just in case
|
||||||
|
|
|
||||||
47
src/info.c
47
src/info.c
|
|
@ -888,6 +888,10 @@ char sprnames[NUMSPRITES + 1][5] =
|
||||||
|
|
||||||
"BUFO", // Battle/Power-UP UFO
|
"BUFO", // Battle/Power-UP UFO
|
||||||
|
|
||||||
|
"CPT1", // Checkpoint Orb
|
||||||
|
"CPT2", // Checkpoint Stick
|
||||||
|
"CPT3", // Checkpoint Base
|
||||||
|
|
||||||
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
||||||
"VIEW",
|
"VIEW",
|
||||||
};
|
};
|
||||||
|
|
@ -5394,6 +5398,22 @@ state_t states[NUMSTATES] =
|
||||||
{SPR_DEZL, 3|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BATTLEUFO_BEAM2
|
{SPR_DEZL, 3|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BATTLEUFO_BEAM2
|
||||||
|
|
||||||
{SPR_RBOW, FF_PAPERSPRITE|FF_ADD|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 14, 2, S_NULL}, // S_POWERUP_AURA
|
{SPR_RBOW, FF_PAPERSPRITE|FF_ADD|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 14, 2, S_NULL}, // S_POWERUP_AURA
|
||||||
|
|
||||||
|
{SPR_CPT3, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CHECKPOINT
|
||||||
|
{SPR_CPT2, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_CHECKPOINT_ARM
|
||||||
|
{SPR_CPT1, FF_ADD|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_CHECKPOINT_ORB_DEAD
|
||||||
|
{SPR_CPT1, FF_ADD|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_CHECKPOINT_ORB_LIVE
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK2}, // S_CHECKPOINT_SPARK1
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK3}, // S_CHECKPOINT_SPARK2
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK4}, // S_CHECKPOINT_SPARK3
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK5}, // S_CHECKPOINT_SPARK4
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK6}, // S_CHECKPOINT_SPARK5
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|5, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK7}, // S_CHECKPOINT_SPARK6
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|6, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK8}, // S_CHECKPOINT_SPARK7
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|7, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK9}, // S_CHECKPOINT_SPARK8
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|8, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK10}, // S_CHECKPOINT_SPARK9
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK11}, // S_CHECKPOINT_SPARK10
|
||||||
|
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_CHECKPOINT_SPARK1}, // S_CHECKPOINT_SPARK11
|
||||||
};
|
};
|
||||||
|
|
||||||
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
|
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
|
||||||
|
|
@ -30305,6 +30325,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
|
||||||
S_NULL // raisestate
|
S_NULL // raisestate
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ // MT_CHECKPOINT_END
|
||||||
|
2030, // doomednum
|
||||||
|
S_CHECKPOINT, // spawnstate
|
||||||
|
1000, // spawnhealth
|
||||||
|
S_NULL, // seestate
|
||||||
|
sfx_None, // seesound
|
||||||
|
0, // reactiontime
|
||||||
|
sfx_None, // attacksound
|
||||||
|
S_NULL, // painstate
|
||||||
|
0, // painchance
|
||||||
|
sfx_None, // painsound
|
||||||
|
S_NULL, // meleestate
|
||||||
|
S_NULL, // missilestate
|
||||||
|
S_NULL, // deathstate
|
||||||
|
S_NULL, // xdeathstate
|
||||||
|
sfx_None, // deathsound
|
||||||
|
0, // speed
|
||||||
|
19*FRACUNIT, // radius
|
||||||
|
75*FRACUNIT, // height
|
||||||
|
0, // display offset
|
||||||
|
0, // mass
|
||||||
|
0, // damage
|
||||||
|
sfx_None, // activesound
|
||||||
|
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY|MF_DONTENCOREMAP, // flags
|
||||||
|
S_NULL // raisestate
|
||||||
|
},
|
||||||
|
|
||||||
{ // MT_SCRIPT_THING
|
{ // MT_SCRIPT_THING
|
||||||
4096, // doomednum
|
4096, // doomednum
|
||||||
S_INVISIBLE, // spawnstate
|
S_INVISIBLE, // spawnstate
|
||||||
|
|
|
||||||
21
src/info.h
21
src/info.h
|
|
@ -1442,6 +1442,10 @@ typedef enum sprite
|
||||||
|
|
||||||
SPR_BUFO, // Battle/Power-UP UFO
|
SPR_BUFO, // Battle/Power-UP UFO
|
||||||
|
|
||||||
|
SPR_CPT1, // Checkpoint Orb
|
||||||
|
SPR_CPT2, // Checkpoint Stick
|
||||||
|
SPR_CPT3, // Checkpoint Base
|
||||||
|
|
||||||
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
||||||
SPR_VIEW,
|
SPR_VIEW,
|
||||||
|
|
||||||
|
|
@ -5823,6 +5827,22 @@ typedef enum state
|
||||||
|
|
||||||
S_POWERUP_AURA,
|
S_POWERUP_AURA,
|
||||||
|
|
||||||
|
S_CHECKPOINT,
|
||||||
|
S_CHECKPOINT_ARM,
|
||||||
|
S_CHECKPOINT_ORB_DEAD,
|
||||||
|
S_CHECKPOINT_ORB_LIVE,
|
||||||
|
S_CHECKPOINT_SPARK1,
|
||||||
|
S_CHECKPOINT_SPARK2,
|
||||||
|
S_CHECKPOINT_SPARK3,
|
||||||
|
S_CHECKPOINT_SPARK4,
|
||||||
|
S_CHECKPOINT_SPARK5,
|
||||||
|
S_CHECKPOINT_SPARK6,
|
||||||
|
S_CHECKPOINT_SPARK7,
|
||||||
|
S_CHECKPOINT_SPARK8,
|
||||||
|
S_CHECKPOINT_SPARK9,
|
||||||
|
S_CHECKPOINT_SPARK10,
|
||||||
|
S_CHECKPOINT_SPARK11,
|
||||||
|
|
||||||
S_FIRSTFREESLOT,
|
S_FIRSTFREESLOT,
|
||||||
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
|
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
|
||||||
NUMSTATES
|
NUMSTATES
|
||||||
|
|
@ -6991,6 +7011,7 @@ typedef enum mobj_type
|
||||||
|
|
||||||
MT_POWERUP_AURA,
|
MT_POWERUP_AURA,
|
||||||
|
|
||||||
|
MT_CHECKPOINT_END,
|
||||||
MT_SCRIPT_THING,
|
MT_SCRIPT_THING,
|
||||||
|
|
||||||
MT_FIRSTFREESLOT,
|
MT_FIRSTFREESLOT,
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,17 @@ void Obj_EmeraldFlareThink(mobj_t *flare);
|
||||||
void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse);
|
void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse);
|
||||||
void Obj_GiveEmerald(mobj_t *emerald);
|
void Obj_GiveEmerald(mobj_t *emerald);
|
||||||
|
|
||||||
|
/* Checkpoints */
|
||||||
|
void Obj_ResetCheckpoints(void);
|
||||||
|
void Obj_LinkCheckpoint(mobj_t *end);
|
||||||
|
void Obj_UnlinkCheckpoint(mobj_t *end);
|
||||||
|
void Obj_CheckpointThink(mobj_t *end);
|
||||||
|
void Obj_CrossCheckpoints(player_t *player, fixed_t old_x, fixed_t old_y);
|
||||||
|
mobj_t *Obj_FindCheckpoint(INT32 id);
|
||||||
|
boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *return_pos);
|
||||||
|
angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint);
|
||||||
|
void Obj_ActivateCheckpointInstantly(mobj_t* mobj);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,5 @@ target_sources(SRB2SDL2 PRIVATE
|
||||||
dash-rings.c
|
dash-rings.c
|
||||||
sneaker-panel.c
|
sneaker-panel.c
|
||||||
emerald.c
|
emerald.c
|
||||||
|
checkpoint.cpp
|
||||||
)
|
)
|
||||||
|
|
|
||||||
598
src/objects/checkpoint.cpp
Normal file
598
src/objects/checkpoint.cpp
Normal file
|
|
@ -0,0 +1,598 @@
|
||||||
|
// DR. ROBOTNIK'S RING RACERS
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Copyright (C) 2023 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 <vector>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "../doomdef.h"
|
||||||
|
#include "../doomtype.h"
|
||||||
|
#include "../info.h"
|
||||||
|
#include "../k_color.h"
|
||||||
|
#include "../k_kart.h"
|
||||||
|
#include "../k_objects.h"
|
||||||
|
#include "../m_bbox.h"
|
||||||
|
#include "../m_fixed.h"
|
||||||
|
#include "../m_random.h"
|
||||||
|
#include "../p_local.h"
|
||||||
|
#include "../p_maputl.h"
|
||||||
|
#include "../p_mobj.h"
|
||||||
|
#include "../p_setup.h"
|
||||||
|
#include "../p_tick.h"
|
||||||
|
#include "../r_defs.h"
|
||||||
|
#include "../r_main.h"
|
||||||
|
#include "../s_sound.h"
|
||||||
|
#include "../sounds.h"
|
||||||
|
#include "../tables.h"
|
||||||
|
|
||||||
|
#define checkpoint_id(o) ((o)->thing_args[0])
|
||||||
|
#define checkpoint_other(o) ((o)->target)
|
||||||
|
#define checkpoint_orb(o) ((o)->tracer)
|
||||||
|
#define checkpoint_arm(o) ((o)->hnext)
|
||||||
|
#define checkpoint_var(o) ((o)->movedir)
|
||||||
|
#define checkpoint_speed(o) ((o)->movecount)
|
||||||
|
#define checkpoint_speed_multiplier(o) ((o)->movefactor)
|
||||||
|
#define checkpoint_reverse(o) ((o)->reactiontime)
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct LineOnDemand : line_t
|
||||||
|
{
|
||||||
|
LineOnDemand(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) :
|
||||||
|
line_t {
|
||||||
|
.v1 = &v1_data_,
|
||||||
|
.dx = x2 - x1,
|
||||||
|
.dy = y2 - y1,
|
||||||
|
.bbox = {std::max(y1, y2), std::min(y1, y2), std::min(x1, x2), std::max(x1, x2)},
|
||||||
|
},
|
||||||
|
v1_data_ {.x = x1, .y = y1}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LineOnDemand(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, fixed_t r) : LineOnDemand(x1, y1, x2, y2)
|
||||||
|
{
|
||||||
|
bbox[BOXTOP] += r;
|
||||||
|
bbox[BOXBOTTOM] -= r;
|
||||||
|
bbox[BOXLEFT] -= r;
|
||||||
|
bbox[BOXRIGHT] += r;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overlaps(const LineOnDemand& other) const
|
||||||
|
{
|
||||||
|
return bbox[BOXTOP] >= other.bbox[BOXBOTTOM] && bbox[BOXBOTTOM] <= other.bbox[BOXTOP] &&
|
||||||
|
bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
vertex_t v1_data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Checkpoint : mobj_t
|
||||||
|
{
|
||||||
|
static constexpr int kArmLength = 59;
|
||||||
|
static constexpr int kOrbRadius = 21;
|
||||||
|
static constexpr int kCookieRadius = 19;
|
||||||
|
static constexpr int kOrbitDistance = kCookieRadius + kArmLength + kOrbRadius;
|
||||||
|
static constexpr fixed_t kBaseSpeed = FRACUNIT/35;
|
||||||
|
static constexpr fixed_t kMinSpeedMultiplier = FRACUNIT/2;
|
||||||
|
static constexpr fixed_t kMaxSpeedMultiplier = 3*FRACUNIT/2;
|
||||||
|
static constexpr fixed_t kSpeedMultiplierRange = kMaxSpeedMultiplier - kMinSpeedMultiplier;
|
||||||
|
static constexpr fixed_t kMinPivotDelay = FRACUNIT/2;
|
||||||
|
static constexpr int kSparkleOffset = 10;
|
||||||
|
static constexpr int kSparkleZ = 34;
|
||||||
|
static constexpr int kSparkleRadius = 12;
|
||||||
|
static constexpr int kSparkleAroundRadius = 128;
|
||||||
|
static constexpr int kSparkleAroundCircumfrence = kSparkleAroundRadius * M_TAU_FIXED;
|
||||||
|
static constexpr int kSparkleAroundCount = kSparkleAroundCircumfrence / kSparkleRadius / FRACUNIT;
|
||||||
|
|
||||||
|
struct Orb : mobj_t
|
||||||
|
{
|
||||||
|
void afterimages()
|
||||||
|
{
|
||||||
|
mobj_t* ghost = P_SpawnGhostMobj(this);
|
||||||
|
|
||||||
|
// Flickers every frame
|
||||||
|
ghost->extravalue1 = 1;
|
||||||
|
ghost->extravalue2 = 2;
|
||||||
|
|
||||||
|
ghost->tics = 8;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arm : mobj_t {};
|
||||||
|
|
||||||
|
INT32 id() const { return checkpoint_id(this); }
|
||||||
|
|
||||||
|
Checkpoint* other() const { return static_cast<Checkpoint*>(checkpoint_other(this)); }
|
||||||
|
void other(Checkpoint* n) { P_SetTarget(&checkpoint_other(this), n); }
|
||||||
|
|
||||||
|
Orb* orb() const { return static_cast<Orb*>(checkpoint_orb(this)); }
|
||||||
|
void orb(Orb* n) { P_SetTarget(&checkpoint_orb(this), n); }
|
||||||
|
|
||||||
|
Arm* arm() const { return static_cast<Arm*>(checkpoint_arm(this)); }
|
||||||
|
void arm(Arm* n) { P_SetTarget(&checkpoint_arm(this), n); }
|
||||||
|
|
||||||
|
fixed_t var() const { return checkpoint_var(this); }
|
||||||
|
void var(fixed_t n) { checkpoint_var(this) = n; }
|
||||||
|
|
||||||
|
fixed_t speed() const { return checkpoint_speed(this); }
|
||||||
|
void speed(fixed_t n) { checkpoint_speed(this) = n; }
|
||||||
|
|
||||||
|
fixed_t speed_multiplier() const { return checkpoint_speed_multiplier(this); }
|
||||||
|
void speed_multiplier(fixed_t n) { checkpoint_speed_multiplier(this) = n; }
|
||||||
|
|
||||||
|
bool reverse() const { return checkpoint_reverse(this); }
|
||||||
|
void reverse(bool n) { checkpoint_reverse(this) = n; }
|
||||||
|
|
||||||
|
// Valid to use as an alpha.
|
||||||
|
bool valid() const
|
||||||
|
{
|
||||||
|
auto f = [](const mobj_t* th) { return !P_MobjWasRemoved(th); };
|
||||||
|
return f(this) && f(other()) && f(orb()) && f(arm());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool activated() const { return var(); }
|
||||||
|
|
||||||
|
// Line between A and B things.
|
||||||
|
LineOnDemand crossing_line() const { return LineOnDemand(x, y, other()->x, other()->y, radius); }
|
||||||
|
|
||||||
|
// Middle between A and B.
|
||||||
|
vector3_t center_position() const
|
||||||
|
{
|
||||||
|
return {x + ((other()->x - x) / 2), y + ((other()->y - y) / 2), z + ((other()->z - z) / 2)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void gingerbread()
|
||||||
|
{
|
||||||
|
P_InstaScale(this, 3 * scale / 2);
|
||||||
|
|
||||||
|
orb(new_piece<Orb>(S_CHECKPOINT_ORB_DEAD));
|
||||||
|
orb()->whiteshadow = true;
|
||||||
|
|
||||||
|
arm(new_piece<Arm>(S_CHECKPOINT_ARM));
|
||||||
|
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void animate()
|
||||||
|
{
|
||||||
|
orient();
|
||||||
|
pull();
|
||||||
|
|
||||||
|
if (speed())
|
||||||
|
{
|
||||||
|
var(var() + speed());
|
||||||
|
|
||||||
|
if (!clip_var())
|
||||||
|
{
|
||||||
|
speed(speed() - FixedDiv(speed() / 50, std::max(speed_multiplier(), 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!activated())
|
||||||
|
{
|
||||||
|
sparkle_between(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void twirl(angle_t dir, fixed_t multiplier)
|
||||||
|
{
|
||||||
|
var(0);
|
||||||
|
speed_multiplier(std::clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier));
|
||||||
|
speed(FixedDiv(kBaseSpeed, speed_multiplier()));
|
||||||
|
reverse(AngleDeltaSigned(angle_to_other(), dir) > 0);
|
||||||
|
|
||||||
|
sparkle_between(FixedMul(80 * mapobjectscale, multiplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
void untwirl()
|
||||||
|
{
|
||||||
|
speed_multiplier(kMinSpeedMultiplier);
|
||||||
|
speed(FixedDiv(-(kBaseSpeed), speed_multiplier()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void activate()
|
||||||
|
{
|
||||||
|
var(FRACUNIT - 1);
|
||||||
|
speed(0);
|
||||||
|
P_SetMobjState(orb(), S_CHECKPOINT_ORB_LIVE);
|
||||||
|
orb()->shadowscale = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deactivate()
|
||||||
|
{
|
||||||
|
var(0);
|
||||||
|
speed(0);
|
||||||
|
P_SetMobjState(orb(), S_CHECKPOINT_ORB_DEAD);
|
||||||
|
orb()->shadowscale = FRACUNIT/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sparkle_around_center()
|
||||||
|
{
|
||||||
|
const vector3_t pos = center_position();
|
||||||
|
|
||||||
|
fixed_t mom = 5 * scale;
|
||||||
|
|
||||||
|
for (angle_t a = 0;;)
|
||||||
|
{
|
||||||
|
spawn_sparkle({pos.x + FixedMul(mom, FCOS(a)), pos.y + FixedMul(mom, FSIN(a)), pos.z}, mom, 20 * scale, a);
|
||||||
|
|
||||||
|
angle_t turn = a + (ANGLE_MAX / kSparkleAroundCount);
|
||||||
|
|
||||||
|
if (turn < a) // overflowed a full 360 degrees
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = turn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static angle_t to_angle(fixed_t f) { return FixedAngle((f & FRACMASK) * 360); }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* new_piece(statenum_t state)
|
||||||
|
{
|
||||||
|
mobj_t* x = P_SpawnMobjFromMobj(this, 0, 0, 0, MT_THOK);
|
||||||
|
|
||||||
|
P_SetMobjState(x, state);
|
||||||
|
|
||||||
|
return static_cast<T*>(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
angle_t angle_to_other() const { return R_PointToAngle2(x, y, other()->x, other()->y); }
|
||||||
|
angle_t facing_angle() const { return angle_to_other() + ANGLE_90; }
|
||||||
|
|
||||||
|
angle_t pivot() const
|
||||||
|
{
|
||||||
|
fixed_t pos = FixedMul(
|
||||||
|
FixedDiv(speed_multiplier() - kMinSpeedMultiplier, kSpeedMultiplierRange),
|
||||||
|
kMinPivotDelay
|
||||||
|
);
|
||||||
|
|
||||||
|
return to_angle(FixedDiv(std::max(var(), pos) - pos, FRACUNIT - pos)) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
void orient()
|
||||||
|
{
|
||||||
|
angle_t facing = facing_angle();
|
||||||
|
|
||||||
|
if (speed() >= 0)
|
||||||
|
{
|
||||||
|
fixed_t range = FRACUNIT + FixedRound((speed_multiplier() - kMinSpeedMultiplier) * 6);
|
||||||
|
|
||||||
|
angle = facing + (to_angle(FixedMul(var(), range)) * (reverse() ? -1 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
arm()->angle = angle - ANGLE_90;
|
||||||
|
arm()->rollangle = -(ANGLE_90) + pivot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pull()
|
||||||
|
{
|
||||||
|
fixed_t r = kOrbitDistance * scale;
|
||||||
|
fixed_t xy = FixedMul(r, FCOS(pivot()));
|
||||||
|
|
||||||
|
P_MoveOrigin(
|
||||||
|
orb(),
|
||||||
|
x + FixedMul(xy, FCOS(arm()->angle)),
|
||||||
|
y + FixedMul(xy, FSIN(arm()->angle)),
|
||||||
|
P_GetMobjHead(this) + (FixedMul(r, FSIN(pivot())) * P_MobjFlip(this))
|
||||||
|
);
|
||||||
|
|
||||||
|
P_MoveOrigin(arm(), orb()->x, orb()->y, orb()->z);
|
||||||
|
|
||||||
|
if (speed())
|
||||||
|
{
|
||||||
|
orb()->afterimages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_sparkle(const vector3_t& pos, fixed_t xy_momentum, fixed_t z_momentum, angle_t dir)
|
||||||
|
{
|
||||||
|
auto rng = [=](int units) { return P_RandomRange(PR_DECORATION, -(units) * scale, +(units) * scale); };
|
||||||
|
|
||||||
|
// From K_DrawDraftCombiring
|
||||||
|
mobj_t* p = P_SpawnMobjFromMobjUnscaled(
|
||||||
|
this,
|
||||||
|
(pos.x - x) + rng(12),
|
||||||
|
(pos.y - y) + rng(12),
|
||||||
|
(pos.z - z) + rng(24),
|
||||||
|
MT_SIGNSPARKLE
|
||||||
|
);
|
||||||
|
|
||||||
|
P_SetMobjState(p, static_cast<statenum_t>(S_CHECKPOINT_SPARK1 + (leveltime % 11)));
|
||||||
|
|
||||||
|
p->colorized = true;
|
||||||
|
|
||||||
|
if (xy_momentum)
|
||||||
|
{
|
||||||
|
P_Thrust(p, dir, xy_momentum);
|
||||||
|
p->momz = P_RandomKey(PR_DECORATION, std::max(z_momentum, 1));
|
||||||
|
p->destscale = 0;
|
||||||
|
p->scalespeed = p->scale / 35;
|
||||||
|
p->color = SKINCOLOR_ULTRAMARINE;
|
||||||
|
p->fuse = 0;
|
||||||
|
|
||||||
|
// Something lags at the start of the level. The
|
||||||
|
// timing is inconsistent, so this value is
|
||||||
|
// vibes-based.
|
||||||
|
constexpr int kIntroDelay = 8;
|
||||||
|
|
||||||
|
if (leveltime < kIntroDelay)
|
||||||
|
{
|
||||||
|
p->hitlag = kIntroDelay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->color = K_RainbowColor(leveltime);
|
||||||
|
p->fuse = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sparkle_between(fixed_t momentum)
|
||||||
|
{
|
||||||
|
angle_t a = angle_to_other();
|
||||||
|
|
||||||
|
if (a < ANGLE_180)
|
||||||
|
{
|
||||||
|
// Let's only do it for one of the two.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
angle_t dir = a - (reverse() ? ANGLE_90 : -(ANGLE_90));
|
||||||
|
|
||||||
|
fixed_t r = kSparkleRadius * scale;
|
||||||
|
fixed_t ofs = (kSparkleOffset * scale) + r;
|
||||||
|
fixed_t between = R_PointToDist2(x, y, other()->x, other()->y);
|
||||||
|
|
||||||
|
for (; ofs < between; ofs += 2 * r)
|
||||||
|
{
|
||||||
|
spawn_sparkle(
|
||||||
|
{x + FixedMul(ofs, FCOS(a)), y + FixedMul(ofs, FSIN(a)), z + (kSparkleZ * scale)},
|
||||||
|
momentum,
|
||||||
|
momentum / 2,
|
||||||
|
dir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clip_var()
|
||||||
|
{
|
||||||
|
if (speed() > 0)
|
||||||
|
{
|
||||||
|
if (var() >= FRACUNIT)
|
||||||
|
{
|
||||||
|
activate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (var() < 0)
|
||||||
|
{
|
||||||
|
deactivate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CheckpointManager
|
||||||
|
{
|
||||||
|
auto begin() { return vec_.begin(); }
|
||||||
|
auto end() { return vec_.end(); }
|
||||||
|
|
||||||
|
auto find(INT32 id) { return std::find_if(begin(), end(), [id](Checkpoint* chk) { return chk->id() == id; }); }
|
||||||
|
|
||||||
|
void push_back(Checkpoint* chk) { vec_.push_back(chk); }
|
||||||
|
|
||||||
|
void erase(const Checkpoint* chk)
|
||||||
|
{
|
||||||
|
if (auto it = std::find(vec_.begin(), vec_.end(), chk); it != end())
|
||||||
|
{
|
||||||
|
vec_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Checkpoint*> vec_;
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckpointManager g_checkpoints;
|
||||||
|
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
void Obj_ResetCheckpoints(void)
|
||||||
|
{
|
||||||
|
g_checkpoints = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Obj_LinkCheckpoint(mobj_t* end)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<Checkpoint*>(end);
|
||||||
|
|
||||||
|
if (chk->spawnpoint && chk->id() == 0)
|
||||||
|
{
|
||||||
|
auto msg = fmt::format(
|
||||||
|
"Checkpoint thing (index #{}, thing type {}) has an invalid ID! ID must not be 0.\n",
|
||||||
|
chk->spawnpoint - mapthings,
|
||||||
|
chk->spawnpoint->type
|
||||||
|
);
|
||||||
|
CONS_Alert(CONS_WARNING, msg.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto it = g_checkpoints.find(chk->id()); it != g_checkpoints.end())
|
||||||
|
{
|
||||||
|
Checkpoint* other = *it;
|
||||||
|
|
||||||
|
if (chk->spawnpoint && other->spawnpoint && chk->spawnpoint->angle != other->spawnpoint->angle)
|
||||||
|
{
|
||||||
|
auto msg = fmt::format(
|
||||||
|
"Checkpoints things with ID {} (index #{} and #{}, thing type {}) do not have matching angles.\n",
|
||||||
|
chk->id(),
|
||||||
|
chk->spawnpoint - mapthings,
|
||||||
|
other->spawnpoint - mapthings,
|
||||||
|
chk->spawnpoint->type
|
||||||
|
);
|
||||||
|
CONS_Alert(CONS_WARNING, msg.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
other->other(chk);
|
||||||
|
chk->other(other);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_checkpoints.push_back(chk);
|
||||||
|
}
|
||||||
|
|
||||||
|
chk->gingerbread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Obj_UnlinkCheckpoint(mobj_t* end)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<const Checkpoint*>(end);
|
||||||
|
|
||||||
|
g_checkpoints.erase(chk);
|
||||||
|
|
||||||
|
P_RemoveMobj(chk->orb());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Obj_CheckpointThink(mobj_t* end)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<Checkpoint*>(end);
|
||||||
|
|
||||||
|
if (!chk->valid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chk->animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y)
|
||||||
|
{
|
||||||
|
LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius);
|
||||||
|
|
||||||
|
auto it = std::find_if(
|
||||||
|
g_checkpoints.begin(),
|
||||||
|
g_checkpoints.end(),
|
||||||
|
[&](const Checkpoint* chk)
|
||||||
|
{
|
||||||
|
if (!chk->valid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineOnDemand gate = chk->crossing_line();
|
||||||
|
|
||||||
|
// Check if the bounding boxes of the two lines
|
||||||
|
// overlap. This relies on the player movement not
|
||||||
|
// being so large that it creates an oversized box,
|
||||||
|
// but thankfully that doesn't seem to happen, under
|
||||||
|
// normal circumstances.
|
||||||
|
if (!ray.overlaps(gate))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, &gate);
|
||||||
|
INT32 oldside = P_PointOnLineSide(old_x, old_y, &gate);
|
||||||
|
|
||||||
|
if (side == oldside)
|
||||||
|
{
|
||||||
|
// Did not cross.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (it == g_checkpoints.end())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkpoint* chk = *it;
|
||||||
|
|
||||||
|
if (chk->activated())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Checkpoint* chk : g_checkpoints)
|
||||||
|
{
|
||||||
|
if (chk->valid())
|
||||||
|
{
|
||||||
|
// Swing down any previously passed checkpoints.
|
||||||
|
// TODO: this could look weird in multiplayer if
|
||||||
|
// other players cross different checkpoints.
|
||||||
|
chk->untwirl();
|
||||||
|
chk->other()->untwirl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angle_t direction = R_PointToAngle2(old_x, old_y, player->mo->x, player->mo->y);
|
||||||
|
fixed_t speed_multiplier = FixedDiv(player->speed, K_GetKartSpeed(player, false, false));
|
||||||
|
|
||||||
|
chk->twirl(direction, speed_multiplier);
|
||||||
|
chk->other()->twirl(direction, speed_multiplier);
|
||||||
|
|
||||||
|
S_StartSound(player->mo, sfx_s3k63);
|
||||||
|
|
||||||
|
player->checkpointId = chk->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
mobj_t *Obj_FindCheckpoint(INT32 id)
|
||||||
|
{
|
||||||
|
auto it = g_checkpoints.find(id);
|
||||||
|
|
||||||
|
return it != g_checkpoints.end() ? *it : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean Obj_GetCheckpointRespawnPosition(const mobj_t* mobj, vector3_t* return_pos)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<const Checkpoint*>(mobj);
|
||||||
|
|
||||||
|
if (!chk->valid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*return_pos = chk->center_position();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
angle_t Obj_GetCheckpointRespawnAngle(const mobj_t* mobj)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<const Checkpoint*>(mobj);
|
||||||
|
|
||||||
|
return chk->spawnpoint ? FixedAngle(chk->spawnpoint->angle * FRACUNIT): 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Obj_ActivateCheckpointInstantly(mobj_t* mobj)
|
||||||
|
{
|
||||||
|
auto chk = static_cast<Checkpoint*>(mobj);
|
||||||
|
|
||||||
|
if (chk->valid())
|
||||||
|
{
|
||||||
|
chk->sparkle_around_center(); // only do it for one
|
||||||
|
chk->activate();
|
||||||
|
chk->other()->activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -263,7 +263,7 @@ void P_RecalcPrecipInSector(sector_t *sector);
|
||||||
void P_PrecipitationEffects(void);
|
void P_PrecipitationEffects(void);
|
||||||
|
|
||||||
void P_RemoveMobj(mobj_t *th);
|
void P_RemoveMobj(mobj_t *th);
|
||||||
boolean P_MobjWasRemoved(mobj_t *th);
|
boolean P_MobjWasRemoved(const mobj_t *th);
|
||||||
void P_RemoveSavegameMobj(mobj_t *th);
|
void P_RemoveSavegameMobj(mobj_t *th);
|
||||||
boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
|
boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
|
||||||
boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
|
boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
|
||||||
|
|
@ -313,7 +313,7 @@ mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 aimtype
|
||||||
#define P_SpawnPlayerMissile(s,t,f) P_SPMAngle(s,t,s->angle,true,f)
|
#define P_SpawnPlayerMissile(s,t,f) P_SPMAngle(s,t,s->angle,true,f)
|
||||||
#define P_SpawnNameFinder(s,t) P_SPMAngle(s,t,s->angle,true,0)
|
#define P_SpawnNameFinder(s,t) P_SPMAngle(s,t,s->angle,true,0)
|
||||||
void P_ColorTeamMissile(mobj_t *missile, player_t *source);
|
void P_ColorTeamMissile(mobj_t *missile, player_t *source);
|
||||||
SINT8 P_MobjFlip(mobj_t *mobj);
|
SINT8 P_MobjFlip(const mobj_t *mobj);
|
||||||
fixed_t P_GetMobjGravity(mobj_t *mo);
|
fixed_t P_GetMobjGravity(mobj_t *mo);
|
||||||
|
|
||||||
void P_CalcChasePostImg(player_t *player, camera_t *thiscam);
|
void P_CalcChasePostImg(player_t *player, camera_t *thiscam);
|
||||||
|
|
|
||||||
10
src/p_map.c
10
src/p_map.c
|
|
@ -3088,6 +3088,16 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, Try
|
||||||
P_CrossSpecialLine(ld, oldside, thing);
|
P_CrossSpecialLine(ld, oldside, thing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently this just iterates all checkpoints.
|
||||||
|
// Pretty shitty way to do it, but only players can
|
||||||
|
// cross it, so it's good enough. Works as long as the
|
||||||
|
// move doesn't cross multiple -- it can only evaluate
|
||||||
|
// one.
|
||||||
|
if (thing->player)
|
||||||
|
{
|
||||||
|
Obj_CrossCheckpoints(thing->player, oldx, oldy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != NULL)
|
if (result != NULL)
|
||||||
|
|
|
||||||
22
src/p_mobj.c
22
src/p_mobj.c
|
|
@ -567,7 +567,7 @@ static boolean P_SetPrecipMobjState(precipmobj_t *mobj, statenum_t state)
|
||||||
//
|
//
|
||||||
// Special utility to return +1 or -1 depending on mobj's gravity
|
// Special utility to return +1 or -1 depending on mobj's gravity
|
||||||
//
|
//
|
||||||
SINT8 P_MobjFlip(mobj_t *mobj)
|
SINT8 P_MobjFlip(const mobj_t *mobj)
|
||||||
{
|
{
|
||||||
if (mobj && mobj->eflags & MFE_VERTICALFLIP)
|
if (mobj && mobj->eflags & MFE_VERTICALFLIP)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -6728,6 +6728,9 @@ static void P_MobjSceneryThink(mobj_t *mobj)
|
||||||
case MT_ARKARROW:
|
case MT_ARKARROW:
|
||||||
Obj_ArkArrowThink(mobj);
|
Obj_ArkArrowThink(mobj);
|
||||||
break;
|
break;
|
||||||
|
case MT_CHECKPOINT_END:
|
||||||
|
Obj_CheckpointThink(mobj);
|
||||||
|
break;
|
||||||
case MT_SCRIPT_THING:
|
case MT_SCRIPT_THING:
|
||||||
{
|
{
|
||||||
if (mobj->thing_args[2] != 0)
|
if (mobj->thing_args[2] != 0)
|
||||||
|
|
@ -10416,6 +10419,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
|
||||||
case MT_CDUFO:
|
case MT_CDUFO:
|
||||||
case MT_BATTLEUFO:
|
case MT_BATTLEUFO:
|
||||||
case MT_SPRAYCAN:
|
case MT_SPRAYCAN:
|
||||||
|
case MT_CHECKPOINT_END:
|
||||||
thing->shadowscale = FRACUNIT;
|
thing->shadowscale = FRACUNIT;
|
||||||
break;
|
break;
|
||||||
case MT_SMALLMACE:
|
case MT_SMALLMACE:
|
||||||
|
|
@ -11293,6 +11297,11 @@ void P_RemoveMobj(mobj_t *mobj)
|
||||||
Obj_UnlinkBattleUFOSpawner(mobj);
|
Obj_UnlinkBattleUFOSpawner(mobj);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MT_CHECKPOINT_END:
|
||||||
|
{
|
||||||
|
Obj_UnlinkCheckpoint(mobj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
@ -11379,7 +11388,7 @@ void P_RemoveMobj(mobj_t *mobj)
|
||||||
|
|
||||||
// This does not need to be added to Lua.
|
// This does not need to be added to Lua.
|
||||||
// To test it in Lua, check mobj.valid
|
// To test it in Lua, check mobj.valid
|
||||||
boolean P_MobjWasRemoved(mobj_t *mobj)
|
boolean P_MobjWasRemoved(const mobj_t *mobj)
|
||||||
{
|
{
|
||||||
if (mobj && mobj->thinker.function.acp1 == (actionf_p1)P_MobjThinker)
|
if (mobj && mobj->thinker.function.acp1 == (actionf_p1)P_MobjThinker)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -12309,6 +12318,10 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
|
||||||
if (modeattacking & ATTACKING_SPB)
|
if (modeattacking & ATTACKING_SPB)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
case MT_CHECKPOINT_END:
|
||||||
|
if (!(gametyperules & GTR_CHECKPOINTS))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -13633,6 +13646,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj)
|
||||||
Obj_SneakerPanelSpawnerSetup(mobj, mthing);
|
Obj_SneakerPanelSpawnerSetup(mobj, mthing);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MT_CHECKPOINT_END:
|
||||||
|
{
|
||||||
|
Obj_LinkCheckpoint(mobj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
|
||||||
WRITEUINT8(save->p, players[i].latestlap);
|
WRITEUINT8(save->p, players[i].latestlap);
|
||||||
WRITEUINT32(save->p, players[i].lapPoints);
|
WRITEUINT32(save->p, players[i].lapPoints);
|
||||||
WRITEINT32(save->p, players[i].cheatchecknum);
|
WRITEINT32(save->p, players[i].cheatchecknum);
|
||||||
|
WRITEINT32(save->p, players[i].checkpointId);
|
||||||
|
|
||||||
WRITEUINT8(save->p, players[i].ctfteam);
|
WRITEUINT8(save->p, players[i].ctfteam);
|
||||||
|
|
||||||
|
|
@ -777,6 +778,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
|
||||||
players[i].latestlap = READUINT8(save->p);
|
players[i].latestlap = READUINT8(save->p);
|
||||||
players[i].lapPoints = READUINT32(save->p);
|
players[i].lapPoints = READUINT32(save->p);
|
||||||
players[i].cheatchecknum = READINT32(save->p);
|
players[i].cheatchecknum = READINT32(save->p);
|
||||||
|
players[i].checkpointId = READINT32(save->p);
|
||||||
|
|
||||||
players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue
|
players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ void P_InitThinkers(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
Obj_ResetUFOSpawners();
|
Obj_ResetUFOSpawners();
|
||||||
|
Obj_ResetCheckpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a new thinker at the end of the list.
|
// Adds a new thinker at the end of the list.
|
||||||
|
|
|
||||||
11
src/p_user.c
11
src/p_user.c
|
|
@ -2794,7 +2794,16 @@ static void P_DeathThink(player_t *player)
|
||||||
|
|
||||||
if (playerGone == false && player->deadtimer > TICRATE)
|
if (playerGone == false && player->deadtimer > TICRATE)
|
||||||
{
|
{
|
||||||
player->playerstate = PST_REBORN;
|
if (!netgame && !splitscreen
|
||||||
|
&& player->bot == false
|
||||||
|
&& (gametyperules & GTR_CHECKPOINTS))
|
||||||
|
{
|
||||||
|
G_SetRetryFlag();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player->playerstate = PST_REBORN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support splitscreen
|
// TODO: support splitscreen
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue