Merge remote-tracking branch 'origin/master' into pick-me-up

This commit is contained in:
Antonio Martinez 2025-05-16 22:26:35 -04:00
commit 1a2f3b0d3b
33 changed files with 462 additions and 72 deletions

View file

@ -974,6 +974,7 @@ consvar_t cv_dummymenuplayer = MenuDummy("dummymenuplayer", "P1").onchange(Dummy
consvar_t cv_dummyprofileautoroulette = MenuDummy("dummyprofileautoroulette", "Off").on_off();
consvar_t cv_dummyprofilefov = MenuDummy("dummyprofilefov", "100").min_max(70, 110);
consvar_t cv_dummyprofilelitesteer = MenuDummy("dummyprofilelitesteer", "Off").on_off();
consvar_t cv_dummyprofilestrictfastfall = MenuDummy("dummprofilestrictfastfall", "Off").on_off();
consvar_t cv_dummyprofiledescriptiveinput = Player("dummyprofiledescriptiveinput", "Modern").values(descriptiveinput_cons_t);
consvar_t cv_dummyprofileautoring = MenuDummy("dummyprofileautoring", "Off").on_off();
consvar_t cv_dummyprofilekickstart = MenuDummy("dummyprofilekickstart", "Off").on_off();
@ -1085,6 +1086,13 @@ consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS] = {
Player("litesteer4", "Off").on_off().onchange(weaponPrefChange4),
};
consvar_t cv_strictfastfall[MAXSPLITSCREENPLAYERS] = {
Player("strictfastfall", "Off").on_off().onchange(weaponPrefChange),
Player("strictfastfall2", "Off").on_off().onchange(weaponPrefChange2),
Player("strictfastfall3", "Off").on_off().onchange(weaponPrefChange3),
Player("strictfastfall4", "Off").on_off().onchange(weaponPrefChange4),
};
consvar_t cv_autoring[MAXSPLITSCREENPLAYERS] = {
Player("autoring", "Off").on_off().onchange(weaponPrefChange),
Player("autoring2", "Off").on_off().onchange(weaponPrefChange2),

View file

@ -123,6 +123,11 @@ static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the
// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled.
static tic_t joindelay = 0;
// Set when the server requests a signature with an IP mismatch.
// This is a common issue on Hamachi, Radmin, and other VPN software that does noncompliant IP remap horeseshit.
// When ths is set, provide only blank signatures. If this is set while joining, the server will degrade us to a GUEST.
static boolean forceGuest = false;
UINT16 pingmeasurecount = 1;
UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
@ -276,9 +281,26 @@ shouldsign_t ShouldSignChallenge(uint8_t *message)
if ((max(now, then) - min(now, then)) > 60*15)
return SIGN_BADTIME;
// ____ _____ ___ ____ _
// / ___|_ _/ _ \| _ \| |
// \___ \ | || | | | |_) | |
// ___) || || |_| | __/|_|
// |____/ |_| \___/|_| (_)
// =========================
// SIGN_BADIP MUST BE CHECKED LAST, AND RETURN ONLY IF ALL OTHER CHECKS HAVE ALREADY SUCCEEDED.
// We allow IP failures through for compatibility with shitty VPNs and fucked-beyond-belief home networks.
// If this is checked before other sign-safety conditons, bad actors can INTENTIONALLY SKIP CHECKS.
if (realIP != claimedIP && I_IsExternalAddress(&realIP))
return SIGN_BADIP;
#ifdef DEVELOP
if (cv_badip.value)
{
CV_AddValue(&cv_badip, -1);
CONS_Alert(CONS_WARNING, "cv_badip enabled, intentionally failing checks\n");
return SIGN_BADIP;
}
#endif
// Do NOT return SIGN_BADIP before doing all other available checks.
return SIGN_OK;
}
@ -951,17 +973,19 @@ static boolean CL_SendJoin(void)
// Don't leak old signatures from prior sessions.
memset(&netbuffer->u.clientcfg.challengeResponse, 0, sizeof(((clientconfig_pak *)0)->challengeResponse));
forceGuest = false;
if (client && netgame)
{
shouldsign_t safe = ShouldSignChallenge(awaitingChallenge);
if (safe != SIGN_OK)
if (safe == SIGN_BADIP)
{
if (safe == SIGN_BADIP)
{
I_Error("External server IP didn't match the message it sent.");
}
else if (safe == SIGN_BADTIME)
forceGuest = true;
}
else if (safe != SIGN_OK)
{
if (safe == SIGN_BADTIME)
{
I_Error("External server sent a message with an unusual timestamp.\nMake sure your system time is set correctly.");
}
@ -978,7 +1002,7 @@ static boolean CL_SendJoin(void)
uint8_t signature[SIGNATURELENGTH];
profile_t *localProfile = PR_GetLocalPlayerProfile(i);
if (PR_IsLocalPlayerGuest(i)) // GUESTS don't have keys
if (PR_IsLocalPlayerGuest(i) || forceGuest) // GUESTS don't have keys
{
memset(signature, 0, sizeof(signature));
}
@ -1450,6 +1474,9 @@ static void CL_LoadReceivedSavegame(boolean reloading)
}
}
}
if (forceGuest)
HU_AddChatText("\x85* This server uses a strange network configuration (VPN?). You have joined as a GUEST.", true);
}
static void CL_ReloadReceivedSavegame(void)
@ -4436,8 +4463,19 @@ static void HandleConnect(SINT8 node)
if (netgame && sigcheck != 0)
{
SV_SendRefuse(node, M_GetText("Signature verification failed."));
return;
uint8_t allZero[SIGNATURELENGTH];
memset(allZero, 0, SIGNATURELENGTH);
if (memcmp(netbuffer->u.clientcfg.challengeResponse[i], allZero, SIGNATURELENGTH) == 0)
{
// The connecting client didn't even try to sign this, probably due to an IP mismatch.
// Let them in as a guest.
memset(lastReceivedKey[node][i], 0, PUBKEYLENGTH);
}
else
{
SV_SendRefuse(node, M_GetText("Signature verification failed."));
return;
}
}
}
@ -5864,11 +5902,11 @@ static void HandlePacketFromPlayer(SINT8 node)
memcpy(lastChallengeAll, netbuffer->u.challengeall.secret, sizeof(lastChallengeAll));
shouldsign_t safe = ShouldSignChallenge(lastChallengeAll);
if (safe != SIGN_OK)
if (safe == SIGN_BADIP)
forceGuest = true;
else if (safe != SIGN_OK)
{
if (safe == SIGN_BADIP)
HandleSigfail("External server sent the wrong IP");
else if (safe == SIGN_BADTIME)
if (safe == SIGN_BADTIME)
HandleSigfail("Bad timestamp - is your time set correctly?");
else
HandleSigfail("Unknown auth error - contact a developer");
@ -5893,7 +5931,7 @@ static void HandlePacketFromPlayer(SINT8 node)
{
uint8_t signature[SIGNATURELENGTH];
profile_t *localProfile = PR_GetLocalPlayerProfile(challengeplayers);
if (!PR_IsLocalPlayerGuest(challengeplayers)) // GUESTS don't have keys
if (!PR_IsLocalPlayerGuest(challengeplayers) && !forceGuest) // GUESTS don't have keys
{
crypto_eddsa_sign(signature, localProfile->secret_key, lastChallengeAll, sizeof(lastChallengeAll));

View file

@ -1176,7 +1176,14 @@ enum {
WP_ANALOGSTICK = 1<<3,
WP_AUTORING = 1<<4,
WP_SELFMUTE = 1<<5,
WP_SELFDEAFEN = 1<<6
WP_SELFDEAFEN = 1<<6,
WP_STRICTFASTFALL = 1<<7,
// WARNING: STUPID LEGACY TIMEWASTER AHEAD
// IF YOU ARE ADDING OR MODIFYING WEAPONPREFS, YOU MUST
// PRESERVE THEM IN G_PlayerReborn -- OTHERWISE THEY
// WILL MYSTERIOUSLY VANISH AFTER ONE RACE
//
// HOURS LOST TO G_PlayerReborn: UNCOUNTABLE
};
void WeaponPref_Send(UINT8 ssplayer)
@ -1207,6 +1214,9 @@ void WeaponPref_Send(UINT8 ssplayer)
prefs |= WP_SELFDEAFEN;
}
if (cv_strictfastfall[ssplayer].value)
prefs |= WP_STRICTFASTFALL;
UINT8 buf[2];
buf[0] = prefs;
buf[1] = cv_mindelay.value;
@ -1235,6 +1245,9 @@ void WeaponPref_Save(UINT8 **cp, INT32 playernum)
if (player->pflags & PF_AUTORING)
prefs |= WP_AUTORING;
if (player->pflags & PF2_STRICTFASTFALL)
prefs |= WP_STRICTFASTFALL;
WRITEUINT8(*cp, prefs);
}
@ -1246,7 +1259,7 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
UINT8 prefs = READUINT8(p);
player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME|PF_AUTOROULETTE|PF_AUTORING);
player->pflags2 &= ~(PF2_SELFMUTE | PF2_SELFDEAFEN);
player->pflags2 &= ~(PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_STRICTFASTFALL);
if (prefs & WP_KICKSTARTACCEL)
player->pflags |= PF_KICKSTARTACCEL;
@ -1271,6 +1284,9 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
if (prefs & WP_SELFDEAFEN)
player->pflags2 |= PF2_SELFDEAFEN;
if (prefs & WP_STRICTFASTFALL)
player->pflags2 |= PF2_STRICTFASTFALL;
if (leveltime < 2)
{
// BAD HACK: No other place I tried to slot this in

View file

@ -138,6 +138,7 @@ typedef enum
PF2_SELFDEAFEN = 1<<2,
PF2_SERVERMUTE = 1<<3,
PF2_SERVERDEAFEN = 1<<4,
PF2_STRICTFASTFALL = 1<<5,
} pflags2_t;
typedef enum
@ -724,6 +725,8 @@ struct player_t
UINT8 noEbrakeMagnet; // Briefly disable 2.2 responsive ebrake if you're bumped by another player.
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
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.

