Merge branch 'master' into ufo-master-difficulty

This commit is contained in:
eebrozgi 2025-06-27 19:52:25 +03:00
commit 1286579457
47 changed files with 1008 additions and 251 deletions

View file

@ -3738,8 +3738,6 @@ static void Got_AddPlayer(const UINT8 **p, INT32 playernum)
CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum);
//G_SpectatePlayerOnJoin(newplayernum); -- caused desyncs in this spot :(
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
@ -3793,6 +3791,13 @@ static void Got_AddPlayer(const UINT8 **p, INT32 playernum)
players[newplayernum].splitscreenindex = splitscreenplayer;
players[newplayernum].bot = false;
// Previously called at the top of this function, commented as
// "caused desyncs in this spot :(". But we can't do this in
// G_PlayerReborn, since that only runs for level contexts and
// allows people to party-crash the vote screen even when
// maxplayers is too low for them. Let's try it here...?
G_SpectatePlayerOnJoin(newplayernum);
if (node == mynode && splitscreenplayer == 0)
S_AttemptToRestoreMusic(); // Earliest viable point
@ -5397,7 +5402,7 @@ static void FuzzTiccmd(ticcmd_t* target)
{
target->buttons |= BT_ACCELERATE;
target->buttons &= ~BT_LOOKBACK;
target->buttons &= ~BT_RESPAWN;
target->buttons &= ~BT_BAIL;
target->buttons &= ~BT_BRAKE;
}
}

View file

@ -135,11 +135,12 @@ typedef enum
typedef enum
{
PF2_SELFMUTE = 1<<1,
PF2_SELFDEAFEN = 1<<2,
PF2_SERVERMUTE = 1<<3,
PF2_SERVERDEAFEN = 1<<4,
PF2_STRICTFASTFALL = 1<<5,
PF2_SELFMUTE = 1<<1,
PF2_SELFDEAFEN = 1<<2,
PF2_SERVERMUTE = 1<<3,
PF2_SERVERDEAFEN = 1<<4,
PF2_STRICTFASTFALL = 1<<5,
PF2_ALWAYSDAMAGED = 1<<6,
} pflags2_t;
typedef enum
@ -430,6 +431,8 @@ struct botvars_t
tic_t rouletteTimeout; // If it takes too long to decide, try lowering priority until we find something valid.
angle_t predictionError; // How bad is our momentum angle relative to where we're trying to go?
angle_t recentDeflection; // How long have we been going straight? (See k_bot.h)
angle_t lastAngle;
};
// player_t struct for round-specific condition tracking
@ -738,7 +741,7 @@ struct player_t
UINT8 tumbleBounces;
UINT16 tumbleHeight; // In *mobjscaled* fracunits, or mfu, not raw fu
UINT16 stunned; // Number of tics during which rings cannot be picked up
UINT8 stunnedCombo; // Number of hits sustained while stunned, reduces consecutive stun penalties
mobj_t *flybot; // One Flybot767 circling the player while stunned
UINT8 justDI; // Turn-lockout timer to briefly prevent unintended turning after DI, resets when actionable or no input
boolean flipDI; // Bananas flip the DI direction. Was a bug, but it made bananas much more interesting.
@ -1079,6 +1082,10 @@ struct player_t
UINT16 progressivethrust; // When getting beat up in GTR_BUMPERS, speed up the longer you've been out of control.
UINT8 ringvisualwarning; // Check with > 1, not >= 1! Set when put in debt, counts down and holds at 1 when still in debt.
UINT32 bailcharge;
UINT32 baildrop;
boolean bailquake;
boolean analoginput; // Has an input been recorded that requires analog usage? For input display.
boolean markedfordeath;

View file

@ -32,12 +32,13 @@ typedef enum
BT_BRAKE = 1<<3, // Brake
BT_ATTACK = 1<<4, // Use Item
BT_LOOKBACK = 1<<5, // Look Backward
BT_RESPAWN = 1<<6, // Respawn
BT_BAIL = 1<<6, // Bail
BT_VOTE = 1<<7, // Vote
BT_SPINDASH = 1<<8, // Spindash
BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE),
BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT),
BT_RESPAWNMASK = (BT_EBRAKEMASK|BT_BAIL),
// free: 1<<9 to 1<<12

View file

@ -1574,6 +1574,13 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_BLOCKRING",
"S_BLOCKBODY",
"S_BAIL",
"S_BAIB1",
"S_BAIB2",
"S_BAIB3",
"S_BAIC",
"S_BAILCHARGE",
"S_AMPRING",
"S_AMPBODY",
"S_AMPAURA",
@ -3564,6 +3571,10 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_BLOCKRING",
"MT_BLOCKBODY",
"MT_BAIL",
"MT_BAILCHARGE",
"MT_BAILSPARKLE",
"MT_AMPRING",
"MT_AMPBODY",
"MT_AMPAURA",
@ -5048,11 +5059,12 @@ struct int_const_s const INT_CONST[] = {
{"BT_BRAKE",BT_BRAKE},
{"BT_ATTACK",BT_ATTACK},
{"BT_LOOKBACK",BT_LOOKBACK},
{"BT_RESPAWN",BT_RESPAWN},
{"BT_BAIL",BT_BAIL},
{"BT_VOTE",BT_VOTE},
{"BT_SPINDASH",BT_SPINDASH}, // Real button now, but triggers the macro same as always.
{"BT_EBRAKEMASK",BT_EBRAKEMASK}, // Macro button
{"BT_SPINDASHMASK",BT_SPINDASHMASK}, // Macro button
{"BT_RESPAWNMASK",BT_RESPAWNMASK}, // Macro button
{"BT_LUAA",BT_LUAA}, // Lua customizable
{"BT_LUAB",BT_LUAB}, // Lua customizable
{"BT_LUAC",BT_LUAC}, // Lua customizable

View file

@ -747,9 +747,9 @@ extern int
// Exp
#define EXP_STABLERATE 3*FRACUNIT/10 // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain
#define EXP_POWER 3*FRACUNIT/100 // adjust to change overall xp volatility
#define MINEXP 25 // The min value target
#define TARGETEXP 120 // Used for grading ...
#define MAXEXP 120 // The max value displayed by the hud and in the tally screen and GP results screen
#define EXP_MIN 25 // The min value target
#define EXP_TARGET 120 // Used for grading ...
#define EXP_MAX 120 // The max value displayed by the hud and in the tally screen and GP results screen
#ifdef __cplusplus
} // extern "C"

View file

