globalize_all_functions

//=========================================================
//	_utility
//
//=========================================================

int functionref( bool = false ) ornull te = null
int EntTracker = 0

global const C_PLAYFX_SINGLE	= 0
global const C_PLAYFX_MULTIPLE	= 1
global const C_PLAYFX_LOOP		= 2

global const MUTEALLFADEIN 		= 2

global struct ZipLine
{
	entity start
	entity mid
	entity end
}

global int HUMAN_RAGDOLL_IMPACT_TABLE_IDX = -1

global struct ArrayDotResultStruct
{
	entity ent
	float dot
}

global struct ShieldDamageModifier
{
	float permanentDamageFrac = TITAN_SHIELD_PERMAMENT_DAMAGE_FRAC
	bool normalizeShieldDamage = false
	float damageScale = 1.0
}


struct
{
	bool isSkyboxView = false
} file

void function Utility_Init()
{
	te = TotalEnts
	EntTracker = 0

	#document( "SetDeathFuncName", "Sets the name of a function that runs when the NPC dies." )
	#document( "CenterPrint", "Print to the screen" )
	#document( "GetAllSoldiers", "Get all living soldiers." )
	#document( "ClearDeathFuncName", "Clears the script death function." )

	HUMAN_RAGDOLL_IMPACT_TABLE_IDX = PrecacheImpactEffectTable( "ragdoll_human" )

	RegisterSignal( "PetTitanUpdated" )
	RegisterSignal( "WaitDeadTimeOut" )
	RegisterSignal( "InventoryChanged" )

	AddClientCommandCallback( "OnDevnetBugScreenshot", ClientCommand_OnDevnetBugScreenshot )
	#if DEV
		FlagInit( "AimAssistSwitchTest_Enabled" )
		AddClientCommandCallback( "DoomTitan", ClientCommand_DoomTitan )
	#endif
}

void function GiveAllTitans()
{

	array<entity> players = GetPlayerArray()
	foreach ( player in players )
	{
		#if MP
		GiveTitanToPlayer( player )
		#endif

		if ( player.IsTitan() )
		{
			entity soul = player.GetTitanSoul()
			if ( soul )
			{
				SoulTitanCore_SetNextAvailableTime( soul, 1 )
			}
		}
	}

	array<entity> titans = GetNPCArrayByClass( "npc_titan" )
	foreach ( titan in titans )
	{
		entity soul = titan.GetTitanSoul()
		if ( soul )
			SoulTitanCore_SetNextAvailableTime( soul, 1 )
	}
}

#if DEV

void function KillAllBadguys()
{
	array<entity> npcs = GetNPCArrayOfEnemies( GetPlayerArray()[0].GetTeam() )
	foreach ( n in npcs )
	{
		if ( n.GetClassName() == "npc_bullseye" )
			continue

		if ( !IsAlive( n ) )
			continue
		n.Die()
	}
}

bool function ClientCommand_DoomTitan( entity player, array<string> args )
{
	entity titan
	if ( player.IsTitan() )
		titan = player
	else
		titan = player.GetPetTitan()

	if ( !IsAlive( titan ) )
		return true

	if ( GetDoomedState( titan ) )
		return true

	entity soul = titan.GetTitanSoul()
	soul.SetShieldHealth( 0 )

	titan.TakeDamage( titan.GetHealth(), null, null, { damageSourceId=damagedef_suicide, scriptType = DF_SKIP_DAMAGE_PROT } )

	return true
}
#endif

void function PrintPlaylists()
{
	printt( "=== PLAYLIST NAMES: ===" )

	int count = GetPlaylistCount()
	for ( int i = 0; i < count; i++ )
	{
		printt( "--", GetPlaylistName( i ) )
	}
}

entity function CreateEntity( string name )
{
//	if ( name == "npc_titan" )
//	{
//		DumpStack(3)
//	}
//	if ( name == "info_particle_system" )
//	{
//		printl( " " )
//		DumpStack(3)
//	}
	return Entities_CreateByClassname( name )
}

// used from the console to quick-test fields
void function setnpcfields( string key, var val )
{
	array<entity> npcs = GetNPCArrayByClass( "npc_soldier" )
	foreach ( npc in npcs )
	{
		npc.kv[ key ] = val
	}
}

void function WaitUntilNumDead( array<entity> guys, int numDead )
{
	Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
	float timeout = -1
	__WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadTracker )
}

void function WaitUntilNumDeadWithTimeout( array<entity> guys, int numDead, float timeout )
{
	Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
	Assert( timeout > 0 )
	__WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadTracker )
}

void function WaitUntilAllDead( array<entity> guys )
{
	int count = guys.len()
	float timeout = -1
	__WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadTracker )
}

void function WaitUntilAllDeadWithTimeout( array<entity> guys, float timeout )
{
	int count = guys.len()
	Assert( timeout > 0 )
	__WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadTracker )
}

void function WaitUntilNumDeadOrLeeched( array<entity> guys, int numDead )
{
	Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
	float timeout = -1
	__WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadOrLeechedTracker )
}

void function WaitUntilNumDeadOrLeechedWithTimeout( array<entity> guys, int numDead, float timeout )
{
	Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
	Assert( timeout > 0 )
	__WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadOrLeechedTracker )
}

void function WaitUntilAllDeadOrLeeched( array<entity> guys )
{
	int count = guys.len()
	float timeout = -1
	__WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadOrLeechedTracker )
}

void function WaitUntilAllDeadOrLeechedWithTimeout( array<entity> guys, float timeout )
{
	int count = guys.len()
	Assert( timeout > 0 )
	__WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadOrLeechedTracker )
}

void function __WaitUntilDeadInternal( array<entity> guys, int numDead, float timeout, void functionref( entity, table<string, int> ) deathTrackerFunc )
{
	table<string, int> master = { count = numDead }

	if ( timeout > 0.0 )
		thread __WaitUntilDeadTrackerDelayedSignal( master, "WaitDeadTimeOut", timeout )

	//when the thread ends, let child threads know
	OnThreadEnd(
		function() : ( master )
		{
			Signal( master, "OnDestroy" )
		}
	)

	foreach ( guy in guys )
		thread deathTrackerFunc( guy, master )

	while ( master.count > 0 )
	{
		table result = WaitSignal( master, "OnDeath", "WaitDeadTimeOut" )
		if ( result.signal == "WaitDeadTimeOut" ) //can't do endsignal, because it will kill the calling function too
			return
	}
}

void function __WaitUntilDeadTrackerDelayedSignal( table<string, int> master, string signal, float delay )
{
	EndSignal( master, signal )

	wait delay
	if ( IsValid( master ) )
		Signal( master, signal )
}

void function __WaitUntilDeadTracker( entity guy, table<string, int> master )
{
	EndSignal( master, "OnDestroy" )
	if ( IsAlive( guy ) )
		WaitSignal( guy, "OnDeath", "OnDestroy" )

	master.count--
	Signal( master, "OnDeath" )
}

void function __WaitUntilDeadOrLeechedTracker( entity guy, table<string, int> master )
{
	EndSignal( master, "OnDestroy" )
	if ( IsAlive( guy ) )
		WaitSignal( guy, "OnDeath", "OnDestroy", "OnLeeched" )

	master.count--
	Signal( master, "OnDeath" )
}

void function SetRebreatherMaskVisible( entity ent, bool visible )
{
	asset modelname = ent.GetModelName()

	int maskIdx = ent.FindBodyGroup( "mask" )
	if ( maskIdx == -1 )
		return

	int visibleIdx = 1
	if ( !visible )
		visibleIdx = 0

	ent.SetBodygroup( maskIdx, visibleIdx )
}

int function EntHasSpawnflag( entity ent, int spawnflagHexVal )
{
	return ( expect int( ent.kv.spawnflags ) & spawnflagHexVal )
}

entity function CreateInfoTarget( vector origin = <0,0,0>, vector angles = <0,0,0> )
{
	entity info_target = CreateEntity( "info_target" )
	info_target.SetOrigin( origin )
	info_target.SetAngles( angles )
	DispatchSpawn( info_target )
	return info_target
}

entity function CreateExpensiveScriptMover( vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0 )
{
	entity script_mover = CreateEntity( "script_mover" )
	script_mover.kv.solid = solidType
	script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
	script_mover.kv.SpawnAsPhysicsMover = 0
	script_mover.SetOrigin( origin )
	script_mover.SetAngles( angles )

	DispatchSpawn( script_mover )
	return script_mover
}

entity function CreateScriptMover( vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0 )
{
	entity script_mover = CreateEntity( "script_mover_lightweight" )
	script_mover.kv.solid = solidType
	script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
	script_mover.kv.SpawnAsPhysicsMover = 0
	script_mover.SetOrigin( origin )
	script_mover.SetAngles( angles )
	DispatchSpawn( script_mover )
	return script_mover
}

entity function CreateScriptMoverModel( asset model, vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0, float fadeDist = -1 )
{
	entity script_mover = CreateEntity( "script_mover_lightweight" )
	script_mover.kv.solid = solidType
	script_mover.kv.fadedist = fadeDist
	script_mover.SetValueForModelKey( model )
	script_mover.kv.SpawnAsPhysicsMover = 0
	script_mover.SetOrigin( origin )
	script_mover.SetAngles( angles )
	DispatchSpawn( script_mover )
	return script_mover
}

entity function CreateExpensiveScriptMoverModel( asset model, vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0, float fadeDist = -1 )
{
	entity script_mover = CreateEntity( "script_mover" )
	script_mover.kv.solid = solidType
	script_mover.kv.fadedist = fadeDist
	script_mover.SetValueForModelKey( model )
	script_mover.kv.SpawnAsPhysicsMover = 0
	script_mover.SetOrigin( origin )
	script_mover.SetAngles( angles )
	DispatchSpawn( script_mover )
	return script_mover
}

entity function CreateOwnedScriptMover( entity owner )
{
	entity script_mover = CreateEntity( "script_mover" )
	script_mover.kv.solid = 0
	script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
	script_mover.kv.SpawnAsPhysicsMover = 0
	script_mover.SetOrigin( owner.GetOrigin() )
	script_mover.SetAngles( owner.GetAngles() )
	DispatchSpawn( script_mover )
	script_mover.Hide()

	script_mover.SetOwner( owner )
	return script_mover
}

// useful for finding out what ents are in a level. Should only be used for debugging.
int function TotalEnts( bool hidden = false )
{
	array<entity> entities

	EntTracker++

	entity ent = Entities_FindInSphere( null, < 0, 0, 0 >, 90000 )
	string name
	for ( ;; )
	{
		if ( ent == null )
			break

		entities.append( ent )

		name = ent.GetTargetName()

		string strPrefix = "Old ent"
		if ( ent.e.totalEntsStoredID == 0 )
		{
			string strPrefix = "* New ent"
			ent.e.totalEntsStoredID = EntTracker
		}

		string strPostfix = ""
		if ( name != "" )
			strPostfix = " \"" + name + "\""

		if ( !hidden )
			printl( strPrefix + " (" + ent.e.totalEntsStoredID + "): " + ent + strPostfix )

		ent = Entities_FindInSphere( ent, < 0, 0, 0 >, 90000 )
	}

	if ( !hidden )
		printl( "Total entities " + entities.len() )

	return entities.len()
}

bool function IsThreadTop()
{
	return getstackinfos( 3 ) == null
}

string function ThisFunc()
{
	return expect string( expect table( getstackinfos( 2 ) )[ "func" ] )
}

entity function ge( int index ) // shorthand version for typing from console
{
	return GetEntByIndex( index )
}

entity function GetEnt( string name )
{
	entity ent = Entities_FindByName( null, name )
	if ( ent == null )
	{
		ent = Entities_FindByClassname( null, name )
		Assert( Entities_FindByClassname( ent, name ) == null, "Tried to GetEnt but there were multiple entities with that name" )
	}
	else
	{
		Assert( Entities_FindByName( ent, name ) == null, "Tried to GetEnt but there were multiple entities with name " + name )
	}

	return ent
}

array<entity> function ArrayWithinCenter( array<entity> ents, vector start, int range )
{
	array<entity> Array
	foreach ( ent in ents )
	{
		if ( Distance( start, ent.GetWorldSpaceCenter() ) > range )
			continue

		Array.append( ent )
	}

	return Array
}

vector function GetCenter( array<entity> ents )
{
	vector total

	foreach ( ent in ents )
	{
		total += ent.GetOrigin()
	}

	total.x /= float( ents.len() )
	total.y /= float( ents.len() )
	total.z /= float( ents.len() )

	return total
}

void function TableRemoveInvalid( table<entity, entity> Table )
{
	array<entity> deleteKey = []

	foreach ( entity key, entity value in Table )
	{
		if ( !IsValid_ThisFrame( key ) )
			deleteKey.append( key )

		if ( !IsValid_ThisFrame( value ) )
			deleteKey.append( key )
	}

	foreach ( key in deleteKey )
	{
		// in this search, two things could end up on the same key
		if ( key in Table )
			delete Table[ key ]
	}
}

void function TableRemoveInvalidByValue( table<entity, entity> Table )
{
	array<entity> deleteKey = []

	foreach ( key, entity value in Table )
	{
		if ( !IsValid_ThisFrame( value ) )
			deleteKey.append( key )
	}

	foreach ( key in deleteKey )
	{
		delete Table[ key ]
	}
}

void function TableRemoveDeadByKey( table<entity, entity> Table )
{
	array<entity> deleteKey = []

	foreach ( key, value in Table )
	{
		if ( !IsAlive( key ) )
			deleteKey.append( key )
	}

	foreach ( key in deleteKey )
	{
		delete Table[ key ]
	}
}


void function ArrayDump( array<var> Array )
{
	for ( int i = 0; i < Array.len(); i++ )
	{
		printl( "index " + i + " is: " + Array[i] )
	}
}


int function DotCompareLargest( ArrayDotResultStruct a, ArrayDotResultStruct b )
{
	if ( a.dot < b.dot )
		return 1
	else if ( a.dot > b.dot )
		return -1

	return 0
}

int function DotCompareSmallest( ArrayDotResultStruct a, ArrayDotResultStruct b )
{
	if ( a.dot > b.dot )
		return 1
	else if ( a.dot < b.dot )
		return -1

	return 0
}

array<ArrayDotResultStruct> function ArrayDotResults( array<entity> Array, entity ent )
{
	array<ArrayDotResultStruct> allResults

	foreach ( arrayEnt in Array )
	{
		ArrayDotResultStruct results

		results.dot = VectorDot_EntToEnt( ent, arrayEnt )
		results.ent = arrayEnt
		allResults.append( results )
	}

	return allResults
}


// Return an array of entities ordered from closest to furthest from the facing of the entity
array<entity> function ArrayClosestToView( array<entity> Array, entity ent )
{
	Assert( type( Array ) == "array" )
	array<ArrayDotResultStruct> allResults = ArrayDotResults( Array, ent )

	allResults.sort( DotCompareLargest )

	array<entity> returnEntities = []

	foreach ( index, result in allResults )
	{
		//printl( "Results are " + result.dot )
		returnEntities.insert( index, result.ent )
	}

	// the actual distances aren't returned
	return returnEntities
}


entity function SpawnRefEnt( entity ent )
{
	printl( "Ent model " + ent.GetValueForModelKey() )
	int attach = ent.LookupAttachment( "ref" )
	vector origin = ent.GetAttachmentOrigin( attach )
	vector angles = ent.GetAttachmentAngles( attach )

	entity ref = CreateEntity( "prop_dynamic" )
	//ref.kv.SpawnAsPhysicsMover = 0
	ref.SetValueForModelKey( $"models/dev/empty_model.mdl" )
	DispatchSpawn( ref )

	ref.SetOrigin( origin )
	ref.SetAngles( angles )
	ref.Hide()
	return ref
}