View file

@ -34,11 +34,12 @@ typedef enum
BT_LOOKBACK = 1<<5, // Look Backward
BT_RESPAWN = 1<<6, // Respawn
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_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE),
BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT),
// free: 1<<8 to 1<<12
// free: 1<<9 to 1<<12
// Lua garbage, replace with freeslottable buttons some day
BT_LUAA = 1<<13,

View file

@ -3081,6 +3081,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_BADNIK_EXPLOSION_SHOCKWAVE2",
"S_BADNIK_EXPLOSION1",
"S_BADNIK_EXPLOSION2",
// Flybot767 (stun)
"S_FLYBOT767",
};
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -3973,6 +3976,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_PULLUPHOOK",
"MT_AMPS",
"MT_FLYBOT767",
};
const char *const MOBJFLAG_LIST[] = {
@ -5005,6 +5010,7 @@ struct int_const_s const INT_CONST[] = {
{"BT_LOOKBACK",BT_LOOKBACK},
{"BT_RESPAWN",BT_RESPAWN},
{"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_LUAA",BT_LUAA}, // Lua customizable
@ -5246,6 +5252,11 @@ struct int_const_s const INT_CONST[] = {
{"TN_CHANGEPITCH",TN_CHANGEPITCH},
{"TN_LOOPING",TN_LOOPING},
{"PICKUP_RINGORSPHERE", PICKUP_RINGORSPHERE},
{"PICKUP_ITEMBOX", PICKUP_ITEMBOX},
{"PICKUP_EGGBOX", PICKUP_EGGBOX},
{"PICKUP_PAPERITEM", PICKUP_PAPERITEM},
{NULL,0}
};

View file

@ -400,7 +400,7 @@ class TiccmdBuilder
};
map(gc_drift, BT_DRIFT); // drift
map(gc_spindash, BT_SPINDASHMASK); // C
map(gc_spindash, BT_SPINDASH|BT_SPINDASHMASK); // C
map(gc_item, BT_ATTACK); // fire
map(gc_lookback, BT_LOOKBACK); // rear view

View file

@ -207,6 +207,7 @@ boolean G_CompatLevel(UINT16 level)
#define DEMO_BOT 0x08
#define DEMO_AUTOROULETTE 0x10
#define DEMO_AUTORING 0x20
#define DEMO_STRICTFASTFALL 0x40
// For demos
#define ZT_FWD 0x0001
@ -2336,6 +2337,8 @@ void G_BeginRecording(void)
i |= DEMO_SPECTATOR;
if (player->pflags & PF_KICKSTARTACCEL)
i |= DEMO_KICKSTART;
if (player->pflags & PF2_STRICTFASTFALL)
i |= DEMO_STRICTFASTFALL;
if (player->pflags & PF_AUTOROULETTE)
i |= DEMO_AUTOROULETTE;
if (player->pflags & PF_AUTORING)
@ -3453,6 +3456,11 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
else
players[p].pflags &= ~PF_KICKSTARTACCEL;
if (flags & DEMO_STRICTFASTFALL)
players[p].pflags |= PF2_STRICTFASTFALL;
else
players[p].pflags &= ~PF2_STRICTFASTFALL;
if (flags & DEMO_AUTOROULETTE)
players[p].pflags |= PF_AUTOROULETTE;
else

View file

@ -2353,7 +2353,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
xtralife = players[player].xtralife;
pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK|PF_AUTORING));
pflags2 = (players[player].pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN));
pflags2 = (players[player].pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN | PF2_STRICTFASTFALL));
// SRB2kart
memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette));