@ -198,6 +198,8 @@ typedef enum
GRADE_S
} gp_rank_e;
#define SEALED_STAR_ENTRY (370*FRACUNIT/400)
struct cupwindata_t
{
UINT8 best_placement;

View file

@ -404,10 +404,7 @@ class TiccmdBuilder
map(gc_item, BT_ATTACK); // fire
map(gc_lookback, BT_LOOKBACK); // rear view
if (!modeattacking)
{
map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn
}
map(gc_bail, BT_BAIL); // bail
map(gc_vote, BT_VOTE); // mp general function button
// lua buttons a thru c

View file

@ -2313,7 +2313,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
jointime = players[player].jointime;
if (jointime <= 1)
{
G_SpectatePlayerOnJoin(player);
// Now called in Got_AddPlayer. In case of weirdness, break glass.
// G_SpectatePlayerOnJoin(player);
betweenmaps = true;
}
@ -4381,7 +4382,7 @@ void G_GetNextMap(void)
&& grandprixinfo.gamespeed >= KARTSPEED_NORMAL)
{
// On A rank pace? Then you get a chance for S rank!
permitrank = (K_CalculateGPGrade(&grandprixinfo.rank) >= GRADE_A);
permitrank = (K_CalculateGPPercent(&grandprixinfo.rank) >= SEALED_STAR_ENTRY);
// If you're on Master, a win floats you to rank-restricted levels for free.
// (This is a different class of challenge!)

View file

@ -123,7 +123,7 @@ typedef enum
gc_lookback = gc_b,
gc_spindash = gc_c,
gc_brake = gc_x,
gc_respawn = gc_y,
gc_bail = gc_y,
gc_vote = gc_z,
gc_item = gc_l,
gc_drift = gc_r,

View file

@ -113,7 +113,7 @@ void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boo
box.patch(but('B', gc_b, BT_LOOKBACK));
box.patch(but('C', gc_c, BT_SPINDASH));
box.patch(but('X', gc_x, BT_BRAKE));
box.patch(but('Y', gc_y, BT_RESPAWN));
box.patch(but('Y', gc_y, BT_BAIL));
box.patch(but('Z', gc_z, BT_VOTE));
box.patch(but('L', gc_l, BT_ATTACK));
box.patch(but('R', gc_r, BT_DRIFT));

View file

@ -198,7 +198,7 @@ void K_drawSpectatorHUD(boolean director)
}
else
{
bool press = D_LocalTiccmd(viewnum)->buttons & BT_RESPAWN;
bool press = D_LocalTiccmd(viewnum)->buttons & BT_BAIL;
const char* label = (press && I_GetTime() % 16 < 8) ? "> <" : ">< ";
list.insert({{label, press ? "<y_pressed>" : "<y>"}, {"Exit", "<c_animated>"}});

View file

@ -334,6 +334,11 @@ char sprnames[NUMSPRITES + 1][5] =
"GRNG", // Guard ring
"GBDY", // Guard body
"BAIL", // Bail charge
"BAIB", // Bail after effect
"BAIC", // Bail sparkle
"TECH", // Bail tech charge
"TRC1", // Charge aura
"TRC2", // Charge fall
"TRC3", // Charge flicker/sparks
@ -2160,6 +2165,16 @@ state_t states[NUMSTATES] =
{SPR_GRNG, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_BLOCKRING
{SPR_GBDY, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_BLOCKBODY
// why can we not use actions on spawn? I'd love to fix it but I imagine all sorts of crazy pain if I change something fundamental like that
{SPR_BAIL, FF_FULLBRIGHT|FF_ANIMATE|0, 9, {NULL}, 8, 1, S_BAIB1}, // S_BAIL
{SPR_BAIB, 0, 0, {A_PlaySound}, sfx_gshb2, 2, S_BAIB2}, // S_BAIB1
{SPR_BAIB, 0, 0, {A_PlaySound}, sfx_gshbd, 2, S_BAIB3}, // S_BAIB2
{SPR_BAIB, FF_FULLBRIGHT|FF_ANIMATE|0, 10, {NULL}, 9, 1, S_NULL}, // S_BAIB3
{SPR_BAIC, FF_FULLBRIGHT|FF_ANIMATE|0, 11, {NULL}, 10, 1, S_NULL}, // S_BAIC
{SPR_TECH, 1, -1, {NULL}, 41, 1, S_NULL}, // S_BAILCHARGE
{SPR_AMPB, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_AMPRING
{SPR_AMPC, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_AMPBODY
{SPR_AMPD, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_AMPAURA
@ -13745,6 +13760,87 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_BAIL
-1, // doomednum
S_BAIL, // 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
67*FRACUNIT, // radius
67*FRACUNIT, // height
1, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_BAILCHARGE
-1, // doomednum
S_BAILCHARGE, // 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
67*FRACUNIT, // radius
67*FRACUNIT, // height
1, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_BAILSPARKLE
-1, // doomednum
S_BAIC, // 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
67*FRACUNIT, // radius
67*FRACUNIT, // height
1, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_DONTENCOREMAP|MF_SCENERY, // flags
S_NULL // raisestate
},
{ // MT_AMPRING
-1, // doomednum
S_AMPRING, // spawnstate

View file

@ -875,6 +875,11 @@ typedef enum sprite
SPR_GRNG, // Guard ring
SPR_GBDY, // Guard body
SPR_BAIL, // Bail charge
SPR_BAIB, // Bail after effect
SPR_BAIC, // Bail sparkle
SPR_TECH, // Bail tech charge
SPR_TRC1, // Charge aura
SPR_TRC2, // Charge fall
SPR_TRC3, // Charge flicker/sparks
@ -2629,6 +2634,13 @@ typedef enum state
S_BLOCKRING,
S_BLOCKBODY,
S_BAIL,
S_BAIB1,
S_BAIB2,
S_BAIB3,
S_BAIC,
S_BAILCHARGE,
S_AMPRING,
S_AMPBODY,
S_AMPAURA,
@ -4646,6 +4658,10 @@ typedef enum mobj_type
MT_BLOCKRING,
MT_BLOCKBODY,
MT_BAIL,
MT_BAILCHARGE,
MT_BAILSPARKLE,
MT_AMPRING,
MT_AMPBODY,
MT_AMPAURA,

View file

@ -586,7 +586,7 @@ const botcontroller_t *K_GetBotController(const mobj_t *mobj)
fixed_t K_BotMapModifier(void)
{
// fuck it we ball
// return 10*FRACUNIT/10;
return 5*FRACUNIT/10;
constexpr INT32 complexity_scale = 10000;
fixed_t modifier_max = (10 * FRACUNIT / 10) - FRACUNIT;
@ -690,9 +690,9 @@ fixed_t K_BotRubberband(const player_t *player)
// Allow the status quo to assert itself a bit. Bots get most of their speed from their
// mechanics adjustments, not from items, so kill some bot speed if they've got bad EXP.
if (player->gradingfactor < FRACUNIT && !(player->botvars.rival))
if (player->gradingfactor < FRACUNIT && !(player->botvars.rival) && player->botvars.difficulty > 1)
{
UINT8 levelreduce = 3; // How much to drop the "effective level" of bots that are consistently behind
UINT8 levelreduce = std::min<UINT8>(3, player->botvars.difficulty); // How much to drop the "effective level" of bots that are consistently behind
expreduce = Easing_Linear((K_EffectiveGradingFactor(player) - MINGRADINGFACTOR) * 2, levelreduce*FRACUNIT, 0);
}
@ -823,12 +823,23 @@ fixed_t K_BotRubberband(const player_t *player)
fixed_t K_UpdateRubberband(player_t *player)
{
fixed_t dest = K_BotRubberband(player);
fixed_t deflect = player->botvars.recentDeflection;
if (deflect > BOTMAXDEFLECTION)
deflect = BOTMAXDEFLECTION;
dest = FixedMul(dest, Easing_Linear(
FixedDiv(deflect, BOTMAXDEFLECTION),
BOTSTRAIGHTSPEED,
BOTTURNSPEED
));
fixed_t ret = player->botvars.rubberband;
UINT8 ease_soften = 8;
UINT8 ease_soften = (ret > dest) ? 3 : 8;
if (player->botvars.bumpslow && dest > ret)
ease_soften *= 10;
ease_soften = 80;
// Ease into the new value.
ret += (dest - player->botvars.rubberband) / ease_soften;
@ -1792,7 +1803,7 @@ static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd)
if (K_TryRingShooter(player, botController) == true && player->botvars.respawnconfirm >= BOTRESPAWNCONFIRM)
{
// We want to respawn. Simply hold Y and stop here!
cmd->buttons |= (BT_RESPAWN | BT_EBRAKEMASK);
cmd->buttons |= BT_RESPAWNMASK;
return;
}
@ -2123,6 +2134,21 @@ void K_UpdateBotGameplayVars(player_t *player)
}
}
angle_t mangle = K_MomentumAngleEx(player->mo, 5*mapobjectscale); // magic threshold
angle_t langle = player->botvars.lastAngle;
angle_t dangle = 0;
if (mangle >= langle)
dangle = mangle - langle;
else
dangle = langle - mangle;
// Writing this made me move my tongue around in my mouth
UINT32 smo = BOTANGLESAMPLES - 1;
player->botvars.recentDeflection = (smo * player->botvars.recentDeflection / BOTANGLESAMPLES) + (dangle / BOTANGLESAMPLES);
player->botvars.lastAngle = mangle;
const botcontroller_t *botController = K_GetBotController(player->mo);
if (K_TryRingShooter(player, botController) == true)
{

View file

@ -35,7 +35,7 @@ extern "C" {
// How many tics in a row do you need to turn in this direction before we'll let you turn.
// Made it as small as possible without making it look like the bots are twitching constantly.
#define BOTTURNCONFIRM 4
#define BOTTURNCONFIRM 1
// How many tics with only one spindash-viable condition before we'll let you spindash.
#define BOTSPINDASHCONFIRM (4*TICRATE)
@ -46,6 +46,11 @@ extern "C" {
// How long it takes for a Lv.1 bot to decide to pick an item.
#define BOT_ITEM_DECISION_TIME (2*TICRATE)
#define BOTSTRAIGHTSPEED (80*FRACUNIT/100) // How fast we move when at 0 deflection.
#define BOTTURNSPEED (100*FRACUNIT/100) // How fast we move when at MAXDEFLECTION deflection.
#define BOTANGLESAMPLES (TICRATE) // Time period to average over. Higher values produce lower peaks that last longer.
#define BOTMAXDEFLECTION (ANG1*3) // Measured in "degrees per tic" here, use debugbots.
// Point for bots to aim for
struct botprediction_t
{

View file

@ -885,18 +885,28 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2)
thing = oldthing;
P_SetTarget(&g_tm.thing, oldg_tm.thing);*/
boolean hit = false;
if (K_KartBouncing(t2, t1->target) == true)
{
if (t2->player && t1->target && t1->target->player)
{
K_PvPTouchDamage(t2, t1->target);
hit = K_PvPTouchDamage(t2, t1->target);
}
// Don't play from t1 else it gets cut out... for some reason.
S_StartSound(t2, sfx_s3k44);
}
return true;
if (hit && (gametyperules & GTR_BUMPERS))
{
K_PopBubbleShield(t1->target->player);
return false;
}
else
{
return true;
}
}
if (K_BubbleShieldCanReflect(t1, t2))
@ -1158,6 +1168,27 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
return false;
}
boolean guard1 = K_PlayerGuard(t1->player);
boolean guard2 = K_PlayerGuard(t2->player);
// Bubble Shield physically extends past guard when inflated,
// makes some sense to suppress this behavior
if (t1->player->bubbleblowup)
guard1 = false;
if (t2->player->bubbleblowup)
guard2 = false;
if (guard1 && guard2)
K_DoPowerClash(t1, t2);
else if (guard1)
K_DoGuardBreak(t1, t2);
else if (guard2)
K_DoGuardBreak(t2, t1);
if (guard1 || guard2)
return false;
// Clash instead of damage if both parties have any of these conditions
auto canClash = [](mobj_t *t1, mobj_t *t2)
{

View file

@ -546,7 +546,7 @@ void K_HandleFollower(player_t *player)
}
// Sal: Turn the follower around when looking backwards.
if ( player->cmd.buttons & BT_LOOKBACK )
if (K_GetKartButtons(player) & BT_LOOKBACK)
{
destAngle += ANGLE_180;
}

View file

@ -34,6 +34,9 @@ void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage)
return;
}
if (mo->player && mo->player->overshield)
tics = min(tics, 3);
mo->hitlag += tics;
mo->hitlag = min(mo->hitlag, MAXHITLAGTICS);

View file

@ -4876,7 +4876,7 @@ static void K_drawKartPlayerCheck(void)
return;
}
if (stplyr->cmd.buttons & BT_LOOKBACK)
if (K_GetKartButtons(stplyr) & BT_LOOKBACK)
{
return;
}
@ -7095,7 +7095,8 @@ static void K_DrawBotDebugger(void)
V_DrawSmallString(8, 66, 0, va("Complexity: %d", K_GetTrackComplexity()));
V_DrawSmallString(8, 70, 0, va("Bot modifier: %.2f", FixedToFloat(K_BotMapModifier())));
V_DrawSmallString(8, 76, 0, va("Prediction error: %d", bot->botvars.predictionError));
V_DrawSmallString(8, 76, 0, va("Prediction error: %.2fdeg", FIXED_TO_FLOAT(FixedDiv(bot->botvars.predictionError, ANG1))));
V_DrawSmallString(8, 80, 0, va("Recent deflection: %.2fdeg", FIXED_TO_FLOAT(FixedDiv(bot->botvars.recentDeflection, ANG1))));
}
static void K_DrawGPRankDebugger(void)
@ -7119,6 +7120,7 @@ static void K_DrawGPRankDebugger(void)
}
grade = K_CalculateGPGrade(&grandprixinfo.rank);
fixed_t percent = K_CalculateGPPercent(&grandprixinfo.rank);
V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT,
va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION));
@ -7132,6 +7134,8 @@ static void K_DrawGPRankDebugger(void)
va("RINGS: %d / %d", grandprixinfo.rank.rings, grandprixinfo.rank.totalRings));
V_DrawThinString(0, 60, V_SNAPTOTOP|V_SNAPTOLEFT,
va("EMERALD: %s", (grandprixinfo.rank.specialWon == true) ? "YES" : "NO"));
V_DrawThinString(0, 70, V_SNAPTOTOP|V_SNAPTOLEFT,
va("PERCENT: %.2f", FixedToFloat(percent)));
switch (grade)
{
@ -7517,7 +7521,7 @@ void K_drawKartHUD(void)
if (ta)
{
using srb2::Draw;
Draw::TextElement text = Draw::TextElement().parse("<y> Restart");
Draw::TextElement text = Draw::TextElement().parse("<z> Restart");
Draw(BASEVIDWIDTH - 19, 2)
.flags(flags | V_YELLOWMAP)
.align(Draw::Align::kRight)

View file

@ -72,13 +72,17 @@
// comeback is Battle Mode's karma comeback, also bool
// mapreset is set when enough players fill an empty server
static void K_PopBubbleShield(player_t *player)
void K_PopBubbleShield(player_t *player)
{
if (player->curshield != KSHIELD_BUBBLE)
return;
S_StartSound(player->mo, sfx_kc31);
K_StripItems(player);
K_AddHitLag(player->mo, 4, false);
vector3_t offset = { 0, 0, 0 };
K_SpawnSingleHitLagSpark(player->mo, &offset, player->mo->scale*2, 4, 0, player->skincolor);
K_SpawnSingleHitLagSpark(player->mo, &offset, player->mo->scale*2, 4, 0, player->skincolor);
player->bubbledrag = false;
}
boolean K_ThunderDome(void)
@ -1179,20 +1183,6 @@ boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2)
K_SpawnBumpForObjs(mobj1, mobj2);
if (mobj1->type == MT_PLAYER && mobj2->type == MT_PLAYER
&& !mobj1->player->powerupVFXTimer && !mobj2->player->powerupVFXTimer)
{
boolean guard1 = K_PlayerGuard(mobj1->player);
boolean guard2 = K_PlayerGuard(mobj2->player);
if (guard1 && guard2)
K_DoPowerClash(mobj1, mobj2);
else if (guard1)
K_DoGuardBreak(mobj1, mobj2);
else if (guard2)
K_DoGuardBreak(mobj2, mobj1);
}
K_PlayerJustBumped(mobj1->player);
K_PlayerJustBumped(mobj2->player);
@ -1607,13 +1597,16 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
{
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));;
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));
player->draftpower += add;
if (player->bot && (player->botvars.rival || cv_levelskull.value))
if (player->bot)
{
// Double speed for the rival!
player->draftpower += add;
if (player->botvars.rival || cv_levelskull.value)
player->draftpower += add;
else if (dest->player->bot) // Reduce bot gluts.
player->draftpower -= 3*add/4;
}
if (gametyperules & GTR_CLOSERPLAYERS)
@ -4066,8 +4059,16 @@ boolean K_KartKickstart(const player_t *player)
UINT16 K_GetKartButtons(const player_t *player)
{
return (player->cmd.buttons |
(K_KartKickstart(player) ? BT_ACCELERATE : 0));
UINT16 buttons = player->cmd.buttons;
if ((buttons & BT_RESPAWNMASK) == BT_RESPAWNMASK)
{
buttons &= ~BT_LOOKBACK;
}
if (K_KartKickstart(player))
{
buttons = buttons | BT_ACCELERATE;
}
return buttons;
}
SINT8 K_GetForwardMove(const player_t *player)
@ -4553,12 +4554,16 @@ void K_DoPowerClash(mobj_t *t1, mobj_t *t2) {
UINT8 lag1 = 5;
UINT8 lag2 = 5;
boolean stripbubble = (gametyperules & GTR_BUMPERS);
// short-circuit instashield for vfx visibility
if (t1->player)
{
t1->player->instashield = 1;
t1->player->speedpunt += 20;
lag1 -= min(lag1, t1->player->speedpunt/10);
if (stripbubble && t1->player->curshield == KSHIELD_BUBBLE)
K_PopBubbleShield(t1->player);
}
if (t2->player)
@ -4566,6 +4571,8 @@ void K_DoPowerClash(mobj_t *t1, mobj_t *t2) {
t2->player->instashield = 1;
t2->player->speedpunt += 20;
lag2 -= min(lag1, t2->player->speedpunt/10);
if (stripbubble && t2->player->curshield == KSHIELD_BUBBLE)
K_PopBubbleShield(t2->player);
}
S_StartSound(t1, sfx_parry);
@ -4603,7 +4610,9 @@ void K_DoGuardBreak(mobj_t *t1, mobj_t *t2) {
angle_t thrangle = R_PointToAngle2(t2->x, t2->y, t1->x, t1->y);
P_Thrust(t1, thrangle, 7*mapobjectscale);
t1->player->pflags2 |= PF2_ALWAYSDAMAGED;
P_DamageMobj(t1, t2, t2, 1, DMG_TUMBLE);
t1->player->pflags2 &= ~PF2_ALWAYSDAMAGED;
clash = P_SpawnMobj((t1->x/2) + (t2->x/2), (t1->y/2) + (t2->y/2), (t1->z/2) + (t2->z/2), MT_GUARDBREAK);
@ -5180,6 +5189,8 @@ void K_UpdateWavedashIndicator(player_t *player)
{
mobj_t *mobj = NULL;
player->vortexBoost = 0;
if (player == NULL)
{
return;
@ -5193,7 +5204,6 @@ void K_UpdateWavedashIndicator(player_t *player)
if (player->wavedashIndicator == NULL || P_MobjWasRemoved(player->wavedashIndicator) == true)
{
K_InitWavedashIndicator(player);
player->vortexBoost = 0;
return;
}
@ -7768,6 +7778,30 @@ void K_PopPlayerShield(player_t *player)
K_UnsetItemOut(player);
}
static void K_DeleteHnextList(player_t *player)
{
mobj_t *work = player->mo, *nextwork;
if (work == NULL || P_MobjWasRemoved(work))
{
return;
}
nextwork = work->hnext;
while ((work = nextwork) && !(work == NULL || P_MobjWasRemoved(work)))
{
nextwork = work->hnext;
if (!work->health)
continue; // taking care of itself
K_SpawnLandMineExplosion(work, player->skincolor, player->mo->hitlag);
P_RemoveMobj(work);
}
}
void K_DropHnextList(player_t *player)
{
mobj_t *work = player->mo, *nextwork, *dropwork;
@ -9734,23 +9768,12 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
&& P_IsObjectOnGround(player->mo)
)
{
// MEGA FUCKING HACK BECAUSE P_SAVEG MOBJS ARE FULL
// Would updating player_saveflags to 32 bits have any negative consequences?
// For now, player->stunned 16th bit is a flag to determine whether the flybots were spawned
// timer counts down at triple speed while spindashing
player->stunned = (player->stunned & 0x8000) | max(0, (player->stunned & 0x7FFF) - (player->spindash ? 3 : 1));
player->stunned = max(0, player->stunned - (player->spindash ? 3 : 1));
// when timer reaches 0, reset the flag and stun combo counter
if ((player->stunned & 0x7FFF) == 0)
// if the flybots aren't spawned, spawn them now!
if (player->stunned != 0 && P_MobjWasRemoved(player->flybot))
{
player->stunned = 0;
player->stunnedCombo = 0;
}
// otherwise if the flybots aren't spawned, spawn them now!
else if ((player->stunned & 0x8000) == 0)
{
player->stunned |= 0x8000;
Obj_SpawnFlybotsForPlayer(player);
}
}
@ -9785,7 +9808,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
// UINT16 oldringboost = player->ringboost;
if (player->superring == 0 || player->stunned)
if (!player->baildrop && (player->superring == 0 || player->stunned))
player->ringboost -= max((player->ringboost / roller), 1);
else if (K_LegacyRingboost(player))
player->ringboost--;
@ -9976,6 +9999,51 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
}
}
if (player->baildrop)
{
if (player->stunned & 0x8000)
player->stunned = 0x8000 | BAILSTUN;
else
player->stunned = BAILSTUN;
mobj_t *pmo = player->mo;
// particle spawn
#define BAILSPARKLE_MAXBAIL 61 // amount of bail rings needed for max sparkle spawn frequency
UINT32 baildropinversefreq = BAILSPARKLE_MAXBAIL - min(player->baildrop, BAILSPARKLE_MAXBAIL-6);
UINT32 baildropmodulo = baildropinversefreq *5/3 /10;
if ((leveltime % (1+baildropmodulo)) == 0)
{
mobj_t *sparkle = P_SpawnMobj(pmo->x + (P_RandomRange(PR_DECORATION, -40,40) * pmo->scale),
pmo->y + (P_RandomRange(PR_DECORATION, -40,40) * pmo->scale),
pmo->z + (pmo->height/2) + (P_RandomRange(PR_DECORATION, -40,40) * pmo->scale),
MT_BAILSPARKLE);
sparkle->scale = pmo->scale;
sparkle->angle = pmo->angle;
sparkle->momx = 3*pmo->momx/4;
sparkle->momy = 3*pmo->momy/4;
sparkle->momz = 3*P_GetMobjZMovement(pmo)/4;
K_MatchGenericExtraFlags(sparkle, pmo);
sparkle->renderflags = (pmo->renderflags & ~RF_TRANSMASK);//|RF_TRANS20|RF_ADD;
}
if ((player->baildrop % BAIL_DROPFREQUENCY) == 0)
{
P_FlingBurst(player, K_MomentumAngle(pmo), MT_FLINGRING, 10*TICRATE, FRACUNIT, player->baildrop/BAIL_DROPFREQUENCY);
S_StartSound(pmo, sfx_gshad);
}
player->baildrop--;
if (player->baildrop == 0)
player->ringboost /= 3;
}
if (player->bailquake && !player->mo->hitlag) // quake as soon as we leave hitlag
{
P_StartQuakeFromMobj(7, 50 * player->mo->scale, 2048 * player->mo->scale, player->mo);
player->bailquake = false;
}
// The precise ordering of start-of-level made me want to cut my head off,
// so let's try this instead. Whatever!
if (leveltime <= starttime || player->gradingpointnum == 0)
@ -10057,7 +10125,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
K_RemoveGrowShrink(player);
}
if (player->respawn.state != RESPAWNST_MOVE && (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN)
if (player->respawn.state != RESPAWNST_MOVE && (player->cmd.buttons & BT_RESPAWNMASK) == BT_RESPAWNMASK)
{
player->finalfailsafe++; // Decremented by ringshooter to "freeze" this timer
// Part-way through the auto-respawn timer, you can tap Ring Shooter to respawn early
@ -10143,7 +10211,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->nextringaward >= ringrate)
{
if (player->instaWhipCharge)
if (player->instaWhipCharge || player->baildrop || player->bailcharge)
{
// Store award rings to do diabolical horseshit with later.
player->nextringaward = ringrate;
@ -10309,6 +10377,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->powerupVFXTimer > 0)
{
player->powerupVFXTimer--;
if (player->powerupVFXTimer == 0)
player->mo->flags &= ~MF_NOCLIPTHING;
}
if (player->dotrickfx && !player->mo->hitlag)
@ -13883,6 +13953,106 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
if ((player->cmd.buttons & BT_BAIL) && (player->cmd.buttons & BT_RESPAWNMASK) != BT_RESPAWNMASK && ((player->itemtype && player->itemamount) || (player->rings > 0) || player->superring > 0 || player->pickuprings > 0 || player->itemRoulette.active))
{
boolean grounded = P_IsObjectOnGround(player->mo);
onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground
if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle ..
{
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE);
S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound
S_StartSound(bail, sfx_kc4e);
P_SetTarget(&bail->target, player->mo);
bail->renderflags |= RF_FULLBRIGHT; // set fullbright here, were gonna animate frames in the thinker and it saves us from setting FF_FULLBRIGHT every frame
}
}
else
{
player->bailcharge = 0;
}
if ((!P_PlayerInPain(player) && player->bailcharge >= 5) || player->bailcharge >= BAIL_MAXCHARGE)
{
player->bailcharge = 0;
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAIL);
P_SetTarget(&bail->target, player->mo);
UINT32 debtrings = 20;
if (player->rings < 0)
{
debtrings -= player->rings;
player->rings = 0;
}
UINT32 totalrings = player->rings + player->superring + player->pickuprings;
if (BAIL_CREDIT_DEBTRINGS)
totalrings += debtrings;
totalrings = max(totalrings, 0);
UINT32 bailboost = FixedInt(FixedMul(totalrings*FRACUNIT, BAIL_BOOST));
UINT32 baildrop = FixedInt(FixedMul((totalrings)*FRACUNIT, BAIL_DROP));
if (player->itemRoulette.active)
{
player->itemRoulette.active = false;
}
K_PopPlayerShield(player);
K_DeleteHnextList(player);
K_DropItems(player);
player->itemamount = 0;
player->itemtype = 0;
/*
if (player->itemamount)
{
K_DropPaperItem(player, player->itemtype, player->itemamount);
player->itemtype = player->itemamount = 0;
}
*/
player->rings = -20;
player->superring = 0;
player->pickuprings = 0;
player->ringboxaward = 0;
player->ringboxdelay = 0;
player->superringdisplay = 0;
player->superringalert = 0;
player->superringpeak = 0;
player->counterdash += TICRATE/8;
player->ringboost += bailboost * (3+K_GetKartRingPower(player, true));
player->baildrop = baildrop * BAIL_DROPFREQUENCY + 1;
K_AddHitLag(player->mo, TICRATE/4, false);
player->bailquake = true; // set for a one time quake effect as soon as hitlag ends
if (P_PlayerInPain(player))
{
player->spinouttimer = 0;
player->spinouttype = 0;
player->tumbleBounces = 0;
player->pflags &= ~PF_TUMBLELASTBOUNCE;
player->mo->rollangle = 0;
P_ResetPitchRoll(player->mo);
}
INT32 fls = K_GetEffectiveFollowerSkin(player);
if (player->follower && fls >= 0 && fls < numfollowers)
{
const follower_t *fl = &followers[fls];
S_StartSound(NULL, fl->hornsound);
}
if (player->amps > 0)
K_DefensiveOverdrive(player);
S_StartSound(player->mo, sfx_kc33);
}
if (player && player->mo && K_PlayerCanUseItem(player))
{
// First, the really specific, finicky items that function without the item being directly in your item slot.
@ -14551,7 +14721,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
S_StartSound(player->mo, sfx_s3k75);
player->bubbleblowup++;
player->bubblecool = player->bubbleblowup*4;
player->bubblecool = player->bubbleblowup * (gametyperules & GTR_BUMPERS ? 6 : 4);
if (player->bubbleblowup > bubbletime*2)
{
@ -15960,111 +16130,115 @@ boolean K_PlayerCanUseItem(player_t *player)
return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime);
}
fixed_t K_GetGradingFactorAdjustment(player_t *player)
// ===
// THE EXP ZONE
// ===
static boolean K_IsValidOpponent(player_t *me, player_t *them)
{
fixed_t power = EXP_POWER; // adjust to change overall xp volatility
const fixed_t stablerate = EXP_STABLERATE; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain
fixed_t result = 0;
UINT8 i = (them - players);
if (!playeringame[i] || players[i].spectator)
return false;
if (me == them)
return false;
if (G_SameTeam(me, them))
return false;
return true;
}
static UINT8 K_Opponents(player_t *player)
{
UINT8 opponents = 0; // players we are competing against
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (K_IsValidOpponent(player, &players[i]))
opponents++;
}
return opponents;
}
static fixed_t K_GradingFactorPower(player_t *player)
{
fixed_t power = EXP_POWER; // adjust to change overall exp volatility
UINT8 opponents = K_Opponents(player);
if (g_teamplay)
power = 3 * power / 4;
INT32 live_players = 0; // players we are competing against
if (opponents < 8)
power += (8 - opponents) * power/4;
return power;
}
static fixed_t K_GradingFactorGainPerWin(player_t *player)
{
return K_GradingFactorPower(player);
}
static fixed_t K_GradingFactorDrainPerCheckpoint(player_t *player)
{
// EXP_STABLERATE: How low do you have to place before losing gradingfactor? 4*FRACUNIT/10 = top 40% of race gains, 60% loses.
UINT8 opponents = K_Opponents(player);
fixed_t power = K_GradingFactorPower(player);
return FixedMul(power, FixedMul(opponents*FRACUNIT, FRACUNIT - EXP_STABLERATE));
}
fixed_t K_GetGradingFactorAdjustment(player_t *player)
{
fixed_t result = 0;
// Increase gradingfactor for each player you're beating...
for (INT32 i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || player == players+i)
if (!K_IsValidOpponent(player, &players[i]))
continue;
if (G_SameTeam(player, &players[i]) == true)
{
// You don't win/lose against your teammates.
continue;
}
live_players++;
}
if (live_players < 8)
{
power += (8 - live_players) * 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 (G_SameTeam(player, &players[i]) == true)
{
// You don't win/lose against your teammates.
continue;
}
if (player->position < players[i].position)
result += power;
result += K_GradingFactorGainPerWin(player);
}
// ...then take all of the XP you could possibly have earned,
// ...then take all of the gradingfactor 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(power, FixedMul(live_players*FRACUNIT, FRACUNIT - stablerate));
// the stable threshold, this results in you losing gradingfactor
result -= K_GradingFactorDrainPerCheckpoint(player);
return result;
}
fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max)
fixed_t K_GetGradingFactorMinMax(player_t *player, boolean max)
{
// Create a dummy player structure for the theoretical last-place player
player_t dummy_player;
memset(&dummy_player, 0, sizeof(player_t));
dummy_player.gradingfactor = FRACUNIT; // Start at 1.0
fixed_t factor = FRACUNIT; // Starting gradingfactor
UINT8 opponents = K_Opponents(player);
UINT8 winning = (max) ? opponents : 0;
if (G_GametypeHasTeams())
for (UINT8 i = 0; i < player->gradingpointnum; i++) // For each gradingpoint you've reached...
{
const UINT8 orange_count = G_CountTeam(TEAM_ORANGE);
const UINT8 blue_count = G_CountTeam(TEAM_BLUE);
if (orange_count <= blue_count)
{
dummy_player.team = TEAM_ORANGE;
}
else
{
dummy_player.team = TEAM_BLUE;
}
dummy_player.position = max ? 0 : D_NumPlayersInRace() + 1; // Ensures that all enemy players are counted, and our dummy won't overlap
for (UINT8 j = 0; j < winning; j++)
factor += K_GradingFactorGainPerWin(player); // If max, increase gradingfactor for each player you could have been beating.
factor -= K_GradingFactorDrainPerCheckpoint(player); // Then, drain like usual.
}
else
{
dummy_player.position = max ? 1 : D_NumPlayersInRace();
}
// Apply the adjustment for each grading point
for (UINT32 i = 0; i < gradingpointnum; i++)
{
dummy_player.gradingfactor += K_GetGradingFactorAdjustment(&dummy_player);
}
return dummy_player.gradingfactor;
return factor;
}
UINT16 K_GetEXP(player_t *player)
{
UINT32 numgradingpoints = K_GetNumGradingPoints();
UINT16 targetminexp = (MINEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a last place player should be at this stage of the race
UINT16 targetexp = (MAXEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a 1.0 factor should be at this stage of the race
fixed_t factormin = K_GetGradingFactorMinMax(player->gradingpointnum, false);
fixed_t factormax = K_GetGradingFactorMinMax(player->gradingpointnum, true);
fixed_t clampedfactor = max(factormin, min(factormax, player->gradingfactor));
fixed_t range = factormax - factormin;
fixed_t normalizedfactor = FixedDiv(clampedfactor - factormin, range);
fixed_t easedexp = Easing_Linear(normalizedfactor, targetminexp, targetexp);
// fixed_t easedexp = Easing_Linear(normalizedfactor, MINEXP*FRACUNIT, MAXEXP*FRACUNIT);
UINT16 exp = easedexp;
// CONS_Printf("Player %s numgradingpoints=%d targetminexp=%d targetexp=%d factormin=%.2f factormax=%.2f clampedfactor=%.2f normalizedfactor=%.2f easedexp=%d\n",
// player_names[player - players], numgradingpoints, targetminexp, targetexp, FIXED_TO_FLOAT(factormin), FIXED_TO_FLOAT(factormax),
// FIXED_TO_FLOAT(clampedfactor), FIXED_TO_FLOAT(normalizedfactor), easedexp);
// UINT16 exp = (player->gradingfactor*100)>>FRACBITS;
fixed_t targetminexp = (EXP_MIN*player->gradingpointnum<<FRACBITS) / max(1,numgradingpoints); // about what a last place player should be at this stage of the race
fixed_t targetmaxexp = (EXP_MAX*player->gradingpointnum<<FRACBITS) / max(1,numgradingpoints); // about what a 1.0 factor should be at this stage of the race
fixed_t factormin = K_GetGradingFactorMinMax(player, false);
fixed_t factormax = K_GetGradingFactorMinMax(player, true);
UINT16 exp = FixedRescale(player->gradingfactor, factormin, factormax, Easing_Linear, targetminexp, targetmaxexp)>>FRACBITS;
// CONS_Printf("Player %s numgradingpoints=%d gradingpoint=%d targetminexp=%d targetmaxexp=%d factor=%.2f factormin=%.2f factormax=%.2f exp=%d\n",
// player_names[player - players], numgradingpoints, player->gradingpointnum, targetminexp, targetmaxexp, FIXED_TO_FLOAT(player->gradingfactor), FIXED_TO_FLOAT(factormin), FIXED_TO_FLOAT(factormax), exp);
return exp;
}
@ -16076,6 +16250,10 @@ UINT32 K_GetNumGradingPoints(void)
return numlaps * (1 + Obj_GetCheckpointCount());
}
// ===
// END EXP ZONE
// ===
void K_BotHitPenalty(player_t *player)
{
if (K_PlayerUsesBotMovement(player))
@ -16301,4 +16479,70 @@ fixed_t K_TeamComebackMultiplier(player_t *player)
return multiplier;
}
void K_ApplyStun(player_t *player, mobj_t *inflictor, mobj_t *source, ATTRUNUSED INT32 damage, ATTRUNUSED UINT8 damagetype)
{
#define BASE_STUN_TICS_MIN (4 * TICRATE)
#define BASE_STUN_TICS_MAX (10 * TICRATE)
#define MAX_STUN_REDUCTION (FRACUNIT/2)
#define STUN_REDUCTION_DISTANCE (20000)
INT32 stunTics = 0;
UINT8 numPlayers = 0;
UINT8 i;
// calculate the number of players playing
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator)
{
numPlayers++;
}
}
// calculate base stun tics
stunTics = Easing_Linear((player->kartweight - 1) * FRACUNIT / 8, BASE_STUN_TICS_MAX, BASE_STUN_TICS_MIN);
// reduce stun in games with more than 8 players
if (numPlayers > 8)
{
stunTics -= 6 * (numPlayers - 8);
}
// 1/3 stun values in battle
if (gametyperules & GTR_SPHERES)
{
stunTics /= 3;
}
if (source && source->player)
{
// hits scored by players apply full stun
;
}
else if (inflictor && (P_IsKartItem(inflictor->type) || P_IsKartFieldItem(inflictor->type)))
{
// items not thrown by a player apply half stun
stunTics /= 2;
}
else
{
// all other hazards apply 1/4 stun
stunTics /= 4;
}
UINT32 dist = K_GetItemRouletteDistance(player, D_NumPlayersInRace());
if (dist > STUN_REDUCTION_DISTANCE)
dist = STUN_REDUCTION_DISTANCE;
fixed_t distfactor = FixedDiv(dist, STUN_REDUCTION_DISTANCE); // 0-1 as you approach STUN_REDUCTION_DISTANCE
fixed_t stunfactor = Easing_Linear(distfactor, FRACUNIT, MAX_STUN_REDUCTION);
stunTics = FixedMul(stunTics*FRACUNIT, stunfactor)/FRACUNIT;
player->stunned = max(stunTics, 0);
#undef BASE_STUN_TICS_MIN
#undef BASE_STUN_TICS_MAX
#undef MAX_STUN_REDUCTION
#undef STUN_REDUCTION_DISTANCE
}
//}

