Merge remote-tracking branch 'origin/race-checkpoint'

This commit is contained in:
AJ Martinez 2024-08-22 22:49:49 -07:00
commit 3670695df6
22 changed files with 1009 additions and 607 deletions

View file

@ -819,7 +819,7 @@ consvar_t cv_fuzz = OnlineCheat("fuzz", "Off").on_off().description("Human playe
consvar_t cv_kartdebugamount = OnlineCheat("debugitemamount", "1").min_max(1, 255).description("If debugitem, give multiple copies of an item"); consvar_t cv_kartdebugamount = OnlineCheat("debugitemamount", "1").min_max(1, 255).description("If debugitem, give multiple copies of an item");
consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().description("Bot AI debugger"); consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().description("Bot AI debugger");
consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off().description("Show items that the roulette can roll"); consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "0").min_max(0, 2).description("Show items that the roulette can roll");
consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way"); consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way");
consvar_t cv_kartdebugbotwhip = OnlineCheat("debugbotwhip", "Off").on_off().description("Disable bot ring and item pickups"); consvar_t cv_kartdebugbotwhip = OnlineCheat("debugbotwhip", "Off").on_off().description("Disable bot ring and item pickups");

View file

@ -6805,6 +6805,22 @@ INT32 D_NumPlayers(void)
return num; return num;
} }
/** Returns the number of players racing, not spectating and includes bots
* \return Number of players. Can be zero if we're running a ::dedicated
* server.
*/
INT32 D_NumPlayersInRace(void)
{
INT32 numPlayers = 0;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator)
numPlayers++;
}
return numPlayers;
}
/** Return whether a player is a real person (not a CPU) and not spectating. /** Return whether a player is a real person (not a CPU) and not spectating.
*/ */
boolean D_IsPlayerHumanAndGaming (INT32 player_number) boolean D_IsPlayerHumanAndGaming (INT32 player_number)

View file

@ -657,6 +657,7 @@ extern UINT8 playernode[MAXPLAYERS];
extern UINT8 playerconsole[MAXPLAYERS]; extern UINT8 playerconsole[MAXPLAYERS];
INT32 D_NumPlayers(void); INT32 D_NumPlayers(void);
INT32 D_NumPlayersInRace(void);
boolean D_IsPlayerHumanAndGaming(INT32 player_number); boolean D_IsPlayerHumanAndGaming(INT32 player_number);
void D_ResetTiccmds(void); void D_ResetTiccmds(void);

View file

@ -495,9 +495,8 @@ struct itemroulette_t
SINT8 *itemList; SINT8 *itemList;
#endif #endif
UINT8 useOdds;
UINT8 playing, exiting; UINT8 playing, exiting;
UINT32 dist, baseDist; UINT32 preexpdist, dist, baseDist;
UINT32 firstDist, secondDist; UINT32 firstDist, secondDist;
UINT32 secondToFirst; UINT32 secondToFirst;
@ -914,6 +913,7 @@ struct player_t
UINT8 laps; // Number of laps (optional) UINT8 laps; // Number of laps (optional)
UINT8 latestlap; UINT8 latestlap;
UINT32 lapPoints; // Points given from laps UINT32 lapPoints; // Points given from laps
INT32 exp;
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 INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp

View file

@ -2131,6 +2131,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
UINT8 laps; UINT8 laps;
UINT8 latestlap; UINT8 latestlap;
UINT32 lapPoints; UINT32 lapPoints;
INT32 exp;
UINT16 skincolor; UINT16 skincolor;
INT32 skin; INT32 skin;
UINT8 availabilities[MAXAVAILABILITY]; UINT8 availabilities[MAXAVAILABILITY];
@ -2319,6 +2320,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
laps = 0; laps = 0;
latestlap = 0; latestlap = 0;
lapPoints = 0; lapPoints = 0;
exp = FRACUNIT;
roundscore = 0; roundscore = 0;
exiting = 0; exiting = 0;
khudfinish = 0; khudfinish = 0;
@ -2356,6 +2358,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
laps = players[player].laps; laps = players[player].laps;
latestlap = players[player].latestlap; latestlap = players[player].latestlap;
lapPoints = players[player].lapPoints; lapPoints = players[player].lapPoints;
exp = players[player].exp;
roundscore = players[player].roundscore; roundscore = players[player].roundscore;
@ -2470,6 +2473,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->laps = laps; p->laps = laps;
p->latestlap = latestlap; p->latestlap = latestlap;
p->lapPoints = lapPoints; p->lapPoints = lapPoints;
p->exp = exp;
p->totalring = totalring; p->totalring = totalring;
for (i = 0; i < LAP__MAX; i++) for (i = 0; i < LAP__MAX; i++)
@ -5248,7 +5252,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
players[i].score = 0; players[i].score = 0;
} }
if (resetplayer || map != gamemap) if (resetplayer || !(gametyperules & GTR_CHECKPOINTS && map == gamemap))
{ {
players[i].checkpointId = 0; players[i].checkpointId = 0;
} }