View file

@ -100,6 +100,7 @@ extern consvar_t cv_pauseifunfocused;
extern consvar_t cv_kickstartaccel[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_autoroulette[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_strictfastfall[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_autoring[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_shrinkme[MAXSPLITSCREENPLAYERS];

View file

@ -111,7 +111,7 @@ void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boo
box.patch(gfx("PAD{}", analog ? "N" : dpad_suffix(dpad)));
box.patch(but('A', gc_a, BT_ACCELERATE));
box.patch(but('B', gc_b, BT_LOOKBACK));
box.patch(but('C', gc_c, BT_SPINDASHMASK));
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('Z', gc_z, BT_VOTE));

View file

@ -771,6 +771,9 @@ char sprnames[NUMSPRITES + 1][5] =
"DIEM", // smoke
"DIEN", // explosion
// Flybot767 (stun)
"STUN",
// Pulley
"HCCH",
"HCHK",
@ -3632,6 +3635,9 @@ state_t states[NUMSTATES] =
{SPR_NULL, 0, 1, {A_PlaySound}, sfx_s3k3d, 1, S_BATTLEBUMPER_EXBLAST1}, // S_BADNIK_EXPLOSION_SHOCKWAVE2
{SPR_NULL, 0, 1, {NULL}, 0, 0, S_BADNIK_EXPLOSION2}, // S_BADNIK_EXPLOSION1
{SPR_WIPD, FF_FULLBRIGHT|FF_RANDOMANIM|FF_ANIMATE, 30, {NULL}, 9, 3, S_NULL}, // S_BADNIK_EXPLOSION2
// Flybot767 (stun)
{SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767
};
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -22320,6 +22326,32 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
S_NULL // raisestate
},
{ // MT_FLYBOT767
-1, // doomednum
S_FLYBOT767, // 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_pop, // deathsound
4*FRACUNIT, // speed
32*FRACUNIT, // radius
15*FRACUNIT, // height
0, // dispoffset
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING, // flags
S_NULL // raisestate
},
};

View file

@ -1306,6 +1306,9 @@ typedef enum sprite
SPR_DIEM, // smoke
SPR_DIEN, // explosion
// Flybot767 (stun)
SPR_STUN,
// Pulley
SPR_HCCH,
SPR_HCHK,
@ -4117,6 +4120,9 @@ typedef enum state
S_BADNIK_EXPLOSION1,
S_BADNIK_EXPLOSION2,
// Flybot767 (stun)
S_FLYBOT767,
S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES
@ -5032,6 +5038,8 @@ typedef enum mobj_type
MT_AMPS,
MT_FLYBOT767,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -497,7 +497,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
break;
}
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1))
if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_ITEMBOX))
{
K_AddAttackObject(thing, side, ((thing->extravalue1 < RINGBOX_TIME) ? 10 : 20));
}
@ -508,7 +508,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
break;
}
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1)) // Can pick up an actual item
if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_ITEMBOX)) // Can pick up an actual item
{
const UINT8 stealth = K_EggboxStealth(thing->x, thing->y);
const UINT8 requiredstealth = (g_nudgeSearch.botmo->player->botvars.difficulty * g_nudgeSearch.botmo->player->botvars.difficulty);
@ -529,7 +529,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
break;
}
if (P_CanPickupItem(g_nudgeSearch.botmo->player, 3))
if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_PAPERITEM))
{
K_AddAttackObject(thing, side, 20);
}
@ -541,8 +541,8 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
break;
}
if ((RINGTOTAL(g_nudgeSearch.botmo->player) < 20 && !(g_nudgeSearch.botmo->player->pflags & PF_RINGLOCK)
&& P_CanPickupItem(g_nudgeSearch.botmo->player, 0))
if ((RINGTOTAL(g_nudgeSearch.botmo->player) < 20
&& P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_RINGORSPHERE))
&& !thing->extravalue1
&& (g_nudgeSearch.botmo->player->itemtype != KITEM_LIGHTNINGSHIELD))
{

View file

@ -178,7 +178,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2)
if (K_TryPickMeUp(t1, t2))
return true;
if (!P_CanPickupItem(t2->player, 2))
if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX))
return true;
K_DropItems(t2->player);

