/* AMX Mod X
*	Barnacle Grapple.
*
* http://aghl.ru/forum/ - Russian Half-Life and Adrenaline Gamer Community
*
* This file is provided as is (no warranties)
*/

#pragma semicolon 1
#pragma ctrlchar '\'

#include <amxmodx>
#include <beams>
#include <hamsandwich>
#include <hl_wpnmod>
#include <xs>


#define PLUGIN "Barnacle Grapple"
#define VERSION "1.1"
#define AUTHOR "KORD_12.7"
 

// Weapon settings
#define WEAPON_NAME 			"weapon_grapple"
#define WEAPON_SLOT			1
#define WEAPON_POSITION			5
#define WEAPON_PRIMARY_AMMO		"" // NULL
#define WEAPON_PRIMARY_AMMO_MAX		-1
#define WEAPON_SECONDARY_AMMO		"" // NULL
#define WEAPON_SECONDARY_AMMO_MAX	-1
#define WEAPON_MAX_CLIP			-1
#define WEAPON_DEFAULT_AMMO		-1
#define WEAPON_FLAGS			0
#define WEAPON_WEIGHT			21

// Grapple settings
#define GRAPPLE_FLY_VELOCITY		1500
#define GRAPPLE_PULL_VELOCITY		400.0

// Hud
#define WEAPON_HUD_TXT			"sprites/weapon_grapple.txt"
#define WEAPON_HUD_SPR			"sprites/weapon_grapple.spr"

// Models
#define MODEL_VIEW			"models/v_bgrap_koshak.mdl"
#define MODEL_WORLD			"models/w_bgrap.mdl"
#define MODEL_PLAYER			"models/p_bgrap.mdl"
#define MODEL_TONGUE_TIP		"models/shock_effect.mdl"

// Sprites
#define SPRITE_TONGUE			"sprites/tongue.spr"

// Sounds
#define SOUND_DRAW			"weapons/alienweap_draw.wav"
#define SOUND_WAIT			"weapons/bgrapple_wait.wav"
#define SOUND_PULL			"weapons/bgrapple_pull.wav"
#define SOUND_FIRE			"weapons/bgrapple_fire.wav"
#define SOUND_COUGH			"weapons/bgrapple_cough.wav"
#define SOUND_CHEW_1			"barnacle/bcl_chew1.wav"
#define SOUND_CHEW_2			"barnacle/bcl_chew2.wav"
#define SOUND_CHEW_3			"barnacle/bcl_chew3.wav"
#define SOUND_IMPACT			"weapons/bgrapple_impact.wav"
#define SOUND_RELEASE			"weapons/bgrapple_release.wav"

// Animation
#define ANIM_EXTENSION			"gauss"

enum _:GrappleAnim
{
	ANIM_BREATHE = 0,
	ANIM_LONGIDLE,
	ANIM_SHORTIDLE,
	ANIM_COUGH,
	ANIM_DOWN,
	ANIM_UP,
	ANIM_FIRE,
	ANIM_FIREWAITING,
	ANIM_FIREREACHED,
	ANIM_FIRETRAVEL,
	ANIM_FIRERELEASE
};

#define SET_SIZE(%0,%1,%2) engfunc(EngFunc_SetSize,%0,%1,%2)
#define SET_ORIGIN(%0,%1) engfunc(EngFunc_SetOrigin,%0,%1)

enum
{
	FIRE_OFF, 
	FIRE_CHARGE
}

#define Offset_iTip	Offset_iuser1
#define Offset_iBeam	Offset_iuser2

#define Offset_iPull	Offset_iuser1
#define Offset_iTarget	Offset_iuser2

//**********************************************
//* Precache resources                         *
//**********************************************

public plugin_precache()
{
	PRECACHE_MODEL(MODEL_VIEW);
	PRECACHE_MODEL(MODEL_WORLD);
	PRECACHE_MODEL(MODEL_PLAYER);
	PRECACHE_MODEL(SPRITE_TONGUE);
	PRECACHE_MODEL(MODEL_TONGUE_TIP);
	
	PRECACHE_SOUND(SOUND_DRAW);
	PRECACHE_SOUND(SOUND_WAIT);
	PRECACHE_SOUND(SOUND_PULL);
	PRECACHE_SOUND(SOUND_FIRE);
	PRECACHE_SOUND(SOUND_COUGH);
	PRECACHE_SOUND(SOUND_CHEW_1);
	PRECACHE_SOUND(SOUND_CHEW_2);
	PRECACHE_SOUND(SOUND_CHEW_3);
	PRECACHE_SOUND(SOUND_IMPACT);
	PRECACHE_SOUND(SOUND_RELEASE);
	
	PRECACHE_GENERIC(WEAPON_HUD_TXT);
	PRECACHE_GENERIC(WEAPON_HUD_SPR); 
}

