From 474a59ab7ca5d9afbe8b0e1df08d3da7983fe938 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 28 Dec 2025 18:03:06 -0500 Subject: [PATCH] Add "Lunatic" and "Maniac" modes DUDE TOUHOU LMAO Jokes aside: * Lunatic = Master difficulty with modifications to make the races significantly more difficult: * Introduces a "lunaticmode" boolean to grandprixinfo; the demoversion has been upped to 0x0010 due to this * The bot modifier is, at MINIMUM, 2.0, making them aggressive as hell; Rival bots use a 2.5 modifier * Bump Spark is always off in this mode * RUNNERAUGMENT results have their distances significantly shortened; if a rival bot takes the lead, this distance is shortened even FURTHER so they don't frontrun against the human player endlessly * Alt. Invinc shows up earlier as a sort of "mercy" for human players; it would otherwise not show up until the race was effectively over * Maniac = Nightmare difficulty (Master at Expert speed) with Lunatic's changes --- src/command.c | 2 ++ src/command.h | 2 ++ src/d_main.cpp | 23 +++++++++++++++++- src/d_netcmd.c | 22 ++++++++++++++++- src/g_demo.c | 15 ++++++++++-- src/k_bot.cpp | 17 +++++++------ src/k_bot.h | 6 +++++ src/k_grandprix.h | 1 + src/k_items.c | 62 +++++++++++++++++++++++++++++++++++++++++++---- src/k_items.h | 1 + src/k_kart.c | 10 +++++++- src/m_menu.c | 10 ++++++++ src/p_setup.c | 4 ++- 13 files changed, 157 insertions(+), 18 deletions(-) diff --git a/src/command.c b/src/command.c index 50d7756b9..2b9a5d94b 100644 --- a/src/command.c +++ b/src/command.c @@ -97,6 +97,8 @@ CV_PossibleValue_t gpdifficulty_cons_t[] = { {KARTSPEED_EXPERT, "Expert"}, {KARTGP_MASTER, "Master"}, {KARTGP_NIGHTMARE, "Nightmare"}, + {KARTGP_LUNATIC, "Lunatic"}, + {KARTGP_MANIAC, "Maniac"}, {0, NULL} }; diff --git a/src/command.h b/src/command.h index 345be77df..bc6900756 100644 --- a/src/command.h +++ b/src/command.h @@ -187,6 +187,8 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTSPEED_EXPERT 3 #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots +#define KARTGP_LUNATIC 6 // Not a speed setting; Master difficulty with some... modifications. +#define KARTGP_MANIAC 7 // Not a speed setting; Nightmare difficulty with some... modifications. extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; extern consvar_t cv_execversion; diff --git a/src/d_main.cpp b/src/d_main.cpp index 319bc1e34..2d408e171 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1957,6 +1957,7 @@ void D_SRB2Main(void) grandprixinfo.gamespeed = KARTSPEED_HARD; grandprixinfo.encore = false; grandprixinfo.masterbots = false; + grandprixinfo.lunaticmode = false; grandprixinfo.gp = true; grandprixinfo.roundnum = 0; @@ -2121,7 +2122,7 @@ void D_SRB2Main(void) if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match { j = atoi(sskill); // assume they gave us a skill number, which is okay too - if (j >= KARTSPEED_EASY && j <= KARTGP_NIGHTMARE) + if (j >= KARTSPEED_EASY && j <= KARTGP_MANIAC) newskill = (INT16)j; } @@ -2137,6 +2138,18 @@ void D_SRB2Main(void) grandprixinfo.masterbots = true; newskill = KARTSPEED_EXPERT; } + else if (newskill == KARTGP_LUNATIC) + { + grandprixinfo.masterbots = true; + grandprixinfo.lunaticmode = true; + newskill = KARTSPEED_HARD; + } + else if (newskill == KARTGP_MANIAC) + { + grandprixinfo.masterbots = true; + grandprixinfo.lunaticmode = true; + newskill = KARTSPEED_EXPERT; + } grandprixinfo.gamespeed = newskill; } @@ -2148,6 +2161,14 @@ void D_SRB2Main(void) { newskill = KARTSPEED_EXPERT; } + else if (newskill == KARTGP_LUNATIC) + { + newskill = KARTSPEED_HARD; + } + else if (newskill == KARTGP_MANIAC) + { + newskill = KARTSPEED_EXPERT; + } if (newskill != -1) CV_SetValue(&cv_kartspeed, newskill); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a21fc6abb..bdf5f2be8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3554,6 +3554,7 @@ static void Command_Map_f(void) { grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); grandprixinfo.masterbots = false; + grandprixinfo.lunaticmode = false; if (option_skill) { @@ -3573,7 +3574,7 @@ static void Command_Map_f(void) if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match { INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too - if (num >= KARTSPEED_EASY && num <= KARTGP_NIGHTMARE) + if (num >= KARTSPEED_EASY && num <= KARTGP_MANIAC) newskill = (INT16)num; } @@ -3589,6 +3590,18 @@ static void Command_Map_f(void) grandprixinfo.gamespeed = KARTSPEED_EXPERT; grandprixinfo.masterbots = true; } + else if (newskill == KARTGP_LUNATIC) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + grandprixinfo.lunaticmode = true; + } + else if (newskill == KARTGP_MANIAC) + { + grandprixinfo.gamespeed = KARTSPEED_EXPERT; + grandprixinfo.masterbots = true; + grandprixinfo.lunaticmode = true; + } else { grandprixinfo.gamespeed = newskill; @@ -8483,6 +8496,13 @@ static void KartBumpSpark_OnChange(void) return; } + if (grandprixinfo.lunaticmode) + { + CONS_Printf("No, no - lunatics don't need Bump Spark!\n"); + bumpsparkactive = 0; + return; + } + if (leveltime < starttime) { CONS_Printf(M_GetText("Bump spark type has been changed to \"%s\".\n"), cv_kartbumpspark.string); diff --git a/src/g_demo.c b/src/g_demo.c index f3a8ebdc5..d12f218ca 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -110,7 +110,7 @@ demoghost *ghosts = NULL; // DEMO RECORDING // -#define DEMOVERSION 0x000F +#define DEMOVERSION 0x0010 #define DEMOHEADER "\xF0" "BlanReplay" "\x0F" #define DF_GHOST 0x01 // This demo contains ghost data too! @@ -268,6 +268,7 @@ typedef struct struct // DF_GRANDPRIX { boolean masterbots; + boolean lunaticmode; UINT8 gamespeed; UINT8 eventmode; } grandprix; @@ -704,6 +705,7 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) boolean rapreset = true; // + extended serverinfo length boolean dubs = true; // Multiple voices boolean availabilities = true; // Store player availabilities + boolean gplunatic = true; // Grand Prix: Lunatic mode INT32 i; // these may not be present in old demo formats, so initialize them @@ -745,6 +747,9 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) /* FALLTHRU */ case 0x000E: availabilities = false; + /* FALLTHRU */ + case 0x000F: + gplunatic = false; break; default: // too old, cannot support. @@ -754,7 +759,7 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) else if (!memcmp(startdp, "\xF0" "KartReplay" "\x0F", 12)) { dubs = rapreset = raflag = false; - serverinfo = availabilities = false; + gplunatic = serverinfo = availabilities = false; switch (header->demoversion) { case 0x0001: // SRB2Kart 1.0.x (only staff ghosts supported) @@ -929,6 +934,10 @@ skipfiles: { header->grandprix.gamespeed = READUINT8(dp); header->grandprix.masterbots = READUINT8(dp) != 0; + + if (gplunatic) + header->grandprix.lunaticmode = READUINT8(dp) != 0; + header->grandprix.eventmode = READUINT8(dp); } @@ -2957,6 +2966,7 @@ void G_BeginRecording(void) { WRITEUINT8(demobuf.p, grandprixinfo.gamespeed); WRITEUINT8(demobuf.p, grandprixinfo.masterbots == true); + WRITEUINT8(demobuf.p, grandprixinfo.lunaticmode == true); WRITEUINT8(demobuf.p, grandprixinfo.eventmode); } @@ -3800,6 +3810,7 @@ void G_DoPlayDemo(char *defdemoname) grandprixinfo.gp = true; grandprixinfo.gamespeed = header.grandprix.gamespeed; grandprixinfo.masterbots = header.grandprix.masterbots; + grandprixinfo.lunaticmode = header.grandprix.lunaticmode; grandprixinfo.eventmode = header.grandprix.eventmode; } diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 92342b30a..6815059ce 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -578,6 +578,10 @@ fixed_t K_BotMapModifier(void) // fuck it we ball //return 5*FRACUNIT/10; // ...with a bit of customization + + if (grandprixinfo.lunaticmode == true) + return std::max(static_cast(BASELUNATICSPEEDMOD), K_TrackModifierMax()); + return K_TrackModifierMax(); #if 0 @@ -689,13 +693,12 @@ fixed_t K_BotRubberband(const player_t *player) ); // +/- x0.35 - const fixed_t rubberStretchiness = FixedMul( - FixedDiv( - 35 * FRACUNIT / 100, - K_GetKartGameSpeedScalar(gamespeed) - ), - K_BotMapModifier() - ); + const fixed_t rubberStretchiness = + FixedMul(FixedDiv(35 * FRACUNIT / 100, K_GetKartGameSpeedScalar(gamespeed)), + ((grandprixinfo.lunaticmode == true) && (player->botvars.rival == true)) + ? std::max(BASELUNATICRIVALSPEEDMOD, + K_BotMapModifier()) // The rival is faster on Lunatic mode. + : K_BotMapModifier()); // Lv. 1: x0.4 min // Lv. MAX: x0.85 min diff --git a/src/k_bot.h b/src/k_bot.h index dcc0e43e8..3e4998352 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -57,6 +57,12 @@ extern consvar_t cv_botdrifting; #define MAXDRIFTSKILL (FRACUNIT/2) +// Minimum bot complexity for Lunatic mode. +#define BASELUNATICSPEEDMOD (2 * FRACUNIT) + +// 2.5 +#define BASELUNATICRIVALSPEEDMOD (5 * FRACUNIT / 2) + typedef enum { DRIFTSTATE_AUTO, diff --git a/src/k_grandprix.h b/src/k_grandprix.h index 4e59fd721..f7c13d5b6 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -32,6 +32,7 @@ extern struct grandprixinfo UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars boolean encore; ///< Ditto, but for encore mode boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode) + boolean lunaticmode; ///< If true, make this GP especially difficult (Lunatic Mode) boolean initalize; ///< If true, we need to initialize a new session. boolean wonround; ///< If false, then we retry the map instead of going to the next. UINT8 eventmode; ///< See GPEVENT_ constants diff --git a/src/k_items.c b/src/k_items.c index 8ffa1e288..6a7746285 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -37,6 +37,7 @@ #include "k_kart.h" #include "k_waypoint.h" #include "k_director.h" +#include "k_grandprix.h" #include "k_cluster.hpp" #include "k_itemlist.hpp" #include "k_items.h" @@ -621,6 +622,8 @@ void K_SetIndirectItemCooldown(tic_t cooldown) } } +#define LUNATIC_RUNNERAUG_CRUNCHER (29127) // FRACUNIT / 2.25 + static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *forceme) { INT32 newodds; @@ -791,8 +794,23 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * if ((flags & KRF_RUNNERAUGMENT) && (result->augcvar[aug_idx] != NULL)) { // These odds get stronger as 1st's frontrun increases. - const INT32 distFromStart = max(secondToFirst - result->augcvar[aug_idx]->value, 0); - const INT32 distRange = (7 * result->augcvar[aug_idx]->value / 2) - result->augcvar[aug_idx]->value; + INT32 runner_distval = result->augcvar[aug_idx]->value; + + if (grandprixinfo.lunaticmode == true) + { + // Lunatic Mode: Divide this distance by 2.25 for + // aggressive frontrun prevention. + runner_distval = FixedMul(runner_distval, LUNATIC_RUNNERAUG_CRUNCHER); + } + + if (roulette->rival_frontrunner == true) + { + // The rival is frontrunning. Be *especially* vicious against them! + runner_distval = (runner_distval / 2); + } + + const INT32 distFromStart = max(secondToFirst - runner_distval, 0); + const INT32 distRange = (7 * runner_distval / 2) - runner_distval; const INT32 mulMax = 24; INT32 multiplier = (distFromStart * mulMax) / distRange; @@ -861,6 +879,8 @@ static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 * return newodds; } +#undef LUNATIC_RUNNERAUG_CRUNCHER + void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]) { // Reset forceme @@ -883,6 +903,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b UINT8 distlen = 0; boolean oddsvalid[MAXODDS]; boolean rivalodds = false; + boolean rivalrunner = false; // Unused now, oops :V (void)bestbumper; @@ -893,6 +914,12 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b rivalodds = true; } + if (player->bot && player->botvars.rival && player->position <= 1) + { + // A rival is frontrunning! + rivalrunner = true; + } + INT32 itemodds[MAXKARTRESULTS]; kartroulette_t roulette = { .pdis = pdis, @@ -904,6 +931,7 @@ UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 b .spbrush = spbrush, .bot = player->bot, .rival = rivalodds, + .rival_frontrunner = rivalrunner, .inBottom = K_IsPlayerLosing(player), }; @@ -1466,7 +1494,8 @@ void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) .mashed = mashed, .spbrush = spbrush, .bot = player->bot, - .rival = player->bot && player->botvars.rival, + .rival = ((player->bot && player->botvars.rival) || (K_IsAltShrunk(player))), + .rival_frontrunner = ((player->bot && player->botvars.rival) && (player->position <= 1)), .inBottom = K_IsPlayerLosing(player), }; @@ -1535,7 +1564,16 @@ void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force) INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme) { (void)result; - odds = K_KartGetInvincibilityOdds(roulette->clusterDist); + + UINT32 cdist = roulette->clusterDist; + + if (grandprixinfo.lunaticmode) + { + // I'm tired, boss. + cdist = (9 * cdist / 5); + } + + odds = K_KartGetInvincibilityOdds(cdist); // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, if (odds >= INVFORCEODDS) @@ -1565,12 +1603,26 @@ INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresul odds = 0; } + UINT32 raceforce_pdis = roulette->pdis; + + if (grandprixinfo.lunaticmode == true) + { + // Multiply by 2.25 + raceforce_pdis = FixedMul(raceforce_pdis, 9 * FRACUNIT / 2); + } + + if (roulette->rival_frontrunner == true) + { + // Frontrunning rival? Multiply by 1.5 + raceforce_pdis = FixedMul(raceforce_pdis, 3 * FRACUNIT / 2); + } + // No forced SPB in 1v1s, it has to be randomly rolled if (roulette->pingame <= 2) { *forceme = 0; } - else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) + else if (K_RaceForceSPB(roulette->playerpos, raceforce_pdis) && spbplace == -1 && K_GetKartResult("selfpropelledbomb")->cooldown == 0) { // Force SPB onto 2nd if they get too far behind. diff --git a/src/k_items.h b/src/k_items.h index fcd45b1e5..951489baa 100644 --- a/src/k_items.h +++ b/src/k_items.h @@ -155,6 +155,7 @@ struct kartroulette_t boolean spbrush; boolean bot; boolean rival; + boolean rival_frontrunner; // Is a Rival bot frontrunning? boolean inBottom; // output: which results are being forced into a player's item slot for one reason or another. Higher value = higher priority. diff --git a/src/k_kart.c b/src/k_kart.c index c40e63697..32dfe7ff7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2532,7 +2532,15 @@ UINT16 K_GetInvincibilityTime(player_t *player) distmul = LEGACYALTINVINMUL; } - fixed_t clustermul = K_InvincibilityEasing(FixedMul(player->distancefromcluster,distmul)); + UINT32 cdist = player->distancefromcluster; + + if (grandprixinfo.lunaticmode) + { + // I'm tired, boss. + cdist = (9 * cdist / 5); + } + + fixed_t clustermul = K_InvincibilityEasing(FixedMul(cdist,distmul)); UINT16 invintics = FixedMul(BASEINVINTIME, clustermul); return max(MININVINTIME, invintics); diff --git a/src/m_menu.c b/src/m_menu.c index 475fbdd60..5f938fc6f 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -6259,6 +6259,16 @@ INT32 MR_StartGrandPrix(INT32 choice) grandprixinfo.gamespeed = KARTSPEED_EXPERT; grandprixinfo.masterbots = true; break; + case KARTGP_LUNATIC: + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.lunaticmode = true; + grandprixinfo.masterbots = true; + break; + case KARTGP_MANIAC: + grandprixinfo.gamespeed = KARTSPEED_EXPERT; + grandprixinfo.lunaticmode = true; + grandprixinfo.masterbots = true; + break; default: CONS_Alert(CONS_WARNING, "Invalid GP difficulty\n"); grandprixinfo.gamespeed = KARTSPEED_NORMAL; diff --git a/src/p_setup.c b/src/p_setup.c index 3b2392ffd..85fb9de83 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8230,7 +8230,9 @@ static void P_InitLevelSettings(boolean reloadinggamestate) if (cv_itemlist.value) itemlistactive = true; - bumpsparkactive = (UINT8)cv_kartbumpspark.value; + // Lunatics don't need Bump Spark! + if (grandprixinfo.lunaticmode == false) + bumpsparkactive = (UINT8)cv_kartbumpspark.value; antibumptime = (tic_t)cv_kartantibump.value * TICRATE;