561 lines
12 KiB
C
561 lines
12 KiB
C
/// \file k_battle.c
|
|
/// \brief SRB2Kart Battle Mode specific code
|
|
|
|
#include "k_battle.h"
|
|
#include "k_boss.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 "p_spec.h"
|
|
|
|
// Item Breaker mode enabled for this map?
|
|
boolean itembreaker = false;
|
|
|
|
// box respawning in battle mode
|
|
INT32 nummapboxes = 0;
|
|
INT32 numgotboxes = 0;
|
|
|
|
// Item Breaker counters
|
|
UINT8 numtargets = 0; // Itemboxes busted
|
|
|
|
INT32 K_StartingBumperCount(void)
|
|
{
|
|
if (itembreaker)
|
|
return 1; // always 1 hit in Item Breaker
|
|
|
|
return cv_kartbumpers.value;
|
|
}
|
|
|
|
boolean K_IsPlayerWanted(player_t *player)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (!(gametyperules & GTR_WANTED))
|
|
return false;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (battlewanted[i] == -1)
|
|
break;
|
|
if (player == &players[battlewanted[i]])
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
boolean K_IsPlayerMostWanted(player_t *player)
|
|
{
|
|
if (mostwanted == -1)
|
|
return false;
|
|
|
|
if (!(gametyperules & GTR_WANTEDSPB))
|
|
return false;
|
|
|
|
if (player == &players[mostwanted])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void K_CalculateBattleWanted(void)
|
|
{
|
|
UINT8 numingame = 0, numwanted = 0;
|
|
SINT8 camppos[MAXPLAYERS]; // who is the biggest camper
|
|
UINT8 ties = 0, nextcamppos = 0;
|
|
UINT8 i, j;
|
|
|
|
mostwanted = -1;
|
|
|
|
if (!(gametyperules & GTR_WANTED))
|
|
{
|
|
memset(battlewanted, -1, sizeof (battlewanted));
|
|
return;
|
|
}
|
|
|
|
wantedcalcdelay = wantedfrequency;
|
|
memset(camppos, -1, sizeof (camppos)); // initialize
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
UINT8 position = 1;
|
|
|
|
if (!playeringame[i] || players[i].spectator) // Not playing
|
|
continue;
|
|
|
|
if (players[i].exiting) // We're done, don't calculate.
|
|
return;
|
|
|
|
if (players[i].bumper <= 0) // Not alive, so don't do anything else
|
|
continue;
|
|
|
|
numingame++;
|
|
|
|
for (j = 0; j < MAXPLAYERS; j++)
|
|
{
|
|
if (!playeringame[j] || players[j].spectator)
|
|
continue;
|
|
|
|
if (players[j].bumper <= 0)
|
|
continue;
|
|
|
|
if (j == i)
|
|
continue;
|
|
|
|
if (K_NumEmeralds(&players[j]) > K_NumEmeralds(&players[i]))
|
|
{
|
|
position++;
|
|
}
|
|
else if (players[j].bumper > players[i].bumper)
|
|
{
|
|
position++;
|
|
}
|
|
else if (players[j].roundscore > players[i].roundscore)
|
|
{
|
|
position++;
|
|
}
|
|
else if (players[j].wanted > players[i].wanted)
|
|
{
|
|
position++;
|
|
}
|
|
}
|
|
|
|
position--; // Make zero based
|
|
|
|
while (camppos[position] != -1) // Port priority!
|
|
position++;
|
|
|
|
camppos[position] = i;
|
|
}
|
|
|
|
if (numingame <= 2) // In 1v1s then there's no need for WANTED.
|
|
numwanted = 0;
|
|
else
|
|
numwanted = min(4, 1 + ((numingame-2) / 4));
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (i+1 > numwanted) // Not enough players for this slot to be wanted!
|
|
{
|
|
battlewanted[i] = -1;
|
|
}
|
|
else
|
|
{
|
|
// Do not add *any* more people if there's too many times that are tied with others.
|
|
// This could theoretically happen very easily if people don't hit each other for a while after the start of a match.
|
|
// (I will be sincerely impressed if more than 2 people tie after people start hitting each other though)
|
|
|
|
if (camppos[nextcamppos] == -1 // Out of entries
|
|
|| ties >= (numwanted-i)) // Already counted ties
|
|
{
|
|
battlewanted[i] = -1;
|
|
continue;
|
|
}
|
|
|
|
if (ties < (numwanted-i))
|
|
{
|
|
ties = 0; // Reset
|
|
for (j = 0; j < 2; j++)
|
|
{
|
|
if (camppos[nextcamppos+(j+1)] == -1) // Nothing beyond, cancel
|
|
break;
|
|
if (players[camppos[nextcamppos]].wanted == players[camppos[nextcamppos+(j+1)]].wanted)
|
|
ties++;
|
|
}
|
|
}
|
|
|
|
if (ties < (numwanted-i)) // Is it still low enough after counting?
|
|
{
|
|
battlewanted[i] = camppos[nextcamppos];
|
|
|
|
if (!nextcamppos)
|
|
mostwanted = battlewanted[i];
|
|
|
|
nextcamppos++;
|
|
}
|
|
else
|
|
battlewanted[i] = -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;
|
|
SINT8 winnernum = -1;
|
|
UINT32 winnerscoreadd = 0, maxroundscore = 0;
|
|
boolean nobumpers = false;
|
|
|
|
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++;
|
|
winnerscoreadd += players[i].roundscore;
|
|
|
|
if (players[i].roundscore > maxroundscore)
|
|
{
|
|
maxroundscore = players[i].roundscore;
|
|
}
|
|
|
|
if (players[i].bumper <= 0) // if you don't have any bumpers, you're probably not a winner
|
|
{
|
|
nobumpers = true;
|
|
continue;
|
|
}
|
|
else if (winnernum != -1) // TWO winners? that's dumb :V
|
|
return;
|
|
|
|
winnernum = i;
|
|
winnerscoreadd -= players[i].roundscore;
|
|
}
|
|
|
|
if (K_CanChangeRules(true) == false)
|
|
{
|
|
if (nobumpers)
|
|
{
|
|
P_DoAllPlayersExit(PF_NOCONTEST, false);
|
|
}
|
|
return;
|
|
}
|
|
else if (numingame <= 1)
|
|
{
|
|
if (!itembreaker && cv_kartitembreaker.value)
|
|
{
|
|
// Reset map to turn on battle capsules
|
|
if (server)
|
|
D_MapChange(gamemap, gametype, encoremode, true, 0, false, false);
|
|
}
|
|
else
|
|
{
|
|
if (nobumpers)
|
|
{
|
|
P_DoAllPlayersExit(PF_NOCONTEST, false);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (winnernum > -1 && playeringame[winnernum])
|
|
{
|
|
if ((players[winnernum].roundscore+winnerscoreadd) == maxroundscore)
|
|
winnerscoreadd++; // break ties if luigi wins by doing nothing
|
|
players[winnernum].roundscore += winnerscoreadd;
|
|
CONS_Printf(M_GetText("%s recieved %d point%s for winning!\n"), player_names[winnernum], winnerscoreadd, (winnerscoreadd == 1 ? "" : "s"));
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++) // This can't go in the earlier loop because winning adds points
|
|
K_KartUpdatePosition(&players[i]);
|
|
|
|
P_DoAllPlayersExit(0, false);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void K_RunPaperItemSpawners(void)
|
|
{
|
|
tic_t interval = 8*TICRATE;
|
|
|
|
thinker_t *th;
|
|
mobj_t *mo;
|
|
|
|
UINT8 pcount = 0;
|
|
INT16 i;
|
|
|
|
if (itembreaker || bossinfo.boss)
|
|
{
|
|
// Gametype uses paper items, but this specific expression doesn't
|
|
return;
|
|
}
|
|
|
|
if (leveltime < starttime)
|
|
{
|
|
// Round hasn't started yet!
|
|
return;
|
|
}
|
|
|
|
if (((leveltime - starttime) % interval) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((players[i].exiting > 0 || (players[i].pflags & PF_ELIMINATED))
|
|
|| ((gametyperules & GTR_BUMPERS) && players[i].bumper <= 0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pcount++;
|
|
}
|
|
|
|
{
|
|
if (pcount > 0)
|
|
{
|
|
#define MAXITEM 64
|
|
mobj_t *spotList[MAXITEM];
|
|
UINT8 spotMap[MAXITEM];
|
|
UINT8 spotCount = 0, spotBackup = 0;
|
|
|
|
INT16 starti = 0;
|
|
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
if (th->function == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
mo = (mobj_t *)th;
|
|
|
|
if (mo->type != MT_PAPERITEMSPOT)
|
|
continue;
|
|
|
|
if (spotCount >= MAXITEM)
|
|
continue;
|
|
|
|
spotList[spotCount] = mo;
|
|
spotMap[spotCount] = spotCount;
|
|
spotCount++;
|
|
}
|
|
|
|
if (spotCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//CONS_Printf("leveltime = %d ", leveltime);
|
|
|
|
spotBackup = spotCount;
|
|
for (i = starti; 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(spotCount);
|
|
}
|
|
|
|
r = spotMap[key];
|
|
|
|
//CONS_Printf("[%d %d %d] ", i, key, r);
|
|
|
|
flip = P_MobjFlip(spotList[r]);
|
|
|
|
{
|
|
|
|
drop = K_CreatePaperItem(
|
|
spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip),
|
|
FixedAngle(P_RandomRange(0, 359) * FRACUNIT), flip,
|
|
0, 0
|
|
);
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
void K_SpawnPlayerBattleBumpers(player_t *p)
|
|
{
|
|
if (!p->mo || p->bumper <= 0)
|
|
return;
|
|
|
|
{
|
|
INT32 i;
|
|
angle_t diff = FixedAngle(360*FRACUNIT/p->bumper);
|
|
angle_t newangle = p->mo->angle;
|
|
mobj_t *bump;
|
|
|
|
for (i = 0; i < p->bumper; 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)
|
|
{
|
|
if ((gametyperules & GTR_ITEMBREAKER) && singleplayercontext && !itembreaker && !bossinfo.boss)
|
|
{
|
|
if (!(K_CanChangeRules(true) && !cv_kartitembreaker.value))
|
|
itembreaker = true;
|
|
}
|
|
|
|
if (gametyperules & GTR_BUMPERS)
|
|
{
|
|
INT32 maxbumpers = K_StartingBumperCount();
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
players[i].bumper = maxbumpers;
|
|
K_SpawnPlayerBattleBumpers(players+i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle respawning the battle boxes.
|
|
void K_RespawnBattleBoxes(void)
|
|
{
|
|
if (!(gametyperules & GTR_BATTLEBOXES))
|
|
return;
|
|
|
|
if (itembreaker)
|
|
return;
|
|
|
|
if (numgotboxes < (4*nummapboxes/5))
|
|
return;
|
|
|
|
thinker_t *th;
|
|
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
mobj_t *box;
|
|
mobj_t *newmobj;
|
|
|
|
if (th->function == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
box = (mobj_t *)th;
|
|
|
|
if (box->type != MT_RANDOMITEM || box->threshold != 68 || box->fuse) // only popped items
|
|
continue;
|
|
|
|
// Respawn from mapthing if you have one!
|
|
if (box->spawnpoint)
|
|
{
|
|
P_SpawnMapThing(box->spawnpoint);
|
|
newmobj = box->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing
|
|
P_SpawnMobj(box->spawnpoint->mobj->x, box->spawnpoint->mobj->y, box->spawnpoint->mobj->z, MT_EXPLODE); // poof into existance
|
|
}
|
|
else
|
|
{
|
|
newmobj = P_SpawnMobj(box->x, box->y, box->z, box->type);
|
|
P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance
|
|
}
|
|
|
|
// Transfer flags2 (strongbox, objectflip)
|
|
newmobj->flags2 = box->flags2;
|
|
P_RemoveMobj(box); // make sure they disappear
|
|
numgotboxes--; // you've restored a box, remove it from the count
|
|
}
|
|
|
|
if (numgotboxes < 0)
|
|
numgotboxes = 0;
|
|
}
|