//**********************************************
//* Register weapon.                           *
//**********************************************

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR);
	
	new iGrapple = wpnmod_register_weapon
	
	(
		WEAPON_NAME,
		WEAPON_SLOT,
		WEAPON_POSITION,
		WEAPON_PRIMARY_AMMO,
		WEAPON_PRIMARY_AMMO_MAX,
		WEAPON_SECONDARY_AMMO,
		WEAPON_SECONDARY_AMMO_MAX,
		WEAPON_MAX_CLIP,
		WEAPON_FLAGS,
		WEAPON_WEIGHT
	);
	
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_Spawn, "Grapple_Spawn");
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_Deploy, "Grapple_Deploy");
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_PrimaryAttack, "Grapple_PrimaryAttack");
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_SecondaryAttack, "Grapple_SecondaryAttack");
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_Idle, "Grapple_Idle");
	wpnmod_register_weapon_forward(iGrapple, Fwd_Wpn_Holster, "Grapple_Holster");
}

//**********************************************
//* Weapon spawn.                              *
//**********************************************

public Grapple_Spawn(const iItem)
{
	// Setting world model
	SET_MODEL(iItem, MODEL_WORLD);
}

//**********************************************
//* Deploys the weapon.                        *
//**********************************************

public Grapple_Deploy(const iItem)
{
	wpnmod_set_offset_int(iItem, Offset_iFireState, FIRE_OFF);
	
	return wpnmod_default_deploy(iItem, MODEL_VIEW, MODEL_PLAYER, ANIM_UP, ANIM_EXTENSION);
}

//**********************************************
//* Called when the weapon is holster.         *
//**********************************************

public Grapple_Holster(const iItem)
{
	Grapple_DestroyEffect(iItem);
}

//**********************************************
//* Displays the idle animation for the weapon.*
//**********************************************

public Grapple_Idle(const iItem, const iPlayer)
{
	if (wpnmod_get_offset_float(iItem, Offset_flTimeWeaponIdle) > 0.0)
	{
		return;
	}
	
	if (wpnmod_get_offset_int(iItem, Offset_iFireState) != FIRE_OFF)
	{
		 Grapple_EndAttack(iItem, iPlayer);
		 return;
	}
	
	new iAnim;
	new Float: flRand;
	new Float: flNextIdle;
		
	if ((flRand = random_float(0.0, 1.0)) > 0.5)
	{
		if (flRand > 0.95)
		{
			iAnim = ANIM_COUGH;
			flNextIdle = 4.63;
		}
		else
		{
			iAnim = ANIM_BREATHE;
			flNextIdle = 2.6;
		}
	}
	else
	{
		iAnim = ANIM_LONGIDLE;
		flNextIdle = 10.03;
	}
	
	wpnmod_send_weapon_anim(iItem, iAnim);
	wpnmod_set_offset_float(iItem, Offset_flTimeWeaponIdle, flNextIdle);
}

//**********************************************
//* The main attack of a weapon is triggered.  *
//**********************************************

public Grapple_PrimaryAttack(const iItem, const iPlayer)
{
	Grapple_StartAttack(iItem, iPlayer, true);
}

//**********************************************
//* Secondary attack of a weapon is triggered. *
//**********************************************

public Grapple_SecondaryAttack(const iItem, const iPlayer)
{
	Grapple_StartAttack(iItem, iPlayer, false);
}

//**********************************************
//* Start Grapple attack.                      *
//**********************************************