View file

@ -8844,7 +8844,7 @@ static inline BlockItReturn_t PIT_AttractingRings(mobj_t *thing)
return BMIT_CONTINUE; // Too far away
}
if (RINGTOTAL(attractmo->player) >= 20 || (attractmo->player->pflags & PF_RINGLOCK))
if (RINGTOTAL(attractmo->player) >= 20 || !P_CanPickupItem(attractmo->player, PICKUP_RINGORSPHERE))
{
// Already reached max -- just joustle rings around.
@ -9406,6 +9406,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->ringdelay)
player->ringdelay--;
if ((player->stunned > 0)
&& (player->respawn.state == RESPAWNST_NONE)
&& !P_PlayerInPain(player)
&& 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));
// when timer reaches 0, reset the flag and stun combo counter
if ((player->stunned & 0x7FFF) == 0)
{
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);
}
}
if (player->trickpanel == TRICKSTATE_READY)
{
if (!player->throwdir && !cmd->turning)
@ -10070,7 +10097,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
}
extern consvar_t cv_fuzz;
if (cv_fuzz.value && P_CanPickupItem(player, 1))
if (cv_fuzz.value && P_CanPickupItem(player, PICKUP_ITEMBOX))
{
K_StartItemRoulette(player, P_RandomRange(PR_FUZZ, 0, 1));
}
@ -12680,6 +12707,10 @@ static void K_KartSpindash(player_t *player)
if (player->fastfall == 0)
{
if (player->pflags2 & PF2_STRICTFASTFALL)
if (!(player->cmd.buttons & BT_SPINDASH))
return;
// Factors 3D momentum.
player->fastfallBase = FixedHypot(player->speed, player->mo->momz);
}

View file

@ -1130,6 +1130,7 @@ extern consvar_t cv_dummyprofileplayername;
extern consvar_t cv_dummyprofilekickstart;
extern consvar_t cv_dummyprofileautoroulette;
extern consvar_t cv_dummyprofilelitesteer;
extern consvar_t cv_dummyprofilestrictfastfall;
extern consvar_t cv_dummyprofiledescriptiveinput;
extern consvar_t cv_dummyprofileautoring;
extern consvar_t cv_dummyprofilerumble;

View file

@ -435,6 +435,11 @@ boolean Obj_DestroyKart(mobj_t *kart);
void Obj_DestroyedKartParticleThink(mobj_t *part);
void Obj_DestroyedKartParticleLanding(mobj_t *part);
/* Flybot767 (stun) */
void Obj_SpawnFlybotsForPlayer(player_t *player);
void Obj_FlybotThink(mobj_t *flybot);
void Obj_FlybotDeath(mobj_t *flybot);
/* Pulley */
void Obj_PulleyThink(mobj_t *root);
void Obj_PulleyHookTouch(mobj_t *special, mobj_t *toucher);

View file