View file

@ -44,6 +44,13 @@ Make sure this matches the actual number of states
#define INSTAWHIP_TETHERBLOCK (TICRATE*4)
#define PUNISHWINDOW (7*TICRATE/10)
#define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic
#define BAIL_DROP (FRACUNIT)
#define BAIL_BOOST (FRACUNIT)
#define BAIL_CREDIT_DEBTRINGS (true)
#define BAIL_DROPFREQUENCY (2)
#define BAILSTUN (TICRATE*10)
#define MAXCOMBOTHRUST (mapobjectscale*20)
#define MAXCOMBOFLOAT (mapobjectscale*10)
#define MINCOMBOTHRUST (mapobjectscale*2)
@ -111,6 +118,8 @@ Make sure this matches the actual number of states
angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed);
void K_PopBubbleShield(player_t *player);
boolean K_IsDuelItem(mobjtype_t type);
boolean K_DuelItemAlwaysSpawns(mapthing_t *mt);
boolean K_InRaceDuel(void);
@ -325,7 +334,7 @@ boolean K_ThunderDome(void);
boolean K_PlayerCanUseItem(player_t *player);
fixed_t K_GetGradingFactorAdjustment(player_t *player);
fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max);
fixed_t K_GetGradingFactorMinMax(player_t *player, boolean max);
UINT16 K_GetEXP(player_t *player);
UINT32 K_GetNumGradingPoints(void);
@ -340,6 +349,8 @@ boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2, boolean allowHostile);
fixed_t K_TeamComebackMultiplier(player_t *player);
void K_ApplyStun(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -465,8 +465,8 @@ boolean M_Responder(event_t *ev)
// Special mid-game input behaviours
if (Playing() && !demo.playback)
{
// Quick Retry (Y in modeattacking)
if (modeattacking && G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true)
// Quick Retry (Z in modeattacking)
if (modeattacking && G_PlayerInputDown(0, gc_vote, splitscreen + 1) == true)
{
M_TryAgain(0);
return true;

View file

@ -153,6 +153,10 @@ void Obj_ChargeFallThink(mobj_t *charge);
void Obj_ChargeReleaseThink(mobj_t *release);
void Obj_ChargeExtraThink(mobj_t *extra);
/* Bail VFX */
void Obj_BailThink(mobj_t *aura);
void Obj_BailChargeThink(mobj_t *aura);
/* Ring Shooter */
boolean Obj_RingShooterThinker(mobj_t *mo);
boolean Obj_PlayerRingShooterFreeze(const player_t *player);
@ -440,6 +444,7 @@ void Obj_DestroyedKartParticleLanding(mobj_t *part);
void Obj_SpawnFlybotsForPlayer(player_t *player);
void Obj_FlybotThink(mobj_t *flybot);
void Obj_FlybotDeath(mobj_t *flybot);
void Obj_FlybotRemoved(mobj_t *flybot);
/* Pulley */
void Obj_PulleyThink(mobj_t *root);

View file

@ -139,7 +139,7 @@ void podiumData_s::Init(void)
constexpr INT32 numRaces = 5;
for (INT32 i = 0; i < rank.numPlayers; i++)
{
rank.totalPoints += numRaces * K_CalculateGPRankPoints(MAXEXP, i+1, rank.totalPlayers);
rank.totalPoints += numRaces * K_CalculateGPRankPoints(EXP_MAX, i+1, rank.totalPlayers);
}
rank.totalRings = numRaces * rank.numPlayers * 20;
@ -180,7 +180,7 @@ void podiumData_s::Init(void)
}
default:
{
lvl->totalExp = TARGETEXP;
lvl->totalExp = EXP_TARGET;
texp += lvl->totalExp * rank.numPlayers;
break;
}
@ -203,7 +203,7 @@ void podiumData_s::Init(void)
dta->rings = M_RandomRange(0, 20);
rgs += dta->rings;
dta->exp = M_RandomRange(MINEXP, MAXEXP);
dta->exp = M_RandomRange(EXP_MIN, EXP_MAX);
pexp += dta->exp;
}
@ -727,8 +727,8 @@ void podiumData_s::Draw(void)
// Colorize the crystal, just like we do for hud
skincolornum_t overlaycolor = SKINCOLOR_MUSTARD;
fixed_t stablerateinverse = FRACUNIT - EXP_STABLERATE;
INT16 exp_range = MAXEXP-MINEXP;
INT16 exp_offset = dta->exp-MINEXP;
INT16 exp_range = EXP_MAX-EXP_MIN;
INT16 exp_offset = dta->exp-EXP_MIN;
fixed_t factor = (exp_offset*FRACUNIT) / exp_range; // 0.0 to 1.0 in fixed
// amount of blue is how much factor is above EXP_STABLERATE, and amount of red is how much factor is below
// assume that EXP_STABLERATE is within 0.0 to 1.0 in fixed
@ -892,9 +892,9 @@ void podiumData_s::Draw(void)
.patch("K_STEXP");
// Colorize the crystal for the totals, just like we do for in race hud
fixed_t extraexpfactor = (MAXEXP*FRACUNIT) / TARGETEXP;
fixed_t extraexpfactor = (EXP_MAX*FRACUNIT) / EXP_TARGET;
INT16 totalExpMax = FixedMul(rank.totalExp*FRACUNIT, extraexpfactor) / FRACUNIT; // im just going to calculate it from target lol
INT16 totalExpMin = rank.numPlayers*MINEXP;
INT16 totalExpMin = rank.numPlayers*EXP_MIN;
skincolornum_t overlaycolor = SKINCOLOR_MUSTARD;
fixed_t stablerateinverse = FRACUNIT - EXP_STABLERATE;
INT16 exp_range = totalExpMax-totalExpMin;

View file

@ -65,9 +65,10 @@ void K_GivePowerUp(player_t* player, kartitems_t powerup, tic_t time)
}
S_StartSound(NULL, sfx_gsha7l);
player->flashing = 2*TICRATE;
player->flashing = 3*TICRATE;
player->mo->hitlag += BATTLE_POWERUP_VFX_TIME;
player->powerupVFXTimer = BATTLE_POWERUP_VFX_TIME;
player->mo->flags |= MF_NOCLIPTHING;
Obj_SpawnPowerUpSpinner(player->mo, powerup, BATTLE_POWERUP_VFX_TIME);
g_darkness.start = leveltime;

