Support alternate gameplay events during GP

- Implementation details:
    - grandprixinfo.eventmode is the reference point
    - All bots have spectator applied and removed at map start depending on eventmode, and I've done my best to guard against side effects of not removing them entirely
    - You shouldn't turn off grandprixinfo.gp when turning on things like specialStage.active or bossinfo.boss when pursuing eventmode behaviour
    - Probably needs to be integrated into XD_MAP for any future netplay support, is currently disabled.
    - You technically don't have to assign a Capsules map to be the bonus and a Special Stage to be the special. A Capsules map can be assigned to a Special Stage too, and a Boss can be assigned to either of them.
    - Special Stages are still just as incomplete as they were before.
- Break the Capsules has special behaviour.
   - Timelimit starts at 20 seconds.
   - Earn 10 seconds (plus a little extra cheaty time) every capsule you destroy.
   - WIN + extra life if you bust all the capsules, COOL if you get some but run out of time, LOSE if you lose your bumper or run out of time without breaking a single capsule.
   - Supposed to also give you rings, but ran into a LOT of difficulty with this and didn't want to commit half-baked stuff, so it'll be a later project.
Also:
- Fix a long standing bug where totalring was reset between maps, preventing the sum from adding up across GP rounds and depriving you of extra lives you were owed.
- Fix an issue where Break the Capsules record attack was KARTSPEED_HARD.
- Send timelimitintics in savegames, since it's handled seperately now.
This commit is contained in:
toaster 2022-10-14 18:34:34 +01:00 committed by GenericHeroGuy
parent b26d2b29de
commit 2939f63418
7 changed files with 161 additions and 34 deletions

View file

