diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e453e45e..d48f2b506 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,6 +72,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 r_data.c r_debug.cpp r_debug_parser.cpp + r_debug_printer.cpp r_draw.cpp r_fps.c r_main.cpp diff --git a/src/d_main.cpp b/src/d_main.cpp index 7dee5488f..ad5276ca1 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -90,6 +90,7 @@ #include "k_dialogue.h" #include "k_bans.h" #include "k_credits.h" +#include "r_debug.hpp" #ifdef HWRENDER #include "hardware/hw_main.h" // 3D View Rendering @@ -663,6 +664,7 @@ static bool D_Display(void) { AM_Drawer(); ST_Drawer(); + srb2::r_debug::draw_frame_list(); F_TextPromptDrawer(); break; } @@ -858,6 +860,7 @@ void D_SRB2Loop(void) g_dc = {}; Z_Frame_Reset(); + srb2::r_debug::clear_frame_list(); { // Casting the return value of a function is bad practice (apparently) diff --git a/src/d_player.h b/src/d_player.h index a29de3368..8411fd80f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -965,6 +965,8 @@ struct player_t mobj_t *hand; mobj_t *flickyAttacker; + SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker! + UINT8 instaWhipCharge; UINT8 defenseLockout; // Committed to universal attack/defense, make 'em vulnerable! No whip/guard. UINT8 instaWhipChargeLockout; // Input safety @@ -978,9 +980,11 @@ struct player_t angle_t besthanddirection; INT16 incontrol; // -1 to -175 when spinning out or tumbling, 1 to 175 when not. Use to check for combo hits or emergency inputs. + UINT16 progressivethrust; // When getting beat up in GTR_BUMPERS, speed up the longer you've been out of control. boolean markedfordeath; boolean dotrickfx; + UINT8 bumperinflate; UINT8 ringboxdelay; // Delay until Ring Box auto-activates UINT8 ringboxaward; // Where did we stop? diff --git a/src/g_demo.c b/src/g_demo.c index 95ebc454a..87e43d750 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -56,6 +56,7 @@ #include "k_follower.h" #include "k_vote.h" #include "k_credits.h" +#include "k_grandprix.h" boolean nodrawers; // for comparative timing purposes boolean noblit; // for comparative timing purposes @@ -64,7 +65,7 @@ tic_t demostarttime; // for comparative timing purposes static char demoname[MAX_WADPATH]; static savebuffer_t demobuf = {0}; static UINT8 *demotime_p, *demoinfo_p; -static UINT8 demoflags; +static UINT16 demoflags; boolean demosynced = true; // console warning message struct demovars_s demo; @@ -103,7 +104,7 @@ demoghost *ghosts = NULL; // DEMO RECORDING // -#define DEMOVERSION 0x0007 +#define DEMOVERSION 0x0008 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" #define DF_ATTACKMASK (ATTACKING_TIME|ATTACKING_LAP|ATTACKING_SPB) // This demo contains time/lap data @@ -117,6 +118,8 @@ demoghost *ghosts = NULL; #define DF_ENCORE 0x40 #define DF_MULTIPLAYER 0x80 // This demo was recorded in multiplayer mode! +#define DF_GRANDPRIX 0x0100 + #define DEMO_SPECTATOR 0x01 #define DEMO_KICKSTART 0x02 #define DEMO_SHRINKME 0x04 @@ -2126,6 +2129,9 @@ void G_BeginRecording(void) if (multiplayer) demoflags |= DF_LUAVARS; + if (grandprixinfo.gp) + demoflags |= DF_GRANDPRIX; + // Setup header. M_Memcpy(demobuf.p, DEMOHEADER, 12); demobuf.p += 12; WRITEUINT8(demobuf.p,VERSION); @@ -2148,7 +2154,7 @@ void G_BeginRecording(void) WRITESTRINGN(demobuf.p, mapheaderinfo[gamemap-1]->lumpname, MAXMAPLUMPNAME); M_Memcpy(demobuf.p, mapmd5, 16); demobuf.p += 16; - WRITEUINT8(demobuf.p, demoflags); + WRITEUINT16(demobuf.p, demoflags); WRITESTRINGN(demobuf.p, gametypes[gametype]->name, MAXGAMETYPELENGTH); @@ -2186,6 +2192,13 @@ void G_BeginRecording(void) // Save netvar data CV_SaveDemoVars(&demobuf.p); + if ((demoflags & DF_GRANDPRIX)) + { + WRITEUINT8(demobuf.p, grandprixinfo.gamespeed); + WRITEUINT8(demobuf.p, grandprixinfo.masterbots == true); + WRITEUINT8(demobuf.p, grandprixinfo.eventmode); + } + // Now store some info for each in-game player // Lat' 12/05/19: Do note that for the first game you load, everything that gets saved here is total garbage; @@ -2274,6 +2287,10 @@ void G_BeginRecording(void) // And mobjtype_t is best with UINT32 too... WRITEUINT32(demobuf.p, player->followitem); + + // GP + WRITESINT8(demobuf.p, player->lives); + WRITEINT16(demobuf.p, player->totalring); } } @@ -2364,7 +2381,7 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) UINT8 G_CmpDemoTime(char *oldname, char *newname) { UINT8 *buffer,*p; - UINT8 flags; + UINT16 flags; UINT32 oldtime = UINT32_MAX, newtime = UINT32_MAX; UINT32 oldlap = UINT32_MAX, newlap = UINT32_MAX; UINT16 oldversion; @@ -2395,7 +2412,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) p += 4; // PLAY SKIPSTRING(p); // gamemap p += 16; // map md5 - flags = READUINT8(p); // demoflags + flags = READUINT16(p); // demoflags SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); @@ -2454,7 +2471,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) } p += 4; // "PLAY" SKIPSTRING(p); // gamemap p += 16; // mapmd5 - flags = READUINT8(p); + flags = READUINT16(p); SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); @@ -2498,7 +2515,8 @@ void G_LoadDemoInfo(menudemo_t *pdemo) { savebuffer_t info = {0}; UINT8 *extrainfo_p; - UINT8 version, subversion, pdemoflags, worknumskins, skinid; + UINT8 version, subversion, worknumskins, skinid; + UINT16 pdemoflags; democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; @@ -2578,7 +2596,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) goto corrupt; } - pdemoflags = READUINT8(info.p); + pdemoflags = READUINT16(info.p); // temp? if (!(pdemoflags & DF_MULTIPLAYER)) @@ -2649,6 +2667,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo) if (pdemoflags & DF_ENCORE) pdemo->kartspeed |= DF_ENCORE; + if (pdemoflags & DF_GRANDPRIX) + pdemo->gp = true; + // Read standings! count = 0; @@ -2914,7 +2935,7 @@ void G_DoPlayDemo(const char *defdemoname) READSTRINGN(demobuf.p, mapname, sizeof(mapname)); // gamemap demobuf.p += 16; // mapmd5 - demoflags = READUINT8(demobuf.p); + demoflags = READUINT16(demobuf.p); READSTRINGN(demobuf.p, gtname, sizeof(gtname)); // gametype @@ -3041,6 +3062,15 @@ void G_DoPlayDemo(const char *defdemoname) // net var data CV_LoadDemoVars(&demobuf.p); + memset(&grandprixinfo, 0, sizeof grandprixinfo); + if ((demoflags & DF_GRANDPRIX)) + { + grandprixinfo.gp = true; + grandprixinfo.gamespeed = READUINT8(demobuf.p); + grandprixinfo.masterbots = READUINT8(demobuf.p) != 0; + grandprixinfo.eventmode = READUINT8(demobuf.p); + } + // Sigh ... it's an empty demo. if (*demobuf.p == DEMOMARKER) { @@ -3216,6 +3246,10 @@ void G_DoPlayDemo(const char *defdemoname) // Followitem players[p].followitem = READUINT32(demobuf.p); + // GP + players[p].lives = READSINT8(demobuf.p); + players[p].totalring = READINT16(demobuf.p); + // Look for the next player p = READUINT8(demobuf.p); } @@ -3249,7 +3283,7 @@ void G_DoPlayDemo(const char *defdemoname) P_SetRandSeed(i, randseed[i]); } - G_InitNew(demoflags & DF_ENCORE, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. + G_InitNew((demoflags & DF_ENCORE) != 0, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. for (i = 0; i < numslots; i++) { @@ -3288,7 +3322,7 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) INT32 i; char name[17], color[MAXCOLORNAME+1], md5[16]; demoghost *gh; - UINT8 flags; + UINT16 flags; UINT8 *p; mapthing_t *mthing; UINT16 count, ghostversion; @@ -3346,7 +3380,7 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) SKIPSTRING(p); // gamemap p += 16; // mapmd5 (possibly check for consistency?) - flags = READUINT8(p); + flags = READUINT16(p); if (!(flags & DF_GHOST)) { CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: No ghost data in this demo.\n"), defdemoname); @@ -3399,6 +3433,9 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) p++; } + if ((flags & DF_GRANDPRIX)) + p += 3; + if (*p == DEMOMARKER) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), defdemoname); @@ -3443,6 +3480,9 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) p += 4; // followitem (maybe change later) + p += 1; // lives + p += 2; // rings + if (READUINT8(p) != 0xFF) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (bad terminator)\n"), defdemoname); @@ -3534,7 +3574,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) { UINT8 *p = buffer; UINT16 ghostversion; - UINT8 flags; + UINT16 flags; INT32 i; staffbrief_t temp = {0}; staffbrief_t *ret = NULL; @@ -3575,7 +3615,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) SKIPSTRING(p); // gamemap p += 16; // mapmd5 (possibly check for consistency?) - flags = READUINT8(p); + flags = READUINT16(p); if (!(flags & DF_GHOST)) { goto fail; // we don't NEED to do it here, but whatever @@ -3608,6 +3648,9 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) p++; // stealth } + if ((flags & DF_GRANDPRIX)) + p += 3; + // Assert first player is in and then read name if (READUINT8(p) != 0) goto fail; diff --git a/src/g_demo.h b/src/g_demo.h index 501f8da72..808f3ff25 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -89,6 +89,7 @@ struct menudemo_t { INT16 gametype; SINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk UINT8 numlaps; + UINT8 gp; struct { UINT8 ranking; diff --git a/src/g_game.c b/src/g_game.c index 293ed9feb..ff18f8e96 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2398,6 +2398,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->ringvolume = 255; p->ringtransparency = 255; + p->pitblame = -1; + p->topAccel = MAXTOPACCEL; p->botvars.rubberband = FRACUNIT; diff --git a/src/k_kart.c b/src/k_kart.c index dbe81babf..d26eca203 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4562,6 +4562,22 @@ static boolean K_LastTumbleBounceCondition(player_t *player) return (player->tumbleBounces > TUMBLEBOUNCES && player->tumbleHeight < 60); } +// Bumpers give you bonus launch height and speed, strengthening your DI to help evade combos. +// bumperinflate visuals are handled by MT_BATTLEBUMPER, but the effects are in K_KartPlayerThink. +void K_BumperInflate(player_t *player) +{ + if (!player || P_MobjWasRemoved(player->mo)) + return; + + if (!(gametyperules & GTR_BUMPERS)) + return; + + player->bumperinflate = 3; + + if (player->mo->health > 1) + S_StartSound(player->mo, sfx_cdpcm9); +} + static void K_HandleTumbleBounce(player_t *player) { player->tumbleBounces++; @@ -4609,6 +4625,8 @@ static void K_HandleTumbleBounce(player_t *player) } } + K_BumperInflate(player); + // A bit of damage hitlag. // This gives a window for DI!! K_AddHitLag(player->mo, 3, true); @@ -8794,6 +8812,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->hyudorotimer) player->hyudorotimer--; + if (player->bumperinflate && player->mo->hitlag == 0) + { + fixed_t thrustdelta = MAXCOMBOTHRUST - MINCOMBOTHRUST; + fixed_t floatdelta = MAXCOMBOFLOAT - MINCOMBOFLOAT; + + fixed_t thrustpertic = thrustdelta / MAXCOMBOTIME; + fixed_t floatpertic = floatdelta / MAXCOMBOTIME; + + fixed_t totalthrust = thrustpertic * player->progressivethrust + MINCOMBOTHRUST; + fixed_t totalfloat = floatpertic * player->progressivethrust + MINCOMBOFLOAT; + + if (player->speed > K_GetKartSpeed(player, false, false)) + totalthrust = 0; + + if (player->tumbleBounces && player->tumbleBounces <= TUMBLEBOUNCES) + { + player->mo->momz += totalfloat; + P_Thrust(player->mo, K_MomentumAngle(player->mo), totalthrust/2); + } + else + { + P_Thrust(player->mo, K_MomentumAngle(player->mo), totalthrust); + } + + player->bumperinflate--; + } + if (player->ringvolume < MINRINGVOLUME) player->ringvolume = MINRINGVOLUME; else if (MAXRINGVOLUME - player->ringvolume < RINGVOLUMEREGEN) @@ -8900,12 +8945,16 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->spinouttimer || player->tumbleBounces) { + if (player->progressivethrust < MAXCOMBOTIME) + player->progressivethrust++; if (player->incontrol > 0) player->incontrol = 0; player->incontrol--; } else { + if (player->progressivethrust) + player->progressivethrust--; if (player->incontrol < 0) player->incontrol = 0; player->incontrol++; @@ -8914,6 +8963,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->incontrol = min(player->incontrol, 5*TICRATE); player->incontrol = max(player->incontrol, -5*TICRATE); + if (player->incontrol == 3*TICRATE) + player->pitblame = -1; + if (P_PlayerInPain(player) || player->respawn.state != RESPAWNST_NONE) { player->lastpickuptype = -1; // got your ass beat, go grab anything diff --git a/src/k_kart.h b/src/k_kart.h index bb532c432..dd4828c20 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -37,6 +37,12 @@ Make sure this matches the actual number of states #define INSTAWHIP_TETHERBLOCK (TICRATE*4) #define PUNISHWINDOW (7*TICRATE/10) +#define MAXCOMBOTHRUST (mapobjectscale*20) +#define MAXCOMBOFLOAT (mapobjectscale*10) +#define MINCOMBOTHRUST (mapobjectscale*2) +#define MINCOMBOFLOAT (mapobjectscale*1) +#define MAXCOMBOTIME (TICRATE*4) + #define FLAMESHIELD_MAX (120) #define RR_PROJECTILE_FUSE (8*TICRATE) @@ -257,6 +263,8 @@ boolean K_IsPlayingDisplayPlayer(player_t *player); boolean K_PlayerCanPunt(player_t *player); void K_MakeObjectReappear(mobj_t *mo); +void K_BumperInflate(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index bda0f8b58..bde4e2469 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -257,8 +257,14 @@ static int player_get(lua_State *L) lua_pushboolean(L, plr->flipDI); else if (fastcmp(field,"markedfordeath")) lua_pushboolean(L, plr->markedfordeath); + else if (fastcmp(field,"incontrol")) + lua_pushboolean(L, plr->incontrol); + else if (fastcmp(field,"progressivethrust")) + lua_pushboolean(L, plr->progressivethrust); else if (fastcmp(field,"dotrickfx")) lua_pushboolean(L, plr->dotrickfx); + else if (fastcmp(field,"bumperinflate")) + lua_pushboolean(L, plr->bumperinflate); else if (fastcmp(field,"ringboxdelay")) lua_pushinteger(L, plr->ringboxdelay); else if (fastcmp(field,"ringboxaward")) @@ -351,6 +357,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->topAccel); else if (fastcmp(field,"instaWhipCharge")) lua_pushinteger(L, plr->instaWhipCharge); + else if (fastcmp(field,"pitblame")) + lua_pushinteger(L, plr->pitblame); else if (fastcmp(field,"defenseLockout")) lua_pushinteger(L, plr->defenseLockout); else if (fastcmp(field,"oldGuard")) @@ -781,10 +789,16 @@ static int player_set(lua_State *L) plr->justDI = luaL_checkinteger(L, 3); else if (fastcmp(field,"flipDI")) plr->flipDI = luaL_checkboolean(L, 3); + else if (fastcmp(field,"incontrol")) + plr->incontrol = luaL_checkinteger(L, 3); + else if (fastcmp(field,"progressivethrust")) + plr->progressivethrust = luaL_checkboolean(L, 3); else if (fastcmp(field,"markedfordeath")) plr->markedfordeath = luaL_checkboolean(L, 3); else if (fastcmp(field,"dotrickfx")) plr->dotrickfx = luaL_checkboolean(L, 3); + else if (fastcmp(field,"bumperinflate")) + plr->bumperinflate = luaL_checkboolean(L, 3); else if (fastcmp(field,"ringboxdelay")) plr->ringboxdelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"ringboxaward")) @@ -877,6 +891,8 @@ static int player_set(lua_State *L) plr->topAccel = luaL_checkinteger(L, 3); else if (fastcmp(field,"instaWhipCharge")) plr->instaWhipCharge = luaL_checkinteger(L, 3); + else if (fastcmp(field,"pitblame")) + plr->pitblame = luaL_checkinteger(L, 3); else if (fastcmp(field,"defenseLockout")) plr->defenseLockout = luaL_checkinteger(L, 3); else if (fastcmp(field,"oldGuard")) diff --git a/src/menus/class-egg-tv/EggTVData.cpp b/src/menus/class-egg-tv/EggTVData.cpp index 78078e9c5..f8fdd66c2 100644 --- a/src/menus/class-egg-tv/EggTVData.cpp +++ b/src/menus/class-egg-tv/EggTVData.cpp @@ -237,7 +237,7 @@ EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) if (info.gametype == GT_RACE) { - gametype_ = Gametype(GT_RACE, Gametype::Race { + gametype_ = Gametype(GT_RACE, info.gp, Gametype::Race { info.numlaps, kartspeed_cons_t[(info.kartspeed & ~(DF_ENCORE)) + 1].strvalue, (info.kartspeed & DF_ENCORE) != 0, @@ -245,7 +245,7 @@ EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) } else { - gametype_ = Gametype(info.gametype); + gametype_ = Gametype(info.gametype, info.gp); } for (const auto& data : info.standings) diff --git a/src/menus/class-egg-tv/EggTVData.hpp b/src/menus/class-egg-tv/EggTVData.hpp index b898ee45a..fa7491ded 100644 --- a/src/menus/class-egg-tv/EggTVData.hpp +++ b/src/menus/class-egg-tv/EggTVData.hpp @@ -193,12 +193,12 @@ public: }; explicit Gametype() {} - explicit Gametype(INT16 gt, Race race) : gametype_(get(gt)), var_(race) {} - explicit Gametype(INT16 gt) : gametype_(get(gt)) {} + explicit Gametype(INT16 gt, bool gp) : gametype_(get(gt)), name_(get_name(gt, gp)) {} + explicit Gametype(INT16 gt, bool gp, Race race) : Gametype(gt, gp) { var_ = race; } bool valid() const { return gametype_; } - std::string_view name() const { return valid() ? gametype_->name : ""; } + std::string_view name() const { return name_; } UINT32 rules() const { return valid() ? gametype_->rules : 0u; } bool ranks_time() const { return !ranks_points(); } @@ -208,8 +208,29 @@ public: private: const gametype_t* gametype_ = nullptr; + std::string_view name_; std::variant var_; + std::string_view get_name(INT16 gt, bool gp) const + { + if (!valid()) + { + return ""; + } + + if (gt == GT_SPECIAL) + { + return "Sealed Star"; + } + + if ((rules() & GTR_PRISONS) && gp) + { + return "Prison Break"; + } + + return gametype_->name; + } + static gametype_t* get(INT16 gt) { return gt >= 0 && gt < numgametypes ? gametypes[gt] : nullptr; } }; diff --git a/src/menus/class-egg-tv/EggTVGraphics.hpp b/src/menus/class-egg-tv/EggTVGraphics.hpp index 643da3b45..cdfc404a2 100644 --- a/src/menus/class-egg-tv/EggTVGraphics.hpp +++ b/src/menus/class-egg-tv/EggTVGraphics.hpp @@ -92,10 +92,9 @@ public: std::unordered_map gametype = { {"Race", "RHGT1"}, {"Battle", "RHGT2"}, - - // TODO: requires support in the demo format - //{"Prisons", "RHGT3"}, - //{"Special", "RHGT4"}, + {"Prison Break", "RHGT3"}, + {"Sealed Star", "RHGT4"}, + {"Versus", "RHGT5"}, }; patch fav = "RHFAV"; diff --git a/src/p_inter.c b/src/p_inter.c index bba888358..5dae5dc82 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2502,7 +2502,17 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS)) { - player->mo->health--; + if ((player->pitblame > -1) && (player->pitblame < MAXPLAYERS) + && (playeringame[player->pitblame]) && (!players[player->pitblame].spectator) + && (players[player->pitblame].mo) && (!P_MobjWasRemoved(players[player->pitblame].mo))) + { + P_DamageMobj(player->mo, players[player->pitblame].mo, players[player->pitblame].mo, 1, DMG_KARMA); + player->pitblame = -1; + } + else if (player->mo->health > 1 || battleprisons) + { + player->mo->health--; + } } if (player->mo->health <= 0) @@ -3134,6 +3144,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } } + if (source && source != player->mo && source->player) + { + if (damagetype != DMG_DEATHPIT) + { + player->pitblame = source->player - players; + } + } + player->sneakertimer = player->numsneakers = 0; player->driftboost = player->strongdriftboost = 0; player->gateBoost = 0; @@ -3143,6 +3161,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->preventfailsafe = TICRATE*3; player->pflags &= ~PF_GAINAX; Obj_EndBungee(player); + K_BumperInflate(target->player); if (player->spectator == false && !(player->charflags & SF_IRONMAN)) { @@ -3167,6 +3186,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da //P_KillPlayer(player, inflictor, source, damagetype); } + // Death save! On your last hit, no matter what, demote to weakest damage type for one last escape chance. + if (player->mo->health == 2 && damage && gametyperules & GTR_BUMPERS) + { + S_StartSound(target, sfx_gshc7); + player->flashing = TICRATE; + type = DMG_STUMBLE; + } + switch (type) { case DMG_STING: diff --git a/src/p_map.c b/src/p_map.c index 67c7ff622..f1a20876c 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -4064,6 +4064,15 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) P_PlayerHitBounceLine(bestslideline, &result->normal); mo->eflags |= MFE_JUSTBOUNCEDWALL; + // 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); + } + mo->momx = tmxmove; mo->momy = tmymove; mo->player->cmomx = tmxmove; diff --git a/src/p_mobj.c b/src/p_mobj.c index b40befd37..6bd3924fc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6235,6 +6235,18 @@ static void P_MobjSceneryThink(mobj_t *mobj) // Shrink your items if the player shrunk too. P_SetScale(mobj, mobj->target->scale); + if (mobj->target->player->bumperinflate && bumpers > mobj->threshold) + { + mobj->frame |= FF_INVERT; + // This line sucks. Scale to player, plus up to 1.5x their size based on how long the combo you're in is. + P_SetScale(mobj, mobj->target->scale + (mobj->target->player->progressivethrust * 3 * mobj->target->scale / 2 / MAXCOMBOTIME)); + + } + else + { + mobj->frame &= ~FF_INVERT; + } + P_UnsetThingPosition(mobj); { const angle_t fa = ang >> ANGLETOFINESHIFT; diff --git a/src/p_saveg.c b/src/p_saveg.c index 1ed3801b0..0117f1fda 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -578,6 +578,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEMEM(save->p, players[i].public_key, PUBKEYLENGTH); + WRITESINT8(save->p, players[i].pitblame); + WRITEUINT8(save->p, players[i].instaWhipCharge); WRITEUINT8(save->p, players[i].defenseLockout); WRITEUINT8(save->p, players[i].oldGuard); @@ -590,9 +592,11 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEANGLE(save->p, players[i].besthanddirection); WRITEINT16(save->p, players[i].incontrol); + WRITEUINT16(save->p, players[i].progressivethrust); WRITEUINT8(save->p, players[i].markedfordeath); WRITEUINT8(save->p, players[i].dotrickfx); + WRITEUINT8(save->p, players[i].bumperinflate); WRITEUINT8(save->p, players[i].ringboxdelay); WRITEUINT8(save->p, players[i].ringboxaward); @@ -1153,6 +1157,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) READMEM(save->p, players[i].public_key, PUBKEYLENGTH); + players[i].pitblame = READSINT8(save->p); + players[i].instaWhipCharge = READUINT8(save->p); players[i].defenseLockout = READUINT8(save->p); players[i].oldGuard = READUINT8(save->p); @@ -1165,9 +1171,11 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].besthanddirection = READANGLE(save->p); players[i].incontrol = READINT16(save->p); + players[i].progressivethrust = READUINT16(save->p); players[i].markedfordeath = READUINT8(save->p); players[i].dotrickfx = READUINT8(save->p); + players[i].bumperinflate = READUINT8(save->p); players[i].ringboxdelay = READUINT8(save->p); players[i].ringboxaward = READUINT8(save->p); diff --git a/src/r_debug.hpp b/src/r_debug.hpp new file mode 100644 index 000000000..1910912c0 --- /dev/null +++ b/src/r_debug.hpp @@ -0,0 +1,24 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef r_debug_hpp +#define r_debug_hpp + +#include "doomtype.h" + +namespace srb2::r_debug +{ + +void add_texture_to_frame_list(INT32 texnum); +void clear_frame_list(); +void draw_frame_list(); + +}; // namespace srb2::r_debug + +#endif/*r_debug_hpp*/ diff --git a/src/r_debug_printer.cpp b/src/r_debug_printer.cpp new file mode 100644 index 000000000..728bf29ee --- /dev/null +++ b/src/r_debug_printer.cpp @@ -0,0 +1,67 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include + +#include "r_debug.hpp" +#include "v_draw.hpp" + +#include "doomdef.h" +#include "doomtype.h" +#include "r_textures.h" +#include "screen.h" + +using srb2::Draw; + +namespace +{ + +std::unordered_set frame_list; + +}; // namespace + +namespace srb2::r_debug +{ + +void add_texture_to_frame_list(INT32 texnum) +{ + if (cht_debug & DBG_RENDER) + { + frame_list.insert(texnum); + } +} + +void clear_frame_list() +{ + frame_list.clear(); +} + +void draw_frame_list() +{ + if (!(cht_debug & DBG_RENDER)) + { + return; + } + + Draw line = Draw(4, BASEVIDHEIGHT - 20) + .font(Draw::Font::kConsole) + .scale(0.5) + .flags(V_ORANGEMAP | V_SNAPTOLEFT | V_SNAPTOBOTTOM); + + line.y(-4 * static_cast(frame_list.size() - 1)).size(32, 4 * frame_list.size()).fill(31); + + for (INT32 texnum : frame_list) + { + line.text("{}", std::string_view {textures[texnum]->name, 8}); + line = line.y(-4); + } +} + +}; // namespace srb2::r_debug diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 6f0ca5224..60012fe80 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -35,6 +35,7 @@ #include "core/memory.h" #include "core/thread_pool.h" #include "k_terrain.h" +#include "r_debug.hpp" extern "C" consvar_t cv_debugfinishline; @@ -2064,7 +2065,13 @@ void R_StoreWallRange(INT32 start, INT32 stop) auto get_flat_tex = [](INT32 texnum) { texnum = R_GetTextureNum(texnum); - return textures[texnum]->holes ? 0 : texnum; // R_DrawWallColumn cannot render holey textures + if (textures[texnum]->holes) + { + srb2::r_debug::add_texture_to_frame_list(texnum); + // R_DrawWallColumn cannot render holey textures + return R_GetTextureNum(R_CheckTextureNumForName("TRANSER1")); + } + return texnum; }; if (!backsector) diff --git a/src/sounds.c b/src/sounds.c index 828f67242..1339fd384 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1448,7 +1448,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"gshc4", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"gshc5", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"gshc6", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, - {"gshc7", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"gshc7", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, //x8away {"gshc8", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"gshc9", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"gshca", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},