View file

@ -322,7 +322,7 @@ void gpRank_t::Init(void)
// (Should this account for all coop players?)
for (i = 0; i < numHumans; i++)
{
totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(MAXEXP, i+1, totalPlayers);
totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(EXP_MAX, i+1, totalPlayers);
}
totalRings = grandprixinfo.cup->numlevels * numHumans * 20;
@ -332,7 +332,7 @@ void gpRank_t::Init(void)
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL)
{
exp += TARGETEXP;
exp += EXP_TARGET;
}
}
@ -372,7 +372,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI
{
for (i = 0; i < numPlayers; i++)
{
deltaPoints += K_CalculateGPRankPoints(MAXEXP, i + 1, totalPlayers);
deltaPoints += K_CalculateGPRankPoints(EXP_MAX, i + 1, totalPlayers);
}
if (addedgt == GT_RACE)
totalPoints += deltaPoints;
@ -391,7 +391,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI
{
if (removedgt == GT_RACE)
{
deltaExp -= TARGETEXP;
deltaExp -= EXP_TARGET;
}
if ((gametypes[removedgt]->rules & GTR_SPHERES) == 0)
{
@ -408,7 +408,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI
{
if (addedgt == GT_RACE)
{
deltaExp += TARGETEXP;
deltaExp += EXP_TARGET;
}
if ((gametypes[addedgt]->rules & GTR_SPHERES) == 0)
{
@ -492,7 +492,7 @@ void gpRank_t::Update(void)
lvl->time = UINT32_MAX;
lvl->totalExp = TARGETEXP;
lvl->totalExp = EXP_TARGET;
lvl->totalPrisons = maptargets;
UINT8 i;
@ -548,13 +548,10 @@ void K_UpdateGPRank(gpRank_t *rankData)
rankData->Update();
}
/*--------------------------------------------------
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
See header file for description.
--------------------------------------------------*/
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
{
INT32 retGrade = GRADE_E;
{
extern consvar_t cv_debugrank;
@ -564,6 +561,8 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
}
}
fixed_t percent = K_CalculateGPPercent(rankData);
static const fixed_t gradePercents[GRADE_A] = {
7*FRACUNIT/20, // D: 35% or higher
10*FRACUNIT/20, // C: 50% or higher
@ -571,8 +570,31 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
17*FRACUNIT/20 // A: 85% or higher
};
INT32 retGrade = GRADE_E;
for (retGrade = GRADE_E; retGrade < GRADE_A; retGrade++)
{
if (percent < gradePercents[retGrade])
{
break;
}
}
if (rankData->specialWon == true)
{
// Winning the Special Stage gives you
// a free grade increase.
retGrade++;
}
return static_cast<gp_rank_e>(retGrade);
}
/*--------------------------------------------------
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
See header file for description.
--------------------------------------------------*/
fixed_t K_CalculateGPPercent(gpRank_t *rankData)
{
rankData->scorePosition = 0;
rankData->scoreGPPoints = 0;
rankData->scoreExp = 0;
@ -625,22 +647,8 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
rankData->scoreContinues;
const fixed_t percent = FixedDiv(rankData->scoreTotal, total);
for (retGrade = GRADE_E; retGrade < GRADE_A; retGrade++)
{
if (percent < gradePercents[retGrade])
{
break;
}
}
if (rankData->specialWon == true)
{
// Winning the Special Stage gives you
// a free grade increase.
retGrade++;
}
return static_cast<gp_rank_e>(retGrade);
return percent;
}
/*--------------------------------------------------

View file

@ -159,6 +159,7 @@ void K_UpdateGPRank(gpRank_t *rankData);
--------------------------------------------------*/
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData);
fixed_t K_CalculateGPPercent(gpRank_t *rankData);
/*--------------------------------------------------

View file

@ -347,7 +347,7 @@ void level_tally_t::Init(player_t *player)
if (player->exp)
{
exp = player->exp;
totalExp = TARGETEXP;
totalExp = EXP_TARGET;
}
}

View file

@ -262,8 +262,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->tumbleHeight);
else if (fastcmp(field,"stunned"))
lua_pushinteger(L, plr->stunned);
else if (fastcmp(field,"stunnedcombo"))
lua_pushinteger(L, plr->stunnedCombo);
else if (fastcmp(field,"flybot"))
LUA_PushUserdata(L, plr->flybot, META_MOBJ);
else if (fastcmp(field,"justdi"))
lua_pushinteger(L, plr->justDI);
else if (fastcmp(field,"flipdi"))
@ -284,6 +284,12 @@ static int player_get(lua_State *L)
lua_pushboolean(L, plr->progressivethrust);
else if (fastcmp(field,"ringvisualwarning"))
lua_pushboolean(L, plr->ringvisualwarning);
else if (fastcmp(field,"bailcharge"))
lua_pushinteger(L, plr->bailcharge);
else if (fastcmp(field,"baildrop"))
lua_pushinteger(L, plr->baildrop);
else if (fastcmp(field,"bailquake"))
lua_pushboolean(L, plr->bailquake);
else if (fastcmp(field,"dotrickfx"))
lua_pushboolean(L, plr->dotrickfx);
else if (fastcmp(field,"stingfx"))
@ -896,8 +902,13 @@ static int player_set(lua_State *L)
plr->tumbleHeight = luaL_checkinteger(L, 3);
else if (fastcmp(field,"stunned"))
plr->stunned = luaL_checkinteger(L, 3);
else if (fastcmp(field,"stunnedcombo"))
plr->stunnedCombo = luaL_checkinteger(L, 3);
else if (fastcmp(field,"flybot"))
{
mobj_t *mo = NULL;
if (!lua_isnil(L, 3))
mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
P_SetTarget(&plr->flybot, mo);
}
else if (fastcmp(field,"justdi"))
plr->justDI = luaL_checkinteger(L, 3);
else if (fastcmp(field,"flipdi"))
@ -910,6 +921,12 @@ static int player_set(lua_State *L)
plr->progressivethrust = luaL_checkboolean(L, 3);
else if (fastcmp(field,"ringvisualwarning"))
plr->ringvisualwarning = luaL_checkboolean(L, 3);
else if (fastcmp(field,"bailcharge"))
plr->bailcharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"baildrop"))
plr->baildrop = luaL_checkinteger(L, 3);
else if (fastcmp(field,"bailquake"))
plr->bailquake = luaL_checkboolean(L, 3);
else if (fastcmp(field,"analoginput"))
plr->analoginput = luaL_checkboolean(L, 3);
else if (fastcmp(field,"transfer"))

View file

@ -430,3 +430,93 @@ const char *easing_funcnames[EASE_MAX] =
#undef COMMA
#undef EASINGFUNC
// ==================
// FEATURE RESCALING
// ==================
/*--------------------------------------------------
fixed_t Rescale(fixed_t value, fixed_t inmin, fixed_t inmax, easingfunc_t easing_func, fixed_t outmin, fixed_t outmax)
Rescales a feature value from [min, max] to [start, end] using
a custom easing function pointer.
Input Arguments:-
value - The input value to rescale
inmin - Minimum value of the input range
inmax - Maximum value of the input range
easing_func - Pointer to the easing function to use
outmin - Start value of the output range
outmax - End value of the output range
Return:-
The rescaled value using the specified easing function.
--------------------------------------------------*/
fixed_t FixedRescale(fixed_t value, fixed_t inmin, fixed_t inmax, easingfunc_t easing_func, fixed_t outmin, fixed_t outmax)
{
// Handle edge case where min == max
if (inmin == inmax)
return outmin;
// Clamp the input value to the range
max(inmin, min(inmax, value));
// Normalize the value to [0, FRACUNIT] range
fixed_t t = FixedDiv(value - inmin, inmax - inmin);
// Apply the easing function if provided
if (easing_func != NULL)
{
return easing_func(t, outmin, outmax);
}
// Fallback to linear if no function provided
return Easing_Linear(t, outmin, outmax);
}
/*--------------------------------------------------
INT16 IntRescale(INT16 value, INT16 inmin, INT16 inmax, easingfunc_t easing_func, INT16 outmin, INT16 outmax)
Rescales a feature value from [min, max] to [start, end] using
a custom easing function pointer.
Can only take in up to INT16 because it uses fixed_t internally
Input Arguments:-
value - The input value to rescale
inmin - Minimum value of the input range
inmax - Maximum value of the input range
easing_func - Pointer to the easing function to use
outmin - Start value of the output range
outmax - End value of the output range
Return:-
The rescaled value using the specified easing function.
--------------------------------------------------*/
INT16 IntRescale(INT16 value, INT16 inmin, INT16 inmax, easingfunc_t easing_func, INT16 outmin, INT16 outmax)
{
// Handle edge case where min == max
if (inmin == inmax)
return outmin;
// Clamp the input value to the range
max(inmin, min(inmax, value));
// Conversion shit
value = value<<FRACBITS;
inmin = inmin<<FRACBITS;
inmax = inmax<<FRACBITS;
outmin = outmin<<FRACBITS;
outmax = outmax<<FRACBITS;
// Normalize the value to [0, FRACUNIT] range
fixed_t t = FixedDiv(value - inmin, inmax - inmin);
// Apply the easing function if provided
if (easing_func != NULL)
{
return easing_func(t, outmin, outmax)>>FRACBITS;
}
// Fallback to linear if no function provided
return Easing_Linear(t, outmin, outmax)>>FRACBITS;
}

View file

@ -105,6 +105,9 @@ EASINGFUNC(InOutBackParameterized) /* Easing_InOutBackParameterized */
#undef EASINGFUNC
fixed_t FixedRescale(fixed_t value, fixed_t inmin, fixed_t inmax, easingfunc_t easing_func, fixed_t outmin, fixed_t outmax);
INT16 IntRescale(INT16 value, INT16 inmin, INT16 inmax, easingfunc_t easing_func, INT16 outmin, INT16 outmax);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -39,10 +39,10 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_CONTROL, "Brake / Go back", "Brake / Go back",
"TLB_X", {.routine = M_ProfileSetControl}, gc_x, 0},
{IT_CONTROL, "Respawn", "Respawn",
{IT_CONTROL, "Ring Bail", "Ring Bail / Burst",
"TLB_Y", {.routine = M_ProfileSetControl}, gc_y, 0},
{IT_CONTROL, "Action", "Multiplayer quick-chat / quick-vote",
{IT_CONTROL, "Action", "Quick-vote / Quick-chat / Time Attack Quick Restart",
"TLB_Z", {.routine = M_ProfileSetControl}, gc_z, 0},
{IT_CONTROL, "Use Item", "Use item",

View file

@ -66,6 +66,7 @@ target_sources(SRB2SDL2 PRIVATE
flame-shield.cpp
stone-shoe.cpp
exp.c
bail.c
)
add_subdirectory(versus)

80
src/objects/bail.c Normal file
View file

@ -0,0 +1,80 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by AJ "Tyron" Martinez.
// Copyright (C) 2025 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file bail.c
/// \brief Charge VFX code.
#include "../doomdef.h"
#include "../info.h"
#include "../k_objects.h"
#include "../info.h"
#include "../k_kart.h"
#include "../p_local.h"
#include "../s_sound.h"
void Obj_BailThink (mobj_t *aura)
{
if (P_MobjWasRemoved(aura->target)
|| aura->target->health == 0
|| aura->target->destscale <= 1 // sealed star fall out
|| !aura->target->player
|| P_PlayerInPain(aura->target->player)) // if you got hit when starting to bail, cancel the VFX
{
P_RemoveMobj(aura);
}
else
{
mobj_t *mo = aura->target;
aura->flags &= ~(MF_NOCLIPTHING);
P_MoveOrigin(aura, mo->x, mo->y, mo->z + mo->height/2);
aura->flags |= MF_NOCLIPTHING;
fixed_t baseScale = 12*mo->scale/10;
P_SetScale(aura, baseScale);
// make target player invisible during the effect, like the retro games
if (aura->state == &states[S_BAIL])
mo->renderflags |= RF_DONTDRAW;
}
}
void Obj_BailChargeThink (mobj_t *aura)
{
if (P_MobjWasRemoved(aura->target)
|| aura->target->health == 0
|| aura->target->destscale <= 1 // sealed star fall out
|| !aura->target->player
|| !aura->target->player->bailcharge)
{
P_RemoveMobj(aura);
}
else
{
mobj_t *mo = aura->target;
player_t *player = mo->player;
// Follow player
aura->flags &= ~(MF_NOCLIPTHING);
P_MoveOrigin(aura, mo->x, mo->y, mo->z + mo->height/2);
aura->flags |= MF_NOCLIPTHING;
// aura->color = mo->color;
aura->frame = ((player->bailcharge-1)/2); // By syncing the frame with the charge timer here
fixed_t baseScale = 13*mo->scale/10;
P_SetScale(aura, baseScale);
mobj_t *ghost = P_SpawnGhostMobj(aura);
ghost->renderflags = (ghost->renderflags & ~RF_TRANSMASK)|RF_ADD;
ghost->fuse = 3;
}
}

View file

@ -43,6 +43,7 @@ void Obj_SpawnFlybotsForPlayer(player_t *player)
{
UINT8 i;
mobj_t *mo = player->mo;
mobj_t *hprev = mo;
fixed_t radius = mo->radius;
for (i = 0; i < FLYBOT_QUANTITY; i++)
@ -61,6 +62,17 @@ void Obj_SpawnFlybotsForPlayer(player_t *player)
flybot->movedir = flybot->old_angle = flybot->angle = angle + ANGLE_90;
flybot->old_z = SetFlybotZ(flybot);
flybot->renderflags |= (i * RF_DONTDRAW);
if (hprev->player)
{
P_SetTarget(&player->flybot, flybot);
}
else
{
P_SetTarget(&hprev->hnext, flybot);
P_SetTarget(&flybot->hprev, hprev);
}
hprev = flybot;
}
}
@ -80,11 +92,18 @@ void Obj_FlybotThink(mobj_t *flybot)
if (mo->player)
{
if (((stunned = mo->player->stunned & 0x7FFF) == 0) || (mo->player->playerstate == PST_DEAD))
if (((stunned = mo->player->stunned) == 0) || (mo->player->playerstate == PST_DEAD))
{
P_KillMobj(flybot, NULL, NULL, 0);
return;
}
// If player is spindashing, spin faster to hint that stun is going down faster
else if (mo->player->spindash)
{
speed *= 2;
flybot->movedir += FLYBOT_BOB_FREQUENCY*2;
}
}
flybot->frame = flybot->frame & ~FF_TRANSMASK;
@ -120,6 +139,11 @@ void Obj_FlybotDeath(mobj_t *flybot)
if (!P_MobjWasRemoved(mo))
{
if (mo->player && (flybot == mo->player->flybot))
{
P_SetTarget(&mo->player->flybot, NULL);
}
mom.x = mo->momx;
mom.y = mo->momy;
mom.z = mo->momz;
@ -140,3 +164,12 @@ void Obj_FlybotDeath(mobj_t *flybot)
angle += ANGLE_90;
}
}
void Obj_FlybotRemoved(mobj_t *flybot)
{
mobj_t *mo = flybot->target;
if (!P_MobjWasRemoved(mo) && mo->player && (flybot == mo->player->flybot))
{
P_SetTarget(&mo->player->flybot, NULL);
}
}

View file

@ -668,7 +668,7 @@ boolean Obj_PlayerRingShooterFreeze(const player_t *player)
const mobj_t *base = player->ringShooter;
if (AllowRingShooter(player) == true
&& (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN
&& (player->cmd.buttons & BT_RESPAWNMASK) == BT_RESPAWNMASK
&& P_MobjWasRemoved(base) == false)
{
return (rs_base_canceled(base) == 0);
@ -682,7 +682,7 @@ void Obj_RingShooterInput(player_t *player)
mobj_t *const base = player->ringShooter;
if (AllowRingShooter(player) == true
&& (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN)
&& (player->cmd.buttons & BT_RESPAWNMASK) == BT_RESPAWNMASK)
{
// "Freeze" final-failsafe timer if we're eligible to ringshooter, but don't reset it.
if (player->finalfailsafe)

View file

@ -3503,7 +3503,7 @@ void A_AttractChase(mobj_t *actor)
if (actor->extravalue1 && actor->type != MT_EMERALD) // SRB2Kart
{
if (!actor->target || P_MobjWasRemoved(actor->target) || !actor->target->player)
if (!actor->target || P_MobjWasRemoved(actor->target) || !actor->target->player || actor->target->player->baildrop || actor->target->player->bailcharge)
{
P_RemoveMobj(actor);
return;
@ -12206,6 +12206,8 @@ void A_BallhogExplode(mobj_t *actor)
mo2->destscale = mo2->scale;
P_SetTarget(&mo2->target, actor->target);
S_StartSound(mo2, actor->info->deathsound);
actor->fuse = 1;
return;
}

View file

@ -723,6 +723,17 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (special->extravalue1)
return;
// No picking up rings while SPB is targetting you
if (player->pflags & PF_RINGLOCK)
return;
// Prepping instawhip? Don't ruin it by collecting rings
if (player->instaWhipCharge)
return;
if (player->baildrop || player->bailcharge)
return;
// Don't immediately pick up spilled rings
if (special->threshold > 0 || P_PlayerInPain(player) || player->spindash) // player->spindash: Otherwise, players can pick up rings that are thrown out of them from invinc spindash penalty
return;
@ -3041,7 +3052,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
UINT8 type = (damagetype & DMG_TYPEMASK);
const boolean hardhit = (type == DMG_EXPLODE || type == DMG_KARMA || type == DMG_TUMBLE); // This damage type can do evil stuff like ALWAYS combo
INT16 ringburst = 5;
UINT16 stunTics = 0;
// Check if the player is allowed to be damaged!
// If not, then spawn the instashield effect instead.
@ -3094,6 +3104,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
invincible = false;
}
if (player->pflags2 && PF2_ALWAYSDAMAGED)
{
invincible = false;
clash = false;
}
// TODO: doing this from P_DamageMobj limits punting to objects that damage the player.
// And it may be kind of yucky.
// But this is easier than accounting for every condition in PIT_CheckThing!
@ -3181,6 +3197,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
}
if (inflictor && !P_MobjWasRemoved(inflictor) && inflictor->momx == 0 && inflictor->momy == 0 && inflictor->momz == 0)
{
// Probably a map hazard.
allowcombo = false;
}
if (allowcombo == false && (target->eflags & MFE_PAUSED))
{
return false;
@ -3395,18 +3417,22 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
K_PopPlayerShield(player);
}
if (!(gametyperules & GTR_SPHERES) && player->tripwireLeniency)
boolean downgraded = false;
if (!(gametyperules & GTR_SPHERES) && player->tripwireLeniency && !P_PlayerInPain(player))
{
switch (type)
{
case DMG_EXPLODE:
type = DMG_TUMBLE;
downgraded = true;
break;
case DMG_TUMBLE:
softenTumble = true;
break;
case DMG_NORMAL:
case DMG_WIPEOUT:
downgraded = true;
type = DMG_STUMBLE;
player->ringburst += 5; // THERE IS SIMPLY NO HOPE AT THIS POINT
K_PopPlayerShield(player);
@ -3453,7 +3479,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
ringburst = 0;
}
if (type != DMG_STUMBLE && type != DMG_WHUMBLE)
if ((type != DMG_STUMBLE && type != DMG_WHUMBLE) || (type == DMG_STUMBLE && downgraded))
{
if (type != DMG_STING)
player->flashing = K_GetKartFlashing(player);
@ -3488,26 +3514,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
// Apply stun!
// Feel free to move these calculations higher up if different damage sources should apply variable stun in future
#define MIN_STUNTICS (4 * TICRATE)
#define MAX_STUNTICS (10 * TICRATE)
stunTics = Easing_Linear((player->kartweight - 1) * FRACUNIT / 8, MAX_STUNTICS, MIN_STUNTICS);
stunTics >>= player->stunnedCombo; // consecutive hits add half as much stun as the previous hit
// 1/3 base stun values in battle
if (gametyperules & GTR_SPHERES)
if (type != DMG_STING)
{
stunTics /= 3;
K_ApplyStun(player, inflictor, source, damage, damagetype);
}
if (player->stunnedCombo < UINT8_MAX)
{
player->stunnedCombo++;
}
player->stunned = (player->stunned & 0x8000) | min(0x7FFF, (player->stunned & 0x7FFF) + stunTics);
#undef MIN_STUNTICS
#undef MAX_STUNTICS
K_DefensiveOverdrive(target->player);
}
}
@ -3612,7 +3623,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
#define RING_LAYER_SIDE_SIZE (3)
#define RING_LAYER_SIZE (RING_LAYER_SIDE_SIZE * 2)
static void P_FlingBurst
void P_FlingBurst
( player_t *player,
angle_t fa,
mobjtype_t objType,

View file

@ -549,6 +549,7 @@ void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End);
void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source);
boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype);
void P_FlingBurst(player_t *player, angle_t fa, mobjtype_t objType, tic_t objFuse, fixed_t objScale, INT32 i);
void P_PlayerRingBurst(player_t *player, INT32 num_rings); /// \todo better fit in p_user.c
void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck);

View file

@ -8923,6 +8923,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
Obj_BlockBodyThink(mobj);
break;
}
case MT_BAIL:
{
Obj_BailThink(mobj);
break;
}
case MT_BAILCHARGE:
{
Obj_BailChargeThink(mobj);
break;
}
case MT_AMPRING:
{
Obj_AmpRingThink(mobj);
@ -11855,6 +11865,11 @@ void P_RemoveMobj(mobj_t *mobj)
Obj_UnlinkRocks(mobj);
break;
}
case MT_FLYBOT767:
{
Obj_FlybotRemoved(mobj);
break;
}
default:
{
break;

View file

@ -72,8 +72,7 @@ static savebuffer_t *current_savebuffer;
#define ARCHIVEBLOCK_WAYPOINTS 0x7F46498F
#define ARCHIVEBLOCK_RNG 0x7FAAB5BD
// Note: This cannot be bigger
// than an UINT16 (for now)
// Note: This cannot have more than 32 entries
typedef enum
{
AWAYVIEW = 0x0001,
@ -93,6 +92,7 @@ typedef enum
BARRIER = 0x4000,
BALLHOGRETICULE = 0x8000,
STONESHOE = 0x10000,
FLYBOT = 0x20000,
} player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save)
@ -368,6 +368,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].stoneShoe)
flags |= STONESHOE;
if (players[i].flybot)
flags |= FLYBOT;
WRITEUINT32(save->p, flags);
if (flags & SKYBOXVIEW)
@ -418,6 +421,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE)
WRITEUINT32(save->p, players[i].stoneShoe->mobjnum);
if (flags & FLYBOT)
WRITEUINT32(save->p, players[i].flybot->mobjnum);
WRITEUINT32(save->p, (UINT32)players[i].followitem);
WRITEUINT32(save->p, players[i].charflags);
@ -464,7 +470,6 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].tumbleBounces);
WRITEUINT16(save->p, players[i].tumbleHeight);
WRITEUINT16(save->p, players[i].stunned);
WRITEUINT8(save->p, players[i].stunnedCombo);
WRITEUINT8(save->p, players[i].justDI);
WRITEUINT8(save->p, players[i].flipDI);
@ -667,6 +672,10 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT16(save->p, players[i].progressivethrust);
WRITEUINT8(save->p, players[i].ringvisualwarning);
WRITEUINT32(save->p, players[i].bailcharge);
WRITEUINT32(save->p, players[i].baildrop);
WRITEUINT8(save->p, players[i].bailquake);
WRITEUINT8(save->p, players[i].analoginput);
WRITEUINT8(save->p, players[i].markedfordeath);
@ -756,7 +765,10 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].botvars.spindashconfirm);
WRITEUINT32(save->p, players[i].botvars.respawnconfirm);
WRITEUINT8(save->p, players[i].botvars.roulettePriority);
WRITEUINT32(save->p, players[i].botvars.rouletteTimeout);
WRITEINT32(save->p, players[i].botvars.rouletteTimeout);
WRITEUINT32(save->p, players[i].botvars.predictionError);
WRITEUINT32(save->p, players[i].botvars.recentDeflection);
WRITEUINT32(save->p, players[i].botvars.lastAngle);
// itemroulette_t
WRITEUINT8(save->p, players[i].itemRoulette.active);
@ -1070,6 +1082,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE)
players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & FLYBOT)
players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p);
players[i].followitem = (mobjtype_t)READUINT32(save->p);
//SetPlayerSkinByNum(i, players[i].skin);
@ -1117,7 +1132,6 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].tumbleBounces = READUINT8(save->p);
players[i].tumbleHeight = READUINT16(save->p);
players[i].stunned = READUINT16(save->p);
players[i].stunnedCombo = READUINT8(save->p);
players[i].justDI = READUINT8(save->p);
players[i].flipDI = (boolean)READUINT8(save->p);
@ -1319,6 +1333,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].progressivethrust = READUINT16(save->p);
players[i].ringvisualwarning = READUINT8(save->p);
players[i].bailcharge = READUINT32(save->p);
players[i].baildrop = READUINT32(save->p);
players[i].bailquake = READUINT8(save->p);
players[i].analoginput = READUINT8(save->p);
players[i].markedfordeath = READUINT8(save->p);
@ -1409,6 +1427,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].botvars.respawnconfirm = READUINT32(save->p);
players[i].botvars.roulettePriority = READUINT8(save->p);
players[i].botvars.rouletteTimeout = READUINT32(save->p);
players[i].botvars.predictionError = READUINT32(save->p);
players[i].botvars.recentDeflection = READUINT32(save->p);
players[i].botvars.lastAngle = READUINT32(save->p);
// itemroulette_t
players[i].itemRoulette.active = (boolean)READUINT8(save->p);
@ -6226,6 +6247,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].stoneShoe))
CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i);
}
if (players[i].flybot)
{
if (!RelinkMobj(&players[i].flybot))
CONS_Debug(DBG_GAMELOGIC, "flybot not found on player %d\n", i);
}
}
}