entity function CreateScriptRef( vector ornull origin = null, vector ornull angles = null )
{
	entity ent = CreateEntity( "script_ref" )

	if ( origin )
		ent.SetOrigin( expect vector( origin ) )

	if ( angles )
		ent.SetAngles( expect vector( angles ) )

	DispatchSpawn( ent )
	return ent
}

entity function CreateScriptRefMinimap( vector origin, vector angles )
{
	entity ent = CreateEntity( "script_ref_minimap" )

	ent.SetOrigin( origin )
	ent.SetAngles( angles )

	DispatchSpawn( ent )

	return ent
}

bool function exists( table tbl, string val )
{
	if ( !(val in tbl) )
		return false

	return tbl[ val ] != null
}

var function TableRandomIndex( table Table )
{
	array Array = []

	foreach ( index, _ in Table )
	{
		Array.append( index )
	}

	return Array.getrandom()
}

// should improve this
float function YawDifference( float yaw1, float yaw2 )
{
	Assert( yaw1 >= 0 )
	Assert( yaw1 <= 360 )
	Assert( yaw2 >= 0 )
	Assert( yaw2 <= 360 )

	float diff = fabs( yaw1 - yaw2 )

	if ( diff > 180 )
	    return 360 - diff
	else
	    return diff

	unreachable
}



/*function TrackIsTouching( ent )
{
	return // now uses IsTouching

	//ent.s.touching <- {}
	//ent.ConnectOutput( "OnStartTouch", TrackIsTouching_OnStartTouch )
	//ent.ConnectOutput( "OnEndTouch", TrackIsTouching_OnEndTouch )
}

void function TrackIsTouching_OnStartTouch( entity self, entity activator, entity caller, var value )
{
	if ( activator )
	{
		self.s.touching[ activator ] <- true
	}
}

void function TrackIsTouching_OnEndTouch( entity self, entity activator, entity caller, var value )
{
	if ( activator )
	{
		if ( activator in self.s.touching )
		{
			delete self.s.touching[ activator ]
		}
	}
}*/

void function NPC_NoTarget( entity self )
{
	self.SetNoTarget( true )
	self.SetNoTargetSmartAmmo( true )
}

vector function GetSimpleTraceEnd( vector start, vector end, float frac )
{
	vector vec = end - start
	vec *= frac
	return start + vec
}

bool function LoadedMain()
{
	if ( "LoadedMain" in level )
		return true

	level.LoadedMain <- true

	return false
}

void function Warning( string msg )
{
	printl( "*** WARNING ***" )
	printl( msg )
	DumpStack()
	printl( "*** WARNING ***" )
}

void function TimeOut( float time )
{
	table Table = {}
	EndSignal( Table, "OnDeath" )
	delaythread( time ) Signal( Table, "OnDeath" )
}

string function GetActiveWeaponClass( entity player )
{
	entity weapon = player.GetActiveWeapon()
	Assert( weapon != null )

	string weaponclass = weapon.GetWeaponClassName()
	return weaponclass
}

bool function HasWeapon( entity ent, string weaponClassName, array<string> mods = [] )
{
	Assert( ent.IsPlayer() || ent.IsNPC() )

	array<entity> weaponArray = ent.GetMainWeapons()
	foreach ( weapon in weaponArray )
	{
		if ( weapon.GetWeaponClassName() == weaponClassName )
		{
			if ( WeaponHasSameMods( weapon, mods ) )
				return true
		}
	}

	return false
}

bool function HasOrdnance( entity ent, string weaponClassName, array<string> mods = [] )
{
	return HasOffhandForSlot( ent, OFFHAND_ORDNANCE, weaponClassName, mods )
}

bool function HasCoreAbility( entity ent, string weaponClassName, array<string> mods = [] )
{
	return HasOffhandForSlot( ent, OFFHAND_EQUIPMENT, weaponClassName, mods )
}

bool function HasSpecial( entity ent, string weaponClassName, array<string> mods = [] )
{
	return HasOffhandForSlot( ent, OFFHAND_SPECIAL, weaponClassName, mods )
}

bool function HasAntiRodeo( entity ent, string weaponClassName, array<string> mods = [] )
{
	return HasOffhandForSlot( ent, OFFHAND_ANTIRODEO, weaponClassName, mods )
}

bool function HasMelee( entity ent, string weaponClassName, array<string> mods = [] )
{
	return HasOffhandForSlot( ent, OFFHAND_MELEE, weaponClassName, mods )
}

bool function HasOffhandForSlot( entity ent, int slot, string weaponClassName, array<string> mods = [] )
{
	Assert( ent.IsPlayer() || ent.IsNPC() )

	entity weapon = ent.GetOffhandWeapon( slot )
	if ( !IsValid( weapon ) )
		return false

	if ( weapon.GetWeaponClassName() != weaponClassName )
		return false

	return WeaponHasSameMods( weapon, mods )
}

bool function WeaponHasSameMods( entity weapon, array<string> mods = [] )
{
	array hasMods = clone mods
	foreach ( mod in weapon.GetMods() )
	{
		hasMods.removebyvalue( mod )
	}

	// has all the same mods.
	return hasMods.len() == 0
}

bool function HasOffhandWeapon( entity ent, string weaponClassName )
{
	Assert( ent.IsPlayer() || ent.IsNPC() )

	array<entity> weaponArray = ent.GetOffhandWeapons()
	foreach ( weapon in weaponArray )
	{
		if ( weapon.GetWeaponClassName() == weaponClassName )
			return true
	}

	return false
}

float function GetFraction( float value, float min, float max )
{
	return ( value - min ) / ( max - min )
}

float function GetFractionClamped( float value, float min, float max )
{
	float frac = GetFraction( value, min, max )
	return clamp( frac, 0.0, 1.0 )
}

float function GetValueFromFraction( float value, float value_min, float value_max, float return_min, float return_max )
{
	float frac = GetFractionClamped( value, value_min, value_max )
	float retVal = return_min + ( ( return_max - return_min ) * frac )
	return clamp( retVal, return_min, return_max )
}

bool function VectorCompare( vector vec1, vector vec2 )
{
	if ( vec1.x != vec2.x )
		return false

	if ( vec1.y != vec2.y )
		return false

	return vec1.z == vec2.z
}

// returns vectordot from viewEnt to targetEnt
float function VectorDot_EntToEnt( entity viewEnt, entity targetEnt )
{
	vector maxs = targetEnt.GetBoundingMaxs()
	vector mins = targetEnt.GetBoundingMins()
	maxs += mins
	maxs.x *= 0.5
	maxs.y *= 0.5
	maxs.z *= 0.5
	vector targetOrg = targetEnt.GetOrigin() + maxs

	maxs = viewEnt.GetBoundingMaxs()
	mins = viewEnt.GetBoundingMins()
	maxs += mins
	maxs.x *= 0.5
	maxs.y *= 0.5
	maxs.z *= 0.5
	vector viewOrg = viewEnt.GetOrigin() + maxs

	//DebugDrawLine( targetOrg, viewOrg, 255, 255, 255, true, 0.5 )
	vector vecToEnt = ( targetOrg - viewOrg )
	vecToEnt = Normalize( vecToEnt )

	float dotVal = DotProduct( vecToEnt, viewEnt.GetForwardVector() )
	return dotVal
}

void function PrecacheEffect( asset effectName )
{
	entity warningParticle = CreateEntity( "info_particle_system" )
	warningParticle.SetValueForEffectNameKey( effectName )
	warningParticle.kv.start_active = 0
	DispatchSpawn( warningParticle )
	warningParticle.Destroy()
}

void function PrecacheEntity( string entName, asset model = $"" )
{
	entity tempEnt = CreateEntity( entName )

	if ( model != $"" )
		tempEnt.SetValueForModelKey( model )

	tempEnt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID

	DispatchSpawn( tempEnt )
	tempEnt.Destroy()
}

void function PrecacheProjectileEntity( string entName, string weaponClassName, asset model = $"" )
{
	entity tempEnt = Entities_CreateProjectileByClassname( entName, weaponClassName )

	if ( model != $"" )
		tempEnt.SetValueForModelKey( model )

	tempEnt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID

	DispatchSpawn( tempEnt )
	tempEnt.Destroy()
}

void function PrecacheSprite( asset spriteName )
{
	entity sprite = CreateEntity( "env_sprite_oriented" )
	sprite.SetValueForModelKey( spriteName )
	sprite.kv.spawnflags = 1
	DispatchSpawn( sprite )
	sprite.Destroy()
}

entity function CreatePointMessage( string msg, vector origin, int displayRadius = 512 )
{
	entity point_message = CreateEntity( "point_message" )
	point_message.SetOrigin( origin )
	point_message.kv.message = msg
	point_message.kv.radius = displayRadius

	DispatchSpawn( point_message )

	return point_message
}

entity function CreateGameText( string msg, float xPos, float yPos, int channel, string color = "255 255 255", float fadein = 2, float fadeout = 0.5, float holdtime = 2 )
{
	entity game_text = CreateEntity( "game_text" )

	game_text.SetScriptName( "gt" + UniqueString()  )
	game_text.kv.message = msg
	game_text.kv["x"] = xPos
	game_text.kv["y"] = yPos
	game_text.kv.channel = channel
	game_text.kv.color = color
	game_text.kv.color2 = "240 110 0"  // doesn't appear to do anything atm, not supporting in params
	game_text.kv.fadein = fadein
	game_text.kv.fadeout = fadeout
	game_text.kv.holdtime = holdtime
	game_text.kv.fxtime = "0.25"

	DispatchSpawn( game_text )

	return game_text
}

// pass the origin where the player's feet would be
// tests a player sized box for any collision and returns true only if it's clear
bool function PlayerCanTeleportHere( entity player, vector testOrg, entity ignoreEnt = null ) //TODO: This is a copy of SP's PlayerPosInSolid(). Not changing it to avoid patching SP. Merge into one function next game
{
	int solidMask = TRACE_MASK_PLAYERSOLID
	vector mins
	vector maxs
	int collisionGroup = TRACE_COLLISION_GROUP_PLAYER
	array<entity> ignoreEnts = [ player ]

	if ( IsValid( ignoreEnt ) )
		ignoreEnts.append( ignoreEnt )
	TraceResults result

	mins = player.GetPlayerMins()
	maxs = player.GetPlayerMaxs()
	result = TraceHull( testOrg, testOrg + < 0, 0, 1 >, mins, maxs, ignoreEnts, solidMask, collisionGroup )

	if ( result.startSolid )
		return false

	return true
}


enum eAttach
{
	No
	ViewAndTeleport
	View
	Teleport
	ThirdPersonView
}


void function LoadDiamond()
{
    printl( " " )
    printl( " " )
    printl( " " )

    // Draw a diamond of a random size and phase (of the moon) so it is easy to separate sections of logs.
    int random_spread = RandomIntRange( 4, 7 )
    float random_fullness = RandomFloat( 2.0 )
    bool functionref( int, int ) compare_func
    string msg

    if ( RandomFloat( 1.0 ) > 0.5 )
    {
	    compare_func = bool function( int a, int b )
	    {
		    return a <= b
	    }
    }
    else
    {
	    compare_func = bool function( int a, int b )
	    {
		    return a >= b
	    }
    }

    for ( int i = 0; i <= random_spread - 2; i++ )
    {
	    msg = ""

	    for ( int p = 0; p <= random_spread - i; p++ )
	    {
		    msg = msg + " "
	    }

	    for ( int p = 0; p <= i * 2; p++ )
	    {
		    if ( p == i * 2 || p == 0 )
		    {
			    msg = msg + "*"
		    }
		    else
		    {
			    int an_int = int( i * random_fullness )

			    if ( compare_func( p, an_int ) )
				    msg = msg + "*"
			    else
				    msg = msg + " "
		    }
	    }

	    printl( msg )
    }

    for ( int i = random_spread - 1; i >= 0; i-- )
    {
	    msg = ""

	    for ( int p = 0; p <= random_spread - i; p++ )
	    {
		    msg = msg + " "
	    }


	    for ( int p = 0; p <= i * 2; p++ )
	    {
		    if ( p == i * 2 || p == 0 )
		    {
			    msg = msg + "*"
		    }
		    else
		    {
			    if ( compare_func( p, int( i * random_fullness ) ) )
			    {
				    msg = msg + "*"
			    }
			    else
			    {
				    msg = msg + " "
			    }
		    }
	    }

	    printl( msg )
    }

    printl( " " )
    printl( " " )
    printl( " " )
}

// this will clear all dropped weapons in the map
void function ClearDroppedWeapons( float delayTime = 0.0 )
{
	if ( delayTime > 0 )
		wait delayTime

	bool onlyNotOwnedWeapons = true  // don't get the ones in guys' hands
	array<entity> weapons = GetWeaponArray( onlyNotOwnedWeapons )

	foreach ( weapon in weapons )
	{
		// don't clean up weapon pickups that were placed in leveled
		int spawnflags = expect string( weapon.kv.spawnflags ).tointeger()
		if ( spawnflags & SF_WEAPON_START_CONSTRAINED )
			continue

		weapon.Destroy()
	}
}

void function ClearActiveProjectilesForTeam( int team, vector searchOrigin = <0,0,0>, float searchDist = -1 )
{
	array<entity> projectiles = GetProjectileArrayEx( "any", team, TEAM_ANY, searchOrigin, searchDist )

	printt( "cleaning up", projectiles.len(), "weapon projectiles for team", team )

	foreach ( proj in projectiles )
	{
		if( !IsValid( proj ) )
			continue

		proj.Destroy()
	}
}

void function RestockPlayerAmmo_Silent( entity player = null )
{
	RestockPlayerAmmo( player, true )
}

void function RestockPlayerAmmo( entity player = null, bool isSilent = false )
{
	array<entity> players
	if ( IsAlive( player ) )
		players.append( player )
	else
		players = GetPlayerArray_Alive()

	foreach( player in players )
	{
		player.RefillAllAmmo()

		if ( !isSilent )
			EmitSoundOnEntityOnlyToPlayer( player, player, "Coop_AmmoBox_AmmoRefill" )
	}
}

entity function CreateLightSprite( vector origin, vector angles, string lightcolor = "255 0 0", float scale = 0.5 )
{
	// attach a light so we can see it
	entity env_sprite = CreateEntity( "env_sprite" )
	env_sprite.SetScriptName( UniqueString( "molotov_sprite" ) )
	env_sprite.kv.rendermode = 5
	env_sprite.kv.origin = origin
	env_sprite.kv.angles = angles
	env_sprite.kv.rendercolor = lightcolor
	env_sprite.kv.renderamt = 255
	env_sprite.kv.framerate = "10.0"
	env_sprite.SetValueForModelKey( $"sprites/glow_05.vmt" )
	env_sprite.kv.scale = string( scale )
	env_sprite.kv.spawnflags = 1
	env_sprite.kv.GlowProxySize = 16.0
	env_sprite.kv.HDRColorScale = 1.0
	DispatchSpawn( env_sprite )
	EntFireByHandle( env_sprite, "ShowSprite", "", 0, null, null )

	return env_sprite
}

// defaultWinner: if it's a tie, return this value
int function GetCurrentWinner( int defaultWinner = TEAM_MILITIA )
{
	int imcScore
	int militiaScore

	if ( IsRoundBased() )
	{
		imcScore = GameRules_GetTeamScore2( TEAM_IMC )
		militiaScore = GameRules_GetTeamScore2( TEAM_MILITIA )

		if ( IsRoundBasedUsingTeamScore() && ( imcScore == militiaScore ) )
		{
			imcScore = GameRules_GetTeamScore( TEAM_IMC )
			militiaScore = GameRules_GetTeamScore( TEAM_MILITIA )
		}
	}
	else
	{
		imcScore = GameRules_GetTeamScore( TEAM_IMC )
		militiaScore = GameRules_GetTeamScore( TEAM_MILITIA )
	}

	int currentWinner = defaultWinner

	if ( militiaScore > imcScore )
		currentWinner = TEAM_MILITIA
	else if ( imcScore > militiaScore )
		currentWinner = TEAM_IMC

	return currentWinner
}