View file

@ -1061,7 +1061,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
return NULL; return NULL;
} }
static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item)
{ {
UINT8 offset; UINT8 offset;
@ -5736,60 +5736,28 @@ static void K_drawDistributionDebugger(void)
itemroulette_t rouletteData = {0}; itemroulette_t rouletteData = {0};
const fixed_t scale = (FRACUNIT >> 1); const fixed_t scale = (FRACUNIT >> 1);
const fixed_t space = 24 * scale;
const fixed_t pad = 9 * scale; const fixed_t pad = 9 * scale;
fixed_t x = -pad; fixed_t x = -pad;
fixed_t y = -pad;
size_t i;
if (R_GetViewNumber() != 0) // only for p1 if (R_GetViewNumber() != 0) // only for p1
{ {
return; return;
} }
K_FillItemRouletteData(stplyr, &rouletteData, false); K_FillItemRouletteData(stplyr, &rouletteData, false, true);
for (i = 0; i < rouletteData.itemListLen; i++) if (cv_kartdebugdistribution.value <= 1)
{ return;
const kartitems_t item = static_cast<kartitems_t>(rouletteData.itemList[i]);
UINT8 amount = 1;
if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+10, V_SNAPTOTOP|V_SNAPTORIGHT, va("speed = %u", rouletteData.speed));
{
x += space;
y = -pad;
}
V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+22, V_SNAPTOTOP|V_SNAPTORIGHT, va("baseDist = %u", rouletteData.baseDist));
K_GetSmallStaticCachedItemPatch(item), NULL); V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+30, V_SNAPTOTOP|V_SNAPTORIGHT, va("dist = %u", rouletteData.dist));
// Display amount for multi-items V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+42, V_SNAPTOTOP|V_SNAPTORIGHT, va("firstDist = %u", rouletteData.firstDist));
amount = K_ItemResultToAmount(item); V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+50, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondDist = %u", rouletteData.secondDist));
if (amount > 1) V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+58, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondToFirst = %u", rouletteData.secondToFirst));
{
V_DrawStringScaled(
x + (18 * scale),
y + (23 * scale),
scale, FRACUNIT, FRACUNIT,
V_SNAPTOTOP,
NULL, HU_FONT,
va("x%d", amount)
);
}
y += space;
}
V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds));
V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed));
V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist));
V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist));
V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist));
V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist));
V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst));
#ifndef ITEM_LIST_SIZE #ifndef ITEM_LIST_SIZE
Z_Free(rouletteData.itemList); Z_Free(rouletteData.itemList);
@ -6096,7 +6064,7 @@ void K_ClearPersistentMessages()
} }
// Return value can be used for "paired" splitscreen messages, true = was displayed // Return value can be used for "paired" splitscreen messages, true = was displayed
void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist) void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist)
{ {
if (!player) if (!player)
return; return;
@ -6564,6 +6532,10 @@ void K_drawKartHUD(void)
if (cv_kartdebugdistribution.value) if (cv_kartdebugdistribution.value)
K_drawDistributionDebugger(); K_drawDistributionDebugger();
// temp debug
V_DrawSmallString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp)));
// V_DrawSmallString(8, 4, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp)));
if (cv_kartdebugnodes.value) if (cv_kartdebugnodes.value)
{ {
UINT8 p; UINT8 p;

View file

@ -108,11 +108,13 @@ extern patch_t *kp_facenum[MAXPLAYERS+1];
extern patch_t *kp_unknownminimap; extern patch_t *kp_unknownminimap;
void K_AddMessage(const char *msg, boolean interrupt, boolean persist); void K_AddMessage(const char *msg, boolean interrupt, boolean persist);
void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist); void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist);
void K_ClearPersistentMessages(void); void K_ClearPersistentMessages(void);
void K_ClearPersistentMessageForPlayer(player_t *player); void K_ClearPersistentMessageForPlayer(player_t *player);
void K_TickMessages(void); void K_TickMessages(void);
patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item);
typedef enum typedef enum
{ {
PLAYERTAG_NONE, PLAYERTAG_NONE,

View file

@ -4007,8 +4007,10 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact)
if (gametyperules & GTR_SPHERES) if (gametyperules & GTR_SPHERES)
return; return;
// Give that Sonic guy some help. UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10);
UINT16 scaledamps = min(amps, amps * (10 + player->kartspeed - player->kartweight) / 10);
if (player->position <= 1)
scaledamps /= 2;
for (int i = 0; i < (scaledamps/2); i++) for (int i = 0; i < (scaledamps/2); i++)
{ {
@ -4052,6 +4054,13 @@ void K_AwardPlayerAmps(player_t *player, UINT8 amps)
if (player->rings <= 0 && player->ampspending == 0) if (player->rings <= 0 && player->ampspending == 0)
{ {
// Auto Overdrive!
// If this is a fresh OD, give 'em some extra juice to make up for lack of flexibility.
if (!player->overdrive && player->mo && !P_MobjWasRemoved(player->mo))
{
S_StartSound(player->mo, sfx_gshac);
player->amps *= 2;
}
K_Overdrive(player); K_Overdrive(player);
} }
} }
@ -4091,7 +4100,7 @@ boolean K_Overdrive(player_t *player)
S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm35);
S_StartSound(player->mo, sfx_cdfm13); S_StartSound(player->mo, sfx_cdfm13);
player->overdrive += (player->amps)*6; player->overdrive += (player->amps)*5;
player->overshield += (player->amps)*2; player->overshield += (player->amps)*2;
player->overdrivepower = FRACUNIT; player->overdrivepower = FRACUNIT;
@ -4112,7 +4121,7 @@ boolean K_DefensiveOverdrive(player_t *player)
S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm35);
S_StartSound(player->mo, sfx_cdfm13); S_StartSound(player->mo, sfx_cdfm13);
player->overdrive += (player->amps)*4; player->overdrive += (player->amps)*3;
player->overshield += (player->amps)*2 + TICRATE*2; player->overshield += (player->amps)*2 + TICRATE*2;
player->overdrivepower = FRACUNIT; player->overdrivepower = FRACUNIT;
@ -7424,7 +7433,7 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds)
// Avoid calling K_FillItemRouletteData since that // Avoid calling K_FillItemRouletteData since that
// function resets PR_ITEM_ROULETTE. // function resets PR_ITEM_ROULETTE.
spawnchance[i] = ( spawnchance[i] = (
totalspawnchance += K_KartGetItemOdds(NULL, NULL, useodds, i) totalspawnchance += K_KartGetBattleOdds(NULL, useodds, i)
); );
} }
@ -12712,6 +12721,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
else else
{ {
UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing);
behind = FixedMul(behind, max(player->exp, FRACUNIT/2));
UINT32 behindMulti = behind / 500; UINT32 behindMulti = behind / 500;
behindMulti = min(behindMulti, 60); behindMulti = min(behindMulti, 60);
award = award * (behindMulti + 10) / 10; award = award * (behindMulti + 10) / 10;
@ -12982,6 +12992,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
else else
player->rocketsneakertimer -= 3*TICRATE; player->rocketsneakertimer -= 3*TICRATE;
player->botvars.itemconfirm = 2*TICRATE; player->botvars.itemconfirm = 2*TICRATE;
player->overshield += TICRATE/2; // TEMP prototype
} }
} }
else if (player->itemamount == 0) else if (player->itemamount == 0)
@ -12997,6 +13008,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{ {
K_DoSneaker(player, 1); K_DoSneaker(player, 1);
K_PlayBoostTaunt(player->mo); K_PlayBoostTaunt(player->mo);
player->overshield += TICRATE/2; // TEMP prototype
player->sneakertimer += TICRATE; // TEMP prototype
player->itemamount--; player->itemamount--;
player->botvars.itemconfirm = 0; player->botvars.itemconfirm = 0;
} }
@ -14780,4 +14793,43 @@ boolean K_PlayerCanUseItem(player_t *player)
return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime); return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime);
} }
fixed_t K_GetExpAdjustment(player_t *player)
{
fixed_t exp_power = 3*FRACUNIT/100; // adjust to change overall xp volatility
fixed_t exp_stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain
fixed_t result = 0;
INT32 live_players = 0;
for (INT32 i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || player == players+i)
continue;
live_players++;
}
if (live_players < 8)
{
exp_power += (8 - live_players) * exp_power/4;
}
// Increase XP for each player you're beating...
for (INT32 i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || player == players+i)
continue;
if (player->position < players[i].position)
result += exp_power;
}
// ...then take all of the XP you could possibly have earned,
// and lose it proportional to the stable rate. If you're below
// the stable threshold, this results in you losing XP.
result -= FixedMul(exp_power, FixedMul(live_players*FRACUNIT, FRACUNIT - exp_stablerate));
return result;
}
//} //}