View file

@ -8698,7 +8698,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
if (G_IsModeAttackRetrying() && !demo.playback)
{
nowtime = lastwipetic;
while (G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true)
while (G_PlayerInputDown(0, gc_vote, splitscreen + 1) == true)
{
while (!((nowtime = I_GetTime()) - lastwipetic))
{

View file

@ -1206,11 +1206,15 @@ void P_ButteredSlope(mobj_t *mo)
// Let's get the gravity strength for the object...
thrust = FixedMul(thrust, abs(P_GetMobjGravity(mo)));
if (mo->friction != ORIG_FRICTION)
fixed_t basefriction = ORIG_FRICTION;
if (mo->player)
basefriction = K_PlayerBaseFriction(mo->player, ORIG_FRICTION);
if (mo->friction != basefriction && basefriction != 0)
{
// ... and its friction against the ground for good measure.
// (divided by original friction to keep behaviour for normal slopes the same)
thrust = FixedMul(thrust, FixedDiv(mo->friction, ORIG_FRICTION));
thrust = FixedMul(thrust, FixedDiv(mo->friction, basefriction));
// Sal: Also consider movefactor of players.
// We want ice to make slopes *really* funnel you in a specific direction.

View file

@ -9635,7 +9635,7 @@ void P_DoQuakeOffset(UINT8 view, mappoint_t *viewPos, mappoint_t *offset)
quake = quake->next;
}
// Add level-based effects.
// Add level-based effects
if (P_MobjWasRemoved(viewer->mo) == false
&& viewer->speed > viewer->mo->scale
&& P_IsObjectOnGround(viewer->mo) == true)
@ -9664,9 +9664,9 @@ void P_DoQuakeOffset(UINT8 view, mappoint_t *viewPos, mappoint_t *offset)
maxShake = FixedMul(mapheaderinfo[gamemap-1]->cameraHeight, mapobjectscale) * 3 / 4;
// For 2p SPLITSCREEN SPECIFICALLY:
// The view is pretty narrow, so move it back 1/4th of the way towards default camera height.
// The view is pretty narrow, so move it back 3/20 of the way towards default camera height.
else
maxShake = FixedMul((mapheaderinfo[gamemap-1]->cameraHeight*3 + cv_cam_height[view].value)/4, mapobjectscale) * 3 / 4;
maxShake = FixedMul((mapheaderinfo[gamemap-1]->cameraHeight*17 + cv_cam_height[view].value*3)/20, mapobjectscale) * 3 / 4;
}
if (battle)

View file

@ -3067,7 +3067,7 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num)
}
// if you hold Y, you will lock on to displayplayer. (The last player you were ""f12-ing"")
if (cam->freecam && cmd->buttons & BT_RESPAWN)
if (cam->freecam && cmd->buttons & BT_BAIL)
{
lastp = &players[displayplayers[0]]; // Fun fact, I was trying displayplayers[0]->mo as if it was Lua like an absolute idiot.
cam->angle = R_PointToAngle2(cam->x, cam->y, lastp->mo->x, lastp->mo->y);
@ -3369,7 +3369,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
if (P_CameraThinker(player, thiscam, resetcalled))
return true;
lookback = ( player->cmd.buttons & BT_LOOKBACK );
lookback = K_GetKartButtons(player) & BT_LOOKBACK;
camspeed = cv_cam_speed[num].value;
camstill = cv_cam_still[num].value || player->seasaw; // RR: seasaws lock the camera so that it isn't disorienting.
@ -3384,10 +3384,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
camheight = FixedMul(mapheaderinfo[gamemap-1]->cameraHeight, cameraScale);
// For 2p SPLITSCREEN SPECIFICALLY:
// The view is pretty narrow, so move it back 1/4th of the way towards default camera height.
// The view is pretty narrow, so move it back 3/20 of the way towards default camera height.
else {
// CONS_Printf( "Camera values: %f / %f / %f \n", FixedToFloat(mapheaderinfo[gamemap-1]->cameraHeight), FixedToFloat(cv_cam_height[num].value), FixedToFloat(cameraScale) );
camheight = FixedMul((mapheaderinfo[gamemap-1]->cameraHeight*3 + cv_cam_height[num].value)/4, cameraScale);
camheight = FixedMul((mapheaderinfo[gamemap-1]->cameraHeight*17 + cv_cam_height[num].value*3)/20, cameraScale);
}
}
@ -4564,9 +4564,7 @@ void P_PlayerThink(player_t *player)
// Strength counts up to diminish fade.
if (player->flashing && player->flashing < UINT16_MAX &&
(player->spectator || !P_PlayerInPain(player)) &&
// Battle: flashing tics do not decrease in the air
(!(gametyperules & GTR_BUMPERS) || P_IsObjectOnGround(player->mo)))
(player->spectator || !P_PlayerInPain(player)))
{
player->flashing--;
}