@ -85,6 +85,7 @@ profile_t* PR_MakeProfile(
newprofile->kickstartaccel = false;
newprofile->autoroulette = false;
newprofile->litesteer = false;
newprofile->strictfastfall = false;
newprofile->descriptiveinput = 1;
newprofile->autoring = false;
newprofile->rumble = true;
@ -108,6 +109,7 @@ profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const
newprofile->kickstartaccel = cv_kickstartaccel[pnum].value;
newprofile->autoroulette = cv_autoroulette[pnum].value;
newprofile->litesteer = cv_litesteer[pnum].value;
newprofile->strictfastfall = cv_strictfastfall[pnum].value;
newprofile->descriptiveinput = cv_descriptiveinput[pnum].value;
newprofile->autoring = cv_autoring[pnum].value;
newprofile->rumble = cv_rumble[pnum].value;
@ -305,6 +307,7 @@ void PR_SaveProfiles(void)
jsonprof.preferences.kickstartaccel = cprof->kickstartaccel;
jsonprof.preferences.autoroulette = cprof->autoroulette;
jsonprof.preferences.litesteer = cprof->litesteer;
jsonprof.preferences.strictfastfall = cprof->strictfastfall;
jsonprof.preferences.descriptiveinput = cprof->descriptiveinput;
jsonprof.preferences.autoring = cprof->autoring;
jsonprof.preferences.rumble = cprof->rumble;
@ -493,6 +496,7 @@ void PR_LoadProfiles(void)
newprof->kickstartaccel = jsprof.preferences.kickstartaccel;
newprof->autoroulette = jsprof.preferences.autoroulette;
newprof->litesteer = jsprof.preferences.litesteer;
newprof->strictfastfall = jsprof.preferences.strictfastfall;
newprof->descriptiveinput = jsprof.preferences.descriptiveinput;
newprof->autoring = jsprof.preferences.autoring;
newprof->rumble = jsprof.preferences.rumble;
@ -597,6 +601,7 @@ static void PR_ApplyProfile_Settings(profile_t *p, UINT8 playernum)
CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel);
CV_StealthSetValue(&cv_autoroulette[playernum], p->autoroulette);
CV_StealthSetValue(&cv_litesteer[playernum], p->litesteer);
CV_StealthSetValue(&cv_strictfastfall[playernum], p->strictfastfall);
CV_StealthSetValue(&cv_descriptiveinput[playernum], p->descriptiveinput);
CV_StealthSetValue(&cv_autoring[playernum], p->autoring);
CV_StealthSetValue(&cv_rumble[playernum], p->rumble);

View file

@ -46,6 +46,7 @@ struct ProfilePreferencesJson
bool kickstartaccel;
bool autoroulette;
bool litesteer;
bool strictfastfall;
uint8_t descriptiveinput;
bool autoring;
bool rumble;
@ -56,6 +57,7 @@ struct ProfilePreferencesJson
kickstartaccel,
autoroulette,
litesteer,
strictfastfall,
descriptiveinput,
autoring,
rumble,
@ -164,6 +166,7 @@ struct profile_t
boolean kickstartaccel; // cv_kickstartaccel
boolean autoroulette; // cv_autoroulette
boolean litesteer; // cv_litesteer
boolean strictfastfall; // cv_strictfastfall
UINT8 descriptiveinput; // cv_descriptiveinput
boolean autoring; // cv_autoring
boolean rumble; // cv_rumble

View file

@ -260,6 +260,10 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->tumbleBounces);
else if (fastcmp(field,"tumbleheight"))
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,"justdi"))
lua_pushinteger(L, plr->justDI);
else if (fastcmp(field,"flipdi"))
@ -878,6 +882,10 @@ static int player_set(lua_State *L)
plr->tumbleBounces = luaL_checkinteger(L, 3);
else if (fastcmp(field,"tumbleheight"))
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,"justdi"))
plr->justDI = luaL_checkinteger(L, 3);
else if (fastcmp(field,"flipdi"))

View file

@ -102,6 +102,7 @@ void M_StartEditProfile(INT32 c)
CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel);
CV_StealthSetValue(&cv_dummyprofileautoroulette, optionsmenu.profile->autoroulette);
CV_StealthSetValue(&cv_dummyprofilelitesteer, optionsmenu.profile->litesteer);
CV_StealthSetValue(&cv_dummyprofilestrictfastfall, optionsmenu.profile->strictfastfall);
CV_StealthSetValue(&cv_dummyprofiledescriptiveinput, optionsmenu.profile->descriptiveinput);
CV_StealthSetValue(&cv_dummyprofileautoring, optionsmenu.profile->autoring);
CV_StealthSetValue(&cv_dummyprofilerumble, optionsmenu.profile->rumble);
@ -114,6 +115,7 @@ void M_StartEditProfile(INT32 c)
CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off
CV_StealthSetValue(&cv_dummyprofileautoroulette, 0); // off
CV_StealthSetValue(&cv_dummyprofilelitesteer, 1); // on
CV_StealthSetValue(&cv_dummyprofilestrictfastfall, 0); // off
CV_StealthSetValue(&cv_dummyprofiledescriptiveinput, 1); // Modern
CV_StealthSetValue(&cv_dummyprofileautoring, 0); // on
CV_StealthSetValue(&cv_dummyprofilerumble, 1); // on

View file