Grapple_StartAttack(const iItem, const iPlayer, const bool: bPull)
{
	static iFireState;
	static iGrappleTip;
	
	static Float: flGameTime;
	
	flGameTime = get_gametime();
	iFireState = wpnmod_get_offset_int(iItem, Offset_iFireState);
	iGrappleTip = wpnmod_get_offset_int(iItem, Offset_iTip);
	
	wpnmod_set_offset_float(iItem, Offset_flReleaseThrow, bPull ? 0.0 : 1.0);
	
	if (iFireState == FIRE_OFF)
	{
		set_pev(iPlayer, pev_punchangle, Float: {-2.0, 0.0, 0.0});
		
		wpnmod_send_weapon_anim(iItem, ANIM_FIRE);
		wpnmod_set_player_anim(iPlayer, PLAYER_ATTACK1);
			
		wpnmod_set_offset_int(iItem, Offset_iFireState, FIRE_CHARGE);
		wpnmod_set_offset_float(iItem, Offset_flTimeWeaponIdle, 0.1);
		wpnmod_set_offset_float(iItem, Offset_flPumpTime, flGameTime + 0.5);
		wpnmod_set_offset_float(iItem, Offset_flStartThrow, flGameTime + 0.5);
	
		emit_sound(iPlayer, CHAN_WEAPON, SOUND_FIRE, 0.9, ATTN_NORM, 0, PITCH_NORM);
	}
	else if (iFireState == FIRE_CHARGE && pev_valid(iGrappleTip))
	{
		if (wpnmod_get_offset_float(iItem, Offset_flStartThrow) < flGameTime)
		{
			if (wpnmod_get_offset_int(iGrappleTip, Offset_iPull))
			{
				wpnmod_send_weapon_anim(iItem, ANIM_FIRETRAVEL);
				wpnmod_set_offset_float(iItem, Offset_flStartThrow, flGameTime + 0.68);
			}
			else
			{
				wpnmod_send_weapon_anim(iItem, ANIM_FIREWAITING);
				wpnmod_set_offset_float(iItem, Offset_flStartThrow, flGameTime + 0.24);
			}
		}	
					
		if (wpnmod_get_offset_float(iItem, Offset_flPumpTime) < flGameTime)
		{
			emit_sound(iPlayer, CHAN_WEAPON, SOUND_PULL, 0.9, ATTN_NORM, 0, PITCH_NORM);
			wpnmod_set_offset_float(iItem, Offset_flPumpTime, flGameTime + 0.68);
		}
			
		if (bPull)
		{
			Grapple_Stab(iItem, iPlayer);
		}
	}
	
	Grapple_UpdateEffect(iItem, iPlayer);
}

//**********************************************
//* End Grapple attack.                        *
//**********************************************

Grapple_EndAttack(const iItem, const iPlayer)
{
	Grapple_DestroyEffect(iItem);
	wpnmod_send_weapon_anim(iItem, ANIM_FIRERELEASE);
	
	wpnmod_set_offset_int(iItem, Offset_iFireState, FIRE_OFF);
	wpnmod_set_offset_float(iItem, Offset_flNextPrimaryAttack, 1.0);
	wpnmod_set_offset_float(iItem, Offset_flNextSecondaryAttack, 1.0);
	wpnmod_set_offset_float(iItem, Offset_flTimeWeaponIdle, 0.9);
	
	emit_sound(iPlayer, CHAN_WEAPON, SOUND_PULL, 0.0, 0.0, SND_STOP, PITCH_NORM);
	emit_sound(iPlayer, CHAN_WEAPON, SOUND_RELEASE, 0.9, ATTN_NORM, 0, PITCH_NORM);
}

//**********************************************
//* Tongue tip effects.                        *
//**********************************************

Grapple_UpdateEffect(const iItem, const iPlayer)
{
	static iTip, iBeam;
	
	iTip = wpnmod_get_offset_int(iItem, Offset_iTip);
	
	if (!pev_valid(iTip))
	{
		Grapple_CreateEffect(iItem, iPlayer);
	}
	
	iBeam = wpnmod_get_offset_int(iItem, Offset_iBeam);
	
	if (pev_valid(iBeam))
	{
		Beam_RelinkBeam(iBeam);
	}
}

Grapple_DestroyEffect(const iItem)
{
	new iBeam = wpnmod_get_offset_int(iItem, Offset_iBeam);
	
	if (pev_valid(iBeam))
	{
		set_pev(iBeam, pev_flags, FL_KILLME);
		wpnmod_set_offset_int(iItem, Offset_iBeam, FM_NULLENT);
	}
	
	new iTip = wpnmod_get_offset_int(iItem, Offset_iTip);
	
	if (pev_valid(iTip))
	{
		set_pev(iTip, pev_flags, FL_KILLME);
		wpnmod_set_offset_int(iItem, Offset_iTip, FM_NULLENT);
	}
}

