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_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_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;
}
/** 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.
*/
boolean D_IsPlayerHumanAndGaming (INT32 player_number)

View file

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

View file

@ -495,9 +495,8 @@ struct itemroulette_t
SINT8 *itemList;
#endif
UINT8 useOdds;
UINT8 playing, exiting;
UINT32 dist, baseDist;
UINT32 preexpdist, dist, baseDist;
UINT32 firstDist, secondDist;
UINT32 secondToFirst;
@ -914,6 +913,7 @@ struct player_t
UINT8 laps; // Number of laps (optional)
UINT8 latestlap;
UINT32 lapPoints; // Points given from laps
INT32 exp;
INT32 cheatchecknum; // The number of the last cheatcheck you hit
INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp

View file

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

View file

@ -1061,7 +1061,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
return NULL;
}
static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item)
patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item)
{
UINT8 offset;
@ -5736,60 +5736,28 @@ static void K_drawDistributionDebugger(void)
itemroulette_t rouletteData = {0};
const fixed_t scale = (FRACUNIT >> 1);
const fixed_t space = 24 * scale;
const fixed_t pad = 9 * scale;
fixed_t x = -pad;
fixed_t y = -pad;
size_t i;
if (R_GetViewNumber() != 0) // only for p1
{
return;
}
K_FillItemRouletteData(stplyr, &rouletteData, false);
K_FillItemRouletteData(stplyr, &rouletteData, false, true);
for (i = 0; i < rouletteData.itemListLen; i++)
{
const kartitems_t item = static_cast<kartitems_t>(rouletteData.itemList[i]);
UINT8 amount = 1;
if (cv_kartdebugdistribution.value <= 1)
return;
if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad)
{
x += space;
y = -pad;
}
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+10, V_SNAPTOTOP|V_SNAPTORIGHT, va("speed = %u", rouletteData.speed));
V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP,
K_GetSmallStaticCachedItemPatch(item), NULL);
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+22, V_SNAPTOTOP|V_SNAPTORIGHT, va("baseDist = %u", rouletteData.baseDist));
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+30, V_SNAPTOTOP|V_SNAPTORIGHT, va("dist = %u", rouletteData.dist));
// Display amount for multi-items
amount = K_ItemResultToAmount(item);
if (amount > 1)
{
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));
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+42, V_SNAPTOTOP|V_SNAPTORIGHT, va("firstDist = %u", rouletteData.firstDist));
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+50, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondDist = %u", rouletteData.secondDist));
V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+58, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondToFirst = %u", rouletteData.secondToFirst));
#ifndef ITEM_LIST_SIZE
Z_Free(rouletteData.itemList);
@ -6096,7 +6064,7 @@ void K_ClearPersistentMessages()
}
// 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)
return;
@ -6564,6 +6532,10 @@ void K_drawKartHUD(void)
if (cv_kartdebugdistribution.value)
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)
{
UINT8 p;

View file

@ -108,11 +108,13 @@ extern patch_t *kp_facenum[MAXPLAYERS+1];
extern patch_t *kp_unknownminimap;
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_ClearPersistentMessageForPlayer(player_t *player);
void K_TickMessages(void);
patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item);
typedef enum
{
PLAYERTAG_NONE,

View file

@ -4007,8 +4007,10 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact)
if (gametyperules & GTR_SPHERES)
return;
// Give that Sonic guy some help.
UINT16 scaledamps = min(amps, amps * (10 + player->kartspeed - player->kartweight) / 10);
UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10);
if (player->position <= 1)
scaledamps /= 2;
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)
{
// 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);
}
}
@ -4091,7 +4100,7 @@ boolean K_Overdrive(player_t *player)
S_StartSound(player->mo, sfx_cdfm35);
S_StartSound(player->mo, sfx_cdfm13);
player->overdrive += (player->amps)*6;
player->overdrive += (player->amps)*5;
player->overshield += (player->amps)*2;
player->overdrivepower = FRACUNIT;
@ -4112,7 +4121,7 @@ boolean K_DefensiveOverdrive(player_t *player)
S_StartSound(player->mo, sfx_cdfm35);
S_StartSound(player->mo, sfx_cdfm13);
player->overdrive += (player->amps)*4;
player->overdrive += (player->amps)*3;
player->overshield += (player->amps)*2 + TICRATE*2;
player->overdrivepower = FRACUNIT;
@ -7424,7 +7433,7 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds)
// Avoid calling K_FillItemRouletteData since that
// function resets PR_ITEM_ROULETTE.
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
{
UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing);
behind = FixedMul(behind, max(player->exp, FRACUNIT/2));
UINT32 behindMulti = behind / 500;
behindMulti = min(behindMulti, 60);
award = award * (behindMulti + 10) / 10;
@ -12982,6 +12992,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
else
player->rocketsneakertimer -= 3*TICRATE;
player->botvars.itemconfirm = 2*TICRATE;
player->overshield += TICRATE/2; // TEMP prototype
}
}
else if (player->itemamount == 0)
@ -12997,6 +13008,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
K_DoSneaker(player, 1);
K_PlayBoostTaunt(player->mo);
player->overshield += TICRATE/2; // TEMP prototype
player->sneakertimer += TICRATE; // TEMP prototype
player->itemamount--;
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);
}
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);
fixed_t K_GetExpAdjustment(player_t *player);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -263,6 +263,9 @@ 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);
UINT32 Obj_GetCheckpointCount();
void Obj_ClearCheckpoints();
void Obj_DeactivateCheckpoints();
/* Rideroid / Rideroid Node */
void Obj_RideroidThink(mobj_t *mo);