@ -98,6 +98,7 @@ static void M_ProfileEditApply(void)
optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value;
optionsmenu.profile->autoroulette = cv_dummyprofileautoroulette.value;
optionsmenu.profile->litesteer = cv_dummyprofilelitesteer.value;
optionsmenu.profile->strictfastfall = cv_dummyprofilestrictfastfall.value;
optionsmenu.profile->descriptiveinput = cv_dummyprofiledescriptiveinput.value;
optionsmenu.profile->autoring = cv_dummyprofileautoring.value;
optionsmenu.profile->rumble = cv_dummyprofilerumble.value;

View file

@ -114,6 +114,9 @@ menuitem_t OPTIONS_ProfileAccessibility[] = {
{IT_STRING | IT_CVAR, "Lite Steer", "Hold DOWN on d-pad/keyboard for shallow turns.",
NULL, {.cvar = &cv_dummyprofilelitesteer}, 0, 0},
{IT_STRING | IT_CVAR, "Strict Fastfall", "Fastfall only with the Spindash button.",
NULL, {.cvar = &cv_dummyprofilestrictfastfall}, 0, 0},
{IT_STRING | IT_CVAR, "Field of View", "Higher FOV lets you see more.",
NULL, {.cvar = &cv_dummyprofilefov}, 0, 0},
@ -144,7 +147,7 @@ menu_t OPTIONS_ProfileAccessibilityDef = {
&OPTIONS_EditProfileDef,
0,
OPTIONS_ProfileAccessibility,
145, 41,
145, 31,
SKINCOLOR_ULTRAMARINE, 0,
MBF_DRAWBGWHILEPLAYING,
"FILE",

View file

@ -60,6 +60,7 @@ target_sources(SRB2SDL2 PRIVATE
pulley.cpp
amps.c
ballhog.cpp
flybot767.c
)
add_subdirectory(versus)

142
src/objects/flybot767.c Normal file
View file

@ -0,0 +1,142 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Lachlan "Lach" Wright
// 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 flybot767.c
/// \brief Flybot767 object code.
#include "../p_local.h"
#include "../k_kart.h"
#include "../k_objects.h"
#include "../s_sound.h"
#include "../m_easing.h"
#define FLYBOT_QUANTITY 2
#define FLYBOT_VERTICAL_OFFSET (16 * FRACUNIT)
#define FLYBOT_BOB_AMPLITUDE (16 * FRACUNIT)
#define FLYBOT_BOB_FREQUENCY (ANG15)
#define FLYBOT_FADE_STARTTIME (2 * TICRATE)
#define FLYBOT_SCALE (17 * FRACUNIT / 20)
static const fixed_t PI = 355 * FRACUNIT / 113;
static fixed_t SetFlybotZ(mobj_t *flybot)
{
flybot->z = FixedMul(mapobjectscale, FLYBOT_VERTICAL_OFFSET) + FixedMul(mapobjectscale, P_ReturnThrustX(NULL, flybot->movedir, FLYBOT_BOB_AMPLITUDE));
if (flybot->eflags & MFE_VERTICALFLIP)
{
flybot->z = -flybot->z - flybot->height;
}
else
{
flybot->z += flybot->target->height;
}
flybot->z += flybot->target->z;
return flybot->z;
}
void Obj_SpawnFlybotsForPlayer(player_t *player)
{
UINT8 i;
mobj_t *mo = player->mo;
fixed_t radius = mo->radius;
for (i = 0; i < FLYBOT_QUANTITY; i++)
{
angle_t angle = mo->angle + ANGLE_90 + FixedAngle(i * 360 * FRACUNIT / FLYBOT_QUANTITY);
mobj_t *flybot = P_SpawnMobj(
mo->x + P_ReturnThrustX(NULL, angle, radius),
mo->y + P_ReturnThrustY(NULL, angle, radius),
mo->z,
MT_FLYBOT767
);
P_InstaScale(flybot, flybot->old_scale = FixedMul(mapobjectscale, FLYBOT_SCALE));
P_SetTarget(&flybot->target, mo);
flybot->eflags |= mo->eflags & MFE_VERTICALFLIP;
flybot->movedir = flybot->old_angle = flybot->angle = angle + ANGLE_90;
flybot->old_z = SetFlybotZ(flybot);
flybot->renderflags |= (i * RF_DONTDRAW);
}
}
void Obj_FlybotThink(mobj_t *flybot)
{
UINT16 stunned = UINT16_MAX;
angle_t deltaAngle, angle;
fixed_t radius, circumference;
fixed_t speed = FixedMul(mapobjectscale, flybot->info->speed);
mobj_t *mo = flybot->target;
if (P_MobjWasRemoved(mo))
{
P_KillMobj(flybot, NULL, NULL, 0);
return;
}
if (mo->player)
{
if (((stunned = mo->player->stunned & 0x7FFF) == 0) || (mo->player->playerstate == PST_DEAD))
{
P_KillMobj(flybot, NULL, NULL, 0);
return;
}
}
flybot->frame = flybot->frame & ~FF_TRANSMASK;
if (stunned < FLYBOT_FADE_STARTTIME)
{
flybot->frame |= Easing_InCubic(FixedDiv(stunned, FLYBOT_FADE_STARTTIME), 7, 1) << FF_TRANSSHIFT;
}
flybot->eflags = (flybot->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP);
flybot->movedir += FLYBOT_BOB_FREQUENCY;
flybot->renderflags ^= RF_DONTDRAW;
radius = mo->radius;
circumference = 2 * FixedMul(PI, radius);
deltaAngle = FixedAngle(FixedMul(FixedDiv(speed, circumference), 360 * FRACUNIT));
flybot->angle += deltaAngle;
angle = flybot->angle - ANGLE_90;
P_MoveOrigin(flybot,
mo->x + P_ReturnThrustX(NULL, angle, radius),
mo->y + P_ReturnThrustY(NULL, angle, radius),
SetFlybotZ(flybot)
);
}
void Obj_FlybotDeath(mobj_t *flybot)
{
UINT8 i;
angle_t angle = 0;
fixed_t hThrust = 4*mapobjectscale, vThrust = 4*mapobjectscale;
vector3_t mom = {0, 0, 0};
mobj_t *mo = flybot->target;
if (!P_MobjWasRemoved(mo))
{
mom.x = mo->momx;
mom.y = mo->momy;
mom.z = mo->momz;
//S_StartSound(mo, flybot->info->deathsound);
}
for (i = 0; i < 4; i++)
{
mo = P_SpawnMobjFromMobj(flybot, 0, 0, 0, MT_PARTICLE);
P_SetMobjState(mo, S_SPINDASHDUST);
mo->flags |= MF_NOSQUISH;
mo->renderflags |= RF_FULLBRIGHT;
mo->momx = mom.x;
mo->momy = mom.y;
mo->momz = mom.z + vThrust;
P_Thrust(mo, angle, hThrust);
vThrust *= -1;
angle += ANGLE_90;
}
}

View file

@ -629,7 +629,7 @@ award_immediately (mobj_t *hyu)
return false;
}
if (!P_CanPickupItem(player, 1))
if (!P_CanPickupItem(player, PICKUP_ITEMBOX))
return false;
// Prevent receiving any more items or even stacked

View file

@ -47,11 +47,11 @@ static player_t *GetItemBoxPlayer(mobj_t *mobj)
continue;
}
// Always use normal item box rules -- could pass in "2" for fakes but they blend in better like this
if (P_CanPickupItem(&players[i], 1))
// Always use normal item box rules -- could pass in "PICKUP_EGGBOX" for fakes but they blend in better like this
if (P_CanPickupItem(&players[i], PICKUP_ITEMBOX))
{
// Check for players who can take this pickup, but won't be allowed to (antifarming)
UINT8 mytype = (mobj->flags2 & MF2_BOSSDEAD) ? 2 : 1;
UINT8 mytype = (mobj->flags2 & MF2_BOSSDEAD) ? CHEESE_RINGBOX : CHEESE_ITEMBOX;
if (P_IsPickupCheesy(&players[i], mytype))
continue;

View file

@ -120,21 +120,32 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
if (player->exiting || mapreset || (player->pflags & PF_ELIMINATED) || player->itemRoulette.reserved)
return false;
// 0: Sphere/Ring
// 1: Random Item / Capsule
// 2: Eggbox
// 3: Paperitem
// See p_local.h for pickup types
if (weapon != 2 && player->instaWhipCharge)
if (weapon != PICKUP_EGGBOX && player->instaWhipCharge)
return false;
if (weapon == 1 && !player->cangrabitems)
if (weapon == PICKUP_ITEMBOX && !player->cangrabitems)
return false;
if (weapon)
if (weapon == PICKUP_RINGORSPHERE)
{
// No picking up rings while SPB is targetting you
if (player->pflags & PF_RINGLOCK)
{
return false;
}
// No picking up rings while stunned
if (player->stunned > 0)
{
return false;
}
}
else
{
// Item slot already taken up
if (weapon == 2)
if (weapon == PICKUP_EGGBOX)
{
// Invulnerable
if (player->flashing > 0)
@ -156,11 +167,11 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
// Item slot already taken up
if (player->itemRoulette.active == true
|| player->ringboxdelay > 0
|| (weapon != 3 && player->itemamount)
|| (weapon != PICKUP_PAPERITEM && player->itemamount)
|| (player->itemflags & IF_ITEMOUT))
return false;
if (weapon == 3 && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE)
if (weapon == PICKUP_PAPERITEM && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE)
return false; // No stacking shields!
}
}
@ -171,7 +182,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
// Allow players to pick up only one pickup from each set of pickups.
// Anticheese pickup types are different than-P_CanPickupItem weapon, because that system is
// already slightly scary without introducing special cases for different types of the same pickup.
// 1 = floating item, 2 = perma ring, 3 = capsule
// See p_local.h for cheese types.
boolean P_IsPickupCheesy(player_t *player, UINT8 type)
{
extern consvar_t cv_debugcheese;
@ -414,7 +425,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (special->scale < special->destscale/2)
return;
if (!P_CanPickupItem(player, 3) || (player->itemamount && player->itemtype != special->threshold))
if (!P_CanPickupItem(player, PICKUP_PAPERITEM) || (player->itemamount && player->itemtype != special->threshold))
return;
player->itemtype = special->threshold;
@ -434,9 +445,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
special->flags &= ~MF_SPECIAL;
return;
case MT_RANDOMITEM: {
UINT8 cheesetype = (special->flags2 & MF2_BOSSDEAD) ? 2 : 1; // perma ring box
UINT8 cheesetype = (special->flags2 & MF2_BOSSDEAD) ? CHEESE_RINGBOX : CHEESE_ITEMBOX; // perma ring box
if (!P_CanPickupItem(player, 1))
if (!P_CanPickupItem(player, PICKUP_ITEMBOX))
return;
if (P_IsPickupCheesy(player, cheesetype))
return;
@ -476,7 +487,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
}
case MT_SPHEREBOX:
if (!P_CanPickupItem(player, 0))
if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE))
return;
special->momx = special->momy = special->momz = 0;
@ -496,15 +507,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
break;
case KITEM_SUPERRING:
if (player->pflags & PF_RINGLOCK) // no cheaty rings
return;
if (player->instaWhipCharge)
if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE)) // no cheaty rings
return;
break;
default:
if (!P_CanPickupItem(player, 1))
if (!P_CanPickupItem(player, PICKUP_ITEMCAPSULE))
return;
if (P_IsPickupCheesy(player, 3))
if (P_IsPickupCheesy(player, CHEESE_ITEMCAPSULE))
return;
break;
}
@ -561,7 +570,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
}
case MT_EMERALD:
if (!P_CanPickupItem(player, 0) || P_PlayerInPain(player))
if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE) || P_PlayerInPain(player))
return;
if (special->threshold > 0)
@ -620,7 +629,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
case MT_CDUFO: // SRB2kart
if (special->fuse || !P_CanPickupItem(player, 1))
if (special->fuse || !P_CanPickupItem(player, PICKUP_ITEMBOX))
return;
K_StartItemRoulette(player, false);
@ -684,19 +693,11 @@ 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;
// 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;
if (!(P_CanPickupItem(player, 0)))
if (!(P_CanPickupItem(player, PICKUP_RINGORSPHERE)))
return;
// Reached the cap, don't waste 'em!
@ -718,7 +719,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
case MT_BLUESPHERE:
if (!(P_CanPickupItem(player, 0)))
if (!(P_CanPickupItem(player, PICKUP_RINGORSPHERE)))
return;
P_GivePlayerSpheres(player, 1);
@ -2304,6 +2305,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
case MT_EMFAUCET_DRIP:
Obj_EMZDripDeath(target);
break;
case MT_FLYBOT767:
Obj_FlybotDeath(target);
break;
default:
break;
}
@ -3004,6 +3008,7 @@ 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.
@ -3394,6 +3399,27 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->flipDI = true;
}
// Apply stun!
// Feel free to move these calculations higher up if different damage sources should apply variable stun in future
#define MIN_STUNTICS (8 * TICRATE)
#define MAX_STUNTICS (18 * 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)
{
stunTics /= 3;
}
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);
}
}