void function SetNpcFollowsPlayerOverride( entity player, void functionref( entity, entity ) override )
{
	player.p.followPlayerOverride = override
}

void function ClearNpcFollowsPlayerOverride( entity player )
{
	player.p.followPlayerOverride = null
}

void function AddCallback_GameStateEnter( int gameState, void functionref() callbackFunc )
{
	Assert( gameState < svGlobal.gameStateEnterCallbacks.len() )

	Assert( !svGlobal.gameStateEnterCallbacks[ gameState ].contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_GameStateEnter" )

	svGlobal.gameStateEnterCallbacks[ gameState ].append( callbackFunc )
}

void function GM_SetObserverFunc( void functionref( entity ) callbackFunc )
{
	svGlobal.observerFunc = callbackFunc
}

void function GM_AddPlayingThinkFunc( void functionref() callbackFunc )
{
	Assert( !svGlobal.playingThinkFuncTable.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with GM_AddPlayingThinkFunc" )

	svGlobal.playingThinkFuncTable.append( callbackFunc )
}

void function GM_AddThirtySecondsLeftFunc( void functionref() callbackFunc )
{
	Assert( !svGlobal.thirtySecondsLeftFuncTable.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with GM_AddThirtySecondsLeftFunc" )

	svGlobal.thirtySecondsLeftFuncTable.append( callbackFunc )
}

void function GM_SetMatchProgressAnnounceFunc( void functionref( int ) callbackFunc )
{
	svGlobal.matchProgressAnnounceFunc = callbackFunc
}

// Get an absolute offset poistion to an entity even if it's been rotated in world space
vector function GetEntPosPlusOffset( entity ent, float offsetX, float offsetY, float offsetZ )
{
	vector entAngles = ent.GetAngles()
	vector entOrg = ent.GetOrigin()

	vector right = AnglesToRight( entAngles )
	right = Normalize( right )
	vector pos = entOrg + ( right * offsetY )

	vector forward = AnglesToForward( entAngles )
	forward = Normalize( forward )
	pos = pos + ( forward * offsetX )

	vector up = AnglesToUp( entAngles )
	up = Normalize( up )
	pos = pos + ( up * offsetZ )

	return pos
}

void function EmitSoundToTeamPlayers( string alias, int team )
{
	array<entity> players = GetPlayerArrayOfTeam( team )

	foreach ( player in players )
		EmitSoundOnEntityOnlyToPlayer( player, player, alias )
}

void function EmitDifferentSoundsAtPositionForPlayerAndWorld( string soundForPlayer, string soundForWorld, vector position, entity player, int teamNum )
{
	if ( IsValid( player ) && player.IsPlayer() )
	{
		EmitSoundAtPositionExceptToPlayer( teamNum, position, player, soundForWorld )
		EmitSoundAtPositionOnlyToPlayer( teamNum, position, player, soundForPlayer)
	}
	else
	{
		EmitSoundAtPosition( teamNum, position, soundForWorld )
	}
}

void function EmitDifferentSoundsOnEntityForPlayerAndWorld( string soundForPlayer, string soundForWorld, entity soundEnt, entity player )
{
	if ( IsValid( player ) && player.IsPlayer() )
	{
		EmitSoundOnEntityExceptToPlayerNotPredicted( soundEnt, player, soundForWorld )
		EmitSoundOnEntityOnlyToPlayer(soundEnt, player, soundForPlayer)
	}
	else
	{
		EmitSoundOnEntity( soundEnt, soundForWorld )
	}
}

// Drop an entity to the ground by tracing straight down from its z-axis
void function DropToGround( entity ent )
{
	vector targetOrigin = OriginToGround( ent.GetOrigin() + <0,0,1> )
	ent.SetOrigin( targetOrigin )
}

void function DropTitanToGround( entity titan, array<entity> ignoreEnts )
{
	vector endOrigin = titan.GetOrigin() - < 0, 0, 20000 >
	vector mins = GetBoundsMin( HULL_TITAN )
	vector maxs = GetBoundsMax( HULL_TITAN )
	TraceResults traceResult = TraceHull( titan.GetOrigin(), endOrigin, mins, maxs, ignoreEnts, TRACE_MASK_TITANSOLID, TRACE_COLLISION_GROUP_NONE )

	titan.SetOrigin( traceResult.endPos )
}

vector function OriginToGround( vector origin )
{
	vector endOrigin = origin - < 0, 0, 20000 >
	TraceResults traceResult = TraceLine( origin, endOrigin, [], TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )

	return traceResult.endPos
}

float function GetVerticalClearance( vector origin )
{
	vector endOrigin = origin + < 0, 0, 20000 >
	TraceResults traceResult = TraceLine( origin, endOrigin, [], TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )
	vector endPos = traceResult.endPos
	float zDelta = ( endPos.z - origin.z )

	return zDelta
}

// ---------------------------------------------------------------------
// Determine if an entity is a valid player spawnpoint
// ---------------------------------------------------------------------
bool function PlayerSpawnpointIsValid( entity ent )
{
	if ( ent.GetClassName() != "prop_dynamic" )
		return false
	if ( ent.GetValueForModelKey() != $"models/humans/pete/mri_male.mdl" )
		return false

	return true
}

// ---------------------------------------------------------------------
// Make an NPC or the player invincible (true/false)
//	(_npc.nut intercepts incoming damage and negates it if the ent is tagged as invincible)
// ---------------------------------------------------------------------
void function MakeInvincible( entity ent )
{
	Assert( IsValid( ent ), "Tried to make invalid " + ent + " invincible" )
	Assert( ent.IsNPC() || ent.IsPlayer(), "MakeInvincible() can only be called on NPCs and the player" )
	Assert( IsAlive( ent ), "Tried to make dead ent " + ent + " invincible" )

	ent.SetInvulnerable()
}

void function ClearInvincible( entity ent )
{
	Assert( IsValid( ent ), "Tried to clear invalid " + ent + " invincible" )

	ent.ClearInvulnerable()
}

bool function IsInvincible( entity ent )
{
	return ent.IsInvulnerable()
}

//-------------------------------------
// Teleport an entity (teleporter) to an entity's org and angles (ent)
//--------------------------------------
void function TeleportToEnt( entity teleporter, entity ent )
{
	Assert( teleporter != null, "Unable to teleport null entity" )
	Assert( ent != null, "Unable to teleport to a null entity" )
	teleporter.SetOrigin( ent.GetOrigin() )
	teleporter.SetAngles( ent.GetAngles() )
}

//-----------------------------------------------------------
// CreateShake() - create and fire an env_shake at a specified origin
// - returns the shake in case you want to parent it
//------------------------------------------------------------

entity function CreateShake_internal( vector org, float amplitude, float frequency, float duration, float radius, int spawnFlags )
{
	entity env_shake = CreateEntity( "env_shake" )
	env_shake.kv.amplitude = amplitude
	env_shake.kv.radius = radius
	env_shake.kv.duration = duration
	env_shake.kv.frequency = frequency
	env_shake.kv.spawnflags = spawnFlags

	DispatchSpawn( env_shake )

	env_shake.SetOrigin( org )

	EntFireByHandle( env_shake, "StartShake", "", 0, null, null )
	EntFireByHandle( env_shake, "Kill", "", ( duration + 1 ), null, null )

	return env_shake
}

entity function CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, 0 );
}

entity function CreateShakeRumbleOnly( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_RUMBLE_ONLY );
}

entity function CreateShakeNoRumble( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_NO_RUMBLE );
}

entity function CreateAirShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_INAIR );
}

entity function CreateAirShakeRumbleOnly( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, (SF_SHAKE_INAIR | SF_SHAKE_RUMBLE_ONLY) );
}

entity function CreateAirShakeNoRumble( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
{
	return CreateShake_internal( org, amplitude, frequency, duration, radius, (SF_SHAKE_INAIR | SF_SHAKE_NO_RUMBLE) );
}

//-------------------------------------
// CreatePhysExplosion - physExplosion...small, medium or large
//--------------------------------------
entity function CreatePhysExplosion( vector org, float radius, int magnitude = 1, int flags = 1, bool dealsDamage = true )
{
	entity env_physexplosion = CreateEntity( "env_physexplosion" )
	env_physexplosion.kv.spawnflags = flags // default 1 = No Damage - Only Force
	env_physexplosion.kv.magnitude = magnitude
	env_physexplosion.kv.radius = string( radius )
	env_physexplosion.SetOrigin( org )
	env_physexplosion.kv.scriptDamageType = damageTypes.explosive
	DispatchSpawn( env_physexplosion )

	EntFireByHandle( env_physexplosion, "Explode", "", 0, null, null )
	EntFireByHandle( env_physexplosion, "Kill", "", 2, null, null )
}

//-----------------------------------------------------------
// CreatePropDynamic( model ) - create a generic prop_dynamic with default properties
//------------------------------------------------------------
entity function CreatePropDynamic( asset model, vector ornull origin = null, vector ornull angles = null, var solidType = 0, float fadeDist = -1 )
{
	entity prop_dynamic = CreateEntity( "prop_dynamic" )
	prop_dynamic.SetValueForModelKey( model )
	prop_dynamic.kv.fadedist = fadeDist
	prop_dynamic.kv.renderamt = 255
	prop_dynamic.kv.rendercolor = "255 255 255"
	prop_dynamic.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
		prop_dynamic.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_dynamic.SetAngles( expect vector( angles ) )
	}
	DispatchSpawn( prop_dynamic )
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
		prop_dynamic.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_dynamic.SetAngles( expect vector( angles ) )
	}

	return prop_dynamic
}


//-----------------------------------------------------------
// CreatePropDynamicLightweight( model ) - create a generic prop_dynamic_lightweight with default properties
//------------------------------------------------------------
entity function CreatePropDynamicLightweight( asset model, vector ornull origin = null, vector ornull angles = null, var solidType = 0, float fadeDist = -1 )
{
	entity prop_dynamic = CreateEntity( "prop_dynamic_lightweight" )
	prop_dynamic.SetValueForModelKey( model )
	prop_dynamic.kv.fadedist = fadeDist
	prop_dynamic.kv.renderamt = 255
	prop_dynamic.kv.rendercolor = "255 255 255"
	prop_dynamic.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
		prop_dynamic.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_dynamic.SetAngles( expect vector( angles ) )
	}
	DispatchSpawn( prop_dynamic )
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
		prop_dynamic.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_dynamic.SetAngles( expect vector( angles ) )
	}

	return prop_dynamic
}


//-----------------------------------------------------------
// CreatePropScript( model ) - create a generic prop_script with default properties
//------------------------------------------------------------
entity function CreatePropScript( asset model, vector ornull origin = null, vector ornull angles = null, int solidType = 0, float fadeDist = -1 )
{
	entity prop_script = CreateEntity( "prop_script" )
	prop_script.SetValueForModelKey( model )
	prop_script.kv.fadedist = fadeDist
	prop_script.kv.renderamt = 255
	prop_script.kv.rendercolor = "255 255 255"
	prop_script.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
		prop_script.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_script.SetAngles( expect vector( angles ) )
	}
	DispatchSpawn( prop_script )
	if ( origin )
	{
		// hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
		prop_script.SetOrigin( expect vector( origin ) )
		if ( angles )
			prop_script.SetAngles( expect vector( angles ) )
	}

	return prop_script
}



//-----------------------------------------------------------
// CreatePropPhysics( model ) - create a generic prop_physics with default properties
//------------------------------------------------------------
entity function CreatePropPhysics( asset model, vector origin, vector angles )
{
	entity prop_physics = CreateEntity( "prop_physics" )
	prop_physics.SetValueForModelKey( model )
	prop_physics.kv.spawnflags = 0
	prop_physics.kv.fadedist = -1
	prop_physics.kv.physdamagescale = 0.1
	prop_physics.kv.inertiaScale = 1.0
	prop_physics.kv.renderamt = 255
	prop_physics.kv.rendercolor = "255 255 255"
	SetTeam( prop_physics, TEAM_BOTH )	// need to have a team other then 0 or it won't take impact damage

	prop_physics.SetOrigin( origin )
	prop_physics.SetAngles( angles )
	DispatchSpawn( prop_physics )

	return prop_physics
}

//-----------------------------------------------------------
// SpawnBullseye() - creates a npc_bullseye and attaches it to an entity
//------------------------------------------------------------
entity function SpawnBullseye( int team, entity ent = null )
{
	entity bullseye = CreateEntity( "npc_bullseye" )
	bullseye.SetScriptName( UniqueString( "bullseye" ) )
	bullseye.kv.rendercolor = "255 255 255"
	bullseye.kv.renderamt = 0
	bullseye.kv.health = 9999
	bullseye.kv.max_health = -1
	bullseye.kv.spawnflags = 516
	bullseye.kv.FieldOfView = 0.5
	bullseye.kv.FieldOfViewAlert = 0.2
	bullseye.kv.AccuracyMultiplier = 1.0
	bullseye.kv.physdamagescale = 1.0
	bullseye.kv.WeaponProficiency = eWeaponProficiency.VERYGOOD
	bullseye.kv.minangle = "360"
	DispatchSpawn( bullseye )

	SetTeam( bullseye, team )

	if ( ent )
	{
		vector bounds = ent.GetBoundingMaxs()
		bullseye.SetOrigin( ent.GetOrigin() + < 0, 0, bounds.z * 0.5 > )
		bullseye.SetParent( ent )
	}

	return bullseye
}

void function CenterPrint( ... )
{
	string msg = ""
	for ( int i = 0; i < vargc; i++ )
		msg = ( msg + " " + string( vargv[ i ] ) )

	int words = expect int( vargc )
	if ( words < 1 )
		words = 1

	float delay = GraphCapped( float( words ), 2.0, 8.0, 2.1, 3.5 )

	entity ent = CreateGameText( msg, -1, 0.5, 10, "255 255 255", 0.25, 0.25, delay )
	EntFireByHandle( ent, "Display", "", 0, null, null )

	thread DestroyCenterPrint( ent, delay )
}

void function DestroyCenterPrint( entity ent, float delay )
{
	wait( delay )
	ent.Destroy()
}

bool function IsValidPlayer( entity player )
{
	if ( !IsValid( player ) )
		return false

	if ( !player.IsPlayer() )
		return false

	if ( IsDisconnected( player ) )
		return false

	return true
}

bool function IsDisconnected( entity player )
{
	return player.p.isDisconnected
}

/****************************************************************************************************\
/*
|*											PLAY FX
\*
\****************************************************************************************************/

entity function PlayFX( asset effectName, vector org, vector ornull optionalAng = null, vector ornull overrideAngle = null )
{
	return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, -1, null, overrideAngle )
}

entity function PlayFXWithControlPoint( asset effectName, vector org, entity cpoint1, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null, int _type = C_PLAYFX_SINGLE )
{
	return __CreateFxInternal( effectName, null, "", org, null, _type, cpoint1, visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle)
}

entity function PlayFXOnEntityWithControlPoint( asset effectName, entity ent, entity cpoint1, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null, int _type = C_PLAYFX_SINGLE )
{
	return __CreateFxInternal( effectName, ent, "", null, null, _type, cpoint1, visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle)
}

entity function PlayFXOnEntity( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null )
{
	return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_SINGLE, null,  visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle )
}

entity function PlayFXForPlayer( asset effectName, entity player, vector ornull org, vector ornull optionalAng = null )
{
	return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, ENTITY_VISIBLE_TO_OWNER, player )
}

entity function PlayFXOnEntityForEveryoneExceptPlayer( asset effectName, entity ent, entity player, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null )
{
	return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_SINGLE, null, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), player )
}