Grapple_CreateEffect(const iItem, const iPlayer)
{
	Grapple_DestroyEffect(iItem);
	
	new iTip; 
	new iBeam;
	
	new Float: vecOrigin[3];
	new Float: vecAngles[3];
	new Float: vecVelocity[3];
	
	velocity_by_aim(iPlayer, GRAPPLE_FLY_VELOCITY, vecVelocity);
	wpnmod_get_gun_position(iPlayer, vecOrigin, 16.0, 8.0, -8.0);
	
	static iszAllocStringCached;
	if (iszAllocStringCached || (iszAllocStringCached = engfunc(EngFunc_AllocString, "info_target")))
	{
		iTip = engfunc(EngFunc_CreateNamedEntity, iszAllocStringCached);
	}
	
	if (pev_valid(iTip))
	{
		engfunc(EngFunc_VecToAngles, vecVelocity, vecAngles);
	
		set_pev(iTip, pev_classname, "grapple_tip");
		set_pev(iTip, pev_solid, SOLID_BBOX);
		set_pev(iTip, pev_movetype, MOVETYPE_FLY);
		set_pev(iTip, pev_velocity, vecVelocity);
		set_pev(iTip, pev_angles, vecAngles);
		set_pev(iTip, pev_owner, iPlayer);
		set_pev(iTip, pev_iuser4, iItem);
		
		SET_ORIGIN(iTip, vecOrigin);
		SET_MODEL(iTip, MODEL_TONGUE_TIP);
		SET_SIZE(iTip, Float: {0.0, 0.0, 0.0}, Float: {0.0, 0.0, 0.0});
		
		wpnmod_set_offset_int(iItem, Offset_iTip, iTip);
	
		if (pev_valid((iBeam = Beam_Create(SPRITE_TONGUE, 15.0))))
		{
			set_pev(iBeam, pev_classname, "tongue_beam");
			
			Beam_EntsInit(iBeam, iTip, iPlayer);
			Beam_SetFlags(iBeam, BEAM_FSOLID);
			Beam_SetEndAttachment(iBeam, 1);
			Beam_SetBrightness(iBeam, 100.0);

			wpnmod_set_offset_int(iItem, Offset_iBeam, iBeam);
		}
		
		wpnmod_set_think(iTip, "GrappleTip_FlyThink");
		wpnmod_set_touch(iTip, "GrappleTip_TongueTouch");
		
		set_pev(iTip, pev_nextthink, get_gametime() + 0.1);
	}
}

//**********************************************
//* Tongue tip think funcs.                    *
//**********************************************

public GrappleTip_FlyThink(const iGrappleTip)
{
	static iItem;
	static iOwner;
	
	iItem = pev(iGrappleTip, pev_iuser4);
	iOwner = pev(iGrappleTip, pev_owner);
	
	set_pev(iGrappleTip, pev_nextthink, get_gametime() + 0.1);
	
	if (!ExecuteHamB(Ham_IsInWorld, iGrappleTip))
	{
		Grapple_EndAttack(iItem, iOwner);
	}
}

public GrappleTip_PullThink(const iGrappleTip)
{
	static iItem;
	static iOwner;
	static iTarget;
	
	static Float: flDistance;
	static Float: vecVelocity[3];
	static Float: vecPlayerOrigin[3];
	static Float: vecGrappleOrigin[3];
	
	iItem = pev(iGrappleTip, pev_iuser4);
	iOwner = pev(iGrappleTip, pev_owner);
	iTarget = wpnmod_get_offset_int(iGrappleTip, Offset_iTarget);
	
	if (pev_valid(iTarget) && ExecuteHamB(Ham_Classify, iTarget) && !ExecuteHamB(Ham_IsAlive, iTarget))
	{
		Grapple_EndAttack(iItem, iOwner);
		return;
	}
	
	set_pev(iGrappleTip, pev_nextthink, get_gametime() + 0.1);
	
	if (!wpnmod_get_offset_int(iGrappleTip, Offset_iPull) || wpnmod_get_offset_float(iItem, Offset_flReleaseThrow))
	{
		return;
	}
	
	pev(iOwner, pev_origin, vecPlayerOrigin);
	pev(iGrappleTip, pev_origin, vecGrappleOrigin);
	
	flDistance = get_distance_f(vecGrappleOrigin, vecPlayerOrigin);
	
	if (flDistance)
	{
		xs_vec_sub(vecGrappleOrigin, vecPlayerOrigin, vecGrappleOrigin);
		xs_vec_mul_scalar(vecGrappleOrigin, GRAPPLE_PULL_VELOCITY / flDistance, vecVelocity);
		set_pev(iOwner, pev_velocity, vecVelocity);
	}
}

