aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut
diff options
context:
space:
mode:
authorBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-08-31 23:14:58 +0100
committerBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-08-31 23:14:58 +0100
commit9a96d0bff56f1969c68bb52a2f33296095bdc67d (patch)
tree4175928e488632705692e3cccafa1a38dd854615 /Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut
parent27bd240871b7c0f2f49fef137718b2e3c208e3b4 (diff)
downloadNorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.tar.gz
NorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.zip
move to new mod format
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut678
1 files changed, 678 insertions, 0 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut
new file mode 100644
index 000000000..e3addf812
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_cloak_drone.gnut
@@ -0,0 +1,678 @@
+untyped
+
+global function CloakDrone_Init
+
+global function SpawnCloakDrone
+global function GetNPCCloakedDrones
+global function RemoveLeftoverCloakedDrones
+const FX_DRONE_CLOAK_BEAM = $"P_drone_cloak_beam"
+
+const float CLOAK_DRONE_REACHED_HARVESTER_DIST = 1300.0
+
+struct
+{
+ int cloakedDronesManagedEntArrayID
+ table<entity,string> cloakedDroneClaimedSquadList
+} file
+
+struct CloakDronePath
+{
+ vector start
+ vector goal
+ bool goalValid = false
+ float lastHeight
+}
+
+function CloakDrone_Init()
+{
+ PrecacheParticleSystem( FX_DRONE_CLOAK_BEAM )
+
+ file.cloakedDronesManagedEntArrayID = CreateScriptManagedEntArray()
+
+ RegisterSignal( "DroneCleanup" )
+ RegisterSignal( "DroneCrashing" )
+}
+
+entity function SpawnCloakDrone( int team, vector origin, vector angles, vector towerOrigin )
+{
+ int droneCount = GetNPCCloakedDrones().len()
+
+ // add some minor randomness to the spawn location as well as an offset based on number of drones in the world.
+ origin += < RandomIntRange( -64, 64 ), RandomIntRange( -64, 64 ), 300 + (droneCount * 128) >
+
+ entity cloakedDrone = CreateGenericDrone( team, origin, angles )
+ SetSpawnOption_AISettings( cloakedDrone, "npc_drone_cloaked" )
+
+ //these enable global damage callbacks for the cloakedDrone
+ cloakedDrone.s.isHidden <- false
+ cloakedDrone.s.fx <- null
+ cloakedDrone.s.towerOrigin <- towerOrigin
+
+ DispatchSpawn( cloakedDrone )
+ SetTeam( cloakedDrone, team )
+ SetTargetName( cloakedDrone, "Cloak Drone" )
+ cloakedDrone.SetTitle( "#NPC_CLOAK_DRONE" )
+ cloakedDrone.SetMaxHealth( 250 )
+ cloakedDrone.SetHealth( 250 )
+ cloakedDrone.SetTakeDamageType( DAMAGE_YES )
+ cloakedDrone.SetDamageNotifications( true )
+ cloakedDrone.SetDeathNotifications( true )
+ cloakedDrone.Solid()
+ cloakedDrone.Show()
+ cloakedDrone.EnableNPCFlag( NPC_IGNORE_ALL )
+
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_IN_SFX )
+
+ cloakedDrone.s.fx = CreateDroneCloakBeam( cloakedDrone )
+
+ SetVisibleEntitiesInConeQueriableEnabled( cloakedDrone, true )
+
+ thread CloakedDronePathThink( cloakedDrone )
+ thread CloakedDroneCloakThink( cloakedDrone )
+
+ #if R1_VGUI_MINIMAP
+ cloakedDrone.Minimap_SetDefaultMaterial( $"vgui/hud/cloak_drone_minimap_orange" )
+ #endif
+ cloakedDrone.Minimap_SetAlignUpright( true )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_IMC, null )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ cloakedDrone.Minimap_SetObjectScale( MINIMAP_CLOAKED_DRONE_SCALE )
+ cloakedDrone.Minimap_SetZOrder( MINIMAP_Z_NPC )
+
+ ShowName( cloakedDrone )
+
+ AddToGlobalCloakedDroneList( cloakedDrone )
+ return cloakedDrone
+}
+
+function AddToGlobalCloakedDroneList( cloakedDrone )
+{
+ AddToScriptManagedEntArray( file.cloakedDronesManagedEntArrayID, cloakedDrone )
+}
+
+array<entity> function GetNPCCloakedDrones()
+{
+ return GetScriptManagedEntArray( file.cloakedDronesManagedEntArrayID )
+}
+
+function RemoveLeftoverCloakedDrones()
+{
+ array<entity> droneArray = GetNPCCloakedDrones()
+ foreach ( cloakedDrone in droneArray )
+ {
+ thread CloakedDroneWarpOutAndDestroy( cloakedDrone )
+ }
+}
+
+void function CloakedDroneWarpOutAndDestroy( entity cloakedDrone )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.SetInvulnerable()
+
+ CloakedDroneWarpOut( cloakedDrone, cloakedDrone.GetOrigin() )
+ cloakedDrone.Destroy()
+}
+
+/************************************************************************************************\
+
+ ###### ## ####### ### ## ## #### ## ## ######
+## ## ## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ##### ## ## ## ## ## ####
+## ## ## ## ######### ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ## ## ## ### ## ##
+ ###### ######## ####### ## ## ## ## #### ## ## ######
+
+\************************************************************************************************/
+//HACK - this should probably move into code
+function CloakedDroneCloakThink( cloakedDrone )
+{
+ expect entity( cloakedDrone )
+
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ cloakedDrone.EndSignal( "DroneCleanup" )
+
+ wait 2 // wait a few seconds since it would start cloaking before picking an npc to follow
+ // some npcs might not be picked since they where already cloaked by accident.
+
+ CloakerThink( cloakedDrone, 400.0, [ "any" ], < 0, 0, -350 >, CloakDroneShouldCloakGuy, 1.5 )
+}
+
+function CloakDroneShouldCloakGuy( cloakedDrone, guy )
+{
+ expect entity( guy )
+ if ( !( guy.IsTitan() || IsSpectre( guy ) || IsGrunt( guy ) || IsSuperSpectre( guy ) ) )
+ return false
+
+ if ( guy.GetTargetName() == "empTitan" )
+ return false
+
+ if ( IsSniperSpectre( guy ) )
+ return false
+
+ if ( IsValid( GetRodeoPilot( guy ) ) )
+ return false
+
+ if ( cloakedDrone.s.isHidden )
+ return false
+
+ if ( StatusEffect_Get( guy, eStatusEffect.sonar_detected ) )
+ return false
+
+ // if ( !cloakedDrone.CanSee( guy ) )
+ // return false
+
+ return true
+}
+
+/************************************************************************************************\
+
+######## ### ######## ## ## #### ## ## ######
+## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## #### ## ##
+######## ## ## ## ######### ## ## ## ## ## ####
+## ######### ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ### ## ##
+## ## ## ## ## ## #### ## ## ######
+
+\************************************************************************************************/
+//HACK -> this should probably move into code
+const VALIDPATHFRAC = 0.99
+
+void function CloakedDronePathThink( entity cloakedDrone )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ cloakedDrone.EndSignal( "DroneCleanup" )
+
+ entity goalNPC = null
+ entity previousNPC = null
+ vector spawnOrigin = cloakedDrone.GetOrigin()
+ vector lastOrigin = cloakedDrone.GetOrigin()
+ float stuckDistSqr = 64.0*64.0
+ float targetLostTime = Time()
+ array<entity> claimedGuys = []
+
+ while( 1 )
+ {
+ while( goalNPC == null )
+ {
+ wait 1.0
+ array<entity> testArray = GetNPCArrayEx( "any", cloakedDrone.GetTeam(), TEAM_ANY, < 0, 0, 0 >, -1 )
+
+ // remove guys already being followed by an cloakedDrone
+ // or in other ways not suitable
+ array<entity> NPCs = []
+ foreach ( guy in testArray )
+ {
+ if ( !IsAlive( guy ) )
+ continue
+
+ //Only cloak titans, spectres, grunts,
+ if ( !( guy.IsTitan() || IsSpectre( guy ) || IsGrunt( guy ) || IsSuperSpectre( guy ) ) )
+ continue
+
+ //Don't cloak arc titans
+ if ( guy.GetTargetName() == "empTitan" )
+ continue
+
+ if ( IsSniperSpectre( guy ) )
+ continue
+
+ if ( IsFragDrone( guy ) )
+ continue
+
+ if ( guy == previousNPC )
+ continue
+
+ if ( guy.ContextAction_IsBusy() )
+ continue
+
+ if ( guy.GetParent() != null )
+ continue
+
+ if ( IsCloaked( guy ) )
+ continue
+
+ if ( IsSquadCenterClose( guy ) == false )
+ continue
+
+ if ( "cloakedDrone" in guy.s && IsAlive( expect entity( guy.s.cloakedDrone ) ) )
+ continue
+
+ if ( CloakedDroneIsSquadClaimed( expect string( guy.kv.squadname ) ) )
+ continue
+
+ if ( IsValid( GetRodeoPilot( guy ) ) )
+ continue
+
+ if ( StatusEffect_Get( guy, eStatusEffect.sonar_detected ) )
+ continue
+
+ NPCs.append( guy )
+ }
+
+ if ( NPCs.len() == 0 )
+ {
+ previousNPC = null
+
+ if ( Time() - targetLostTime > 10 )
+ {
+ // couldn't find anything to cloak for 10 seconds so we'll warp out until we find something
+ if ( cloakedDrone.s.isHidden == false )
+ CloakedDroneWarpOut( cloakedDrone, spawnOrigin )
+ }
+ continue
+ }
+
+ goalNPC = FindBestCloakTarget( NPCs, cloakedDrone.GetOrigin(), cloakedDrone )
+ Assert( goalNPC )
+ }
+
+ CloakedDroneClaimSquad( cloakedDrone, expect string( goalNPC.kv.squadname ) )
+
+ waitthread CloakedDronePathFollowNPC( cloakedDrone, goalNPC )
+
+ CloakedDroneReleaseSquad( cloakedDrone )
+
+ previousNPC = goalNPC
+ goalNPC = null
+ targetLostTime = Time()
+
+ float distSqr = DistanceSqr( lastOrigin, cloakedDrone.GetOrigin() )
+ if ( distSqr < stuckDistSqr )
+ CloakedDroneWarpOut( cloakedDrone, spawnOrigin )
+
+ lastOrigin = cloakedDrone.GetOrigin()
+ }
+}
+
+void function CloakedDroneClaimSquad( entity cloakedDrone, string squadname )
+{
+ if ( GetNPCSquadSize( squadname ) )
+ file.cloakedDroneClaimedSquadList[ cloakedDrone ] <- squadname
+}
+
+void function CloakedDroneReleaseSquad( entity cloakedDrone )
+{
+ if ( cloakedDrone in file.cloakedDroneClaimedSquadList )
+ delete file.cloakedDroneClaimedSquadList[ cloakedDrone ]
+}
+
+bool function CloakedDroneIsSquadClaimed( string squadname )
+{
+ table<entity,string> cloneTable = clone file.cloakedDroneClaimedSquadList
+ foreach ( entity cloakedDrone, squad in cloneTable )
+ {
+ if ( !IsAlive( cloakedDrone ) )
+ delete file.cloakedDroneClaimedSquadList[ cloakedDrone ]
+ else if ( squad == squadname )
+ return true
+ }
+ return false
+}
+
+void function CloakedDronePathFollowNPC( entity cloakedDrone, entity goalNPC )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ goalNPC.EndSignal( "OnDeath" )
+ goalNPC.EndSignal( "OnDestroy" )
+
+ if ( !( "cloakedDrone" in goalNPC.s ) )
+ goalNPC.s.cloakedDrone <- null
+ goalNPC.s.cloakedDrone = cloakedDrone
+
+ OnThreadEnd(
+ function() : ( goalNPC )
+ {
+ if ( IsAlive( goalNPC ) )
+ goalNPC.s.cloakedDrone = null
+ }
+ )
+
+ int droneTeam = cloakedDrone.GetTeam()
+
+ //vector maxs = < 64, 64, 53.5 >//bigger than model to compensate for large effect
+ //vector mins = < -64, -64, -64 >
+
+ vector maxs = < 32, 32, 32 >//bigger than model to compensate for large effect
+ vector mins = < -32, -32, -32 >
+
+ int mask = cloakedDrone.GetPhysicsSolidMask()
+
+ float defaultHeight = 300
+ array<float> traceHeightsLow = [ -75.0, -150.0, -250.0 ]
+ array<float> traceHeightsHigh = [ 150.0, 300.0, 800.0, 1500.0 ]
+
+ float waitTime = 0.25
+
+ CloakDronePath path
+ path.goalValid = false
+ path.lastHeight = defaultHeight
+
+ //If drone is following titan wait for titan to leave bubble shield.
+ if ( goalNPC.IsTitan() )
+ WaitTillHotDropComplete( goalNPC )
+
+ while( goalNPC.GetTeam() == droneTeam )
+ {
+ if ( IsValid( GetRodeoPilot( goalNPC ) ) )
+ return
+
+ //If our target npc gets revealed by a sonar pulse, ditch that chump.
+ if ( StatusEffect_Get( goalNPC, eStatusEffect.sonar_detected ) )
+ return
+
+ float minDist = CLOAK_DRONE_REACHED_HARVESTER_DIST * CLOAK_DRONE_REACHED_HARVESTER_DIST
+ float distToGenerator = DistanceSqr( goalNPC.GetOrigin(), cloakedDrone.s.towerOrigin )
+ //if we've gotten our npc to the generator, go find someone farther out to escort.
+ if ( distToGenerator <= minDist )
+ return
+
+ //DebugDrawCircleOnEnt( goalNPC, 20, 255, 0, 0, 0.1 )
+
+ float startTime = Time()
+ path.goalValid = false
+
+ CloakedDroneFindPathDefault( path, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ //find a new path if necessary
+ if ( !path.goalValid )
+ {
+ //lets check some heights and see if any are valid
+ CloakedDroneFindPathHorizontal( path, traceHeightsLow, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //OK so no way to directly go to those heights - lets see if we can move vertically down,
+ CloakedDroneFindPathVertical( path, traceHeightsLow, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //still no good...lets check up
+ CloakedDroneFindPathHorizontal( path, traceHeightsHigh, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //no direct shots up - lets try moving vertically up first
+ CloakedDroneFindPathVertical( path, traceHeightsHigh, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+ }
+ }
+ }
+ }
+
+ // if we can't find a valid path find a new goal
+ if ( !path.goalValid )
+ {
+ waitthread CloakedDroneWarpOut( cloakedDrone, GetCloakTargetOrigin( goalNPC ) + < 0, 0, defaultHeight > )
+ CloakedDroneWarpIn( cloakedDrone, GetCloakTargetOrigin( goalNPC ) + < 0, 0, defaultHeight > )
+ continue
+ }
+
+ if ( cloakedDrone.s.isHidden == true )
+ CloakedDroneWarpIn( cloakedDrone, cloakedDrone.GetOrigin() )
+
+ thread AssaultOrigin( cloakedDrone, path.goal )
+
+ float endTime = Time()
+ float elapsedTime = endTime - startTime
+ if ( elapsedTime < waitTime )
+ wait waitTime - elapsedTime
+ }
+}
+
+bool function CloakedDroneFindPathDefault( CloakDronePath path, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ vector offset = < 0, 0, defaultHeight >
+ path.start = ( cloakedDrone.GetOrigin() ) + < 0, 0, 32 > //Offset so path start is just above drone instead at bottom of drone.
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ //find out if we can get there using the default height
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ] , mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 50, 0, 0, true, 1.0 )
+ if ( result.fraction >= VALIDPATHFRAC )
+ {
+ path.lastHeight = defaultHeight
+ path.goalValid = true
+ }
+
+ return path.goalValid
+}
+
+bool function CloakedDroneFindPathHorizontal( CloakDronePath path, array<float> traceHeights, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ wait 0.1
+
+ vector offset
+ float testHeight
+
+ //slight optimization... recheck if the last time was also not the default height
+ if ( path.lastHeight != defaultHeight )
+ {
+ offset = < 0, 0, defaultHeight + path.lastHeight >
+ path.start = ( cloakedDrone.GetOrigin() )
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 0, 255, 0, true, 1.0 )
+ if ( result.fraction >= VALIDPATHFRAC )
+ {
+ path.goalValid = true
+ return path.goalValid
+ }
+ }
+
+ for ( int i = 0; i < traceHeights.len(); i++ )
+ {
+ testHeight = traceHeights[ i ]
+ if ( path.lastHeight == testHeight )
+ continue
+
+// wait 0.1
+
+ offset = < 0, 0, defaultHeight + testHeight >
+ path.start = ( cloakedDrone.GetOrigin() ) + ( testHeight > 0 ? < 0, 0, 0 > : < 0, 0, 32 > ) //Check from the top or bottom of the drone depending on if the drone is going up or down
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ if ( result.fraction < VALIDPATHFRAC )
+ {
+ //DebugDrawLine( path.start, path.goal, 200, 0, 0, true, 3.0 )
+ continue
+ }
+
+ //DebugDrawLine( path.start, path.goal, 0, 255, 0, true, 3.0 )
+
+ path.lastHeight = testHeight
+ path.goalValid = true
+ break
+ }
+
+ return path.goalValid
+}
+
+bool function CloakedDroneFindPathVertical( CloakDronePath path, array<float> traceHeights, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ vector offset
+ vector origin
+ float testHeight
+
+ for ( int i = 0; i < traceHeights.len(); i++ )
+ {
+ wait 0.1
+
+ testHeight = traceHeights[ i ]
+ origin = cloakedDrone.GetOrigin()
+ offset = < 0, 0, defaultHeight + testHeight >
+ path.start = < origin.x, origin.y, defaultHeight + testHeight >
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 50, 50, 100, true, 1.0 )
+ if ( result.fraction < VALIDPATHFRAC )
+ continue
+
+ //ok so it's valid - lets see if we can move to it from where we are
+// wait 0.1
+
+ path.goal = < path.start.x, path.start.y, path.start.z >
+ path.start = cloakedDrone.GetOrigin()
+
+ result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 255, 255, 0, true, 1.0 )
+ if ( result.fraction < VALIDPATHFRAC )
+ continue
+
+ path.lastHeight = testHeight
+ path.goalValid = true
+ break
+ }
+
+ return path.goalValid
+}
+
+void function CloakedDroneWarpOut( entity cloakedDrone, vector origin )
+{
+ if ( cloakedDrone.s.isHidden == false )
+ {
+ // only do this if we are not already hidden
+ FadeOutSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX, 0.5 )
+ FadeOutSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX, 0.5 )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_OUT_SFX )
+
+ cloakedDrone.s.fx.Fire( "StopPlayEndCap" )
+ cloakedDrone.SetTitle( "" )
+ cloakedDrone.s.isHidden = true
+ cloakedDrone.NotSolid()
+ cloakedDrone.Minimap_Hide( TEAM_IMC, null )
+ cloakedDrone.Minimap_Hide( TEAM_MILITIA, null )
+ cloakedDrone.SetNoTarget( true )
+ // let the beam fx end
+
+ if ( "smokeEffect" in cloakedDrone.s )
+ {
+ cloakedDrone.s.smokeEffect.Kill_Deprecated_UseDestroyInstead()
+ delete cloakedDrone.s.smokeEffect
+ }
+ UntrackAllToneMarks( cloakedDrone )
+
+ wait 0.3 // wait a bit before hidding the done so that the fx looks better
+ cloakedDrone.Hide()
+ }
+
+ wait 2.0
+
+ cloakedDrone.DisableBehavior( "Follow" )
+ thread AssaultOrigin( cloakedDrone, origin )
+ cloakedDrone.SetOrigin( origin )
+}
+
+void function CloakedDroneWarpIn( entity cloakedDrone, vector origin )
+{
+ cloakedDrone.DisableBehavior( "Follow" )
+ cloakedDrone.SetOrigin( origin )
+ PutEntityInSafeSpot( cloakedDrone, cloakedDrone, null, cloakedDrone.GetOrigin() + <0, 0, 32>, cloakedDrone.GetOrigin() )
+ thread AssaultOrigin( cloakedDrone, origin )
+
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_IN_SFX )
+
+ cloakedDrone.Show()
+ cloakedDrone.s.fx.Fire( "start" )
+ cloakedDrone.SetTitle( "#NPC_CLOAK_DRONE" )
+ cloakedDrone.s.isHidden = false
+ cloakedDrone.Solid()
+ cloakedDrone.Minimap_AlwaysShow( TEAM_IMC, null )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ cloakedDrone.SetNoTarget( false )
+}
+
+
+entity function CreateDroneCloakBeam( entity cloakedDrone )
+{
+ entity fx = PlayLoopFXOnEntity( FX_DRONE_CLOAK_BEAM, cloakedDrone, "", null, < 90, 0, 0 > )//, visibilityFlagOverride = null, visibilityFlagEntOverride = null )
+ return fx
+}
+
+entity function FindBestCloakTarget( array<entity> npcArray, vector origin, entity drone )
+{
+ entity selectedNPC = null
+ float maxDist = 10000 * 10000
+ float minDist = 1300 * 1300
+ float highestScore = -1
+
+ foreach ( npc in npcArray )
+ {
+ float score = 0
+ float distToGenerator = DistanceSqr( npc.GetOrigin(), drone.s.towerOrigin )
+ if ( distToGenerator > minDist )
+ {
+ // only give dist bonus if we aren't to close to the generator.
+ local dist = DistanceSqr( npc.GetOrigin(), origin )
+ score = GraphCapped( dist, maxDist, minDist, 0, 1 )
+ }
+
+ if ( npc.IsTitan() )
+ {
+ score += 0.75
+ if ( IsArcTitan( npc ) )
+ score -= 0.1
+ if ( IsMortarTitan( npc ) )
+ score -= 0.2
+// if ( IsNukeTitan( npc ) )
+// score += 0.1
+ }
+ if ( score > highestScore )
+ {
+ highestScore = score
+ selectedNPC = npc
+ }
+ }
+
+ return selectedNPC
+}
+
+vector function GetCloakTargetOrigin( entity npc )
+{
+ // returns the center of squad if the npc is in one
+ // else returns a good spot to cloak a titan
+
+ vector origin
+
+ if ( GetNPCSquadSize( npc.kv.squadname ) == 0 )
+ {
+ origin = npc.GetOrigin() + npc.GetNPCVelocity()
+ }
+ else
+ origin = npc.GetSquadCentroid()
+
+ Assert( origin.x < ( 16384 * 100 ) );
+
+ // defensive hack
+ if ( origin.x > ( 16384 * 100 ) )
+ origin = npc.GetOrigin()
+
+ return origin
+}
+
+function IsSquadCenterClose( npc, dist = 256 )
+{
+ // return true if there is no squad
+ if ( GetNPCSquadSize( npc.kv.squadname ) == 0 )
+ return true
+
+ // return true if the squad isn't too spread out.
+ if ( DistanceSqr( npc.GetSquadCentroid(), npc.GetOrigin() ) <= ( dist * dist ) )
+ return true
+
+ return false
+}