s1-scripts-dev/raw/maps/mp/_tracking_drone.gsc
2025-05-21 16:23:17 +02:00

1540 lines
39 KiB
Plaintext

/*
Tracking Drone
Author: Ben Retan
Description: A piece of equipment that tracks and highlights enemy players
*/
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\gametypes\_hostmigration;
STUNNED_TIME = 10.0;
Z_OFFSET = ( 0, 0, 90 );
BALL_DRONE_SPAWN_STAND_UP_OFFSET = 80;
BALL_DRONE_SPAWN_CROUCH_UP_OFFSET = 65;
BALL_DRONE_SPAWN_PRONE_UP_OFFSET = 37;
BALL_DRONE_SPAWN_FORWARD_OFFSET = 50;
BALL_DRONE_SPAWN_SIDE_OFFSET = 0;
BALL_DRONE_STAND_UP_OFFSET = 65;
BALL_DRONE_CROUCH_UP_OFFSET = 50;
BALL_DRONE_PRONE_UP_OFFSET = 22;
BALL_DRONE_FORWARD_OFFSET = 65;
BALL_DRONE_SIDE_OFFSET = 0;
HORDE_BALL_DRONE_STAND_UP_OFFSET = 105;
HORDE_BALL_DRONE_CROUCH_UP_OFFSET = 75;
HORDE_BALL_DRONE_PRONE_UP_OFFSET = 45;
HORDE_BALL_DRONE_FORWARD_OFFSET = 0;
HORDE_BALL_DRONE_SIDE_OFFSET = 30;
watchTrackingDroneUsage()
{
self endon( "spawned_player" );
self endon( "disconnect" );
self endon( "death" );
self endon( "faux_spawn" );
if(!IsDefined(level.trackingDroneSettings))
{
trackingDroneInit();
}
while ( 1 )
{
self waittill( "grenade_fire", grenade, weapname );
shortWeaponName = maps\mp\_utility::strip_suffix( weapname, "_lefthand" );
if ( shortWeaponName == "tracking_drone_mp" )
{
grenade thread destroy_tracking_drone_in_water();
wait(0.5);
if ( !IsRemovedEntity( grenade ) && IsDefined( grenade ) )
{
self.trackingDroneStartPosition = grenade.origin;
self.trackingDroneStartAngles = grenade.angles;
grenade deleteTrackingDrone();
if ( !prevent_tracking_drone_in_water( self.trackingDroneStartPosition ) )
{
tryUseTrackingDrone( weapname );
}
}
}
}
}
trackingDroneInit()
{
level.trackingDroneMaxPerPlayer = 1;
level.trackingDroneSettings = SpawnStruct();
level.trackingDroneSettings.timeOut = 20.0;
level.trackingDroneSettings.ExplosiveTimeOut = 30.0;
level.trackingDroneSettings.health = 999999; // keep it from dying anywhere in code
level.trackingDroneSettings.maxHealth = 60; // this is what we check against for death
level.trackingDroneSettings.vehicleInfo = "vehicle_tracking_drone_mp";
level.trackingDroneSettings.modelBase = "npc_drone_tracking";
level.trackingDroneSettings.fxId_sparks = Loadfx( "vfx/sparks/direct_hack_stun" );
level.trackingDroneSettings.fxId_laser_glow = Loadfx( "vfx/lights/tracking_drone_laser_blue" );
level.trackingDroneSettings.fxId_explode = LoadFX( "vfx/explosion/tracking_drone_explosion" );
level.trackingDroneSettings.fxId_LethalExplode = LoadFX( "vfx/explosion/frag_grenade_default" );
level.trackingDroneSettings.fxId_warning = LoadFX( "vfx/lights/light_tracking_drone_blink_warning" );
level.trackingDroneSettings.fxId_enemy_light = LoadFX( "vfx/lights/light_tracking_drone_blink_enemy" );
level.trackingDroneSettings.fxId_friendly_light = LoadFX( "vfx/lights/light_tracking_drone_blink_friendly" );
level.trackingDroneSettings.fxId_thruster_down = LoadFX( "vfx/distortion/tracking_drone_distortion_down" );
level.trackingDroneSettings.fxId_thruster_up = LoadFX( "vfx/distortion/tracking_drone_distortion_up" );
level.trackingDroneSettings.fxId_engine_distort = LoadFX( "vfx/distortion/tracking_drone_distortion_hemi" );
level.trackingDroneSettings.sound_explode = "veh_tracking_drone_explode";
level.trackingDroneSettings.sound_lock = "veh_tracking_drone_lock_lp";
level.trackingDrones = [];
foreach(player in level.players)
{
player.is_being_tracked = false;
}
level thread onTrackingPlayerConnect();
level.trackingDroneTimeout = level.trackingDroneSettings.timeOut;
level.explosiveDroneTimeout = level.trackingDroneSettings.ExplosiveTimeOut;
level.trackingDroneDebugPosition = 0;
level.trackingDroneDebugPositionForward = BALL_DRONE_FORWARD_OFFSET;
level.trackingDroneDebugPositionHeight = BALL_DRONE_SIDE_OFFSET;
}
tryUseTrackingDrone( weaponName ) // self == player
{
numIncomingVehicles = 1;
if( self isUsingRemote() )
{
return false;
}
else if( exceededMaxTrackingDrones( ) )
{
self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
return false;
}
else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
{
self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
return false;
}
// Force init (this is for test bots)
if(!IsDefined(self.trackingDroneArray))
{
self.trackingDroneArray = [];
}
// Limit the player to a max of 2 drones
if ( self.trackingDroneArray.size )
{
self.trackingDroneArray = array_removeUndefined( self.trackingDroneArray );
if( self.trackingDroneArray.size >= level.trackingDroneMaxPerPlayer )
{
if(isDefined(self.trackingDroneArray[0]))
{
self.trackingDroneArray[0] thread trackingDrone_leave();
}
}
}
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
trackingDrone = createTrackingDrone( weaponName );
if( !IsDefined( trackingDrone ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
return false;
}
trackingDrone.weaponName = weaponName;
self.trackingDroneArray[ self.trackingDroneArray.size ] = trackingDrone;
level.trackingDrones = array_removeUndefined( level.trackingDrones );
level.trackingDrones[level.trackingDrones.size] = trackingDrone;
self thread startTrackingDrone( trackingDrone );
return true;
}
createTrackingDrone( weaponName, bRecreate, startPosition, startAngles, DroneType ) // self == player
{
if(!IsDefined(bRecreate))
{
bRecreate = false;
}
if(!bRecreate)
{
origin = self GetEye();
forward = AnglesToForward( self GetPlayerAngles() );
startAng = self GetPlayerAngles();
forward = AnglesToForward( startAng );
side = AnglesToRight(startAng );
forwardOffset = forward * BALL_DRONE_SPAWN_FORWARD_OFFSET;
sideOffset = side * BALL_DRONE_SPAWN_SIDE_OFFSET;
heightOffset = BALL_DRONE_SPAWN_STAND_UP_OFFSET;
switch( self getStance() )
{
case "stand":
heightOffset = BALL_DRONE_SPAWN_STAND_UP_OFFSET;
break;
case "crouch":
heightOffset = BALL_DRONE_SPAWN_CROUCH_UP_OFFSET;
break;
case "prone":
heightOffset = BALL_DRONE_SPAWN_PRONE_UP_OFFSET;
break;
}
targetOffset = (0, 0, heightOffset);
targetOffset += sideOffset;
targetOffset += forwardOffset;
/#
if( level.trackingDroneDebugPosition )
{
targetOffset = (0, 0, level.trackingDroneDebugSpawnPositionHeight );
targetOffset += forward * level.trackingDroneDebugSpawnPositionForward;
}
#/
if(IsDefined(self.trackingDroneStartPosition) && IsDefined(self.trackingDroneStartAngles))
{
startPos = self.trackingDroneStartPosition;
startAng = self.trackingDroneStartAngles;
}
else
{
startPos = self.origin + targetOffset;
}
/*playerStartPos = self.origin;
trace = BulletTrace( playerStartPos, startPos, false );
// make sure we aren't starting in geo
attempts = 3;
while( trace[ "surfacetype" ] != "none" && attempts > 0 )
{
startPos = self.origin + ( VectorNormalize( playerStartPos - trace[ "position" ] ) * 5 );
trace = BulletTrace( playerStartPos, startPos, false );
attempts--;
wait( 0.05 );
}
if( attempts <= 0 )
return;*/
// make sure there are path nodes in the level
/*if(!IsDefined(GetNodesOnPath(startPos, self.origin)))
{
self SetWeaponAmmoStock( weaponName, self GetWeaponAmmoStock( weaponName ) + 1 );
self IPrintLnBold( &"MP_TRACKING_DRONE_UNAVAILABLE" );
return;
}*/
}
else
{
startPos = startPosition;
targetPos = startPosition;
startAng = startAngles;
}
/*
if(DroneType == "explosive_drone")
{
drone = SpawnHelicopter( self, startPos, startAng, level.trackingDroneSettings.vehicleInfo, "vehicle_pdrone" );
if( !IsDefined( drone ) )
return;
}
else
{
drone = SpawnHelicopter( self, startPos, startAng, level.trackingDroneSettings.vehicleInfo, level.trackingDroneSettings.modelBase );
if( !IsDefined( drone ) )
return;
}
*/
owner = self;
if ( isdefined ( level.isHorde ) && level.isHorde )
owner = level.player;
drone = SpawnHelicopter( owner, startPos, startAng, level.trackingDroneSettings.vehicleInfo, level.trackingDroneSettings.modelBase );
if( !IsDefined( drone ) )
return;
if(IsDefined(DroneType))
{
drone.type = "explosive_drone";
drone SetModel("vehicle_pdrone");
}
else
{
drone.type = "tracking_drone";
}
//drone EnableAimAssist();
// make expendable if it is non-lethal drone
drone make_entity_sentient_mp( self.team);
drone MakeVehicleNotCollideWithPlayers( true );
drone addToTrackingDroneList();
drone thread removeFromTrackingDroneListOnDeath();
drone.health = level.trackingDroneSettings.health;
drone.maxHealth = level.trackingDroneSettings.maxHealth;
drone.damageTaken = 0; // how much damage has it taken
drone.speed = 20;
drone.followSpeed = 20;
drone.owner = self;
drone.team = self.team;
drone Vehicle_SetSpeed( drone.speed, 10, 10 );
drone SetYawSpeed( 120, 90 );
drone SetNearGoalNotifyDist( 64 );
drone SetHoverParams( 4, 5, 5 );
drone.fx_tag0 = undefined;
if(IsDefined(drone.type))
{
if(drone.type == "tracking_drone")
{
drone.fx_tag0 = "fx_joint_0";
}
else if(drone.type == "explosive_drone")
{
drone.fx_tag0 = "TAG_EYE";
}
}
// set icon
if ( level.teamBased )
drone maps\mp\_entityheadicons::setTeamHeadIcon( self.team, (0,0,25), drone.fx_tag0 );
else
drone maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, (0,0,25), drone.fx_tag0);
// tracking
drone.maxTrackingRange = 2000;
drone.maxLaserRange = 300;
drone.trackedPlayer = undefined;
maxPitch = 45;
maxRoll = 45;
drone SetMaxPitchRoll( maxPitch, maxRoll );
drone.targetPos = startPos;
drone.attract_strength = 10000;
drone.attract_range = 150;
drone.attractor = Missile_CreateAttractorEnt( drone, drone.attract_strength, drone.attract_range );
drone.hasDodged = false;
drone.stunned = false;
drone.inactive = false;
drone thread maps\mp\gametypes\_damage::setEntityDamageCallback( drone.maxHealth, undefined, ::onTrackingDroneDeath, undefined, false );
drone thread trackingDrone_watchDisable();
drone thread trackingDrone_watchDeath();
drone thread trackingDrone_watchOwnerLoss();
drone thread trackingDrone_watchOwnerDeath();
drone thread trackingDrone_watchRoundEnd();
drone thread trackingDrone_watchHostMigration();
if ( !isdefined ( level.isHorde ) )
drone thread trackingDrone_watchTimeout();
// if(drone.type == "explosive_drone")
// {
// drone thread ExplosiveDrone_enemy_lightFX();
// drone thread ExplosiveDrone_friendly_lightFX();
// }
// else if(drone.type == "tracking_drone")
// {
// drone thread trackingDrone_enemy_lightFX();
// drone thread trackingDrone_friendly_lightFX();
// drone thread drone_thrusterFX();
// }
if(drone.type == "tracking_drone")
{
drone thread trackingDrone_enemy_lightFX();
drone thread trackingDrone_friendly_lightFX();
drone thread drone_thrusterFX();
}
return drone;
}
idleTargetMover( ent ) // self == player
{
self endon( "disconnect" );
level endon( "game_ended" );
ent endon( "death" );
// keep the idleTarget entity behind the player so the turret is always default looking back there
forward = AnglesToForward( self.angles );
while( true )
{
if( isReallyAlive( self ) && !self isUsingRemote() && AnglesToForward( self.angles ) != forward )
{
forward = AnglesToForward( self.angles );
pos = self.origin + ( forward * -100 ) + ( 0, 0, 40 );
ent MoveTo( pos, 0.5 );
}
wait( 0.5 );
}
}
trackingDrone_lightFX( fx, player ) // self == drone
{
player endon("disconnect");
PlayFXOnTagForClients( fx, self, "fx_light_1", player );
wait 0.05;
PlayFXOnTagForClients( fx, self, "fx_light_2", player );
wait 0.05;
PlayFXOnTagForClients( fx, self, "fx_light_3", player );
wait 0.05;
PlayFXOnTagForClients( fx, self, "fx_light_4", player );
}
trackingDrone_enemy_lightFX() // self == drone
{
// looping fx
self endon( "death" );
foreach( player in level.players )
{
if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team != self.team )
{
self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_enemy_light, player );
wait(0.2);
}
}
}
trackingDrone_friendly_lightFX() // self == drone
{
// looping fx
self endon( "death" );
foreach( player in level.players )
{
if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team == self.team )
{
self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
wait(0.2);
}
}
self thread watchConnectedPlayFX();
self thread watchJoinedTeamPlayFX();
}
drone_thrusterFX()
{
self endon( "death" );
foreach( player in level.players )
{
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_F", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_K", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_L", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_R", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_engine_distort))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_engine_distort, self, "TAG_WEAPON", player );
}
wait(0.25);
/*PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_F", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_K", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_L", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_R", player );*/
}
while( true )
{
level waittill( "connected", player );
player waittill( "spawned_player" );
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_F", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_K", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_L", player );
}
wait(0.1);
if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_R", player );
}
wait(0.1);
{
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_engine_distort, self, "TAG_WEAPON", player );
}
wait(0.25);
/*PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_F", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_K", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_L", player );
wait(0.1);
PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_R", player );*/
}
}
watchConnectedPlayFX() // self == drone
{
self endon( "death" );
// play fx for late comers
while( true )
{
level waittill( "connected", player );
player waittill( "spawned_player" );
if( IsDefined( player ) && player.team == self.team )
{
self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
wait(0.2);
}
}
}
watchJoinedTeamPlayFX() // self == drone
{
self endon( "death" );
// play fx for team changers
while( true )
{
level waittill( "joined_team", player );
player waittill( "spawned_player" );
if( IsDefined( player ) && player.team == self.team )
{
self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
wait(0.2);
}
}
}
///////////////////// TEMP FX FOR TEMP EXPLOSIVE DRONE MESH ///////////////////////////////////////////
////
//ExplosiveDrone_enemy_lightFX() // self == drone
//{
// // looping fx
//
// self endon( "death" );
//
// foreach( player in level.players )
// {
// if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team != self.team )
// {
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_0", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_1", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_2", player );
// }
//
// }
//}
//
//ExplosiveDrone_friendly_lightFX() // self == drone
//{
// // looping fx
//
// self endon( "death" );
//
// foreach( player in level.players )
// {
// if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team == self.team )
// {
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
// }
// }
//
// self thread watchConnectedExplosivePlayFX();
// self thread watchJoinedTeamExplosivePlayFX();
//}
//
//watchConnectedExplosivePlayFX() // self == drone
//{
// self endon( "death" );
//
// // play fx for late comers
// while( true )
// {
// level waittill( "connected", player );
// player waittill( "spawned_player" );
//
// if( IsDefined( player ) && player.team == self.team )
// {
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
// }
// }
//}
//
//watchJoinedTeamExplosivePlayFX() // self == drone
//{
// self endon( "death" );
//
// // play fx for team changers
// while( true )
// {
// level waittill( "joined_team", player );
// player waittill( "spawned_player" );
//
// if( IsDefined( player ) && player.team == self.team )
// {
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
// wait 0.15;
// PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
// }
// }
//}
/////
///////////////////// TEMP FX FOR TEMP EXPLOSIVE DRONE MESH ///////////////////////////////////////////
startTrackingDrone( drone ) // self == player
{
level endon( "game_ended" );
drone endon( "death" );
// begin following player
drone thread trackingDrone_followTarget();
drone thread aud_drone_start_jets();
if(IsDefined(drone.type))
{
if(drone.type == "explosive_drone")
{
drone thread CheckForExplosiveGoal();
/*
result = self waittill_any_return( "goal", "near_goal", "hit_goal" );
*/
}
// begin highlighting target
else if(drone.type == "tracking_drone" && !isdefined ( level.isHorde ) )
{
drone thread trackingDrone_highlightTarget();
}
}
}
CheckForExplosiveGoal()
{
level endon( "game_ended" );
level endon( "host_migration_begin" );
self endon( "death" );
self endon( "leaving" );
while(true)
{
self waittill_any( "goal", "near_goal", "hit_goal");
{
if(self.trackedPlayer != self.owner && isReallyAlive(self.trackedPlayer))
{
DistanceToTargetSq = DistanceSquared(self.trackedPlayer.origin, self.origin);
if(DistanceToTargetSq <= 16384)
{
self notify("exploding");
self thread BlowUpDroneSequence();
break;
}
}
}
}
}
BlowUpDroneSequence()
{
Time = 2;
// play sound
// blink lights
StoredOwner = undefined;
if(isdefined(self.owner))
{
StoredOwner = self.owner;
}
if( IsDefined( self ))
{
self thread TurnOnDangerLights();
self playsound("drone_warning_beap");
}
wait(Time);
if(isDefined(self))
{
self PlaySound("drone_bomb_explosion");
up_v = AnglesToUp(self.angles);
forward_v = AnglesToForward(self.angles);
PlayFx(level.trackingDroneSettings.fxId_LethalExplode, self.origin, forward_v, up_v);
if(isdefined(StoredOwner))
{
self RadiusDamage(self.origin, 256, 1000, 25, StoredOwner, "MOD_EXPLOSIVE", "killstreak_missile_strike_mp");
}
else
{
self RadiusDamage(self.origin, 256, 1000, 25, undefined, "MOD_EXPLOSIVE", "killstreak_missile_strike_mp");
}
self notify("death");
}
}
TurnOnDangerLights()
{
if( IsDefined( self ))
{
StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_0");
StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_1");
StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_2");
StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0");
StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1");
StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2");
}
wait(0.05);
if( IsDefined( self ))
{
PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_0");
PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_1");
}
wait(0.15);
if( IsDefined( self ))
{
PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_2");
}
}
trackingDrone_followTarget() // self == drone
{
level endon( "game_ended" );
level endon( "host_migration_begin" );
self endon( "death" );
self endon( "leaving" );
self endon( "exploding" );
if( !IsDefined( self.owner ) )
{
self thread trackingDrone_leave();
return;
}
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self Vehicle_SetSpeed( self.followSpeed, 10, 10 );
self.previousTrackedPlayer = self.owner;
self.trackedPlayer = undefined;
if ( isdefined ( level.isHorde ) && level.isHorde )
self.trackedPlayer = self.owner;
while( true )
{
if( IsDefined( self.stunned ) && self.stunned )
{
wait( 0.5 );
continue;
}
if( IsDefined( self.owner ) && IsAlive( self.owner ) )
{
// Try to find a target to track
maxRangeSquared = self.maxTrackingRange * self.maxTrackingRange;
closestDistanceSquared = maxRangeSquared;
if ( !isdefined ( level.isHorde ) )
{
if(!IsDefined(self.trackedPlayer) || self.trackedPlayer == self.owner)
{
foreach( player in level.players )
{
if( IsDefined( player ) && IsAlive(player) && player.team != self.team && !player _hasPerk( "specialty_blindeye" ) )
{
currentDistanceSquared = DistanceSquared(self.origin, player.origin);
if(currentDistanceSquared < closestDistanceSquared)
{
closestDistanceSquared = currentDistanceSquared;
self.trackedPlayer = player;
self thread watchPlayerDeathDisconnect(player);
}
}
}
}
}
// Track the owner if there are no current enemies to track
if(!IsDefined(self.trackedPlayer))
{
self.trackedPlayer = self.owner;
}
// Track current target
if( IsDefined( self.trackedPlayer ))
{
trackingDrone_moveToPlayer(self.trackedPlayer);
}
// Set previous tracked target
if(self.trackedPlayer != self.previousTrackedPlayer )
{
stopHighlightingPlayer(self.previousTrackedPlayer );
self.previousTrackedPlayer = self.trackedPlayer;
}
}
wait( 1 );
}
}
watchPlayerDeathDisconnect(trackedPlayer) // self == drone
{
self endon( "death" );
self endon( "leaving" );
self endon( "exploding" );
trackedPlayer waittill_any( "death", "disconnect", "faux_spawn", "joined_team" );
if( IsDefined(trackedPlayer) )
{
if(trackedPlayer.is_being_tracked == true)
{
if(!IsAlive(trackedPlayer))
trackedPlayer.died_being_tracked = true;
self thread trackingDrone_leave();
}
else
{
self.trackedPlayer = undefined;
}
}
}
trackingDrone_moveToPlayer( playerToMoveTo ) // self == drone
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self notify( "trackingDrone_moveToPlayer" );
self endon( "trackingDrone_moveToPlayer" );
// collect the ideal offsets from the player
forwardOffset = 0;
sideOffset = 0;
heightOffset = 0;
if( isdefined ( level.isHorde ) && level.isHorde )
{
forwardOffset = -1 * HORDE_BALL_DRONE_FORWARD_OFFSET;
sideOffset = HORDE_BALL_DRONE_SIDE_OFFSET;
switch( playerToMoveTo getStance() )
{
case "stand":
heightOffset = HORDE_BALL_DRONE_STAND_UP_OFFSET;
break;
case "crouch":
heightOffset = HORDE_BALL_DRONE_CROUCH_UP_OFFSET;
break;
case "prone":
heightOffset = HORDE_BALL_DRONE_PRONE_UP_OFFSET;
break;
}
}
else
{
forwardOffset = -1 * BALL_DRONE_FORWARD_OFFSET;
sideOffset = BALL_DRONE_SIDE_OFFSET;
switch( playerToMoveTo getStance() )
{
case "stand":
heightOffset = BALL_DRONE_STAND_UP_OFFSET;
break;
case "crouch":
heightOffset = BALL_DRONE_CROUCH_UP_OFFSET;
break;
case "prone":
heightOffset = BALL_DRONE_PRONE_UP_OFFSET;
break;
}
}
targetOffset = (sideOffset, forwardOffset, heightOffset);
/#
if( level.trackingDroneDebugPosition )
{
targetOffset = (0, -1*level.trackingDroneDebugPositionForward, level.trackingDroneDebugPositionHeight );
}
#/
// ask code to navigate us as close as possible to the offset from the owner, set us as in-transit and start a thread waiting for us to get to the goal
self SetDroneGoalPos( playerToMoveTo, targetOffset );
self.inTransit = true;
self thread trackingDrone_watchForGoal();
self thread trackingDrone_watchTargetDisconnect();
}
trackingDrone_stopMovement()
{
// ask code to navigate us as close as possible to the offset from the owner, set us as in-transit and start a thread waiting for us to get to the goal
self SetVehGoalPos( self.origin, 1 );
self.inTransit = false;
self.inactive = true;
}
trackingDrone_changeOwner(newOwner) // self = drone
{
// Change ownership of drone
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
trackingDrone = newOwner createTrackingDrone( self.weaponName, true, self.origin, self.angles );
if( !IsDefined( trackingDrone ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
return false;
}
// Create array if it does not exist
if(!IsDefined(newOwner.trackingDroneArray))
{
newOwner.trackingDroneArray = [];
}
newOwner.trackingDroneArray[ newOwner.trackingDroneArray.size ] = trackingDrone;
level.trackingDrones = array_removeUndefined( level.trackingDrones );
level.trackingDrones[level.trackingDrones.size] = trackingDrone;
newOwner thread startTrackingDrone( trackingDrone );
if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
{
StopFXOnTag( level.trackingDroneSettings.fxId_sparks, self, self.fx_tag0 );
}
self removeTrackingDrone();
return true;
}
trackingDrone_highlightTarget() // self == drone
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
if( !IsDefined( self.owner ) )
{
self thread trackingDrone_leave();
return;
}
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
// Create the laser
self.laserTag = Spawn( "script_model", self.origin );
self.laserTag SetModel( "tag_laser" );
while( true )
{
if(IsDefined(self.trackedPlayer))
{
self.laserTag.origin = self GetTagOrigin("tag_weapon");
randomRange = 20;
randomOffset = ( randomFloat( randomRange ), randomFloat( randomRange ), randomFloat( randomRange ) ) - ( 10, 10, 10 );
heightOffset = BALL_DRONE_STAND_UP_OFFSET;
switch( self.trackedPlayer getStance() )
{
case "stand":
heightOffset = BALL_DRONE_STAND_UP_OFFSET;
break;
case "crouch":
heightOffset = BALL_DRONE_CROUCH_UP_OFFSET;
break;
case "prone":
heightOffset = BALL_DRONE_PRONE_UP_OFFSET;
break;
}
self.laserTag.angles = VectorToAngles( ( self.trackedPlayer.origin + ( 0, 0, heightOffset - 20 ) + randomOffset ) - self.origin );
}
if( IsDefined( self.stunned ) && self.stunned )
{
wait( 0.5 );
continue;
}
// Highlight enemies
traceEntity = undefined;
if(IsDefined(self.trackedPlayer))
{
traceData = BulletTrace( self.origin, self.trackedPlayer.origin, true, self );
traceEntity = traceData["entity"];
}
if(IsDefined(self.trackedPlayer) &&
self.trackedPlayer != self.owner &&
IsDefined(traceEntity) &&
traceEntity == self.trackedPlayer &&
DistanceSquared(self.origin, self.trackedPlayer.origin) < self.maxLaserRange * self.maxLaserRange)
{
if( self.trackedPlayer.is_being_tracked == false)
{
startHighlightingPlayer(self.trackedPlayer);
}
}
else
{
if(IsDefined(self.trackedPlayer) && self.trackedPlayer.is_being_tracked == true)
{
stopHighlightingPlayer(self.trackedPlayer);
}
}
wait( 0.05 );
}
}
startHighlightingPlayer(playerToStart) // self == drone
{
self.laserTag LaserOn("tracking_drone_laser");
playfxontag(level.trackingDroneSettings.fxId_laser_glow, self.laserTag, "tag_laser");
if( IsDefined( level.trackingDroneSettings.sound_lock ) )
{
self PlayLoopSound( level.trackingDroneSettings.sound_lock );
}
playerToStart setPerk( "specialty_radararrow", true, false );
if ( playerToStart.is_being_tracked == false )
{
playerToStart.is_being_tracked = true;
playerToStart.TrackedByPlayer = self.owner;
}
}
stopHighlightingPlayer(playerToStop) // self == drone.trackedPlayer
{
if( IsDefined( self.laserTag ) )
{
self.laserTag LaserOff();
stopfxontag(level.trackingDroneSettings.fxId_laser_glow, self.laserTag, "tag_laser");
}
if(IsDefined(playerToStop))
{
if( IsDefined( level.trackingDroneSettings.sound_lock ) )
{
self StopLoopSound( );
}
if ( playerToStop HasPerk( "specialty_radararrow", true ) )
{
playerToStop unsetPerk( "specialty_radararrow", true );
}
playerToStop notify( "player_not_tracked" );
playerToStop.is_being_tracked = false;
playerToStop.TrackedByPlayer = undefined;
}
}
onTrackingPlayerConnect()
{
level endon( "game_ended" );
while ( true )
{
level waittill( "connected", player );
player.is_being_tracked = false;
// possible for multiple players to come in on one frame, so double check that we have a value for all players
foreach ( player in level.players )
{
if (!IsDefined(player.is_being_tracked))
player.is_being_tracked = false;
}
}
}
/#
debugDrawDronePath()
{
self endon( "death" );
self endon( "hit_goal" );
self notify( "debugDrawDronePath" );
self endon( "debugDrawDronePath" );
while( true )
{
nodePath = GetNodesOnPath( self.owner.origin, self.origin );
if( IsDefined( nodePath ) )
{
for( i = 0; i < nodePath.size; i++ )
{
if( IsDefined( nodePath[ i + 1 ] ) )
Line( nodePath[ i ].origin + Z_OFFSET, nodePath[ i + 1 ].origin + Z_OFFSET, ( 1, 0, 0 ) );
}
}
wait( 0.05 );
}
}
#/
trackingDrone_watchForGoal() // self == drone
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self notify( "trackingDrone_watchForGoal" );
self endon( "trackingDrone_watchForGoal" );
result = self waittill_any_return( "goal", "near_goal", "hit_goal" );
self.inTransit = false;
self.inactive = false;
self notify( "hit_goal" );
}
/* ============================
State Trackers
============================ */
trackingDrone_watchDeath() // self == drone
{
level endon( "game_ended" );
self endon( "gone" );
self waittill( "death" );
self thread trackingDroneDestroyed();
}
trackingDrone_watchTimeout() // self == drone
{
level endon ( "game_ended" );
level endon( "host_migration_begin" );
self endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
timeout = level.trackingDroneTimeout;
if(self.type == "explosive_drone")
{
timeout = level.explosiveDroneTimeout;
}
wait( timeout );
self thread trackingDrone_leave();
}
trackingDrone_watchOwnerLoss() // self == drone
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
self notify( "owner_gone" );
// leave
self thread trackingDrone_leave();
}
trackingDrone_watchOwnerDeath() // self == drone
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
while( true )
{
self.owner waittill( "death" );
self thread trackingDrone_leave();
}
}
trackingDrone_watchTargetDisconnect() // self == drone
{
level endon( "game_ended" );
level endon( "host_migration_begin" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
self notify( "trackingDrone_watchTargetDisconnect" );
self endon( "trackingDrone_watchTargetDisconnect" );
self.trackedPlayer waittill( "disconnect" );
stopHighlightingPlayer( self.trackedPlayer );
trackingDrone_moveToPlayer( self.owner );
}
trackingDrone_watchRoundEnd() // self == drone
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
level waittill_any( "round_end_finished", "game_ended" );
// leave
self thread trackingDrone_leave();
}
trackingDrone_watchHostMigration() // self == drone
{
level endon( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "death" );
self.owner endon( "disconnect" );
self endon( "owner_gone" );
level waittill( "host_migration_begin" );
stopHighlightingPlayer( self.trackedPlayer );
trackingDrone_stopMovement();
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
self thread trackingDrone_changeOwner( self.owner );
}
trackingDrone_leave() // self == drone
{
self endon( "death" );
self notify( "leaving" );
stopHighlightingPlayer(self.trackedPlayer);
trackingDroneExplode();
}
/* ============================
End State Trackers
============================ */
/* ============================
Damage and Death Monitors
============================ */
onTrackingDroneDeath( attacker, weapon, meansOfDeath, damage )
{
self notify ( "death" );
}
trackingDrone_watchDisable()
{
self endon( "death" );
self.owner endon( "disconnect" );
level endon( "game_ended" );
while( true )
{
self waittill( "emp_damage", attacker, duration );
self thread trackingDrone_stunned();
}
}
trackingDrone_stunned() // self == drone
{
self notify( "trackingDrone_stunned" );
self endon( "trackingDrone_stunned" );
self endon( "death" );
self.owner endon( "disconnect" );
level endon( "game_ended" );
trackingDrone_stunBegin();
wait( STUNNED_TIME );
trackingDrone_stunEnd();
}
trackingDrone_stunBegin()
{
if(self.stunned)
return;
self.stunned = true;
if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
{
PlayFXOnTag( level.trackingDroneSettings.fxId_sparks, self, self.fx_tag0 );
}
thread stopHighlightingPlayer(self.trackedPlayer);
self.trackedPlayer = undefined;
self.previousTrackedPlayer = self.owner;
self thread trackingDrone_stopMovement();
}
trackingDrone_stunEnd()
{
if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
{
KillFXOnTag( level.trackingDroneSettings.fxId_sparks, self, self.fx_tag0 );
}
self.stunned = false;
self.inactive = false;
}
trackingDroneDestroyed() // self == drone
{
if( !IsDefined( self ) )
return;
//self.owner IPrintLnBold( &"TRACKING_DRONE_DESTROYED" );
// Turn off the highlighting
stopHighlightingPlayer(self.trackedPlayer);
// stop stuned behavior and vfx
trackingDrone_stunEnd();
// TODO: could put some drama here as it crashes
trackingDroneExplode();
}
trackingDroneExplode() // self == drone
{
if( IsDefined( level.trackingDroneSettings.fxId_explode ) )
{
PlayFX( level.trackingDroneSettings.fxId_explode, self.origin );
}
if( IsDefined( level.trackingDroneSettings.sound_explode ) )
{
self PlaySound( level.trackingDroneSettings.sound_explode );
}
self notify( "explode" );
self removeTrackingDrone();
}
deleteTrackingDrone() // self == drone
{
if ( !IsRemovedEntity( self ) && IsDefined( self ) )
{
if ( IsDefined( self.attractor ) )
{
Missile_DeleteAttractor( self.attractor );
}
self delete();
}
}
removeTrackingDrone() // self == drone
{
// decrement the faux vehicle count right before it is deleted this way we know for sure it is gone
decrementFauxVehicleCount();
if( IsDefined( self.owner ) && IsDefined( self.owner.trackingDrone ) )
self.owner.trackingDrone = undefined;
if ( IsDefined( self.laserTag ) )
{
self.laserTag Delete();
}
self deleteTrackingDrone();
}
/* ============================
End Damage and Death Monitors
============================ */
/* ============================
List and Count Management
============================ */
addToTrackingDroneList()
{
level.trackingDrones[ self GetEntityNumber() ] = self;
}
removeFromTrackingDroneListOnDeath()
{
entNum = self GetEntityNumber();
self waittill ( "death" );
level.trackingDrones[ entNum ] = undefined;
level.trackingDrones = array_removeUndefined( level.trackingDrones );
}
exceededMaxTrackingDrones( )
{
if( level.trackingDrones.size >= maxVehiclesAllowed() )
return true;
else
return false;
}
/* ============================
End List and Count Management
============================ */
aud_drone_start_jets()
{
self playloopsound( "veh_tracking_drone_jets_lp" );
}
destroy_tracking_drone_in_water() // self = tracking drone grenade
{
self endon( "death" );
if ( !isDefined( level.water_triggers ) )
{
return;
}
while ( true )
{
foreach ( trig in level.water_triggers )
{
if ( self IsTouching( trig ) )
{
if ( IsDefined( level.trackingDroneSettings.fxId_explode ) )
{
PlayFX( level.trackingDroneSettings.fxId_explode, self.origin );
}
if ( IsDefined( level.trackingDroneSettings.sound_explode ) )
{
self PlaySound( level.trackingDroneSettings.sound_explode );
}
self deleteTrackingDrone();
}
}
wait( 0.05 );
}
}
prevent_tracking_drone_in_water( pos )
{
if ( !isDefined( level.water_triggers ) )
{
return false;
}
foreach ( trig in level.water_triggers )
{
if ( IsPointInVolume( pos, trig ) )
{
return true;
}
}
return false;
}