//**********************************************
//* Tongue tip touch func.                     *
//**********************************************

public GrappleTip_TongueTouch(const iGrappleTip, const iOther)
{
	new iHit;
	new iItem;
	new iOwner;
	
	new szClassName[32];
	new szTextureName[13];
	
	new Float: vecOrigin[3];
	new Float: vecVelocity[3];
	
	iItem = pev(iGrappleTip, pev_iuser4);
	iOwner = pev(iGrappleTip, pev_owner);
	
	pev(iGrappleTip, pev_origin, vecOrigin);
	pev(iGrappleTip, pev_velocity, vecVelocity);
	
	if (pev(iOther, pev_flags) & (FL_CLIENT | FL_MONSTER))
	{
		iHit = true;
		
		set_pev(iGrappleTip, pev_movetype, MOVETYPE_FOLLOW);
		set_pev(iGrappleTip, pev_skin, iOther);
		set_pev(iGrappleTip, pev_body, 0);
		set_pev(iGrappleTip, pev_aiment, iOther);
	}
	else
	{
		pev(iOther, pev_classname, szClassName, charsmax(szClassName));
		
		if (equali(szClassName, "ammo_spore"))
		{
			iHit = true;
		}
		else
		{
			xs_vec_normalize(vecVelocity, vecVelocity);
			xs_vec_mul_scalar(vecVelocity, 8.0, vecVelocity);
			xs_vec_add(vecVelocity, vecOrigin, vecVelocity);
			
			engfunc(EngFunc_TraceTexture, iOther, vecOrigin, vecVelocity, szTextureName, charsmax(szTextureName));
			
			if (equali(szTextureName, "xeno_grapple"))
			{
				iHit = true;
			}
		}	
	}
	
	if (!iHit)
	{
		Grapple_EndAttack(iItem, iOwner);
	}
	else
	{
		wpnmod_set_think(iGrappleTip, "GrappleTip_PullThink");
		set_pev(iGrappleTip, pev_nextthink, get_gametime() + 0.1);
		
		emit_sound(iOwner, CHAN_ITEM, SOUND_IMPACT, 0.9, ATTN_NORM, 0, PITCH_NORM);
	}
	
	set_pev(iGrappleTip, pev_solid, SOLID_NOT);
	set_pev(iGrappleTip, pev_velocity, Float: {0.0, 0.0, 0.0});
	
	wpnmod_set_offset_int(iGrappleTip, Offset_iPull, iHit);
	wpnmod_set_offset_int(iGrappleTip, Offset_iTarget, iOther);
}

//**********************************************
//* Make damage to victim.                     *
//**********************************************

