RingRacers/src/k_battle.c
Sally Coolatta 57f53a5531 Search bonus maps to calculate total capsules
No more moving goalpost! This means we can put it on the intermission screen and not just the final rankings screen.
2023-03-06 18:26:07 -05:00

799 lines
16 KiB
C

/// \file k_battle.c
/// \brief SRB2Kart Battle Mode specific code
#include "k_battle.h"
#include "k_kart.h"
#include "doomtype.h"
#include "doomdata.h"
#include "g_game.h"
#include "p_mobj.h"
#include "p_local.h"
#include "p_setup.h"
#include "p_slopes.h" // P_GetZAt
#include "r_main.h"
#include "r_defs.h" // MAXFFLOORS
#include "info.h"
#include "s_sound.h"
#include "m_random.h"
#include "r_sky.h" // skyflatnum
#include "k_grandprix.h" // K_CanChangeRules
#include "k_boss.h" // bossinfo.valid
#include "p_spec.h"
#include "k_objects.h"
#include "k_rank.h"
// Battle overtime info
struct battleovertime battleovertime;
// Capsules mode enabled for this map?
boolean battlecapsules = false;
// box respawning in battle mode
INT32 nummapboxes = 0;
INT32 numgotboxes = 0;
// Capsule counters
UINT8 maptargets = 0; // Capsules in map
UINT8 numtargets = 0; // Capsules busted
INT32 K_StartingBumperCount(void)
{
if (battlecapsules)
return 1; // always 1 hit in Break the Capsules
return cv_kartbumpers.value;
}
boolean K_IsPlayerWanted(player_t *player)
{
UINT8 i = 0, nump = 0, numfirst = 0;
for (; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
nump++;
if (players[i].position > 1)
continue;
numfirst++;
}
return ((numfirst < nump) && !player->spectator && (player->position == 1));
}
void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount)
{
statenum_t st;
mobj_t *pt;
if (!source || !source->mo)
return;
if (amount == 1)
st = S_BATTLEPOINT1A;
else if (amount == 2)
st = S_BATTLEPOINT2A;
else if (amount == 3)
st = S_BATTLEPOINT3A;
else
return; // NO STATE!
pt = P_SpawnMobj(source->mo->x, source->mo->y, source->mo->z, MT_BATTLEPOINT);
P_SetTarget(&pt->target, source->mo);
P_SetMobjState(pt, st);
if (victim && victim->skincolor)
pt->color = victim->skincolor;
else
pt->color = source->skincolor;
if (encoremode)
pt->renderflags ^= RF_HORIZONTALFLIP;
}
void K_CheckBumpers(void)
{
UINT8 i;
UINT8 numingame = 0;
UINT32 toproundscore = 0;
UINT8 nobumpers = 0;
if (!(gametyperules & GTR_BUMPERS))
return;
if (gameaction == ga_completed)
return;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator) // not even in-game
continue;
if (players[i].exiting) // we're already exiting! stop!
return;
numingame++;
if (players[i].roundscore > toproundscore)
{
toproundscore = players[i].roundscore;
}
if (players[i].bumpers <= 0) // if you don't have any bumpers, you're probably not a winner
{
nobumpers++;
}
}
if (battlecapsules || bossinfo.valid)
{
if (nobumpers > 0 && nobumpers >= numingame)
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (players[i].spectator)
continue;
players[i].pflags |= PF_NOCONTEST;
P_DoPlayerExit(&players[i]);
}
}
return;
}
else
{
g_hiscore = toproundscore;
}
if (numingame <= 1)
{
if ((gametyperules & GTR_CAPSULES) && (K_CanChangeRules(true) == true))
{
// Reset map to turn on battle capsules
if (server)
D_MapChange(gamemap, gametype, encoremode, true, 0, false, false);
}
return;
}
}
void K_CheckEmeralds(player_t *player)
{
UINT8 i;
if (!(gametyperules & GTR_POWERSTONES))
{
return;
}
if (!ALLCHAOSEMERALDS(player->emeralds))
{
return;
}
player->roundscore++; // lol
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (&players[i] == player)
{
continue;
}
players[i].bumpers = 0;
}
K_CheckBumpers();
}
UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType)
{
switch (emeraldType)
{
case EMERALD_CHAOS1:
return SKINCOLOR_CHAOSEMERALD1;
case EMERALD_CHAOS2:
return SKINCOLOR_CHAOSEMERALD2;
case EMERALD_CHAOS3:
return SKINCOLOR_CHAOSEMERALD3;
case EMERALD_CHAOS4:
return SKINCOLOR_CHAOSEMERALD4;
case EMERALD_CHAOS5:
return SKINCOLOR_CHAOSEMERALD5;
case EMERALD_CHAOS6:
return SKINCOLOR_CHAOSEMERALD6;
case EMERALD_CHAOS7:
return SKINCOLOR_CHAOSEMERALD7;
default:
return SKINCOLOR_NONE;
}
}
mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType)
{
boolean validEmerald = true;
mobj_t *emerald = P_SpawnMobj(x, y, z, MT_EMERALD);
mobj_t *overlay;
P_Thrust(emerald,
FixedAngle(P_RandomFixed(PR_ITEM_ROULETTE) * 180) + angle,
24 * mapobjectscale);
emerald->momz = flip * 24 * mapobjectscale;
if (emerald->eflags & MFE_UNDERWATER)
emerald->momz = (117 * emerald->momz) / 200;
emerald->threshold = 10;
switch (emeraldType)
{
case EMERALD_CHAOS1:
case EMERALD_CHAOS2:
case EMERALD_CHAOS3:
case EMERALD_CHAOS4:
case EMERALD_CHAOS5:
case EMERALD_CHAOS6:
case EMERALD_CHAOS7:
emerald->color = K_GetChaosEmeraldColor(emeraldType);
break;
default:
CONS_Printf("Invalid emerald type %d\n", emeraldType);
validEmerald = false;
break;
}
if (validEmerald == true)
{
emerald->extravalue1 = emeraldType;
}
overlay = P_SpawnMobjFromMobj(emerald, 0, 0, 0, MT_OVERLAY);
P_SetTarget(&overlay->target, emerald);
P_SetMobjState(overlay, S_CHAOSEMERALD_UNDER);
overlay->color = emerald->color;
return emerald;
}
mobj_t *K_SpawnSphereBox(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 amount)
{
mobj_t *drop = P_SpawnMobj(x, y, z, MT_SPHEREBOX);
drop->angle = angle;
P_Thrust(drop,
FixedAngle(P_RandomFixed(PR_ITEM_ROULETTE) * 180) + angle,
P_RandomRange(PR_ITEM_ROULETTE, 4, 12) * mapobjectscale);
drop->momz = flip * 12 * mapobjectscale;
if (drop->eflags & MFE_UNDERWATER)
drop->momz = (117 * drop->momz) / 200;
drop->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
drop->extravalue1 = amount;
return drop;
}
void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType)
{
UINT8 i;
SINT8 flip = P_MobjFlip(player->mo);
for (i = 0; i < 14; i++)
{
UINT32 emeraldFlag = (1 << i);
if ((player->emeralds & emeraldFlag) && (emeraldFlag & emeraldType))
{
mobj_t *emerald = K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag);
P_SetTarget(&emerald->target, player->mo);
player->emeralds &= ~emeraldFlag;
}
}
}
UINT8 K_NumEmeralds(player_t *player)
{
UINT8 i;
UINT8 num = 0;
for (i = 0; i < 14; i++)
{
UINT32 emeraldFlag = (1 << i);
if (player->emeralds & emeraldFlag)
{
num++;
}
}
return num;
}
static inline boolean IsOnInterval(tic_t interval)
{
return ((leveltime - starttime) % interval) == 0;
}
void K_RunPaperItemSpawners(void)
{
const boolean overtime = (battleovertime.enabled >= 10*TICRATE);
const tic_t interval = BATTLE_SPAWN_INTERVAL;
const boolean canmakeemeralds = (gametyperules & GTR_POWERSTONES);
UINT32 emeraldsSpawned = 0;
UINT32 firstUnspawnedEmerald = 0;
thinker_t *th;
mobj_t *mo;
UINT8 pcount = 0;
INT16 i;
if (battlecapsules)
{
// Gametype uses paper items, but this specific expression doesn't
return;
}
if (leveltime < starttime)
{
// Round hasn't started yet!
return;
}
if (!IsOnInterval(interval))
{
return;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
emeraldsSpawned |= players[i].emeralds;
if ((players[i].exiting > 0 || (players[i].pflags & PF_ELIMINATED))
|| ((gametyperules & GTR_BUMPERS) && players[i].bumpers <= 0))
{
continue;
}
pcount++;
}
if (overtime == true)
{
SINT8 flip = 1;
// Just find emeralds, no paper spots
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo = (mobj_t *)th;
if (mo->type == MT_EMERALD)
{
emeraldsSpawned |= mo->extravalue1;
}
}
if (canmakeemeralds)
{
for (i = 0; i < 7; i++)
{
UINT32 emeraldFlag = (1 << i);
if (!(emeraldsSpawned & emeraldFlag))
{
firstUnspawnedEmerald = emeraldFlag;
break;
}
}
}
if (firstUnspawnedEmerald != 0)
{
K_SpawnChaosEmerald(
battleovertime.x, battleovertime.y, battleovertime.z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
firstUnspawnedEmerald
);
}
else
{
K_CreatePaperItem(
battleovertime.x, battleovertime.y, battleovertime.z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
0, 0
);
if (gametyperules & GTR_SPHERES)
{
K_SpawnSphereBox(
battleovertime.x, battleovertime.y, battleovertime.z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
10
);
}
}
}
else
{
if (pcount > 0)
{
#define MAXITEM 64
mobj_t *spotList[MAXITEM];
UINT8 spotMap[MAXITEM];
UINT8 spotCount = 0, spotBackup = 0, spotAvailable = 0;
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo = (mobj_t *)th;
if (mo->type == MT_EMERALD)
{
emeraldsSpawned |= mo->extravalue1;
}
if (mo->type == MT_MONITOR)
{
emeraldsSpawned |= Obj_MonitorGetEmerald(mo);
}
if (mo->type != MT_PAPERITEMSPOT)
continue;
if (spotCount >= MAXITEM)
continue;
if (Obj_ItemSpotIsAvailable(mo))
{
// spotMap first only includes spots
// where a monitor doesn't exist
spotMap[spotAvailable] = spotCount;
spotAvailable++;
}
spotList[spotCount] = mo;
spotCount++;
}
if (spotCount <= 0)
{
return;
}
if (canmakeemeralds)
{
for (i = 0; i < 7; i++)
{
UINT32 emeraldFlag = (1 << i);
if (!(emeraldsSpawned & emeraldFlag))
{
firstUnspawnedEmerald = emeraldFlag;
break;
}
}
}
//CONS_Printf("leveltime = %d ", leveltime);
if (spotAvailable > 0)
{
const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)];
Obj_ItemSpotAssignMonitor(spotList[r], Obj_SpawnMonitor(
spotList[r], 1 + pcount, firstUnspawnedEmerald));
}
for (i = 0; i < spotCount; ++i)
{
// now spotMap includes every spot
spotMap[i] = i;
}
if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval))
{
spotBackup = spotCount;
for (i = 0; i < pcount; i++)
{
UINT8 r = 0, key = 0;
mobj_t *drop = NULL;
SINT8 flip = 1;
if (spotCount == 0)
{
// all are accessible again
spotCount = spotBackup;
}
if (spotCount == 1)
{
key = 0;
}
else
{
key = P_RandomKey(PR_ITEM_ROULETTE, spotCount);
}
r = spotMap[key];
//CONS_Printf("[%d %d %d] ", i, key, r);
flip = P_MobjFlip(spotList[r]);
drop = K_SpawnSphereBox(
spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
10
);
K_FlipFromObject(drop, spotList[r]);
spotCount--;
if (key != spotCount)
{
// So the core theory of what's going on is that we keep every
// available option at the front of the array, so we don't have
// to skip over any gaps or do recursion to avoid doubles.
// But because spotCount can be reset in the case of a low
// quanitity of item spawnpoints in a map, we still need every
// entry in the array, even outside of the "visible" range.
// A series of swaps allows us to adhere to both constraints.
// -toast 22/03/22 (semipalindromic!)
spotMap[key] = spotMap[spotCount];
spotMap[spotCount] = r; // was set to spotMap[key] previously
}
}
}
//CONS_Printf("\n");
}
}
}
static void K_SpawnOvertimeLaser(fixed_t x, fixed_t y, fixed_t scale)
{
UINT8 i, j;
for (i = 0; i <= r_splitscreen; i++)
{
player_t *player = &players[displayplayers[i]];
fixed_t zpos;
SINT8 flip;
if (player == NULL || player->mo == NULL || P_MobjWasRemoved(player->mo) == true)
{
continue;
}
if (player->mo->eflags & MFE_VERTICALFLIP)
{
zpos = player->mo->z + player->mo->height;
}
else
{
zpos = player->mo->z;
}
flip = P_MobjFlip(player->mo);
for (j = 0; j < 3; j++)
{
mobj_t *mo = P_SpawnMobj(x, y, zpos, MT_OVERTIME_PARTICLE);
if (player->mo->eflags & MFE_VERTICALFLIP)
{
mo->flags2 |= MF2_OBJECTFLIP;
mo->eflags |= MFE_VERTICALFLIP;
}
mo->angle = R_PointToAngle2(mo->x, mo->y, battleovertime.x, battleovertime.y) + ANGLE_90;
mo->renderflags |= (RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player)));
P_SetScale(mo, scale);
switch (j)
{
case 0:
P_SetMobjState(mo, S_OVERTIME_BULB1);
if (leveltime & 1)
mo->frame += 1;
//P_SetScale(mo, mapobjectscale);
zpos += 35 * mo->scale * flip;
break;
case 1:
P_SetMobjState(mo, S_OVERTIME_LASER);
if (leveltime & 1)
mo->frame += 3;
else
mo->frame += (leveltime / 2) % 3;
//P_SetScale(mo, scale);
zpos += 346 * mo->scale * flip;
if (battleovertime.enabled < 10*TICRATE)
mo->renderflags |= RF_TRANS50;
break;
case 2:
P_SetMobjState(mo, S_OVERTIME_BULB2);
if (leveltime & 1)
mo->frame += 1;
//P_SetScale(mo, mapobjectscale);
break;
default:
I_Error("Bruh moment has occured\n");
return;
}
}
}
}
void K_RunBattleOvertime(void)
{
if (battleovertime.enabled < 10*TICRATE)
{
battleovertime.enabled++;
if (battleovertime.enabled == TICRATE)
S_StartSound(NULL, sfx_bhurry);
if (battleovertime.enabled == 10*TICRATE)
S_StartSound(NULL, sfx_kc40);
}
else if (battleovertime.radius > 0)
{
const fixed_t minradius = 768 * mapobjectscale;
if (battleovertime.radius > minradius)
battleovertime.radius -= 2*mapobjectscale;
else
battleovertime.radius = minradius;
}
if (battleovertime.radius > 0)
{
const INT32 orbs = 32;
const angle_t angoff = ANGLE_MAX / orbs;
const UINT8 spriteSpacing = 128;
fixed_t circumference = FixedMul(M_PI_FIXED, battleovertime.radius * 2);
fixed_t scale = max(circumference / spriteSpacing / orbs, mapobjectscale);
fixed_t size = FixedMul(mobjinfo[MT_OVERTIME_PARTICLE].radius, scale);
fixed_t posOffset = max(battleovertime.radius - size, 0);
INT32 i;
for (i = 0; i < orbs; i++)
{
angle_t ang = (i * angoff) + FixedAngle((leveltime * FRACUNIT) / 4);
fixed_t x = battleovertime.x + P_ReturnThrustX(NULL, ang, posOffset);
fixed_t y = battleovertime.y + P_ReturnThrustY(NULL, ang, posOffset);
K_SpawnOvertimeLaser(x, y, scale);
}
}
}
void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj)
{
UINT8 sequence = mt->args[0] - 1;
fixed_t speed = (FRACUNIT >> 3) * mt->args[1];
boolean backandforth = (mt->args[2] & TMBCF_BACKANDFORTH);
boolean reverse = (mt->args[2] & TMBCF_REVERSE);
mobj_t *target = NULL;
// Find the inital target
if (reverse)
{
target = P_GetLastTubeWaypoint(sequence);
}
else
{
target = P_GetFirstTubeWaypoint(sequence);
}
if (!target)
{
CONS_Alert(CONS_WARNING, "No target waypoint found for moving capsule (seq: #%d)\n", sequence);
return;
}
P_SetTarget(&mobj->target, target);
mobj->lastlook = sequence;
mobj->movecount = target->health;
mobj->movefactor = speed;
if (backandforth) {
mobj->flags2 |= MF2_AMBUSH;
} else {
mobj->flags2 &= ~MF2_AMBUSH;
}
if (reverse) {
mobj->cvmem = -1;
} else {
mobj->cvmem = 1;
}
}
void K_SpawnPlayerBattleBumpers(player_t *p)
{
if (!p->mo || p->bumpers <= 0)
return;
{
INT32 i;
angle_t diff = FixedAngle(360*FRACUNIT/p->bumpers);
angle_t newangle = p->mo->angle;
mobj_t *bump;
for (i = 0; i < p->bumpers; i++)
{
bump = P_SpawnMobjFromMobj(p->mo,
P_ReturnThrustX(p->mo, newangle + ANGLE_180, 64*FRACUNIT),
P_ReturnThrustY(p->mo, newangle + ANGLE_180, 64*FRACUNIT),
0, MT_BATTLEBUMPER);
bump->threshold = i;
P_SetTarget(&bump->target, p->mo);
bump->angle = newangle;
bump->color = p->mo->color;
if (p->mo->renderflags & RF_DONTDRAW)
bump->renderflags |= RF_DONTDRAW;
else
bump->renderflags &= ~RF_DONTDRAW;
newangle += diff;
}
}
}
void K_BattleInit(boolean singleplayercontext)
{
size_t i;
if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules)
{
mapthing_t *mt = mapthings;
for (i = 0; i < nummapthings; i++, mt++)
{
if (mt->type == mobjinfo[MT_BATTLECAPSULE].doomednum)
P_SpawnMapThing(mt);
}
battlecapsules = true;
}
if (gametyperules & GTR_BUMPERS)
{
INT32 maxbumpers = K_StartingBumperCount();
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
players[i].bumpers = maxbumpers;
K_SpawnPlayerBattleBumpers(players+i);
}
}
}