entity function PlayFXForEveryoneExceptPlayer( asset effectName, entity player, vector ornull org, vector ornull optionalAng = null )
{
	return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), player )
}

void function PlayFXOnTitanPlayerForTime( asset effectName, entity titan, string attachment, float duration )
{
	titan.EndSignal( "OnDeath" )
	titan.EndSignal( "DisembarkingTitan" )
	titan.EndSignal( "TitanEjectionStarted" )

	entity fx = PlayFXOnEntityForEveryoneExceptPlayer( effectName, titan, titan, attachment )

	OnThreadEnd(
	function() : ( fx )
		{
			if ( IsValid(fx) )
			{
				fx.Destroy()
			}
		}
	)

	wait duration
}

entity function ClientStylePlayFXOnEntity( asset effectName, entity ent, string tag, float duration = 2.0 )
{
	string name = ent.GetScriptName()
	ent.SetScriptName( UniqueString() ) // hack because you can only specify control points by name
	// hack this is also not quite right because we can't specify the attachment type on the server... should be trivial to add in code:
	// change DEFINE_FIELD( m_parentAttachmentType, FIELD_INTEGER ), to DEFINE_KEYFIELD( m_parentAttachmentType, FIELD_INTEGER, "attachmentType" ),
	entity result = __CreateFxInternal( effectName, ent, tag, null, null, C_PLAYFX_SINGLE, ent, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), ent )
	EntFireByHandle( result, "Kill", "", duration, null, null )
	ent.SetScriptName( name )

	return result
}

entity function PlayLoopFX( asset effectName, vector ornull org, vector ornull optionalAng = null )
{
	return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_LOOP )
}

entity function PlayLoopFXOnEntity( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null )
{
	return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_LOOP, null, visibilityFlagOverride, visibilityFlagEntOverride )
}

entity function __CreateFxInternal( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = <0,0,0>, vector ornull optionalRotation = <0,0,0>,
									int _type = C_PLAYFX_SINGLE, entity cpointEnt1 = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null )
{
	entity fx = CreateEntity( "info_particle_system" )
	fx.SetValueForEffectNameKey( effectName )
	if( visibilityFlagOverride != -1 )
		fx.kv.VisibilityFlags = visibilityFlagOverride
	else
		fx.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
	fx.kv.start_active = 1
	fx.e.fxType = _type

	if ( _type == C_PLAYFX_SINGLE )
		fx.DoNotCreateFXOnRestore();

	vector coreOrg
	vector coreAng

	//are we attaching to an ent?
	if ( ent )
	{
		//are we attaching to a tag on an ent
		if ( optionalTag != "" )
		{
			int attachID = ent.LookupAttachment( optionalTag )
			coreOrg = ent.GetAttachmentOrigin( attachID )
			coreAng = ent.GetAttachmentAngles( attachID )
		}
		else
		{
			coreOrg = ent.GetOrigin()
			coreAng = ent.GetAngles()
		}

		fx.Code_SetTeam( ent.GetTeam() );
	}
	else //if not we're just playing in space
	{
		optionalTag = ""
		coreOrg = < 0, 0, 0 >
		coreAng = < 0, 0, 0 >
	}

	if ( optionalTranslation )
	{
		expect vector( optionalTranslation )
		if ( ent )
			coreOrg = PositionOffsetFromEnt( ent, optionalTranslation.x, optionalTranslation.y, optionalTranslation.z )
		else
			coreOrg = coreOrg + optionalTranslation
	}

	coreOrg = ClampToWorldspace( coreOrg )
	fx.SetOrigin( coreOrg )

	if ( overrideAngle )
	{
		expect vector( overrideAngle )
		fx.SetAngles( overrideAngle )
	}
	else if ( optionalRotation )
	{
		expect vector( optionalRotation )
		fx.SetAngles( coreAng + optionalRotation )
	}
	else
	{
		fx.SetAngles( coreAng )
	}

	if ( ent )
	{
		if ( !ent.IsMarkedForDeletion() ) // TODO: This is a hack for shipping. The real solution is to spawn the FX before deleting the parent entity.
		{
			fx.SetParent( ent, optionalTag, true )
		}
	}

	if ( visibilityFlagEntOverride != null )
		fx.SetOwner( visibilityFlagEntOverride )

	if ( cpointEnt1 )
		fx.kv.cpoint1 = cpointEnt1.GetTargetName()

	DispatchSpawn( fx )
	thread __DeleteFxInternal( fx )

	//SetTargetName( fx, "FX_" + effectName )
	return fx
}

void function __DeleteFxInternal( entity fx )
{
	//if it loops or is multiple then don't delete internally
	if ( fx.e.fxType == C_PLAYFX_MULTIPLE )
		return

	if ( fx.e.fxType == C_PLAYFX_LOOP )
		return

	wait 30 //no way to know when an effect is over
	if ( !IsValid( fx ) )
		return

	fx.ClearParent()
	fx.Destroy()
}

void function StartFX( entity fx )
{
	Assert( fx.e.fxType == C_PLAYFX_LOOP, "Tried to use StartFX() on effect that is not LOOPING" )
	EntFireByHandle( fx, "Start", "", 0, null, null )
}

void function StopFX( entity fx )
{
	Assert( fx.e.fxType == C_PLAYFX_LOOP, "Tried to use StopFX() on effect that is not LOOPING" )
	EntFireByHandle( fx, "Stop", "", 0, null, null )
}

void function ReplayFX( entity fx )
{
	Assert( fx.e.fxType == C_PLAYFX_MULTIPLE, "Tried to use ReplayFX() on effect that is not MULTIPLE" )
	//thread it because there is a WaitFrame() inside the function
	thread __ReplayFXInternal( fx )
}

void function __ReplayFXInternal( entity fx )
{
	//for non-looping fx, we must stop first before we can fire again
	EntFireByHandle( fx, "Stop", "", 0, null, null )
	//we can't start in the same frame, WaitFrame() skips 1 false
	//it should be noted that "WaitFrame()" doesn't work with a timescale above 1
	WaitFrame()
	//may have died since the last frame
	if ( IsValid( fx ) )
		EntFireByHandle( fx, "Start", "", 0, null, null )
}

void function Entity_StopFXArray( entity ent )
{
	foreach( fx in ent.e.fxArray )
	{
		if ( IsValid( fx ) )
		{
			StopFX( fx )
			fx.Destroy()
		}
	}
}

/****************************************************************************************************\
|*											end play fx
\****************************************************************************************************/


table function GetDamageTableFromInfo( var damageInfo )
{
	table Table = {}
	Table.damageSourceId <- DamageInfo_GetDamageSourceIdentifier( damageInfo )
	Table.origin <- DamageInfo_GetDamagePosition( damageInfo )
	Table.force <- DamageInfo_GetDamageForce( damageInfo )
	Table.scriptType <- DamageInfo_GetCustomDamageType( damageInfo )

	return Table
}

bool function EntityInSolid( entity ent, entity ignoreEnt = null, int buffer = 0 ) //TODO:  This function returns true for a player standing inside a friendly grunt. It also returns true if you are right up against a ceiling.Needs fixing for next game
{
	Assert( IsValid( ent ) )
	int solidMask
	vector mins
	vector maxs
	int collisionGroup
	array<entity> ignoreEnts = []

	ignoreEnts.append( ent )

	if ( IsValid( ignoreEnt ) )
	{
		ignoreEnts.append( ignoreEnt )
	}

	if ( ent.IsTitan() )
		solidMask = TRACE_MASK_TITANSOLID
	else if ( ent.IsPlayer() )
		solidMask = TRACE_MASK_PLAYERSOLID
	else
		solidMask = TRACE_MASK_NPCSOLID

	if ( ent.IsPlayer() )
	{
		mins = ent.GetPlayerMins()
		maxs = ent.GetPlayerMaxs()
		collisionGroup = TRACE_COLLISION_GROUP_PLAYER
	}
	else
	{
		Assert( ent.IsNPC() )
		mins = ent.GetBoundingMins()
		maxs = ent.GetBoundingMaxs()
		collisionGroup = TRACE_COLLISION_GROUP_NONE
	}

	if ( buffer > 0 )
	{
		mins.x -= float( buffer )
		mins.y -= float( buffer )
		maxs.x += float( buffer )
		maxs.y += float( buffer )
	}

	// if we got into solid, teleport back to safe place
	vector currentOrigin = ent.GetOrigin()
	TraceResults result = TraceHull( currentOrigin, currentOrigin + < 0, 0, 1 >, mins, maxs, ignoreEnts, solidMask, collisionGroup )
	//PrintTable( result )
	//DrawArrow( result.endPos, Vector(0,0,0), 5, 150 )
	if ( result.startSolid )
		return true

	return result.fraction < 1.0 // TODO: Probably not needed according to Jiesang. Fix after ship.
}

bool function EntityInSpecifiedEnt( entity ent, entity specifiedEnt, int buffer = 0 )
{
	Assert( IsValid( ent ) )
	Assert( IsValid( specifiedEnt ) )

	int solidMask
	vector mins
	vector maxs
	int collisionGroup

	if ( ent.IsTitan() )
		solidMask = TRACE_MASK_TITANSOLID
	else if ( ent.IsPlayer() )
		solidMask = TRACE_MASK_PLAYERSOLID
	else
		solidMask = TRACE_MASK_NPCSOLID

	if ( ent.IsPlayer() )
	{
		mins = ent.GetPlayerMins()
		maxs = ent.GetPlayerMaxs()
		collisionGroup = TRACE_COLLISION_GROUP_PLAYER
	}
	else
	{
		Assert( ent.IsNPC() )
		mins = ent.GetBoundingMins()
		maxs = ent.GetBoundingMaxs()
		collisionGroup = TRACE_COLLISION_GROUP_NONE
	}

	if ( buffer > 0 )
	{
		mins.x -= float( buffer )
		mins.y -= float( buffer )
		maxs.x += float( buffer )
		maxs.y += float( buffer )
	}

	// if we got into solid, teleport back to safe place
	vector currentOrigin = ent.GetOrigin()
	TraceResults result = TraceHull( currentOrigin, currentOrigin + < 0, 0, 1 >, mins, maxs, null, solidMask, collisionGroup )
	//PrintTable( result )
	//DrawArrow( result.endPos, Vector(0,0,0), 5, 150 )
	if ( result.startSolid == false )
		return false

	return result.hitEnt == specifiedEnt
}

void function KillFromInfo( entity ent, var damageInfo )
{
	entity attacker = DamageInfo_GetAttacker( damageInfo )
	entity weapon = DamageInfo_GetWeapon( damageInfo )
	//float amount = DamageInfo_GetDamage( damageInfo )

	// JFS: if the player became invalid, the attacker becomes the projectile which is bad
	if ( attacker.IsProjectile() )
		attacker = svGlobal.worldspawn

	if ( !weapon )
		weapon = attacker

	table Table = GetDamageTableFromInfo( damageInfo )
	Table.forceKill <- true

	ent.TakeDamage( 9999, attacker, weapon, Table )
}


entity function GetPlayerTitanInMap( entity player )
{
	// temporarily flipped
	entity petTitan = player.GetPetTitan()
	if ( IsValid( petTitan ) && IsAlive( petTitan ) )
		return petTitan

	// first try to return the player's actual titan
	if ( player.IsTitan() )
		return player

	return null
}


entity function GetPlayerTitanFromSouls( entity player )
{
	// returns the first owned titan found
	array<entity> souls = GetTitanSoulArray()
	foreach ( soul in souls )
	{
		if ( !IsValid( soul ) )
			continue

		if ( soul.GetBossPlayer() != player )
			continue

		if ( !IsAlive( soul.GetTitan() ) )
			continue

		return soul.GetTitan()
	}

	return null
}

void function DisableTitanShield( entity titan )
{
	entity soul = titan.GetTitanSoul()

	soul.SetShieldHealth( 0 )
	soul.SetShieldHealthMax( 0 )
}

void function DisableShield( entity ent )
{
	entity soul = ent.GetTitanSoul()

	if ( soul )
	{
		DisableTitanShield( ent )
		return
	}

	ent.SetShieldHealth( 0 )
	ent.SetShieldHealthMax( 0 )
}

void function SetVelocityTowardsEntity( entity entToMove, entity targetEnt, float speed )
{
	Assert( speed > 0 )
	Assert( IsValid( entToMove ) )
	Assert( IsValid( targetEnt ) )

	vector direction = ( targetEnt.GetWorldSpaceCenter() - entToMove.GetOrigin() )
	direction = Normalize( direction ) * speed
	entToMove.SetVelocity( direction )
}

void function SetVelocityTowardsEntityTag( entity entToMove, entity targetEnt, string targetTag, float speed )
{
	Assert( speed > 0 )
	Assert( IsValid( entToMove ) )
	Assert( IsValid( targetEnt ) )

	int attachID = targetEnt.LookupAttachment( targetTag )
	vector attachOrigin = targetEnt.GetAttachmentOrigin( attachID )

	vector direction = ( attachOrigin - entToMove.GetOrigin() )
	direction = Normalize( direction ) * speed
	entToMove.SetVelocity( direction )
}

void function EntityDemigod_TryAdjustDamageInfo( entity ent, var damageInfo )
{
	float dmg = DamageInfo_GetDamage( damageInfo )
	if ( dmg <= 0 )
		return

	if ( ent.IsTitan() && !GetDoomedState( ent ) ) //Allow demigod titans to go into doomed
		return

	int bottomLimit = 5
	if ( ent.GetHealth() <= bottomLimit )
		ent.SetHealth( bottomLimit + 1 ) //Set it up so that you at least take 1 damage, for hit indicators etc to trigger

	int health = ent.GetHealth()

	if ( health - dmg <= bottomLimit )
	{
		int newdmg = health - bottomLimit
		DamageInfo_SetDamage( damageInfo, newdmg )
		//printt( "setting damage to ", newdmg )
	}
}

void function DebugDamageInfo( entity ent, var damageInfo )
{
	printt( "damage to " + ent + ": " + DamageInfo_GetDamage( damageInfo ) )
	int damageType = DamageInfo_GetCustomDamageType( damageInfo )
	printt( "explosive: " + ( damageType & DF_EXPLOSION ) )
	printt( "bullet: " + ( damageType & DF_BULLET ) )
	printt( "gib: " + ( damageType & DF_GIB ) )

	vector dampos = DamageInfo_GetDamagePosition( damageInfo )
	vector org = DamageInfo_GetInflictor( damageInfo ).GetOrigin()
	DebugDrawLine( dampos, org, 255, 0, 0, true, 10.0 )
	DebugDrawLine( org, ent.GetOrigin(), 255, 255, 0, true, 10.0 )
}

vector function RandomVecInDome( vector dir )
{
	vector angles = VectorToAngles( dir )
	vector forward = AnglesToForward( angles )
	vector right = AnglesToRight( angles )
	vector up = AnglesToUp( angles )

	float offsetRight = RandomFloatRange( -1, 1 )
	float offsetUp = RandomFloatRange( -1, 1 )
	float offsetForward = RandomFloat( 1.0 )

	vector endPos = < 0, 0, 0 >
	endPos += forward * offsetForward
	endPos += up * offsetUp
	endPos += right * offsetRight
	endPos.Norm()

	return endPos
}

float function GetAnimEventTime( asset modelname, string anim, string event )
{
	entity dummy = CreatePropDynamic( modelname )
	dummy.Hide()

	float duration 	= dummy.GetSequenceDuration( anim )
	float frac 		= dummy.GetScriptedAnimEventCycleFrac( anim, event )

	dummy.Destroy()

	//this might cause some issues in R2 - but we'll fix them as we go - it's important this doesn't silently fail
	Assert( frac > 0.0, "event: " + event + " doesn't exist in animation: " + anim )
	Assert( frac < 1.0, "event: " + event + " doesn't exist in animation: " + anim )

	return duration * frac
}

