mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1485 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1485 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// DR. ROBOTNIK'S RING RACERS
 | 
						|
//-----------------------------------------------------------------------------
 | 
						|
// Copyright (C) 2025 by Kart Krew.
 | 
						|
//
 | 
						|
// This program is free software distributed under the
 | 
						|
// terms of the GNU General Public License, version 2.
 | 
						|
// See the 'LICENSE' file for more details.
 | 
						|
//-----------------------------------------------------------------------------
 | 
						|
/// \file  k_collide.cpp
 | 
						|
/// \brief SRB2Kart item collision hooks
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
 | 
						|
#include "k_collide.h"
 | 
						|
#include "doomtype.h"
 | 
						|
#include "p_mobj.h"
 | 
						|
#include "k_kart.h"
 | 
						|
#include "p_local.h"
 | 
						|
#include "s_sound.h"
 | 
						|
#include "r_main.h" // R_PointToAngle2, R_PointToDist2
 | 
						|
#include "hu_stuff.h" // Sink snipe print
 | 
						|
#include "doomdef.h" // Sink snipe print
 | 
						|
#include "g_game.h" // Sink snipe print
 | 
						|
#include "k_objects.h"
 | 
						|
#include "k_roulette.h"
 | 
						|
#include "k_podium.h"
 | 
						|
#include "k_powerup.h"
 | 
						|
#include "k_hitlag.h"
 | 
						|
#include "m_random.h"
 | 
						|
#include "k_hud.h" // K_AddMessage
 | 
						|
#include "m_easing.h"
 | 
						|
#include "r_skins.h"
 | 
						|
 | 
						|
angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	fixed_t momux, momuy;
 | 
						|
	angle_t test;
 | 
						|
 | 
						|
	if (!(t1->flags & MF_PAPERCOLLISION))
 | 
						|
	{
 | 
						|
		return R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90;
 | 
						|
	}
 | 
						|
 | 
						|
	test = R_PointToAngle2(0, 0, t2->momx, t2->momy) + ANGLE_90 - t1->angle;
 | 
						|
	if (test > ANGLE_180)
 | 
						|
		test = t1->angle + ANGLE_180;
 | 
						|
	else
 | 
						|
		test = t1->angle;
 | 
						|
 | 
						|
	// intentional way around - sine...
 | 
						|
	momuy = P_AproxDistance(t2->momx, t2->momy);
 | 
						|
	momux = t2->momx - P_ReturnThrustY(t2, test, 2*momuy);
 | 
						|
	momuy = t2->momy - P_ReturnThrustX(t2, test, 2*momuy);
 | 
						|
 | 
						|
	return R_PointToAngle2(0, 0, momux, momuy);
 | 
						|
}
 | 
						|
 | 
						|
extern "C" consvar_t cv_debugpickmeup;
 | 
						|
 | 
						|
boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	boolean damageitem = false;
 | 
						|
 | 
						|
	if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t1->health <= 0 || t2->health <= 0)
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (((t1->type == MT_BANANA_SHIELD) && (t2->type == MT_BANANA_SHIELD))
 | 
						|
		&& (t1->target == t2->target)) // Don't hit each other if you have the same target
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t1->type == MT_BALLHOG && t2->type == MT_BALLHOG)
 | 
						|
		return true; // Ballhogs don't collide with eachother
 | 
						|
 | 
						|
	if (t1->type == MT_BALLHOGBOOM && t2->type == MT_BALLHOGBOOM)
 | 
						|
		return true; // Ballhogs don't collide with eachother
 | 
						|
 | 
						|
	if (t1->type == MT_BALLHOGBOOM && t2->type == MT_PLAYER && t1->target == t2 && !cv_debugpickmeup.value)
 | 
						|
		return true; // Allied hog explosion, not snatchable but shouldn't damage
 | 
						|
 | 
						|
	if (K_TryPickMeUp(t1, t2, false))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		if (t2->player->flashing > 0 && t2->hitlag == 0)
 | 
						|
			return true;
 | 
						|
 | 
						|
		// Banana snipe!
 | 
						|
		if (t1->type == MT_BANANA && t1->health > 1)
 | 
						|
			S_StartSound(t2, sfx_bsnipe);
 | 
						|
 | 
						|
		if (t1->type != MT_BALLHOGBOOM) // ballhog booms linger and expire after their anim is done
 | 
						|
		{
 | 
						|
			damageitem = true;
 | 
						|
		}
 | 
						|
 | 
						|
		if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
 | 
						|
		{
 | 
						|
			// Melt item
 | 
						|
			S_StartSound(t2, sfx_s3k43);
 | 
						|
		}
 | 
						|
		else if (K_IsRidingFloatingTop(t2->player))
 | 
						|
		{
 | 
						|
			// Float over silly banana
 | 
						|
			damageitem = false;
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL|DMG_WOMBO);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
 | 
						|
		|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
 | 
						|
		|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD
 | 
						|
		|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
 | 
						|
	{
 | 
						|
		// Other Item Damage
 | 
						|
		angle_t bounceangle = K_GetCollideAngle(t1, t2);
 | 
						|
 | 
						|
		S_StartSound(t2, t2->info->deathsound);
 | 
						|
		P_KillMobj(t2, t1, t1, DMG_NORMAL);
 | 
						|
 | 
						|
		P_SetObjectMomZ(t2, 24*FRACUNIT, false);
 | 
						|
		P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
 | 
						|
 | 
						|
		P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
 | 
						|
 | 
						|
		damageitem = true;
 | 
						|
	}
 | 
						|
	else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
 | 
						|
	{
 | 
						|
		damageitem = true;
 | 
						|
		// Bomb death
 | 
						|
		P_KillMobj(t2, t1, t1, DMG_NORMAL);
 | 
						|
	}
 | 
						|
	else if (t2->flags & MF_SHOOTABLE)
 | 
						|
	{
 | 
						|
		// Shootable damage
 | 
						|
		P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
 | 
						|
		damageitem = true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (damageitem && P_MobjWasRemoved(t1) == false)
 | 
						|
	{
 | 
						|
		angle_t bounceangle;
 | 
						|
 | 
						|
		if (P_MobjWasRemoved(t2) == false)
 | 
						|
		{
 | 
						|
			bounceangle = K_GetCollideAngle(t2, t1);
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			bounceangle = K_MomentumAngle(t1) + ANGLE_90;
 | 
						|
			t2 = NULL; // handles the arguments to P_KillMobj
 | 
						|
		}
 | 
						|
 | 
						|
		// This Item Damage
 | 
						|
		S_StartSound(t1, t1->info->deathsound);
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
 | 
						|
		P_SetObjectMomZ(t1, 24*FRACUNIT, false);
 | 
						|
 | 
						|
		P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	// Push fakes out of other item boxes
 | 
						|
	if (t2->type == MT_RANDOMITEM || t2->type == MT_EGGMANITEM)
 | 
						|
	{
 | 
						|
		P_InstaThrust(t1, R_PointToAngle2(t2->x, t2->y, t1->x, t1->y), t2->radius/4);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		if ((t1->target == t2 || t1->target == t2->target) && (t1->threshold > 0))
 | 
						|
			return true;
 | 
						|
 | 
						|
		if (t1->health <= 0 || t2->health <= 0)
 | 
						|
			return true;
 | 
						|
 | 
						|
		if (K_TryPickMeUp(t1, t2, false))
 | 
						|
			return true;
 | 
						|
 | 
						|
		if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX))
 | 
						|
			return true;
 | 
						|
 | 
						|
		K_DropItems(t2->player);
 | 
						|
		K_StartEggmanRoulette(t2->player);
 | 
						|
 | 
						|
		if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
 | 
						|
		{
 | 
						|
			// Melt item
 | 
						|
			S_StartSound(t2, sfx_s3k43);
 | 
						|
			P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			Obj_SpawnItemDebrisEffects(t1, t2);
 | 
						|
 | 
						|
#if 0
 | 
						|
			// Eggbox snipe!
 | 
						|
			if (t1->type == MT_EGGMANITEM && t1->health > 1)
 | 
						|
				S_StartSound(t2, sfx_bsnipe);
 | 
						|
#endif
 | 
						|
 | 
						|
			if (t1->target && t1->target->player)
 | 
						|
			{
 | 
						|
				t2->player->eggmanblame = t1->target->player - players;
 | 
						|
 | 
						|
				if (t1->target->hnext == t1)
 | 
						|
				{
 | 
						|
					P_SetTarget(&t1->target->hnext, NULL);
 | 
						|
					t1->target->player->itemflags &= ~IF_EGGMANOUT;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			P_RemoveMobj(t1);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static mobj_t *grenade;
 | 
						|
static fixed_t explodedist;
 | 
						|
static boolean explodespin;
 | 
						|
static INT32 minehitlag;
 | 
						|
 | 
						|
static inline boolean PIT_SSMineChecks(mobj_t *thing)
 | 
						|
{
 | 
						|
	if (thing == grenade) // Don't explode yourself! Endless loop!
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (thing->health <= 0)
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (!(thing->flags & MF_SHOOTABLE) || (thing->flags & MF_SCENERY))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (thing->player && (thing->player->spectator || thing->player->hyudorotimer > 0))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (P_AproxDistance(P_AproxDistance(thing->x - grenade->x, thing->y - grenade->y), thing->z - grenade->z) > explodedist)
 | 
						|
		return true; // Too far away
 | 
						|
 | 
						|
	if (P_CheckSight(grenade, thing) == false)
 | 
						|
		return true; // Not in sight
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
extern "C" consvar_t cv_debugpickmeup;
 | 
						|
 | 
						|
static inline BlockItReturn_t PIT_SSMineSearch(mobj_t *thing)
 | 
						|
{
 | 
						|
	if (grenade == NULL || P_MobjWasRemoved(grenade))
 | 
						|
		return BMIT_ABORT; // There's the possibility these can chain react onto themselves after they've already died if there are enough all in one spot
 | 
						|
 | 
						|
	if (grenade->flags2 & MF2_DEBRIS) // don't explode twice
 | 
						|
		return BMIT_ABORT;
 | 
						|
 | 
						|
	switch (thing->type)
 | 
						|
	{
 | 
						|
	case MT_PLAYER: // Don't explode for anything but an actual player.
 | 
						|
	case MT_SPECIAL_UFO: // Also UFO catcher
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!cv_debugpickmeup.value)
 | 
						|
	{
 | 
						|
		if (grenade->target && !P_MobjWasRemoved(grenade->target))
 | 
						|
		{
 | 
						|
			if (thing == grenade->target) // Don't blow up at your owner instantly.
 | 
						|
				return BMIT_CONTINUE;
 | 
						|
 | 
						|
			if (grenade->target->player && thing->player && G_SameTeam(grenade->target->player, thing->player))
 | 
						|
				return BMIT_CONTINUE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (PIT_SSMineChecks(thing) == true)
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
 | 
						|
	// Explode!
 | 
						|
	P_SetMobjState(grenade, grenade->info->deathstate);
 | 
						|
	return BMIT_ABORT;
 | 
						|
}
 | 
						|
 | 
						|
void K_DoMineSearch(mobj_t *actor, fixed_t size)
 | 
						|
{
 | 
						|
	INT32 bx, by, xl, xh, yl, yh;
 | 
						|
 | 
						|
	explodedist = FixedMul(size, actor->scale);
 | 
						|
	grenade = actor;
 | 
						|
 | 
						|
	yh = (unsigned)(actor->y + (explodedist + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	yl = (unsigned)(actor->y - (explodedist + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	xh = (unsigned)(actor->x + (explodedist + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
	xl = (unsigned)(actor->x - (explodedist + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
 | 
						|
	BMBOUNDFIX (xl, xh, yl, yh);
 | 
						|
 | 
						|
	for (by = yl; by <= yh; by++)
 | 
						|
		for (bx = xl; bx <= xh; bx++)
 | 
						|
			P_BlockThingsIterator(bx, by, PIT_SSMineSearch);
 | 
						|
}
 | 
						|
 | 
						|
static inline BlockItReturn_t PIT_SSMineExplode(mobj_t *thing)
 | 
						|
{
 | 
						|
	const INT32 oldhitlag = thing->hitlag;
 | 
						|
	INT32 lagadded;
 | 
						|
 | 
						|
	if (grenade == NULL || P_MobjWasRemoved(grenade))
 | 
						|
		return BMIT_ABORT; // There's the possibility these can chain react onto themselves after they've already died if there are enough all in one spot
 | 
						|
 | 
						|
#if 0
 | 
						|
	if (grenade->flags2 & MF2_DEBRIS) // don't explode twice
 | 
						|
		return BMIT_ABORT;
 | 
						|
#endif
 | 
						|
 | 
						|
	if (PIT_SSMineChecks(thing) == true)
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
 | 
						|
	// Don't do Big Boy Damage to the UFO Catcher with
 | 
						|
	// lingering spinout damage
 | 
						|
	if (thing->type == MT_SPECIAL_UFO && explodespin)
 | 
						|
	{
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	P_DamageMobj(thing, grenade, grenade->target, 1, (explodespin ? DMG_NORMAL : DMG_EXPLODE));
 | 
						|
 | 
						|
	lagadded = (thing->hitlag - oldhitlag);
 | 
						|
 | 
						|
	if (lagadded > minehitlag)
 | 
						|
	{
 | 
						|
		minehitlag = lagadded;
 | 
						|
	}
 | 
						|
 | 
						|
	return BMIT_CONTINUE;
 | 
						|
}
 | 
						|
 | 
						|
tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin)
 | 
						|
{
 | 
						|
	INT32 bx, by, xl, xh, yl, yh;
 | 
						|
 | 
						|
	explodespin = spin;
 | 
						|
	explodedist = FixedMul(size, actor->scale);
 | 
						|
	grenade = actor;
 | 
						|
	minehitlag = 0;
 | 
						|
 | 
						|
	// Use blockmap to check for nearby shootables
 | 
						|
	yh = (unsigned)(actor->y + explodedist - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	yl = (unsigned)(actor->y - explodedist - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	xh = (unsigned)(actor->x + explodedist - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
	xl = (unsigned)(actor->x - explodedist - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
 | 
						|
	BMBOUNDFIX (xl, xh, yl, yh);
 | 
						|
 | 
						|
	for (by = yl; by <= yh; by++)
 | 
						|
		for (bx = xl; bx <= xh; bx++)
 | 
						|
			P_BlockThingsIterator(bx, by, PIT_SSMineExplode);
 | 
						|
 | 
						|
	// Set this flag to ensure that the inital action won't be triggered twice.
 | 
						|
	actor->flags2 |= MF2_DEBRIS;
 | 
						|
 | 
						|
	if (minehitlag == 0)
 | 
						|
	{
 | 
						|
		minehitlag = actor->hitlag;
 | 
						|
	}
 | 
						|
 | 
						|
	// Set this flag to ensure the hitbox timer doesn't get extended with every player hit
 | 
						|
	actor->flags |= MF_NOHITLAGFORME;
 | 
						|
	actor->hitlag = 0; // same deal
 | 
						|
 | 
						|
	if (!spin)
 | 
						|
	{
 | 
						|
		return minehitlag;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_MineCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t1->health <= 0 || t2->health <= 0)
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		if (t2->player->flashing > 0 && t2->hitlag == 0)
 | 
						|
			return true;
 | 
						|
 | 
						|
		if (K_TryPickMeUp(t1, t2, false))
 | 
						|
			return true;
 | 
						|
 | 
						|
		// Bomb punting
 | 
						|
		if ((t1->state >= &states[S_SSMINE1] && t1->state <= &states[S_SSMINE4])
 | 
						|
			|| (t1->state >= &states[S_SSMINE_DEPLOY8] && t1->state <= &states[S_SSMINE_EXPLODE2]))
 | 
						|
		{
 | 
						|
			P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			K_PuntMine(t1, t2);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ
 | 
						|
		|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD
 | 
						|
		|| t2->type == MT_GACHABOM)
 | 
						|
	{
 | 
						|
		// Bomb death
 | 
						|
		angle_t bounceangle = K_GetCollideAngle(t1, t2);
 | 
						|
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
 | 
						|
		// Other Item Damage
 | 
						|
		S_StartSound(t2, t2->info->deathsound);
 | 
						|
		P_KillMobj(t2, t1, t1, DMG_NORMAL);
 | 
						|
 | 
						|
		P_SetObjectMomZ(t2, 24*FRACUNIT, false);
 | 
						|
		P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
 | 
						|
	}
 | 
						|
	else if (t2->flags & MF_SHOOTABLE)
 | 
						|
	{
 | 
						|
		// Bomb death
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
		// Shootable damage
 | 
						|
		P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t1->health <= 0 || t2->health <= 0)
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (K_TryPickMeUp(t1, t2, false))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		const INT32 oldhitlag = t2->hitlag;
 | 
						|
 | 
						|
		if (t2->player->flashing)
 | 
						|
			return true;
 | 
						|
 | 
						|
		// Banana snipe!
 | 
						|
		if (t1->health > 1)
 | 
						|
		{
 | 
						|
			if (t1->target
 | 
						|
			&& t1->target->player
 | 
						|
			&& t2->player != t1->target->player)
 | 
						|
			{
 | 
						|
				t1->target->player->roundconditions.landmine_dunk = true;
 | 
						|
				t1->target->player->roundconditions.checkthisframe = true;
 | 
						|
			}
 | 
						|
 | 
						|
			S_StartSound(t2, sfx_bsnipe);
 | 
						|
		}
 | 
						|
 | 
						|
		if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
 | 
						|
		{
 | 
						|
			// Melt item
 | 
						|
			S_StartSound(t2, sfx_s3k43);
 | 
						|
			K_SetHitLagForObjects(t2, t1, t1->target, 3, false);
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			// Player Damage
 | 
						|
			P_DamageMobj(t2, t1, t1->target, 1, DMG_TUMBLE);
 | 
						|
		}
 | 
						|
 | 
						|
		t1->reactiontime = (t2->hitlag - oldhitlag);
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
	}
 | 
						|
	else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
 | 
						|
		|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
 | 
						|
		|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD
 | 
						|
		|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
 | 
						|
	{
 | 
						|
		// Other Item Damage
 | 
						|
		angle_t bounceangle = K_GetCollideAngle(t1, t2);
 | 
						|
 | 
						|
		if (t2->eflags & MFE_VERTICALFLIP)
 | 
						|
			t2->z -= t2->height;
 | 
						|
		else
 | 
						|
			t2->z += t2->height;
 | 
						|
 | 
						|
		P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
 | 
						|
 | 
						|
		S_StartSound(t2, t2->info->deathsound);
 | 
						|
		P_KillMobj(t2, t1, t1, DMG_NORMAL);
 | 
						|
 | 
						|
		if (P_MobjWasRemoved(t2))
 | 
						|
		{
 | 
						|
			t2 = NULL; // handles the arguments to P_KillMobj
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			P_SetObjectMomZ(t2, 24*FRACUNIT, false);
 | 
						|
			P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
 | 
						|
 | 
						|
			t1->reactiontime = t2->hitlag;
 | 
						|
		}
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
	}
 | 
						|
	else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
 | 
						|
	{
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
		// Bomb death
 | 
						|
		P_KillMobj(t2, t1, t1, DMG_NORMAL);
 | 
						|
	}
 | 
						|
	else if (t2->flags & MF_SHOOTABLE)
 | 
						|
	{
 | 
						|
		// Shootable damage
 | 
						|
		P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
 | 
						|
 | 
						|
		if (P_MobjWasRemoved(t2))
 | 
						|
		{
 | 
						|
			t2 = NULL; // handles the arguments to P_KillMobj
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			t1->reactiontime = t2->hitlag;
 | 
						|
		}
 | 
						|
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	mobj_t *draggeddroptarget = (t1->type == MT_DROPTARGET_SHIELD) ? t1->target : NULL;
 | 
						|
	UINT8 strength;
 | 
						|
 | 
						|
	if (((t1->target == t2) || (t1->target == t2->target)) && ((t1->threshold > 0 && t2->type == MT_PLAYER) || (t2->type != MT_PLAYER && t2->threshold > 0)))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t1->health <= 0 || t2->health <= 0)
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t2->player && (t2->player->hyudorotimer || t2->player->justbumped))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (K_TryPickMeUp(t1, t2, false))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (draggeddroptarget && P_MobjWasRemoved(draggeddroptarget))
 | 
						|
		draggeddroptarget = NULL; // Beware order-of-execution on crushers, I guess?!
 | 
						|
 | 
						|
	if (t1->health > 3) // forward thrown
 | 
						|
	{
 | 
						|
		strength = 0;
 | 
						|
	}
 | 
						|
	else if (t1->reactiontime == 0 || draggeddroptarget)
 | 
						|
	{
 | 
						|
		strength = 80;
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		strength = 140;
 | 
						|
	}
 | 
						|
 | 
						|
	// Intensify bumps if already spinning...
 | 
						|
	P_Thrust(t1, R_PointToAngle2(t1->x, t1->y, t2->x, t2->y), strength * t1->scale);
 | 
						|
 | 
						|
	if (draggeddroptarget)
 | 
						|
	{
 | 
						|
		// "Pass through" the shock of the impact, part 1.
 | 
						|
		t1->momx = t1->target->momx;
 | 
						|
		t1->momy = t1->target->momy;
 | 
						|
		t1->momz = t1->target->momz;
 | 
						|
	}
 | 
						|
 | 
						|
	fixed_t bumppower = FRACUNIT;
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		fixed_t speeddampen = FixedDiv(t2->player->speed, 2*K_GetKartSpeed(t2->player, false, false));
 | 
						|
		bumppower = Easing_InQuad(
 | 
						|
			std::min(speeddampen, FRACUNIT),
 | 
						|
			FRACUNIT,
 | 
						|
			3*FRACUNIT/4
 | 
						|
		);
 | 
						|
		if (t2->player->tripwireLeniency || t2->player->tripwirePass != TRIPWIRE_NONE)
 | 
						|
			bumppower = FRACUNIT/2;
 | 
						|
	}
 | 
						|
 | 
						|
	if (t2->type == MT_INSTAWHIP)
 | 
						|
		bumppower = 0;
 | 
						|
 | 
						|
	{
 | 
						|
		angle_t t2angle = R_PointToAngle2(t2->momx, t2->momy, 0, 0);
 | 
						|
		angle_t t2deflect;
 | 
						|
		fixed_t t1speed, t2speed;
 | 
						|
 | 
						|
		if (t2->type == MT_INSTAWHIP && t2->target && !P_MobjWasRemoved(t2->target))
 | 
						|
		{
 | 
						|
			t2angle = R_PointToAngle2(t2->target->momx, t2->target->momy, 0, 0);
 | 
						|
			t2speed = FixedHypot(t2->target->momx, t2->target->momy);
 | 
						|
			P_InstaThrust(t1, ANGLE_180 + R_PointToAngle2(t1->x, t1->y, t2->x, t2->y), 100*t2->target->scale + t2speed);
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			K_KartBouncing(t1, t2);
 | 
						|
			t2speed = FixedHypot(t2->momx, t2->momy);
 | 
						|
		}
 | 
						|
 | 
						|
		t1speed = FixedHypot(t1->momx, t1->momy);
 | 
						|
 | 
						|
		t2deflect = t2angle - R_PointToAngle2(0, 0, t2->momx, t2->momy);
 | 
						|
		if (t2deflect > ANGLE_180)
 | 
						|
			t2deflect = InvAngle(t2deflect);
 | 
						|
		if (t2deflect < ANG10)
 | 
						|
			P_InstaThrust(t2, t2angle, FixedMul(t2speed, bumppower));
 | 
						|
 | 
						|
		t1->angle = t1->old_angle = R_PointToAngle2(0, 0, t1->momx, t1->momy);
 | 
						|
 | 
						|
		t1->reactiontime = (7 * (t1speed + t2speed)) / (4 * t1->scale);
 | 
						|
		if (t1->reactiontime < 10)
 | 
						|
			t1->reactiontime = 10;
 | 
						|
		t1->threshold = 10;
 | 
						|
	}
 | 
						|
 | 
						|
	t1->renderflags &= ~RF_FULLDARK; // brightest on the bump
 | 
						|
 | 
						|
	if (draggeddroptarget)
 | 
						|
	{
 | 
						|
		// "Pass through" the shock of the impact, part 2.
 | 
						|
		draggeddroptarget->momx = t1->momx;
 | 
						|
		draggeddroptarget->momy = t1->momy;
 | 
						|
		draggeddroptarget->momz = t1->momz;
 | 
						|
 | 
						|
		// Have the drop target travel between them.
 | 
						|
		t1->momx = (t1->momx + t2->momx)/2;
 | 
						|
		t1->momy = (t1->momy + t2->momy)/2;
 | 
						|
		t1->momz = (t1->momz + t2->momz)/2;
 | 
						|
 | 
						|
		K_AddHitLag(t1->target, 6, false);
 | 
						|
	}
 | 
						|
 | 
						|
	K_AddHitLag(t1, 6, true);
 | 
						|
	K_AddHitLag(t2, 6, false);
 | 
						|
 | 
						|
	if (t2->type == MT_INSTAWHIP && t2->target && !P_MobjWasRemoved(t2->target))
 | 
						|
		K_AddHitLag(t2->target, 6, false);
 | 
						|
 | 
						|
	{
 | 
						|
		mobj_t *ghost = P_SpawnGhostMobj(t1);
 | 
						|
		UINT8 i;
 | 
						|
 | 
						|
		P_SetScale(ghost, 3*ghost->destscale/2);
 | 
						|
		ghost->destscale = 15*ghost->destscale/2;
 | 
						|
		ghost->fuse = 10;
 | 
						|
		ghost->scalespeed = (ghost->destscale - ghost->scale)/ghost->fuse;
 | 
						|
 | 
						|
		for (i = 0; i < 2; i++)
 | 
						|
		{
 | 
						|
			mobj_t *blast = P_SpawnMobjFromMobj(t1, 0, 0, FixedDiv(t1->height, t1->scale), MT_BATTLEBUMPER_BLAST);
 | 
						|
			P_SetScale(blast, 5*blast->scale/2);
 | 
						|
 | 
						|
			blast->angle = R_PointToAngle2(0, 0, t1->momx, t1->momy) + ANGLE_45;
 | 
						|
			if (i & 1)
 | 
						|
			{
 | 
						|
				blast->angle += ANGLE_90;
 | 
						|
			}
 | 
						|
 | 
						|
			blast->destscale *= 10;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	t1->flags |= MF_SHOOTABLE;
 | 
						|
	// The following sets t1->target to t2, so draggeddroptarget keeps it persisting...
 | 
						|
	P_DamageMobj(t1, t2, (t2->target ? t2->target : t2), 1, DMG_NORMAL);
 | 
						|
 | 
						|
	switch (t1->health)
 | 
						|
	{
 | 
						|
		case 3:
 | 
						|
			t1->color = SKINCOLOR_LIME;
 | 
						|
			break;
 | 
						|
 | 
						|
		case 2:
 | 
						|
			t1->color = SKINCOLOR_GOLD;
 | 
						|
			break;
 | 
						|
 | 
						|
		case 1:
 | 
						|
			t1->color = SKINCOLOR_CRIMSON;
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	t1->flags &= ~MF_SHOOTABLE;
 | 
						|
 | 
						|
	t1->spritexscale = 3*FRACUNIT;
 | 
						|
	t1->spriteyscale = 3*FRACUNIT/2;
 | 
						|
 | 
						|
	if (!t2->player)
 | 
						|
	{
 | 
						|
		t2->angle += ANGLE_180;
 | 
						|
		if (t2->type == MT_JAWZ)
 | 
						|
			P_SetTarget(&t2->tracer, t2->target); // Back to the source!
 | 
						|
		t2->threshold = 10;
 | 
						|
	}
 | 
						|
 | 
						|
	if (t1->reactiontime > 1000) {
 | 
						|
		S_StartSound(t2, sfx_kdtrg3);
 | 
						|
	} else if (t1->reactiontime > 500) {
 | 
						|
		S_StartSound(t2, sfx_kdtrg2);
 | 
						|
	} else {
 | 
						|
		S_StartSound(t2, sfx_kdtrg1);
 | 
						|
	}
 | 
						|
 | 
						|
	if (t1->tracer && t1->tracer->player && t2->player && t2->player != t1->tracer->player)
 | 
						|
	{
 | 
						|
		K_SpawnAmps(t1->tracer->player, K_PvPAmpReward(20, t1->tracer->player, t2->player), t1);
 | 
						|
	}
 | 
						|
 | 
						|
	if (draggeddroptarget && !P_MobjWasRemoved(draggeddroptarget) && draggeddroptarget->player)
 | 
						|
	{
 | 
						|
		// The following removes t1, be warned
 | 
						|
		// (its newly assigned properties are moved across)
 | 
						|
		K_DropHnextList(draggeddroptarget->player);
 | 
						|
		// Do NOT modify or reference t1 after this line
 | 
						|
		// I mean it! Do not even absentmindedly try it
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static mobj_t *lightningSource;
 | 
						|
static fixed_t lightningDist;
 | 
						|
 | 
						|
static inline BlockItReturn_t PIT_LightningShieldAttack(mobj_t *thing)
 | 
						|
{
 | 
						|
	if (lightningSource == NULL || P_MobjWasRemoved(lightningSource))
 | 
						|
	{
 | 
						|
		// Invalid?
 | 
						|
		return BMIT_ABORT;
 | 
						|
	}
 | 
						|
 | 
						|
	if (thing == NULL || P_MobjWasRemoved(thing))
 | 
						|
	{
 | 
						|
		// Invalid?
 | 
						|
		return BMIT_ABORT;
 | 
						|
	}
 | 
						|
 | 
						|
	if (thing == lightningSource)
 | 
						|
	{
 | 
						|
		// Don't explode yourself!!
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (thing->health <= 0)
 | 
						|
	{
 | 
						|
		// Dead
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (thing->type != MT_SPB)
 | 
						|
	{
 | 
						|
		if (!(thing->flags & MF_SHOOTABLE) || (thing->flags & MF_SCENERY))
 | 
						|
		{
 | 
						|
			// Not shootable
 | 
						|
			return BMIT_CONTINUE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (thing->player && thing->player->spectator)
 | 
						|
	{
 | 
						|
		// Spectator
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (P_AproxDistance(thing->x - lightningSource->x, thing->y - lightningSource->y) > lightningDist + thing->radius)
 | 
						|
	{
 | 
						|
		// Too far away
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	// see if it went over / under
 | 
						|
	if (lightningSource->z - lightningDist > thing->z + thing->height)
 | 
						|
		return BMIT_CONTINUE; // overhead
 | 
						|
	if (lightningSource->z + lightningSource->height + lightningDist < thing->z)
 | 
						|
		return BMIT_CONTINUE; // underneath
 | 
						|
 | 
						|
#if 0
 | 
						|
	if (P_CheckSight(lightningSource, thing) == false)
 | 
						|
	{
 | 
						|
		// Not in sight
 | 
						|
		return BMIT_CONTINUE;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	P_DamageMobj(thing, lightningSource, lightningSource, 1, DMG_VOLTAGE|DMG_CANTHURTSELF|DMG_WOMBO);
 | 
						|
	return BMIT_CONTINUE;
 | 
						|
}
 | 
						|
 | 
						|
void K_LightningShieldAttack(mobj_t *actor, fixed_t size)
 | 
						|
{
 | 
						|
	INT32 bx, by, xl, xh, yl, yh;
 | 
						|
 | 
						|
	lightningDist = FixedMul(size, actor->scale);
 | 
						|
	lightningSource = actor;
 | 
						|
 | 
						|
	// Use blockmap to check for nearby shootables
 | 
						|
	yh = (unsigned)(actor->y + lightningDist - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	yl = (unsigned)(actor->y - lightningDist - bmaporgy)>>MAPBLOCKSHIFT;
 | 
						|
	xh = (unsigned)(actor->x + lightningDist - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
	xl = (unsigned)(actor->x - lightningDist - bmaporgx)>>MAPBLOCKSHIFT;
 | 
						|
 | 
						|
	BMBOUNDFIX (xl, xh, yl, yh);
 | 
						|
 | 
						|
	for (by = yl; by <= yh; by++)
 | 
						|
		for (bx = xl; bx <= xh; bx++)
 | 
						|
			P_BlockThingsIterator(bx, by, PIT_LightningShieldAttack);
 | 
						|
}
 | 
						|
 | 
						|
boolean K_BubbleShieldCanReflect(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	return (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_GACHABOM
 | 
						|
		|| t2->type == MT_BANANA || t2->type == MT_EGGMANITEM || t2->type == MT_BALLHOG
 | 
						|
		|| t2->type == MT_SSMINE || t2->type == MT_LANDMINE || t2->type == MT_SINK
 | 
						|
		|| t2->type == MT_GARDENTOP
 | 
						|
		|| t2->type == MT_DROPTARGET
 | 
						|
		|| t2->type == MT_KART_LEFTOVER
 | 
						|
		|| (t2->type == MT_PLAYER && t1->target != t2));
 | 
						|
}
 | 
						|
 | 
						|
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	mobj_t *owner = t1->player ? t1 : t1->target;
 | 
						|
 | 
						|
	if (t2->target != owner || !t2->threshold || t2->type == MT_DROPTARGET)
 | 
						|
	{
 | 
						|
		if (t1->player && K_PlayerGuard(t1->player))
 | 
						|
		{
 | 
						|
			K_KartSolidBounce(t1, t2);
 | 
						|
			K_DoPowerClash(t1, t2);
 | 
						|
		}
 | 
						|
		if (!t2->momx && !t2->momy)
 | 
						|
		{
 | 
						|
			t2->momz += (24*t2->scale) * P_MobjFlip(t2);
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			t2->momx = -6*t2->momx;
 | 
						|
			t2->momy = -6*t2->momy;
 | 
						|
			t2->momz = -6*t2->momz;
 | 
						|
			t2->angle += ANGLE_180;
 | 
						|
		}
 | 
						|
		if (t2->type == MT_JAWZ)
 | 
						|
			P_SetTarget(&t2->tracer, t2->target); // Back to the source!
 | 
						|
		P_SetTarget(&t2->target, owner); // Let the source reflect it back again!
 | 
						|
		t2->threshold = 10;
 | 
						|
		S_StartSound(t1, sfx_s3k44);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (t2->type == MT_PLAYER)
 | 
						|
	{
 | 
						|
		// Counter desyncs
 | 
						|
		/*mobj_t *oldthing = thing;
 | 
						|
		mobj_t *oldg_tm.thing = g_tm.thing;
 | 
						|
 | 
						|
		P_Thrust(g_tm.thing, R_PointToAngle2(thing->x, thing->y, g_tm.thing->x, g_tm.thing->y), 4*thing->scale);
 | 
						|
 | 
						|
		thing = oldthing;
 | 
						|
		P_SetTarget(&g_tm.thing, oldg_tm.thing);*/
 | 
						|
 | 
						|
		boolean hit = false;
 | 
						|
 | 
						|
		if (K_KartBouncing(t2, t1->target) == true)
 | 
						|
		{
 | 
						|
			if (t2->player && t1->target && t1->target->player)
 | 
						|
			{
 | 
						|
				hit = K_PvPTouchDamage(t2, t1->target);
 | 
						|
			}
 | 
						|
 | 
						|
			// Don't play from t1 else it gets cut out... for some reason.
 | 
						|
			S_StartSound(t2, sfx_s3k44);
 | 
						|
		}
 | 
						|
 | 
						|
		if (hit && (gametyperules & GTR_BUMPERS))
 | 
						|
		{
 | 
						|
			K_PopBubbleShield(t1->target->player);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (K_BubbleShieldCanReflect(t1, t2))
 | 
						|
	{
 | 
						|
		return K_BubbleShieldReflect(t1, t2);
 | 
						|
	}
 | 
						|
 | 
						|
	if (t2->flags & MF_SHOOTABLE)
 | 
						|
	{
 | 
						|
		P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
 | 
						|
{
 | 
						|
	int victimHitlag = 10;
 | 
						|
	int attackerHitlag = 4;
 | 
						|
 | 
						|
	// EV1 is used to indicate that we should no longer hit monitors.
 | 
						|
	// EV2 indicates we should no longer hit anything.
 | 
						|
	if (shield->extravalue2)
 | 
						|
		return false;
 | 
						|
 | 
						|
	mobj_t *attacker = shield->target;
 | 
						|
 | 
						|
	if (!attacker || P_MobjWasRemoved(attacker) || !attacker->player)
 | 
						|
		return false; // How did we even get here?
 | 
						|
 | 
						|
	player_t *attackerPlayer = attacker->player;
 | 
						|
 | 
						|
	if (victim->player)
 | 
						|
	{
 | 
						|
		player_t *victimPlayer = victim->player;
 | 
						|
 | 
						|
		if (victim == attacker)
 | 
						|
			return false;
 | 
						|
 | 
						|
		// If both players have a whip, hits are order-of-execution dependent and that sucks.
 | 
						|
		// Player expectation is a clash here.
 | 
						|
		if (victimPlayer->whip && !P_MobjWasRemoved(victimPlayer->whip))
 | 
						|
		{
 | 
						|
			if (victim->hitlag != 0)
 | 
						|
				return false;
 | 
						|
 | 
						|
			victimPlayer->whip->extravalue2 = 1;
 | 
						|
			shield->extravalue2 = 1;
 | 
						|
 | 
						|
			K_DoPowerClash(victim, attacker);
 | 
						|
 | 
						|
			victim->renderflags &= ~RF_DONTDRAW;
 | 
						|
			attacker->renderflags &= ~RF_DONTDRAW;
 | 
						|
 | 
						|
			angle_t thrangle = R_PointToAngle2(attacker->x, attacker->y, victim->x, victim->y);
 | 
						|
			P_Thrust(victim, thrangle, mapobjectscale*28);
 | 
						|
			P_Thrust(attacker, ANGLE_180 + thrangle, mapobjectscale*28);
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		if (P_PlayerInPain(victimPlayer) ? victim->hitlag == 0 : victimPlayer->flashing == 0)
 | 
						|
		{
 | 
						|
			// Instawhip _always_ loses to guard.
 | 
						|
			if (K_PlayerGuard(victimPlayer))
 | 
						|
			//if (true)
 | 
						|
			{
 | 
						|
				victimHitlag = 3*victimHitlag;
 | 
						|
 | 
						|
				if (P_PlayerInPain(attackerPlayer))
 | 
						|
					return false; // never punish shield more than once
 | 
						|
 | 
						|
				angle_t thrangle = R_PointToAngle2(victim->x, victim->y, shield->x, shield->y);
 | 
						|
				attacker->momx = attacker->momy = 0;
 | 
						|
				P_Thrust(attacker, thrangle, mapobjectscale*7);
 | 
						|
 | 
						|
				// target is inflictor: hack to let invincible players lose to guard
 | 
						|
				P_DamageMobj(attacker, attacker, victim, 1, DMG_TUMBLE);
 | 
						|
 | 
						|
				// A little extra juice, so successful reads are usually positive or zero on spheres.
 | 
						|
				victimPlayer->spheres = std::min(victimPlayer->spheres + 10, 40);
 | 
						|
 | 
						|
				shield->renderflags &= ~RF_DONTDRAW;
 | 
						|
				shield->flags |= MF_NOCLIPTHING;
 | 
						|
 | 
						|
				// Attacker should be free to all reasonable followups.
 | 
						|
				attacker->renderflags &= ~RF_DONTDRAW;
 | 
						|
				attackerPlayer->spindashboost = 0;
 | 
						|
				attackerPlayer->sneakertimer = 0;
 | 
						|
				attackerPlayer->panelsneakertimer = 0;
 | 
						|
				attackerPlayer->weaksneakertimer = 0;
 | 
						|
				attackerPlayer->instaWhipCharge = 0;
 | 
						|
				attackerPlayer->flashing = 0;
 | 
						|
 | 
						|
				K_AddMessageForPlayer(victimPlayer, "Whip Reflected!", false, false);
 | 
						|
				K_AddMessageForPlayer(attackerPlayer, "COUNTERED!!", false, false);
 | 
						|
 | 
						|
				// Localized broly for a local event.
 | 
						|
				if (mobj_t *broly = Obj_SpawnBrolyKi(victim, victimHitlag/2))
 | 
						|
				{
 | 
						|
					broly->extravalue2 = 16*mapobjectscale;
 | 
						|
				}
 | 
						|
 | 
						|
				P_PlayVictorySound(victim);
 | 
						|
 | 
						|
				P_DamageMobj(attacker, attacker, victim, 1, DMG_TUMBLE);
 | 
						|
 | 
						|
				S_StartSound(victim, sfx_mbv92);
 | 
						|
				K_AddHitLag(attacker, victimHitlag, true);
 | 
						|
				K_AddHitLag(victim, attackerHitlag, false);
 | 
						|
 | 
						|
				K_DoPowerClash(shield, victim); // REJECTED
 | 
						|
 | 
						|
				shield->extravalue2 = 1;
 | 
						|
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
 | 
						|
			// if you're here, you're getting hit
 | 
						|
			// Damage is a bit hacky, we want only a small loss-of-control
 | 
						|
			// while still behaving as if it's a "real" hit.
 | 
						|
			P_PlayRinglossSound(victim);
 | 
						|
			P_PlayerRingBurst(victimPlayer, 5);
 | 
						|
			P_DamageMobj(victim, shield, attacker, 1, DMG_WHUMBLE);
 | 
						|
 | 
						|
			K_DropPowerUps(victimPlayer);
 | 
						|
 | 
						|
			angle_t thrangle = ANGLE_180 + R_PointToAngle2(victim->x, victim->y, shield->x, shield->y);
 | 
						|
			P_Thrust(victim, thrangle, mapobjectscale*40);
 | 
						|
 | 
						|
			K_AddHitLag(victim, victimHitlag, true);
 | 
						|
			K_AddHitLag(attacker, attackerHitlag, false);
 | 
						|
			shield->hitlag = attacker->hitlag;
 | 
						|
 | 
						|
			if (attackerPlayer->roundconditions.whip_hyuu == false
 | 
						|
				&& attackerPlayer->hyudorotimer > 0)
 | 
						|
			{
 | 
						|
				attackerPlayer->roundconditions.whip_hyuu = true;
 | 
						|
				attackerPlayer->roundconditions.checkthisframe = true;
 | 
						|
			}
 | 
						|
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	else if (victim->type == MT_SUPER_FLICKY)
 | 
						|
	{
 | 
						|
		if (Obj_IsSuperFlickyWhippable(victim, attacker))
 | 
						|
		{
 | 
						|
			K_AddHitLag(victim, victimHitlag, true);
 | 
						|
			K_AddHitLag(attacker, attackerHitlag, false);
 | 
						|
			shield->hitlag = attacker->hitlag;
 | 
						|
 | 
						|
			Obj_WhipSuperFlicky(victim);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	else if (victim->type == MT_DROPTARGET || victim->type == MT_DROPTARGET_SHIELD)
 | 
						|
	{
 | 
						|
		if (K_TryPickMeUp(attacker, victim, true))
 | 
						|
		{
 | 
						|
			shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			K_DropTargetCollide(victim, shield);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		if (victim->flags & MF_SHOOTABLE)
 | 
						|
		{
 | 
						|
			// Monitor hack. We can hit monitors once per instawhip, no multihit shredding!
 | 
						|
			// Damage values in Obj_MonitorGetDamage.
 | 
						|
			// Apply to UFO also -- steelt 29062023
 | 
						|
			if (victim->type == MT_MONITOR || victim->type == MT_BATTLEUFO || victim->type == MT_BALLSWITCH_BALL)
 | 
						|
			{
 | 
						|
				if (shield->extravalue1 == 1)
 | 
						|
					return false;
 | 
						|
				shield->extravalue1 = 1;
 | 
						|
			}
 | 
						|
 | 
						|
			if (K_TryPickMeUp(attacker, victim, true))
 | 
						|
			{
 | 
						|
				shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL);
 | 
						|
				K_AddHitLag(attacker, attackerHitlag, false);
 | 
						|
				shield->hitlag = attacker->hitlag;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (K_TryPickMeUp(t1, t2, false))
 | 
						|
		return true;
 | 
						|
 | 
						|
	if (t2->player)
 | 
						|
	{
 | 
						|
		if (t2->player->flashing > 0 && t2->hitlag == 0)
 | 
						|
			return true;
 | 
						|
 | 
						|
		S_StartSound(NULL, sfx_bsnipe); // let all players hear it.
 | 
						|
 | 
						|
		if (t1->target && !P_MobjWasRemoved(t1->target) && t1->target->player)
 | 
						|
			K_SpawnAmps(t1->target->player, 50, t2);
 | 
						|
 | 
						|
		HU_SetCEchoFlags(0);
 | 
						|
		HU_SetCEchoDuration(5);
 | 
						|
		HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[t2->player-players]));
 | 
						|
		I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[t2->player-players]);
 | 
						|
 | 
						|
		P_DamageMobj(t2, t1, t1->target, 1, DMG_INSTAKILL);
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
	}
 | 
						|
	else if (t2->flags & MF_SHOOTABLE)
 | 
						|
	{
 | 
						|
		// Shootable damage
 | 
						|
		P_KillMobj(t2, t2, t1->target, DMG_NORMAL);
 | 
						|
		if (P_MobjWasRemoved(t2))
 | 
						|
		{
 | 
						|
			t2 = NULL; // handles the arguments to P_KillMobj
 | 
						|
		}
 | 
						|
		// This item damage
 | 
						|
		P_KillMobj(t1, t2, t2, DMG_NORMAL);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_FallingRockCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (t2->player || t2->type == MT_FALLINGROCK)
 | 
						|
		K_KartBouncing(t2, t1);
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	if (K_PodiumSequence() == true)
 | 
						|
	{
 | 
						|
		// Always regular bumps, no ring toss.
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	// What the fuck is calling this with stale refs? Whatever, validation's cheap.
 | 
						|
	if (P_MobjWasRemoved(t1) || P_MobjWasRemoved(t2) || !t1->player || !t2->player)
 | 
						|
		return false;
 | 
						|
 | 
						|
	if (G_SameTeam(t1->player, t2->player))
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	boolean guard1 = K_PlayerGuard(t1->player);
 | 
						|
	boolean guard2 = K_PlayerGuard(t2->player);
 | 
						|
 | 
						|
	// Bubble Shield physically extends past guard when inflated,
 | 
						|
	// makes some sense to suppress this behavior
 | 
						|
	if (t1->player->bubbleblowup)
 | 
						|
		guard1 = false;
 | 
						|
	if (t2->player->bubbleblowup)
 | 
						|
		guard2 = false;
 | 
						|
 | 
						|
	if (guard1 && guard2)
 | 
						|
		K_DoPowerClash(t1, t2);
 | 
						|
	else if (guard1)
 | 
						|
		K_DoGuardBreak(t1, t2);
 | 
						|
	else if (guard2)
 | 
						|
		K_DoGuardBreak(t2, t1);
 | 
						|
 | 
						|
	if (guard1 || guard2)
 | 
						|
		return false;
 | 
						|
 | 
						|
	// Clash instead of damage if both parties have any of these conditions
 | 
						|
	auto canClash = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		return (K_IsBigger(t1, t2) == true)
 | 
						|
			|| (t1->player->invincibilitytimer > 0)
 | 
						|
			|| (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD)
 | 
						|
			|| (t1->player->curshield == KSHIELD_TOP && !K_IsHoldingDownTop(t1->player))
 | 
						|
			|| (t1->player->bubbleblowup > 0);
 | 
						|
	};
 | 
						|
 | 
						|
	if (canClash(t1, t2) && canClash(t2, t1))
 | 
						|
	{
 | 
						|
		K_DoPowerClash(t1, t2);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	auto forEither = [t1, t2](auto conditionCallable, auto damageCallable)
 | 
						|
	{
 | 
						|
		const bool t1Condition = conditionCallable(t1, t2);
 | 
						|
		const bool t2Condition = conditionCallable(t2, t1);
 | 
						|
 | 
						|
		if (t1Condition == true && t2Condition == false)
 | 
						|
		{
 | 
						|
			damageCallable(t1, t2);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		else if (t1Condition == false && t2Condition == true)
 | 
						|
		{
 | 
						|
			damageCallable(t2, t1);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	};
 | 
						|
 | 
						|
	auto doDamage = [](UINT8 damageType)
 | 
						|
	{
 | 
						|
		return [damageType](mobj_t *t1, mobj_t *t2)
 | 
						|
		{
 | 
						|
			P_DamageMobj(t2, t1, t1, 1, damageType);
 | 
						|
		};
 | 
						|
	};
 | 
						|
 | 
						|
	// Cause tumble on invincibility
 | 
						|
	auto shouldTumble = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		return (t1->player->invincibilitytimer > 0);
 | 
						|
	};
 | 
						|
 | 
						|
	if (forEither(shouldTumble, doDamage(DMG_TUMBLE)))
 | 
						|
	{
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Flame Shield dash damage
 | 
						|
	// Bubble Shield blowup damage
 | 
						|
	auto shouldWipeout = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		return (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD)
 | 
						|
			|| (t1->player->bubbleblowup > 0);
 | 
						|
	};
 | 
						|
 | 
						|
	if (forEither(shouldWipeout, doDamage(DMG_WIPEOUT | DMG_WOMBO)))
 | 
						|
	{
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Battle Mode Sneaker damage
 | 
						|
	// (Pogo Spring damage is handled in head-stomping code)
 | 
						|
	if (gametyperules & GTR_BUMPERS)
 | 
						|
	{
 | 
						|
		auto shouldSteal = [](mobj_t *t1, mobj_t *t2)
 | 
						|
		{
 | 
						|
			return ((t1->player->sneakertimer > 0 || t1->player->panelsneakertimer > 0 || t1->player->weaksneakertimer > 0)
 | 
						|
				&& !P_PlayerInPain(t1->player)
 | 
						|
				&& (t1->player->flashing == 0));
 | 
						|
		};
 | 
						|
 | 
						|
		if (forEither(shouldSteal, doDamage(DMG_WIPEOUT | DMG_STEAL | DMG_WOMBO)))
 | 
						|
		{
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Cause stumble on scale difference
 | 
						|
	auto shouldStumble = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		return K_IsBigger(t1, t2);
 | 
						|
	};
 | 
						|
 | 
						|
	auto doStumble = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		K_StumblePlayer(t2->player);
 | 
						|
		K_SpawnAmps(t1->player, K_PvPAmpReward(20, t1->player, t2->player), t2);
 | 
						|
	};
 | 
						|
 | 
						|
	if (forEither(shouldStumble, doStumble))
 | 
						|
	{
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// Ring sting, this is a bit more unique
 | 
						|
	auto doSting = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		if (t2->player->curshield != KSHIELD_NONE)
 | 
						|
		{
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		boolean damagedpresting = (t2->player->flashing || P_PlayerInPain(t2->player));
 | 
						|
 | 
						|
		// CONS_Printf("T1=%s T2=%s\n", player_names[t1->player - players], player_names[t2->player - players]);
 | 
						|
		// CONS_Printf("DPS=%d\n", damagedpresting);
 | 
						|
 | 
						|
		if (P_PlayerInPain(t1->player) || t1->player->flashing)
 | 
						|
		{
 | 
						|
			// CONS_Printf("T1 pain\n");
 | 
						|
			if (!(t1->player->pflags2 & PF2_SAMEFRAMESTUNG))
 | 
						|
				return false;
 | 
						|
			// CONS_Printf("...but ignored\n");
 | 
						|
		}
 | 
						|
 | 
						|
		bool stung = false;
 | 
						|
 | 
						|
		if (RINGTOTAL(t2->player) <= 0 && t2->health == 1 && !(t2->player->pflags2 & PF2_UNSTINGABLE))
 | 
						|
		{
 | 
						|
			P_DamageMobj(t2, t1, t1, 1, DMG_STING|DMG_WOMBO);
 | 
						|
			// CONS_Printf("T2 stung\n");
 | 
						|
			if (!damagedpresting)
 | 
						|
			{
 | 
						|
				t2->player->pflags2 |= PF2_SAMEFRAMESTUNG;
 | 
						|
				// CONS_Printf("T2 SFS\n");
 | 
						|
			}
 | 
						|
			stung = true;
 | 
						|
		}
 | 
						|
 | 
						|
		P_PlayerRingBurst(t2->player, 1);
 | 
						|
 | 
						|
		return stung;
 | 
						|
	};
 | 
						|
 | 
						|
	// No damage hitlag for stinging.
 | 
						|
	auto removeDamageHitlag = [](mobj_t *t1, mobj_t *t2)
 | 
						|
	{
 | 
						|
		t1->eflags &= ~MFE_DAMAGEHITLAG;
 | 
						|
	};
 | 
						|
 | 
						|
	// Looks bad, but "forEither" actually runs if t1 XOR t2 were damaged.
 | 
						|
	// I don't even think we use the touchdamage return value but I'm too
 | 
						|
	// afraid to change it now. Fix this if you're the next guy and annoyed
 | 
						|
	if (forEither(doSting, removeDamageHitlag))
 | 
						|
	{
 | 
						|
		t1->player->pflags2 &= ~PF2_SAMEFRAMESTUNG;
 | 
						|
		t2->player->pflags2 &= ~PF2_SAMEFRAMESTUNG;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	t1->player->pflags2 &= ~PF2_SAMEFRAMESTUNG;
 | 
						|
	t2->player->pflags2 &= ~PF2_SAMEFRAMESTUNG;
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void K_PuntHazard(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	// TODO: spawn a unique mobjtype other than MT_GHOST
 | 
						|
	mobj_t *img = P_SpawnGhostMobj(t1);
 | 
						|
 | 
						|
	K_MakeObjectReappear(t1);
 | 
						|
 | 
						|
	img->flags &= ~MF_NOGRAVITY;
 | 
						|
	img->renderflags = t1->renderflags & ~RF_DONTDRAW;
 | 
						|
	img->extravalue1 = 1;
 | 
						|
	img->extravalue2 = 2;
 | 
						|
	img->fuse = 2*TICRATE;
 | 
						|
 | 
						|
	struct Vector
 | 
						|
	{
 | 
						|
		fixed_t x_, y_, z_;
 | 
						|
		fixed_t h_ = FixedHypot(x_, y_);
 | 
						|
		fixed_t speed_ = std::max(60 * mapobjectscale, FixedHypot(h_, z_) * 2);
 | 
						|
 | 
						|
		explicit Vector(fixed_t x, fixed_t y, fixed_t z) : x_(x), y_(y), z_(z) {}
 | 
						|
		explicit Vector(const mobj_t* mo) :
 | 
						|
			Vector(std::max(
 | 
						|
				Vector(mo->x - mo->old_x, mo->y - mo->old_y, mo->z - mo->old_z),
 | 
						|
				Vector(mo->momx, mo->momy, mo->momz)
 | 
						|
			))
 | 
						|
		{
 | 
						|
		}
 | 
						|
		explicit Vector(const Vector&) = default;
 | 
						|
 | 
						|
		bool operator<(const Vector& b) const { return speed_ < b.speed_; }
 | 
						|
 | 
						|
		void invert()
 | 
						|
		{
 | 
						|
			x_ = -x_;
 | 
						|
			y_ = -y_;
 | 
						|
			z_ = -z_;
 | 
						|
		}
 | 
						|
 | 
						|
		void thrust(mobj_t* mo) const
 | 
						|
		{
 | 
						|
			angle_t yaw = R_PointToAngle2(0, 0, h_, z_);
 | 
						|
			yaw = std::max(AbsAngle(yaw), static_cast<angle_t>(ANGLE_11hh)) + (yaw & ANGLE_180);
 | 
						|
 | 
						|
			P_InstaThrust(mo, R_PointToAngle2(0, 0, x_, y_), FixedMul(speed_, FCOS(yaw)));
 | 
						|
			mo->momz = FixedMul(speed_, FSIN(yaw));
 | 
						|
		}
 | 
						|
	};
 | 
						|
 | 
						|
	Vector h_vector(t1);
 | 
						|
	Vector p_vector(t2);
 | 
						|
 | 
						|
	h_vector.invert();
 | 
						|
 | 
						|
	std::max(h_vector, p_vector).thrust(img);
 | 
						|
 | 
						|
	K_DoPowerClash(img, t2); // applies hitlag
 | 
						|
	P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
 | 
						|
}
 | 
						|
 | 
						|
boolean K_PuntCollide(mobj_t *t1, mobj_t *t2)
 | 
						|
{
 | 
						|
	// MF_SHOOTABLE will get damaged directly, instead
 | 
						|
	if (t1->flags & (MF_DONTPUNT | MF_SHOOTABLE))
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!t2->player)
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!K_PlayerCanPunt(t2->player))
 | 
						|
	{
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (t1->flags & MF_ELEMENTAL)
 | 
						|
	{
 | 
						|
		K_MakeObjectReappear(t1);
 | 
						|
 | 
						|
		// copied from MT_ITEMCAPSULE
 | 
						|
		UINT8 i;
 | 
						|
		INT16 spacing = (t1->radius >> 1) / t1->scale;
 | 
						|
		// dust effects
 | 
						|
		for (i = 0; i < 10; i++)
 | 
						|
		{
 | 
						|
			fixed_t rand_x;
 | 
						|
			fixed_t rand_y;
 | 
						|
			fixed_t rand_z;
 | 
						|
 | 
						|
			// note: determinate random argument eval order
 | 
						|
			rand_z = P_RandomRange(PR_ITEM_DEBRIS, 0, 4*spacing) * FRACUNIT;
 | 
						|
			rand_y = P_RandomRange(PR_ITEM_DEBRIS, -spacing, spacing) * FRACUNIT;
 | 
						|
			rand_x = P_RandomRange(PR_ITEM_DEBRIS, -spacing, spacing) * FRACUNIT;
 | 
						|
			mobj_t *puff = P_SpawnMobjFromMobj(
 | 
						|
				t1,
 | 
						|
				rand_x,
 | 
						|
				rand_y,
 | 
						|
				rand_z,
 | 
						|
				MT_SPINDASHDUST
 | 
						|
			);
 | 
						|
 | 
						|
			puff->momz = puff->scale * P_MobjFlip(puff);
 | 
						|
 | 
						|
			P_Thrust(puff, R_PointToAngle2(t2->x, t2->y, puff->x, puff->y), 3*puff->scale);
 | 
						|
 | 
						|
			puff->momx += t2->momx / 2;
 | 
						|
			puff->momy += t2->momy / 2;
 | 
						|
			puff->momz += t2->momz / 2;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		K_PuntHazard(t1, t2);
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 |