// 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<mo->angle = special->target->angle; P_SetObjectMomZ(player->mo, 12<mo, player->mo->angle, 20<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)<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<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)<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<x, inflictor->y, target->x, target->y)+ANGLE_90, 16<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<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<> 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<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++); } }