void function SetDeathFuncName( entity npc, string functionNameString )
{
	Assert( npc.kv.deathScriptFuncName == "", "deathScriptFuncName was already set" )
	npc.kv.deathScriptFuncName = functionNameString
}

void function ClearDeathFuncName( entity npc )
{
	npc.kv.deathScriptFuncName = ""
}

/*function PushSunLightAngles( x, y, z )
{
	entity clight = GetEnt( "env_cascade_light" )
	Assert( clight )

	clight.PushAngles( x, y, z )
}

function PopSunLightAngles()
{
	entity clight = GetEnt( "env_cascade_light" )
	Assert( clight )

	clight.PopAngles()
}*/

entity function GetPilotAntiPersonnelWeapon( entity player )
{
	array<entity> weaponsArray = player.GetMainWeapons()
	foreach( weapon in weaponsArray )
	{
			int weaponType = weapon.GetWeaponType()
			if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN )
				continue;

			return weapon
	}

	return null
}

entity function GetPilotSideArmWeapon( entity player )
{
	array<entity> weaponsArray = player.GetMainWeapons()
	foreach( weapon in weaponsArray )
	{
		if ( weapon.GetWeaponType() == WT_SIDEARM )
			return weapon
	}

	return null
}

entity function GetPilotAntiTitanWeapon( entity player )
{
	array<entity> weaponsArray = player.GetMainWeapons()
	foreach( weapon in weaponsArray )
	{
		if ( weapon.GetWeaponType() == WT_ANTITITAN )
			return weapon
	}

	return null
}

bool function PilotHasSniperWeapon( entity player )
{
	array<entity> weaponsArray = player.GetMainWeapons()
	foreach ( weapon in weaponsArray )
	{
		if ( IsValid( weapon ) && weapon.GetWeaponInfoFileKeyField( "is_sniper" ) == 1 )
			return true
	}

	return false
}

bool function PilotActiveWeaponIsSniper( entity player )
{
	entity weapon = player.GetActiveWeapon()

	if ( IsValid( weapon ) && weapon.GetWeaponInfoFileKeyField( "is_sniper" ) == 1 )
		return true

	return false
}

