RingRacers/src/k_battle.c
2024-04-14 03:32:02 -04:00

995 lines
21 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 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 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"
#include "music.h"
#include "hu_stuff.h"
#include "m_easing.h"
#include "k_endcam.h"
#include "p_tick.h"
#define BARRIER_MIN_RADIUS (768 * mapobjectscale)
// Battle overtime info
struct battleovertime battleovertime;
struct battleufo g_battleufo;
// Capsules mode enabled for this map?
boolean battleprisons = 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
// Battle: someone won by collecting all 7 Chaos Emeralds
tic_t g_emeraldWin = 0;
INT32 K_StartingBumperCount(void)
{
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
return 0;
if (battleprisons || K_CheckBossIntro() || !K_CanChangeRules(true))
{
if (grandprixinfo.gp)
{
switch (grandprixinfo.gamespeed)
{
case KARTSPEED_HARD:
return (grandprixinfo.masterbots == true) ? 0 : 1;
case KARTSPEED_NORMAL:
return 2;
case KARTSPEED_EASY:
return 3;
}
}
return 2; // Normal
}
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;
UINT8 nobumpers = 0;
UINT8 eliminated = 0;
SINT8 kingofthehill = -1;
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 (!P_MobjWasRemoved(players[i].mo) && players[i].mo->health <= 0) // if you don't have any bumpers, you're probably not a winner
{
nobumpers++;
}
if (players[i].pflags & PF_ELIMINATED)
{
eliminated++;
}
else
{
kingofthehill = i;
}
}
if (numingame - eliminated == 2 && battleovertime.enabled && battleovertime.radius <= BARRIER_MIN_RADIUS)
{
Music_Stop("battle_overtime");
S_StartSound(NULL, sfx_kc4b); // Loud noise helps mask transition
}
if (K_Cooperative())
{
if (nobumpers > 0 && nobumpers >= numingame)
{
P_DoAllPlayersExit(PF_NOCONTEST, false);
return;
}
}
else if (numingame > 1)
{
// If every other player is eliminated, the
// last player standing wins by default.
if (eliminated >= numingame - 1)
{
K_EndBattleRound(kingofthehill != -1 ? &players[kingofthehill] : NULL);
return;
}
}
else
{
if ((gametyperules & GTR_PRISONS) && !battleprisons && (K_CanChangeRules(true) == true))
{
// Reset map to turn on battle prisons
if (server)
D_MapChange(gamemap, gametype, encoremode, true, 1, false, false);
return;
}
}
}
void K_CheckEmeralds(player_t *player)
{
if (!(gametyperules & GTR_POWERSTONES))
{
return;
}
if (!ALLCHAOSEMERALDS(player->emeralds))
{
return;
}
if (!K_EndBattleRound(player))
{
return;
}
// TODO: this would be better if the timing lived in
// Tally code. But I didn't do it that, so this just
// shittily approximates syncing up with Tally.
g_emeraldWin = leveltime + (3*TICRATE);
if (!P_MobjWasRemoved(player->mo))
{
K_StartRoundWinCamera(
player->mo,
player->angleturn + ANGLE_180,
400*mapobjectscale,
6*TICRATE,
FRACUNIT/16
);
g_emeraldWin += g_endcam.swirlDuration;
}
}
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,
36 * mapobjectscale);
emerald->momz = flip * 36 * 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;
if (gametyperules & GTR_CLOSERPLAYERS)
{
emerald->fuse = BATTLE_DESPAWN_TIME;
}
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->extravalue2 = amount;
return drop;
}
void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType)
{
UINT8 i;
SINT8 flip = P_MobjFlip(player->mo);
if (player->incontrol < TICRATE)
return;
for (i = 0; i < 14; i++)
{
UINT32 emeraldFlag = (1 << i);
if ((player->emeralds & emeraldFlag) && (emeraldFlag & emeraldType))
{
K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag);
player->emeralds &= ~emeraldFlag;
break; // Drop only one emerald. Emerald wins are hard enough!
}
}
}
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;
}
static UINT32 CountEmeraldsSpawned(const mobj_t *mo)
{
switch (mo->type)
{
case MT_EMERALD:
return mo->extravalue1;
case MT_MONITOR:
return Obj_MonitorGetEmerald(mo);
default:
return 0U;
}
}
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 (battleprisons)
{
// Gametype uses paper items, but this specific expression doesn't
return;
}
if (leveltime < starttime)
{
// Round hasn't started yet!
return;
}
if (leveltime == g_battleufo.due && overtime == false)
{
Obj_SpawnBattleUFOFromSpawner();
}
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) && !P_MobjWasRemoved(players[i].mo) && players[i].mo->health <= 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;
emeraldsSpawned |= CountEmeraldsSpawned(mo);
}
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_FlingPaperItem(
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;
UINT8 monitorsSpawned = 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;
emeraldsSpawned |= CountEmeraldsSpawned(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++;
}
else
{
monitorsSpawned++;
}
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);
// Duel = 2 + 1 = 3 / 2 = 1
// Small = 5 + 1 = 6 / 2 = 3
// Medium = 10 + 1 = 11 / 2 = 5
// Large = 16 + 1 = 17 / 2 = 8
if (spotAvailable > 0 && monitorsSpawned < (mapheaderinfo[gamemap - 1]->playerLimit + 1) / 2)
{
const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)];
Obj_ItemSpotAssignMonitor(spotList[r], Obj_SpawnMonitor(
spotList[r], 3, firstUnspawnedEmerald));
}
for (i = 0; i < spotCount; ++i)
{
// now spotMap includes every spot
spotMap[i] = i;
}
if ((gametyperules & GTR_SPHERES) && IsOnInterval(16 * 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)
{
const fixed_t heightPadding = 346 * scale;
UINT8 i, j;
for (i = 0; i <= r_splitscreen; i++)
{
camera_t *cam = &camera[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 = cam->z + player->mo->height;
zpos = min(zpos + heightPadding, cam->centerceilingz);
}
else
{
zpos = cam->z;
zpos = max(zpos - heightPadding, cam->centerfloorz);
}
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))) | RF_HIDEINSKYBOX;
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_SpawnOvertimeBarrier(void)
{
if (battleovertime.radius <= 0)
{
return;
}
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_RunBattleOvertime(void)
{
if (battleovertime.enabled < 10*TICRATE)
{
battleovertime.enabled++;
if (battleovertime.enabled == TICRATE)
{
S_StartSound(NULL, sfx_bhurry);
HU_DoTitlecardCEchoForDuration(NULL, "HURRY UP!!", true, 2*TICRATE);
Music_DelayEnd("level", 0);
}
else if (battleovertime.enabled == 10*TICRATE)
{
S_StartSound(NULL, sfx_kc40);
P_StartQuake(5, 64 * mapobjectscale, 0, NULL);
battleovertime.start = leveltime;
}
if (!Music_Playing("level") && !Music_Playing("battle_overtime"))
{
Music_Play("battle_overtime");
Music_Play("battle_overtime_stress");
// Sync approximately with looping section of
// battle_overtime. (This is file dependant.)
Music_Seek("battle_overtime_stress", 1756);
}
}
else if (battleovertime.radius > 0)
{
const fixed_t minradius = BARRIER_MIN_RADIUS;
const fixed_t oldradius = battleovertime.radius;
if (battleovertime.radius > minradius)
{
extern consvar_t cv_barriertime;
tic_t t = leveltime - battleovertime.start;
const tic_t duration = cv_barriertime.value * TICRATE;
battleovertime.radius = Easing_OutSine(min(t, duration) * FRACUNIT / duration, battleovertime.initial_radius, minradius);
}
if (battleovertime.radius <= minradius && oldradius > minradius)
{
battleovertime.radius = minradius;
K_CheckBumpers();
S_StartSound(NULL, sfx_kc40);
P_StartQuake(5, 64 * mapobjectscale, 0, NULL);
}
// Subtract the 10 second grace period of the barrier
if (battleovertime.enabled < 25*TICRATE)
{
battleovertime.enabled++;
Obj_PointPlayersToXY(battleovertime.x, battleovertime.y);
}
}
if (!P_LevelIsFrozen())
{
K_SpawnOvertimeBarrier();
}
}
void K_SetupMovingCapsule(mapthing_t *mt, mobj_t *mobj)
{
UINT8 sequence = mt->thing_args[0] - 1;
fixed_t speed = (FRACUNIT >> 3) * mt->thing_args[1];
boolean backandforth = (mt->thing_args[2] & TMBCF_BACKANDFORTH);
boolean reverse = (mt->thing_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)
{
const UINT8 bumpers = K_Bumpers(p);
if (bumpers <= 0)
{
return;
}
{
INT32 i;
angle_t diff = FixedAngle(360*FRACUNIT / bumpers);
angle_t newangle = p->mo->angle;
mobj_t *bump;
for (i = 0; i < 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_PRISONS) && singleplayercontext && !battleprisons && !cv_battletest.value)
{
mapthing_t *mt = mapthings;
for (i = 0; i < nummapthings; i++, mt++)
{
if (mt->type == mobjinfo[MT_BATTLECAPSULE].doomednum)
P_SpawnMapThing(mt);
else if (mt->type == mobjinfo[MT_CDUFO].doomednum)
maptargets++;
}
battleprisons = true;
}
g_battleufo.due = starttime;
g_battleufo.previousId = Obj_RandomBattleUFOSpawnerID() - 1;
g_emeraldWin = 0;
}
UINT8 K_Bumpers(player_t *player)
{
if ((gametyperules & GTR_BUMPERS) == 0)
{
return 0;
}
if (P_MobjWasRemoved(player->mo))
{
return 0;
}
if (player->mo->health < 1)
{
return 0;
}
if (player->mo->health > UINT8_MAX)
{
return UINT8_MAX;
}
return (player->mo->health - 1);
}
INT32 K_BumpersToHealth(UINT8 bumpers)
{
return (bumpers + 1);
}
boolean K_BattleOvertimeKiller(mobj_t *mobj)
{
if (battleovertime.enabled < 10*TICRATE)
{
return false;
}
fixed_t distance = R_PointToDist2(mobj->x, mobj->y, battleovertime.x, battleovertime.y);
if (distance <= battleovertime.radius)
{
return false;
}
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
return true;
}
boolean K_EndBattleRound(player_t *victor)
{
if (victor)
{
if (victor->exiting)
{
// In Battle, players always exit altogether.
// So it can be assumed that if this player is
// exiting, the round has already ended.
return false;
}
if (gametyperules & GTR_POINTLIMIT)
{
// Lock the winner in before the round ends.
victor->roundscore = 100;
}
}
P_DoAllPlayersExit(0, false);
return true;
}