diff --git a/src/cvars.cpp b/src/cvars.cpp index 173ba956a..2ea673fdc 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -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), diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 2ec42c4de..33e9dceaa 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -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)); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b10215e50..054504912 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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 diff --git a/src/d_player.h b/src/d_player.h index 758edd1ec..f19dec65d 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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. diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 88ad11c6e..f0c5f4f28 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -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, diff --git a/src/deh_tables.c b/src/deh_tables.c index 44195a340..35c7b649d 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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} }; diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index df036eab8..b8d45e3dc 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -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 diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 1d36dcb7e..88828fee6 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -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 diff --git a/src/g_game.c b/src/g_game.c index 0b305c3d1..1a55d67f8 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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)); diff --git a/src/g_game.h b/src/g_game.h index f19f819da..14ee6494a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -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]; diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp index fbfd03ee2..c3dab0a53 100644 --- a/src/hud/input-display.cpp +++ b/src/hud/input-display.cpp @@ -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)); diff --git a/src/info.c b/src/info.c index 902575c82..faed52546 100644 --- a/src/info.c +++ b/src/info.c @@ -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 + }, }; diff --git a/src/info.h b/src/info.h index 0345073b7..a525bfd50 100644 --- a/src/info.h +++ b/src/info.h @@ -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 diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 666e2adcf..e60b6c85d 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -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)) { diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 061b25500..0948b3be1 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -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); diff --git a/src/k_kart.c b/src/k_kart.c index 2910fb4c5..9bb27e5b9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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); } diff --git a/src/k_menu.h b/src/k_menu.h index bd2973ff1..21cef5fa5 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -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; diff --git a/src/k_objects.h b/src/k_objects.h index c661e47d2..0d75c222a 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -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); diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index 8ccab2229..06febe5c7 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -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); diff --git a/src/k_profiles.h b/src/k_profiles.h index 4c71ee074..9572bd973 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -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 diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index ee78293f7..a2e5e20d0 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -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")) diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index 0fc2a0de8..8f61f01cd 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -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 diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index f432c24ef..fa5eb661c 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -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; diff --git a/src/menus/options-profiles-edit-accessibility.cpp b/src/menus/options-profiles-edit-accessibility.cpp index bbb09f97a..d347b3ca4 100644 --- a/src/menus/options-profiles-edit-accessibility.cpp +++ b/src/menus/options-profiles-edit-accessibility.cpp @@ -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", diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 593d5b8f6..35ee3ad6c 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -60,6 +60,7 @@ target_sources(SRB2SDL2 PRIVATE pulley.cpp amps.c ballhog.cpp + flybot767.c ) add_subdirectory(versus) diff --git a/src/objects/flybot767.c b/src/objects/flybot767.c new file mode 100644 index 000000000..790276387 --- /dev/null +++ b/src/objects/flybot767.c @@ -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; + } +} diff --git a/src/objects/hyudoro.c b/src/objects/hyudoro.c index 2527ea903..f1e0f440e 100644 --- a/src/objects/hyudoro.c +++ b/src/objects/hyudoro.c @@ -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 diff --git a/src/objects/random-item.c b/src/objects/random-item.c index 3ef9d1f9b..70f565046 100644 --- a/src/objects/random-item.c +++ b/src/objects/random-item.c @@ -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; diff --git a/src/p_inter.c b/src/p_inter.c index 76997b541..b15bf8dab 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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); } } diff --git a/src/p_local.h b/src/p_local.h index ee269807f..505ccd8e0 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -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); diff --git a/src/p_map.c b/src/p_map.c index c4e831118..1289dc71f 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 102a183c9..a38c6651f 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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); diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 90cea3d74..591613d6f 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -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);