@ -2891,6 +2891,10 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r
{
pencoremode = bossinfo.encore;
}
else if (specialStage.active == true)
{
pencoremode = specialStage.encore;
}
else if (grandprixinfo.gp == true)
{
pencoremode = grandprixinfo.encore;

View file

@ -59,6 +59,7 @@
#include "k_color.h"
#include "k_grandprix.h"
#include "k_boss.h"
#include "k_specialstage.h"
#include "k_bot.h"
#include "k_odds.h"
#include "doomstat.h"
@ -2837,7 +2838,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
starposttime = 0;
prevcheck = 0;
nextcheck = 0;
xtralife = 0;
for (i = 0; i < LAP__MAX; i++)
{
@ -4397,20 +4397,104 @@ static INT16 G_GetNextMap(boolean advancemap)
}
else if (grandprixinfo.gp == true)
{
if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session
// G: oh dear, this whole GP block has loads of side effects...
// for now, just repeat the same map for "map +"
// you're not supposed to use that in GP anyways
if (!advancemap || // ...right?
grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session
{
newmap = curmap; // Same map
}
else
{
if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map
INT32 lastgametype = gametype;
// If we're in a GP event, don't immediately follow it up with another.
// I also suspect this will not work with online GP so I'm gonna prevent it right now.
// The server might have to communicate eventmode (alongside other GP data) in XD_MAP later.
if (netgame || grandprixinfo.eventmode != GPEVENT_NONE)
{
grandprixinfo.eventmode = GPEVENT_NONE;
G_SetGametype(GT_RACE);
if (gametype != lastgametype)
D_GameTypeChanged(lastgametype);
specialStage.active = false;
bossinfo.boss = false;
}
// Special stage
else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels)
{
INT16 totaltotalring = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (players[i].spectator)
continue;
if (players[i].bot)
continue;
totaltotalring += players[i].totalring;
}
if (totaltotalring >= 50)
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]
&& mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_SPECIAL|TOL_BOSS|TOL_BATTLE))
{
grandprixinfo.eventmode = GPEVENT_SPECIAL;
newmap = cupLevelNum;
}
}
}
else if (grandprixinfo.roundnum == (grandprixinfo.cup->numlevels+1)/2) // 3 for a 5-map cup
{
// todo any other condition?
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]
&& mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE))
{
grandprixinfo.eventmode = GPEVENT_BONUS;
newmap = cupLevelNum;
}
}
}
if (grandprixinfo.eventmode != GPEVENT_NONE)
{
// nextmap is set above
const INT32 newtol = mapheaderinfo[newmap]->typeoflevel;
if (newtol & TOL_SPECIAL)
{
specialStage.active = true;
specialStage.encore = grandprixinfo.encore;
}
else //(if newtol & (TOL_BATTLE|TOL_BOSS)) -- safe to assume??
{
G_SetGametype(GT_BATTLE);
if (gametype != lastgametype)
D_GameTypeChanged(lastgametype);
if (newtol & TOL_BOSS)
{
K_ResetBossInfo();
bossinfo.boss = true;
bossinfo.encore = grandprixinfo.encore;
}
}
}
else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map
{
newmap = NEXTMAP_CEREMONY; // ceremonymap
}
else
{
// Proceed to next map
const INT32 cupLevelNum =grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum];
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[grandprixinfo.roundnum];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum])
{

View file

@ -493,7 +493,7 @@ void K_BattleInit(UINT8 numPlayers)
{
if (modeattacking != ATTACKING_ITEMBREAK)
{
if (!cv_kartitembreaker.value)
if (K_CanChangeRules() && !cv_kartitembreaker.value)
goto afteritembreaker;
if (numPlayers > 1)

View file

@ -1642,7 +1642,15 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI
else
{
drawtime = timelimitintics - drawtime;
if (drawtime < 5*TICRATE)
if (secretextratime)
;
else if (extratimeintics)
{
jitter = 2;
if (leveltime & 1)
jitter = -jitter;
}
else if (drawtime < 5*TICRATE)
{
jitter = 1;
if (drawtime & 2)

View file

@ -3284,9 +3284,12 @@ void A_AttractChase(mobj_t *actor)
// Set attraction flag
actor->cusval = 1;
if ((actor->tracer->player->itemtype == KITEM_THUNDERSHIELD
if (
actor->tracer->player && actor->tracer->health
&& ((gametyperules & GTR_BUMPERS)
|| (actor->tracer->player->itemtype == KITEM_THUNDERSHIELD
&& RINGTOTAL(actor->tracer->player) < actor->tracer->player->ringmax
&& !(actor->tracer->player->pflags & PF_RINGLOCK))
&& !(actor->tracer->player->pflags & PF_RINGLOCK)))
//&& P_CheckSight(actor, actor->tracer)
)
{

View file

@ -1004,17 +1004,37 @@ void P_CheckTimeLimit(void)
{
INT32 i, k;
if (exitcountdown)
return;
if (!timelimitintics)
return;
if (!(multiplayer || netgame))
return;
if (!(gametyperules & GTR_TIMELIMIT))
return;
if (itembreaker)
return;
if (secretextratime)
{
secretextratime--;
timelimitintics++;
}
else if (extratimeintics)
{
timelimitintics++;
if (leveltime & 1)
;
else
{
if (extratimeintics > 20)
{
extratimeintics -= 20;
timelimitintics += 20;
}
else
{
timelimitintics += extratimeintics;
extratimeintics = 0;
}
S_StartSound(NULL, sfx_ptally);
}
}
if (leveltime < (timelimitintics + starttime))
return;
@ -1408,15 +1428,39 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
if (itembreaker)
{
// G: uncommented this for funsies
mobj_t * ring;
angle_t dir = 0;
if (inflictor)
dir = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y);
else
dir = R_PointToAngle2(source->x, source->y, target->x, target->y);
for (UINT8 i = 0; i < 2; i++)
{
dir += (ANGLE_MAX/3);
ring = P_SpawnMobj(target->x, target->y, target->z, MT_RING);
ring->angle = dir;
P_InstaThrust(ring, dir, 16*ring->scale);
ring->momz = 8 * target->scale * P_MobjFlip(target);
P_SetTarget(&ring->tracer, source);
source->player->pickuprings++;
}
target->flags2 |= MF2_BOSSFLEE;
target->flags2 |= MF2_DONTRESPAWN;
K_SpawnBattlePoints(source->player, NULL, 1);
// All targets busted!
if (++numtargets >= nummapboxes)
{
P_DoAllPlayersExit(0, true);
P_DoAllPlayersExit(0, (grandprixinfo.gp == true));
}
else if (timelimitintics)
{
S_StartSound(NULL, sfx_s221);
extratimeintics += 10*TICRATE;
secretextratime = TICRATE/2;
}
}
if (cv_itemrespawn.value && modeattacking == ATTACKING_NONE && !itembreaker)

View file

@ -8390,22 +8390,6 @@ static void P_InitGametype(void)
numlaps = K_RaceLapCount(gamemap - 1);
if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss)
{
if (K_CanChangeRules() == false)
{
timelimitintics = timelimits[gametype] * (60*TICRATE);
}
else
{
timelimitintics = cv_timelimit.value * (60*TICRATE);
}
}
else
{
timelimitintics = 0;
}
wantedcalcdelay = wantedfrequency*2;
indirectitemcooldown = 0;