RingRacers/src/p_inter.c
2022-09-20 22:20:44 +00:00

2265 lines
58 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
//
// 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 p_inter.c
/// \brief Handling interactions (i.e., collisions)
#include "doomdef.h"
#include "i_system.h"
#include "am_map.h"
#include "g_game.h"
#include "m_random.h"
#include "p_local.h"
#include "s_sound.h"
#include "r_main.h"
#include "st_stuff.h"
#include "hu_stuff.h"
#include "lua_hook.h"
#include "m_cond.h" // unlockables, emblems, etc
#include "p_setup.h"
#include "m_cheat.h" // objectplace
#include "m_misc.h"
#include "v_video.h" // video flags for CEchos
#include "f_finale.h"
// SRB2kart
#include "k_kart.h"
#include "k_battle.h"
#include "k_pwrlv.h"
#include "k_grandprix.h"
#include "k_boss.h"
#include "k_respawn.h"
#include "p_spec.h"
#include "k_objects.h"
// CTF player names
#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : ""
#define CTFTEAMENDCODE(pl) pl->ctfteam ? "\x80" : ""
void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period)
{
BasicFF_t Basicfeed;
if (!player)
return;
Basicfeed.Duration = (UINT32)(duration * (100L/TICRATE));
Basicfeed.ForceX = Basicfeed.ForceY = 1;
Basicfeed.Gain = 25000;
Basicfeed.Magnitude = period*10;
Basicfeed.player = player;
/// \todo test FFB
P_RampConstant(&Basicfeed, attack, fade);
}
void P_ForceConstant(const BasicFF_t *FFInfo)
{
JoyFF_t ConstantQuake;
if (!FFInfo || !FFInfo->player)
return;
ConstantQuake.ForceX = FFInfo->ForceX;
ConstantQuake.ForceY = FFInfo->ForceY;
ConstantQuake.Duration = FFInfo->Duration;
ConstantQuake.Gain = FFInfo->Gain;
ConstantQuake.Magnitude = FFInfo->Magnitude;
if (FFInfo->player == &players[consoleplayer])
I_Tactile(ConstantForce, &ConstantQuake);
else if (splitscreen && FFInfo->player == &players[g_localplayers[1]])
I_Tactile2(ConstantForce, &ConstantQuake);
else if (splitscreen > 1 && FFInfo->player == &players[g_localplayers[2]])
I_Tactile3(ConstantForce, &ConstantQuake);
else if (splitscreen > 2 && FFInfo->player == &players[g_localplayers[3]])
I_Tactile4(ConstantForce, &ConstantQuake);
}
void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End)
{
JoyFF_t RampQuake;
if (!FFInfo || !FFInfo->player)
return;
RampQuake.ForceX = FFInfo->ForceX;
RampQuake.ForceY = FFInfo->ForceY;
RampQuake.Duration = FFInfo->Duration;
RampQuake.Gain = FFInfo->Gain;
RampQuake.Magnitude = FFInfo->Magnitude;
RampQuake.Start = Start;
RampQuake.End = End;
if (FFInfo->player == &players[consoleplayer])
I_Tactile(ConstantForce, &RampQuake);
else if (splitscreen && FFInfo->player == &players[g_localplayers[1]])
I_Tactile2(ConstantForce, &RampQuake);
else if (splitscreen > 1 && FFInfo->player == &players[g_localplayers[2]])
I_Tactile3(ConstantForce, &RampQuake);
else if (splitscreen > 2 && FFInfo->player == &players[g_localplayers[3]])
I_Tactile4(ConstantForce, &RampQuake);
}
//
// GET STUFF
//
//
// P_CanPickupItem
//
// Returns true if the player is in a state where they can pick up items.
//
boolean P_CanPickupItem(player_t *player, UINT8 weapon)
{
if (player->exiting || mapreset || (player->pflags & PF_ELIMINATED))
return false;
if ((gametyperules & GTR_BUMPERS) // No bumpers in Match
#ifndef OTHERKARMAMODES
&& !weapon
#endif
&& player->bumpers <= 0)
return false;
if (weapon)
{
// Item slot already taken up
if (weapon == 2)
{
// Invulnerable
if (player->flashing > 0)
return false;
// Already have fake
if (player->roulettetype == 2
|| player->eggmanexplode)
return false;
}
else
{
// Item-specific timer going off
if (player->stealingtimer
|| player->rocketsneakertimer
|| player->eggmanexplode)
return false;
// Item slot already taken up
if (player->itemroulette
|| (weapon != 3 && player->itemamount)
|| (player->pflags & PF_ITEMOUT))
return false;
if (weapon == 3 && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE)
return false; // No stacking shields!
}
}
return true;
}
/** Takes action based on a ::MF_SPECIAL thing touched by a player.
* Actually, this just checks a few things (heights, toucher->player, no
* objectplace, no dead or disappearing things)
*
* The special thing may be collected and disappear, or a sound may play, or
* both.
*
* \param special The special thing.
* \param toucher The player's mobj.
* \param heightcheck Whether or not to make sure the player and the object
* are actually touching.
*/
void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
{
player_t *player;
INT32 i;
if (objectplacing)
return;
I_Assert(special != NULL);
I_Assert(toucher != NULL);
// Dead thing touching.
// Can happen with a sliding player corpse.
if (toucher->health <= 0)
return;
if (special->health <= 0)
return;
if (heightcheck)
{
fixed_t toucher_bottom = toucher->z;
fixed_t special_bottom = special->z;
if (toucher->flags & MF_PICKUPFROMBELOW)
toucher_bottom -= toucher->height;
if (special->flags & MF_PICKUPFROMBELOW)
special_bottom -= special->height;
if (toucher->momz < 0) {
if (toucher_bottom + toucher->momz > special->z + special->height)
return;
} else if (toucher_bottom > special->z + special->height)
return;
if (toucher->momz > 0) {
if (toucher->z + toucher->height + toucher->momz < special_bottom)
return;
} else if (toucher->z + toucher->height < special_bottom)
return;
}
player = toucher->player;
I_Assert(player != NULL); // Only players can touch stuff!
if (player->spectator)
return;
// Ignore multihits in "ouchie" mode
if (special->flags & (MF_ENEMY|MF_BOSS) && special->flags2 & MF2_FRET)
return;
if (LUA_HookTouchSpecial(special, toucher) || P_MobjWasRemoved(special))
return;
if ((special->flags & (MF_ENEMY|MF_BOSS)) && !(special->flags & MF_MISSILE))
{
////////////////////////////////////////////////////////
/////ENEMIES & BOSSES!!/////////////////////////////////
////////////////////////////////////////////////////////
P_DamageMobj(toucher, special, special, 1, DMG_NORMAL);
return;
}
else
{
// We now identify by object type, not sprite! Tails 04-11-2001
switch (special->type)
{
case MT_MEMENTOSTP: // Mementos teleport
// Teleport player to the other teleporter (special->target). We'll assume there's always only ever 2.
if (!special->target)
return; // foolproof crash prevention check!!!!!
P_SetOrigin(player->mo, special->target->x, special->target->y, special->target->z + (48<<FRACBITS));
player->mo->angle = special->target->angle;
P_SetObjectMomZ(player->mo, 12<<FRACBITS, false);
P_InstaThrust(player->mo, player->mo->angle, 20<<FRACBITS);
return;
case MT_FLOATINGITEM: // SRB2Kart
if (!P_CanPickupItem(player, 3) || (player->itemamount && player->itemtype != special->threshold))
return;
if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)
return;
player->itemtype = special->threshold;
if ((UINT16)(player->itemamount) + special->movecount > 255)
player->itemamount = 255;
else
player->itemamount += special->movecount;
S_StartSound(special, special->info->deathsound);
P_SetTarget(&special->tracer, toucher);
special->flags2 |= MF2_NIGHTSPULL;
special->destscale = mapobjectscale>>4;
special->scalespeed <<= 1;
special->flags &= ~MF_SPECIAL;
return;
case MT_RANDOMITEM:
if (!P_CanPickupItem(player, 1))
return;
special->momx = special->momy = special->momz = 0;
P_SetTarget(&special->target, toucher);
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
return;
case MT_SPHEREBOX:
if (!P_CanPickupItem(player, 0))
return;
special->momx = special->momy = special->momz = 0;
P_SetTarget(&special->target, toucher);
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
return;
case MT_ITEMCAPSULE:
if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)
return;
if (special->scale < special->extravalue1) // don't break it while it's respawning
return;
switch (special->threshold)
{
case KITEM_SPB:
if (K_IsSPBInGame()) // don't spawn a second SPB
return;
break;
case KITEM_SUPERRING:
if (player->pflags & PF_RINGLOCK) // no cheaty rings
return;
break;
default:
if (!P_CanPickupItem(player, 1))
return;
break;
}
S_StartSound(toucher, special->info->deathsound);
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
return;
case MT_KARMAHITBOX:
if (!special->target->player)
return;
if (player == special->target->player)
return;
if (player->bumpers <= 0)
return;
if (special->target->player->exiting || player->exiting)
return;
if (P_PlayerInPain(special->target->player))
return;
if (special->target->player->karmadelay > 0)
return;
{
mobj_t *boom;
if (P_DamageMobj(toucher, special, special->target, 1, DMG_KARMA) == false)
{
return;
}
boom = P_SpawnMobj(special->target->x, special->target->y, special->target->z, MT_BOOMEXPLODE);
boom->scale = special->target->scale;
boom->destscale = special->target->scale;
boom->momz = 5*FRACUNIT;
if (special->target->color)
boom->color = special->target->color;
else
boom->color = SKINCOLOR_KETCHUP;
S_StartSound(boom, special->info->attacksound);
special->target->player->karthud[khud_yougotem] = 2*TICRATE;
special->target->player->karmadelay = comebacktime;
}
return;
case MT_SPB:
if ((special->target == toucher || special->target == toucher->target) && (special->threshold > 0))
return;
if (special->health <= 0 || toucher->health <= 0)
return;
if (player->spectator)
return;
if (special->tracer && !P_MobjWasRemoved(special->tracer) && toucher == special->tracer)
{
mobj_t *spbexplode;
if (player->bubbleblowup > 0)
{
K_DropHnextList(player, false);
special->extravalue1 = 2; // WAIT...
special->extravalue2 = 52; // Slightly over the respawn timer length
return;
}
S_StopSound(special); // Don't continue playing the gurgle or the siren
spbexplode = P_SpawnMobj(toucher->x, toucher->y, toucher->z, MT_SPBEXPLOSION);
spbexplode->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback
if (special->target && !P_MobjWasRemoved(special->target))
P_SetTarget(&spbexplode->target, special->target);
P_RemoveMobj(special);
}
else
{
P_DamageMobj(player->mo, special, special->target, 1, DMG_NORMAL);
}
return;
case MT_EMERALD:
if (!P_CanPickupItem(player, 0))
return;
if (special->threshold > 0)
return;
if (toucher->hitlag > 0)
return;
player->emeralds |= special->extravalue1;
K_CheckEmeralds(player);
break;
/*
case MT_EERIEFOG:
special->frame &= ~FF_TRANS80;
special->frame |= FF_TRANS90;
return;
*/
case MT_SMK_MOLE:
if (special->target && !P_MobjWasRemoved(special->target))
return;
if (special->health <= 0 || toucher->health <= 0)
return;
if (!player->mo || player->spectator)
return;
// kill
if (player->invincibilitytimer > 0
|| player->growshrinktimer > 0
|| player->flamedash > 0)
{
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
return;
}
// no interaction
if (player->flashing > 0 || player->hyudorotimer > 0 || P_PlayerInPain(player))
return;
// attach to player!
P_SetTarget(&special->target, toucher);
S_StartSound(special, sfx_s1a2);
return;
case MT_CDUFO: // SRB2kart
if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0))
return;
player->itemroulette = 1;
player->roulettetype = 1;
// Karma fireworks
for (i = 0; i < 5; i++)
{
mobj_t *firework = P_SpawnMobj(special->x, special->y, special->z, MT_KARMAFIREWORK);
firework->momx = toucher->momx;
firework->momy = toucher->momy;
firework->momz = toucher->momz;
P_Thrust(firework, FixedAngle((72*i)<<FRACBITS), P_RandomRange(1,8)*special->scale);
P_SetObjectMomZ(firework, P_RandomRange(1,8)*special->scale, false);
firework->color = toucher->color;
}
S_StartSound(toucher, sfx_cdfm73); // they don't make this sound in the original game but it's nice to have a "reward" for good play
//special->momx = special->momy = special->momz = 0;
special->momz = -(3*special->scale)/2;
//P_SetTarget(&special->target, toucher);
special->fuse = 2*TICRATE;
break;
case MT_BALLOON: // SRB2kart
P_SetObjectMomZ(toucher, 20<<FRACBITS, false);
break;
case MT_BUBBLESHIELDTRAP:
if ((special->target == toucher || special->target == toucher->target) && (special->threshold > 0))
return;
if (special->tracer && !P_MobjWasRemoved(special->tracer))
return;
if (special->health <= 0 || toucher->health <= 0)
return;
if (!player->mo || player->spectator)
return;
// attach to player!
P_SetTarget(&special->tracer, toucher);
toucher->flags |= MF_NOGRAVITY;
toucher->momz = (8*toucher->scale) * P_MobjFlip(toucher);
S_StartSound(toucher, sfx_s1b2);
return;
case MT_HYUDORO:
Obj_HyudoroCollide(special, toucher);
return;
case MT_RING:
case MT_FLINGRING:
if (special->extravalue1)
return;
// No picking up rings while SPB is targetting you
if (player->pflags & PF_RINGLOCK)
return;
// Don't immediately pick up spilled rings
if (special->threshold > 0 || P_PlayerInPain(player))
return;
if (!(P_CanPickupItem(player, 0)))
return;
// Reached the cap, don't waste 'em!
if (RINGTOTAL(player) >= 20)
return;
special->momx = special->momy = special->momz = 0;
special->extravalue1 = 1; // Ring collect animation timer
special->angle = R_PointToAngle2(toucher->x, toucher->y, special->x, special->y); // animation angle
P_SetTarget(&special->target, toucher); // toucher for thinker
player->pickuprings++;
return;
case MT_BLUESPHERE:
if (!(P_CanPickupItem(player, 0)))
return;
P_GivePlayerSpheres(player, 1);
break;
// Secret emblem thingy
case MT_EMBLEM:
{
if (demo.playback || special->health > MAXEMBLEMS)
return;
emblemlocations[special->health-1].collected = true;
M_UpdateUnlockablesAndExtraEmblems();
G_SaveGameData();
break;
}
// CTF Flags
case MT_REDFLAG:
case MT_BLUEFLAG:
return;
case MT_STARPOST:
P_TouchStarPost(special, player, special->spawnpoint && (special->spawnpoint->options & MTF_OBJECTSPECIAL));
return;
case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED:
if (toucher->momx || toucher->momy)
{
special->momx = toucher->momx;
special->momy = toucher->momy;
special->momz = P_AproxDistance(toucher->momx, toucher->momy)/4;
if (toucher->momz > 0)
special->momz += toucher->momz/8;
P_SetMobjState(special, special->info->seestate);
}
return;
case MT_WATERDROP:
if (special->state == &states[special->info->spawnstate])
{
special->z = toucher->z+toucher->height-FixedMul(8*FRACUNIT, special->scale);
special->momz = 0;
special->flags |= MF_NOGRAVITY;
P_SetMobjState (special, special->info->deathstate);
S_StartSound (special, special->info->deathsound+(P_RandomKey(special->info->mass)));
}
return;
default: // SOC or script pickup
P_SetTarget(&special->target, toucher);
break;
}
}
S_StartSound(toucher, special->info->deathsound); // was NULL, but changed to player so you could hear others pick up rings
P_KillMobj(special, NULL, toucher, DMG_NORMAL);
special->shadowscale = 0;
}
/** Saves a player's level progress at a star post
*
* \param post The star post to trigger
* \param player The player that should receive the checkpoint
* \param snaptopost If true, the respawn point will use the star post's position, otherwise player x/y and star post z
*/
void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost)
{
mobj_t *toucher = player->mo;
(void)snaptopost;
// Player must have touched all previous starposts
if (post->health - player->starpostnum > 1)
{
if (!player->checkskip)
S_StartSound(toucher, sfx_lose);
player->checkskip = 3;
return;
}
// With the parameter + angle setup, we can go up to 1365 star posts. Who needs that many?
if (post->health > 1365)
{
CONS_Debug(DBG_GAMELOGIC, "Bad Starpost Number!\n");
return;
}
if (player->starpostnum >= post->health)
return; // Already hit this post
player->starpostnum = post->health;
}
// Easily make it so that overtime works offline
#define TESTOVERTIMEINFREEPLAY
/** Checks if the level timer is over the timelimit and the round should end,
* unless you are in overtime. In which case leveltime may stretch out beyond
* timelimitintics and overtime's status will be checked here each tick.
* Verify that the value of ::cv_timelimit is greater than zero before
* calling this function.
*
* \sa cv_timelimit, P_CheckPointLimit, P_UpdateSpecials
*/
void P_CheckTimeLimit(void)
{
INT32 i;
if (!cv_timelimit.value)
return;
#ifndef TESTOVERTIMEINFREEPLAY
if (battlecapsules) // capsules override any time limit settings
return;
#endif
if (!(gametyperules & GTR_TIMELIMIT))
return;
if (bossinfo.boss == true)
return;
if (leveltime < (timelimitintics + starttime))
return;
if (gameaction == ga_completed)
return;
if ((cv_overtime.value) && (gametyperules & GTR_OVERTIME))
{
#ifndef TESTOVERTIMEINFREEPLAY
boolean foundone = false; // Overtime is used for closing off down to a specific item.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (foundone)
{
#endif
// Initiate the kill zone
if (!battleovertime.enabled)
{
thinker_t *th;
mobj_t *center = NULL;
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
mobj_t *thismo;
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
thismo = (mobj_t *)th;
if (thismo->type == MT_OVERTIME_CENTER)
{
center = thismo;
break;
}
}
if (center == NULL || P_MobjWasRemoved(center))
{
CONS_Alert(CONS_WARNING, "No center point for overtime!\n");
battleovertime.x = 0;
battleovertime.y = 0;
battleovertime.z = 0;
}
else
{
battleovertime.x = center->x;
battleovertime.y = center->y;
battleovertime.z = center->z;
}
battleovertime.radius = 4096 * mapobjectscale;
battleovertime.enabled = 1;
S_StartSound(NULL, sfx_kc47);
}
return;
#ifndef TESTOVERTIMEINFREEPLAY
}
else
foundone = true;
}
#endif
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].exiting)
return;
P_DoPlayerExit(&players[i]);
}
}
/** Checks if a player's score is over the pointlimit and the round should end.
* Verify that the value of ::cv_pointlimit is greater than zero before
* calling this function.
*
* \sa cv_pointlimit, P_CheckTimeLimit, P_UpdateSpecials
*/
void P_CheckPointLimit(void)
{
INT32 i;
if (!cv_pointlimit.value)
return;
if (!(multiplayer || netgame))
return;
if (!(gametyperules & GTR_POINTLIMIT))
return;
if (bossinfo.boss == true)
return;
// pointlimit is nonzero, check if it's been reached by this player
if (G_GametypeHasTeams())
{
// Just check both teams
if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore)
{
if (server)
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
}
else
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if ((UINT32)cv_pointlimit.value <= players[i].roundscore)
{
for (i = 0; i < MAXPLAYERS; i++) // AAAAA nested loop using the same iteration variable ;;
{
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].exiting)
return;
P_DoPlayerExit(&players[i]);
}
/*if (server)
SendNetXCmd(XD_EXITLEVEL, NULL, 0);*/
return; // good thing we're leaving the function immediately instead of letting the loop get mangled!
}
}
}
}
// Checks whether or not to end a race netgame.
boolean P_CheckRacers(void)
{
const boolean griefed = (spectateGriefed > 0);
boolean eliminateLast = cv_karteliminatelast.value;
boolean allHumansDone = true;
//boolean allBotsDone = true;
UINT8 numPlaying = 0;
UINT8 numExiting = 0;
UINT8 numHumans = 0;
UINT8 numBots = 0;
UINT8 i;
// Check if all the players in the race have finished. If so, end the level.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].lives <= 0)
{
// Y'all aren't even playing
continue;
}
numPlaying++;
if (players[i].bot)
{
numBots++;
}
else
{
numHumans++;
}
if (players[i].exiting || (players[i].pflags & PF_NOCONTEST))
{
numExiting++;
}
else
{
if (players[i].bot)
{
//allBotsDone = false;
}
else
{
allHumansDone = false;
}
}
}
if (numPlaying <= 1)
{
// Never do this without enough players.
eliminateLast = false;
}
else
{
if (griefed == true)
{
// Don't do this if someone spectated
eliminateLast = false;
}
else if (grandprixinfo.gp == true)
{
// Always do this in GP
eliminateLast = true;
}
}
if (eliminateLast == true && (numExiting >= numPlaying-1))
{
// Everyone's done playing but one guy apparently.
// Just kill everyone who is still playing.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].lives <= 0)
{
// Y'all aren't even playing
continue;
}
if (players[i].exiting || (players[i].pflags & PF_NOCONTEST))
{
// You're done, you're free to go.
continue;
}
P_DoTimeOver(&players[i]);
}
// Everyone should be done playing at this point now.
racecountdown = exitcountdown = 0;
return true;
}
if (numHumans > 0 && allHumansDone == true)
{
// There might be bots that are still going,
// but all of the humans are done, so we can exit now.
racecountdown = exitcountdown = 0;
return true;
}
// SO, we're not done playing.
// Let's see if it's time to start the death counter!
if (racecountdown == 0)
{
// If the winners are all done, then start the death timer.
UINT8 winningPos = max(1, numPlaying / 2);
if (numPlaying % 2) // Any remainder? Then round up.
{
winningPos++;
}
if (numExiting >= winningPos)
{
tic_t countdown = 30*TICRATE; // 30 seconds left to finish, get going!
if (K_CanChangeRules() == true)
{
// Custom timer
countdown = cv_countdowntime.value * TICRATE;
}
racecountdown = countdown + 1;
}
}
// We're still playing, but no one else is,
// so we need to reset spectator griefing.
if (numPlaying <= 1)
{
spectateGriefed = 0;
}
// We are still having fun and playing the game :)
return false;
}
/** Kills an object.
*
* \param target The victim.
* \param inflictor The attack weapon. May be NULL (environmental damage).
* \param source The attacker. May be NULL.
* \param damagetype The type of damage dealt that killed the target. If bit 7 (0x80) was set, this was an instant-death.
* \todo Cleanup, refactor, split up.
* \sa P_DamageMobj
*/
void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
{
mobj_t *mo;
if (target->flags & (MF_ENEMY|MF_BOSS))
target->momx = target->momy = target->momz = 0;
// SRB2kart
if (target->type != MT_PLAYER && !(target->flags & MF_MONITOR)
&& !(target->type == MT_ORBINAUT || target->type == MT_ORBINAUT_SHIELD
|| target->type == MT_JAWZ || target->type == MT_JAWZ_DUD || target->type == MT_JAWZ_SHIELD
|| target->type == MT_BANANA || target->type == MT_BANANA_SHIELD
|| target->type == MT_DROPTARGET || target->type == MT_DROPTARGET_SHIELD
|| target->type == MT_EGGMANITEM || target->type == MT_EGGMANITEM_SHIELD
|| target->type == MT_BALLHOG || target->type == MT_SPB)) // kart dead items
target->flags |= MF_NOGRAVITY; // Don't drop Tails 03-08-2000
else
target->flags &= ~MF_NOGRAVITY; // lose it if you for whatever reason have it, I'm looking at you shields
//
if (target->flags2 & MF2_NIGHTSPULL)
{
P_SetTarget(&target->tracer, NULL);
target->movefactor = 0; // reset NightsItemChase timer
}
// dead target is no more shootable
target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SPECIAL);
target->flags2 &= ~(MF2_SKULLFLY|MF2_NIGHTSPULL);
target->health = 0; // This makes it easy to check if something's dead elsewhere.
if (target->type != MT_BATTLEBUMPER)
{
target->shadowscale = 0;
}
if (LUA_HookMobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target))
return;
//K_SetHitLagForObjects(target, inflictor, MAXHITLAGTICS, true);
// SRB2kart
// I wish I knew a better way to do this
if (target->target && target->target->player && target->target->player->mo)
{
if ((target->target->player->pflags & PF_EGGMANOUT) && target->type == MT_EGGMANITEM_SHIELD)
target->target->player->pflags &= ~PF_EGGMANOUT;
if (target->target->player->pflags & PF_ITEMOUT)
{
if ((target->type == MT_BANANA_SHIELD && target->target->player->itemtype == KITEM_BANANA) // trail items
|| (target->type == MT_SSMINE_SHIELD && target->target->player->itemtype == KITEM_MINE)
|| (target->type == MT_DROPTARGET_SHIELD && target->target->player->itemtype == KITEM_DROPTARGET)
|| (target->type == MT_SINK_SHIELD && target->target->player->itemtype == KITEM_KITCHENSINK))
{
if (target->movedir != 0 && target->movedir < (UINT16)target->target->player->itemamount)
{
if (target->target->hnext)
K_KillBananaChain(target->target->hnext, inflictor, source);
target->target->player->itemamount = 0;
}
else if (target->target->player->itemamount)
target->target->player->itemamount--;
}
else if ((target->type == MT_ORBINAUT_SHIELD && target->target->player->itemtype == KITEM_ORBINAUT) // orbit items
|| (target->type == MT_JAWZ_SHIELD && target->target->player->itemtype == KITEM_JAWZ))
{
if (target->target->player->itemamount)
target->target->player->itemamount--;
if (target->lastlook != 0)
{
K_RepairOrbitChain(target);
}
}
if (!target->target->player->itemamount)
target->target->player->pflags &= ~PF_ITEMOUT;
if (target->target->hnext == target)
P_SetTarget(&target->target->hnext, NULL);
}
}
// Above block does not clean up rocket sneakers when a player dies, so we need to do it here target->target is null when using rocket sneakers
if (target->player)
K_DropRocketSneaker(target->player);
// Let EVERYONE know what happened to a player! 01-29-2002 Tails
if (target->player && !target->player->spectator)
{
if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
G_StopMetalRecording(true);
target->renderflags &= ~RF_DONTDRAW;
}
// if killed by a player
if (source && source->player)
{
if (target->flags & MF_MONITOR || target->type == MT_RANDOMITEM)
{
P_SetTarget(&target->target, source);
if (gametyperules & GTR_BUMPERS)
{
target->fuse = 2;
}
else
{
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (&players[i] == source->player)
{
continue;
}
if (playeringame[i] && !players[i].spectator && players[i].lives != 0)
{
break;
}
}
if (i < MAXPLAYERS)
{
// Respawn items in multiplayer, don't respawn them when alone
target->fuse = 2*TICRATE + 2;
}
}
}
}
// if a player avatar dies...
if (target->player)
{
UINT8 i;
target->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block
P_UnsetThingPosition(target);
target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
P_SetThingPosition(target);
target->standingslope = NULL;
target->terrain = NULL;
target->pmomz = 0;
target->player->playerstate = PST_DEAD;
// respawn from where you died
target->player->respawn.pointx = target->x;
target->player->respawn.pointy = target->y;
target->player->respawn.pointz = target->z;
if (target->player == &players[consoleplayer])
{
// don't die in auto map,
// switch view prior to dying
if (automapactive)
AM_Stop();
}
//added : 22-02-98: recenter view for next life...
for (i = 0; i <= r_splitscreen; i++)
{
if (target->player == &players[displayplayers[i]])
{
localaiming[i] = 0;
break;
}
}
if (gametyperules & GTR_BUMPERS)
K_CheckBumpers();
target->player->trickpanel = 0;
}
if (source && target && target->player && source->player)
P_PlayVictorySound(source); // Killer laughs at you. LAUGHS! BWAHAHAHA!
// Other death animation effects
switch(target->type)
{
case MT_BOUNCEPICKUP:
case MT_RAILPICKUP:
case MT_AUTOPICKUP:
case MT_EXPLODEPICKUP:
case MT_SCATTERPICKUP:
case MT_GRENADEPICKUP:
P_SetObjectMomZ(target, FRACUNIT, false);
target->fuse = target->info->damage;
break;
case MT_BUGGLE:
if (inflictor && inflictor->player // did a player kill you? Spawn relative to the player so they're bound to get it
&& P_AproxDistance(inflictor->x - target->x, inflictor->y - target->y) <= inflictor->radius + target->radius + FixedMul(8*FRACUNIT, inflictor->scale) // close enough?
&& inflictor->z <= target->z + target->height + FixedMul(8*FRACUNIT, inflictor->scale)
&& inflictor->z + inflictor->height >= target->z - FixedMul(8*FRACUNIT, inflictor->scale))
mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE);
else
mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE);
mo->destscale = target->scale;
P_SetScale(mo, mo->destscale);
P_SetMobjState(mo, mo->info->raisestate);
break;
case MT_YELLOWSHELL:
P_SpawnMobjFromMobj(target, 0, 0, 0, MT_YELLOWSPRING);
break;
case MT_CRAWLACOMMANDER:
target->momx = target->momy = target->momz = 0;
break;
case MT_CRUSHSTACEAN:
if (target->tracer)
{
mobj_t *chain = target->tracer->target, *chainnext;
while (chain)
{
chainnext = chain->target;
P_RemoveMobj(chain);
chain = chainnext;
}
S_StopSound(target->tracer);
P_KillMobj(target->tracer, inflictor, source, damagetype);
}
break;
case MT_BANPYURA:
if (target->tracer)
{
S_StopSound(target->tracer);
P_KillMobj(target->tracer, inflictor, source, damagetype);
}
break;
case MT_EGGSHIELD:
P_SetObjectMomZ(target, 4*target->scale, false);
P_InstaThrust(target, target->angle, 3*target->scale);
target->flags = (target->flags|MF_NOCLIPHEIGHT) & ~MF_NOGRAVITY;
break;
case MT_DRAGONBOMBER:
{
mobj_t *segment = target;
while (segment->tracer != NULL)
{
P_KillMobj(segment->tracer, NULL, NULL, DMG_NORMAL);
segment = segment->tracer;
}
break;
}
case MT_EGGMOBILE3:
{
mobj_t *mo2;
thinker_t *th;
UINT32 i = 0; // to check how many clones we've removed
// scan the thinkers to make sure all the old pinch dummies are gone on death
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 != (mobjtype_t)target->info->mass)
continue;
if (mo->tracer != target)
continue;
P_KillMobj(mo, inflictor, source, damagetype);
mo->destscale = mo->scale/8;
mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE);
mo->momz = mo->info->speed;
mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
P_InitAngle(mo2, mo->angle);
P_SetMobjState(mo2, S_BOSSSEBH2);
if (++i == 2) // we've already removed 2 of these, let's stop now
break;
else
S_StartSound(mo, mo->info->deathsound); // done once to prevent sound stacking
}
}
break;
case MT_BIGMINE:
if (inflictor)
{
fixed_t dx = target->x - inflictor->x, dy = target->y - inflictor->y, dz = target->z - inflictor->z;
fixed_t dm = FixedHypot(dz, FixedHypot(dy, dx));
target->momx = FixedDiv(FixedDiv(dx, dm), dm)*512;
target->momy = FixedDiv(FixedDiv(dy, dm), dm)*512;
target->momz = FixedDiv(FixedDiv(dz, dm), dm)*512;
}
if (source)
P_SetTarget(&target->tracer, source);
break;
case MT_BLASTEXECUTOR:
if (target->spawnpoint)
P_LinedefExecute(target->spawnpoint->angle, (source ? source : inflictor), target->subsector->sector);
break;
case MT_SPINBOBERT:
if (target->hnext)
P_KillMobj(target->hnext, inflictor, source, damagetype);
if (target->hprev)
P_KillMobj(target->hprev, inflictor, source, damagetype);
break;
case MT_EGGTRAP:
// Time for birdies! Yaaaaaaaay!
target->fuse = TICRATE;
break;
case MT_MINECART:
A_Scream(target);
target->momx = target->momy = target->momz = 0;
if (target->target && target->target->health)
P_KillMobj(target->target, target, source, DMG_NORMAL);
break;
case MT_PLAYER:
if (damagetype != DMG_SPECTATOR)
{
angle_t flingAngle;
mobj_t *kart;
target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player)
target->momx = target->momy = target->momz = 0;
kart = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_KART_LEFTOVER);
if (kart && !P_MobjWasRemoved(kart))
{
kart->angle = target->angle;
kart->color = target->color;
kart->hitlag = target->hitlag;
kart->eflags |= MFE_DAMAGEHITLAG;
P_SetObjectMomZ(kart, 6*FRACUNIT, false);
kart->extravalue1 = target->player->kartweight;
// Copy interp data
kart->old_angle = target->old_angle;
kart->old_x = target->old_x;
kart->old_y = target->old_y;
kart->old_z = target->old_z;
}
if (source && !P_MobjWasRemoved(source))
{
flingAngle = R_PointToAngle2(
source->x - source->momx, source->y - source->momy,
target->x, target->y
);
}
else
{
flingAngle = target->angle + ANGLE_180;
if (P_RandomByte() & 1)
{
flingAngle -= ANGLE_45;
}
else
{
flingAngle += ANGLE_45;
}
}
P_InstaThrust(target, flingAngle, 14 * target->scale);
P_SetObjectMomZ(target, 14*FRACUNIT, false);
P_PlayDeathSound(target);
}
break;
case MT_METALSONIC_RACE:
target->fuse = TICRATE*3;
target->momx = target->momy = target->momz = 0;
P_SetObjectMomZ(target, 14*FRACUNIT, false);
target->flags = (target->flags & ~MF_NOGRAVITY)|(MF_NOCLIP|MF_NOCLIPTHING);
break;
// SRB2Kart:
case MT_SMK_ICEBLOCK:
{
mobj_t *cur = target->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
P_SetMobjState(cur, S_SMK_ICEBLOCK2);
cur = cur->hnext;
}
target->fuse = 10;
S_StartSound(target, sfx_s3k80);
}
break;
case MT_ITEMCAPSULE:
{
UINT8 i;
mobj_t *attacker = inflictor ? inflictor : source;
mobj_t *part = target->hnext;
angle_t angle = FixedAngle(360*P_RandomFixed());
INT16 spacing = (target->radius >> 1) / target->scale;
// set respawn fuse
if (modeattacking) // no respawns
;
else if (target->threshold == KITEM_SUPERRING)
target->fuse = 20*TICRATE;
else
target->fuse = 40*TICRATE;
// burst effects
for (i = 0; i < 2; i++)
{
mobj_t *blast = P_SpawnMobjFromMobj(target, 0, 0, target->info->height >> 1, MT_BATTLEBUMPER_BLAST);
P_InitAngle(blast, angle + i*ANGLE_90);
P_SetScale(blast, 2*blast->scale/3);
blast->destscale = 2*blast->scale;
}
// dust effects
for (i = 0; i < 10; i++)
{
mobj_t *puff = P_SpawnMobjFromMobj(
target,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(0, 4*spacing) * FRACUNIT,
MT_SPINDASHDUST
);
P_SetScale(puff, (puff->destscale *= 2));
puff->momz = puff->scale * P_MobjFlip(puff);
P_Thrust(puff, R_PointToAngle2(target->x, target->y, puff->x, puff->y), 3*puff->scale);
if (attacker)
{
puff->momx += attacker->momx;
puff->momy += attacker->momy;
puff->momz += attacker->momz;
}
}
// remove inside item
if (target->tracer && !P_MobjWasRemoved(target->tracer))
P_RemoveMobj(target->tracer);
// bust capsule caps
while (part && !P_MobjWasRemoved(part))
{
P_InstaThrust(part, part->angle + ANGLE_90, 6 * part->target->scale);
P_SetObjectMomZ(part, 6 * FRACUNIT, false);
part->fuse = TICRATE/2;
part->flags &= ~MF_NOGRAVITY;
if (attacker)
{
part->momx += attacker->momx;
part->momy += attacker->momy;
part->momz += attacker->momz;
}
part = part->hnext;
}
// give the player an item!
if (source && source->player)
{
player_t *player = source->player;
// special behavior for ring capsules
if (target->threshold == KITEM_SUPERRING)
{
K_AwardPlayerRings(player, 5 * target->movecount, true);
break;
}
// special behavior for SPB capsules
if (target->threshold == KITEM_SPB)
{
K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0);
break;
}
if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention
{
player->itemtype = KITEM_SAD;
player->itemamount = 1;
}
else
{
player->itemtype = target->threshold;
if (K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) // never give more than 1 shield
player->itemamount = 1;
else
player->itemamount = max(1, target->movecount);
}
player->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_itemblinkmode] = 0;
player->itemroulette = 0;
player->roulettetype = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolf);
}
break;
}
case MT_BATTLECAPSULE:
{
mobj_t *cur;
numtargets++;
target->fuse = 16;
target->flags |= MF_NOCLIP|MF_NOCLIPTHING;
cur = target->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
// Shoot every piece outward
if (!(cur->x == target->x && cur->y == target->y))
{
P_InstaThrust(cur,
R_PointToAngle2(target->x, target->y, cur->x, cur->y),
R_PointToDist2(target->x, target->y, cur->x, cur->y) / 12
);
}
cur->momz = 8 * target->scale * P_MobjFlip(target);
cur->flags &= ~MF_NOGRAVITY;
cur->tics = TICRATE;
cur->frame &= ~FF_ANIMATE; // Stop animating the propellers
cur = cur->hnext;
}
// All targets busted!
if (numtargets >= maptargets)
{
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
P_DoPlayerExit(&players[i]);
}
}
break;
case MT_BATTLEBUMPER:
{
mobj_t *owner = target->target;
mobj_t *overlay;
S_StartSound(target, sfx_kc52);
target->flags &= ~MF_NOGRAVITY;
target->destscale = (3 * target->destscale) / 2;
target->scalespeed = FRACUNIT/100;
if (owner && !P_MobjWasRemoved(owner))
{
P_Thrust(target, R_PointToAngle2(owner->x, owner->y, target->x, target->y), 4 * target->scale);
}
target->momz += (24 * target->scale) * P_MobjFlip(target);
target->fuse = 8;
overlay = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_OVERLAY);
P_SetTarget(&target->tracer, overlay);
P_SetTarget(&overlay->target, target);
overlay->color = target->color;
P_SetMobjState(overlay, S_INVISIBLE);
}
break;
case MT_DROPTARGET:
case MT_DROPTARGET_SHIELD:
target->fuse = 1;
break;
default:
break;
}
if ((target->type == MT_JAWZ || target->type == MT_JAWZ_DUD || target->type == MT_JAWZ_SHIELD) && !(target->flags2 & MF2_AMBUSH))
{
target->z += P_MobjFlip(target)*20*target->scale;
}
// kill tracer
if (target->type == MT_FROGGER)
{
if (target->tracer && !P_MobjWasRemoved(target->tracer))
P_KillMobj(target->tracer, inflictor, source, DMG_NORMAL);
}
if (target->type == MT_FROGGER || target->type == MT_ROBRA_HEAD || target->type == MT_BLUEROBRA_HEAD) // clean hnext list
{
mobj_t *cur = target->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
P_KillMobj(cur, inflictor, source, DMG_NORMAL);
cur = cur->hnext;
}
}
// Bounce up on death
if (target->type == MT_SMK_PIPE || target->type == MT_SMK_MOLE || target->type == MT_SMK_THWOMP)
{
target->flags &= (~MF_NOGRAVITY);
if (target->eflags & MFE_VERTICALFLIP)
target->z -= target->height;
else
target->z += target->height;
S_StartSound(target, target->info->deathsound);
P_SetObjectMomZ(target, 8<<FRACBITS, false);
if (inflictor)
P_InstaThrust(target, R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y)+ANGLE_90, 16<<FRACBITS);
}
// Final state setting - do something instead of P_SetMobjState;
// Final state setting - do something instead of P_SetMobjState;
if (target->type == MT_SPIKE && target->info->deathstate != S_NULL)
{
const angle_t ang = ((inflictor) ? inflictor->angle : 0) + ANGLE_90;
const fixed_t scale = target->scale;
const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale);
const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
mobj_t *chunk;
fixed_t momz;
S_StartSound(target, target->info->deathsound);
if (target->info->xdeathstate != S_NULL)
{
momz = 6*scale;
if (flip)
momz *= -1;
#define makechunk(angtweak, xmov, ymov) \
chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);\
P_SetMobjState(chunk, target->info->xdeathstate);\
chunk->health = 0;\
P_InitAngle(chunk, angtweak);\
P_UnsetThingPosition(chunk);\
chunk->flags = MF_NOCLIP;\
chunk->x += xmov;\
chunk->y += ymov;\
P_SetThingPosition(chunk);\
P_InstaThrust(chunk,chunk->angle, 4*scale);\
chunk->momz = momz
makechunk(ang + ANGLE_180, -xoffs, -yoffs);
makechunk(ang, xoffs, yoffs);
#undef makechunk
}
momz = 7*scale;
if (flip)
momz *= -1;
chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);
P_SetMobjState(chunk, target->info->deathstate);
chunk->health = 0;
P_InitAngle(chunk, ang + ANGLE_180);
P_UnsetThingPosition(chunk);
chunk->flags = MF_NOCLIP;
chunk->x -= xoffs;
chunk->y -= yoffs;
if (flip)
chunk->z -= 12*scale;
else
chunk->z += 12*scale;
P_SetThingPosition(chunk);
P_InstaThrust(chunk, chunk->angle, 2*scale);
chunk->momz = momz;
P_SetMobjState(target, target->info->deathstate);
target->health = 0;
target->angle = ang;
P_UnsetThingPosition(target);
target->flags = MF_NOCLIP;
target->x += xoffs;
target->y += yoffs;
target->z = chunk->z;
P_SetThingPosition(target);
P_InstaThrust(target, target->angle, 2*scale);
target->momz = momz;
}
else if (target->type == MT_WALLSPIKE && target->info->deathstate != S_NULL)
{
const angle_t ang = (/*(inflictor) ? inflictor->angle : */target->angle) + ANGLE_90;
const fixed_t scale = target->scale;
const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale), forwardxoffs = P_ReturnThrustX(target, target->angle, 7*scale), forwardyoffs = P_ReturnThrustY(target, target->angle, 7*scale);
const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
mobj_t *chunk;
boolean sprflip;
S_StartSound(target, target->info->deathsound);
if (!P_MobjWasRemoved(target->tracer))
P_RemoveMobj(target->tracer);
if (target->info->xdeathstate != S_NULL)
{
sprflip = P_RandomChance(FRACUNIT/2);
#define makechunk(angtweak, xmov, ymov) \
chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\
P_SetMobjState(chunk, target->info->xdeathstate);\
chunk->health = 0;\
P_InitAngle(chunk, target->angle);\
P_UnsetThingPosition(chunk);\
chunk->flags = MF_NOCLIP;\
chunk->x += xmov - forwardxoffs;\
chunk->y += ymov - forwardyoffs;\
P_SetThingPosition(chunk);\
P_InstaThrust(chunk, angtweak, 4*scale);\
chunk->momz = P_RandomRange(5, 7)*scale;\
if (flip)\
chunk->momz *= -1;\
if (sprflip)\
chunk->frame |= FF_VERTICALFLIP
makechunk(ang + ANGLE_180, -xoffs, -yoffs);
sprflip = !sprflip;
makechunk(ang, xoffs, yoffs);
#undef makechunk
}
sprflip = P_RandomChance(FRACUNIT/2);
chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);
P_SetMobjState(chunk, target->info->deathstate);
chunk->health = 0;
P_InitAngle(chunk, target->angle);
P_UnsetThingPosition(chunk);
chunk->flags = MF_NOCLIP;
chunk->x += forwardxoffs - xoffs;
chunk->y += forwardyoffs - yoffs;
P_SetThingPosition(chunk);
P_InstaThrust(chunk, ang + ANGLE_180, 2*scale);
chunk->momz = P_RandomRange(5, 7)*scale;
if (flip)
chunk->momz *= -1;
if (sprflip)
chunk->frame |= FF_VERTICALFLIP;
P_SetMobjState(target, target->info->deathstate);
target->health = 0;
P_UnsetThingPosition(target);
target->flags = MF_NOCLIP;
target->x += forwardxoffs + xoffs;
target->y += forwardyoffs + yoffs;
P_SetThingPosition(target);
P_InstaThrust(target, ang, 2*scale);
target->momz = P_RandomRange(5, 7)*scale;
if (flip)
target->momz *= -1;
if (!sprflip)
target->frame |= FF_VERTICALFLIP;
}
else if (target->player)
{
P_SetPlayerMobjState(target, target->info->deathstate);
}
else
#ifdef DEBUG_NULL_DEATHSTATE
P_SetMobjState(target, S_NULL);
#else
P_SetMobjState(target, target->info->deathstate);
#endif
/** \note For player, the above is redundant because of P_SetMobjState (target, S_PLAY_DIE1)
in P_DamageMobj()
Graue 12-22-2003 */
}
static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
{
(void)inflictor;
(void)damage;
// SRB2Kart: We want to hurt ourselves, so it's now DMG_CANTHURTSELF
if (damagetype & DMG_CANTHURTSELF)
{
// You can't kill yourself, idiot...
if (source == target)
return false;
if (G_GametypeHasTeams())
{
// Don't hurt your team, either!
if (source->player->ctfteam == target->player->ctfteam)
return false;
}
}
return true;
}
static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 type)
{
(void)source;
if (player->exiting)
{
player->mo->destscale = 1;
player->mo->flags |= MF_NOCLIPTHING;
return false;
}
K_DestroyBumpers(player, 1);
switch (type)
{
case DMG_DEATHPIT:
// Respawn kill types
K_DoIngameRespawn(player);
return false;
case DMG_SPECTATOR:
// disappearifies, but still gotta put items back in play
break;
default:
// Everything else REALLY kills
if (leveltime < starttime)
{
K_DoFault(player);
}
break;
}
K_DropEmeraldsFromPlayer(player, player->emeralds);
K_SetHitLagForObjects(player->mo, inflictor, MAXHITLAGTICS, true);
player->carry = CR_NONE;
K_KartResetPlayerColor(player);
P_ResetPlayer(player);
if (player->spectator == false)
{
player->mo->renderflags &= ~RF_DONTDRAW;
}
P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
if (type == DMG_TIMEOVER)
{
if (gametyperules & GTR_CIRCUIT)
{
mobj_t *boom;
player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP);
player->mo->renderflags |= RF_DONTDRAW;
boom = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FZEROBOOM);
boom->scale = player->mo->scale;
P_InitAngle(boom, player->mo->angle);
P_SetTarget(&boom->target, player->mo);
}
K_DestroyBumpers(player, player->bumpers);
player->pflags |= PF_ELIMINATED;
}
return true;
}
/** Damages an object, which may or may not be a player.
* For melee attacks, source and inflictor are the same.
*
* \param target The object being damaged.
* \param inflictor The thing that caused the damage: creature, missile,
* gargoyle, and so forth. Can be NULL in the case of
* environmental damage, such as slime or crushing.
* \param source The creature or person responsible. For example, if a
* player is hit by a ring, the player who shot it. In some
* cases, the target will go after this object after
* receiving damage. This can be NULL.
* \param damage Amount of damage to be dealt.
* \param damagetype Type of damage to be dealt. If bit 7 (0x80) is set, this is an instant-kill.
* \return True if the target sustained damage, otherwise false.
* \todo Clean up this mess, split into multiple functions.
* \sa P_KillMobj
*/
boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
{
player_t *player;
boolean force = false;
INT32 laglength = 6;
if (objectplacing)
return false;
if (target->health <= 0)
return false;
// Spectator handling
if (damagetype != DMG_SPECTATOR && target->player && target->player->spectator)
return false;
if (source && source->player && source->player->spectator)
return false;
if (((damagetype & DMG_TYPEMASK) == DMG_STING)
|| ((inflictor && !P_MobjWasRemoved(inflictor)) && inflictor->type == MT_BANANA && inflictor->health <= 1))
{
laglength = 2;
}
else if (target->type == MT_DROPTARGET || target->type == MT_DROPTARGET_SHIELD)
{
laglength = 0; // handled elsewhere
}
// Everything above here can't be forced.
if (!metalrecording)
{
UINT8 shouldForce = LUA_HookShouldDamage(target, inflictor, source, damage, damagetype);
if (P_MobjWasRemoved(target))
return (shouldForce == 1); // mobj was removed
if (shouldForce == 1)
force = true;
else if (shouldForce == 2)
return false;
}
if (!force)
{
if (!(target->flags & MF_SHOOTABLE))
return false; // shouldn't happen...
if (!(damagetype & DMG_DEATHMASK) && target->hitlag > 0 && inflictor == NULL)
return false;
}
if (target->flags2 & MF2_SKULLFLY)
target->momx = target->momy = target->momz = 0;
if (target->flags & (MF_ENEMY|MF_BOSS))
{
if (!force && target->flags2 & MF2_FRET) // Currently flashing from being hit
return false;
if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
return true;
if (target->health > 1)
target->flags2 |= MF2_FRET;
}
player = target->player;
if (player) // Player is the target
{
if (player->pflags & PF_GODMODE)
return false;
if (!force)
{
// Player hits another player
if (source && source->player)
{
if (!P_PlayerHitsPlayer(target, inflictor, source, damage, damagetype))
return false;
}
}
// Instant-Death
if ((damagetype & DMG_DEATHMASK))
{
if (!P_KillPlayer(player, inflictor, source, damagetype))
return false;
}
else if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
{
return true;
}
else
{
const UINT8 type = (damagetype & DMG_TYPEMASK);
const boolean hardhit = (type == DMG_EXPLODE || type == DMG_KARMA || type == DMG_TUMBLE); // This damage type can do evil stuff like ALWAYS combo
INT16 ringburst = 5;
// Check if the player is allowed to be damaged!
// If not, then spawn the instashield effect instead.
if (!force && !(inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1 == 1))
{
if (gametyperules & GTR_BUMPERS)
{
if (player->bumpers <= 0 && player->karmadelay)
{
// No bumpers & in WAIT, can't be hurt
K_DoInstashield(player);
return false;
}
}
else
{
if (damagetype & DMG_STEAL)
{
// Gametype does not have bumpers, steal damage is intended to not do anything
// (No instashield is intentional)
return false;
}
}
if (player->invincibilitytimer > 0 || player->growshrinktimer > 0 || player->hyudorotimer > 0)
{
// Full invulnerability
K_DoInstashield(player);
return false;
}
{
// Check if we should allow wombo combos (hard hits by default, inverted by the presence of DMG_WOMBO).
boolean allowcombo = (hardhit == !(damagetype & DMG_WOMBO));
// Tumble is a special case.
if (type == DMG_TUMBLE)
{
// don't allow constant combo
if (player->tumbleBounces == 1 && (P_MobjFlip(target)*target->momz > 0))
allowcombo = false;
}
if ((target->hitlag == 0 || allowcombo == false) && player->flashing > 0)
{
// Post-hit invincibility
K_DoInstashield(player);
return false;
}
}
}
// We successfully damaged them! Give 'em some bumpers!
if (type != DMG_STING)
{
UINT8 takeBumpers = 1;
if (damagetype & DMG_STEAL)
{
takeBumpers = 2;
if (type == DMG_KARMA)
{
takeBumpers = player->bumpers;
}
}
else
{
if (type == DMG_KARMA)
{
// Take half of their bumpers for karma comeback damage
takeBumpers = max(1, player->bumpers / 2);
}
}
if (source && source != player->mo && source->player)
{
// Extend the invincibility if the hit was a direct hit.
if (inflictor == source && source->player->invincibilitytimer)
{
tic_t kinvextend;
if (gametype == GT_BATTLE)
kinvextend = 2*TICRATE;
else
kinvextend = 5*TICRATE;
source->player->invincibilitytimer += kinvextend;
}
K_TryHurtSoundExchange(target, source);
K_BattleAwardHit(source->player, player, inflictor, takeBumpers);
K_TakeBumpersFromPlayer(source->player, player, takeBumpers);
if (type == DMG_KARMA)
{
// Destroy any remainder bumpers from the player for karma comeback damage
K_DestroyBumpers(player, player->bumpers);
}
else
{
source->player->overtimekarma += 5*TICRATE;
}
if (damagetype & DMG_STEAL)
{
// Give them ALL of your emeralds instantly :)
source->player->emeralds |= player->emeralds;
player->emeralds = 0;
K_CheckEmeralds(source->player);
}
}
else
{
K_DestroyBumpers(player, takeBumpers);
}
if (!(damagetype & DMG_STEAL))
{
// Drop all of your emeralds
K_DropEmeraldsFromPlayer(player, player->emeralds);
}
}
player->sneakertimer = player->numsneakers = 0;
player->driftboost = player->strongdriftboost = 0;
player->ringboost = 0;
player->glanceDir = 0;
player->pflags &= ~PF_LOOKDOWN;
switch (type)
{
case DMG_STING:
K_DebtStingPlayer(player, source);
K_KartPainEnergyFling(player);
ringburst = 0;
break;
case DMG_TUMBLE:
K_TumblePlayer(player, inflictor, source);
ringburst = 10;
break;
case DMG_EXPLODE:
case DMG_KARMA:
ringburst = K_ExplodePlayer(player, inflictor, source);
break;
case DMG_WIPEOUT:
if (P_IsDisplayPlayer(player))
P_StartQuake(32<<FRACBITS, 5);
K_SpinPlayer(player, inflictor, source, KSPIN_WIPEOUT);
K_KartPainEnergyFling(player);
break;
case DMG_NORMAL:
default:
K_SpinPlayer(player, inflictor, source, KSPIN_SPINOUT);
break;
}
if (type != DMG_STING)
{
player->flashing = K_GetKartFlashing(player);
}
P_PlayRinglossSound(target);
if (ringburst > 0)
{
P_PlayerRingBurst(player, ringburst);
}
K_PlayPainSound(target, source);
if ((hardhit == true) || (cv_kartdebughuddrop.value && !modeattacking))
{
K_DropItems(player);
}
else
{
K_DropHnextList(player, false);
}
player->instashield = 15;
K_SetHitLagForObjects(target, inflictor, laglength, true);
if (inflictor && !P_MobjWasRemoved(inflictor) && inflictor->type == MT_BANANA)
{
player->flipDI = true;
}
return true;
}
}
else
{
if (damagetype & DMG_STEAL)
{
// Not a player, steal damage is intended to not do anything
return false;
}
}
// do the damage
if (damagetype & DMG_DEATHMASK)
target->health = 0;
else
target->health -= damage;
if (source && source->player && target)
G_GhostAddHit((INT32) (source->player - players), target);
K_SetHitLagForObjects(target, inflictor, laglength, true);
if (target->health <= 0)
{
P_KillMobj(target, inflictor, source, damagetype);
return true;
}
//K_SetHitLagForObjects(target, inflictor, laglength, true);
if (player)
P_ResetPlayer(target->player);
else
P_SetMobjState(target, target->info->painstate);
if (!P_MobjWasRemoved(target))
{
// if not intent on another player,
// chase after this one
P_SetTarget(&target->target, source);
}
return true;
}
static void P_FlingBurst
( player_t *player,
angle_t fa,
mobjtype_t objType,
tic_t objFuse,
fixed_t objScale,
INT32 i)
{
mobj_t *mo;
fixed_t ns;
fixed_t momxy = 5<<FRACBITS, momz = 12<<FRACBITS; // base horizonal/vertical thrusts
INT32 mx = (i + 1) >> 1;
mo = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, objType);
mo->threshold = 10; // not useful for spikes
mo->fuse = objFuse;
P_SetTarget(&mo->target, player->mo);
if (objScale != FRACUNIT)
{
P_SetScale(mo, FixedMul(objScale, mo->scale));
mo->destscale = mo->scale;
}
/*
0: 0
1: 1 = (1+1)/2 = 1
2: 1 = (2+1)/2 = 1
3: 2 = (3+1)/2 = 2
4: 2 = (4+1)/2 = 2
5: 3 = (4+1)/2 = 2
*/
// Angle / height offset changes every other ring
momxy -= mx * FRACUNIT;
momz += mx * (2<<FRACBITS);
if (i & 1)
fa += ANGLE_180;
ns = FixedMul(momxy, player->mo->scale);
mo->momx = (mo->target->momx/2) + FixedMul(FINECOSINE(fa>>ANGLETOFINESHIFT), ns);
mo->momy = (mo->target->momy/2) + FixedMul(FINESINE(fa>>ANGLETOFINESHIFT), ns);
ns = FixedMul(momz, player->mo->scale);
mo->momz = (mo->target->momz/2) + ((ns) * P_MobjFlip(mo));
}
/** Spills an injured player's rings.
*
* \param player The player who is losing rings.
* \param num_rings Number of rings lost. A maximum of 20 rings will be
* spawned.
* \sa P_PlayerFlagBurst
*/
void P_PlayerRingBurst(player_t *player, INT32 num_rings)
{
INT32 num_fling_rings;
INT32 i;
angle_t fa;
// Rings shouldn't be in Battle!
if (gametyperules & GTR_SPHERES)
return;
// Better safe than sorry.
if (!player)
return;
// Have a shield? You get hit, but don't lose your rings!
if (K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE)
return;
// 20 is the maximum number of rings that can be taken from you at once - half the span of your counter
if (num_rings > 20)
num_rings = 20;
else if (num_rings <= 0)
return;
num_rings = -P_GivePlayerRings(player, -num_rings);
num_fling_rings = num_rings+min(0, player->rings);
// determine first angle
fa = player->mo->angle + ((P_RandomByte() & 1) ? -ANGLE_90 : ANGLE_90);
for (i = 0; i < num_fling_rings; i++)
{
P_FlingBurst(player, fa, MT_FLINGRING, 60*TICRATE, FRACUNIT, i);
}
while (i < num_rings)
{
P_FlingBurst(player, fa, MT_DEBTSPIKE, 0, 3 * FRACUNIT / 2, i++);
}
}