View file

@ -288,6 +288,8 @@ boolean K_ThunderDome(void);
boolean K_PlayerCanUseItem(player_t *player); boolean K_PlayerCanUseItem(player_t *player);
fixed_t K_GetExpAdjustment(player_t *player);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -263,6 +263,9 @@ mobj_t *Obj_FindCheckpoint(INT32 id);
boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *return_pos); boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *return_pos);
angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint); angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint);
void Obj_ActivateCheckpointInstantly(mobj_t* mobj); void Obj_ActivateCheckpointInstantly(mobj_t* mobj);
UINT32 Obj_GetCheckpointCount();
void Obj_ClearCheckpoints();
void Obj_DeactivateCheckpoints();
/* Rideroid / Rideroid Node */ /* Rideroid / Rideroid Node */
void Obj_RideroidThink(mobj_t *mo); void Obj_RideroidThink(mobj_t *mo);

View file

@ -26,6 +26,7 @@
#include "byteptr.h" #include "byteptr.h"
#include "k_race.h" #include "k_race.h"
#include "command.h" #include "command.h"
#include "k_objects.h"
// I was ALMOST tempted to start tearing apart all // I was ALMOST tempted to start tearing apart all
// of the map loading code and turning it into C++ // of the map loading code and turning it into C++
@ -510,7 +511,8 @@ void gpRank_t::Update(void)
} }
lvl->time = UINT32_MAX; lvl->time = UINT32_MAX;
lvl->totalLapPoints = K_RaceLapCount(gamemap - 1) * 2;
lvl->totalLapPoints = ( K_RaceLapCount(gamemap - 1) + Obj_GetCheckpointCount() )* 2;
lvl->totalPrisons = maptargets; lvl->totalPrisons = maptargets;
UINT8 i; UINT8 i;