View file

@ -778,7 +778,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"kc30", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc31", false, 64, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc32", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc33", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc33", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away
{"kc34", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc35", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc36", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
@ -805,7 +805,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"kc4b", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc4c", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Pop-shot"},
{"kc4d", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Power up"},
{"kc4e", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc4e", false, 64, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away
{"kc4f", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc50", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"kc51", false, 64, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
@ -1432,18 +1432,18 @@ sfxinfo_t S_sfx[NUMSFX] =
{"gshaf", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb0", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb1", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb2", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb2", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away
{"gshb3", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb4", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb5", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb6", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb7", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb8", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb9", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshb9", false, 64, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x4away
{"gshba", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshbb", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshbc", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshbd", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshbd", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away
{"gshbe", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshbf", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gshc0a", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},

View file

@ -400,14 +400,14 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
if (roundqueue.position == roundqueue.size-1)
{
// On A rank pace? Then you get a chance for S rank!
gp_rank_e rankforline = K_CalculateGPGrade(&grandprixinfo.rank);
fixed_t rankforline = K_CalculateGPPercent(&grandprixinfo.rank);
data.showrank = (rankforline >= GRADE_A);
data.showrank = (rankforline >= SEALED_STAR_ENTRY);
data.linemeter =
(std::min(rankforline, GRADE_A)
(std::min(rankforline, SEALED_STAR_ENTRY)
* (2 * TICRATE)
) / GRADE_A;
) / SEALED_STAR_ENTRY;
// G_NextMap will float you to rank-restricted stages on Master wins.
// Fudge the rank display.