void function ScreenFadeToColor( entity player, float r, float g, float b, float a, float fadeTime = 1.7, float holdTime = 0.0 )
{
	Assert( IsValid( player ) )

	ScreenFade( player, r, g, b, a, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
}


void function ScreenFadeFromColor( entity player, float r, float g, float b, float a, float fadeTime = 2.0, float holdTime = 2.0 )
{
	Assert( IsValid( player ) )

	ScreenFade( player, r, g, b, a, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
}

void function ScreenFadeToBlack( entity player, float fadeTime, float holdTime )
{
	Assert( IsValid( player ) )

	ScreenFade( player, 0, 0, 1, 255, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
}

void function ScreenFadeFromBlack( entity player, float fadeTime = 2.0, float holdTime = 2.0 )
{
	Assert( IsValid( player ) )

	ScreenFade( player, 0, 1, 0, 255, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
}

void function ScreenFadeToBlackForever( entity player, float fadeTime = 1.7 )
{
	Assert( IsValid( player ) )

	ScreenFade( player, 0, 0, 1, 255, fadeTime, 0, FFADE_OUT | FFADE_STAYOUT )
}

/*******************************************************
/ Server Effects
/
/	CreateServerEffect_Friendly( effectName, team )
/	CreateServerEffect_Enemy( effectName, team )
/	CreateServerEffect_Owner( effectName, owner )
/	SetServerEffectControlPoint( effectEnt, controlPoint, vecValue )
/	StartServerEffect( effectEnt )
/	StartServerEffectInWorld( effectEnt, origin, angles )
/	StartServerEffectOnEntity( effectEnt, ent, tag = null )
/
*******************************************************/

// NOTE: this does not play the effect, use StartEffectOnEntity
entity function CreateServerEffect_Friendly( asset effectName, int team )
{
	entity friendlyEffect = _CreateServerEffect( effectName, 2 ) // ENTITY_VISIBLE_TO_FRIENDLY
	friendlyEffect.kv.TeamNum = team
	friendlyEffect.kv.teamnumber = team

	return friendlyEffect
}

// NOTE: this does not play the effect, use StartEffectOnEntity
entity function CreateServerEffect_Enemy( asset effectName, int team )
{
	entity enemyEffect = _CreateServerEffect( effectName, 4 ) // ENTITY_VISIBLE_TO_ENEMY
	enemyEffect.kv.TeamNum = team
	enemyEffect.kv.teamnumber = team

	return enemyEffect
}

// NOTE: this does not play the effect, use StartEffectOnEntity
entity function CreateServerEffect_Owner( asset effectName, entity owner )
{
	Assert( IsValid( owner ) )

	entity ownerEffect = _CreateServerEffect( effectName, 1 ) // ENTITY_VISIBLE_TO_OWNER
	ownerEffect.kv.TeamNum = owner.GetTeam()
	ownerEffect.SetOwner( owner )

	return ownerEffect
}

entity function _CreateServerEffect( asset effectName, int visFlags )
{
	entity serverEffect = CreateEntity( "info_particle_system" )
	serverEffect.SetOrigin( <0, 0, 0> )
	serverEffect.SetAngles( <0, 0, 0> )
	serverEffect.SetValueForEffectNameKey( effectName )
	serverEffect.kv.start_active = 1
	serverEffect.kv.VisibilityFlags = visFlags

	thread _ServerEffectCleanup( serverEffect )
	return serverEffect
}

entity function SetServerEffectControlPoint( entity effectEnt, int controlPoint, vector vecValue ) // for now, only support static
{
	entity helper = CreateEntity( "info_placement_helper" )
	helper.SetOrigin( vecValue )
	effectEnt.SetControlPointEnt( controlPoint, helper )
	effectEnt.e.fxControlPoints.append( helper )

	return helper
}

void function StartServerEffect( entity effectEnt )
{
	DispatchSpawn( effectEnt )
}

void function StartServerEffectInWorld( entity effectEnt, vector origin, vector angles )
{
	effectEnt.SetOrigin( origin )
	effectEnt.SetAngles( angles )
	DispatchSpawn( effectEnt )
}

void function StartServerEffectOnEntity( entity effectEnt, entity ent, string tag = "" )
{
	Assert( IsValid( effectEnt ) )
	Assert( IsValid( ent ) )

	if ( tag != "" )
	{
		int attachID = ent.LookupAttachment( tag )
		vector origin = ent.GetAttachmentOrigin( attachID )
		vector angles = ent.GetAttachmentAngles( attachID )

		origin = ClampToWorldspace( origin )
		effectEnt.SetOrigin( origin )
		effectEnt.SetAngles( angles )
		effectEnt.SetParent( ent, tag, true )
	}
	else
	{
		effectEnt.SetParent( ent )
	}

	DispatchSpawn( effectEnt )
}

void function _ServerEffectCleanup( entity effectEnt )
{
	effectEnt.WaitSignal( "OnDestroy" )

	foreach ( entity controlPoint in effectEnt.e.fxControlPoints )
	{
		controlPoint.Destroy()
	}
}

float function GetYaw( vector org1, vector org2 )
{
	vector vec = org2 - org1
	vector angles = VectorToAngles( vec )
	return angles.y
}

void function HideName( entity ent )
{
	ent.SetNameVisibleToFriendly( false )
	ent.SetNameVisibleToEnemy( false )
	ent.SetNameVisibleToNeutral( false )
	ent.SetNameVisibleToOwner( false )
}

void function ShowName( entity ent )
{
	ent.SetNameVisibleToFriendly( true )
	ent.SetNameVisibleToEnemy( true )
	ent.SetNameVisibleToNeutral( true )
	ent.SetNameVisibleToOwner( true )
}

void function ShowNameToAllExceptOwner( entity ent )
{
	ent.SetNameVisibleToFriendly( true )
	ent.SetNameVisibleToEnemy( true )
	ent.SetNameVisibleToNeutral( true )
	ent.SetNameVisibleToOwner( false )
}

void function EmitSoundOnEntityToTeamExceptPlayer( entity ent, string sound, int team, entity excludePlayer )
{
	array<entity> players = GetPlayerArrayOfTeam( team )

	foreach ( player in players )
	{
		if ( player == excludePlayer )
			continue

		EmitSoundOnEntityOnlyToPlayer( ent, player, sound )
	}
}

#if DEV
// DEV function to toggle player view between the skybox and the real world.
void function ToggleSkyboxView( float scale = 0.001 )
{
	entity player = GetEntByIndex( 1 )

	entity skyboxCamLevel = GetEnt( "skybox_cam_level" )

	Assert( IsValid( skyboxCamLevel ), "Could not find a sky_camera entity named \"skybox_cam_level\" in this map." )

	vector skyOrigin = skyboxCamLevel.GetOrigin()

	if ( !file.isSkyboxView )
	{
		if ( !player.IsNoclipping() )
		{
			ClientCommand( player, "noclip" )
			wait( 0.25 )
		}

		ClientCommand( player, "sv_noclipspeed 0.1" )
		file.isSkyboxView = true
		vector offset = player.GetOrigin()
		offset *= scale

		player.SetOrigin( skyOrigin + offset - < 0.0, 0.0, 60.0 - (60.0 * scale) > )
	}
	else
	{
		ClientCommand( player, "sv_noclipspeed 5" )
		file.isSkyboxView = false
		vector offset = player.GetOrigin() - skyOrigin + < 0.0, 0.0, 60.0 - (60.0 * scale) >
		offset *= 1.0 / scale

		offset = ClampToWorldspace( offset )

		player.SetOrigin( offset )
	}
}

void function DamageRange( float value, float headShotMultiplier, int playerHealth = 200 )
{
	printt( "Damage Range: ", value, headShotMultiplier )

	float bodyShot = value
	float headShot = value * headShotMultiplier

	int maxHeadshots = 0

	int simHealth = playerHealth
	while ( simHealth > 0 )
	{
		simHealth = (simHealth.tofloat() - headShot).tointeger()
		maxHeadshots++
	}

	printt( "HeadShots:    BodyShots:     Total:" )

	simHealth = playerHealth
	int numHeadshots = 0
	while ( numHeadshots < maxHeadshots )
	{
		simHealth = playerHealth
		for ( int hsIdx = 0; hsIdx < numHeadshots; hsIdx++ )
		{
			simHealth = (simHealth.tofloat() - headShot).tointeger()
		}

		int numBodyShots = 0
		while ( simHealth > 0 )
		{
			simHealth = (simHealth.tofloat() - bodyShot).tointeger()
			numBodyShots++
		}
		printt( format( "%i             %i              %i", numHeadshots, numBodyShots, numHeadshots + numBodyShots ) )
		numHeadshots++
	}

	printt( format( "%i             %i              %i", numHeadshots, 0, numHeadshots ) )
}

#endif // DEV

void function MuteAll( entity player, int fadeOutTime = 2 )
{
	//DumpStack(2)
	Assert( player.IsPlayer() )

	Assert( fadeOutTime >= 1 && fadeOutTime <= 4 , "Only have 4 kinds of fadeout to play, time must be in the range [1,4]" )

	string fadeoutSoundString

	switch( fadeOutTime )
	{
		case 1:
			fadeoutSoundString = "1_second_fadeout"
			break

		case 2:
			fadeoutSoundString = "2_second_fadeout"
			break

		case 3:
			fadeoutSoundString = "3_second_fadeout"
			break

		case 4:
			fadeoutSoundString = "4_second_fadeout"
			break

		default:
			unreachable

	}

	printt( "Apply " +  fadeoutSoundString + " to player: " + player )

	EmitSoundOnEntityOnlyToPlayer( player, player, fadeoutSoundString )
}

//Mutes all except halftime sounds and dialogue
void function MuteHalfTime( entity player )
{
	Assert( player.IsPlayer() )
	printt( "Apply HalfTime_fadeout to player: " + player )
	EmitSoundOnEntityOnlyToPlayer( player, player, "HalfTime_fadeout" )
}

void function UnMuteAll( entity player )
{
	//DumpStack(2)
	Assert( player.IsPlayer() )

	//Just stop all the possible fadeout sounds.
	printt( "Stopping all fadeout for player: " + player )
	StopSoundOnEntity( player, "1_second_fadeout" )
	StopSoundOnEntity( player, "2_second_fadeout" )
	StopSoundOnEntity( player, "3_second_fadeout" )
	StopSoundOnEntity( player, "4_second_fadeout" )
	StopSoundOnEntity( player, "HalfTime_fadeout" )
}

void function AllPlayersMuteAll( int time = MUTEALLFADEIN )
{
	array<entity> players = GetPlayerArray()

	foreach ( player in players )
		MuteAll( player, time )
}

void function AllPlayersUnMuteAll()
{
	array<entity> players = GetPlayerArray()

	foreach ( player in players )
		UnMuteAll( player )
}

void function TakeAmmoFromPlayer( entity player )
{
	array<entity> mainWeapons = player.GetMainWeapons()
	array<entity> offhandWeapons = player.GetOffhandWeapons()

	foreach ( weapon in mainWeapons )
	{
		weapon.SetWeaponPrimaryAmmoCount( 0 )

		if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
			weapon.SetWeaponPrimaryClipCount( 0 )
	}

	foreach ( weapon in offhandWeapons )
	{
		weapon.SetWeaponPrimaryAmmoCount( 0 )

		if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
			weapon.SetWeaponPrimaryClipCount( 0 )
	}
}


bool function NearFlagSpawnPoint( vector dropPoint )
{
	if ( "flagSpawnPoint" in level && IsValid( level.flagSpawnPoint ) )
	{
		vector fspOrigin = expect entity( level.flagSpawnPoint ).GetOrigin()
		if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
			return true
	}

	if ( "flagReturnPoint" in level && IsValid( level.flagReturnPoint ) )
	{
		vector fspOrigin = expect entity( level.flagReturnPoint ).GetOrigin()
		if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
			return true
	}

	if ( "flagSpawnPoints" in level )
	{
		foreach ( flagSpawnPoint in svGlobal.flagSpawnPoints )
		{
			vector fspOrigin = flagSpawnPoint.GetOrigin()
			if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
				return true
		}
	}

	return false
}

bool function HasCinematicFlag( entity player, int flag )
{
	Assert( player.IsPlayer() )
	Assert( IsValid( player ) )
	return ( player.GetCinematicEventFlags() & flag ) != 0
}

void function AddCinematicFlag( entity player, int flag )
{
	Assert( player.IsPlayer() )
	Assert( IsValid( player ) )
	player.SetCinematicEventFlags( player.GetCinematicEventFlags() | flag )
	player.Signal( "CE_FLAGS_CHANGED" )
}

void function RemoveCinematicFlag( entity player, int flag )
{
	Assert( player.IsPlayer() )
	Assert( IsValid( player ) )
	player.SetCinematicEventFlags( player.GetCinematicEventFlags() & ( ~flag ) )
	player.Signal( "CE_FLAGS_CHANGED" )
}

void function SkyScaleDefault( entity ent, float time = 1.0 )
{
	if ( IsValid( ent ) )
		ent.LerpSkyScale( SKYSCALE_DEFAULT, time )
}

void function MoveSpawn( string targetName, vector origin, vector angles )
{
	entity ent = GetEnt( targetName )
	ent.SetOrigin( origin )
	ent.SetAngles( angles )
}

/*
function CheckDailyChallengeAchievement( entity player )
{
	if ( player.GetPersistentVar( "cu8achievement.ach_allDailyChallengesForDay" ) == true )
		return

	int maxRefs = PersistenceGetArrayCount( "activeDailyChallenges" )
	int todaysDailiesComplete = 0
	int today = Daily_GetDayForCurrentTime()
	for ( int i = 0; i < maxRefs; i++ )
	{
		int day = player.GetPersistentVarAsInt( "activeDailyChallenges[" + i + "].day" )
		if ( day != today )
			continue

		local ref = player.GetPersistentVar( "activeDailyChallenges[" + i + "].ref" )
		if ( !IsChallengeComplete( ref, player ) )
			continue

		todaysDailiesComplete++
	}

	if ( todaysDailiesComplete >= 3 )
		player.SetPersistentVar( "cu8achievement.ach_allDailyChallengesForDay", true )
}
*/

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Get an array of all linked entities targeted to one after the other down the chain
array<entity> function GetEntityTargetChain_Deprecated( entity ent )
{
	array<entity> entityChain = []
	entity currentEnt = ent
	entity nextEnt

	while ( true )
	{
		nextEnt = GetEnt( currentEnt.GetTarget_Deprecated() )
		if ( IsValid( nextEnt ) )
			entityChain.append( nextEnt )
		else
			return entityChain
		currentEnt = nextEnt
	}

	unreachable
}

void function PlayImpactFXTable( vector origin, entity owner, string impactFX, int flags = 0 )
{
	Explosion(
		origin,										//center,
		owner,										//attacker,
		owner,										//inflictor,flags
		0,											//damage,
		0,											//damageHeavyArmor,
		1,											//innerRadius,
		1,											//outerRadius,
		flags,										//flags,
		origin,										//projectileLaunchOrigin,
		0,											//explosionForce,
		damageTypes.explosive,						//scriptDamageFlags,
		-1,											//scriptDamageSourceIdentifier,
		impactFX )									//impactEffectTableName
}

void function SetSignalDelayed( entity ent, string signal, float delay )
{
	thread __SetSignalDelayedThread( ent, signal, delay )
}

void function __SetSignalDelayedThread( entity ent, string signal, float delay )
{
	EndSignal( ent, signal )	// so that if we call this again with the same signal on the same ent we won't get multiple signal events.

	wait delay
	if ( IsValid( ent ) )
		Signal( ent, signal )
}

#if DEV
table function GetPlayerPos( entity player = null )
{
	if ( !player )
		player = gp()[0]

	vector org = player.GetOrigin()
	vector vec = player.GetViewVector()
	vector ang = VectorToAngles( vec )

	return { origin = org, angles = ang }
}

string function GetScriptPos( entity player = null )
{
	table playerPos = GetPlayerPos( player )
	vector origin = expect vector( playerPos.origin )
	vector angles = expect vector( playerPos.angles )

	string returnStr = CreateOriginAnglesString( origin, <0, angles.y, 0> )
	return returnStr
}

string function CreateOriginAnglesString( vector origin, vector angles )
{
	string returnStr = "< " + origin.x + ", " + origin.y + ", " + origin.z + " >, < " + angles.x + ", " + angles.y + ", " + angles.z + " >"
	return returnStr
}

void function DistCheck_SetTestPoint()
{
	svGlobal.distCheckTestPoint = expect vector( GetPlayerPos().origin )
	printt( "DistCheck test point set to:", svGlobal.distCheckTestPoint )
}

void function DistCheck()
{
	vector here = expect vector( GetPlayerPos().origin )
	float dist = Distance( here, svGlobal.distCheckTestPoint )
	printt( "Distance:", dist, "units from", svGlobal.distCheckTestPoint )
}
#endif // DEV

void function ClearChildren( entity parentEnt ) //Probably should have code give us a GetChildren() function that returns a list instead of having to iterate through NextMovePeer
{
	entity childEnt = parentEnt.FirstMoveChild()
	entity nextChildEnt

	while ( childEnt != null )
	{
		nextChildEnt = childEnt.NextMovePeer()
		childEnt.ClearParent()

		childEnt = nextChildEnt
	}
}

void function ForceTimeLimitDone()
{
	level.devForcedWin = true
	level.devForcedTimeLimit = true
	svGlobal.levelEnt.Signal( "devForcedWin" )
	ServerCommand( "mp_enabletimelimit 1" )
	ServerCommand( "mp_enablematchending 1" )
}


void function UpdateBadRepPresent()
{
	array<entity> players = GetPlayerArray()
	bool found = false
/*	always set to false for now
	foreach ( player in players )
	{
		if ( player.HasBadReputation() )
		{
			found = true
			break
		}
	}
*/
	level.nv.badRepPresent = found
	level.ui.badRepPresent = found
}

void function Dev_PrintMessage( entity player, string text, string subText = "", float duration = 7.0, string soundAlias = "" )
{
	#if DEV
	// Build the message on the client
	string sendMessage
	for ( int textType = 0 ; textType < 2 ; textType++ )
	{
		sendMessage = textType == 0 ? text : subText

		for ( int i = 0; i < sendMessage.len(); i++ )
		{
			Remote_CallFunction_NonReplay( player, "Dev_BuildClientMessage", textType, sendMessage[i] )
		}
	}
	Remote_CallFunction_NonReplay( player, "Dev_PrintClientMessage", duration )
	if ( soundAlias != "" )
		EmitSoundOnEntity( player, soundAlias )
	#endif
}

bool function IsAttackDefendBased() //If needed to, we can make this a .nv and then move this function into utility_shared
{
	return expect bool( level.attackDefendBased )
}

bool function IsRoundBasedUsingTeamScore()  //If needed to, we can make this a .nv and then move this function into utility_shared
{
	return IsRoundBased() && expect bool( level.roundBasedUsingTeamScore )
}

bool function ShouldResetRoundBasedTeamScore()
{
	return IsRoundBased() && svGlobal.roundBasedTeamScore_RoundReset
}

void function CreateZipline( vector startPos, vector endPos )
{
	string startpointName = UniqueString( "rope_startpoint" )
	string endpointName = UniqueString( "rope_endpoint" )

	entity rope_start = CreateEntity( "move_rope" )
	SetTargetName( rope_start, startpointName )
	rope_start.kv.NextKey = endpointName
	rope_start.kv.MoveSpeed = 64
	rope_start.kv.Slack = 25
	rope_start.kv.Subdiv = "2"
	rope_start.kv.Width = "2"
	rope_start.kv.Type = "0"
	rope_start.kv.TextureScale = "1"
	rope_start.kv.RopeMaterial = "cable/zipline.vmt"
	rope_start.kv.PositionInterpolator = 2
	rope_start.kv.Zipline = "1"
	rope_start.kv.ZiplineAutoDetachDistance = "150"
	rope_start.kv.ZiplineSagEnable = "0"
	rope_start.kv.ZiplineSagHeight = "50"
	rope_start.SetOrigin( startPos )

	entity rope_end = CreateEntity( "keyframe_rope" )
	SetTargetName( rope_end, endpointName )
	rope_end.kv.MoveSpeed = 64
	rope_end.kv.Slack = 25
	rope_end.kv.Subdiv = "2"
	rope_end.kv.Width = "2"
	rope_end.kv.Type = "0"
	rope_end.kv.TextureScale = "1"
	rope_end.kv.RopeMaterial = "cable/zipline.vmt"
	rope_end.kv.PositionInterpolator = 2
	rope_end.kv.Zipline = "1"
	rope_end.kv.ZiplineAutoDetachDistance = "150"
	rope_end.kv.ZiplineSagEnable = "0"
	rope_end.kv.ZiplineSagHeight = "50"
	rope_end.SetOrigin( endPos )

	DispatchSpawn( rope_start )
	DispatchSpawn( rope_end )
}

string function GetNPCTitanSettingFile( entity titan )
{
	Assert( titan.IsTitan(), titan + " is not a titan" )
	return titan.ai.titanSettings.titanSetFile
}

void function DecodeBitField( int bitField )
{
	for ( int bitIndex = 0; bitIndex < 32; bitIndex++ )
	{
		if ( bitField & (1 << bitIndex) )
		{
			printt( "Comparison: ", bitField, "& ( 1 <<", bitIndex, ") = ", bitField & (1 << bitIndex) )
			printt( "BIT SET: ", bitIndex, bitField, 1 << bitIndex )
		}
	}
}

void function DropWeapon( entity npc )
{
	entity weapon = npc.GetActiveWeapon()
	if ( !weapon )
		return

	string name = weapon.GetWeaponClassName()

	// giving the weapon you have drops a new one in its place
	npc.GiveWeapon( name )
	npc.TakeActiveWeapon()
}

void function TestGiveGunTo( int index, string weaponName )
{
	entity ent = GetEntByIndex( index )
	if ( !ent )
	{
		printt( "No entity for index:", index )
		return;
	}

	TakePrimaryWeapon( ent )
	ent.GiveWeapon( weaponName )
	ent.SetActiveWeaponByName( weaponName )
}

string function GetEditorClass( entity self )
{
	if ( self.HasKey( "editorclass" ) )
		return expect string( self.kv.editorclass )

	return ""
}

bool function TitanHasRegenningShield( entity soul )
{
	if ( !TitanShieldRegenEnabled() )
		return false

	if ( !TitanShieldDecayEnabled() )
		return true

	if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
		return true

	return false
}

void function DelayShieldDecayTime( entity soul, float delay )
{
	soul.e.nextShieldDecayTime = Time() + delay
}

void function HighlightWeapon( entity weapon )
{
#if HAS_WEAPON_PICKUP_HIGHLIGHT
	if ( weapon.IsLoadoutPickup() )
	{
		Highlight_SetOwnedHighlight( weapon, "sp_loadout_pickup" )
		Highlight_SetNeutralHighlight( weapon, "sp_loadout_pickup" )
	}
	else
	{
		Highlight_SetOwnedHighlight( weapon, "weapon_drop_active" )
		Highlight_SetNeutralHighlight( weapon, "weapon_drop_normal" )
	}
#endif // #if HAS_WEAPON_PICKUP_HIGHLIGHT
}

void function WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null, string failsafeFlag = "" )
{
	EndSignal( ent, "OnDestroy" )
	EndSignal( player, "OnDeath" )

	//trigger = the trigger ther player must be touching while doing the check
	//failsafeFlag = bypass everything if this flag gets set

	if ( failsafeFlag != "" )
		EndSignal( level, failsafeFlag )

	float minDistSqr = minDist * minDist
	Assert( minDistSqr >= 0 )
	float timeoutTime = Time() + timeOut

	while( true )
	{

		if ( timeOut > 0 && Time() > timeoutTime )
			break

		if ( failsafeFlag != "" && Flag( failsafeFlag ) )
			break

		// Within range?
		if ( minDistSqr > 0 && DistanceSqr( player.GetOrigin(), ent.GetOrigin() ) > minDistSqr )
		{
			WaitFrame()
			continue
		}

		// Touching trigger?
		if ( ( trigger != null ) && ( !trigger.IsTouching( player ) ) )
		{
			WaitFrame()
			continue
		}

		if ( PlayerCanSee( player, ent, doTrace, degrees ) )
			break

		WaitFrame()
	}
}

void function SetTargetName( entity ent, string name )
{
	ent.SetValueForKey( "targetname", name )
}

ZipLine function CreateZipLine( vector start, vector end, int autoDetachDistance = 150, float ziplineMoveSpeedScale = 1.0 )
{
	string midpointName = UniqueString( "rope_midpoint" )
	string endpointName = UniqueString( "rope_endpoint" )

	entity rope_start = CreateEntity( "move_rope" )
	rope_start.kv.NextKey = midpointName
	rope_start.kv.MoveSpeed = 0
	rope_start.kv.ZiplineMoveSpeedScale = ziplineMoveSpeedScale
	rope_start.kv.Slack = 0
	rope_start.kv.Subdiv = 0
	rope_start.kv.Width = "2"
	rope_start.kv.TextureScale = "1"
	rope_start.kv.RopeMaterial = "cable/zipline.vmt"
	rope_start.kv.PositionInterpolator = 2
	rope_start.kv.Zipline = "1"
	rope_start.kv.ZiplineAutoDetachDistance = string( autoDetachDistance )
	rope_start.kv.ZiplineSagEnable = "0"
	rope_start.kv.ZiplineSagHeight = "0"
	rope_start.SetOrigin( start )

	entity rope_mid = CreateEntity( "keyframe_rope" )
	SetTargetName( rope_mid, midpointName )
	rope_start.kv.NextKey = endpointName
	rope_mid.SetOrigin( ( start + end ) * 0.5 )
	//rope_mid.SetOrigin( start )

	entity rope_end = CreateEntity( "keyframe_rope" )
	SetTargetName( rope_end, endpointName )
	rope_end.SetOrigin( end )

	// Dispatch spawn entities
	DispatchSpawn( rope_start )
	DispatchSpawn( rope_mid )
	DispatchSpawn( rope_end )

	ZipLine zipLine
	zipLine.start = rope_start
	zipLine.mid = rope_mid
	zipLine.end = rope_end

	return zipLine
}

entity function GetPlayerFromEntity( entity ent )
{
	entity player = null

	if ( ent.IsPlayer() )
	{
		player = ent
	}
	else if ( ent.IsNPC() )
	{
		player = ent.GetBossPlayer()
	}
	else
	{
		player = ent.GetOwner()
		if ( !player || !player.IsPlayer() )
			return null
	}

	if ( IsValid_ThisFrame( player ) )
		return player

	return null
}

void function SetHumanRagdollImpactTable( entity ent )
{
	ent.SetRagdollImpactFX( HUMAN_RAGDOLL_IMPACT_TABLE_IDX )
}

bool function ScriptManagedEntArrayContains( int handle, entity ent )
{
	array< entity > ents = GetScriptManagedEntArray( handle )
	foreach ( ent in ents )
	{
		if ( ent == ent )
			return true
	}

	return false
}

void function HideCrit( entity ent )
{
	int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" )

	if ( bodyGroupIndex == -1 )
	{
		return
	}

	ent.SetBodygroup( bodyGroupIndex, 1 )
}

void function ShowCrit( entity ent )
{
	int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" )

	if ( bodyGroupIndex == -1 )
	{
		return
	}

	ent.SetBodygroup( bodyGroupIndex, 0 )
}


#if DEV
void function TeleportEnemyBotToView()
{
	entity player = gp()[0]

	TraceResults traceResults = PlayerViewTrace( player )

	if ( traceResults.fraction >= 1.0 )
		return

	array<entity> players = GetPlayerArrayOfEnemies_Alive( player.GetTeam() )
	foreach ( enemy in players )
	{
		if ( !enemy.IsBot() )
			continue

		enemy.SetOrigin( traceResults.endPos )
		return
	}
}

void function TeleportEntityToView( entity ent )
{
	entity player = gp()[0]

	TraceResults traceResults = PlayerViewTrace( player )

	if ( traceResults.fraction >= 1.0 )
		return

	ent.SetOrigin( traceResults.endPos )
	//traceResults.surfaceNormal
}

void function TeleportFriendlyBotToView()
{
	entity player = gp()[0]

	TraceResults traceResults = PlayerViewTrace( player )

	if ( traceResults.fraction >= 1.0 )
		return

	array<entity> players = GetPlayerArrayOfTeam_Alive( player.GetTeam() )
	foreach ( enemy in players )
	{
		if ( !enemy.IsBot() )
			continue

		enemy.SetOrigin( traceResults.endPos )
		return
	}
}

void function TeleportBotToAbove()
{
	entity player = gp()[0]

	array<entity> players = GetPlayerArray_AlivePilots()
	foreach ( enemy in players )
	{
		if ( !enemy.IsBot() )
			continue

		enemy.SetOrigin( player.GetOrigin() + < 0, 0, 512 > )
		return
	}
}

TraceResults function PlayerViewTrace( entity player, float distance = 10000 )
{
	vector eyePosition = player.EyePosition()
	vector viewVector = player.GetViewVector()

	TraceResults traceResults = TraceLine( eyePosition, eyePosition + viewVector * distance, player, TRACE_MASK_SHOT_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )

	return traceResults
}
#endif

void function ClearPlayerAnimViewEntity( entity player, float time = 0.3 )
{
	entity viewEnt = player.GetFirstPersonProxy()
	viewEnt.HideFirstPersonProxy()
	viewEnt.Anim_Stop()

	player.AnimViewEntity_SetLerpOutTime( time )
	player.AnimViewEntity_Clear()
	player.p.currViewConeFunction = null
}


void function BrushMoves( entity brush )
{
	float moveTime = float( brush.kv.move_time )
	int movedir = int( brush.kv.movedirection )

	BrushMovesInDirection( brush, movedir, moveTime )
}


void function BrushMovesInDirection( entity ent, int dir, float moveTime = 0, float blendIn = 0, float blendOut = 0, float lip = 8 )
{
	entity mover = CreateOwnedScriptMover( ent )
	OnThreadEnd(
		function() : ( ent, mover )
		{
			if ( IsValid( ent ) )
				ent.ClearParent()
			if ( IsValid( mover ) )
				mover.Destroy()
		}
	)

	dir %= 360
	if ( dir > 180 )
		dir -= 360
	else if ( dir < -180 )
		dir += 360

	string moveAxis = GetMoveAxisFromDir( dir )

	ent.SetParent( mover )
	vector origin = ent.GetOrigin()
	float moveAmount
	if ( ent.HasKey( "move_amount" ) )
	{
		moveAmount = float( ent.kv.move_amount ) - lip
		switch ( moveAxis )
		{
			case "x":
			case "y":
				if ( dir < 0 )
					moveAmount *= -1
				break

			case "z":
				if ( dir == -1 )
					moveAmount *= -1
				break
		}
	}
	else
	{
		switch ( moveAxis )
		{
			case "x":
				moveAmount = GetEntWidth( ent ) - lip
				if ( dir < 0 )
					moveAmount *= -1
				break

			case "y":
				moveAmount = GetEntDepth( ent ) - lip
				if ( dir < 0 )
					moveAmount *= -1
				break

			case "z":
				moveAmount = GetEntHeight( ent ) - lip
				if ( dir == -1 )
					moveAmount *= -1
				break
		}
	}

	switch ( moveAxis )
	{
		case "x":
			origin.x += moveAmount
			break
		case "y":
			origin.y += moveAmount
			break
		case "z":
			origin.z += moveAmount
			break
	}

	if ( moveTime > 0 )
	{
		mover.NonPhysicsMoveTo( origin, moveTime, blendIn, blendOut )
		wait moveTime
	}
	else
	{
		mover.SetOrigin( origin )
	}
}

string function GetMoveAxisFromDir( int dir )
{
	if ( dir == 1 || dir == -1 )
		return "z"

	if ( dir % 180 == 0 )
		return "x"

	return "y"
}


float function GetEntHeight( entity ent )
{
	return ent.GetBoundingMaxs().z - ent.GetBoundingMins().z
}

float function GetEntWidth( entity ent )
{
	return ent.GetBoundingMaxs().x - ent.GetBoundingMins().x
}

float function GetEntDepth( entity ent )
{
	return ent.GetBoundingMaxs().y - ent.GetBoundingMins().y
}

void function PushEntWithVelocity( entity ent, vector velocity )
{
	if ( !ent.IsPlayer() && !ent.IsNPC() )
		return

	if ( !IsAlive( ent ) )
		return

	float scale = 1.0
	float pushbackScale = 1.0
	if ( ent.IsTitan() )
	{
		entity soul = ent.GetTitanSoul()
		if ( soul != null ) // defensive fix
		{
			string settings = GetSoulPlayerSettings( soul )
			var scale = Dev_GetPlayerSettingByKeyField_Global( settings, "pushbackScale" )
			if ( scale != null )
			{
				pushbackScale = expect float( scale )
			}
		}
	}

	scale = 1.0 - StatusEffect_Get( ent, eStatusEffect.pushback_dampen )
	scale = scale * pushbackScale

	velocity *= scale

	ent.SetVelocity( velocity )
}



bool function IsPlayerMalePilot( entity player )
{
	Assert( player.IsPlayer() )

	if ( !IsPilot( player ) )
		return false

	return !IsPlayerFemale( player )
}

bool function IsPlayerFemalePilot( entity player )
{
	Assert( player.IsPlayer() )

	if ( !IsPilot( player ) )
		return false

	return IsPlayerFemale( player )
}

bool function IsFacingEnemy( entity guy, entity enemy, int viewAngle = 75 )
{
	vector dir = enemy.GetOrigin() - guy.GetOrigin()
	dir = Normalize( dir )
	float dot = DotProduct( guy.GetPlayerOrNPCViewVector(), dir )
	float yaw = DotToAngle( dot )

	return ( yaw < viewAngle )
}

void function SetSquad( entity guy, string squadName )
{
	Assert( IsValid( guy ) )

	if ( guy.kv.squadname == squadName )
		return

	// we only want squads containing NPCs of the same class
	#if HAS_AI_SQUAD_LIMITS
	Assert( SquadValidForClass( squadName, guy.GetClassName() ), "Can't put AI " + guy + " in squad " + squadName + ", because it contains one or more AI with a different class." )
	Assert( SquadCanAcceptNewMembers( guy, squadName ), "Can't add AI " + guy + " to squad " + squadName + ", because that squad already has " + SQUAD_SIZE + " slots filled or reserved." )
	#endif

	guy.SetSquad( squadName )
}

void function PushPlayersApart( entity target, entity attacker, float speed )
{
	vector dif = Normalize( target.GetOrigin() - attacker.GetOrigin() )
	dif *= speed
	PushPlayerAway( target, dif )
	PushPlayerAway( attacker, -dif )
}

void function PushPlayerAway( entity target, vector velocity )
{
	#if MP
	if ( !target.IsPlayer() && !target.IsNPC() )
		return
	#endif

	vector result = velocity // + target.GetVelocity()
	result.z = max( 200, fabs( velocity.z ) )
	target.SetVelocity( result )
	//DebugDrawLine( target.GetOrigin(), target.GetOrigin() + result * 5, 255, 0, 0, true, 5.0 )
}


int function SortBySpawnTime( entity ent1, entity ent2 )
{
	if ( ent1.e.spawnTime > ent2.e.spawnTime )
		return 1

	if ( ent2.e.spawnTime > ent1.e.spawnTime )
		return -1

	return 0
}

void function HolsterAndDisableWeapons( entity player )
{
	player.HolsterWeapon()
	DisableOffhandWeapons( player )
}

void function HolsterViewModelAndDisableWeapons( entity player ) //Note that this skips the first person holster animation, and it appears to 3p observers you still have a gun out
{
	player.DisableWeaponViewModel()
	DisableOffhandWeapons( player )
}


void function DeployAndEnableWeapons( entity player )
{
	player.DeployWeapon()
	EnableOffhandWeapons( player )
}

void function DeployViewModelAndEnableWeapons( entity player )
{
	if ( IsAlive( player ) )
		player.EnableWeaponViewModel()
	EnableOffhandWeapons( player )
}

//Investigate: This might be getting called without enableoffhandweapons being called. If so,  Server_TurnOffhandWeaponsDisabledOn() should be used instead of this stack system.
void function DisableOffhandWeapons( entity player )
{
	player.Server_TurnOffhandWeaponsDisabledOn()
	player.p.disableOffhandWeaponsStackCount++
}

void function EnableOffhandWeapons( entity player )
{
	player.p.disableOffhandWeaponsStackCount--
	if ( player.p.disableOffhandWeaponsStackCount <= 0 )
		player.Server_TurnOffhandWeaponsDisabledOff()

	Assert( player.p.disableOffhandWeaponsStackCount >= 0, "Warning! Called EnableOffhandWeapons() but the weapons aren't disabled!" )
}

void function PushEntWithDamageInfoAndDistanceScale( entity ent, var damageInfo, float nearRange, float farRange, float nearScale, float farScale, float forceMultiplier_dotBase = 0.5 )
{
	float scale = GraphCapped( DamageInfo_GetDistFromAttackOrigin( damageInfo ), nearRange, farRange, nearScale, farScale )

	if ( scale > 0 )
		PushEntWithDamageInfo( ent, damageInfo, forceMultiplier_dotBase, scale )
}

void function PushEntWithDamageInfo( entity ent, var damageInfo, float forceMultiplier_dotBase = 0.5, float forceMultiplier_dotScale = 0.5 )
{
	int source = DamageInfo_GetDamageSourceIdentifier( damageInfo )
	switch ( source )
	{
 		case eDamageSourceId.mp_titanweapon_vortex_shield:
  		case eDamageSourceId.mp_titanweapon_vortex_shield_ion:
			return
	}

	entity projectile = DamageInfo_GetInflictor( damageInfo )
	if ( !IsValid( projectile ) )
		return

	vector attackDirection = Normalize( projectile.GetVelocity() )
	float damage = DamageInfo_GetDamage( damageInfo )

	PushEntWithDamageFromDirection( ent, damage, attackDirection, forceMultiplier_dotBase, forceMultiplier_dotScale )
}

void function PushEntWithDamageFromDirection( entity ent, float damage, vector attackDirection, float forceMultiplier_dotBase = 0.5, float forceMultiplier_dotScale = 0.5 )
{

	float speed
	if ( damage < 900 )
		speed = GraphCapped( damage, 0, 900, 0, 650 )
	else
		speed = GraphCapped( damage, 900, 1400, 650, 1400 )

	vector direction = attackDirection + <0,0,0>
	direction.z *= 0.25
	vector force = direction * speed

	force += < 0, 0, fabs( direction.z ) * 0.25 >

	vector velocity = ent.GetVelocity()
	vector baseVel = Normalize( velocity + <0,0,0> )

	float dot = DotProduct( baseVel, attackDirection ) * -1
	float dotMultiplier
	if ( dot > 0 )
	{
		dot *= forceMultiplier_dotScale
	}
	else
	{
		dot = 0
	}

	force *= ( forceMultiplier_dotBase + dot )
	//printt( "force " + Length( force ) )
	velocity += force
	PushEntWithVelocity( ent, velocity )
}


void function SetPlayerAnimViewEntity( entity player, entity model )
{
	// clear any attempts to hide the view anim entity
	player.Signal( "NewViewAnimEntity" )
	player.AnimViewEntity_SetEntity( model )
}


void function RandomizeHead( entity model ) //Randomize head across all available heads
{
	int headIndex = model.FindBodyGroup( "head" )
	if ( headIndex == -1 )
	{
		//printt( "HeadIndex == -1, returning" )
		return
	}
	int numOfHeads = model.GetBodyGroupModelCount( headIndex ) - 1 // last one is no head
	//printt( "Num of Heads: " + numOfHeads )

	if ( HasTeamSkin( model ) )
	{
		RandomizeHeadByTeam( model, headIndex, numOfHeads )
		return
	}
	else
	{
		int randomHeadIndex = RandomInt( numOfHeads )
		//printt( "Set head to: : " + randomHeadIndex )
		model.SetBodygroup( headIndex, randomHeadIndex )
	}
}

bool function HasTeamSkin( entity model )
{
	return  "teamSkin" in model.CreateTableFromModelKeyValues()
}

void function RandomizeHeadByTeam( entity model, int headIndex, int numOfHeads ) //Randomize head across heads available to a particular team. Assumes for a model all imc heads are first, then all militia heads are later.
{
	float midPoint = float( numOfHeads / 2 )

	int randomHeadIndex = 0
	if ( model.GetTeam() == TEAM_IMC )
	{
		randomHeadIndex = RandomInt( midPoint )
	}
	else if ( model.GetTeam() == TEAM_MILITIA )
	{
		randomHeadIndex = RandomIntRange( midPoint, numOfHeads )
	}
	//printt( "Model ", model.GetModelName(), " is using ", numOfHeads, " randomHeadIndex")

	//printt( "Set head to: : " + randomHeadIndex )
	model.SetBodygroup( headIndex, randomHeadIndex )
}

void function TakeWeaponsForArray( entity ent, array<entity> weapons )
{
	foreach ( weapon in weapons )
	{
		ent.TakeWeaponNow( weapon.GetWeaponClassName() )
	}
}

void function ScaleHealth( entity ent, float scale )
{
	Assert( IsAlive( ent ) )

	int maxHealth = ent.GetMaxHealth()
	float healthRatio = float( ent.GetHealth() ) / maxHealth
	maxHealth = int( maxHealth * scale )
	ent.SetHealth( maxHealth * healthRatio )
	ent.SetMaxHealth( maxHealth )
}

void function TeleportPlayerToEnt( entity player, entity org )
{
	if ( !IsValid( player ) )
		return
	Assert( player.IsPlayer() )
	player.SetOrigin( org.GetOrigin() )
	player.SetAngles( org.GetAngles() )
}

float function ShieldModifyDamage( entity ent, var damageInfo )
{
	entity victim
	if ( ent.IsTitan() )
		victim = ent.GetTitanSoul()
	else
		victim = ent

	int shieldHealth = victim.GetShieldHealth()

	float damage = DamageInfo_GetDamage( damageInfo )

	int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )
	ShieldDamageModifier damageModifier = GetShieldDamageModifier( damageInfo )
	damage *= damageModifier.damageScale

	float healthFrac = GetHealthFrac( victim )

	float permanentDamage = (damage * damageModifier.permanentDamageFrac * healthFrac)

	float shieldDamage

	if ( damageSourceIdentifier == eDamageSourceId.titanEmpField )
	{
		shieldDamage = min( 1000.0, float( shieldHealth ) )
	}
	else
	{
		if ( damageModifier.normalizeShieldDamage )
			shieldDamage = damage * 0.5
		else
			shieldDamage = damage - permanentDamage

		// if ( IsSoul( victim ) && SoulHasPassive( victim, ePassives.PAS_SHIELD_BOOST ) )
		// 	shieldDamage *= SHIELD_BOOST_DAMAGE_DAMPEN

		if ( IsSoul( victim ) && SoulHasPassive( victim, ePassives.PAS_BERSERKER ) )
			shieldDamage *= BERSERKER_INCOMING_DAMAGE_DAMPEN
	}

	float newShieldHealth = shieldHealth - shieldDamage

	victim.SetShieldHealth( max( 0, newShieldHealth ) )

	if ( shieldHealth > 0 && newShieldHealth <= 0 )
	{
		if ( ent.IsPlayer() )
		{
			EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_energyshield_down_3P" )
			EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_energyshield_down_1P" )
		}
		else if ( ent.GetScriptName() == "fw_team_tower" )
		{
			EmitSoundOnEntity( ent, "TitanWar_Harvester_ShieldDown" )

			#if FACTION_DIALOGUE_ENABLED
				PlayFactionDialogueToTeam( "fortwar_baseShieldDownFriendly", ent.GetTeam() )
				PlayFactionDialogueToTeam( "fortwar_baseShieldDownEnemy", GetOtherTeam( ent.GetTeam() ) )
			#endif
		}
		else
		{
			EmitSoundOnEntity( ent, "titan_energyshield_down_3P" )
		}
	}

	DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )

	if ( newShieldHealth < 0 )
	{
		DamageInfo_SetDamage( damageInfo, fabs( newShieldHealth ) + permanentDamage )
	}
	else
	{
		if ( permanentDamage == 0 )
		{
			entity attacker = DamageInfo_GetAttacker( damageInfo )
			vector damageOrigin = GetDamageOrigin( damageInfo, ent )
			int damageType = DamageInfo_GetCustomDamageType( damageInfo )
			int attackerEHandle = attacker.GetEncodedEHandle()
			int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )

			if ( attacker.IsPlayer() )
				attacker.NotifyDidDamage( ent, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamagePosition( damageInfo ), damageType, shieldDamage, DamageInfo_GetDamageFlags( damageInfo ), DamageInfo_GetHitGroup( damageInfo ), DamageInfo_GetWeapon( damageInfo ), DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
			if ( ent.IsPlayer() )
				Remote_CallFunction_Replay( ent, "ServerCallback_TitanTookDamage", shieldDamage, damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, null, false, 0 )
		}
		DamageInfo_SetDamage( damageInfo, permanentDamage )
	}

	float actualShieldDamage = min( shieldHealth, shieldDamage )

	if ( actualShieldDamage > 0 )
	{
		foreach ( func in ent.e.entPostShieldDamageCallbacks )
		{
			func( ent, damageInfo, actualShieldDamage )
		}
	}

	return actualShieldDamage
}

ShieldDamageModifier function GetShieldDamageModifier( var damageInfo )
{
	ShieldDamageModifier damageModifier

	// Disabling Shield Damage Modifiers and rebalancing the weapons. The below mechanics seem cool in an R1 style system though so leaving them commented out.
	// NOTE: Changing Damage Scale has a buggy interaction with permanent damage that must be fixed if we re-enable this.
	/*
	int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )

	switch ( damageSourceIdentifier )
	{
		case eDamageSourceId.mp_weapon_thermite_grenade:
			damageModifier.permanentDamageFrac = 0.9
			break
	}

	if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_ELECTRICAL )
	{
		// amped version
		if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_xo16 )
			damageModifier.damageScale *= 1.5

		// amped version
		if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_triple_threat )
			damageModifier.damageScale *= 1.5

		if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_arc_cannon )
			damageModifier.damageScale *= 1.5
	}
	*/

	if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_ELECTRICAL )
	{
		int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )
		// Vanguard Arc Rounds
		if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_xo16_vanguard )
			damageModifier.damageScale *= 1.5
	}


	return damageModifier
}