File diff suppressed because it is too large Load diff

View file

@ -78,17 +78,14 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result);
/*-------------------------------------------------- /*--------------------------------------------------
INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); INT32 K_KartGetBattleOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item);
Gets the frequency an item should show up in Gets legacy item priority.
an item bracket, and adjusted for special Currently used only for Battle monitors/spawners.
factors (such as Frantic Items).
Input Arguments:- Input Arguments:-
player - The player we intend to give the item to later. player - The player we intend to give the item to later.
Can be NULL for generic use. Can be NULL for generic use.
roulette - The roulette data that we intend to
insert this item into.
pos - The item bracket we are in. pos - The item bracket we are in.
item - The item to give. item - The item to give.
@ -97,11 +94,11 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result);
into the roulette. into the roulette.
--------------------------------------------------*/ --------------------------------------------------*/
INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item);
/*-------------------------------------------------- /*--------------------------------------------------
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox); void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun);
Fills out the item roulette struct when it is Fills out the item roulette struct when it is
initially created. This function needs to be initially created. This function needs to be
@ -113,12 +110,13 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
Can be NULL for generic use. Can be NULL for generic use.
roulette - The roulette data struct to fill out. roulette - The roulette data struct to fill out.
ringbox - Is this roulette fill triggered by a just-respawned Ring Box? ringbox - Is this roulette fill triggered by a just-respawned Ring Box?
dryrun - Are we calling this from the distribution debugger? Don't call RNG or write roulette data!
Return:- Return:-
N/A N/A
--------------------------------------------------*/ --------------------------------------------------*/
void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox); void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun);
/*-------------------------------------------------- /*--------------------------------------------------

View file

@ -36,6 +36,7 @@
#include "r_fps.h" #include "r_fps.h"
#include "g_party.h" #include "g_party.h"
#include "g_input.h" #include "g_input.h"
#include "k_objects.h"
boolean level_tally_t::UseBonuses(void) boolean level_tally_t::UseBonuses(void)
{ {
@ -343,7 +344,7 @@ void level_tally_t::Init(player_t *player)
if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT) if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT)
{ {
laps = player->lapPoints; laps = player->lapPoints;
totalLaps = numlaps; totalLaps = numlaps + numlaps * Obj_GetCheckpointCount();
if (inDuel == false) if (inDuel == false)
{ {

View file

@ -37,6 +37,7 @@ struct MobjList
{ {
ptr->next(front()); ptr->next(front());
front(ptr); front(ptr);
count_++;
} }
void erase(T* node) void erase(T* node)
@ -45,6 +46,7 @@ struct MobjList
{ {
front(node->next()); front(node->next());
node->next(nullptr); node->next(nullptr);
count_--;
return; return;
} }
@ -69,6 +71,7 @@ struct MobjList
{ {
prev->next(node->next()); prev->next(node->next());
node->next(nullptr); node->next(nullptr);
count_--;
break; break;
} }
} }
@ -77,9 +80,12 @@ struct MobjList
auto begin() const { return view().begin(); } auto begin() const { return view().begin(); }
auto end() const { return view().end(); } auto end() const { return view().end(); }
auto count() { return count_; }
private: private:
void front(T* ptr) { Mobj::ManagedPtr {Head} = ptr; } void front(T* ptr) { Mobj::ManagedPtr {Head} = ptr; }
auto view() const { return MobjListView(front(), [](T* node) { return node->next(); }); } auto view() const { return MobjListView(front(), [](T* node) { return node->next(); }); }
UINT32 count_ = 0;
}; };
}; // namespace srb2 }; // namespace srb2

View file

@ -55,7 +55,7 @@ void Obj_AmpsThink (mobj_t *amps)
amps->extravalue2++; amps->extravalue2++;
speed += amps->extravalue1 * amps->scale/2; speed += amps->extravalue2 * amps->scale/2;
fakez = mo->z + (vert * amps->extravalue1 / AMP_ARCTIME); fakez = mo->z + (vert * amps->extravalue1 / AMP_ARCTIME);
damper = 1; damper = 1;

View file

@ -17,6 +17,7 @@
#include "../doomdef.h" #include "../doomdef.h"
#include "../doomtype.h" #include "../doomtype.h"
#include "../info.h" #include "../info.h"
#include "../g_game.h"
#include "../k_color.h" #include "../k_color.h"
#include "../k_kart.h" #include "../k_kart.h"
#include "../k_objects.h" #include "../k_objects.h"
@ -34,9 +35,17 @@
#include "../sounds.h" #include "../sounds.h"
#include "../tables.h" #include "../tables.h"
using std::vector;
using std::pair;
using std::min;
using std::max;
using std::clamp;
extern mobj_t* svg_checkpoints; extern mobj_t* svg_checkpoints;
#define checkpoint_id(o) ((o)->thing_args[0]) #define checkpoint_id(o) ((o)->thing_args[0])
#define checkpoint_linetag(o) ((o)->thing_args[1])
#define checkpoint_extralength(o) ((o)->thing_args[2])
#define checkpoint_other(o) ((o)->target) #define checkpoint_other(o) ((o)->target)
#define checkpoint_orb(o) ((o)->tracer) #define checkpoint_orb(o) ((o)->tracer)
#define checkpoint_arm(o) ((o)->hnext) #define checkpoint_arm(o) ((o)->hnext)
@ -51,12 +60,14 @@ namespace
struct LineOnDemand : line_t struct LineOnDemand : line_t
{ {
LineOnDemand(const line_t* line) {}
LineOnDemand(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) : LineOnDemand(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) :
line_t { line_t {
.v1 = &v1_data_, .v1 = &v1_data_,
.dx = x2 - x1, .dx = x2 - x1,
.dy = y2 - y1, .dy = y2 - y1,
.bbox = {std::max(y1, y2), std::min(y1, y2), std::min(x1, x2), std::max(x1, x2)}, .bbox = {max(y1, y2), min(y1, y2), min(x1, x2), max(x1, x2)},
}, },
v1_data_ {.x = x1, .y = y1} v1_data_ {.x = x1, .y = y1}
{ {
@ -76,6 +87,12 @@ struct LineOnDemand : line_t
bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT]; bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT];
} }
bool overlaps(const line_t& other) const
{
return bbox[BOXTOP] >= other.bbox[BOXBOTTOM] && bbox[BOXBOTTOM] <= other.bbox[BOXTOP] &&
bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT];
}
private: private:
vertex_t v1_data_; vertex_t v1_data_;
}; };
@ -170,6 +187,30 @@ struct Checkpoint : mobj_t
deactivate(); deactivate();
} }
// will not work properly after a player enters intoa new lap
INT32 players_passed()
{
INT32 pcount = 0;
for (INT32 i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator && players[i].checkpointId >= id())
pcount++;
}
return pcount;
}
boolean top_half_has_passed()
{
INT32 pcount = 0;
INT32 winningpos = 1;
INT32 nump = D_NumPlayersInRace();
winningpos = nump / 2;
winningpos += nump % 2;
return players_passed() >= winningpos;
}
void animate() void animate()
{ {
orient(); orient();
@ -181,10 +222,11 @@ struct Checkpoint : mobj_t
if (!clip_var()) if (!clip_var())
{ {
speed(speed() - FixedDiv(speed() / 50, std::max<fixed_t>(speed_multiplier(), 1))); speed(speed() - FixedDiv(speed() / 50, max<fixed_t>(speed_multiplier(), 1)));
} }
} }
else if (!activated())
if (!top_half_has_passed())
{ {
sparkle_between(0); sparkle_between(0);
} }
@ -193,7 +235,7 @@ struct Checkpoint : mobj_t
void twirl(angle_t dir, fixed_t multiplier) void twirl(angle_t dir, fixed_t multiplier)
{ {
var(0); var(0);
speed_multiplier(std::clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier)); speed_multiplier(clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier));
speed(FixedDiv(kBaseSpeed, speed_multiplier())); speed(FixedDiv(kBaseSpeed, speed_multiplier()));
reverse(AngleDeltaSigned(angle_to_other(), dir) > 0); reverse(AngleDeltaSigned(angle_to_other(), dir) > 0);
@ -266,7 +308,7 @@ private:
kMinPivotDelay kMinPivotDelay
); );
return to_angle(FixedDiv(std::max(var(), pos) - pos, FRACUNIT - pos)) / 4; return to_angle(FixedDiv(max(var(), pos) - pos, FRACUNIT - pos)) / 4;
} }
void orient() void orient()
@ -304,7 +346,7 @@ private:
} }
} }
void spawn_sparkle(const vector3_t& pos, fixed_t xy_momentum, fixed_t z_momentum, angle_t dir) void spawn_sparkle(const vector3_t& pos, fixed_t xy_momentum, fixed_t z_momentum, angle_t dir, skincolornum_t color = SKINCOLOR_ULTRAMARINE)
{ {
auto rng = [=](int units) { return P_RandomRange(PR_DECORATION, -(units) * scale, +(units) * scale); }; auto rng = [=](int units) { return P_RandomRange(PR_DECORATION, -(units) * scale, +(units) * scale); };
@ -324,10 +366,10 @@ private:
if (xy_momentum) if (xy_momentum)
{ {
P_Thrust(p, dir, xy_momentum); P_Thrust(p, dir, xy_momentum);
p->momz = P_RandomKey(PR_DECORATION, std::max<fixed_t>(z_momentum, 1)); p->momz = P_RandomKey(PR_DECORATION, max<fixed_t>(z_momentum, 1));
p->destscale = 0; p->destscale = 0;
p->scalespeed = p->scale / 35; p->scalespeed = p->scale / 35;
p->color = SKINCOLOR_ULTRAMARINE; p->color = color;
p->fuse = 0; p->fuse = 0;
// Something lags at the start of the level. The // Something lags at the start of the level. The
@ -342,7 +384,7 @@ private:
} }
else else
{ {
p->color = K_RainbowColor(leveltime); p->color = color;
p->fuse = 2; p->fuse = 2;
} }
} }
@ -369,7 +411,8 @@ private:
{x + FixedMul(ofs, FCOS(a)), y + FixedMul(ofs, FSIN(a)), z + (kSparkleZ * scale)}, {x + FixedMul(ofs, FCOS(a)), y + FixedMul(ofs, FSIN(a)), z + (kSparkleZ * scale)},
momentum, momentum,
momentum / 2, momentum / 2,
dir dir,
activated() ? SKINCOLOR_GREEN : SKINCOLOR_ULTRAMARINE
); );
} }
} }
@ -402,25 +445,41 @@ struct CheckpointManager
auto begin() { return list_.begin(); } auto begin() { return list_.begin(); }
auto end() { return list_.end(); } auto end() { return list_.end(); }
auto find(INT32 id) { return std::find_if(begin(), end(), [id](Checkpoint* chk) { return chk->id() == id; }); } auto find_checkpoint(INT32 id) {
auto it = find_if(list_.begin(), list_.end(), [id](auto pair) { return pair.first->id() == id; });
if (it != list_.end())
{
return it->first;
}
return static_cast<Checkpoint*>(nullptr);
}
void push_front(Checkpoint* chk) { list_.push_front(chk); } // auto find_pair(Checkpoint* chk) {
// pair<Checkpoint*, vector<line_t*>> retpair;
// auto it = find_if(list_.begin(), list_.end(), [chk](auto pair) { return pair.first == chk; });
// if (it != list_.end())
// {
// retpair = *it;
// return retpair;
// }
// return static_cast<pair<Checkpoint*, vector<line_t*>>>(nullptr);
// }
void erase(Checkpoint* chk) { list_.erase(chk); } void remove_checkpoint(mobj_t* end)
{
private:
srb2::MobjList<Checkpoint, svg_checkpoints> list_;
};
CheckpointManager g_checkpoints;
}; // namespace
void Obj_LinkCheckpoint(mobj_t* end)
{
auto chk = static_cast<Checkpoint*>(end); auto chk = static_cast<Checkpoint*>(end);
auto it = find_if(list_.begin(), list_.end(), [&](auto pair) { return pair.first == chk; });
if (it != list_.end())
{
list_.erase(it);
}
}
if (chk->spawnpoint && chk->id() == 0) void link_checkpoint(mobj_t* end)
{
auto chk = static_cast<Checkpoint*>(end);
auto id = chk->id();
if (chk->spawnpoint && id == 0)
{ {
auto msg = fmt::format( auto msg = fmt::format(
"Checkpoint thing (index #{}, thing type {}) has an invalid ID! ID must not be 0.\n", "Checkpoint thing (index #{}, thing type {}) has an invalid ID! ID must not be 0.\n",
@ -431,10 +490,8 @@ void Obj_LinkCheckpoint(mobj_t* end)
return; return;
} }
if (auto it = g_checkpoints.find(chk->id()); it != g_checkpoints.end()) if (auto other = find_checkpoint(id))
{ {
Checkpoint* other = *it;
if (chk->spawnpoint && other->spawnpoint && chk->spawnpoint->angle != other->spawnpoint->angle) if (chk->spawnpoint && other->spawnpoint && chk->spawnpoint->angle != other->spawnpoint->angle)
{ {
auto msg = fmt::format( auto msg = fmt::format(
@ -447,25 +504,51 @@ void Obj_LinkCheckpoint(mobj_t* end)
CONS_Alert(CONS_WARNING, "%s", msg.c_str()); CONS_Alert(CONS_WARNING, "%s", msg.c_str());
return; return;
} }
other->other(chk); other->other(chk);
chk->other(other); chk->other(other);
} }
else else // Checkpoint isn't in the list, find any associated tagged lines and make the pair
{ {
g_checkpoints.push_front(chk); vector<line_t*> checklines;
if (checkpoint_linetag(chk))
{
INT32 li;
INT32 tag = checkpoint_linetag(chk);
TAG_ITER_LINES(tag, li)
{
line_t* line = lines + li;
checklines.push_back(line);
}
}
list_.emplace_back(chk, move(checklines));
} }
chk->gingerbread(); chk->gingerbread();
}
void clear() { list_.clear(); }
auto count() { return list_.size(); }
private:
vector<pair<Checkpoint*, vector<line_t*>>> list_;
};
CheckpointManager g_checkpoints;
}; // namespace
void Obj_LinkCheckpoint(mobj_t* end)
{
g_checkpoints.link_checkpoint(end);
} }
void Obj_UnlinkCheckpoint(mobj_t* end) void Obj_UnlinkCheckpoint(mobj_t* end)
{ {
auto chk = static_cast<Checkpoint*>(end); auto chk = static_cast<Checkpoint*>(end);
g_checkpoints.remove_checkpoint(end);
g_checkpoints.erase(chk);
P_RemoveMobj(chk->orb()); P_RemoveMobj(chk->orb());
P_RemoveMobj(chk->arm());
} }
void Obj_CheckpointThink(mobj_t* end) void Obj_CheckpointThink(mobj_t* end)
@ -480,39 +563,64 @@ void Obj_CheckpointThink(mobj_t* end)
chk->animate(); chk->animate();
} }
void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) void __attribute__((optimize("O0"))) 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); LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius);
auto it = std::find_if( auto it = find_if(
g_checkpoints.begin(), g_checkpoints.begin(),
g_checkpoints.end(), g_checkpoints.end(),
[&](const Checkpoint* chk) [&](auto chkpair)
{ {
Checkpoint* chk = chkpair.first;
if (!chk->valid()) if (!chk->valid())
{ {
return false; return false;
} }
LineOnDemand gate = chk->crossing_line(); LineOnDemand* gate;
if (chkpair.second.empty())
{
LineOnDemand dyngate = chk->crossing_line();
if (!ray.overlaps(dyngate))
return false;
gate = &dyngate;
}
else
{
auto it = find_if(
chkpair.second.begin(),
chkpair.second.end(),
[&](const line_t* line)
{
return ray.overlaps(*line);
}
);
if (it == chkpair.second.end())
{
return false;
}
line_t* line = *it;
gate = static_cast<LineOnDemand*>(line);
}
// Check if the bounding boxes of the two lines // Check if the bounding boxes of the two lines
// overlap. This relies on the player movement not // overlap. This relies on the player movement not
// being so large that it creates an oversized box, // being so large that it creates an oversized box,
// but thankfully that doesn't seem to happen, under // but thankfully that doesn't seem to happen, under
// normal circumstances. // normal circumstances.
if (!ray.overlaps(gate))
{
return false;
}
INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, &gate); INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, gate);
INT32 oldside = P_PointOnLineSide(old_x, old_y, &gate); INT32 oldside = P_PointOnLineSide(old_x, old_y, gate);
if (side == oldside) if (side == oldside)
{ {
// Did not cross. // Did not cross.
return false; return false;
} }
return true; return true;
@ -524,41 +632,58 @@ void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y)
return; return;
} }
Checkpoint* chk = *it; Checkpoint* chk = it->first;
if (chk->activated()) if (player->checkpointId == chk->id())
{ {
return; return;
} }
for (Checkpoint* chk : g_checkpoints) if (player->position <= 1)
{ {
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);
}
if (gametyperules & GTR_CHECKPOINTS)
{
for (auto chkpair : g_checkpoints)
{
Checkpoint* chk = chkpair.first;
if (chk->valid()) 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->untwirl();
chk->other()->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); S_StartSound(player->mo, sfx_s3k63);
player->checkpointId = chk->id(); player->checkpointId = chk->id();
if (D_NumPlayersInRace() > 1 && !K_IsPlayerLosing(player))
{
if (player->position == 1)
{
player->lapPoints += 2;
}
else
{
player->lapPoints += 1;
}
}
player->exp += K_GetExpAdjustment(player);
K_UpdatePowerLevels(player, player->laps, false);
} }
mobj_t *Obj_FindCheckpoint(INT32 id) mobj_t* Obj_FindCheckpoint(INT32 id)
{ {
auto it = g_checkpoints.find(id); return g_checkpoints.find_checkpoint(id);
return it != g_checkpoints.end() ? *it : nullptr;
} }
boolean Obj_GetCheckpointRespawnPosition(const mobj_t* mobj, vector3_t* return_pos) boolean Obj_GetCheckpointRespawnPosition(const mobj_t* mobj, vector3_t* return_pos)
@ -593,3 +718,27 @@ void Obj_ActivateCheckpointInstantly(mobj_t* mobj)
chk->other()->activate(); chk->other()->activate();
} }
} }
// Returns a count of checkpoint gates, not objects
UINT32 Obj_GetCheckpointCount()
{
return g_checkpoints.count();
}
void Obj_ClearCheckpoints()
{
g_checkpoints.clear();
}
void Obj_DeactivateCheckpoints()
{
for (auto chkpair : g_checkpoints)
{
Checkpoint* chk = chkpair.first;
if (chk->valid())
{
chk->untwirl();
chk->other()->untwirl();
}
}
}

View file

@ -3092,11 +3092,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
{ {
K_DoPowerClash(target, inflictor); K_DoPowerClash(target, inflictor);
if (inflictor->type != MT_PLAYER)
{
K_SpawnAmps(player, 5, inflictor);
}
if (inflictor->type == MT_SUPER_FLICKY) if (inflictor->type == MT_SUPER_FLICKY)
{ {
Obj_BlockSuperFlicky(inflictor); Obj_BlockSuperFlicky(inflictor);
@ -3565,4 +3560,6 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
{ {
P_FlingBurst(player, fa, MT_DEBTSPIKE, 0, 3 * FRACUNIT / 2, i++); P_FlingBurst(player, fa, MT_DEBTSPIKE, 0, 3 * FRACUNIT / 2, i++);
} }
K_DefensiveOverdrive(player);
} }

View file

@ -12572,8 +12572,6 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
return false; return false;
break; break;
case MT_CHECKPOINT_END: case MT_CHECKPOINT_END:
if (!(gametyperules & GTR_CHECKPOINTS))
return false;
break; break;
default: default:
break; break;

View file

@ -281,6 +281,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].laps); WRITEUINT8(save->p, players[i].laps);
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].exp);
WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].cheatchecknum);
WRITEINT32(save->p, players[i].checkpointId); WRITEINT32(save->p, players[i].checkpointId);
@ -743,7 +744,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
} }
#endif #endif
WRITEUINT8(save->p, players[i].itemRoulette.useOdds); WRITEUINT32(save->p, players[i].itemRoulette.preexpdist);
WRITEUINT32(save->p, players[i].itemRoulette.dist); WRITEUINT32(save->p, players[i].itemRoulette.dist);
WRITEUINT32(save->p, players[i].itemRoulette.index); WRITEUINT32(save->p, players[i].itemRoulette.index);
WRITEUINT8(save->p, players[i].itemRoulette.sound); WRITEUINT8(save->p, players[i].itemRoulette.sound);
@ -937,6 +938,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].laps = READUINT8(save->p); // Number of laps (optional) players[i].laps = READUINT8(save->p); // Number of laps (optional)
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].exp = READINT32(save->p);
players[i].cheatchecknum = READINT32(save->p); players[i].cheatchecknum = READINT32(save->p);
players[i].checkpointId = READINT32(save->p); players[i].checkpointId = READINT32(save->p);
@ -1366,7 +1368,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
} }
#endif #endif
players[i].itemRoulette.useOdds = READUINT8(save->p); players[i].itemRoulette.preexpdist = READUINT32(save->p);
players[i].itemRoulette.dist = READUINT32(save->p); players[i].itemRoulette.dist = READUINT32(save->p);
players[i].itemRoulette.index = (size_t)READUINT32(save->p); players[i].itemRoulette.index = (size_t)READUINT32(save->p);
players[i].itemRoulette.sound = READUINT8(save->p); players[i].itemRoulette.sound = READUINT8(save->p);

View file

@ -118,6 +118,7 @@
#include "k_hud.h" // K_ClearPersistentMessages #include "k_hud.h" // K_ClearPersistentMessages
#include "k_endcam.h" #include "k_endcam.h"
#include "k_credits.h" #include "k_credits.h"
#include "k_objects.h"
// Replay names have time // Replay names have time
#if !defined (UNDER_CE) #if !defined (UNDER_CE)
@ -8586,6 +8587,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
LUA_InvalidateLevel(); LUA_InvalidateLevel();
Obj_ClearCheckpoints();
for (ss = sectors; sectors+numsectors != ss; ss++) for (ss = sectors; sectors+numsectors != ss; ss++)
{ {
Z_Free(ss->attached); Z_Free(ss->attached);

View file

@ -2117,6 +2117,14 @@ static void K_HandleLapIncrement(player_t *player)
player->lapPoints++; player->lapPoints++;
} }
} }
player->exp += K_GetExpAdjustment(player);
if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS))
{
Obj_DeactivateCheckpoints();
}
player->checkpointId = 0;
} }
// Set up lap animation vars // Set up lap animation vars