aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/_utility.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/_utility.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_utility.gnut4394
1 files changed, 4394 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/_utility.gnut b/Northstar.CustomServers/scripts/vscripts/_utility.gnut
new file mode 100644
index 000000000..50851dae0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_utility.gnut
@@ -0,0 +1,4394 @@
+
+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" )
+}
+
+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
+}