View file

@ -26,6 +26,7 @@
#include "byteptr.h"
#include "k_race.h"
#include "command.h"
#include "k_objects.h"
// I was ALMOST tempted to start tearing apart all
// of the map loading code and turning it into C++
@ -510,7 +511,8 @@ void gpRank_t::Update(void)
}
lvl->time = UINT32_MAX;
lvl->totalLapPoints = K_RaceLapCount(gamemap - 1) * 2;
lvl->totalLapPoints = ( K_RaceLapCount(gamemap - 1) + Obj_GetCheckpointCount() )* 2;
lvl->totalPrisons = maptargets;
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
an item bracket, and adjusted for special
factors (such as Frantic Items).
Gets legacy item priority.
Currently used only for Battle monitors/spawners.
Input Arguments:-
player - The player we intend to give the item to later.
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.
item - The item to give.
@ -97,11 +94,11 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result);
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
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.
roulette - The roulette data struct to fill out.
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:-
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 "g_party.h"
#include "g_input.h"
#include "k_objects.h"
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)
{
laps = player->lapPoints;
totalLaps = numlaps;
totalLaps = numlaps + numlaps * Obj_GetCheckpointCount();
if (inDuel == false)
{

View file

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

View file

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

View file

@ -17,6 +17,7 @@
#include "../doomdef.h"
#include "../doomtype.h"
#include "../info.h"
#include "../g_game.h"
#include "../k_color.h"
#include "../k_kart.h"
#include "../k_objects.h"
@ -34,9 +35,17 @@
#include "../sounds.h"
#include "../tables.h"
using std::vector;
using std::pair;
using std::min;
using std::max;
using std::clamp;
extern mobj_t* svg_checkpoints;
#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_orb(o) ((o)->tracer)
#define checkpoint_arm(o) ((o)->hnext)
@ -51,12 +60,14 @@ namespace
struct LineOnDemand : line_t
{
LineOnDemand(const line_t* line) {}
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)},
.bbox = {max(y1, y2), min(y1, y2), min(x1, x2), max(x1, x2)},
},
v1_data_ {.x = x1, .y = y1}
{
@ -76,6 +87,12 @@ struct LineOnDemand : line_t
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:
vertex_t v1_data_;
};
@ -170,6 +187,30 @@ struct Checkpoint : mobj_t
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()
{
orient();
@ -181,10 +222,11 @@ struct Checkpoint : mobj_t
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);
}
@ -193,7 +235,7 @@ struct Checkpoint : mobj_t
void twirl(angle_t dir, fixed_t multiplier)
{
var(0);
speed_multiplier(std::clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier));
speed_multiplier(clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier));
speed(FixedDiv(kBaseSpeed, speed_multiplier()));
reverse(AngleDeltaSigned(angle_to_other(), dir) > 0);
@ -266,7 +308,7 @@ private:
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()
@ -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); };
@ -324,10 +366,10 @@ private:
if (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->scalespeed = p->scale / 35;
p->color = SKINCOLOR_ULTRAMARINE;
p->color = color;
p->fuse = 0;
// Something lags at the start of the level. The
@ -342,7 +384,7 @@ private:
}
else
{
p->color = K_RainbowColor(leveltime);
p->color = color;
p->fuse = 2;
}
}
@ -369,7 +411,8 @@ private:
{x + FixedMul(ofs, FCOS(a)), y + FixedMul(ofs, FSIN(a)), z + (kSparkleZ * scale)},
momentum,
momentum / 2,
dir
dir,
activated() ? SKINCOLOR_GREEN : SKINCOLOR_ULTRAMARINE
);
}
}
@ -402,14 +445,93 @@ struct CheckpointManager
auto begin() { return list_.begin(); }
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)
{
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);
}
}
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(
"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, "%s", msg.c_str());
return;
}
if (auto other = find_checkpoint(id))
{
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, "%s", msg.c_str());
return;
}
other->other(chk);
chk->other(other);
}
else // Checkpoint isn't in the list, find any associated tagged lines and make the pair
{
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();
}
void clear() { list_.clear(); }
auto count() { return list_.size(); }
private:
srb2::MobjList<Checkpoint, svg_checkpoints> list_;
vector<pair<Checkpoint*, vector<line_t*>>> list_;
};
CheckpointManager g_checkpoints;
@ -418,54 +540,15 @@ CheckpointManager 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, "%s", 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, "%s", msg.c_str());
return;
}
other->other(chk);
chk->other(other);
}
else
{
g_checkpoints.push_front(chk);
}
chk->gingerbread();
g_checkpoints.link_checkpoint(end);
}
void Obj_UnlinkCheckpoint(mobj_t* end)
{
auto chk = static_cast<Checkpoint*>(end);
g_checkpoints.erase(chk);
g_checkpoints.remove_checkpoint(end);
P_RemoveMobj(chk->orb());
P_RemoveMobj(chk->arm());
}
void Obj_CheckpointThink(mobj_t* end)
@ -480,39 +563,64 @@ void Obj_CheckpointThink(mobj_t* end)
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);
auto it = std::find_if(
auto it = find_if(
g_checkpoints.begin(),
g_checkpoints.end(),
[&](const Checkpoint* chk)
[&](auto chkpair)
{
Checkpoint* chk = chkpair.first;
if (!chk->valid())
{
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
// 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);
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;
@ -524,41 +632,58 @@ void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y)
return;
}
Checkpoint* chk = *it;
Checkpoint* chk = it->first;
if (chk->activated())
if (player->checkpointId == chk->id())
{
return;
}
for (Checkpoint* chk : g_checkpoints)
if (player->position <= 1)
{
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);
}
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())
{
chk->untwirl();
chk->other()->untwirl();
}
}
}
S_StartSound(player->mo, sfx_s3k63);
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 it != g_checkpoints.end() ? *it : nullptr;
return g_checkpoints.find_checkpoint(id);
}
boolean Obj_GetCheckpointRespawnPosition(const mobj_t* mobj, vector3_t* return_pos)
@ -593,3 +718,27 @@ void Obj_ActivateCheckpointInstantly(mobj_t* mobj)
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);
if (inflictor->type != MT_PLAYER)
{
K_SpawnAmps(player, 5, inflictor);
}
if (inflictor->type == MT_SUPER_FLICKY)
{
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++);
}
K_DefensiveOverdrive(player);
}

View file

@ -12572,8 +12572,6 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
return false;
break;
case MT_CHECKPOINT_END:
if (!(gametyperules & GTR_CHECKPOINTS))
return false;
break;
default:
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].latestlap);
WRITEUINT32(save->p, players[i].lapPoints);
WRITEINT32(save->p, players[i].exp);
WRITEINT32(save->p, players[i].cheatchecknum);
WRITEINT32(save->p, players[i].checkpointId);
@ -743,7 +744,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
}
#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.index);
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].latestlap = READUINT8(save->p);
players[i].lapPoints = READUINT32(save->p);
players[i].exp = READINT32(save->p);
players[i].cheatchecknum = READINT32(save->p);
players[i].checkpointId = READINT32(save->p);
@ -1366,7 +1368,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
}
#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.index = (size_t)READUINT32(save->p);
players[i].itemRoulette.sound = READUINT8(save->p);

View file

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

View file

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