aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/_utility_shared.nut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_utility_shared.nut4069
1 files changed, 4069 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut b/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut
new file mode 100644
index 00000000..e3cb0dbf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut
@@ -0,0 +1,4069 @@
+untyped
+
+globalize_all_functions
+
+const DEV_DRAWALLTRIGGERS = 0
+
+global const CHARGE_TOOL = "sp_weapon_arc_tool"
+
+global const TRIG_FLAG_NONE = 0
+global const TRIG_FLAG_PLAYERONLY = 0x0001
+global const TRIG_FLAG_NPCONLY = 0x0002
+global const TRIG_FLAG_NOCONTEXTBUSY = 0x0004
+global const TRIG_FLAG_ONCE = 0x0008
+global const TRIG_FLAG_EXCLUSIVE = 0x0010 // can only be triggered by entities passed in at creation
+global const TRIG_FLAG_DEVDRAW = 0x0020
+global const TRIG_FLAG_START_DISABLED = 0x0040
+global const TRIG_FLAG_NO_PHASE_SHIFT = 0x0080
+global const float MAP_EXTENTS = 128*128
+/*
+const TRIG_FLAG_ = 0x0080
+const TRIG_FLAG_ = 0x0100*/
+
+global const TRIGGER_INTERNAL_SIGNAL = "OnTrigger"
+
+global const CALCULATE_SEQUENCE_BLEND_TIME = -1.0
+
+global struct ArrayDistanceEntry
+{
+ float distanceSqr
+ entity ent
+ vector origin
+}
+
+global struct GravityLandData
+{
+ array<vector> points
+ TraceResults& traceResults
+ float elapsedTime
+}
+
+global struct FirstPersonSequenceStruct
+{
+ string firstPersonAnim = ""
+ string thirdPersonAnim = ""
+ string firstPersonAnimIdle = ""
+ string thirdPersonAnimIdle = ""
+ string relativeAnim = ""
+ string attachment = ""
+ bool teleport = false
+ bool noParent = false
+ float blendTime = CALCULATE_SEQUENCE_BLEND_TIME
+ float firstPersonBlendOutTime = -1.0
+ bool noViewLerp = false
+ bool hideProxy = false
+ void functionref( entity ) viewConeFunction = null
+ vector ornull origin = null
+ vector ornull angles = null
+ bool enablePlanting = false
+ float setInitialTime = 0.0 //set the starting point of the animation in seconds
+ bool useAnimatedRefAttachment = false //Position entity using ref every frame instead of using root motion
+ bool renderWithViewModels = false
+ bool gravity = false // force gravity command on sequence
+ bool playerPushable = false
+ array< string > thirdPersonCameraAttachments = []
+ bool thirdPersonCameraVisibilityChecks = false
+ entity thirdPersonCameraEntity = null
+ bool snapPlayerFeetToEyes = true
+}
+
+global struct FrontRightDotProductsStruct
+{
+ float forwardDot = 0.0
+ float rightDot = 0.0
+}
+
+global struct RaySphereIntersectStruct
+{
+ bool result
+ float enterFrac
+ float leaveFrac
+}
+
+void function Utility_Shared_Init()
+{
+ RegisterSignal( TRIGGER_INTERNAL_SIGNAL )
+ RegisterSignal( "devForcedWin" )
+
+ #document( "IsAlive", "Returns true if the given ent is not null, and is alive." )
+ #document( "ArrayWithin", "Remove ents from array that are out of range" )
+}
+
+#if DEV
+// short cut for the console
+// script gp()[0].Die( gp()[1] )
+array<entity> function gp()
+{
+ return GetPlayerArray()
+}
+#endif
+
+void function InitWeaponScripts()
+{
+ SmartAmmo_Init()
+
+ // WEAPON SCRIPTS
+ ArcCannon_Init()
+ Grenade_FileInit()
+ Vortex_Init()
+
+// #if SERVER
+// PrecacheProjectileEntity( "grenade_frag" )
+// PrecacheProjectileEntity( "crossbow_bolt" )
+// #endif
+
+ MpWeaponDroneBeam_Init()
+ MpWeaponDroneRocket_Init()
+ MpWeaponDronePlasma_Init()
+ MpWeaponTurretPlasma_Init()
+ MpWeaponTurretLaser_Init()
+ MpWeaponSuperSpectre_Init()
+ MpWeaponGunshipLauncher_Init()
+ MpWeaponFragDrone_Init()
+ MpAbilityShifter_Init()
+ MpTitanabilityBubbleShield_Init()
+ MpTitanabilityAmpedWall_Init()
+ MpTitanabilityFusionCore_Init()
+ MpTitanweapon40mm_Init()
+ MpTitanWeaponpredatorcannon_Init()
+ MpTitanweaponRocketeetRocketStream_Init()
+ MpTitanweaponMeteor_Init()
+ MpTitanWeapon_SniperInit()
+ MpTitanweaponVortexShield_Init()
+ MpTitanweaponXo16_Init()
+ MpWeaponDefender_Init()
+ MpWeaponDmr_Init()
+ MpWeaponProximityMine_Init()
+ MpWeaponRocketLauncher_Init()
+ MpWeaponNPCRocketLauncher_Init()
+ MpWeaponSatchel_Init()
+ MpWeaponSmartPistol_Init()
+ MpWeaponSniper_Init()
+ MpWeaponLSTAR_Init()
+ MpTitanWeaponParticleAccelerator_Init()
+ MpWeaponMegaTurret_Init()
+ MpWeaponZipline_Init()
+ SpWeaponHoldBeam_Init()
+ MpTitanweaponArcBall_Init()
+ MpWeaponDeployableCover_Init()
+ MpTitanAbilityBasicBlock_Init()
+ MpTitanAbilityLaserTrip_Init()
+ MpTitanWeaponArcWave_Init()
+ MpTitanWeaponFlameWave_Init()
+ MpWeaponAlternatorSMG_Init()
+ MpWeaponGreandeElectricSmoke_Init()
+ MpWeaponGrenadeGravity_Init()
+ MpWeaponDeployableCloakfield_Init()
+ MpWeaponTether_Init()
+ MpWeaponTripWire_Init()
+ MpTitanAbilitySmartCore_Init()
+ MpTitanAbilitySlowTrap_Init()
+ MpTitanAbilityPowerShot_Init()
+ MpTitanAbilityAmmoSwap_Init()
+ MpTitanAbilityRocketeerAmmoSwap_Init()
+ MpTitanAbilityHeatShield_Init()
+ SonarGrenade_Init()
+ MpTitanAbilityGunShield_Init()
+ MpTitanWeaponLaserLite_Init()
+ MpTitanWeaponSword_Init()
+ MpTitanAbilityHover_Init()
+ MpTitanWeaponTrackerRockets_Init()
+ MpTitanWeaponStunLaser_Init()
+ MpTitanWeaponShoulderRockets_Init()
+ MpTitanAbilitySmoke_Init()
+ #if MP
+ MpWeaponArcTrap_Init()
+ #endif
+
+ #if SERVER
+ BallLightning_Init()
+ #endif
+}
+
+float function GetCurrentPlaylistVarFloat( string val, float useVal )
+{
+ var result = GetCurrentPlaylistVarOrUseValue( val, useVal + "" )
+ if ( result == null || result == "" )
+ return 0.0
+
+ return float( result )
+}
+
+void function SetSkinForTeam( entity ent, int team )
+{
+ if ( team == TEAM_IMC )
+ ent.SetSkin( 0 )
+ else if ( team == TEAM_MILITIA )
+ ent.SetSkin( 1 )
+}
+
+void function TableDump( table Table, int depth = 0 )
+{
+ if ( depth > 4 )
+ return
+
+ foreach ( k, v in Table )
+ {
+ printl( "Key: " + k + " Value: " + v )
+ if ( type( v ) == "table" && depth )
+ TableDump( expect table( v ), depth + 1 )
+ }
+}
+
+/*entity function GetVortexWeapon( entity player )
+{
+ for ( int weaponIndex = 0; weaponIndex < 2; weaponIndex++ )
+ {
+ entity weapon = player.GetOffhandWeapon( weaponIndex )
+ if ( !IsValid( weapon ) )
+ continue
+ if ( weapon.GetWeaponClassName() != "mp_titanweapon_vortex_shield" )
+ continue
+ return weapon
+ }
+
+ Assert( false, "Vortex weapon not found!" )
+ unreachable
+}*/
+
+entity function GetClosest( array<entity> entArray, vector origin, float maxdist = -1.0 )
+{
+ Assert( entArray.len() > 0 )
+
+ entity bestEnt = entArray[ 0 ]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = LengthSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ if ( maxdist >= 0.0 )
+ {
+ if ( bestDistSqr > maxdist * maxdist )
+ return null
+ }
+
+ return bestEnt
+}
+
+entity function GetClosest2D( array<entity> entArray, vector origin, float maxdist = -1.0 )
+{
+ Assert( entArray.len() > 0, "Empty array!" )
+
+ entity bestEnt = entArray[ 0 ]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = Length2DSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ if ( maxdist >= 0.0 )
+ {
+ if ( bestDistSqr > maxdist * maxdist )
+ return null
+ }
+
+ return bestEnt
+}
+
+bool function GameModeHasCapturePoints()
+{
+ #if CLIENT
+ return clGlobal.hardpointStringIDs.len() > 0
+ #elseif SERVER
+ return svGlobal.hardpointStringIDs.len() > 0
+ #endif
+}
+
+entity function GetFarthest( array<entity> entArray, vector origin )
+{
+ Assert( entArray.len() > 0, "Empty array!" )
+
+ entity bestEnt = entArray[0]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = DistanceSqr( newEnt.GetOrigin(), origin )
+
+ if ( newDistSqr > bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ return bestEnt
+}
+
+int function GetClosestIndex( array<entity> Array, vector origin )
+{
+ Assert( Array.len() > 0 )
+
+ int index = 0
+ float distSqr = LengthSqr( Array[ index ].GetOrigin() - origin )
+
+ entity newEnt
+ float newDistSqr
+ for ( int i = 1; i < Array.len(); i++ )
+ {
+ newEnt = Array[ i ]
+ newDistSqr = LengthSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < distSqr )
+ {
+ index = i
+ distSqr = newDistSqr
+ }
+ }
+
+ return index
+}
+
+// nothing in the game uses the format "Table.r/g/b/a"... wtf is the point of this function
+table function StringToColors( string colorString, string delimiter = " " )
+{
+ PerfStart( PerfIndexShared.StringToColors + SharedPerfIndexStart )
+ array<string> tokens = split( colorString, delimiter )
+
+ Assert( tokens.len() >= 3 )
+
+ table Table = {}
+ Table.r <- int( tokens[0] )
+ Table.g <- int( tokens[1] )
+ Table.b <- int( tokens[2] )
+
+ if ( tokens.len() == 4 )
+ Table.a <- int( tokens[3] )
+ else
+ Table.a <- 255
+
+ PerfEnd( PerfIndexShared.StringToColors + SharedPerfIndexStart )
+ return Table
+}
+
+// TODO: Set return type to array<int> when SetColor() accepts this type
+function ColorStringToArray( string colorString )
+{
+ array<string> tokens = split( colorString, " " )
+
+ Assert( tokens.len() >= 3 && tokens.len() <= 4 )
+
+ array colorArray
+ foreach ( token in tokens )
+ colorArray.append( int( token ) )
+
+ return colorArray
+}
+
+// Evaluate a generic order ( coefficientArray.len() - 1 ) polynomial
+// e.g. to evaluate (Ax + B), call EvaluatePolynomial(x, A, B)
+// Note that EvaluatePolynomial(x) returns 0 and
+// EvaluatePolynomial(x, A) returns A, which are technically correct
+// but perhaps not what you expect
+float function EvaluatePolynomial( float x, array<float> coefficientArray )
+{
+ float sum = 0.0
+
+ for ( int i = 0; i < coefficientArray.len() - 1; ++i )
+ sum += coefficientArray[ i ] * pow( x, coefficientArray.len() -1 - i )
+
+ if ( coefficientArray.len() >= 1 )
+ sum += coefficientArray[ coefficientArray.len() - 1 ]
+
+ return sum
+}
+
+void function WaitForever()
+{
+ #if SERVER
+ svGlobal.levelEnt.WaitSignal( "forever" )
+ #elseif CLIENT
+ clGlobal.levelEnt.WaitSignal( "forever" )
+ #endif
+}
+
+#if SERVER
+
+bool function ShouldDoReplay( entity player, entity attacker, float replayTime, int methodOfDeath )
+{
+ if ( ShouldDoReplayIsForcedByCode() )
+ {
+ print( "ShouldDoReplay(): Doing a replay because code forced it." );
+ return true
+ }
+
+ if ( GetCurrentPlaylistVarInt( "replay_disabled", 0 ) == 1 )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because 'replay_disabled' is enabled in the current playlist.\n" );
+ return false
+ }
+
+ switch( methodOfDeath )
+ {
+ case eDamageSourceId.human_execution:
+ case eDamageSourceId.titan_execution:
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player died from an execution.\n" );
+ return false
+ }
+ }
+
+ if ( level.nv.replayDisabled )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because replays are disabled for the level.\n" );
+ return false
+ }
+
+ if ( Time() - player.p.connectTime <= replayTime ) //Bad things happen if we try to do a kill replay that lasts longer than the player entity existing on the server
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player is not old enough.\n" );
+ return false
+ }
+
+ if ( player == attacker )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the attacker is the player.\n" );
+ return false
+ }
+
+ if ( player.IsBot() == true )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player is a bot.\n" );
+ return false
+ }
+
+ return AttackerShouldTriggerReplay( attacker )
+}
+
+// Don't let things like killbrushes show replays
+bool function AttackerShouldTriggerReplay( entity attacker )
+{
+ if ( !IsValid( attacker ) )
+ {
+ print( "AttackerShouldTriggerReplay(): Not doing a replay because the attacker is not valid.\n" )
+ return false
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ print( "AttackerShouldTriggerReplay(): Doing a replay because the attacker is a player.\n" )
+ return true
+ }
+
+ if ( attacker.IsNPC() )
+ {
+ print( "AttackerShouldTriggerReplay(): Doing a replay because the attacker is an NPC.\n" )
+ return true
+ }
+
+ print( "AttackerShouldTriggerReplay(): Not doing a replay by default.\n" )
+ return false
+}
+#endif // #if SERVER
+
+vector function RandomVec( float range )
+{
+ // could rewrite so it doesnt make a box of random.
+ vector vec = Vector( 0, 0, 0 )
+ vec.x = RandomFloatRange( -range, range )
+ vec.y = RandomFloatRange( -range, range )
+ vec.z = RandomFloatRange( -range, range )
+
+ return vec
+}
+
+function ArrayValuesToTableKeys( arr )
+{
+ Assert( type( arr ) == "array", "Not an array" )
+
+ local resultTable = {}
+ for ( int i = 0; i < arr.len(); ++ i)
+ {
+ resultTable[ arr[ i ] ] <- 1
+ }
+
+ return resultTable
+}
+
+function TableKeysToArray( tab )
+{
+ Assert( type( tab ) == "table", "Not a table" )
+
+ local resultArray = []
+ resultArray.resize( tab.len() )
+ int currentArrayIndex = 0
+ foreach ( key, val in tab )
+ {
+ resultArray[ currentArrayIndex ] = key
+ ++currentArrayIndex
+ }
+
+ return resultArray
+}
+
+function TableRandom( Table )
+{
+ Assert( type( Table ) == "table", "Not a table" )
+
+ local Array = []
+
+ foreach ( entry, contents in Table )
+ {
+ Array.append( contents )
+ }
+
+ return Array.getrandom()
+}
+
+int function RandomWeightedIndex( array Array )
+{
+ int count = Array.len()
+ Assert( count != 0, "Array is empty" )
+
+ int sum = int( ( count * ( count + 1 ) ) / 2.0 ) // ( n * ( n + 1 ) ) / 2
+ int randInt = RandomInt( sum )
+ for ( int i = 0 ; i < count ; i++ )
+ {
+ int rangeForThisIndex = count - i
+ if ( randInt < rangeForThisIndex )
+ return i
+
+ randInt -= rangeForThisIndex
+ }
+
+ Assert( 0 )
+ unreachable
+}
+
+bool function IsValid_ThisFrame( entity ent )
+{
+ if ( ent == null )
+ return false
+
+ return expect bool( ent.IsValidInternal() )
+}
+
+bool function IsAlive( entity ent )
+{
+ if ( ent == null )
+ return false
+ if ( !ent.IsValidInternal() )
+ return false
+
+ return ent.IsEntAlive()
+}
+
+#if DEV && SERVER
+void function vduon()
+{
+ PlayConversationToAll( "TitanReplacement" )
+}
+
+void function playconvtest( string conv )
+{
+ entity player = GetPlayerArray()[0]
+ array<entity> guys = GetAllSoldiers()
+ if ( !guys.len() )
+ {
+ printt( "No AI!!" )
+ return
+ }
+ entity guy = GetClosest( guys, player.GetOrigin() )
+ if ( conv in player.s.lastAIConversationTime )
+ delete player.s.lastAIConversationTime[ conv ]
+
+ printt( "Play ai conversation " + conv )
+ PlaySquadConversationToAll( conv, guy )
+}
+#endif //DEV
+
+void function FighterExplodes( entity ship )
+{
+ vector origin = ship.GetOrigin()
+ vector angles = ship.GetAngles()
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "AngelCity_Scr_RedeyeWeaponExplos" )
+ #if SERVER
+ PlayFX( FX_HORNET_DEATH, origin )
+ #else
+ int fxid = GetParticleSystemIndex( FX_HORNET_DEATH )
+ StartParticleEffectInWorld( fxid, origin, angles )
+ #endif
+}
+
+vector function PositionOffsetFromEnt( entity ent, float offsetX, float offsetY, float offsetZ )
+{
+ vector angles = ent.GetAngles()
+ vector origin = ent.GetOrigin()
+ origin += AnglesToForward( angles ) * offsetX
+ origin += AnglesToRight( angles ) * offsetY
+ origin += AnglesToUp( angles ) * offsetZ
+ return origin
+}
+
+vector function PositionOffsetFromOriginAngles( vector origin, vector angles, float offsetX, float offsetY, float offsetZ )
+{
+ origin += AnglesToForward( angles ) * offsetX
+ origin += AnglesToRight( angles ) * offsetY
+ origin += AnglesToUp( angles ) * offsetZ
+ return origin
+}
+
+
+bool function IsMenuLevel()
+{
+ return IsLobby()
+}
+
+function Dump( package, depth = 0 )
+{
+ if ( depth > 6 )
+ return
+
+ foreach ( k, v in package )
+ {
+ for ( int i = 0; i < depth; i++ )
+ print( " ")
+
+ if ( IsTable( package ) )
+ printl( "Key: " + k + " Value: " + v )
+ if ( IsArray( package ) )
+ printl( "Index: " + k + " Value: " + v )
+
+ if ( IsTable( v ) || IsArray( v ) )
+ Dump( v, depth + 1 )
+ }
+}
+
+bool function UseShortNPCTitles()
+{
+ return GetCurrentPlaylistVarInt( "npc_short_titles", 0 ) ? true : false
+}
+
+string function GetShortNPCTitle( int team )
+{
+ return GetTeamName( team )
+}
+
+bool function IsIMCOrMilitiaTeam( int team )
+{
+ return team == TEAM_MILITIA || team == TEAM_IMC
+}
+
+int function GetOtherTeam( int team )
+{
+ if ( team == TEAM_IMC )
+ return TEAM_MILITIA
+
+ if ( team == TEAM_MILITIA )
+ return TEAM_IMC
+
+ Assert( false, "Trying to GetOtherTeam() for team: " + team + " that is neither Militia nor IMC" )
+ unreachable
+}
+
+float function VectorDot_PlayerToOrigin( entity player, vector targetOrigin )
+{
+ vector playerEyePosition = player.EyePosition()
+ vector vecToEnt = ( targetOrigin - playerEyePosition )
+ vecToEnt.Norm()
+
+ // GetViewVector() only works on the player
+ float dotVal = vecToEnt.Dot( player.GetViewVector() )
+ return dotVal
+}
+
+float function VectorDot_DirectionToOrigin( entity player, vector direction, vector targetOrigin )
+{
+ vector playerEyePosition = player.EyePosition()
+ vector vecToEnt = ( targetOrigin - playerEyePosition )
+ vecToEnt.Norm()
+
+ // GetViewVector() only works on the player
+ float dotVal = DotProduct( vecToEnt, direction )
+ return dotVal
+}
+
+void function WaitUntilWithinDistance( entity player, entity titan, float dist )
+{
+ float distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ if ( DistanceSqr( player.GetOrigin(), titan.GetOrigin() ) <= distSqr )
+ return
+ }
+ wait 0.1
+ }
+}
+
+void function WaitUntilBeyondDistance( entity player, entity titan, float dist )
+{
+ float distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ if ( DistanceSqr( player.GetOrigin(), titan.GetOrigin() ) > distSqr )
+ return
+ }
+ wait 0.1
+ }
+}
+
+bool function IsModelViewer()
+{
+ return GetMapName() == "mp_model_viewer"
+}
+
+
+//----------------------------------//
+// Tweening functions //
+// Pass in a fraction 0.0 - 1.0 //
+// Get a fraction back 0.0 - 1.0 //
+//----------------------------------//
+
+// simple linear tweening - no easing, no acceleration
+float function Tween_Linear( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return frac
+}
+
+// quadratic easing out - decelerating to zero velocity
+float function Tween_QuadEaseOut( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return -1.0 * frac*(frac-2)
+}
+
+// exponential easing out - decelerating to zero velocity
+float function Tween_ExpoEaseOut( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return -pow( 2.0, -10.0 * frac ) + 1.0
+}
+
+float function Tween_ExpoEaseIn( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return pow( 2, 10 * ( frac - 1 ) );
+}
+
+bool function LegalOrigin( vector origin )
+{
+ if ( fabs( origin.x ) > MAX_WORLD_COORD )
+ return false
+
+ if ( fabs( origin.y ) > MAX_WORLD_COORD )
+ return false
+
+ if ( fabs( origin.z ) > MAX_WORLD_COORD )
+ return false
+
+ return true
+}
+
+vector function AnglesOnSurface( surfaceNormal, playerVelocity )
+{
+ playerVelocity.Norm()
+ vector right = CrossProduct( playerVelocity, surfaceNormal )
+ vector forward = CrossProduct( surfaceNormal, right )
+ vector angles = VectorToAngles( forward )
+ angles.z = atan2( right.z, surfaceNormal.z ) * RAD_TO_DEG
+
+ return angles
+}
+
+vector function ClampToWorldspace( vector origin )
+{
+ // temp solution for start positions that are outside the world bounds
+ origin.x = clamp( origin.x, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+ origin.y = clamp( origin.y, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+ origin.z = clamp( origin.z, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+
+ return origin
+}
+
+function UseReturnTrue( user, usee )
+{
+ return true
+}
+
+function ControlPanel_CanUseFunction( playerUser, controlPanel )
+{
+ expect entity( playerUser )
+ expect entity( controlPanel )
+
+ // Does a simple cone FOV check from the screen to the player's eyes
+ int maxAngleToAxisAllowedDegrees = 60
+
+ vector playerEyePos = playerUser.EyePosition()
+ int attachmentIndex = controlPanel.LookupAttachment( "PANEL_SCREEN_MIDDLE" )
+
+ Assert( attachmentIndex != 0 )
+ vector controlPanelScreenPosition = controlPanel.GetAttachmentOrigin( attachmentIndex )
+ vector controlPanelScreenAngles = controlPanel.GetAttachmentAngles( attachmentIndex )
+ vector controlPanelScreenForward = AnglesToForward( controlPanelScreenAngles )
+
+ vector screenToPlayerEyes = Normalize( playerEyePos - controlPanelScreenPosition )
+
+ return DotProduct( screenToPlayerEyes, controlPanelScreenForward ) > deg_cos( maxAngleToAxisAllowedDegrees )
+}
+
+function SentryTurret_CanUseFunction( playerUser, sentryTurret )
+{
+ expect entity( playerUser )
+ expect entity( sentryTurret )
+
+ // Does a simple cone FOV check from the screen to the player's eyes
+ int maxAngleToAxisAllowedDegrees = 90
+
+ vector playerEyePos = playerUser.EyePosition()
+ int attachmentIndex = sentryTurret.LookupAttachment( "turret_player_use" )
+
+ Assert( attachmentIndex != 0 )
+ vector sentryTurretUsePosition = sentryTurret.GetAttachmentOrigin( attachmentIndex )
+ vector sentryTurretUseAngles = sentryTurret.GetAttachmentAngles( attachmentIndex )
+ vector sentryTurretUseForward = AnglesToForward( sentryTurretUseAngles )
+
+ vector useToPlayerEyes = Normalize( playerEyePos - sentryTurretUsePosition )
+
+ return DotProduct( useToPlayerEyes, sentryTurretUseForward ) > deg_cos( maxAngleToAxisAllowedDegrees )
+}
+
+void function ArrayRemoveInvalid( array<entity> ents )
+{
+ for ( int i = ents.len() - 1; i >= 0; i-- )
+ {
+ if ( !IsValid( ents[ i ] ) )
+ ents.remove( i )
+ }
+}
+
+bool function HasDamageStates( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+ return ( "damageStateInfo" in ent.s )
+}
+
+bool function HasHitData( entity ent )
+{
+ return ( "hasHitData" in ent.s && expect bool( ent.s.hasHitData ) )
+}
+
+FrontRightDotProductsStruct function GetFrontRightDots( entity baseEnt, entity relativeEnt, string optionalTag = "" )
+{
+ if ( optionalTag != "" )
+ {
+ int attachIndex = baseEnt.LookupAttachment( optionalTag )
+ vector origin = baseEnt.GetAttachmentOrigin( attachIndex )
+ vector angles = baseEnt.GetAttachmentAngles( attachIndex )
+ angles.x = 0
+ angles.z = 0
+ vector forward = AnglesToForward( angles )
+ vector right = AnglesToRight( angles )
+
+ vector targetOrg = relativeEnt.GetOrigin()
+ vector vecToEnt = ( targetOrg - origin )
+// printt( "vecToEnt ", vecToEnt )
+ vecToEnt.z = 0
+
+ vecToEnt.Norm()
+
+
+ FrontRightDotProductsStruct result
+ result.forwardDot = DotProduct( vecToEnt, forward )
+ result.rightDot = DotProduct( vecToEnt, right )
+
+ // red: forward for incoming ent
+ //DebugDrawLine( origin, origin + vecToEnt * 150, 255, 0, 0, true, 5 )
+
+ // green: tag forward
+ //DebugDrawLine( origin, origin + forward * 150, 0, 255, 0, true, 5 )
+
+ // blue: tag right
+ //DebugDrawLine( origin, origin + right * 150, 0, 0, 255, true, 5 )
+ return result
+ }
+
+ vector targetOrg = relativeEnt.GetOrigin()
+ vector origin = baseEnt.GetOrigin()
+ vector vecToEnt = ( targetOrg - origin )
+ vecToEnt.Norm()
+
+ FrontRightDotProductsStruct result
+ result.forwardDot = vecToEnt.Dot( baseEnt.GetForwardVector() )
+ result.rightDot = vecToEnt.Dot( baseEnt.GetRightVector() )
+ return result
+}
+
+
+
+array<vector> function GetAllPointsOnBezier( array<vector> points, int numSegments, float debugDrawTime = 0.0 )
+{
+ Assert( points.len() >= 2 )
+ Assert( numSegments > 0 )
+ array<vector> curvePoints = []
+
+ // Debug draw the points used for the curve
+ if ( debugDrawTime )
+ {
+ for ( int i = 0; i < points.len() - 1; i++ )
+ DebugDrawLine( points[i], points[i + 1], 150, 150, 150, true, debugDrawTime )
+ }
+
+ for ( int i = 0; i < numSegments; i++ )
+ {
+ float t = ( i.tofloat() / ( numSegments.tofloat() - 1.0 ) ).tofloat()
+ curvePoints.append( GetSinglePointOnBezier( points, t ) )
+ }
+
+ return curvePoints
+}
+
+vector function GetSinglePointOnBezier( array<vector> points, float t )
+{
+ // evaluate a point on a bezier-curve. t goes from 0 to 1.0
+
+ array<vector> lastPoints = clone points
+ for(;;)
+ {
+ array<vector> newPoints = []
+ for ( int i = 0; i < lastPoints.len() - 1; i++ )
+ newPoints.append( lastPoints[i] + ( lastPoints[i+1] - lastPoints[i] ) * t )
+
+ if ( newPoints.len() == 1 )
+ return newPoints[0]
+
+ lastPoints = newPoints
+ }
+
+ unreachable
+}
+
+bool function GetDoomedState( entity ent )
+{
+ entity soul = ent.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return false
+
+ return soul.IsDoomed()
+}
+
+bool function TitanCoreInUse( entity player )
+{
+ Assert( player.IsTitan() )
+
+ if ( !IsAlive( player ) )
+ return false
+
+ return Time() < SoulTitanCore_GetExpireTime( player.GetTitanSoul() )
+}
+
+
+// Return float or null
+function GetTitanCoreTimeRemaining( entity player )
+{
+ if ( !player.IsTitan() )
+ return null
+
+ entity soul = player.GetTitanSoul()
+
+ if ( !soul )
+ return null
+
+ return SoulTitanCore_GetExpireTime( soul ) - Time()
+}
+
+bool function CoreAvailableDuringDoomState()
+{
+ return true
+}
+
+bool function HasAntiTitanWeapon( entity guy )
+{
+ foreach ( weapon in guy.GetMainWeapons() )
+ {
+ if ( weapon.GetWeaponType() == WT_ANTITITAN )
+ return true
+ }
+ return false
+}
+
+float function GetTitanCoreActiveTime( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( weapon ) )
+ {
+ printt( "WARNING: tried to get core active time, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( player ) )
+ return 5.0 // default
+ }
+
+ return GetTitanCoreDurationFromWeapon( weapon )
+}
+
+float function GetTitanCoreChargeTime( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( weapon ) )
+ {
+ printt( "WARNING: tried to get core charge time, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( player ) )
+ return 1.0 // default
+ }
+
+ return GetTitanCoreChargeTimeFromWeapon( weapon )
+}
+
+float function GetTitanCoreChargeTimeFromWeapon( entity weapon )
+{
+ return expect float( weapon.GetWeaponInfoFileKeyField( "chargeup_time" ) )
+}
+
+float function GetTitanCoreBuildTimeFromWeapon( entity weapon )
+{
+ return expect float( weapon.GetWeaponInfoFileKeyField( "core_build_time" ).tofloat() )
+}
+
+float function GetTitanCoreDurationFromWeapon( entity weapon )
+{
+ float coreDuration = weapon.GetCoreDuration()
+
+ entity player = weapon.GetWeaponOwner()
+ if ( IsValid( player ) && player.IsPlayer() )
+ {
+ if ( PlayerHasPassive( player, ePassives.PAS_MARATHON_CORE ) )
+ coreDuration *= TITAN_CORE_MARATHON_CORE_MULTIPLIER
+ }
+
+ return coreDuration
+}
+
+float function GetCoreBuildTime( entity titan )
+{
+ if ( titan.IsPlayer() )
+ titan = GetTitanFromPlayer( titan )
+
+ Assert( titan != null )
+
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ //printt( "WARNING: tried to set build timer, but core weapon was invalid." )
+ //printt( "titan is alive? " + IsAlive( titan ) )
+ return 200.0 // default
+ }
+
+
+ return GetTitanCoreBuildTimeFromWeapon( coreWeapon )
+}
+
+string function GetCoreShortName( entity titan )
+{
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ printt( "WARNING: tried to get core name, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( titan ) )
+ return "#HUD_READY"
+ }
+
+ string name = expect string( coreWeapon.GetWeaponInfoFileKeyField( "shortprintname" ) )
+ return name
+}
+
+string ornull function GetCoreOSConversationName( entity titan, string event )
+{
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ printt( "WARNING: tried to get core sound for " + event + ", but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( titan ) )
+ return null
+ }
+
+ var alias = coreWeapon.GetWeaponInfoFileKeyField( "dialog_" + event )
+
+ if ( alias == null )
+ return null
+
+ expect string( alias )
+
+ return alias
+}
+
+entity function GetTitanFromPlayer( entity player )
+{
+ Assert( player.IsPlayer() )
+ if ( player.IsTitan() )
+ return player
+
+ return player.GetPetTitan()
+}
+
+int function GetNuclearPayload( entity player )
+{
+ if ( !GetDoomedState( player ) )
+ return 0
+
+ int payload = 0
+ if ( PlayerHasPassive( player, ePassives.PAS_NUCLEAR_CORE ) )
+ payload += 2
+
+ if ( PlayerHasPassive( player, ePassives.PAS_BUILD_UP_NUCLEAR_CORE ) )
+ payload += 1
+
+ return payload
+}
+
+entity function GetCloak( entity ent )
+{
+ return GetOffhand( ent, "mp_ability_cloak" )
+}
+
+entity function GetOffhand( entity ent, string classname )
+{
+ entity offhand = ent.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhand ) && offhand.GetWeaponClassName() == classname )
+ return offhand
+
+ offhand = ent.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhand ) && offhand.GetWeaponClassName() == classname )
+ return offhand
+
+ return null
+}
+
+bool function IsCloaked( entity ent )
+{
+ return ent.IsCloaked( true ) //pass true to ignore flicker time -
+}
+
+float function TimeSpentInCurrentState()
+{
+ return Time() - expect float( level.nv.gameStateChangeTime )
+}
+
+float function DotToAngle( float dot )
+{
+ return acos( dot ) * RAD_TO_DEG
+}
+
+float function AngleToDot( float angle )
+{
+ return cos( angle * DEG_TO_RAD )
+}
+
+int function GetGameState()
+{
+ return expect int( GetServerVar( "gameState" ) )
+}
+
+bool function GamePlaying()
+{
+ return GetGameState() == eGameState.Playing
+}
+
+bool function GamePlayingOrSuddenDeath()
+{
+ int gameState = GetGameState()
+ return gameState == eGameState.Playing || gameState == eGameState.SuddenDeath
+}
+
+bool function IsOdd( int num )
+{
+ return ( num % 2 ) == 1
+}
+
+bool function IsEven( int num )
+{
+ return !IsOdd( num )
+}
+
+vector function VectorReflectionAcrossNormal( vector vec, vector normal )
+{
+ return ( vec - normal * ( 2 * DotProduct( vec, normal ) ) )
+}
+
+// Return an array of entities ordered from farthest to closest to the specified origin
+array<entity> function ArrayFarthest( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+
+ allResults.sort( DistanceCompareFarthest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ // the actual distances aren't returned
+ return returnEntities
+}
+
+// Return an array of vectors ordered from closest to furthest from the specified origin
+array<vector> function ArrayFarthestVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+
+ allResults.sort( DistanceCompareFarthest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin
+array<entity> function ArrayClosest( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ return returnEntities
+}
+
+// Return an array of vectors ordered from closest to furthest from the specified origin
+array<vector> function ArrayClosestVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+array<entity> function ArrayClosestWithinDistance( array<entity> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnEntities.append( result.ent )
+ }
+
+ return returnEntities
+}
+
+array<vector> function ArrayClosestVectorWithinDistance( array<vector> vecArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnVecs.append( result.origin )
+ }
+
+ return returnVecs
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<entity> function ArrayClosest2D( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResults( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ return returnEntities
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<vector> function ArrayClosest2DVector( array<vector> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResultsVector( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+array<entity> function ArrayClosest2DWithinDistance( array<entity> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResults( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnEntities.append( result.ent )
+ }
+
+ return returnEntities
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<vector> function ArrayClosest2DVectorWithinDistance( array<vector> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResultsVector( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnVecs.append( result.origin )
+ }
+
+ return returnVecs
+}
+
+bool function ArrayEntityWithinDistance( array<entity> entArray, vector origin, float distance )
+{
+ float distSq = distance * distance
+ foreach( entity ent in entArray )
+ {
+ if ( DistanceSqr( ent.GetOrigin(), origin ) <= distSq )
+ return true
+ }
+ return false
+}
+
+function TableRemove( Table, entry )
+{
+ Assert( typeof Table == "table" )
+
+ foreach ( index, tableEntry in Table )
+ {
+ if ( tableEntry == entry )
+ {
+ Table[ index ] = null
+ }
+ }
+}
+
+function TableInvert( Table )
+{
+ table invertedTable = {}
+ foreach ( key, value in Table )
+ invertedTable[ value ] <- key
+
+ return invertedTable
+}
+
+int function DistanceCompareClosest( ArrayDistanceEntry a, ArrayDistanceEntry b )
+{
+ if ( a.distanceSqr > b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr < b.distanceSqr )
+ return -1
+
+ return 0;
+}
+
+int function DistanceCompareFarthest( ArrayDistanceEntry a, ArrayDistanceEntry b )
+{
+ if ( a.distanceSqr < b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr > b.distanceSqr )
+ return -1
+
+ return 0;
+}
+
+array<ArrayDistanceEntry> function ArrayDistanceResults( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( ent in entArray )
+ {
+ ArrayDistanceEntry entry
+
+ vector entOrigin = ent.GetOrigin()
+ if ( IsSpawner( ent ) )
+ {
+ var spawnKVs = ent.GetSpawnEntityKeyValues()
+ entOrigin = StringToVector( string( spawnKVs.origin ) )
+ }
+ entry.distanceSqr = DistanceSqr( entOrigin, origin )
+ entry.ent = ent
+ entry.origin = entOrigin
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistanceResultsVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( vec in vecArray )
+ {
+ ArrayDistanceEntry entry
+
+ entry.distanceSqr = DistanceSqr( vec, origin )
+ entry.ent = null
+ entry.origin = vec
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistance2DResults( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( ent in entArray )
+ {
+ ArrayDistanceEntry entry
+
+ vector entOrigin = ent.GetOrigin()
+
+ entry.distanceSqr = Distance2DSqr( entOrigin, origin )
+ entry.ent = ent
+ entry.origin = entOrigin
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistance2DResultsVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( vec in vecArray )
+ {
+ ArrayDistanceEntry entry
+
+ entry.distanceSqr = Distance2DSqr( vec, origin )
+ entry.ent = null
+ entry.origin = vec
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+GravityLandData function GetGravityLandData( vector startPos, vector parentVelocity, vector objectVelocity, float timeLimit, bool bDrawPath = false, float bDrawPathDuration = 0.0, array pathColor = [ 255, 255, 0 ] )
+{
+ GravityLandData returnData
+
+ Assert( timeLimit > 0 )
+
+ float MAX_TIME_ELAPSE = 6.0
+ float timeElapsePerTrace = 0.1
+
+ float sv_gravity = 750.0
+ float ent_gravity = 1.0
+ float gravityScale = 1.0
+
+ vector traceStart = startPos
+ vector traceEnd = traceStart
+ float traceFrac
+ int traceCount = 0
+
+ objectVelocity += parentVelocity
+
+ while( returnData.elapsedTime <= timeLimit )
+ {
+ objectVelocity.z -= ( ent_gravity * sv_gravity * timeElapsePerTrace * gravityScale )
+
+ traceEnd += objectVelocity * timeElapsePerTrace
+ returnData.points.append( traceEnd )
+ if ( bDrawPath )
+ DebugDrawLine( traceStart, traceEnd, pathColor[0], pathColor[1], pathColor[2], false, bDrawPathDuration )
+
+ traceFrac = TraceLineSimple( traceStart, traceEnd, null )
+ traceCount++
+ if ( traceFrac < 1.0 )
+ {
+ returnData.traceResults = TraceLine( traceStart, traceEnd, null, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+ return returnData
+ }
+ traceStart = traceEnd
+ returnData.elapsedTime += timeElapsePerTrace
+ }
+
+ return returnData
+}
+
+float function GetPulseFrac( rate = 1, startTime = 0 )
+{
+ return (1 - cos( ( Time() - startTime ) * (rate * (2*PI)) )) / 2
+}
+
+bool function IsPetTitan( titan )
+{
+ Assert( titan.IsTitan() )
+
+ return titan.GetTitanSoul().GetBossPlayer() != null
+}
+
+vector function StringToVector( string vecString, string delimiter = " " )
+{
+ array<string> tokens = split( vecString, delimiter )
+
+ Assert( tokens.len() >= 3 )
+
+ return Vector( float( tokens[0] ), float( tokens[1] ), float( tokens[2] ) )
+}
+
+float function GetShieldHealthFrac( entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return 0.0
+
+ if ( HasSoul( ent ) )
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( IsValid( soul ) )
+ ent = soul
+ }
+
+ int shieldHealth = ent.GetShieldHealth()
+ int shieldMaxHealth = ent.GetShieldHealthMax()
+
+ if ( shieldMaxHealth == 0 )
+ return 0.0
+
+ return float( shieldHealth ) / float( shieldMaxHealth )
+}
+
+vector function HackGetDeltaToRef( vector origin, vector angles, entity ent, string anim )
+{
+ AnimRefPoint animStartPos = ent.Anim_GetStartForRefPoint( anim, origin, angles )
+
+ vector delta = origin - animStartPos.origin
+ return origin + delta
+}
+
+vector function HackGetDeltaToRefOnPlane( vector origin, vector angles, entity ent, string anim, vector up )
+{
+ AnimRefPoint animStartPos = ent.Anim_GetStartForRefPoint( anim, origin, angles )
+
+ vector delta = origin - animStartPos.origin
+ vector nDelta = Normalize( delta )
+ vector xProd = CrossProduct( nDelta, up )
+ vector G = CrossProduct( up, xProd )
+ vector planarDelta = G * DotProduct( delta, G )
+ vector P = origin + planarDelta
+
+// DebugDrawLine( origin + delta, origin, 255, 0, 0, true, 1.0 )
+// DebugDrawLine( P, origin, 0,255, 100, true, 1.0 )
+
+ return P
+}
+
+TraceResults function GetViewTrace( entity ent )
+{
+ vector traceStart = ent.EyePosition()
+ vector traceEnd = traceStart + (ent.GetPlayerOrNPCViewVector() * 56756) // longest possible trace given our map size limits
+ array<entity> ignoreEnts = [ ent ]
+
+ return TraceLine( traceStart, traceEnd, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+}
+
+function GetModSourceID( modString )
+{
+ foreach ( name, id in getconsttable().eModSourceId )
+ {
+ if ( string( name ) == modString )
+ return id
+ }
+
+ return null
+}
+
+void function ArrayRemoveDead( array<entity> entArray )
+{
+ for ( int i = entArray.len() - 1; i >= 0; i-- )
+ {
+ if ( !IsAlive( entArray[ i ] ) )
+ entArray.remove( i )
+ }
+}
+
+array<entity> function GetSortedPlayers( IntFromEntityCompare compareFunc, int team )
+{
+ array<entity> players
+
+ if ( team )
+ players = GetPlayerArrayOfTeam( team )
+ else
+ players = GetPlayerArray()
+
+ players.sort( compareFunc )
+
+ return players
+}
+
+
+// Sorts by kills and resolves ties in this order: fewest deaths, most titan kills, most assists
+int function CompareKills( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal > bVal )
+ return 1
+ else if ( aVal < bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_ASSISTS )
+ bVal = b.GetPlayerGameStat( PGS_ASSISTS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+// Sorts by kills and resolves ties in this order: fewest deaths, most titan kills, most assists
+int function CompareAssaultScore( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareScore( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareAssault( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareDefense( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareLTS( entity a, entity b )
+{
+ int result = CompareTitanKills( a, b )
+ if ( result != 0 )
+ return result
+
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareCP( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+
+int function CompareCTF( entity a, entity b )
+{
+ // Capture the flag sorting. Sort priority = flag captures > flag returns > pilot kills > titan kills > death
+
+ // 1) Flag Captures
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Flag Returns
+ result = CompareDefense( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 3) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareSpeedball( entity a, entity b )
+{
+ // Capture the flag sorting. Sort priority = pilot kills > flag captures > death
+
+ // 1) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 2) Flag Captures
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareMFD( entity a, entity b )
+{
+ // 1) Marks Killed
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Marks Outlasted
+ result = CompareDefense( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 5) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareScavenger( entity a, entity b )
+{
+ // 1) Ore Captured
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 3) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareFW( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+int function CompareHunter( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+// Sorts by kills, deaths and then cash
+int function CompareATCOOP( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal > bVal )
+ return 1
+ else if ( aVal < bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_SCORE )
+ bVal = b.GetPlayerGameStat( PGS_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareFD( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_DETONATION_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_DETONATION_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareTitanKills( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+bool function TitanEjectIsDisabled()
+{
+ return GetGlobalNetBool( "titanEjectEnabled" ) == false
+}
+
+bool function IsHitEffectiveVsTitan( entity victim, int damageType )
+{
+ Assert( victim.IsTitan() )
+
+ if ( victim.IsPlayer() )
+ {
+ if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) )
+ return false
+ }
+
+ if ( !( damageType & DF_CRITICAL ) && ( damageType & DF_BULLET || damageType & DF_MAX_RANGE ) )
+ return false
+
+ return true
+}
+
+bool function IsHitEffectiveVsNonTitan( entity victim, int damageType )
+{
+ if ( damageType & DF_BULLET || damageType & DF_MAX_RANGE )
+ return false;
+
+ return true
+}
+
+bool function IsPilot( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( !ent.IsPlayer() )
+ return false
+
+ if ( ent.IsTitan() )
+ return false
+
+ return true
+}
+
+bool function IsPilotDecoy( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( ent.GetClassName() != "player_decoy" )
+ return false
+
+ return true
+}
+
+string function HardpointIDToString( int id )
+{
+ array<string> hardpointIDString = [ "a", "b", "c" ]
+
+ Assert( id >= 0 && id < hardpointIDString.len() )
+
+ return hardpointIDString[ id ]
+}
+
+string function Dev_TeamIDToString( id )
+{
+ if ( id == TEAM_IMC )
+ return "IMC"
+ if ( id == TEAM_MILITIA )
+ return "MIL"
+
+ return "UNASSIGNED/UNKNOWN TEAM NAME"
+}
+
+array<entity> function ArrayWithin( array<entity> Array, vector origin, float maxDist )
+{
+ float maxDistSqr = maxDist * maxDist
+
+ array<entity> resultArray = []
+ foreach ( ent in Array )
+ {
+ float distSqr = DistanceSqr( origin, ent.GetOrigin() )
+ if ( distSqr <= maxDistSqr )
+ resultArray.append( ent )
+ }
+ return resultArray
+}
+
+function GetTitanChassis( entity titan )
+{
+ if ( !("titanChassis" in titan.s ) )
+ {
+ if ( HasSoul( titan ) )
+ {
+ entity soul = titan.GetTitanSoul()
+ titan.s.titanChassis <- GetSoulTitanSubClass( soul )
+ }
+ else
+ {
+ return "Invalid Chassis"
+ }
+ }
+
+ return titan.s.titanChassis
+}
+
+vector function ClampVectorToCube( vector vecStart, vector vec, vector cubeOrigin, float cubeSize )
+{
+ float halfCubeSize = cubeSize * 0.5
+ vector cubeMins = < -halfCubeSize, -halfCubeSize, -halfCubeSize >
+ vector cubeMaxs = < halfCubeSize, halfCubeSize, halfCubeSize >
+
+ return ClampVectorToBox( vecStart, vec, cubeOrigin, cubeMins, cubeMaxs )
+}
+
+vector function ClampVectorToBox( vector vecStart, vector vec, vector cubeOrigin, vector cubeMins, vector cubeMaxs )
+{
+ float smallestClampScale = 1.0
+ vector vecEnd = vecStart + vec
+
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.x, cubeMaxs.x, vecStart.x, vecEnd.x, vec.x, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.y, cubeMaxs.y, vecStart.y, vecEnd.y, vec.y, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.z, cubeMaxs.z, vecStart.z, vecEnd.z, vec.z, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.x, cubeMins.x, vecStart.x, vecEnd.x, vec.x, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.y, cubeMins.y, vecStart.y, vecEnd.y, vec.y, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.z, cubeMins.z, vecStart.z, vecEnd.z, vec.z, smallestClampScale )
+
+ return vec * smallestClampScale
+}
+
+float function ClampVectorComponentToCubeMax( float cubeOrigin, float cubeSize, float vecStart, float vecEnd, float vec, float smallestClampScale )
+{
+ float max = cubeOrigin + cubeSize
+ float clearance = fabs( vecStart - max )
+ if ( vecEnd > max )
+ {
+ float scale = fabs( clearance / ( ( vecStart + vec ) - vecStart ) )
+ if ( scale > 0 && scale < smallestClampScale )
+ return scale
+ }
+
+ return smallestClampScale
+}
+
+float function ClampVectorComponentToCubeMin( float cubeOrigin, float cubeSize, float vecStart, float vecEnd, float vec, float smallestClampScale )
+{
+ float min = cubeOrigin - cubeSize
+ float clearance = fabs( min - vecStart )
+ if ( vecEnd < min )
+ {
+ float scale = fabs( clearance / ( ( vecStart + vec ) - vecStart ) )
+ if ( scale > 0 && scale < smallestClampScale )
+ return scale
+ }
+
+ return smallestClampScale
+}
+
+bool function PointInCapsule( vector vecBottom, vector vecTop, float radius, vector point )
+{
+ return GetDistanceFromLineSegment( vecBottom, vecTop, point ) <= radius
+}
+
+bool function PointInCylinder( vector vecBottom, vector vecTop, float radius, vector point )
+{
+ if ( GetDistanceFromLineSegment( vecBottom, vecTop, point ) > radius )
+ return false
+
+ vector bottomVec = Normalize( vecTop - vecBottom )
+ vector pointToBottom = Normalize( point - vecBottom )
+
+ vector topVec = Normalize( vecBottom - vecTop )
+ vector pointToTop = Normalize( point - vecTop )
+
+ if ( DotProduct( bottomVec, pointToBottom ) < 0 )
+ return false
+
+ if ( DotProduct( topVec, pointToTop ) < 0.0 )
+ return false
+
+ return true
+}
+
+float function AngleDiff( float ang, float targetAng )
+{
+ float delta = ( targetAng - ang ) % 360.0
+ if ( targetAng > ang )
+ {
+ if ( delta >= 180.0 )
+ delta -= 360.0;
+ }
+ else
+ {
+ if ( delta <= -180.0 )
+ delta += 360.0;
+ }
+ return delta
+}
+
+
+float function ClampAngle( float ang )
+{
+ while( ang > 360 )
+ ang -= 360
+ while( ang < 0 )
+ ang += 360
+ return ang
+}
+
+float function ClampAngle180( float ang )
+{
+ while( ang > 180 )
+ ang -= 180
+ while( ang < -180 )
+ ang += 180
+ return ang
+}
+
+vector function ShortestRotation( vector ang, vector targetAng )
+{
+ return Vector( AngleDiff( ang.x, targetAng.x ), AngleDiff( ang.y, targetAng.y ), AngleDiff( ang.z, targetAng.z ) )
+}
+
+int function GetWinningTeam()
+{
+ if ( level.nv.winningTeam != null )
+ return expect int( level.nv.winningTeam )
+
+ if ( IsFFAGame() )
+ return GetWinningTeam_FFA()
+
+ if ( IsRoundBased() )
+ {
+ if ( GameRules_GetTeamScore2( TEAM_IMC ) > GameRules_GetTeamScore2( TEAM_MILITIA ) )
+ return TEAM_IMC
+
+ 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
+
+ if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ return TEAM_MILITIA
+ }
+
+ return TEAM_UNASSIGNED
+}
+
+int function GetWinningTeam_FFA()
+{
+ if ( level.nv.winningTeam != null )
+ return expect int( level.nv.winningTeam )
+
+ int maxScore = 0
+ int playerTeam
+ int currentScore
+ int winningTeam = TEAM_UNASSIGNED
+
+ foreach( player in GetPlayerArray() )
+ {
+ playerTeam = player.GetTeam()
+ if ( IsRoundBased() )
+ currentScore = GameRules_GetTeamScore2( playerTeam )
+ else
+ currentScore = GameRules_GetTeamScore( playerTeam )
+
+ if ( currentScore == maxScore) //Treat multiple teams as having the same score as no team winning
+ winningTeam = TEAM_UNASSIGNED
+
+ if ( currentScore > maxScore )
+ {
+ maxScore = currentScore
+ winningTeam = playerTeam
+ }
+ }
+
+ return winningTeam
+
+}
+
+void function EmitSkyboxSoundAtPosition( vector positionInSkybox, string sound, float skyboxScale = 0.001, bool clamp = false )
+{
+ if ( IsServer() )
+ clamp = true // sounds cannot play outside 16k limit on server
+ vector position = SkyboxToWorldPosition( positionInSkybox, skyboxScale, clamp )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, position, sound )
+}
+
+vector function SkyboxToWorldPosition( vector positionInSkybox, float skyboxScale = 0.001, bool clamp = true )
+{
+ Assert( skyboxScale > 0 )
+ Assert( "skyboxCamOrigin" in level )
+
+ vector position = Vector( 0.0, 0.0, 0.0 )
+ vector skyOrigin = expect vector( level.skyboxCamOrigin )
+
+ #if CLIENT
+ position = ( positionInSkybox - skyOrigin ) * ( 1.0 / skyboxScale )
+
+ if ( clamp )
+ {
+ entity localViewPlayer = GetLocalViewPlayer()
+ Assert( localViewPlayer )
+ vector localViewPlayerOrg = localViewPlayer.GetOrigin()
+
+ position = localViewPlayerOrg + ClampVectorToCube( localViewPlayerOrg, position - localViewPlayerOrg, Vector( 0.0, 0.0, 0.0 ), 32000.0 )
+ }
+ #else
+ position = ( positionInSkybox - skyOrigin ) * ( 1.0 / skyboxScale )
+
+ if ( clamp )
+ position = ClampVectorToCube( Vector( 0.0, 0.0, 0.0 ), position, Vector( 0.0, 0.0, 0.0 ), 32000.0 )
+ #endif // CLIENT
+
+ return position
+}
+
+void function FadeOutSoundOnEntityAfterDelay( entity ent, string soundAlias, float delay, float fadeTime )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ ent.EndSignal( "OnDestroy" )
+ wait delay
+ FadeOutSoundOnEntity( ent, soundAlias, fadeTime )
+}
+
+function GetRandomKeyFromWeightedTable( Table )
+{
+ local weightTotal = 0.0
+ foreach ( key, value in Table )
+ {
+ weightTotal += value
+ }
+
+ local randomValue = RandomFloat( weightTotal )
+
+ foreach ( key, value in Table )
+ {
+ if ( randomValue <= weightTotal && randomValue >= weightTotal - value)
+ return key
+ weightTotal -= value
+ }
+}
+
+bool function IsMatchOver()
+{
+ if ( IsRoundBased() && level.nv.gameEndTime )
+ return true
+ else if ( !IsRoundBased() && level.nv.gameEndTime && Time() > level.nv.gameEndTime )
+ return true
+
+ return false
+}
+
+bool function IsScoringNonStandard()
+{
+ return expect bool( level.nv.nonStandardScoring )
+}
+
+bool function IsRoundBased()
+{
+ return expect bool( level.nv.roundBased )
+}
+
+int function GetRoundsPlayed()
+{
+ return expect int( level.nv.roundsPlayed )
+}
+
+bool function IsEliminationBased()
+{
+ return Riff_EliminationMode() != eEliminationMode.Default
+}
+
+bool function IsPilotEliminationBased()
+{
+ return ( Riff_EliminationMode() == eEliminationMode.Pilots || Riff_EliminationMode() == eEliminationMode.PilotsTitans )
+}
+
+bool function IsTitanEliminationBased()
+{
+ return ( Riff_EliminationMode() == eEliminationMode.Titans || Riff_EliminationMode() == eEliminationMode.PilotsTitans )
+}
+
+bool function IsSingleTeamMode()
+{
+ return ( 1 == GetCurrentPlaylistVarInt( "max_teams", 2 ) )
+}
+
+void function __WarpInEffectShared( vector origin, vector angles, string sfx, float preWaitOverride = -1.0 )
+{
+ float preWait = 2.0
+ float sfxWait = 0.1
+ float totalTime = WARPINFXTIME
+
+ if ( sfx == "" )
+ sfx = "dropship_warpin"
+
+ if ( preWaitOverride >= 0.0 )
+ wait preWaitOverride
+ else
+ wait preWait //this needs to go and the const for warpin fx time needs to change - but not this game - the intro system is too dependent on it
+
+ #if CLIENT
+ int fxIndex = GetParticleSystemIndex( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE )
+ StartParticleEffectInWorld( fxIndex, origin, angles )
+ #else
+ entity fx = PlayFX( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE, origin, angles )
+ fx.FXEnableRenderAlways()
+ fx.DisableHibernation()
+ #endif // CLIENT
+
+ wait sfxWait
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sfx )
+
+ wait totalTime - preWait - sfxWait
+}
+
+void function __WarpOutEffectShared( entity dropship )
+{
+ int attach = dropship.LookupAttachment( "origin" )
+ vector origin = dropship.GetAttachmentOrigin( attach )
+ vector angles = dropship.GetAttachmentAngles( attach )
+
+ #if CLIENT
+ int fxIndex = GetParticleSystemIndex( FX_GUNSHIP_CRASH_EXPLOSION_EXIT )
+ StartParticleEffectInWorld( fxIndex, origin, angles )
+ #else
+ entity fx = PlayFX( FX_GUNSHIP_CRASH_EXPLOSION_EXIT, origin, angles )
+ fx.FXEnableRenderAlways()
+ fx.DisableHibernation()
+ #endif // CLIENT
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "dropship_warpout" )
+}
+
+bool function IsSwitchSidesBased()
+{
+ return (level.nv.switchedSides != null)
+}
+
+int function HasSwitchedSides() //This returns an int instead of a bool! Should rewrite
+{
+ return expect int( level.nv.switchedSides )
+}
+
+bool function IsFirstRoundAfterSwitchingSides()
+{
+ if ( !IsSwitchSidesBased() )
+ return false
+
+ if ( IsRoundBased() )
+ return level.nv.switchedSides > 0 && GetRoundsPlayed() == level.nv.switchedSides
+ else
+ return level.nv.switchedSides > 0
+
+ unreachable
+}
+
+void function CamBlendFov( entity cam, float oldFov, float newFov, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ float interp = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+ cam.SetFOV( GraphCapped( interp, 0.0, 1.0, oldFov, newFov ) )
+ wait( 0.0 )
+ currentTime = Time()
+ }
+}
+
+void function CamFollowEnt( entity cam, entity ent, float duration, vector offset = <0.0, 0.0, 0.0>, string attachment = "", bool isInSkybox = false )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ vector camOrg = Vector( 0.0, 0.0, 0.0 )
+
+ vector targetPos = Vector( 0.0, 0.0, 0.0 )
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + duration
+ vector diff = Vector( 0.0, 0.0, 0.0 )
+ int attachID = ent.LookupAttachment( attachment )
+
+ while ( endTime > currentTime )
+ {
+ camOrg = cam.GetOrigin()
+
+ if ( attachID <= 0 )
+ targetPos = ent.GetOrigin()
+ else
+ targetPos = ent.GetAttachmentOrigin( attachID )
+
+ if ( isInSkybox )
+ targetPos = SkyboxToWorldPosition( targetPos )
+ diff = ( targetPos + offset ) - camOrg
+
+ cam.SetAngles( VectorToAngles( diff ) )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamFacePos( entity cam, vector pos, float duration )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + duration
+ vector diff = Vector( 0.0, 0.0, 0.0 )
+
+ while ( endTime > currentTime )
+ {
+ diff = pos - cam.GetOrigin()
+
+ cam.SetAngles( VectorToAngles( diff ) )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromFollowToAng( entity cam, entity ent, vector endAng, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ vector camOrg = cam.GetOrigin()
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ vector diff = ent.GetOrigin() - camOrg
+ vector anglesToEnt = VectorToAngles( diff )
+
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = anglesToEnt + ShortestRotation( anglesToEnt, endAng ) * frac
+
+ cam.SetAngles( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromPosToPos( entity cam, vector startPos, vector endPos, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+ vector diff = endPos - startPos
+
+ while ( endTime > currentTime )
+ {
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = startPos + diff * frac
+
+ cam.SetOrigin( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromAngToAng( entity cam, vector startAng, vector endAng, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = startAng + ShortestRotation( startAng, endAng ) * frac
+
+ cam.SetAngles( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+int function AddBitMask( int bitsExisting, int bitsToAdd )
+{
+ return bitsExisting | bitsToAdd
+}
+
+int function RemoveBitMask( int bitsExisting, int bitsToRemove )
+{
+ return bitsExisting & ( ~bitsToRemove )
+}
+
+bool function HasBitMask( int bitsExisting, int bitsToCheck )
+{
+ int bitsCommon = bitsExisting & bitsToCheck
+ return bitsCommon == bitsToCheck
+}
+
+float function GetDeathCamLength( entity player )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return DEATHCAM_TIME_SHORT
+ else
+ return DEATHCAM_TIME
+
+ unreachable
+}
+
+float function GetRespawnButtonCamTime( player )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return DEATHCAM_TIME_SHORT + RESPAWN_BUTTON_BUFFER
+ else
+ return DEATHCAM_TIME + RESPAWN_BUTTON_BUFFER
+
+ unreachable
+}
+
+float function GetKillReplayAfterTime( player )
+{
+ if ( IsSingleplayer() )
+ return 4.0
+
+ if ( !GamePlayingOrSuddenDeath() )
+ return KILL_REPLAY_AFTER_KILL_TIME_SHORT
+
+ return KILL_REPLAY_AFTER_KILL_TIME
+}
+
+function IntroPreviewOn()
+{
+ local bugnum = GetBugReproNum()
+ switch( bugnum )
+ {
+ case 1337:
+ case 13371:
+ case 13372:
+ case 13373:
+ case 1338:
+ case 13381:
+ case 13382:
+ case 13383:
+ return bugnum
+
+ default:
+ return null
+ }
+}
+
+bool function EntHasModelSet( entity ent )
+{
+ asset modelName = ent.GetModelName()
+
+ if ( modelName == $"" || modelName == $"?" )
+ return false
+
+ return true
+}
+
+string function GenerateTitanOSAlias( entity player, string aliasSuffix )
+{
+ //HACK: Temp fix for blocker bug. Fixing correctly next.
+ if ( IsSingleplayer() )
+ {
+ return "diag_gs_titanBt_" + aliasSuffix
+ }
+ else
+ {
+ entity titan
+ if ( player.IsTitan() )
+ titan = player
+ else
+ titan = player.GetPetTitan()
+
+ Assert( IsValid( titan ) )
+ string titanCharacterName = GetTitanCharacterName( titan )
+ string primeTitanString = ""
+
+ if ( IsTitanPrimeTitan( titan ) )
+ primeTitanString = "_prime"
+
+ string modifiedAlias = "diag_gs_titan" + titanCharacterName + primeTitanString + "_" + aliasSuffix
+ return modifiedAlias
+ }
+ unreachable
+}
+
+void function AddCallback_OnUseEntity( entity ent, callbackFunc )
+{
+ AssertParameters( callbackFunc, 2, "ent, player" )
+
+ if ( !( "onUseEntityCallbacks" in ent.s ) )
+ ent.s.onUseEntityCallbacks <- []
+
+ Assert( !ent.s.onUseEntityCallbacks.contains( callbackFunc ), "Already added " + FunctionToString( callbackFunc ) + " with AddCalback_OnUseEntity" )
+ ent.s.onUseEntityCallbacks.append( callbackFunc )
+}
+
+void function SetWaveSpawnType( int spawnType )
+{
+ shGlobal.waveSpawnType = spawnType
+}
+
+int function GetWaveSpawnType()
+{
+ return shGlobal.waveSpawnType
+}
+
+void function SetWaveSpawnInterval( float interval )
+{
+ shGlobal.waveSpawnInterval = interval
+}
+
+float function GetWaveSpawnInterval()
+{
+ return shGlobal.waveSpawnInterval
+}
+
+bool function IsArcTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_arc"
+}
+
+bool function IsNukeTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_nuke"
+}
+
+bool function IsMortarTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_mortar"
+}
+
+bool function IsFragDrone( entity npc )
+{
+ #if SERVER
+ return npc.GetClassName() == "npc_frag_drone"
+ #endif
+
+ #if CLIENT
+ return npc.GetSignifierName() == "npc_frag_drone"
+ #endif
+}
+
+bool function IsSniperSpectre( entity npc )
+{
+ return false
+}
+
+bool function IsVortexSphere( entity ent )
+{
+ return ( ent.GetClassName() == "vortex_sphere" )
+}
+
+bool function PointIsWithinBounds( vector point, vector mins, vector maxs )
+{
+ Assert( mins.x < maxs.x )
+ Assert( mins.y < maxs.y )
+ Assert( mins.z < maxs.z )
+
+ return ( ( point.z >= mins.z && point.z <= maxs.z ) &&
+ ( point.x >= mins.x && point.x <= maxs.x ) &&
+ ( point.y >= mins.y && point.y <= maxs.y ) )
+}
+
+int function GetSpStartIndex()
+{
+ //HACK -> this should use some other code driven thing, not GetBugReproNum
+ int index = GetBugReproNum()
+
+ if ( index < 0 )
+ return 0
+
+ return index
+}
+
+// return all living soldiers
+array<entity> function GetAllSoldiers()
+{
+ return GetNPCArrayByClass( "npc_soldier" )
+}
+
+int function GameTeams_GetNumLivingPlayers( int teamIndex = TEAM_ANY )
+{
+ int noOfLivingPlayers = 0
+
+ array<entity> players
+ if ( teamIndex == TEAM_ANY )
+ players = GetPlayerArray()
+ else
+ players = GetPlayerArrayOfTeam( teamIndex )
+
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ ++noOfLivingPlayers
+ }
+
+ return noOfLivingPlayers
+}
+
+bool function GameTeams_TeamHasDeadPlayers( int team )
+{
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( !IsAlive( teamPlayer ) )
+ return true
+ }
+ return false
+}
+
+typedef EntitiesDidLoadCallbackType void functionref()
+array<EntitiesDidLoadCallbackType> _EntitiesDidLoadTypedCallbacks
+
+void function RunCallbacks_EntitiesDidLoad()
+{
+ // reloading the level so don't do callbacks
+ if ( "forcedReloading" in level )
+ return
+
+ foreach ( callback in _EntitiesDidLoadTypedCallbacks )
+ {
+ thread callback()
+ }
+}
+
+void function AddCallback_EntitiesDidLoad( EntitiesDidLoadCallbackType callback )
+{
+ _EntitiesDidLoadTypedCallbacks.append( callback )
+}
+
+bool function IsTitanNPC( entity ent )
+{
+ return ent.IsTitan() && ent.IsNPC()
+}
+
+entity function InflictorOwner( entity inflictor )
+{
+ if ( IsValid( inflictor ) )
+ {
+ entity inflictorOwner = inflictor.GetOwner()
+ if ( IsValid( inflictorOwner ) )
+ inflictor = inflictorOwner
+ }
+
+ return inflictor
+}
+
+bool function IsPlayerControlledSpectre( entity ent )
+{
+ return ent.GetClassName() == "npc_spectre" && ent.GetBossPlayer() != null
+}
+
+bool function IsPlayerControlledTurret( entity ent )
+{
+ return IsTurret( ent ) && ent.GetBossPlayer() != null
+}
+
+bool function TitanShieldDecayEnabled()
+{
+ return ( GetCurrentPlaylistVarInt( "titan_shield_decay", 0 ) == 1 )
+}
+
+bool function TitanShieldRegenEnabled()
+{
+ return ( GetCurrentPlaylistVarInt( "titan_shield_regen", 0 ) == 1 )
+}
+
+bool function DoomStateDisabled()
+{
+ return ( GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "disabled" || GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "lastsegment" )
+}
+
+bool function NoWeaponDoomState()
+{
+ return ( GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "noweapon" )
+}
+
+entity function GetPetTitanOwner( entity titan )
+{
+ array<entity> players = GetPlayerArray()
+ entity foundPlayer
+ foreach ( player in players )
+ {
+ if ( player.GetPetTitan() == titan )
+ {
+ Assert( foundPlayer == null, player + " and " + foundPlayer + " both own " + titan )
+ foundPlayer = player
+ }
+ }
+
+ return foundPlayer
+}
+
+entity function GetSoulFromPlayer( entity player )
+{
+ Assert( player.IsPlayer(), "argument should be a player" )
+
+ if ( player.IsTitan() )
+ return player.GetTitanSoul()
+ else if ( IsValid( player.GetPetTitan() ) )
+ return player.GetPetTitan().GetTitanSoul()
+
+ return null
+}
+
+string function GetPlayerBodyType( player )
+{
+ return expect string( player.GetPlayerSettingsField( "weaponClass" ) )
+}
+
+
+void function SetTeam( entity ent, int team )
+{
+ #if CLIENT
+ ent.Code_SetTeam( team )
+ #else
+ if ( ent.IsPlayer() )
+ {
+ ent.Code_SetTeam( team )
+ }
+ else if ( ent.IsNPC() )
+ {
+ int currentTeam = ent.GetTeam()
+ bool alreadyAssignedValidTeam = ( currentTeam == TEAM_IMC || currentTeam == TEAM_MILITIA )
+
+ ent.Code_SetTeam( team )
+
+ if ( ent.GetModelName() == $"" )
+ return
+
+ FixupTitle( ent )
+
+ if ( IsGrunt( ent ) || IsSpectre( ent ) )
+ {
+ if ( IsMultiplayer() )
+ {
+ int eHandle = ent.GetEncodedEHandle()
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_UpdateOverheadIconForNPC", eHandle )
+ }
+ }
+ }
+ else if ( IsShieldDrone( ent ) )
+ {
+ if ( team == 0 )
+ {
+ // anybody can use neutral shield drone
+ ent.SetUsable()
+ }
+ else
+ {
+ // only friendlies use a team shield drone
+ ent.SetUsableByGroup( "friendlies pilot" )
+ }
+ }
+
+ table modelTable = ent.CreateTableFromModelKeyValues()
+
+ if ( !( "teamSkin" in modelTable ) )
+ return
+
+ if ( alreadyAssignedValidTeam && ( !( "swapTeamOnLeech" in modelTable.teamSkin ) ) )
+ return
+
+ SetSkinForTeam( ent, team )
+ }
+ else
+ {
+ ent.Code_SetTeam( team )
+ }
+ #endif
+}
+
+void function PrintTraceResults( TraceResults results )
+{
+ printt( "TraceResults: " )
+ printt( "=========================" )
+ printt( "hitEnt: " + results.hitEnt )
+ printt( "endPos: " + results.endPos )
+ printt( "surfaceNormal: " + results.surfaceNormal )
+ printt( "surfaceName: " + results.surfaceName )
+ printt( "fraction: " + results.fraction )
+ printt( "fractionLeftSolid: " + results.fractionLeftSolid )
+ printt( "hitGroup: " + results.hitGroup )
+ printt( "startSolid: " + results.startSolid )
+ printt( "allSolid: " + results.allSolid )
+ printt( "hitSky: " + results.hitSky )
+ printt( "contents: " + results.contents )
+ printt( "=========================" )
+}
+
+bool function PROTO_AlternateDoomedState()
+{
+ return ( GetCurrentPlaylistVarInt( "infinite_doomed_state", 1 ) == 1 )
+}
+
+bool function PROTO_VariableRegenDelay()
+{
+ return ( GetCurrentPlaylistVarInt( "variable_regen_delay", 1 ) == 1 )
+}
+
+bool function PROTO_AutoTitansDisabled()
+{
+ return ( GetCurrentPlaylistVarInt( "always_enable_autotitans", 1 ) == 0 )
+}
+
+bool function TitanDamageRewardsTitanCoreTime()
+{
+ if ( GetCurrentPlaylistVarInt( "titan_core_from_titan_damage", 0 ) != 0 )
+ return true
+ return false
+}
+
+vector function ClampToMap( vector pos )
+{
+ return IterateAxis( pos, LimitAxisToMapExtents )
+}
+
+vector function IterateAxis( vector pos, float functionref( float ) func )
+{
+ pos.x = func( pos.x )
+ pos.y = func( pos.y )
+ pos.z = func( pos.z )
+ return pos
+}
+
+float function LimitAxisToMapExtents( float axisVal )
+{
+ if ( axisVal >= MAP_EXTENTS )
+ axisVal = MAP_EXTENTS - 1
+ else if ( axisVal <= -MAP_EXTENTS )
+ axisVal = -( MAP_EXTENTS - 1 )
+ return axisVal
+}
+
+bool function PilotSpawnOntoTitanIsEnabledInPlaylist( entity player )
+{
+ if ( GetCurrentPlaylistVarInt( "titan_spawn_deploy_enabled", 0 ) != 0 )
+ return true
+ return false
+}
+
+bool function PlayerCanSpawnIntoTitan( entity player )
+{
+ if ( !PilotSpawnOntoTitanIsEnabledInPlaylist( player ) )
+ return false
+
+ entity titan = player.GetPetTitan()
+
+ if ( !IsAlive( titan ) )
+ return false
+
+ if ( GetDoomedState( titan ) )
+ return false
+
+ if ( titan.ContextAction_IsActive() )
+ return false
+
+ return false // turned off until todd figures out how to enable
+}
+
+array< vector > function EntitiesToOrigins( array< entity > ents )
+{
+ array<vector> origins
+
+ foreach ( ent in ents )
+ {
+ origins.append( ent.GetOrigin() )
+ }
+
+ return origins
+}
+
+vector function GetMedianOriginOfEntities( array<entity> ents )
+{
+ array<vector> origins = EntitiesToOrigins( ents )
+ return GetMedianOrigin( origins )
+}
+
+vector function GetMedianOrigin( array<vector> origins )
+{
+ if ( origins.len() == 1 )
+ return origins[0]
+
+ vector median
+
+ int middleIndex1
+ int middleIndex2
+
+ if ( IsEven( origins.len() ) )
+ {
+ middleIndex1 = origins.len() / 2
+ middleIndex2 = middleIndex1
+ }
+ else
+ {
+ middleIndex1 = int( floor( origins.len() / 2.0 ) )
+ middleIndex2 = middleIndex1 + 1
+ }
+
+ origins.sort( CompareVecX )
+ median.x = ( origins[ middleIndex1 ].x + origins[ middleIndex2 ].x ) / 2.0
+
+ origins.sort( CompareVecY )
+ median.y = ( origins[ middleIndex1 ].y + origins[ middleIndex2 ].y ) / 2.0
+
+ origins.sort( CompareVecZ )
+ median.z = ( origins[ middleIndex1 ].z + origins[ middleIndex2 ].z ) / 2.0
+
+ return median
+}
+
+int function CompareVecX( vector a, vector b )
+{
+ if ( a.x > b.x )
+ return 1
+
+ return -1
+}
+
+int function CompareVecY( vector a, vector b )
+{
+ if ( a.y > b.y )
+ return 1
+
+ return -1
+}
+
+int function CompareVecZ( vector a, vector b )
+{
+ if ( a.z > b.z )
+ return 1
+
+ return -1
+}
+
+float function GetFractionAlongPath( array<entity> nodes, vector p )
+{
+ float totalDistance = GetPathDistance( nodes )
+
+ // See which segment we are currently on (closest to)
+ int closestSegment = -1
+ float closestDist = 9999
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ float dist = GetDistanceSqrFromLineSegment( nodes[i].GetOrigin(), nodes[i + 1].GetOrigin(), p )
+ if ( closestSegment < 0 || dist < closestDist )
+ {
+ closestSegment = i
+ closestDist = dist
+ }
+ }
+ Assert( closestSegment >= 0 )
+ Assert( closestSegment < nodes.len() - 1 )
+
+ // Get the distance along the path already traveled
+ float distTraveled = 0.0
+ for( int i = 0 ; i < closestSegment; i++ )
+ {
+ //DebugDrawLine( nodes[i].GetOrigin(), nodes[i + 1].GetOrigin(), 255, 255, 0, true, 0.1 )
+ distTraveled += Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ }
+
+ // Add the distance traveled on current segment
+ vector closestPointOnSegment = GetClosestPointOnLineSegment( nodes[closestSegment].GetOrigin(), nodes[closestSegment + 1].GetOrigin(), p )
+ //DebugDrawLine( nodes[closestSegment].GetOrigin(), closestPointOnSegment, 255, 255, 0, true, 0.1 )
+ distTraveled += Distance( nodes[closestSegment].GetOrigin(), closestPointOnSegment )
+
+ return clamp( distTraveled / totalDistance, 0.0, 1.0 )
+}
+
+float function GetPathDistance( array<entity> nodes )
+{
+ float totalDist = 0.0
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ //DebugDrawSphere( nodes[i].GetOrigin(), 16.0, 255, 0, 0, true, 0.1 )
+ totalDist += Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ }
+ //DebugDrawSphere( nodes[nodes.len() -1].GetOrigin(), 16.0, 255, 0, 0, true, 0.1 )
+
+ return totalDist
+}
+
+void function WaittillAnimDone( entity animatingEnt )
+{
+ waitthread WaittillAnimDone_Thread( animatingEnt )
+}
+
+void function WaittillAnimDone_Thread( entity animatingEnt )
+{
+ if ( animatingEnt.IsPlayer() )
+ animatingEnt.EndSignal( "OnDestroy" )
+
+ animatingEnt.EndSignal( "OnAnimationInterrupted" )
+ animatingEnt.WaitSignal( "OnAnimationDone" )
+}
+
+array<entity> function GetEntityLinkChain( entity startNode )
+{
+ Assert( IsValid( startNode ) )
+ array<entity> nodes
+ nodes.append( startNode )
+ while(true)
+ {
+ entity nextNode = nodes[nodes.len() - 1].GetLinkEnt()
+ if ( !IsValid( nextNode ) )
+ break
+ nodes.append( nextNode )
+ }
+ return nodes
+}
+
+float function HealthRatio( entity ent )
+{
+ int health = ent.GetHealth()
+ int maxHealth = ent.GetMaxHealth()
+ return float( health ) / maxHealth
+}
+
+vector function GetPointOnPathForFraction( array<entity> nodes, float frac )
+{
+ Assert( frac >= 0 )
+
+ float totalPathDist = GetPathDistance( nodes )
+ float distRemaining = totalPathDist * frac
+ vector point = nodes[0].GetOrigin()
+
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ float segmentDist = Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ if ( segmentDist <= distRemaining )
+ {
+ // Add the whole segment
+ distRemaining -= segmentDist
+ point = nodes[i+1].GetOrigin()
+ }
+ else
+ {
+ // Fraction ends somewhere in this segment
+ vector dirVec = Normalize( nodes[i+1].GetOrigin() - nodes[i].GetOrigin() )
+ point = nodes[i].GetOrigin() + ( dirVec * distRemaining )
+ distRemaining = 0
+ }
+ if ( distRemaining <= 0 )
+ break
+ }
+
+ if ( frac > 1.0 && distRemaining > 0 )
+ {
+ vector dirVec = Normalize( nodes[nodes.len() - 1].GetOrigin() - nodes[nodes.len() - 2].GetOrigin() )
+ point = nodes[nodes.len() - 1].GetOrigin() + ( dirVec * distRemaining )
+ }
+
+ return point
+}
+
+bool function PlayerBlockedByTeamEMP( entity player )
+{
+ return ( player.nv.empEndTime > Time() )
+}
+
+#if SERVER
+void function Embark_Allow( entity player )
+{
+ player.SetTitanEmbarkEnabled( true )
+}
+
+void function Embark_Disallow( entity player )
+{
+ player.SetTitanEmbarkEnabled( false )
+}
+
+void function Disembark_Allow( entity player )
+{
+ player.SetTitanDisembarkEnabled( true )
+}
+
+void function Disembark_Disallow( entity player )
+{
+ player.SetTitanDisembarkEnabled( false )
+}
+#endif
+
+bool function CanEmbark( entity player )
+{
+ return player.GetTitanEmbarkEnabled()
+}
+
+bool function CanDisembark( entity player )
+{
+ return player.GetTitanDisembarkEnabled()
+}
+
+string function GetDroneType( entity npc )
+{
+ return expect string( npc.Dev_GetAISettingByKeyField( "drone_type" ) )
+}
+
+vector function FlattenVector( vector vec )
+{
+ return Vector( vec.x, vec.y, 0 )
+}
+
+vector function FlattenAngles( vector angles )
+{
+ return Vector( 0, angles.y, 0 )
+}
+
+bool function IsHumanSized( entity ent )
+{
+ if ( ent.IsPlayer() )
+ return ent.IsHuman()
+
+ if ( ent.IsNPC() )
+ {
+
+ if ( ent.GetAIClass() == AIC_SMALL_TURRET )
+ return true
+
+ string bodyType = ent.GetBodyType()
+ return bodyType == "human" || bodyType == "marvin"
+ }
+
+ return false
+}
+
+bool function IsDropship( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_dropship"
+#elseif CLIENT
+ if ( !ent.IsNPC() )
+ return false
+ //Probably should not use GetClassName, but npc_dropship isn't a class so can't use instanceof?
+ return ( ent.GetClassName() == "npc_dropship" || ent.GetSignifierName() == "npc_dropship" )
+#endif
+}
+
+bool function IsSpecialist( entity ent )
+{
+ return IsGrunt( ent ) && ent.IsMechanical()
+}
+
+bool function IsGrunt( entity ent )
+{
+#if SERVER
+ return ent.IsNPC() && ent.GetClassName() == "npc_soldier"
+#elseif CLIENT
+ return ent.IsNPC() && ent.GetSignifierName() == "npc_soldier"
+#endif
+}
+
+bool function IsMarvin( entity ent )
+{
+ return ent.IsNPC() && ent.GetAIClass() == AIC_MARVIN
+}
+
+bool function IsSpectre( entity ent )
+{
+ return ent.IsNPC() && ent.GetAIClass() == AIC_SPECTRE
+}
+
+bool function IsWorldSpawn( entity ent )
+{
+ #if SERVER
+ return ent.GetClassName() == "worldspawn"
+ #elseif CLIENT
+ return ent.GetSignifierName() == "worldspawn"
+ #endif
+}
+
+bool function IsEnvironment( entity ent )
+{
+ #if SERVER
+ return ent.GetClassName() == "trigger_hurt"
+ #elseif CLIENT
+ return ent.GetSignifierName() == "trigger_hurt"
+ #endif
+}
+
+bool function IsSuperSpectre( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_super_spectre"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_super_spectre"
+#endif
+}
+
+bool function IsAndroidNPC( entity ent )
+{
+ return ( IsSpectre( ent ) || IsStalker( ent ) || IsMarvin( ent ) )
+}
+
+bool function IsStalker( entity ent )
+{
+ return ent.IsNPC() && ( ent.GetAIClass() == AIC_STALKER || ent.GetAIClass() == AIC_STALKER_CRAWLING )
+}
+
+bool function IsProwler( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_prowler"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_prowler"
+#endif
+}
+
+bool function IsAirDrone( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_drone"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_drone"
+#endif
+}
+
+bool function IsPilotElite( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_pilot_elite"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_pilot_elite"
+#endif
+}
+
+bool function IsAttackDrone( entity ent )
+{
+ return ( ent.IsNPC() && !ent.IsNonCombatAI() && IsAirDrone( ent ) )
+}
+
+bool function IsGunship( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_gunship"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_gunship"
+#endif
+}
+
+bool function IsMinion( entity ent )
+{
+ if ( IsGrunt( ent ) )
+ return true
+
+ if ( IsSpectre( ent ) )
+ return true
+
+ return false
+}
+
+bool function IsShieldDrone( entity ent )
+{
+#if SERVER
+ if ( ent.GetClassName() != "npc_drone" )
+ return false
+#elseif CLIENT
+ if ( ent.GetSignifierName() != "npc_drone" )
+ return false
+#endif
+
+ return GetDroneType( ent ) == "drone_type_shield"
+}
+
+#if SERVER
+bool function IsTick( entity ent )
+{
+ return (ent.IsNPC() && (ent.GetAIClass() == AIC_FRAG_DRONE))
+}
+
+bool function IsNPCTitan( entity ent )
+{
+ return ent.IsNPC() && ent.IsTitan()
+}
+#endif
+
+bool function NPC_GruntChatterSPEnabled( entity npc )
+{
+ if ( !IsSingleplayer() )
+ return false
+
+ if ( !npc.IsNPC() )
+ return false
+
+ if ( npc.GetClassName() != "npc_soldier" )
+ return false
+
+ return true
+}
+
+RaySphereIntersectStruct function IntersectRayWithSphere( vector rayStart, vector rayEnd, vector sphereOrigin, float sphereRadius )
+{
+ RaySphereIntersectStruct intersection
+
+ vector vecSphereToRay = rayStart - sphereOrigin
+
+ vector vecRayDelta = rayEnd - rayStart
+ float a = DotProduct( vecRayDelta, vecRayDelta )
+
+ if ( a == 0.0 )
+ {
+ intersection.result = LengthSqr( vecSphereToRay ) <= sphereRadius * sphereRadius
+ intersection.enterFrac = 0.0
+ intersection.leaveFrac = 0.0
+ return intersection
+ }
+
+ float b = 2 * DotProduct( vecSphereToRay, vecRayDelta )
+ float c = DotProduct( vecSphereToRay, vecSphereToRay ) - sphereRadius * sphereRadius
+ float discrim = b * b - 4 * a * c
+ if ( discrim < 0.0 )
+ {
+ intersection.result = false
+ return intersection
+ }
+
+ discrim = sqrt( discrim )
+ float oo2a = 0.5 / a
+ intersection.enterFrac = ( - b - discrim ) * oo2a
+ intersection.leaveFrac = ( - b + discrim ) * oo2a
+
+ if ( ( intersection.enterFrac > 1.0 ) || ( intersection.leaveFrac < 0.0 ) )
+ {
+ intersection.result = false
+ return intersection
+ }
+
+ if ( intersection.enterFrac < 0.0 )
+ intersection.enterFrac = 0.0
+ if ( intersection.leaveFrac > 1.0 )
+ intersection.leaveFrac = 1.0
+
+ intersection.result = true
+ return intersection
+}
+
+table function GetTableFromString( string inString )
+{
+ if ( inString.len() > 0 )
+ return expect table( getconsttable()[ inString ] )
+
+ return {}
+}
+
+int function GetWeaponDamageNear( entity weapon, entity victim )
+{
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( weaponOwner.IsNPC() )
+ {
+ if ( victim.GetArmorType() == ARMOR_TYPE_HEAVY )
+ return weapon.GetWeaponSettingInt( eWeaponVar.npc_damage_near_value_titanarmor )
+ else
+ return weapon.GetWeaponSettingInt( eWeaponVar.npc_damage_near_value )
+ }
+ else
+ {
+ if ( victim.GetArmorType() == ARMOR_TYPE_HEAVY )
+ return weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor )
+ else
+ return weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value )
+ }
+
+ unreachable
+}
+
+void function PrintFirstPersonSequenceStruct( FirstPersonSequenceStruct fpsStruct )
+{
+ printt( "Printing FirstPersonSequenceStruct:" )
+
+ printt( "firstPersonAnim: " + fpsStruct.firstPersonAnim )
+ printt( "thirdPersonAnim: " + fpsStruct.thirdPersonAnim )
+ printt( "firstPersonAnimIdle: " + fpsStruct.firstPersonAnimIdle )
+ printt( "thirdPersonAnimIdle: " + fpsStruct.thirdPersonAnimIdle )
+ printt( "relativeAnim: " + fpsStruct.relativeAnim )
+ printt( "attachment: " + fpsStruct.attachment )
+ printt( "teleport: " + fpsStruct.teleport )
+ printt( "noParent: " + fpsStruct.noParent )
+ printt( "blendTime: " + fpsStruct.blendTime )
+ printt( "noViewLerp: " + fpsStruct.noViewLerp )
+ printt( "hideProxy: " + fpsStruct.hideProxy )
+ printt( "viewConeFunction: " + string( fpsStruct.viewConeFunction ) )
+ printt( "origin: " + string( fpsStruct.origin ) )
+ printt( "angles: " + string ( fpsStruct.angles ) )
+ printt( "enablePlanting: " + fpsStruct.enablePlanting )
+ printt( "setInitialTime: " + fpsStruct.setInitialTime )
+ printt( "useAnimatedRefAttachment: " + fpsStruct.useAnimatedRefAttachment )
+ printt( "renderWithViewModels: " + fpsStruct.renderWithViewModels )
+ printt( "gravity: " + fpsStruct.gravity )
+
+}
+
+void function WaitSignalOrTimeout( entity ent, float timeout, string signal1, string signal2 = "", string signal3 = "" )
+{
+ Assert( IsValid( ent ) )
+
+ ent.EndSignal( signal1 )
+
+ if ( signal2 != "" )
+ ent.EndSignal( signal2 )
+
+ if ( signal3 != "" )
+ ent.EndSignal( signal3 )
+
+ wait( timeout )
+}
+
+array<vector> function GetShortestLineSegmentConnectingLineSegments( vector line1Point1, vector line1Point2, vector line2Point1, vector line2Point2 )
+{
+ // From Paul Bourke's algorithm "The shortest line between two lines in 3D" at http://paulbourke.net/geometry/pointlineplane/
+
+ vector p1 = line1Point1
+ vector p2 = line1Point2
+ vector p3 = line2Point1
+ vector p4 = line2Point2
+ vector p13 = p1 - p3
+ vector p21 = p2 - p1
+ vector p43 = p4 - p3
+
+ if ( Length( p43 ) < 1.0 )
+ {
+ array<vector> resultVectors
+ resultVectors.append( p4 )
+ resultVectors.append( p3 )
+ return resultVectors
+ }
+
+ if ( Length( p21 ) < 1.0 )
+ {
+ array<vector> resultVectors
+ resultVectors.append( p2 )
+ resultVectors.append( p1 )
+ return resultVectors
+ }
+
+ float d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z
+ float d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z
+ float d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z
+ float d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z
+ float d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z
+
+
+ float denom = d2121 * d4343 - d4321 * d4321
+ Assert( fabs( denom ) > 0.01 )
+ float numer = d1343 * d4321 - d1321 * d4343
+
+ float mua = numer / denom
+ float mub = (d1343 + d4321 * (mua)) / d4343
+
+ vector resultVec1
+ vector resultVec2
+ resultVec1.x = p1.x + mua * p21.x
+ resultVec1.y = p1.y + mua * p21.y
+ resultVec1.z = p1.z + mua * p21.z
+ resultVec2.x = p3.x + mub * p43.x
+ resultVec2.y = p3.y + mub * p43.y
+ resultVec2.z = p3.z + mub * p43.z
+
+ array<vector> resultVectors
+ resultVectors.append( resultVec1 )
+ resultVectors.append( resultVec2 )
+ return resultVectors
+}
+
+vector function GetClosestPointToLineSegments( vector line1Point1, vector line1Point2, vector line2Point1, vector line2Point2 )
+{
+ array<vector> results = GetShortestLineSegmentConnectingLineSegments( line1Point1, line1Point2, line2Point1, line2Point2 )
+ Assert( results.len() == 2 )
+ return ( results[0] + results[1] ) / 2.0
+}
+
+
+bool function PlayerCanSee( entity player, entity ent, bool doTrace, float degrees )
+{
+ float minDot = deg_cos( degrees )
+
+ // On screen?
+ float dot = DotProduct( Normalize( ent.GetWorldSpaceCenter() - player.EyePosition() ), player.GetViewVector() )
+ if ( dot < minDot )
+ return false
+
+ // Can trace to it?
+ if ( doTrace )
+ {
+ TraceResults trace = TraceLine( player.EyePosition(), ent.GetWorldSpaceCenter(), null, TRACE_MASK_BLOCKLOS, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.hitEnt == ent || trace.fraction >= 0.99 )
+ return true
+ else
+ return false
+ }
+ else
+ return true
+
+ Assert( 0, "shouldn't ever get here")
+ unreachable
+}
+
+bool function PlayerCanSeePos( entity player, vector pos, bool doTrace, float degrees )
+{
+ float minDot = deg_cos( degrees )
+ float dot = DotProduct( Normalize( pos - player.EyePosition() ), player.GetViewVector() )
+ if ( dot < minDot )
+ return false
+
+ if ( doTrace )
+ {
+ TraceResults trace = TraceLine( player.EyePosition(), pos, null, TRACE_MASK_BLOCKLOS, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.fraction < 0.99 )
+ return false
+ }
+
+ return true
+}
+
+bool function VectorsFacingSameDirection( vector v1, vector v2, float degreesThreshold )
+{
+ float minDot = deg_cos( degreesThreshold )
+ float dot = DotProduct( Normalize( v1 ), Normalize( v2 ) )
+ return ( dot >= minDot )
+}
+
+vector function GetRelativeDelta( vector origin, entity ref, string attachment = "" )
+{
+ vector pos
+ vector right
+ vector forward
+ vector up
+
+ if ( attachment != "" )
+ {
+ int attachID = ref.LookupAttachment( attachment )
+ pos = ref.GetAttachmentOrigin( attachID )
+ vector angles = ref.GetAttachmentAngles( attachID )
+ right = AnglesToRight( angles )
+ forward = AnglesToForward( angles )
+ up = AnglesToUp( angles )
+ }
+ else
+ {
+ pos = ref.GetOrigin()
+ right = ref.GetRightVector()
+ forward = ref.GetForwardVector()
+ up = ref.GetUpVector()
+ }
+
+ vector x = GetClosestPointOnLineSegment( pos + right * -16384, pos + right * 16384, origin )
+ vector y = GetClosestPointOnLineSegment( pos + forward * -16384, pos + forward * 16384, origin )
+ vector z = GetClosestPointOnLineSegment( pos + up * -16384, pos + up * 16384, origin )
+
+ float distx = Distance(pos, x)
+ float disty = Distance(pos, y)
+ float distz = Distance(pos, z)
+
+ if ( DotProduct( x - pos, right ) < 0 )
+ distx *= -1
+ if ( DotProduct( y - pos, forward ) < 0 )
+ disty *= -1
+ if ( DotProduct( z - pos, up ) < 0 )
+ distz *= -1
+
+ return Vector( distx, disty, distz )
+}
+
+#if SERVER
+float function GetRoundTimeLimit_ForGameMode()
+{
+ #if DEV
+ if ( level.devForcedTimeLimit )
+ {
+ //Make it needed to be called multiple times for RoundBasedGameModes
+ level.devForcedTimeLimit = 0
+ return 0.1
+ }
+ #endif
+
+ #if MP
+ if ( GameState_GetTimeLimitOverride() >= 0 )
+ return GameState_GetTimeLimitOverride()
+ #endif
+
+ if ( !GameMode_IsDefined( GAMETYPE ) )
+ return GetCurrentPlaylistVarFloat( "roundtimelimit", 10 )
+ else
+ return GameMode_GetRoundTimeLimit( GAMETYPE )
+
+ unreachable
+}
+#endif
+
+bool function HasIronRules()
+{
+ bool result = (GetCurrentPlaylistVarInt( "iron_rules", 0 ) != 0)
+ return result
+}
+
+vector function GetWorldOriginFromRelativeDelta( vector delta, entity ref )
+{
+ vector right = ref.GetRightVector() * delta.x
+ vector forward = ref.GetForwardVector() * delta.y
+ vector up = ref.GetUpVector() * delta.z
+
+ return ref.GetOrigin() + right + forward + up
+}
+
+bool function IsHardcoreGameMode()
+{
+ return GetCurrentPlaylistVarInt( "gm_hardcore_settings", 0 ) == 1
+}
+
+bool function PlayerHasWeapon( entity player, string weaponName )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ weapons.extend( player.GetOffhandWeapons() )
+
+ foreach ( weapon in weapons )
+ {
+ if ( weapon.GetWeaponClassName() == weaponName )
+ return true
+ }
+
+ return false
+}
+
+bool function PlayerCanUseWeapon( entity player, string weaponClass )
+{
+ return ( ( player.IsTitan() && weaponClass == "titan" ) || ( !player.IsTitan() && weaponClass == "human" ) )
+}
+
+string function GetTitanCharacterName( entity titan )
+{
+ Assert( titan.IsTitan() )
+
+ string setFile
+
+ if ( titan.IsPlayer() )
+ {
+ setFile = titan.GetPlayerSettings()
+ }
+ else
+ {
+ string aiSettingsFile = titan.GetAISettingsName()
+ setFile = expect string( Dev_GetAISettingByKeyField_Global( aiSettingsFile, "npc_titan_player_settings" ) )
+ }
+
+ return GetTitanCharacterNameFromSetFile( setFile )
+}
+
+bool function IsTitanPrimeTitan( entity titan )
+{
+ Assert( titan.IsTitan() )
+ string setFile
+
+ if ( titan.IsPlayer() )
+ {
+ setFile = titan.GetPlayerSettings()
+ }
+ else
+ {
+ string aiSettingsFile = titan.GetAISettingsName()
+ setFile = expect string( Dev_GetAISettingByKeyField_Global( aiSettingsFile, "npc_titan_player_settings" ) )
+ }
+
+ return Dev_GetPlayerSettingByKeyField_Global( setFile, "isPrime" ) == 1
+
+} \ No newline at end of file