public Grapple_Stab(const iItem, const iPlayer)
{
	new Float: flDmgTime;
	new Float: flGameTime = get_gametime();
	
	pev(iItem, pev_dmgtime, flDmgTime);
	
	if (flDmgTime > flGameTime)
	{
		return;
	}
	
	set_pev(iItem, pev_dmgtime, flGameTime + 0.5);
	
	#define Instance(%0) ((%0 == -1) ? 0 : %0)
	
	new iTrace;
	new iTarget;
	new iEntity;
	new iGrappleTip;
	
	new Float: vecSrc[3];
	new Float: vecEnd[3];
	new Float: vecAngle[3];
	new Float: vecForward[3];
	new Float: flFraction;
	
	iTrace = create_tr2();
	iGrappleTip = wpnmod_get_offset_int(iItem, Offset_iTip);
	iTarget = wpnmod_get_offset_int(iGrappleTip, Offset_iTarget);
	
	pev(iPlayer, pev_origin, vecSrc);
	pev(iPlayer, pev_v_angle, vecAngle);
	
	engfunc(EngFunc_MakeVectors, vecAngle);	
	global_get(glb_v_forward, vecForward);
	
	xs_vec_mul_scalar(vecForward, 32.0, vecForward);
	xs_vec_add(vecForward, vecSrc, vecEnd);

	engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, iPlayer, iTrace);
	get_tr2(iTrace, TR_flFraction, flFraction);
	
	if (flFraction >= 1.0)
	{
		engfunc(EngFunc_TraceHull, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, HULL_HEAD, iPlayer, iTrace);
		get_tr2(iTrace, TR_flFraction, flFraction);
		
		if (flFraction < 1.0)
		{
			new iHit = Instance(get_tr2(iTrace, TR_pHit));
			
			if (!iHit || ExecuteHamB(Ham_IsBSPModel, iHit))
			{
				FindHullIntersection(vecSrc, iTrace, Float: {-16.0, -16.0, -18.0}, Float: {16.0,  16.0,  18.0}, iPlayer);
			}
			
			get_tr2(iTrace, TR_vecEndPos, vecEnd);
		}
	}
	
	get_tr2(iTrace, TR_flFraction, flFraction);
	
	if (flFraction < 1.0)
	{
		iEntity = Instance(get_tr2(iTrace, TR_pHit));
		
		if (iEntity && ExecuteHamB(Ham_Classify, iEntity) != CLASS_NONE && iEntity == iTarget)
		{
			wpnmod_clear_multi_damage();
			
			pev(iPlayer, pev_v_angle, vecAngle);
			engfunc(EngFunc_MakeVectors, vecAngle);	
			
			global_get(glb_v_forward, vecForward);
			ExecuteHamB(Ham_TraceAttack, iEntity, iPlayer, 50.0, vecForward, iTrace, DMG_SLASH | DMG_ALWAYSGIB);
			
			wpnmod_apply_multi_damage(iPlayer, iPlayer);
				
			switch (random_num(0, 1))
			{
				case 0: emit_sound(iPlayer, CHAN_ITEM, SOUND_CHEW_1, 1.0, ATTN_NORM, 0, PITCH_NORM);
				case 1: emit_sound(iPlayer, CHAN_ITEM, SOUND_CHEW_2, 1.0, ATTN_NORM, 0, PITCH_NORM);
				case 2: emit_sound(iPlayer, CHAN_ITEM, SOUND_CHEW_3, 1.0, ATTN_NORM, 0, PITCH_NORM);
			}
		}
	}

	free_tr2(iTrace);
}

//**********************************************
//* Some usefull stocks.                       *
//**********************************************

stock FindHullIntersection(const Float: vecSrc[3], &iTrace, const Float: vecMins[3], const Float: vecMaxs[3], const iEntity)
{
	new i, j, k;
	new iTempTrace;
	
	new Float: vecEnd[3];
	new Float: vecEndPos[3];
	new Float: vecHullEnd[3];
	new Float: vecMinMaxs[2][3];
	
	new Float: flDistance;
	new Float: flFraction;
	new Float: flThisDistance;
	
	flDistance = 999999.0;
	
	xs_vec_copy(vecMins, vecMinMaxs[0]);
	xs_vec_copy(vecMaxs, vecMinMaxs[1]);
	
	get_tr2(iTrace, TR_vecEndPos, vecHullEnd);
	
	xs_vec_sub(vecHullEnd, vecSrc, vecHullEnd);
	xs_vec_mul_scalar(vecHullEnd, 2.0, vecHullEnd);
	xs_vec_add(vecHullEnd, vecSrc, vecHullEnd);
	
	engfunc(EngFunc_TraceLine, vecSrc, vecHullEnd, DONT_IGNORE_MONSTERS, iEntity, (iTempTrace = create_tr2()));
	get_tr2(iTempTrace, TR_flFraction, flFraction);
	
	if (flFraction < 1.0)
	{
		free_tr2(iTrace);
		
		iTrace = iTempTrace;
		return;
	}
	
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 2; j++)
		{
			for (k = 0; k < 2; k++)
			{
				vecEnd[0] = vecHullEnd[0] + vecMinMaxs[i][0];
				vecEnd[1] = vecHullEnd[1] + vecMinMaxs[j][1];
				vecEnd[2] = vecHullEnd[2] + vecMinMaxs[k][2];
				
				engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, iEntity, iTempTrace);
				get_tr2(iTempTrace, TR_flFraction, flFraction);
				
				if (flFraction < 1.0)
				{
					get_tr2(iTempTrace, TR_vecEndPos, vecEndPos);
					xs_vec_sub(vecEndPos, vecSrc, vecEndPos);
					
					if ((flThisDistance = xs_vec_len(vecEndPos)) < flDistance)
					{
						free_tr2(iTrace);
						
						iTrace = iTempTrace;
						flDistance = flThisDistance;
					}
				}
			}
		}
	}
}
