835 lines
23 KiB
Plaintext
835 lines
23 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include common_scripts\utility;
|
|
|
|
EMP_GRENADE_TIME = 3.5;
|
|
|
|
init()
|
|
{
|
|
level.killStreakFuncs[ "littlebird_support" ] = ::tryUseLBSupport;
|
|
|
|
level.heliGuardSettings = [];
|
|
|
|
level.heliGuardSettings[ "littlebird_support" ] = spawnStruct();
|
|
level.heliGuardSettings[ "littlebird_support" ].timeOut = 60.0;
|
|
level.heliGuardSettings[ "littlebird_support" ].health = 999999; // keep it from dying anywhere in code
|
|
level.heliGuardSettings[ "littlebird_support" ].maxHealth = 2000; // this is what we check against for death
|
|
level.heliGuardSettings[ "littlebird_support" ].streakName = "littlebird_support";
|
|
level.heliGuardSettings[ "littlebird_support" ].vehicleInfo = "attack_littlebird_mp";
|
|
level.heliGuardSettings[ "littlebird_support" ].weaponInfo = "littlebird_guard_minigun_mp";
|
|
level.heliGuardSettings[ "littlebird_support" ].weaponModelLeft = "vehicle_little_bird_minigun_left";
|
|
level.heliGuardSettings[ "littlebird_support" ].weaponModelRight = "vehicle_little_bird_minigun_right";
|
|
level.heliGuardSettings[ "littlebird_support" ].weaponTagLeft = "tag_flash";
|
|
level.heliGuardSettings[ "littlebird_support" ].weaponTagRight = "tag_flash_2";
|
|
level.heliGuardSettings[ "littlebird_support" ].sentryMode = "auto_nonai";
|
|
level.heliGuardSettings[ "littlebird_support" ].modelBase = level.littlebird_model;
|
|
level.heliGuardSettings[ "littlebird_support" ].teamSplash = "used_littlebird_support";
|
|
|
|
lbSupport_setAirStartNodes();
|
|
lbSupport_setAirNodeMesh();
|
|
|
|
/#
|
|
SetDevDvarIfUninitialized( "scr_lbguard_timeout", 60.0 );
|
|
#/
|
|
}
|
|
|
|
|
|
tryUseLBSupport( lifeId, streakName ) // self == player
|
|
{
|
|
heliGuardType = "littlebird_support";
|
|
|
|
numIncomingVehicles = 1;
|
|
|
|
if( IsDefined( level.littlebirdGuard ) || maps\mp\killstreaks\_helicopter::exceededMaxLittlebirds( heliGuardType ) )
|
|
{
|
|
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
|
|
return false;
|
|
}
|
|
else if( !level.air_node_mesh.size )
|
|
{
|
|
self IPrintLnBold( &"KILLSTREAKS_UNAVAILABLE_IN_LEVEL" );
|
|
return false;
|
|
}
|
|
else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
|
|
{
|
|
self IPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
|
|
return false;
|
|
}
|
|
|
|
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
|
|
incrementFauxVehicleCount();
|
|
|
|
littleBird = createLBGuard( heliGuardType );
|
|
if ( !IsDefined( littleBird ) )
|
|
{
|
|
// decrement the faux vehicle count since this failed to spawn
|
|
decrementFauxVehicleCount();
|
|
|
|
return false;
|
|
}
|
|
|
|
self thread startLBSupport( littleBird );
|
|
|
|
level thread teamPlayerCardSplash( level.heliGuardSettings[ heliGuardType ].teamSplash, self, self.team );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
createLBGuard( heliGuardType )
|
|
{
|
|
closestStartNode = lbSupport_getClosestStartNode( self.origin );
|
|
if( IsDefined( closestStartNode.angles ) )
|
|
startAng = closestStartNode.angles;
|
|
else
|
|
startAng = ( 0, 0, 0);
|
|
|
|
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
|
|
|
|
closestNode = lbSupport_getClosestNode( self.origin );
|
|
|
|
forward = AnglesToForward( self.angles );
|
|
targetPos = ( closestNode.origin*(1,1,0) ) + ( (0,0,1)*flyHeight ) + ( forward * -100 );
|
|
|
|
startPos = closestStartNode.origin;
|
|
|
|
lb = spawnHelicopter( self, startPos, startAng, level.heliGuardSettings[ heliGuardType ].vehicleInfo, level.heliGuardSettings[ heliGuardType ].modelBase );
|
|
if ( !IsDefined( lb ) )
|
|
return;
|
|
|
|
lb maps\mp\killstreaks\_helicopter::addToLittleBirdList();
|
|
lb thread maps\mp\killstreaks\_helicopter::removeFromLittleBirdListOnDeath();
|
|
|
|
lb.health = level.heliGuardSettings[ heliGuardType ].health;
|
|
lb.maxHealth = level.heliGuardSettings[ heliGuardType ].maxHealth;
|
|
lb.damageTaken = 0; // how much damage has it taken
|
|
|
|
lb.speed = 100;
|
|
lb.followSpeed = 40;
|
|
lb.owner = self;
|
|
lb SetOtherEnt(self);
|
|
lb.team = self.team;
|
|
lb SetMaxPitchRoll( 45, 45 );
|
|
lb Vehicle_SetSpeed( lb.speed, 100, 40 );
|
|
lb SetYawSpeed( 120, 60 );
|
|
lb setneargoalnotifydist( 512 );
|
|
lb.killCount = 0;
|
|
lb.heliType = "littlebird";
|
|
lb.heliGuardType = "littlebird_support";
|
|
lb.targettingRadius = 2000; // matches the maxRange on the turret gdt setting
|
|
//lb ThermalDrawEnable();
|
|
lb make_entity_sentient_mp( lb.team );
|
|
|
|
lb.targetPos = targetPos;
|
|
lb.currentNode = closestNode;
|
|
|
|
mgTurret = SpawnTurret( "misc_turret", lb.origin, level.heliGuardSettings[ heliGuardType ].weaponInfo );
|
|
mgTurret LinkTo( lb, level.heliGuardSettings[ heliGuardType ].weaponTagLeft, (0,0,0), (0,0,0) );
|
|
mgTurret SetModel( level.heliGuardSettings[ heliGuardType ].weaponModelLeft );
|
|
mgTurret.angles = lb.angles;
|
|
mgTurret.owner = lb.owner;
|
|
mgTurret.team = self.team;
|
|
mgTurret makeTurretInoperable();
|
|
mgTurret.vehicle = lb;
|
|
//mgTurret ThermalDrawEnable();
|
|
|
|
lb.mgTurretLeft = mgTurret;
|
|
lb.mgTurretLeft SetDefaultDropPitch( 0 );
|
|
|
|
killCamOrigin = ( lb.origin + ( ( AnglesToForward( lb.angles ) * -100 ) + ( AnglesToRight( lb.angles ) * -100 ) ) ) + ( 0, 0, 50 );
|
|
mgTurret.killCamEnt = Spawn( "script_model", killCamOrigin );
|
|
mgTurret.killCamEnt SetScriptMoverKillCam( "explosive" );
|
|
mgTurret.killCamEnt LinkTo( lb, "tag_origin" );
|
|
|
|
mgTurret = SpawnTurret( "misc_turret", lb.origin, level.heliGuardSettings[ heliGuardType ].weaponInfo );
|
|
mgTurret LinkTo( lb, level.heliGuardSettings[ heliGuardType ].weaponTagRight, (0,0,0), (0,0,0) );
|
|
mgTurret SetModel( level.heliGuardSettings[ heliGuardType ].weaponModelRight );
|
|
mgTurret.angles = lb.angles;
|
|
mgTurret.owner = lb.owner;
|
|
mgTurret.team = self.team;
|
|
mgTurret makeTurretInoperable();
|
|
mgTurret.vehicle = lb;
|
|
lb.mgTurretRight = mgTurret;
|
|
lb.mgTurretRight SetDefaultDropPitch( 0 );
|
|
|
|
killCamOrigin = ( lb.origin + ( ( AnglesToForward( lb.angles ) * -100 ) + ( AnglesToRight( lb.angles ) * 100 ) ) ) + ( 0, 0, 50 );
|
|
mgTurret.killCamEnt = Spawn( "script_model", killCamOrigin );
|
|
mgTurret.killCamEnt SetScriptMoverKillCam( "explosive" );
|
|
mgTurret.killCamEnt LinkTo( lb, "tag_origin" );
|
|
|
|
if ( level.teamBased )
|
|
{
|
|
lb.mgTurretLeft setTurretTeam( self.team );
|
|
lb.mgTurretRight setTurretTeam( self.team );
|
|
}
|
|
|
|
lb.mgTurretLeft SetMode( level.heliGuardSettings[ heliGuardType ].sentryMode );
|
|
lb.mgTurretRight SetMode( level.heliGuardSettings[ heliGuardType ].sentryMode );
|
|
|
|
lb.mgTurretLeft SetSentryOwner( self );
|
|
lb.mgTurretRight SetSentryOwner( self );
|
|
|
|
lb.mgTurretLeft thread lbSupport_attackTargets();
|
|
lb.mgTurretRight thread lbSupport_attackTargets();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lb.attract_strength = 10000;
|
|
lb.attract_range = 150;
|
|
lb.attractor = Missile_CreateAttractorEnt( lb, lb.attract_strength, lb.attract_range );
|
|
|
|
lb.hasDodged = false;
|
|
lb.empGrenaded = false;
|
|
|
|
lb thread lbSupport_handleDamage();
|
|
lb thread lbSupport_watchDeath();
|
|
lb thread lbSupport_watchTimeout();
|
|
lb thread lbSupport_watchOwnerLoss();
|
|
lb thread lbSupport_watchOwnerDamage();
|
|
lb thread lbSupport_watchRoundEnd();
|
|
lb thread lbSupport_lightFX();
|
|
|
|
level.littlebirdGuard = lb;
|
|
|
|
lb.owner maps\mp\_matchdata::logKillstreakEvent( level.heliGuardSettings[ lb.heliGuardType ].streakName, lb.targetPos );
|
|
|
|
return lb;
|
|
}
|
|
|
|
lbSupport_lightFX()
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_nose" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_belly" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail1" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail2" );
|
|
}
|
|
|
|
startLBSupport( littleBird ) // self == player
|
|
{
|
|
level endon( "game_ended" );
|
|
littleBird endon( "death" );
|
|
|
|
// look at the player
|
|
littleBird SetLookAtEnt( self );
|
|
|
|
// go to pos
|
|
littleBird setVehGoalPos( littleBird.targetPos );
|
|
littleBird waittill( "near_goal" );
|
|
littleBird Vehicle_SetSpeed( littleBird.speed, 60, 30 );
|
|
littleBird waittill ( "goal" );
|
|
|
|
// drop to target
|
|
littleBird setVehGoalPos( littleBird.currentNode.origin, 1 );
|
|
littleBird waittill ( "goal" );
|
|
|
|
// begin following player
|
|
littleBird thread lbSupport_followPlayer();
|
|
|
|
// dodge the first sam or lock-on attack
|
|
littleBird thread maps\mp\killstreaks\_flares::flares_handleIncomingSAM( ::lbSupport_watchSAMProximity );
|
|
littleBird thread maps\mp\killstreaks\_flares::flares_handleIncomingStinger( ::lbSupport_watchStingerProximity );
|
|
}
|
|
|
|
|
|
lbSupport_followPlayer() // self == lb
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
|
|
if( !IsDefined( self.owner ) )
|
|
{
|
|
self thread lbSupport_leave();
|
|
return;
|
|
}
|
|
|
|
self.owner endon( "disconnect" );
|
|
self endon( "owner_gone" );
|
|
|
|
self Vehicle_SetSpeed( self.followSpeed, 20, 20 );
|
|
while( true )
|
|
{
|
|
if( IsDefined( self.owner ) && IsAlive( self.owner ) )
|
|
{
|
|
currentNode = lbSupport_getClosestLinkedNode( self.owner.origin );
|
|
if( IsDefined( currentNode ) && currentNode != self.currentNode )
|
|
{
|
|
self.currentNode = currentNode;
|
|
// don't thread because we want to waittill goal before we pick the next node
|
|
lbSupport_moveToPlayer();
|
|
continue;
|
|
}
|
|
}
|
|
wait( 1 );
|
|
}
|
|
}
|
|
|
|
|
|
lbSupport_moveToPlayer() // self == lb
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
self.owner endon( "death" );
|
|
self.owner endon( "disconnect" );
|
|
self endon( "owner_gone" );
|
|
|
|
self notify( "lbSupport_moveToPlayer" );
|
|
self endon( "lbSupport_moveToPlayer" );
|
|
|
|
self.inTransit = true;
|
|
self setVehGoalPos( self.currentNode.origin + ( 0, 0, 100 ), 1 );
|
|
self waittill ( "goal" );
|
|
self.inTransit = false;
|
|
self notify( "hit_goal" );
|
|
}
|
|
|
|
|
|
//
|
|
// state trackers
|
|
//
|
|
|
|
|
|
lbSupport_watchDeath()
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "gone" );
|
|
|
|
self waittill( "death" );
|
|
|
|
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
|
|
}
|
|
|
|
|
|
lbSupport_watchTimeout()
|
|
{
|
|
level endon ( "game_ended" );
|
|
self endon( "death" );
|
|
self.owner endon( "disconnect" );
|
|
self endon( "owner_gone" );
|
|
|
|
timeout = level.heliGuardSettings[ self.heliGuardType ].timeOut;
|
|
/#
|
|
timeout = GetDvarFloat( "scr_lbguard_timeout" );
|
|
#/
|
|
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeout );
|
|
|
|
self thread lbSupport_leave();
|
|
}
|
|
|
|
|
|
lbSupport_watchOwnerLoss()
|
|
{
|
|
level endon ( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
|
|
self.owner waittill( "killstreak_disowned" );
|
|
|
|
self notify( "owner_gone" );
|
|
|
|
// leave
|
|
self thread lbSupport_leave();
|
|
}
|
|
|
|
lbSupport_watchOwnerDamage() // self == lb
|
|
{
|
|
level endon ( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
self.owner endon( "disconnect" );
|
|
self endon( "owner_gone" );
|
|
|
|
while( true )
|
|
{
|
|
// if someone is attacking the owner, attack them
|
|
self.owner waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
|
|
|
|
if( isPlayer( attacker ) )
|
|
{
|
|
if( attacker != self.owner &&
|
|
Distance2D( attacker.origin, self.origin ) <= self.targettingRadius &&
|
|
!attacker _hasPerk( "specialty_blindeye" ) &&
|
|
!( level.hardcoreMode && level.teamBased && attacker.team == self.team ) )
|
|
{
|
|
self SetLookAtEnt( attacker );
|
|
if( IsDefined( self.mgTurretLeft ) )
|
|
self.mgTurretLeft SetTargetEntity( attacker );
|
|
if( IsDefined( self.mgTurretRight ) )
|
|
self.mgTurretRight SetTargetEntity( attacker );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lbSupport_watchRoundEnd()
|
|
{
|
|
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 lbSupport_leave();
|
|
}
|
|
|
|
lbSupport_leave()
|
|
{
|
|
self endon( "death" );
|
|
self notify( "leaving" );
|
|
level.littlebirdGuard = undefined;
|
|
|
|
self ClearLookAtEnt();
|
|
|
|
// rise
|
|
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
|
|
targetPos = self.origin + (0,0,flyHeight);
|
|
self Vehicle_SetSpeed( self.speed, 60 );
|
|
self SetMaxPitchRoll( 45, 180 );
|
|
self setVehGoalPos( targetPos );
|
|
self waittill ( "goal" );
|
|
|
|
// leave
|
|
targetPos = targetPos + AnglesToForward( self.angles ) * 15000;
|
|
// make sure it doesn't fly away backwards
|
|
endEnt = Spawn( "script_origin", targetPos );
|
|
if( IsDefined( endEnt ) )
|
|
{
|
|
self SetLookAtEnt( endEnt );
|
|
endEnt thread wait_and_delete( 3.0 );
|
|
}
|
|
self setVehGoalPos( targetPos );
|
|
self waittill ( "goal" );
|
|
|
|
// remove
|
|
self notify( "gone" );
|
|
self maps\mp\killstreaks\_helicopter::removeLittlebird();
|
|
}
|
|
|
|
wait_and_delete( waitTime )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
wait( waitTime );
|
|
self delete();
|
|
}
|
|
|
|
//
|
|
// Damage, death, and destruction
|
|
//
|
|
|
|
lbSupport_handleDamage() // self == lb
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
self SetCanDamage( true );
|
|
|
|
while( true )
|
|
{
|
|
self waittill( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, iDFlags, weapon );
|
|
|
|
// don't allow people to destroy things on their team if FF is off
|
|
if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
|
|
continue;
|
|
|
|
if ( !IsDefined( self ) )
|
|
return;
|
|
|
|
if ( IsDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
|
|
self.wasDamagedFromBulletPenetration = true;
|
|
|
|
self.wasDamaged = true;
|
|
|
|
modifiedDamage = damage;
|
|
|
|
if( IsPlayer( attacker ) )
|
|
{
|
|
// attack the attacker
|
|
if( attacker != self.owner &&
|
|
Distance2D( attacker.origin, self.origin ) <= self.targettingRadius &&
|
|
!attacker _hasPerk( "specialty_blindeye" ) &&
|
|
!( level.hardcoreMode && level.teamBased && attacker.team == self.team ) )
|
|
{
|
|
self SetLookAtEnt( attacker );
|
|
if( IsDefined( self.mgTurretLeft ) )
|
|
self.mgTurretLeft SetTargetEntity( attacker );
|
|
if( IsDefined( self.mgTurretRight ) )
|
|
self.mgTurretRight SetTargetEntity( attacker );
|
|
}
|
|
|
|
attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
|
|
|
|
if( meansOfDeath == "MOD_RIFLE_BULLET" || meansOfDeath == "MOD_PISTOL_BULLET" )
|
|
{
|
|
if ( attacker _hasPerk( "specialty_armorpiercing" ) )
|
|
modifiedDamage += damage * level.armorPiercingMod;
|
|
}
|
|
}
|
|
|
|
// in case we are shooting from a remote position, like being in the osprey gunner shooting this
|
|
if( IsDefined( attacker.owner ) && IsPlayer( attacker.owner ) )
|
|
{
|
|
attacker.owner maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "helicopter" );
|
|
}
|
|
|
|
if( IsDefined( weapon ) )
|
|
{
|
|
switch( weapon )
|
|
{
|
|
case "ac130_105mm_mp":
|
|
case "ac130_40mm_mp":
|
|
case "stinger_mp":
|
|
case "javelin_mp":
|
|
case "remote_mortar_missile_mp":
|
|
case "remotemissile_projectile_mp":
|
|
self.largeProjectileDamage = true;
|
|
modifiedDamage = self.maxHealth + 1;
|
|
break;
|
|
|
|
case "sam_projectile_mp":
|
|
self.largeProjectileDamage = true;
|
|
modifiedDamage = self.maxHealth * 0.25; // takes about 1 burst of sam rockets
|
|
break;
|
|
|
|
case "emp_grenade_mp":
|
|
// NOTE: don't destroy helicopters with an emp grenade, it didn't look good in practice
|
|
modifiedDamage = 0;
|
|
self thread lbSupport_EMPGrenaded();
|
|
break;
|
|
|
|
case "osprey_player_minigun_mp":
|
|
self.largeProjectileDamage = false;
|
|
modifiedDamage *= 2; // since it's a larger caliber, make it hurt
|
|
break;
|
|
}
|
|
|
|
maps\mp\killstreaks\_killstreaks::killstreakHit( attacker, weapon, self );
|
|
}
|
|
|
|
self.damageTaken += modifiedDamage;
|
|
|
|
if ( self.damageTaken >= self.maxHealth )
|
|
{
|
|
if ( isPlayer( attacker ) && ( !IsDefined( self.owner ) || attacker != self.owner ) )
|
|
{
|
|
attacker notify( "destroyed_helicopter" );
|
|
attacker notify( "destroyed_killstreak", weapon );
|
|
thread teamPlayerCardSplash( "callout_destroyed_little_bird", attacker );
|
|
attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", 300, weapon, meansOfDeath );
|
|
attacker thread maps\mp\gametypes\_rank::xpEventPopup( "destroyed_little_bird" );
|
|
thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, attacker, damage, meansOfDeath, weapon );
|
|
}
|
|
|
|
if( IsDefined( self.owner ) )
|
|
self.owner thread leaderDialogOnPlayer( "lbguard_destroyed" );
|
|
|
|
self notify ( "death" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
lbSupport_EMPGrenaded() // self == vehicle
|
|
{
|
|
self notify( "lbSupport_EMPGrenaded" );
|
|
self endon( "lbSupport_EMPGrenaded" );
|
|
|
|
self endon( "death" );
|
|
self.owner endon( "disconnect" );
|
|
level endon( "game_ended" );
|
|
|
|
self.empGrenaded = true;
|
|
if( IsDefined( self.mgTurretRight ) )
|
|
self.mgTurretRight notify( "stop_shooting" );
|
|
if( IsDefined( self.mgTurretLeft ) )
|
|
self.mgTurretLeft notify( "stop_shooting" );
|
|
// TU0: using the ims fx and not creating its own because we've already locked down ConfigStrings and it will shift them if we load fx in this file
|
|
if( IsDefined( level._effect[ "ims_sensor_explode" ] ) )
|
|
{
|
|
if( IsDefined( self.mgTurretRight ) )
|
|
PlayFXOnTag( getfx( "ims_sensor_explode" ), self.mgTurretRight, "tag_aim" );
|
|
if( IsDefined( self.mgTurretLeft ) )
|
|
PlayFXOnTag( getfx( "ims_sensor_explode" ), self.mgTurretLeft, "tag_aim" );
|
|
}
|
|
|
|
wait( EMP_GRENADE_TIME );
|
|
|
|
self.empGrenaded = false;
|
|
if( IsDefined( self.mgTurretRight ) )
|
|
self.mgTurretRight notify( "turretstatechange" );
|
|
if( IsDefined( self.mgTurretLeft ) )
|
|
self.mgTurretLeft notify( "turretstatechange" );
|
|
}
|
|
|
|
// this is different from the flares monitoring that the other helicopters do
|
|
// we want this to dodge the missiles if at all possible, making for a cool evasive manuever
|
|
lbSupport_watchSAMProximity( player, missileTeam, missileTarget, missileGroup ) // self == level
|
|
{
|
|
level endon ( "game_ended" );
|
|
missileTarget endon( "death" );
|
|
|
|
for( i = 0; i < missileGroup.size; i++ )
|
|
{
|
|
if( IsDefined( missileGroup[ i ] ) && !missileTarget.hasDodged )
|
|
{
|
|
missileTarget.hasDodged = true;
|
|
|
|
newTarget = spawn( "script_origin", missileTarget.origin );
|
|
newTarget.angles = missileTarget.angles;
|
|
newTarget MoveGravity( AnglesToRight( missileGroup[ i ].angles ) * -1000, 0.05 );
|
|
newTarget thread maps\mp\killstreaks\_flares::flares_deleteAfterTime( 5.0 );
|
|
|
|
for( j = 0; j < missileGroup.size; j++ )
|
|
{
|
|
if( IsDefined( missileGroup[ j ] ) )
|
|
{
|
|
missileGroup[ j ] Missile_SetTargetEnt( newTarget );
|
|
}
|
|
}
|
|
|
|
// dodge the incoming missiles
|
|
dodgePoint = missileTarget.origin + ( AnglesToRight( missileGroup[ i ].angles ) * 200 );
|
|
missileTarget Vehicle_SetSpeed( missileTarget.speed, 100, 40 );
|
|
missileTarget SetVehGoalPos( dodgePoint, true );
|
|
|
|
wait( 2.0 );
|
|
missileTarget Vehicle_SetSpeed( missileTarget.followSpeed, 20, 20 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is different from the flares monitoring that the other helicopters do
|
|
// we want this to dodge the missiles if at all possible, making for a cool evasive manuever
|
|
lbSupport_watchStingerProximity( player, missileTeam, missileTarget ) // self == missile
|
|
{
|
|
level endon ( "game_ended" );
|
|
missileTarget endon( "death" );
|
|
|
|
if( IsDefined( self ) && !missileTarget.hasDodged )
|
|
{
|
|
missileTarget.hasDodged = true;
|
|
|
|
newTarget = spawn( "script_origin", missileTarget.origin );
|
|
newTarget.angles = missileTarget.angles;
|
|
newTarget MoveGravity( AnglesToRight( self.angles ) * -1000, 0.05 );
|
|
newTarget thread maps\mp\killstreaks\_flares::flares_deleteAfterTime( 5.0 );
|
|
|
|
self Missile_SetTargetEnt( newTarget );
|
|
|
|
// dodge the incoming missiles
|
|
dodgePoint = missileTarget.origin + ( AnglesToRight( self.angles ) * 200 );
|
|
missileTarget Vehicle_SetSpeed( missileTarget.speed, 100, 40 );
|
|
missileTarget SetVehGoalPos( dodgePoint, true );
|
|
|
|
wait( 2.0 );
|
|
missileTarget Vehicle_SetSpeed( missileTarget.followSpeed, 20, 20 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// node funcs
|
|
//
|
|
|
|
lbSupport_getClosestStartNode( pos )
|
|
{
|
|
// gets the start node that is closest to the position passed in
|
|
closestNode = undefined;
|
|
closestDistance = 999999;
|
|
|
|
foreach( loc in level.air_start_nodes )
|
|
{
|
|
nodeDistance = distance( loc.origin, pos );
|
|
if ( nodeDistance < closestDistance )
|
|
{
|
|
closestNode = loc;
|
|
closestDistance = nodeDistance;
|
|
}
|
|
}
|
|
|
|
return closestNode;
|
|
}
|
|
|
|
lbSupport_getClosestNode( pos )
|
|
{
|
|
// gets the closest node to the position passed in, regardless of link
|
|
closestNode = undefined;
|
|
closestDistance = 999999;
|
|
|
|
foreach( loc in level.air_node_mesh )
|
|
{
|
|
nodeDistance = distance( loc.origin, pos );
|
|
if ( nodeDistance < closestDistance )
|
|
{
|
|
closestNode = loc;
|
|
closestDistance = nodeDistance;
|
|
}
|
|
}
|
|
|
|
return closestNode;
|
|
}
|
|
|
|
lbSupport_getClosestLinkedNode( pos ) // self == lb
|
|
{
|
|
// gets the linked node that is closest to the current position and moving towards the position passed in
|
|
closestNode = undefined;
|
|
totalDistance = Distance2D( self.currentNode.origin, pos );
|
|
closestDistance = totalDistance;
|
|
|
|
// loop through each neighbor and find the closest to the final goal
|
|
foreach( loc in self.currentNode.neighbors )
|
|
{
|
|
nodeDistance = Distance2D( loc.origin, pos );
|
|
if ( nodeDistance < totalDistance && nodeDistance < closestDistance )
|
|
{
|
|
closestNode = loc;
|
|
closestDistance = nodeDistance;
|
|
}
|
|
}
|
|
|
|
return closestNode;
|
|
}
|
|
|
|
lbSupport_arrayContains( array, compare )
|
|
{
|
|
if ( array.size <= 0 )
|
|
return false;
|
|
|
|
foreach ( member in array )
|
|
{
|
|
if ( member == compare )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
lbSupport_getLinkedStructs()
|
|
{
|
|
array = [];
|
|
|
|
if ( IsDefined( self.script_linkTo ) )
|
|
{
|
|
linknames = get_links();
|
|
for ( i = 0; i < linknames.size; i++ )
|
|
{
|
|
ent = getstruct( linknames[ i ], "script_linkname" );
|
|
if ( IsDefined( ent ) )
|
|
{
|
|
array[ array.size ] = ent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
lbSupport_setAirStartNodes()
|
|
{
|
|
level.air_start_nodes = getstructarray( "chopper_boss_path_start", "targetname" );
|
|
|
|
foreach( loc in level.air_start_nodes )
|
|
{
|
|
// Grab array of path loc refs that this loc links to
|
|
loc.neighbors = loc lbSupport_getLinkedStructs();
|
|
}
|
|
}
|
|
|
|
lbSupport_setAirNodeMesh()
|
|
{
|
|
level.air_node_mesh = getstructarray( "so_chopper_boss_path_struct", "script_noteworthy" );
|
|
|
|
foreach( loc in level.air_node_mesh )
|
|
{
|
|
// Grab array of path loc refs that this loc links to
|
|
loc.neighbors = loc lbSupport_getLinkedStructs();
|
|
|
|
// Step through each loc in the map and if it
|
|
// links to this loc, add it
|
|
foreach( other_loc in level.air_node_mesh )
|
|
{
|
|
if ( loc == other_loc )
|
|
continue;
|
|
|
|
if ( !lbSupport_arrayContains( loc.neighbors, other_loc ) && lbSupport_arrayContains( other_loc lbSupport_getLinkedStructs(), loc ) )
|
|
loc.neighbors[ loc.neighbors.size ] = other_loc;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ============================
|
|
Turret Logic Functions
|
|
============================ */
|
|
|
|
lbSupport_attackTargets() // self == turret
|
|
{
|
|
self.vehicle endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill( "turretstatechange" );
|
|
|
|
if ( self IsFiringTurret() && !self.vehicle.empGrenaded )
|
|
self thread lbSupport_burstFireStart();
|
|
else
|
|
self thread lbSupport_burstFireStop();
|
|
}
|
|
}
|
|
|
|
lbSupport_burstFireStart() // self == turret
|
|
{
|
|
self.vehicle endon( "death" );
|
|
self.vehicle endon( "leaving" );
|
|
self endon( "stop_shooting" );
|
|
level endon( "game_ended" );
|
|
|
|
fireTime = 0.1;
|
|
minShots = 40;
|
|
maxShots = 80;
|
|
minPause = 1.0;
|
|
maxPause = 2.0;
|
|
|
|
for ( ;; )
|
|
{
|
|
numShots = RandomIntRange( minShots, maxShots + 1 );
|
|
|
|
for ( i = 0; i < numShots; i++ )
|
|
{
|
|
targetEnt = self GetTurretTarget( false );
|
|
if( IsDefined( targetEnt ) &&
|
|
( !IsDefined( targetEnt.spawntime ) || ( gettime() - targetEnt.spawntime )/1000 > 5 ) &&
|
|
( IsDefined( targetEnt.team ) && targetEnt.team != "spectator" ) &&
|
|
isReallyAlive( targetEnt ) )
|
|
{
|
|
self.vehicle SetLookAtEnt( targetEnt );
|
|
self ShootTurret();
|
|
}
|
|
|
|
wait ( fireTime );
|
|
}
|
|
|
|
wait ( RandomFloatRange( minPause, maxPause ) );
|
|
}
|
|
}
|
|
|
|
lbSupport_burstFireStop() // self == turret
|
|
{
|
|
self notify( "stop_shooting" );
|
|
if( IsDefined( self.vehicle.owner ) )
|
|
self.vehicle SetLookAtEnt( self.vehicle.owner );
|
|
}
|
|
|
|
/* ============================
|
|
END Turret Logic Functions
|
|
============================ */
|