View file

@ -557,6 +557,17 @@ void P_CheckTimeLimit(void);
void P_CheckPointLimit(void);
boolean P_CheckRacers(void);
// Pickup types
#define PICKUP_RINGORSPHERE 0
#define PICKUP_ITEMBOX 1
#define PICKUP_EGGBOX 2
#define PICKUP_PAPERITEM 3
#define PICKUP_ITEMCAPSULE 4
#define CHEESE_ITEMBOX 1
#define CHEESE_RINGBOX 2
#define CHEESE_ITEMCAPSULE 3
boolean P_CanPickupItem(player_t *player, UINT8 weapon);
boolean P_IsPickupCheesy(player_t *player, UINT8 type);
void P_UpdateLastPickup(player_t *player, UINT8 type);

View file

@ -35,6 +35,7 @@
#include "k_terrain.h"
#include "k_objects.h"
#include "k_boss.h"
#include "k_hitlag.h" // K_AddHitlag
#include "r_splats.h"
@ -4120,10 +4121,24 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
// Combo avoidance!
if (mo->player && P_PlayerInPain(mo->player) && gametyperules & GTR_BUMPERS && mo->health == 1)
{
K_StumblePlayer(mo->player);
K_BumperInflate(mo->player);
mo->player->tumbleBounces = TUMBLEBOUNCES;
mo->hitlag = max(mo->hitlag, 6);
P_ResetPlayer(mo->player);
mo->player->spinouttimer = 0;
mo->player->wipeoutslow = 0;
mo->player->tumbleBounces = 0;
K_AddHitLag(mo, 3, false);
// "I dunno man, just fuckin' do it" - jart
S_StartSound(mo, sfx_mbs45);
S_StartSound(mo, sfx_mbs45);
S_StartSound(mo, sfx_mbs45);
S_StartSound(mo, sfx_mbs45);
S_StartSound(mo, sfx_mbv84);
if (mo->eflags & MFE_VERTICALFLIP)
mo->momz -= 40*mo->scale;
else
mo->momz += 40*mo->scale;
}
mo->momx = tmxmove;

View file

@ -10081,6 +10081,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
break;
}
case MT_FLYBOT767:
{
Obj_FlybotThink(mobj);
break;
}
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);

View file

@ -455,6 +455,8 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].noEbrakeMagnet);
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);
@ -1097,6 +1099,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].noEbrakeMagnet = READUINT8(save->p);
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);