void function AddCallback_NPCLeeched( void functionref( entity, entity ) callbackFunc )
{
	Assert( !( svGlobal.onLeechedCustomCallbackFunc.contains( callbackFunc ) ) )
	svGlobal.onLeechedCustomCallbackFunc.append( callbackFunc )
}

void function MessageToPlayer( entity player, int eventID, entity ent = null, var eventVal = null )
{
	var eHandle = null
	if ( ent )
		eHandle = ent.GetEncodedEHandle()

	Remote_CallFunction_NonReplay( player, "ServerCallback_EventNotification", eventID, eHandle, eventVal )
	//SendHudMessage( player, message, 0.33, 0.28, 255, 255, 255, 255, 0.15, 3.0, 0.5 )
}


void function MessageToTeam( int team, int eventID, entity excludePlayer = null, entity ent = null, var eventVal = null )
{
	array<entity> players = GetPlayerArray()

	foreach ( player in players )
	{
		if ( player.GetTeam() != team )
			continue

		if ( player == excludePlayer )
			continue

		MessageToPlayer( player, eventID, ent, eventVal )
	}
}

void function MessageToAll( int eventID, entity excludePlayer = null, entity ent = null, var eventVal = null )
{
	array<entity> players = GetPlayerArray()

	foreach ( player in players )
	{
		if ( player == excludePlayer )
			continue

		MessageToPlayer( player, eventID, ent, eventVal )
	}
}



string function ReloadScriptsInternal()
{
	reloadingScripts = true
	reloadedScripts = true
	ReloadingScriptsBegin()

	if ( IsMenuLevel() )
	{
		reloadingScripts = false
		ReloadingScriptsEnd()
		return ""
	}

	TitanEmbark_Init()

	ReloadScriptCallbacks()

	reloadingScripts = false
	ReloadingScriptsEnd()

	return ( "reloaded server scripts" )
}

string function ReloadScripts()
{
	ServerCommand( "fs_report_sync_opens 0" ) // makes reload scripts slow
	delaythread ( 0 ) ReloadScriptsInternal()

	return ( "reloaded server scripts" )
}

void function SetGameEndTime(float seconds)
{
	SetServerVar( "gameEndTime", Time() + seconds )
}

void function SetRoundEndTime(float seconds)
{
	SetServerVar( "roundEndTime", Time() + seconds )
}

int function GameTime_TimeLimitSeconds()
{
	if ( IsRoundBased() )
	{
		return ( GetRoundTimeLimit_ForGameMode() * 60.0 ).tointeger()
	}
	else
	{
		if ( IsSuddenDeathGameMode() && GetGameState() == eGameState.SuddenDeath )
			return ( GetTimeLimit_ForGameMode() * 60.0 ).tointeger() + ( GetSuddenDeathTimeLimit_ForGameMode() * 60.0 ).tointeger()
		else
			return ( GetTimeLimit_ForGameMode() * 60.0 ).tointeger()
	}
	unreachable
}

int function GetSuddenDeathTimeLimit_ForGameMode()
{
	string mode = GameRules_GetGameMode()
	string playlistString = "suddendeath_timelimit"

	return GetCurrentPlaylistVarInt( playlistString, 4 )
}

int function GameTime_TimeLimitMinutes()
{
	if ( IsRoundBased() )
		return floor( GetRoundTimeLimit_ForGameMode() ).tointeger()
	else
		return floor( GetTimeLimit_ForGameMode() ).tointeger()
	unreachable
}

int function GameTime_TimeLeftMinutes()
{
	if ( GetGameState() == eGameState.WaitingForPlayers )
		return 0
	if ( GetGameState() == eGameState.Prematch )
		return int( ( expect float( GetServerVar( "gameStartTime" ) ) - Time()) / 60.0 )

	return floor( GameTime_TimeLimitMinutes() - GameTime_PlayingTime() / 60 ).tointeger()
}

int function GameTime_TimeLeftSeconds()
{
	if ( GetGameState() == eGameState.Prematch )
		return int( expect float( GetServerVar( "gameStartTime" ) ) - Time() )

	return floor( GameTime_TimeLimitSeconds() - GameTime_PlayingTime() ).tointeger()
}

int function GameTime_Seconds()
{
	return floor( Time() ).tointeger()
}

int function GameTime_Minutes()
{
	return int( floor( GameTime_Seconds() / 60 ) )
}

float function GameTime_PlayingTime()
{
	return GameTime_PlayingTimeSince( Time() )
}

float function GameTime_PlayingTimeSince( float sinceTime )
{
	int gameState = GetGameState()
	
	// temp fix because i have no fucking clue why this crashes

	if ( gameState < eGameState.Playing )
		return 0

	if ( IsRoundBased() )
	{
		if ( gameState > eGameState.SuddenDeath )
			return (expect float( GetServerVar( "roundEndTime" ) ) - expect float( GetServerVar( "roundStartTime" ) ) )
		else
			return sinceTime - expect float( GetServerVar( "roundStartTime" ) )
	
	}
	else
	{
		if ( gameState > eGameState.SuddenDeath )
			return (expect float( GetServerVar( "gameEndTime" ) ) - expect float( GetServerVar( "gameStartTime" ) ) )
		else
			return sinceTime - expect float( GetServerVar( "gameStartTime" ) )
	}
	
	unreachable
}

float function GameTime_TimeSpentInCurrentState()
{
	return Time() - expect float( GetServerVar( "gameStateChangeTime" ) )
}

int function GameScore_GetFirstToScoreLimit()
{
	return expect int( level.firstToScoreLimit )
}

bool function GameScore_AllowPointsOverLimit()
{
	return svGlobal.allowPointsOverLimit
}

int function GameScore_GetWinningTeam()
{
	if ( GameScore_GetFirstToScoreLimit() )
		return GameScore_GetFirstToScoreLimit()

	if ( IsRoundBased() )
	{
		if ( GameRules_GetTeamScore2( TEAM_IMC ) > GameRules_GetTeamScore2( TEAM_MILITIA ) )
			return TEAM_IMC
		else if ( GameRules_GetTeamScore2( TEAM_MILITIA ) > GameRules_GetTeamScore2( TEAM_IMC ) )
			return TEAM_MILITIA
	}
	else
	{
		if ( GameRules_GetTeamScore( TEAM_IMC ) > GameRules_GetTeamScore( TEAM_MILITIA ) )
			return TEAM_IMC
		else if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
			return TEAM_MILITIA
	}

	return TEAM_UNASSIGNED
}

int function GameScore_GetWinningTeam_ThisRound()
{
	if ( GameScore_GetFirstToScoreLimit() )
		return GameScore_GetFirstToScoreLimit()

	Assert ( IsRoundBased() )

	if ( GameRules_GetTeamScore( TEAM_IMC ) > GameRules_GetTeamScore( TEAM_MILITIA ) )
		return TEAM_IMC
	else if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
		return TEAM_MILITIA

	return TEAM_UNASSIGNED
}

#if DEV
void function KillIMC()
{
	array<entity> enemies = GetNPCArrayOfTeam( TEAM_IMC )
	foreach ( enemy in enemies )
	{
		enemy.Die()
	}
}

void function killtitans()
{
	printt( "Script command: Kill all titans" )
	array<entity> titans = GetTitanArray()
	foreach ( titan in titans )
		titan.Die()
}

void function killminions()
{
	printt( "Script command: Kill all minions" )
	array<entity> minions = GetAllMinions()
	foreach ( minion in minions )
	{
		minion.Die()
	}
}
#endif


array<entity> function GetTeamMinions( int team )
{
	array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
	ai.extend( GetNPCArrayByClass( "npc_spectre" ) )

	for ( int i = 0; i < ai.len(); i++ )
	{
		if ( ai[i].GetTeam() != team )
		{
			ai.remove(i)
			i--
		}
	}

	return ai
}

array<entity> function GetAllMinions()
{
	array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
	ai.extend( GetNPCArrayByClass( "npc_spectre" ) )
	ai.extend( GetNPCArrayByClass( "npc_drone" ) )

	return ai
}


bool function GameScore_IsLowScoreDifference()
{
	int winningTeam = GameScore_GetWinningTeam()

	if ( !winningTeam )
		return true

	int losingTeam = GetOtherTeam( winningTeam )

	int winningTeamScore
	int losingTeamScore

	if ( IsRoundBased() )
	{
		winningTeamScore = GameRules_GetTeamScore2( winningTeam )
		losingTeamScore =  GameRules_GetTeamScore2( losingTeam )
	}
	else
	{
		winningTeamScore =  GameRules_GetTeamScore( winningTeam )
		losingTeamScore =   GameRules_GetTeamScore( losingTeam )
	}

	return ( winningTeamScore - losingTeamScore < 2 )
}

bool function IsFastPilot( entity player )
{
	Assert( IsPilot( player ), "Pilot only check" )

	if ( player.IsWallHanging() )
		return false

	if ( player.IsWallRunning() )
		return true

	if ( !player.IsOnGround() )
		return true

	if ( LengthSqr( player.GetSmoothedVelocity() ) > 180*180 || LengthSqr( player.GetVelocity() ) > 180*180 )
		return true

	return false
}

void function KillPlayer( entity player, int damageSource )
{
	#if DEV
	printt( "Played Killed from script: " )
	DumpStack()
	#endif

	Assert( IsAlive( player ) )
	Assert( player.IsPlayer() )
	player.Die( svGlobal.worldspawn, svGlobal.worldspawn, { damageSourceId = damageSource, scriptType=DF_SKIP_DAMAGE_PROT | DF_SKIPS_DOOMED_STATE } )
}


//////////////////////////////////////////////////////////
void function TurretChangeTeam( entity turret, int team )
{
	if ( team != TEAM_UNASSIGNED )
	{
		// If a turret is on some player's team it should never be invulnerable
		MakeTurretVulnerable( turret )
	}

	SetTeam( turret, team )

	// refresh the turret client side particle effects
	UpdateTurretClientSideParticleEffects( turret )
}

void function MakeTurretInvulnerable( entity turret )
{
	Assert( IsValid( turret ) )
	turret.SetInvulnerable()
	turret.SetNoTarget(true)
	turret.SetNoTargetSmartAmmo(true)
}

void function MakeTurretVulnerable( entity turret )
{
	Assert( IsValid( turret ) )
	turret.ClearInvulnerable()
	turret.SetNoTarget(false)
	turret.SetNoTargetSmartAmmo(false)
}


void function UpdateTurretClientSideParticleEffects( entity turret )
{
	if ( !IsValid( turret ) )
		return

	int turretEHandle = turret.GetEncodedEHandle()
	array<entity> players = GetPlayerArray()
	foreach( player in players )
	{
		Remote_CallFunction_Replay( player, "ServerCallback_TurretRefresh", turretEHandle )
	}
}



bool function TakePrimaryWeapon( entity player )
{
	array<entity> weapons = player.GetMainWeapons()
	foreach ( index, weaponEnt in weapons )
	{
		int weaponType = weaponEnt.GetWeaponType()
		if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN )
			continue;

		string weapon = weaponEnt.GetWeaponClassName()
		player.TakeWeaponNow( weapon )
		return true
	}
	return false
}

bool function TakeSecondaryWeapon( entity player )
{
	array<entity> weapons = player.GetMainWeapons()
	foreach ( index, weaponEnt in weapons )
	{
		if ( weaponEnt.GetWeaponType() != WT_ANTITITAN )
			continue

		string weapon = weaponEnt.GetWeaponClassName()
		player.TakeWeaponNow( weapon )
		return true
	}
	return false
}

bool function TakeSidearmWeapon( entity player )
{
	array<entity> weapons = player.GetMainWeapons()
	foreach ( index, weaponEnt in weapons )
	{
		if ( weaponEnt.GetWeaponType() != WT_SIDEARM )
			continue

		string weapon = weaponEnt.GetWeaponClassName()
		player.TakeWeaponNow( weapon )
		return true
	}
	return false
}

void function TakeAllWeapons( entity ent )
{
	if ( ent.IsPlayer() )
	{
		ent.RemoveAllItems()
		array<entity> weapons = ent.GetMainWeapons()
		foreach ( weapon in weapons )
		{
			Assert( 0, ent + " still has weapon " + weapon.GetWeaponClassName() + " after doing takeallweapons" )
		}
	}
	else
	{
		array<entity> weapons = ent.GetMainWeapons()
		TakeWeaponsForArray( ent, weapons )

		weapons = ent.GetOffhandWeapons()
		foreach ( index, weapon in clone weapons )
		{
			ent.TakeOffhandWeapon( index )
		}
		TakeWeaponsForArray( ent, weapons )
	}
}


void function SetSpawnflags( entity ent, int spawnFlags )
{
	ent.kv.spawnflags = spawnFlags
}


void function DestroyAfterDelay( entity ent, float delay )
{
	Assert( IsNewThread(), "Must be threaded off" )

	ent.EndSignal( "OnDestroy" )

	wait( delay )

	ent.Destroy()
}

void function UnlockAchievement( entity player, int achievementID )
{
	Assert( IsValid( player ), "Can't unlock achievement on invalid player entity" )
	Assert( player.IsPlayer(), "Can't unlock achivement on non-player entity" )
	Assert( achievementID > 0 && achievementID < achievements.MAX_ACHIVEMENTS, "Tried to unlock achievement with invalid enum value" )

	Remote_CallFunction_UI( player, "ScriptCallback_UnlockAchievement", achievementID )
}

void function UpdateHeroStatsForPlayer( entity player )
{
	if ( !IsValid( player ) )
		return
	Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateHeroStats" )
}

void function TestDeathFall()
{
	entity trigger = GetEntByScriptName( "DeathFallTrigger" )
	table results = WaitSignal( trigger, "OnTrigger" )
	printt( "DEATH FALL TRIGGERED" )
	PrintTable( results )
}

bool function PlayerHasTitan( entity player )
{
	entity titan
	if ( player.IsTitan() )
		titan = player
	else
		titan = player.GetPetTitan()

	if ( IsAlive( titan ) )
		return true

	return false
}