diff --git a/userraw/maps/mp/killstreaks/_helicopter.gsc b/userraw/maps/mp/killstreaks/_helicopter.gsc index ad1a02b..7651777 100644 --- a/userraw/maps/mp/killstreaks/_helicopter.gsc +++ b/userraw/maps/mp/killstreaks/_helicopter.gsc @@ -1,2187 +1,2187 @@ -#include maps\mp\_utility; -#include maps\mp\gametypes\_hud_util; -#include common_scripts\utility; - - -init() -{ - path_start = getentarray( "heli_start", "targetname" ); // start pointers, point to the actual start node on path - loop_start = getentarray( "heli_loop_start", "targetname" ); // start pointers for loop path in the map - - if ( !path_start.size && !loop_start.size) - return; - - level.heli_types = []; - - precacheHelicopter( "vehicle_cobra_helicopter_fly_low", "cobra" ); - precacheHelicopter( "vehicle_mi24p_hind_mp", "hind" ); - precacheHelicopter( "vehicle_mi-28_mp", "mi28" ); - precacheHelicopter( "vehicle_apache_mp", "apache" ); - precacheHelicopter( "vehicle_pavelow", "pavelow" ); - precacheHelicopter( "vehicle_pavelow_opfor", "pavelow" ); - precacheHelicopter( "vehicle_little_bird_armed", "cobra" ); - - precacheitem( "cobra_FFAR_mp" ); - precacheitem( "cobra_20mm_mp" ); - precacheitem( "cobra_player_minigun_mp" ); - precacheitem( "heli_remote_mp" ); - precacheVehicle( "cobra_mp" ); - precacheVehicle( "cobra_minigun_mp" ); - precacheVehicle( "pavelow_mp" ); - precacheTurret( "pavelow_minigun_mp" ); - precacheString( &"MP_CIVILIAN_AIR_TRAFFIC" ); - - level.chopper = undefined; - - // array of paths, each element is an array of start nodes that all leads to a single destination node - level.heli_start_nodes = getEntArray( "heli_start", "targetname" ); - assertEx( level.heli_start_nodes.size, "No \"heli_start\" nodes found in map!" ); - - level.heli_loop_nodes = getEntArray( "heli_loop_start", "targetname" ); - assertEx( level.heli_loop_nodes.size, "No \"heli_loop_start\" nodes found in map!" ); - - level.heli_leave_nodes = getEntArray( "heli_leave", "targetname" ); - assertEx( level.heli_leave_nodes.size, "No \"heli_leave\" nodes found in map!" ); - - level.heli_crash_nodes = getEntArray( "heli_crash_start", "targetname" ); - assertEx( level.heli_crash_nodes.size, "No \"heli_crash_start\" nodes found in map!" ); - - level.heli_missile_rof = 5; // missile rate of fire, one every this many seconds per target, could fire two at the same time to different targets - level.heli_maxhealth = 1500; // max health of the helicopter - level.heli_debug = 0; // debug mode, draws debugging info on screen - - level.heli_targeting_delay = 0.5; // targeting delay - level.heli_turretReloadTime = 1.5; // mini-gun reload time - level.heli_turretClipSize = 40; // mini-gun clip size, rounds before reload - level.heli_visual_range = 3500; // distance radius helicopter will acquire targets (see) - - level.heli_target_spawnprotection = 5; // players are this many seconds safe from helicopter after spawn - level.heli_target_recognition = 0.5; // percentage of the player's body the helicopter sees before it labels him as a target - level.heli_missile_friendlycare = 256; // if friendly is within this distance of the target, do not shoot missile - level.heli_missile_target_cone = 0.3; // dot product of vector target to helicopter forward, 0.5 is in 90 range, bigger the number, smaller the cone - level.heli_armor_bulletdamage = 0.3; // damage multiplier to bullets onto helicopter's armor - - level.heli_attract_strength = 1000; - level.heli_attract_range = 4096; - - level.heli_angle_offset = 90; - level.heli_forced_wait = 0; - - // helicopter fx - level.chopper_fx["explode"]["death"] = []; - level.chopper_fx["explode"]["large"] = loadfx ("explosions/helicopter_explosion_secondary_small"); - level.chopper_fx["explode"]["medium"] = loadfx ("explosions/aerial_explosion"); - level.chopper_fx["smoke"]["trail"] = loadfx ("smoke/smoke_trail_white_heli"); - level.chopper_fx["fire"]["trail"]["medium"] = loadfx ("fire/fire_smoke_trail_L_emitter"); - level.chopper_fx["fire"]["trail"]["large"] = loadfx ("fire/fire_smoke_trail_L"); - - level.chopper_fx["damage"]["light_smoke"] = loadfx ("smoke/smoke_trail_white_heli_emitter"); - level.chopper_fx["damage"]["heavy_smoke"] = loadfx ("smoke/smoke_trail_black_heli_emitter"); - level.chopper_fx["damage"]["on_fire"] = loadfx ("fire/fire_smoke_trail_L_emitter"); - - level.chopper_fx["light"]["left"] = loadfx( "misc/aircraft_light_wingtip_green" ); - level.chopper_fx["light"]["right"] = loadfx( "misc/aircraft_light_wingtip_red" ); - level.chopper_fx["light"]["belly"] = loadfx( "misc/aircraft_light_red_blink" ); - level.chopper_fx["light"]["tail"] = loadfx( "misc/aircraft_light_white_blink" ); - - level.fx_heli_dust = loadfx ("treadfx/heli_dust_default"); - level.fx_heli_water = loadfx ("treadfx/heli_water"); - - makeHeliType( "cobra", "explosions/helicopter_explosion_cobra_low", ::defaultLightFX ); - addAirExplosion( "cobra", "explosions/aerial_explosion_cobra_low_mp" ); - - makeHeliType( "pavelow", "explosions/helicopter_explosion_pavelow", ::pavelowLightFx ); - addAirExplosion( "pavelow", "explosions/aerial_explosion_pavelow_mp" ); - - makeHeliType( "mi28", "explosions/helicopter_explosion_mi28_flying", ::defaultLightFX ); - addAirExplosion( "mi28", "explosions/aerial_explosion_mi28_flying_mp" ); - - makeHeliType( "hind", "explosions/helicopter_explosion_hind_chernobyl", ::defaultLightFX ); - addAirExplosion( "hind", "explosions/aerial_explosion_hind_chernobyl_mp" ); - - makeHeliType( "apache", "explosions/helicopter_explosion_apache", ::defaultLightFX ); - addAirExplosion( "apache", "explosions/aerial_explosion_apache_mp" ); - - makeHeliType( "littlebird", "explosions/aerial_explosion_littlebird_mp", ::defaultLightFX ); - addAirExplosion( "littlebird", "explosions/aerial_explosion_littlebird_mp" ); - - //makeHeliType( "harrier", "explosions/harrier_exposion_ground", ::defaultLightFX ); - - - level.killstreakFuncs["helicopter"] = ::useHelicopter; - level.killstreakFuncs["helicopter_blackbox"] = ::useHelicopterBlackbox; - level.killstreakFuncs["helicopter_flares"] = ::useHelicopterFlares; - level.killstreakFuncs["helicopter_minigun"] = ::useHelicopterMinigun; - level.killstreakFuncs["helicopter_mk19"] = ::useHelicopterMK19; - - level.heliDialog["tracking"][0] = "ac130_fco_moreenemy"; - level.heliDialog["tracking"][1] = "ac130_fco_getthatguy"; - level.heliDialog["tracking"][2] = "ac130_fco_guyrunnin"; - level.heliDialog["tracking"][3] = "ac130_fco_gotarunner"; - level.heliDialog["tracking"][4] = "ac130_fco_personnelthere"; - level.heliDialog["tracking"][5] = "ac130_fco_rightthere"; - level.heliDialog["tracking"][6] = "ac130_fco_tracking"; - - level.heliDialog["locked"][0] = "ac130_fco_lightemup"; - level.heliDialog["locked"][1] = "ac130_fco_takehimout"; - level.heliDialog["locked"][2] = "ac130_fco_nailthoseguys"; - - level.lastHeliDialogTime = 0; - - queueCreate( "helicopter" ); -} - - -makeHeliType( heliType, deathFx, lightFXFunc ) -{ - level.chopper_fx["explode"]["death"][ heliType ] = loadFx( deathFX ); - level.lightFxFunc[ heliType ] = lightFXFunc; -} - -addAirExplosion( heliType, explodeFx ) -{ - level.chopper_fx["explode"]["air_death"][ heliType ] = loadFx( explodeFx ); -} - - -pavelowLightFX() -{ - playFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_L_wing1" ); - wait ( 0.05 ); - playFXOnTag( level.chopper_fx["light"]["right"], self, "tag_light_R_wing1" ); - 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_tail" ); - wait ( 0.05 ); - playFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail2" ); - wait ( 0.05 ); - playFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_cockpit01" ); -} - - -defaultLightFX() -{ - playFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_L_wing" ); - wait ( 0.05 ); - playFXOnTag( level.chopper_fx["light"]["right"], self, "tag_light_R_wing" ); - 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_tail" ); -} - - -useHelicopter( lifeId ) -{ - return tryUseHelicopter( lifeId ); -} - -useHelicopterBlackbox( lifeId ) -{ - return tryUseHelicopter( lifeId, "blackbox" ); -} - -useHelicopterFlares( lifeId ) -{ - return tryUseHelicopter( lifeId, "flares" ); -} - - -useHelicopterMinigun( lifeId ) -{ - if ( isDefined( self.lastStand ) && !self _hasPerk( "specialty_finalstand" ) ) - { - self iPrintLnBold( &"MP_UNAVILABLE_IN_LASTSTAND" ); - return false; - } - - return tryUseHelicopter( lifeId, "minigun" ); -} - - -useHelicopterMK19( lifeId ) -{ - if ( isDefined( self.lastStand ) && !self _hasPerk( "specialty_finalstand" ) ) - { - self iPrintLnBold( &"MP_UNAVILABLE_IN_LASTSTAND" ); - return false; - } - return tryUseHelicopter( lifeId, "mk19" ); -} - - -tryUseHelicopter( lifeId, heliType ) -{ - if ( isDefined( level.civilianJetFlyBy ) ) - { - self iPrintLnBold( &"MP_CIVILIAN_AIR_TRAFFIC" ); - return false; - } - - if ( (!isDefined( heliType ) || heliType == "flares") && isDefined( level.chopper ) ) - { - self iPrintLnBold( &"MP_HELI_IN_QUEUE" ); - - if ( isDefined( heliType ) ) - streakName = "helicopter_" + heliType; - else - streakName = "helicopter"; - - self maps\mp\killstreaks\_killstreaks::shuffleKillStreaksFILO( streakName ); - self maps\mp\killstreaks\_killstreaks::giveOwnedKillstreakItem(); - - queueEnt = spawn( "script_origin", (0,0,0) ); - queueEnt hide(); - queueEnt thread deleteOnEntNotify( self, "disconnect" ); - queueEnt.player = self; - queueEnt.lifeId = lifeId; - queueEnt.heliType = heliType; - queueEnt.streakName = streakName; - - queueAdd( "helicopter", queueEnt ); - - return false; - } - else if ( isDefined( level.chopper ) ) - { - self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" ); - return false; - } - - if ( isDefined( heliType ) && heliType == "minigun" ) - { - self setUsingRemote( "helicopter_" + heliType ); - result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak(); - - if ( result != "success" ) - { - if ( result != "disconnect" ) - self clearUsingRemote(); - - return false; - } - - if ( isDefined( level.chopper ) ) - { - self clearUsingRemote(); - self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" ); - return false; - } - } - - - self startHelicopter( lifeId, heliType ); - return true; -} - - -deleteOnEntNotify( ent, notifyString ) -{ - self endon ( "death" ); - ent waittill ( notifyString ); - - self delete(); -} - - -startHelicopter( lifeId, heliType ) -{ - if ( !isDefined( heliType ) ) - heliType = ""; - - switch ( heliType ) - { - case "flares": - eventType = "helicopter_flares"; - break; - case "minigun": - eventType = "helicopter_minigun"; - break; - default: - eventType = "helicopter"; - break; - } - - team = self.pers["team"]; - - startNode = level.heli_start_nodes[ randomInt( level.heli_start_nodes.size ) ]; - - self maps\mp\_matchdata::logKillstreakEvent( eventType, self.origin ); - - thread heli_think( lifeId, self, startnode, self.pers["team"], heliType ); -} - - -precacheHelicopter( model, heliType ) -{ - deathfx = loadfx ("explosions/tanker_explosion"); - - precacheModel( model ); - - level.heli_types[model] = heliType; - - /******************************************************/ - /* SETUP WEAPON TAGS */ - /******************************************************/ - - level.cobra_missile_models = []; - level.cobra_missile_models["cobra_Hellfire"] = "projectile_hellfire_missile"; - - precachemodel( level.cobra_missile_models["cobra_Hellfire"] ); - - // helicopter sounds: - level.heli_sound["allies"]["hit"] = "cobra_helicopter_hit"; - level.heli_sound["allies"]["hitsecondary"] = "cobra_helicopter_secondary_exp"; - level.heli_sound["allies"]["damaged"] = "cobra_helicopter_damaged"; - level.heli_sound["allies"]["spinloop"] = "cobra_helicopter_dying_loop"; - level.heli_sound["allies"]["spinstart"] = "cobra_helicopter_dying_layer"; - level.heli_sound["allies"]["crash"] = "cobra_helicopter_crash"; - level.heli_sound["allies"]["missilefire"] = "weap_cobra_missile_fire"; - level.heli_sound["axis"]["hit"] = "cobra_helicopter_hit"; - level.heli_sound["axis"]["hitsecondary"] = "cobra_helicopter_secondary_exp"; - level.heli_sound["axis"]["damaged"] = "cobra_helicopter_damaged"; - level.heli_sound["axis"]["spinloop"] = "cobra_helicopter_dying_loop"; - level.heli_sound["axis"]["spinstart"] = "cobra_helicopter_dying_layer"; - level.heli_sound["axis"]["crash"] = "cobra_helicopter_crash"; - level.heli_sound["axis"]["missilefire"] = "weap_cobra_missile_fire"; -} - - -spawn_helicopter( owner, origin, angles, vehicleType, modelName ) -{ - chopper = spawnHelicopter( owner, origin, angles, vehicleType, modelName ); - - if ( !isDefined( chopper ) ) - return undefined; - - chopper.heli_type = level.heli_types[ modelName ]; - - chopper thread [[ level.lightFxFunc[ chopper.heli_type ] ]](); - - chopper addToHeliList(); - - chopper.zOffset = (0,0,chopper getTagOrigin( "tag_origin" )[2] - chopper getTagOrigin( "tag_ground" )[2]); - chopper.attractor = Missile_CreateAttractorEnt( chopper, level.heli_attract_strength, level.heli_attract_range ); - - chopper.damageCallback = ::Callback_VehicleDamage; - - return chopper; -} - - -heliRide( lifeId, chopper ) -{ - self endon ( "disconnect" ); - chopper endon ( "helicopter_done" ); - - thread teamPlayerCardSplash( "used_helicopter_minigun", self ); - self VisionSetThermalForPlayer( "black_bw", 0 ); - //self RemoteCameraSoundscapeOn(); - self _giveWeapon("heli_remote_mp"); - self SwitchToWeapon("heli_remote_mp"); - self VisionSetThermalForPlayer( game["thermal_vision"], 6 ); - self ThermalVisionOn(); - self ThermalVisionFOFOverlayOn(); - self thread thermalVision( chopper ); - if ( getDvarInt( "camera_thirdPerson" ) ) - self setThirdPersonDOF( false ); - - chopper VehicleTurretControlOn( self ); - - self PlayerLinkWeaponviewToDelta( chopper, "tag_player", 1.0, 180, 180, 0, 180, true ); - - chopper.gunner = self; - - self.heliRideLifeId = lifeId; - - self thread endRideOnHelicopterDone( chopper ); - - self thread weaponLockThink( chopper ); - - while ( true ) - { - chopper waittill( "turret_fire" ); - chopper fireWeapon(); - - earthquake (0.2, 1, chopper.origin, 1000); - } -} - - -thermalVision( chopper ) -{ - chopper endon ( "helicopter_done" ); - - if ( getIntProperty( "ac130_thermal_enabled", 1 ) == 0 ) - return; - - inverted = false; - - self visionSetThermalForPlayer( game["thermal_vision"], 3 ); - - self notifyOnPlayerCommand( "switch thermal", "+activate" ); - - for (;;) - { - self waittill ( "switch thermal" ); - - if ( !inverted ) - { - self visionSetThermalForPlayer( "missilecam", 0.62 ); - if ( isdefined( level.HUDItem[ "thermal_mode" ] ) ) - level.HUDItem[ "thermal_mode" ] settext ( &"AC130_HUD_THERMAL_BHOT" ); - } - else - { - self visionSetThermalForPlayer( game["thermal_vision"], 0.51 ); - if ( isdefined( level.HUDItem[ "thermal_mode" ] ) ) - level.HUDItem[ "thermal_mode" ] settext ( &"AC130_HUD_THERMAL_WHOT" ); - } - - inverted = !inverted; - } -} - - -weaponLockThink( chopper ) -{ - self endon ( "disconnect" ); - chopper endon ( "helicopter_done" ); - - if ( !isDefined( level.heliTargetOrigin ) ) - { - level.heliTargetOrigin = spawn( "script_origin", (0,0,0) ); - level.heliTargetOrigin hide(); - } - - for ( ;; ) - { - trace = bulletTrace( self getEye(), self getEye() + (anglesToForward( self getPlayerAngles() ) * 100000 ), 1, self ); - level.heliTargetOrigin.origin = trace["position"]; - - targetListLOS = []; - targetListNoLOS = []; - foreach ( player in level.players ) - { - if ( !isAlive( player ) ) - continue; - - if ( level.teamBased && player.team == self.team ) - continue; - - if ( player == self ) - continue; - - if ( player _hasPerk( "specialty_coldblooded" ) ) - continue; - - if ( isDefined( player.spawntime ) && ( getTime() - player.spawntime )/1000 <= 5 ) - continue; - - player.remoteHeliLOS = true; - if ( !bulletTracePassed( self getEye(), player.origin + (0,0,32), false, chopper ) ) - { - //if ( distance( player.origin, trace["position"] ) > 256 ) - // continue; - - targetListNoLOS[targetListNoLOS.size] = player; - } - else - { - targetListLOS[targetListLOS.size] = player; - } - } - - targetsInReticle = []; - - /* - foreach ( target in targetList ) - { - insideReticle = self WorldPointInReticle_Circle( target.origin, 65, 1200 ); - - if ( !insideReticle ) - continue; - - targetsInReticle[targetsInReticle.size] = target; - } - */ - - targetsInReticle = targetListLOS; - foreach ( target in targetListNoLos ) - { - targetListLOS[targetListLOS.size] = target; - } - - if ( targetsInReticle.size != 0 ) - { - sortedTargets = SortByDistance( targetsInReticle, trace["position"] ); - - if ( distance( sortedTargets[0].origin, trace["position"] ) < 384 && sortedTargets[0] DamageConeTrace( trace["position"] ) ) - { - self weaponLockFinalize( sortedTargets[0] ); - heliDialog( "locked" ); - } - else - { - self weaponLockStart( sortedTargets[0] ); - heliDialog( "tracking" ); - } - } - else - { - self weaponLockFree(); - } - - wait ( 0.05 ); - } -} - - -heliDialog( dialogGroup ) -{ - if ( getTime() - level.lastHeliDialogTime < 6000 ) - return; - - level.lastHeliDialogTime = getTime(); - - randomIndex = randomInt( level.heliDialog[ dialogGroup ].size ); - soundAlias = level.heliDialog[ dialogGroup ][ randomIndex ]; - - fullSoundAlias = maps\mp\gametypes\_teams::getTeamVoicePrefix( self.team ) + soundAlias; - - self playLocalSound( fullSoundAlias ); -} - - -endRide( chopper ) -{ - self RemoteCameraSoundscapeOff(); - self ThermalVisionOff(); - self ThermalVisionFOFOverlayOff(); - self unlink(); - self switchToWeapon( self getLastWeapon() ); - self clearUsingRemote(); - - if ( getDvarInt( "camera_thirdPerson" ) ) - self setThirdPersonDOF( true ); - - self visionSetThermalForPlayer( game["thermal_vision"], 0 ); - - weaponList = self GetWeaponsListExclusives(); - foreach ( weapon in weaponList ) - self takeWeapon( weapon ); - - if ( isDefined( chopper ) ) - chopper VehicleTurretControlOff( self ); - - self notify ( "heliPlayer_removed" ); -} - - -endRideOnHelicopterDone( chopper ) -{ - self endon ( "disconnect" ); - - chopper waittill ( "helicopter_done" ); - - self endRide( chopper ); -} - - -getPosNearEnemies() -{ - validEnemies = []; - - foreach ( player in level.players ) - { - if ( player.team == "spectator" ) - continue; - - if ( player.team == self.team ) - continue; - - if ( !isAlive( player ) ) - continue; - - if ( !bulletTracePassed( player.origin, player.origin + (0,0,2048), false, player ) ) - continue; - - player.remoteHeliDist = 0; - validEnemies[validEnemies.size] = player; - } - - if ( !validEnemies.size ) - return undefined; - - for ( i = 0; i < validEnemies.size; i++ ) - { - for ( j = i + 1; j < validEnemies.size; j++ ) - { - dist = distanceSquared( validEnemies[i].origin, validEnemies[j].origin ); - - validEnemies[i].remoteHeliDist += dist; - validEnemies[j].remoteHeliDist += dist; - } - } - - bestPlayer = validEnemies[0]; - foreach ( player in validEnemies ) - { - if ( player.remoteHeliDist < bestPlayer.remoteHeliDist ) - bestPlayer = player; - } - - return ( bestPlayer.origin ); -} - - -updateAreaNodes( areaNodes ) -{ - validEnemies = []; - - foreach ( node in areaNodes ) - { - node.validPlayers = []; - node.nodeScore = 0; - } - - foreach ( player in level.players ) - { - if ( !isAlive( player ) ) - continue; - - if ( player.team == self.team ) - continue; - - foreach ( node in areaNodes ) - { - if ( distanceSquared( player.origin, node.origin ) > 1048576 ) - continue; - - node.validPlayers[node.validPlayers.size] = player; - } - } - - bestNode = areaNodes[0]; - foreach ( node in areaNodes ) - { - heliNode = getEnt( node.target, "targetname" ); - foreach ( player in node.validPlayers ) - { - node.nodeScore += 1; - - if ( bulletTracePassed( player.origin + (0,0,32), heliNode.origin, false, player ) ) - node.nodeScore += 3; - } - - if ( node.nodeScore > bestNode.nodeScore ) - bestNode = node; - } - - return ( getEnt( bestNode.target, "targetname" ) ); -} - - -// spawn helicopter at a start node and monitors it -heli_think( lifeId, owner, startnode, heli_team, heliType ) -{ - heliOrigin = startnode.origin; - heliAngles = startnode.angles; - - switch( heliType ) - { - case "minigun": - vehicleType = "cobra_minigun_mp"; - if ( owner.team == "allies" ) - vehicleModel = "vehicle_apache_mp"; - else - vehicleModel = "vehicle_mi-28_mp"; - break; - case "flares": - vehicleType = "pavelow_mp"; - if ( owner.team == "allies" ) - vehicleModel = "vehicle_pavelow"; - else - vehicleModel = "vehicle_pavelow_opfor"; - break; - default: - vehicleType = "cobra_mp"; - if ( owner.team == "allies" ) - vehicleModel = "vehicle_cobra_helicopter_fly_low"; - else - vehicleModel = "vehicle_mi24p_hind_mp"; - break; - } - - chopper = spawn_helicopter( owner, heliOrigin, heliAngles, vehicleType, vehicleModel ); - - if ( !isDefined( chopper ) ) - return; - - level.chopper = chopper; - chopper.heliType = heliType; - chopper.lifeId = lifeId; - chopper.team = heli_team; - chopper.pers["team"] = heli_team; - chopper.owner = owner; - - if ( heliType == "flares" ) - chopper.maxhealth = level.heli_maxhealth*2; // max health - else - chopper.maxhealth = level.heli_maxhealth; // max health - - chopper.targeting_delay = level.heli_targeting_delay; // delay between per targeting scan - in seconds - chopper.primaryTarget = undefined; // primary target ( player ) - chopper.secondaryTarget = undefined; // secondary target ( player ) - chopper.attacker = undefined; // last player that shot the helicopter - chopper.currentstate = "ok"; // health state - - if ( heliType == "flares" || heliType == "minigun" ) - chopper thread heli_flares_monitor(); - - // helicopter loop threads - chopper thread heli_leave_on_disconnect( owner ); - chopper thread heli_leave_on_changeTeams( owner ); - chopper thread heli_leave_on_gameended( owner ); - chopper thread heli_damage_monitor(); // monitors damage - chopper thread heli_health(); // display helicopter's health through smoke/fire - chopper thread heli_existance(); - - // flight logic - chopper endon ( "helicopter_done" ); - chopper endon ( "crashing" ); - chopper endon ( "leaving" ); - chopper endon ( "death" ); - - // initial fight into play space - if ( heliType == "minigun" ) - { - owner thread heliRide( lifeId, chopper ); - chopper thread heli_leave_on_spawned( owner ); - } - - attackAreas = getEntArray( "heli_attack_area", "targetname" ); - //attackAreas = []; - loopNode = level.heli_loop_nodes[ randomInt( level.heli_loop_nodes.size ) ]; - - // specific logic per type - switch ( heliType ) - { - case "minigun": - chopper thread heli_targeting(); - chopper heli_fly_simple_path( startNode ); - chopper thread heli_leave_on_timeout( 40.0 ); - if ( attackAreas.size ) - chopper thread heli_fly_well( attackAreas ); - else - chopper thread heli_fly_loop_path( loopNode ); - break; - case "flares": - chopper thread makeGunShip(); - thread teamPlayerCardSplash( "used_helicopter_flares", owner ); - chopper heli_fly_simple_path( startNode ); - chopper thread heli_leave_on_timeout( 60.0 ); - chopper thread heli_fly_loop_path( loopNode ); - break; - default: - chopper thread attack_targets(); - chopper thread heli_targeting(); - chopper heli_fly_simple_path( startNode ); - chopper thread heli_leave_on_timeout( 60.0 ); - chopper thread heli_fly_loop_path( loopNode ); - break; - } -} - - -makeGunShip() -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - wait ( 0.5 ); - - mgTurret = spawnTurret( "misc_turret", self.origin, "pavelow_minigun_mp" ); - mgTurret.lifeId = self.lifeId; - mgTurret linkTo( self, "tag_gunner_left", ( 0,0,0 ), ( 0,0,0) ); - mgTurret setModel( "weapon_minigun" ); - mgTurret.owner = self.owner; - mgTurret.team = self.team; - mgTurret makeTurretInoperable(); - mgTurret.pers["team"] = self.team; - mgTurret.killCamEnt = self; - self.mgTurretLeft = mgTurret; - self.mgTurretLeft SetDefaultDropPitch( 0 ); - - mgTurret = spawnTurret( "misc_turret", self.origin, "pavelow_minigun_mp" ); - mgTurret.lifeId = self.lifeId; - mgTurret linkTo( self, "tag_gunner_right", ( 0,0,0 ), ( 0,0,0) ); - mgTurret setModel( "weapon_minigun" ); - mgTurret.owner = self.owner; - mgTurret.team = self.team; - mgTurret makeTurretInoperable(); - mgTurret.pers["team"] = self.team; - mgTurret.killCamEnt = self; - self.mgTurretRight = mgTurret; - self.mgTurretRight SetDefaultDropPitch( 0 ); - - if ( level.teamBased ) - { - self.mgTurretLeft setTurretTeam( self.team ); - self.mgTurretRight setTurretTeam( self.team ); - } - - self.mgTurretLeft setMode( "auto_nonai" ); - self.mgTurretRight setMode( "auto_nonai" ); - - self.mgTurretLeft SetSentryOwner( self.owner ); - self.mgTurretRight SetSentryOwner( self.owner ); - - self.mgTurretLeft SetTurretMinimapVisible( false ); - self.mgTurretRight SetTurretMinimapVisible( false ); - - self.mgTurretLeft thread sentry_attackTargets(); - self.mgTurretRight thread sentry_attackTargets(); - - self thread deleteTurretsWhenDone(); -} - - -deleteTurretsWhenDone() -{ - self waittill ( "helicopter_done" ); - - self.mgTurretRight delete(); - self.mgTurretLeft delete(); -} - - -sentry_attackTargets() -{ - self endon( "death" ); - self endon ( "helicopter_done" ); - - level endon( "game_ended" ); - - for ( ;; ) - { - self waittill( "turretstatechange" ); - - if ( self isFiringTurret() ) - self thread sentry_burstFireStart(); - else - self thread sentry_burstFireStop(); - } -} - - -sentry_burstFireStart() -{ - self endon( "death" ); - self endon( "stop_shooting" ); - self endon( "leaving" ); - - 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) ) - self shootTurret(); - - wait ( fireTime ); - } - - wait ( randomFloatRange( minPause, maxPause ) ); - } -} - - -sentry_burstFireStop() -{ - self notify( "stop_shooting" ); -} - - -heli_existance() -{ - entityNumber = self getEntityNumber(); - - self waittill_any( "death", "crashing", "leaving" ); - - self removeFromHeliList( entityNumber ); - - self notify( "helicopter_done" ); - - player = undefined; - queueEnt = queueRemoveFirst( "helicopter" ); - if ( !isDefined( queueEnt ) ) - { - level.chopper = undefined; - return; - } - - player = queueEnt.player; - lifeId = queueEnt.lifeId; - streakName = queueEnt.streakName; - heliType = queueEnt.heliType; - queueEnt delete(); - - if ( isDefined( player ) && (player.sessionstate == "playing" || player.sessionstate == "dead") ) - { - player maps\mp\killstreaks\_killstreaks::usedKillstreak( streakName, true ); - player startHelicopter( lifeId, heliType ); - } - else - { - level.chopper = undefined; - } -} - - -// helicopter targeting logic -heli_targeting() -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - // targeting sweep cycle - for ( ;; ) - { - // array of helicopter's targets - targets = []; - self.primaryTarget = undefined; - self.secondaryTarget = undefined; - - players = level.players; - - foreach ( player in level.players ) - { - if ( !canTarget_turret( player ) ) - continue; - - targets[targets.size] = player; - } - - if ( targets.size ) - { - targetPlayer = getBestPrimaryTarget( targets ); - self.primaryTarget = targetPlayer; - self notify( "primary acquired" ); - } - - if ( isDefined( level.harriers ) ) - { - foreach( harrier in level.harriers ) - { - if( !isDefined( harrier ) ) - continue; - - if ( (level.teamBased && harrier.team != self.team) || (!level.teamBased && harrier.owner != self.owner) ) - { - self notify( "secondary acquired" ); - self.secondaryTarget = harrier; - } - } - } - - wait ( 0.5 ); - } -} - -// targetability -canTarget_turret( player ) -{ - canTarget = true; - - if ( !isAlive( player ) || player.sessionstate != "playing" ) - return false; - - if ( self.heliType != "flares" ) - { - if ( !self Vehicle_CanTurretTargetPoint( player.origin+(0,0,40), 1, self ) ) - return false; - } - - if ( distance( player.origin, self.origin ) > level.heli_visual_range ) - return false; - - if ( level.teamBased && player.pers["team"] == self.team ) - return false; - - if ( player == self.owner ) - return false; - - if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= 5 ) - return false; - - if ( player _hasPerk( "specialty_coldblooded" ) ) - return false; - - heli_centroid = self.origin + ( 0, 0, -160 ); - heli_forward_norm = anglestoforward( self.angles ); - heli_turret_point = heli_centroid + 144*heli_forward_norm; - - if ( player sightConeTrace( heli_turret_point, self) < level.heli_target_recognition ) - return false; - - return canTarget; -} - - -getBestPrimaryTarget( targets ) -{ - foreach ( player in targets ) - update_player_threat( player ); - - // find primary target, highest threat level - highest = 0; - primaryTarget = undefined; - - foreach ( player in targets ) - { - assertEx( isDefined( player.threatlevel ), "Target player does not have threat level" ); - - if ( player.threatlevel < highest ) - continue; - - highest = player.threatlevel; - primaryTarget = player; - } - - assertEx( isDefined( primaryTarget ), "Targets exist, but none was assigned as primary" ); - - return ( primaryTarget ); -} - - -// threat factors -update_player_threat( player ) -{ - player.threatlevel = 0; - - // distance factor - dist = distance( player.origin, self.origin ); - player.threatlevel += ( (level.heli_visual_range - dist)/level.heli_visual_range )*100; // inverse distance % with respect to helicopter targeting range - - // behavior factor - if ( isdefined( self.attacker ) && player == self.attacker ) - player.threatlevel += 100; - - // player score factor - player.threatlevel += player.score*4; - - if( isdefined( player.antithreat ) ) - player.threatlevel -= player.antithreat; - - if( player.threatlevel <= 0 ) - player.threatlevel = 1; -} - - -// resets helicopter's motion values -heli_reset() -{ - self clearTargetYaw(); - self clearGoalYaw(); - self Vehicle_SetSpeed( 60, 25 ); - self setyawspeed( 75, 45, 45 ); - //self setjitterparams( (30, 30, 30), 4, 6 ); - self setmaxpitchroll( 30, 30 ); - self setneargoalnotifydist( 256 ); - self setturningability(0.9); -} - - -Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ) -{ - if ( !isDefined( attacker ) || attacker == self ) - return; - - if ( !maps\mp\gameTypes\_weapons::attackerCanDamageItem( attacker, self.owner ) ) - return; - - switch ( weapon ) - { - case "ac130_105mm_mp": - case "ac130_40mm_mp": - case "stinger_mp": - case "javelin_mp": - case "remotemissile_projectile_mp": - self.largeProjectileDamage = true; - damage = self.maxhealth + 1; - break; - } - - if( self.damageTaken+damage >= self.maxhealth ) - { - validAttacker = undefined; - - if ( !isDefined(self.owner) || attacker != self.owner ) - validAttacker = attacker; - - if ( isDefined( validAttacker ) ) - { - validAttacker notify( "destroyed_killstreak", weapon ); - } - } - - self Vehicle_FinishDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ); -} - - -addRecentDamage( damage ) -{ - self endon( "death" ); - - self.recentDamageAmount += damage; - - wait ( 4.0 ); - self.recentDamageAmount -= damage; -} - - -// accumulate damage and react -heli_damage_monitor() -{ - self endon( "death" ); - self endon( "crashing" ); - self endon( "leaving" ); - - self.damageTaken = 0; - self.recentDamageAmount = 0; - - for( ;; ) - { - // this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is. - self waittill( "damage", damage, attacker, direction_vec, P, type ); - - assert( isDefined( attacker ) ); - - self.attacker = attacker; - - if ( isPlayer( attacker ) ) - { - attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "" ); - - if ( type == "MOD_RIFLE_BULLET" || type == "MOD_PISTOL_BULLET" ) - { - damage *= level.heli_armor_bulletdamage; - - if ( attacker _hasPerk( "specialty_armorpiercing" ) ) - damage += damage*level.armorPiercingMod; - } - } - - self.damageTaken += damage; - - self thread addRecentDamage( damage ); - - if( self.damageTaken > self.maxhealth && ((level.teamBased && self.team != attacker.team) || !level.teamBased) ) - { - validAttacker = undefined; - if ( isDefined( attacker.owner ) && (!isDefined(self.owner) || attacker.owner != self.owner) ) - validAttacker = attacker.owner; - else if ( !isDefined(attacker.owner) && attacker.classname == "script_vehicle" ) - return; - else if ( !isDefined(self.owner) || attacker != self.owner ) - validAttacker = attacker; - - if ( isDefined( validAttacker ) ) - { - attacker notify( "destroyed_helicopter" ); - - if ( self.heliType == "flares" ) - { - thread teamPlayerCardSplash( "callout_destroyed_helicopter_flares", validAttacker ); - xpVal = 400; - } - else if ( self.heliType == "minigun" ) - { - thread teamPlayerCardSplash( "callout_destroyed_helicopter_minigun", validAttacker ); - xpVal = 300; - } - else - { - thread teamPlayerCardSplash( "callout_destroyed_helicopter", validAttacker ); - xpVal = 200; - } - - validAttacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", xpVal ); - thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, validAttacker, damage, type ); - - } - } - } -} - - -heli_health() -{ - self endon( "death" ); - self endon( "leaving" ); - self endon( "crashing" ); - - self.currentstate = "ok"; - self.laststate = "ok"; - self setdamagestage( 3 ); - - damageState = 3; - self setDamageStage( damageState ); - - for ( ;; ) - { - if ( self.damageTaken >= (self.maxhealth * 0.33) && damageState == 3 ) - { - damageState = 2; - self setDamageStage( damageState ); - self.currentstate = "light smoke"; - playFxOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_engine_left" ); - } - else if ( self.damageTaken >= (self.maxhealth * 0.66) && damageState == 2 ) - { - damageState = 1; - self setDamageStage( damageState ); - self.currentstate = "heavy smoke"; - stopFxOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_engine_left" ); - playFxOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_engine_left" ); - } - else if( self.damageTaken > self.maxhealth ) - { - damageState = 0; - self setDamageStage( damageState ); - - stopFxOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_engine_left" ); - - if ( IsDefined( self.largeProjectileDamage ) && self.largeProjectileDamage ) - { - self thread heli_explode( true ); - } - else - { - playFxOnTag( level.chopper_fx["damage"]["on_fire"], self, "tag_engine_left" ); - self thread heli_crash(); - } - } - - wait 0.05; - } -} - - -// attach helicopter on crash path -heli_crash() -{ - self notify( "crashing" ); - - crashNode = level.heli_crash_nodes[ randomInt( level.heli_crash_nodes.size ) ]; - - self thread heli_spin( 180 ); - self thread heli_secondary_explosions(); - self heli_fly_simple_path( crashNode ); - - self thread heli_explode(); -} - -heli_secondary_explosions() -{ - playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" ); - self playSound ( level.heli_sound[self.team]["hitsecondary"] ); - - wait ( 3.0 ); - - if ( !isDefined( self ) ) - return; - - playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" ); - self playSound ( level.heli_sound[self.team]["hitsecondary"] ); -} - -// self spin at one rev per 2 sec -heli_spin( speed ) -{ - self endon( "death" ); - - // play hit sound immediately so players know they got it - self playSound ( level.heli_sound[self.team]["hit"] ); - - // play heli crashing spinning sound - self thread spinSoundShortly(); - - // spins until death - self setyawspeed( speed, speed, speed ); - while ( isdefined( self ) ) - { - self settargetyaw( self.angles[1]+(speed*0.9) ); - wait ( 1 ); - } -} - - -spinSoundShortly() -{ - self endon("death"); - - wait .25; - - self stopLoopSound(); - wait .05; - self playLoopSound( level.heli_sound[self.team]["spinloop"] ); - wait .05; - self playLoopSound( level.heli_sound[self.team]["spinstart"] ); -} - - -// crash explosion -heli_explode( altStyle ) -{ - self notify( "death" ); - - if ( isDefined( altStyle ) && isDefined( level.chopper_fx["explode"]["air_death"][self.heli_type] ) ) - { - deathAngles = self getTagAngles( "tag_deathfx" ); - - playFx( level.chopper_fx["explode"]["air_death"][self.heli_type], self getTagOrigin( "tag_deathfx" ), anglesToForward( deathAngles ), anglesToUp( deathAngles ) ); - //playFxOnTag( level.chopper_fx["explode"]["air_death"][self.heli_type], self, "tag_deathfx" ); - } - else - { - org = self.origin; - forward = ( self.origin + ( 0, 0, 1 ) ) - self.origin; - playFx( level.chopper_fx["explode"]["death"][self.heli_type], org, forward ); - } - - - // play heli explosion sound - self playSound( level.heli_sound[self.team]["crash"] ); - - // give "death" notify time to process - wait ( 0.05 ); - self delete(); -} - - -fire_missile( sMissileType, iShots, eTarget ) -{ - if ( !isdefined( iShots ) ) - iShots = 1; - assert( self.health > 0 ); - - weaponName = undefined; - weaponShootTime = undefined; - defaultWeapon = "cobra_20mm_mp"; - tags = []; - switch( sMissileType ) - { - case "ffar": - weaponName = "harrier_FFAR_mp"; - - tags[ 0 ] = "tag_store_r_2"; - break; - default: - assertMsg( "Invalid missile type specified. Must be ffar" ); - break; - } - assert( isdefined( weaponName ) ); - assert( tags.size > 0 ); - - weaponShootTime = weaponfiretime( weaponName ); - assert( isdefined( weaponShootTime ) ); - - self setVehWeapon( weaponName ); - nextMissileTag = -1; - for( i = 0 ; i < iShots ; i++ ) // I don't believe iShots > 1 is properly supported; we don't set the weapon each time - { - nextMissileTag++; - if ( nextMissileTag >= tags.size ) - nextMissileTag = 0; - - self setVehWeapon( "harrier_FFAR_mp" ); - - if ( isdefined( eTarget ) ) - { - eMissile = self fireWeapon( tags[ nextMissileTag ], eTarget ); - eMissile Missile_SetFlightmodeDirect(); - eMissile Missile_SetTargetEnt( eTarget ); - } - else - { - eMissile = self fireWeapon( tags[ nextMissileTag ] ); - eMissile Missile_SetFlightmodeDirect(); - eMissile Missile_SetTargetEnt( eTarget ); - } - - if ( i < iShots - 1 ) - wait weaponShootTime; - } - // avoid calling setVehWeapon again this frame or the client doesn't hear about the original weapon change -} - -// checks if owner is valid, returns false if not valid -check_owner() -{ - if ( !isdefined( self.owner ) || !isdefined( self.owner.pers["team"] ) || self.owner.pers["team"] != self.team ) - { - self thread heli_leave(); - - return false; - } - - return true; -} - - -heli_leave_on_disconnect( owner ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - owner waittill( "disconnect" ); - - self thread heli_leave(); -} - -heli_leave_on_changeTeams( owner ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - owner waittill_any( "joined_team", "joined_spectators" ); - - self thread heli_leave(); -} - -heli_leave_on_spawned( owner ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - owner waittill( "spawned" ); - - self thread heli_leave(); -} - -heli_leave_on_gameended( owner ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - level waittill ( "game_ended" ); - - self thread heli_leave(); -} - -heli_leave_on_timeout( timeOut ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeOut ); - - self thread heli_leave(); -} - -attack_targets() -{ - //self thread turret_kill_players(); - self thread attack_primary(); - self thread attack_secondary(); -} - - -// missile only -attack_secondary() -{ - self endon( "death" ); - self endon( "crashing" ); - self endon( "leaving" ); - - for( ;; ) - { - if ( isdefined( self.secondaryTarget ) ) - { - self.secondaryTarget.antithreat = undefined; - self.missileTarget = self.secondaryTarget; - - antithreat = 0; - - while( isdefined( self.missileTarget ) && isalive( self.missileTarget ) ) - { - // if selected target is not in missile hit range, skip - if( self missile_target_sight_check( self.missileTarget ) ) - self thread missile_support( self.missileTarget, level.heli_missile_rof); - else - break; - - self waittill( "missile ready" ); - - // target might disconnect or change during last assault cycle - if ( !isdefined( self.secondaryTarget ) || ( isdefined( self.secondaryTarget ) && self.missileTarget != self.secondaryTarget ) ) - break; - } - // reset the antithreat factor - if ( isdefined( self.missileTarget ) ) - self.missileTarget.antithreat = undefined; - } - self waittill( "secondary acquired" ); - - // check if owner has left, if so, leave - self check_owner(); - } -} - -// check if missile is in hittable sight zone -missile_target_sight_check( missiletarget ) -{ - heli2target_normal = vectornormalize( missiletarget.origin - self.origin ); - heli2forward = anglestoforward( self.angles ); - heli2forward_normal = vectornormalize( heli2forward ); - - heli_dot_target = vectordot( heli2target_normal, heli2forward_normal ); - - if ( heli_dot_target >= level.heli_missile_target_cone ) - { - debug_print3d_simple( "Missile sight: " + heli_dot_target, self, ( 0,0,-40 ), 40 ); - return true; - } - return false; -} - -// if wait for turret turning is too slow, enable missile assault support -missile_support( target_player, rof ) -{ - self endon( "death" ); - self endon( "crashing" ); - self endon( "leaving" ); - - if ( isdefined( target_player ) ) - { - if ( level.teambased ) - { - if ( isDefined( target_player.owner ) && target_player.team != self.team ) - { - self fire_missile( "ffar", 1, target_player ); - self notify( "missile fired" ); - } - } - else - { - if ( isDefined( target_player.owner ) && target_player.owner != self.owner ) - { - self fire_missile( "ffar", 1, target_player ); - self notify( "missile fired" ); - } - } - } - - wait ( rof ); - self notify ( "missile ready" ); - - return; -} - -// mini-gun with missile support -attack_primary() -{ - self endon( "death" ); - self endon( "crashing" ); - self endon( "leaving" ); - - while ( 1 ) - { - wait ( 0.05 ); - - if ( !isAlive( self.primaryTarget ) ) - continue; - - currentTarget = self.primaryTarget; - - currentTarget.antithreat = 0; - - if ( randomInt(5) < 3 ) - angle = currentTarget.angles[1] + randomFloatRange( -30, 30 ); - else - angle = randomInt( 360 ); - - radiusOffset = 96; - - xOffset = cos( angle ) * radiusOffset; - yOffset = sin( angle ) * radiusOffset; - - self setTurretTargetEnt( currentTarget, (xOffset,yOffset,40) ); - - self waitOnTargetOrDeath( currentTarget, 3.0 ); - - if ( !isAlive( currentTarget ) || !self Vehicle_CanTurretTargetPoint( currentTarget.origin+(0,0,40) ) ) - continue; - - weaponShootTime = weaponFireTime( "cobra_20mm_mp" ); - - convergenceMod = 1; - shotsSinceLastSighting = 0; - - self playLoopSound( "weap_cobra_20mm_fire_npc" ); - for ( i = 0; i < level.heli_turretClipSize; i++ ) - { - self setVehWeapon( "cobra_20mm_mp" ); - self fireWeapon( "tag_flash" ); - - if ( i < level.heli_turretClipSize - 1 ) - wait weaponShootTime; - - if ( !isDefined( currentTarget ) ) - break; - - if ( self Vehicle_CanTurretTargetPoint( currentTarget.origin+(0,0,40), 1, self ) ) - { - convergenceMod = max( convergenceMod - 0.05, 0 ); - shotsSinceLastSighting = 0; - } - else - { - shotsSinceLastSighting++; - } - - if ( shotsSinceLastSighting > 10 ) - break; - - targetPos = ( (xOffset*convergenceMod)+randomFloatRange( -6, 6 ),(yOffset*convergenceMod)+randomFloatRange( -6, 6 ),40+randomFloatRange( -6, 6 ) ); - - self setTurretTargetEnt( currentTarget, targetPos ); - } - self stopLoopSound(); - - // lower the target's threat since already assaulted on - if ( isAlive( currentTarget ) ) - currentTarget.antithreat += 100; - - wait ( randomFloatRange( 0.5, 2.0 ) ); - } -} - -waitOnTargetOrDeath( target, timeOut ) -{ - self endon ( "death" ); - self endon ( "helicopter_done" ); - - target endon ( "death" ); - target endon ( "disconnect" ); - - self waittill_notify_or_timeout( "turret_on_target", timeOut ); -} - - -fireMissile( missileTarget ) -{ - self endon( "death" ); - self endon( "crashing" ); - self endon( "leaving" ); - - assert( self.health > 0 ); - - if ( !isdefined( missileTarget ) ) - return; - - if ( Distance2D(self.origin, missileTarget.origin ) < 512 ) - return; - - self setVehWeapon( "harrier_FFAR_mp" ); - missile = self fireWeapon( "tag_flash", missileTarget ); - missile Missile_SetFlightmodeDirect(); - missile Missile_SetTargetEnt( missileTarget ); -} - - -// ==================================================================================== -// Helicopter Pathing Logic -// ==================================================================================== - -getOriginOffsets( goalNode ) -{ - startOrigin = self.origin; - endOrigin = goalNode.origin; - - numTraces = 0; - maxTraces = 40; - - traceOffset = (0,0,-196); - - traceOrigin = physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ); - - while ( distance( traceOrigin, endOrigin+traceOffset ) > 10 && numTraces < maxTraces ) - { - println( "trace failed: " + distance( physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ), endOrigin+traceOffset ) ); - - if ( startOrigin[2] < endOrigin[2] ) - { - startOrigin += (0,0,128); - } - else if ( startOrigin[2] > endOrigin[2] ) - { - endOrigin += (0,0,128); - } - else - { - startOrigin += (0,0,128); - endOrigin += (0,0,128); - } - - //thread draw_line( startOrigin+traceOffset, endOrigin+traceOffset, (0,1,9), 200 ); - numTraces++; - - traceOrigin = physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ); - } - - offsets = []; - offsets["start"] = startOrigin; - offsets["end"] = endOrigin; - return offsets; -} - - -travelToNode( goalNode ) -{ - originOffets = getOriginOffsets( goalNode ); - - if ( originOffets["start"] != self.origin ) - { - // motion change via node - if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) - { - heli_speed = goalNode.script_airspeed; - heli_accel = goalNode.script_accel; - } - else - { - heli_speed = 30+randomInt(20); - heli_accel = 15+randomInt(15); - } - - self Vehicle_SetSpeed( heli_speed, heli_accel ); - self setvehgoalpos( originOffets["start"] + (0,0,30), 0 ); - // calculate ideal yaw - self setgoalyaw( goalNode.angles[ 1 ] + level.heli_angle_offset ); - - //println( "setting goal to startOrigin" ); - - self waittill ( "goal" ); - } - - if ( originOffets["end"] != goalNode.origin ) - { - // motion change via node - if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) - { - heli_speed = goalNode.script_airspeed; - heli_accel = goalNode.script_accel; - } - else - { - heli_speed = 30+randomInt(20); - heli_accel = 15+randomInt(15); - } - - self Vehicle_SetSpeed( heli_speed, heli_accel ); - self setvehgoalpos( originOffets["end"] + (0,0,30), 0 ); - // calculate ideal yaw - self setgoalyaw( goalNode.angles[ 1 ] + level.heli_angle_offset ); - - //println( "setting goal to endOrigin" ); - - self waittill ( "goal" ); - } -} - - -heli_fly_simple_path( startNode ) -{ - self endon ( "death" ); - self endon ( "leaving" ); - - // only one thread instance allowed - self notify( "flying"); - self endon( "flying" ); - - heli_reset(); - - currentNode = startNode; - while ( isDefined( currentNode.target ) ) - { - nextNode = getEnt( currentNode.target, "targetname" ); - assertEx( isDefined( nextNode ), "Next node in path is undefined, but has targetname" ); - - if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) - { - heli_speed = currentNode.script_airspeed; - heli_accel = currentNode.script_accel; - } - else - { - heli_speed = 30 + randomInt(20); - heli_accel = 15 + randomInt(15); - } - - self Vehicle_SetSpeed( heli_speed, heli_accel ); - - // end of the path - if ( !isDefined( nextNode.target ) ) - { - self setVehGoalPos( nextNode.origin+(self.zOffset), true ); - self waittill( "near_goal" ); - } - else - { - self setVehGoalPos( nextNode.origin+(self.zOffset), false ); - self waittill( "near_goal" ); - - self setGoalYaw( nextNode.angles[ 1 ] ); - - self waittillmatch( "goal" ); - } - - currentNode = nextNode; - } - - printLn( currentNode.origin ); - printLn( self.origin ); -} - - -heli_fly_loop_path( startNode ) -{ - self endon ( "death" ); - self endon ( "crashing" ); - self endon ( "leaving" ); - - // only one thread instance allowed - self notify( "flying"); - self endon( "flying" ); - - heli_reset(); - - self thread heli_loop_speed_control( startNode ); - - currentNode = startNode; - while ( isDefined( currentNode.target ) ) - { - nextNode = getEnt( currentNode.target, "targetname" ); - assertEx( isDefined( nextNode ), "Next node in path is undefined, but has targetname" ); - - if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) - { - self.desired_speed = currentNode.script_airspeed; - self.desired_accel = currentNode.script_accel; - } - else - { - self.desired_speed = 30 + randomInt( 20 ); - self.desired_accel = 15 + randomInt( 15 ); - } - - if ( self.heliType == "flares" ) - { - self.desired_speed *= 0.5; - self.desired_accel *= 0.5; - } - - if ( isDefined( nextNode.script_delay ) && isDefined( self.primaryTarget ) && !self heli_is_threatened() ) - { - self setVehGoalPos( nextNode.origin+(self.zOffset), true ); - self waittill( "near_goal" ); - - wait ( nextNode.script_delay ); - } - else - { - self setVehGoalPos( nextNode.origin+(self.zOffset), false ); - self waittill( "near_goal" ); - - self setGoalYaw( nextNode.angles[ 1 ] ); - - self waittillmatch( "goal" ); - } - - currentNode = nextNode; - } -} - - -heli_loop_speed_control( currentNode ) -{ - self endon ( "death" ); - self endon ( "crashing" ); - self endon ( "leaving" ); - - if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) - { - self.desired_speed = currentNode.script_airspeed; - self.desired_accel = currentNode.script_accel; - } - else - { - self.desired_speed = 30 + randomInt( 20 ); - self.desired_accel = 15 + randomInt( 15 ); - } - - lastSpeed = 0; - lastAccel = 0; - - while ( 1 ) - { - goalSpeed = self.desired_speed; - goalAccel = self.desired_accel; - - if ( self.heliType != "flares" && isDefined( self.primaryTarget ) && !self heli_is_threatened() ) - goalSpeed *= 0.25; - - if ( lastSpeed != goalSpeed || lastAccel != goalAccel ) - { - self Vehicle_SetSpeed( goalSpeed, goalAccel ); - - lastSpeed = goalSpeed; - lastAccel = goalAccel; - } - - wait ( 0.05 ); - } -} - - -heli_is_threatened() -{ - if ( self.recentDamageAmount > 50 ) - return true; - - if ( self.currentState == "heavy smoke" ) - return true; - - return false; -} - - -heli_fly_well( destNodes ) -{ - self notify( "flying"); - self endon( "flying" ); - - self endon ( "death" ); - self endon ( "crashing" ); - self endon ( "leaving" ); - - for ( ;; ) - { - currentNode = self get_best_area_attack_node( destNodes ); - - travelToNode( currentNode ); - - // motion change via node - if( isdefined( currentNode.script_airspeed ) && isdefined( currentNode.script_accel ) ) - { - heli_speed = currentNode.script_airspeed; - heli_accel = currentNode.script_accel; - } - else - { - heli_speed = 30+randomInt(20); - heli_accel = 15+randomInt(15); - } - - self Vehicle_SetSpeed( heli_speed, heli_accel ); - self setvehgoalpos( currentNode.origin + self.zOffset, 1 ); - self setgoalyaw( currentNode.angles[ 1 ] + level.heli_angle_offset ); - - if ( level.heli_forced_wait != 0 ) - { - self waittill( "near_goal" ); //self waittillmatch( "goal" ); - wait ( level.heli_forced_wait ); - } - else if ( !isdefined( currentNode.script_delay ) ) - { - self waittill( "near_goal" ); //self waittillmatch( "goal" ); - - wait ( 5 + randomInt( 5 ) ); - } - else - { - self waittillmatch( "goal" ); - wait ( currentNode.script_delay ); - } - } -} - - -get_best_area_attack_node( destNodes ) -{ - return updateAreaNodes( destNodes ); -} - - -// helicopter leaving parameter, can not be damaged while leaving -heli_leave() -{ - self notify( "leaving" ); - - leaveNode = level.heli_leave_nodes[ randomInt( level.heli_leave_nodes.size ) ]; - - self heli_reset(); - self Vehicle_SetSpeed( 100, 45 ); - self setvehgoalpos( leaveNode.origin, 1 ); - self waittillmatch( "goal" ); - self notify( "death" ); - - // give "death" notify time to process - wait ( 0.05 ); - self delete(); -} - - -// ==================================================================================== -// DEBUG INFORMATION -// ==================================================================================== - -debug_print3d( message, color, ent, origin_offset, frames ) -{ - if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) - self thread draw_text( message, color, ent, origin_offset, frames ); -} - -debug_print3d_simple( message, ent, offset, frames ) -{ - if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) - { - if( isdefined( frames ) ) - thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, frames ); - else - thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, 0 ); - } -} - -debug_line( from, to, color, frames ) -{ - if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 && !isdefined( frames ) ) - { - thread draw_line( from, to, color ); - } - else if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) - thread draw_line( from, to, color, frames); -} - -draw_text( msg, color, ent, offset, frames ) -{ - //level endon( "helicopter_done" ); - if( frames == 0 ) - { - while ( isdefined( ent ) ) - { - print3d( ent.origin+offset, msg , color, 0.5, 4 ); - wait 0.05; - } - } - else - { - for( i=0; i < frames; i++ ) - { - if( !isdefined( ent ) ) - break; - print3d( ent.origin+offset, msg , color, 0.5, 4 ); - wait 0.05; - } - } -} - -draw_line( from, to, color, frames ) -{ - //level endon( "helicopter_done" ); - if( isdefined( frames ) ) - { - for( i=0; i 256 ) + // continue; + + targetListNoLOS[targetListNoLOS.size] = player; + } + else + { + targetListLOS[targetListLOS.size] = player; + } + } + + targetsInReticle = []; + + /* + foreach ( target in targetList ) + { + insideReticle = self WorldPointInReticle_Circle( target.origin, 65, 1200 ); + + if ( !insideReticle ) + continue; + + targetsInReticle[targetsInReticle.size] = target; + } + */ + + targetsInReticle = targetListLOS; + foreach ( target in targetListNoLos ) + { + targetListLOS[targetListLOS.size] = target; + } + + if ( targetsInReticle.size != 0 ) + { + sortedTargets = SortByDistance( targetsInReticle, trace["position"] ); + + if ( distance( sortedTargets[0].origin, trace["position"] ) < 384 && sortedTargets[0] DamageConeTrace( trace["position"] ) ) + { + self weaponLockFinalize( sortedTargets[0] ); + heliDialog( "locked" ); + } + else + { + self weaponLockStart( sortedTargets[0] ); + heliDialog( "tracking" ); + } + } + else + { + self weaponLockFree(); + } + + wait ( 0.05 ); + } +} + + +heliDialog( dialogGroup ) +{ + if ( getTime() - level.lastHeliDialogTime < 6000 ) + return; + + level.lastHeliDialogTime = getTime(); + + randomIndex = randomInt( level.heliDialog[ dialogGroup ].size ); + soundAlias = level.heliDialog[ dialogGroup ][ randomIndex ]; + + fullSoundAlias = maps\mp\gametypes\_teams::getTeamVoicePrefix( self.team ) + soundAlias; + + self playLocalSound( fullSoundAlias ); +} + + +endRide( chopper ) +{ + self RemoteCameraSoundscapeOff(); + self ThermalVisionOff(); + self ThermalVisionFOFOverlayOff(); + self unlink(); + self switchToWeapon( self getLastWeapon() ); + self clearUsingRemote(); + + if ( getDvarInt( "camera_thirdPerson" ) ) + self setThirdPersonDOF( true ); + + self visionSetThermalForPlayer( game["thermal_vision"], 0 ); + + weaponList = self GetWeaponsListExclusives(); + foreach ( weapon in weaponList ) + self takeWeapon( weapon ); + + if ( isDefined( chopper ) ) + chopper VehicleTurretControlOff( self ); + + self notify ( "heliPlayer_removed" ); +} + + +endRideOnHelicopterDone( chopper ) +{ + self endon ( "disconnect" ); + + chopper waittill ( "helicopter_done" ); + + self endRide( chopper ); +} + + +getPosNearEnemies() +{ + validEnemies = []; + + foreach ( player in level.players ) + { + if ( player.team == "spectator" ) + continue; + + if ( player.team == self.team ) + continue; + + if ( !isAlive( player ) ) + continue; + + if ( !bulletTracePassed( player.origin, player.origin + (0,0,2048), false, player ) ) + continue; + + player.remoteHeliDist = 0; + validEnemies[validEnemies.size] = player; + } + + if ( !validEnemies.size ) + return undefined; + + for ( i = 0; i < validEnemies.size; i++ ) + { + for ( j = i + 1; j < validEnemies.size; j++ ) + { + dist = distanceSquared( validEnemies[i].origin, validEnemies[j].origin ); + + validEnemies[i].remoteHeliDist += dist; + validEnemies[j].remoteHeliDist += dist; + } + } + + bestPlayer = validEnemies[0]; + foreach ( player in validEnemies ) + { + if ( player.remoteHeliDist < bestPlayer.remoteHeliDist ) + bestPlayer = player; + } + + return ( bestPlayer.origin ); +} + + +updateAreaNodes( areaNodes ) +{ + validEnemies = []; + + foreach ( node in areaNodes ) + { + node.validPlayers = []; + node.nodeScore = 0; + } + + foreach ( player in level.players ) + { + if ( !isAlive( player ) ) + continue; + + if ( player.team == self.team ) + continue; + + foreach ( node in areaNodes ) + { + if ( distanceSquared( player.origin, node.origin ) > 1048576 ) + continue; + + node.validPlayers[node.validPlayers.size] = player; + } + } + + bestNode = areaNodes[0]; + foreach ( node in areaNodes ) + { + heliNode = getEnt( node.target, "targetname" ); + foreach ( player in node.validPlayers ) + { + node.nodeScore += 1; + + if ( bulletTracePassed( player.origin + (0,0,32), heliNode.origin, false, player ) ) + node.nodeScore += 3; + } + + if ( node.nodeScore > bestNode.nodeScore ) + bestNode = node; + } + + return ( getEnt( bestNode.target, "targetname" ) ); +} + + +// spawn helicopter at a start node and monitors it +heli_think( lifeId, owner, startnode, heli_team, heliType ) +{ + heliOrigin = startnode.origin; + heliAngles = startnode.angles; + + switch( heliType ) + { + case "minigun": + vehicleType = "cobra_minigun_mp"; + if ( owner.team == "allies" ) + vehicleModel = "vehicle_apache_mp"; + else + vehicleModel = "vehicle_mi-28_mp"; + break; + case "flares": + vehicleType = "pavelow_mp"; + if ( owner.team == "allies" ) + vehicleModel = "vehicle_pavelow"; + else + vehicleModel = "vehicle_pavelow_opfor"; + break; + default: + vehicleType = "cobra_mp"; + if ( owner.team == "allies" ) + vehicleModel = "vehicle_cobra_helicopter_fly_low"; + else + vehicleModel = "vehicle_mi24p_hind_mp"; + break; + } + + chopper = spawn_helicopter( owner, heliOrigin, heliAngles, vehicleType, vehicleModel ); + + if ( !isDefined( chopper ) ) + return; + + level.chopper = chopper; + chopper.heliType = heliType; + chopper.lifeId = lifeId; + chopper.team = heli_team; + chopper.pers["team"] = heli_team; + chopper.owner = owner; + + if ( heliType == "flares" ) + chopper.maxhealth = level.heli_maxhealth*2; // max health + else + chopper.maxhealth = level.heli_maxhealth; // max health + + chopper.targeting_delay = level.heli_targeting_delay; // delay between per targeting scan - in seconds + chopper.primaryTarget = undefined; // primary target ( player ) + chopper.secondaryTarget = undefined; // secondary target ( player ) + chopper.attacker = undefined; // last player that shot the helicopter + chopper.currentstate = "ok"; // health state + + if ( heliType == "flares" || heliType == "minigun" ) + chopper thread heli_flares_monitor(); + + // helicopter loop threads + chopper thread heli_leave_on_disconnect( owner ); + chopper thread heli_leave_on_changeTeams( owner ); + chopper thread heli_leave_on_gameended( owner ); + chopper thread heli_damage_monitor(); // monitors damage + chopper thread heli_health(); // display helicopter's health through smoke/fire + chopper thread heli_existance(); + + // flight logic + chopper endon ( "helicopter_done" ); + chopper endon ( "crashing" ); + chopper endon ( "leaving" ); + chopper endon ( "death" ); + + // initial fight into play space + if ( heliType == "minigun" ) + { + owner thread heliRide( lifeId, chopper ); + chopper thread heli_leave_on_spawned( owner ); + } + + attackAreas = getEntArray( "heli_attack_area", "targetname" ); + //attackAreas = []; + loopNode = level.heli_loop_nodes[ randomInt( level.heli_loop_nodes.size ) ]; + + // specific logic per type + switch ( heliType ) + { + case "minigun": + chopper thread heli_targeting(); + chopper heli_fly_simple_path( startNode ); + chopper thread heli_leave_on_timeout( 40.0 ); + if ( attackAreas.size ) + chopper thread heli_fly_well( attackAreas ); + else + chopper thread heli_fly_loop_path( loopNode ); + break; + case "flares": + chopper thread makeGunShip(); + thread teamPlayerCardSplash( "used_helicopter_flares", owner ); + chopper heli_fly_simple_path( startNode ); + chopper thread heli_leave_on_timeout( 60.0 ); + chopper thread heli_fly_loop_path( loopNode ); + break; + default: + chopper thread attack_targets(); + chopper thread heli_targeting(); + chopper heli_fly_simple_path( startNode ); + chopper thread heli_leave_on_timeout( 60.0 ); + chopper thread heli_fly_loop_path( loopNode ); + break; + } +} + + +makeGunShip() +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + wait ( 0.5 ); + + mgTurret = spawnTurret( "misc_turret", self.origin, "pavelow_minigun_mp" ); + mgTurret.lifeId = self.lifeId; + mgTurret linkTo( self, "tag_gunner_left", ( 0,0,0 ), ( 0,0,0) ); + mgTurret setModel( "weapon_minigun" ); + mgTurret.owner = self.owner; + mgTurret.team = self.team; + mgTurret makeTurretInoperable(); + mgTurret.pers["team"] = self.team; + mgTurret.killCamEnt = self; + self.mgTurretLeft = mgTurret; + self.mgTurretLeft SetDefaultDropPitch( 0 ); + + mgTurret = spawnTurret( "misc_turret", self.origin, "pavelow_minigun_mp" ); + mgTurret.lifeId = self.lifeId; + mgTurret linkTo( self, "tag_gunner_right", ( 0,0,0 ), ( 0,0,0) ); + mgTurret setModel( "weapon_minigun" ); + mgTurret.owner = self.owner; + mgTurret.team = self.team; + mgTurret makeTurretInoperable(); + mgTurret.pers["team"] = self.team; + mgTurret.killCamEnt = self; + self.mgTurretRight = mgTurret; + self.mgTurretRight SetDefaultDropPitch( 0 ); + + if ( level.teamBased ) + { + self.mgTurretLeft setTurretTeam( self.team ); + self.mgTurretRight setTurretTeam( self.team ); + } + + self.mgTurretLeft setMode( "auto_nonai" ); + self.mgTurretRight setMode( "auto_nonai" ); + + self.mgTurretLeft SetSentryOwner( self.owner ); + self.mgTurretRight SetSentryOwner( self.owner ); + + self.mgTurretLeft SetTurretMinimapVisible( false ); + self.mgTurretRight SetTurretMinimapVisible( false ); + + self.mgTurretLeft thread sentry_attackTargets(); + self.mgTurretRight thread sentry_attackTargets(); + + self thread deleteTurretsWhenDone(); +} + + +deleteTurretsWhenDone() +{ + self waittill ( "helicopter_done" ); + + self.mgTurretRight delete(); + self.mgTurretLeft delete(); +} + + +sentry_attackTargets() +{ + self endon( "death" ); + self endon ( "helicopter_done" ); + + level endon( "game_ended" ); + + for ( ;; ) + { + self waittill( "turretstatechange" ); + + if ( self isFiringTurret() ) + self thread sentry_burstFireStart(); + else + self thread sentry_burstFireStop(); + } +} + + +sentry_burstFireStart() +{ + self endon( "death" ); + self endon( "stop_shooting" ); + self endon( "leaving" ); + + 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) ) + self shootTurret(); + + wait ( fireTime ); + } + + wait ( randomFloatRange( minPause, maxPause ) ); + } +} + + +sentry_burstFireStop() +{ + self notify( "stop_shooting" ); +} + + +heli_existance() +{ + entityNumber = self getEntityNumber(); + + self waittill_any( "death", "crashing", "leaving" ); + + self removeFromHeliList( entityNumber ); + + self notify( "helicopter_done" ); + + player = undefined; + queueEnt = queueRemoveFirst( "helicopter" ); + if ( !isDefined( queueEnt ) ) + { + level.chopper = undefined; + return; + } + + player = queueEnt.player; + lifeId = queueEnt.lifeId; + streakName = queueEnt.streakName; + heliType = queueEnt.heliType; + queueEnt delete(); + + if ( isDefined( player ) && (player.sessionstate == "playing" || player.sessionstate == "dead") ) + { + player maps\mp\killstreaks\_killstreaks::usedKillstreak( streakName, true ); + player startHelicopter( lifeId, heliType ); + } + else + { + level.chopper = undefined; + } +} + + +// helicopter targeting logic +heli_targeting() +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + // targeting sweep cycle + for ( ;; ) + { + // array of helicopter's targets + targets = []; + self.primaryTarget = undefined; + self.secondaryTarget = undefined; + + players = level.players; + + foreach ( player in level.players ) + { + if ( !canTarget_turret( player ) ) + continue; + + targets[targets.size] = player; + } + + if ( targets.size ) + { + targetPlayer = getBestPrimaryTarget( targets ); + self.primaryTarget = targetPlayer; + self notify( "primary acquired" ); + } + + if ( isDefined( level.harriers ) ) + { + foreach( harrier in level.harriers ) + { + if( !isDefined( harrier ) ) + continue; + + if ( (level.teamBased && harrier.team != self.team) || (!level.teamBased && harrier.owner != self.owner) ) + { + self notify( "secondary acquired" ); + self.secondaryTarget = harrier; + } + } + } + + wait ( 0.5 ); + } +} + +// targetability +canTarget_turret( player ) +{ + canTarget = true; + + if ( !isAlive( player ) || player.sessionstate != "playing" ) + return false; + + if ( self.heliType != "flares" ) + { + if ( !self Vehicle_CanTurretTargetPoint( player.origin+(0,0,40), 1, self ) ) + return false; + } + + if ( distance( player.origin, self.origin ) > level.heli_visual_range ) + return false; + + if ( level.teamBased && player.pers["team"] == self.team ) + return false; + + if ( player == self.owner ) + return false; + + if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= 5 ) + return false; + + if ( player _hasPerk( "specialty_coldblooded" ) ) + return false; + + heli_centroid = self.origin + ( 0, 0, -160 ); + heli_forward_norm = anglestoforward( self.angles ); + heli_turret_point = heli_centroid + 144*heli_forward_norm; + + if ( player sightConeTrace( heli_turret_point, self) < level.heli_target_recognition ) + return false; + + return canTarget; +} + + +getBestPrimaryTarget( targets ) +{ + foreach ( player in targets ) + update_player_threat( player ); + + // find primary target, highest threat level + highest = 0; + primaryTarget = undefined; + + foreach ( player in targets ) + { + assertEx( isDefined( player.threatlevel ), "Target player does not have threat level" ); + + if ( player.threatlevel < highest ) + continue; + + highest = player.threatlevel; + primaryTarget = player; + } + + assertEx( isDefined( primaryTarget ), "Targets exist, but none was assigned as primary" ); + + return ( primaryTarget ); +} + + +// threat factors +update_player_threat( player ) +{ + player.threatlevel = 0; + + // distance factor + dist = distance( player.origin, self.origin ); + player.threatlevel += ( (level.heli_visual_range - dist)/level.heli_visual_range )*100; // inverse distance % with respect to helicopter targeting range + + // behavior factor + if ( isdefined( self.attacker ) && player == self.attacker ) + player.threatlevel += 100; + + // player score factor + player.threatlevel += player.score*4; + + if( isdefined( player.antithreat ) ) + player.threatlevel -= player.antithreat; + + if( player.threatlevel <= 0 ) + player.threatlevel = 1; +} + + +// resets helicopter's motion values +heli_reset() +{ + self clearTargetYaw(); + self clearGoalYaw(); + self Vehicle_SetSpeed( 60, 25 ); + self setyawspeed( 75, 45, 45 ); + //self setjitterparams( (30, 30, 30), 4, 6 ); + self setmaxpitchroll( 30, 30 ); + self setneargoalnotifydist( 256 ); + self setturningability(0.9); +} + + +Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ) +{ + if ( !isDefined( attacker ) || attacker == self ) + return; + + if ( !maps\mp\gameTypes\_weapons::attackerCanDamageItem( attacker, self.owner ) ) + return; + + switch ( weapon ) + { + case "ac130_105mm_mp": + case "ac130_40mm_mp": + case "stinger_mp": + case "javelin_mp": + case "remotemissile_projectile_mp": + self.largeProjectileDamage = true; + damage = self.maxhealth + 1; + break; + } + + if( self.damageTaken+damage >= self.maxhealth ) + { + validAttacker = undefined; + + if ( !isDefined(self.owner) || attacker != self.owner ) + validAttacker = attacker; + + if ( isDefined( validAttacker ) ) + { + validAttacker notify( "destroyed_killstreak", weapon ); + } + } + + self Vehicle_FinishDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName ); +} + + +addRecentDamage( damage ) +{ + self endon( "death" ); + + self.recentDamageAmount += damage; + + wait ( 4.0 ); + self.recentDamageAmount -= damage; +} + + +// accumulate damage and react +heli_damage_monitor() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + self.damageTaken = 0; + self.recentDamageAmount = 0; + + for( ;; ) + { + // this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is. + self waittill( "damage", damage, attacker, direction_vec, P, type ); + + assert( isDefined( attacker ) ); + + self.attacker = attacker; + + if ( isPlayer( attacker ) ) + { + attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "" ); + + if ( type == "MOD_RIFLE_BULLET" || type == "MOD_PISTOL_BULLET" ) + { + damage *= level.heli_armor_bulletdamage; + + if ( attacker _hasPerk( "specialty_armorpiercing" ) ) + damage += damage*level.armorPiercingMod; + } + } + + self.damageTaken += damage; + + self thread addRecentDamage( damage ); + + if( self.damageTaken > self.maxhealth && ((level.teamBased && self.team != attacker.team) || !level.teamBased) ) + { + validAttacker = undefined; + if ( isDefined( attacker.owner ) && (!isDefined(self.owner) || attacker.owner != self.owner) ) + validAttacker = attacker.owner; + else if ( !isDefined(attacker.owner) && attacker.classname == "script_vehicle" ) + return; + else if ( !isDefined(self.owner) || attacker != self.owner ) + validAttacker = attacker; + + if ( isDefined( validAttacker ) ) + { + attacker notify( "destroyed_helicopter" ); + + if ( self.heliType == "flares" ) + { + thread teamPlayerCardSplash( "callout_destroyed_helicopter_flares", validAttacker ); + xpVal = 400; + } + else if ( self.heliType == "minigun" ) + { + thread teamPlayerCardSplash( "callout_destroyed_helicopter_minigun", validAttacker ); + xpVal = 300; + } + else + { + thread teamPlayerCardSplash( "callout_destroyed_helicopter", validAttacker ); + xpVal = 200; + } + + validAttacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", xpVal ); + thread maps\mp\gametypes\_missions::vehicleKilled( self.owner, self, undefined, validAttacker, damage, type ); + + } + } + } +} + + +heli_health() +{ + self endon( "death" ); + self endon( "leaving" ); + self endon( "crashing" ); + + self.currentstate = "ok"; + self.laststate = "ok"; + self setdamagestage( 3 ); + + damageState = 3; + self setDamageStage( damageState ); + + for ( ;; ) + { + if ( self.damageTaken >= (self.maxhealth * 0.33) && damageState == 3 ) + { + damageState = 2; + self setDamageStage( damageState ); + self.currentstate = "light smoke"; + playFxOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_engine_left" ); + } + else if ( self.damageTaken >= (self.maxhealth * 0.66) && damageState == 2 ) + { + damageState = 1; + self setDamageStage( damageState ); + self.currentstate = "heavy smoke"; + stopFxOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_engine_left" ); + playFxOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_engine_left" ); + } + else if( self.damageTaken > self.maxhealth ) + { + damageState = 0; + self setDamageStage( damageState ); + + stopFxOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_engine_left" ); + + if ( IsDefined( self.largeProjectileDamage ) && self.largeProjectileDamage ) + { + self thread heli_explode( true ); + } + else + { + playFxOnTag( level.chopper_fx["damage"]["on_fire"], self, "tag_engine_left" ); + self thread heli_crash(); + } + } + + wait 0.05; + } +} + + +// attach helicopter on crash path +heli_crash() +{ + self notify( "crashing" ); + + crashNode = level.heli_crash_nodes[ randomInt( level.heli_crash_nodes.size ) ]; + + self thread heli_spin( 180 ); + self thread heli_secondary_explosions(); + self heli_fly_simple_path( crashNode ); + + self thread heli_explode(); +} + +heli_secondary_explosions() +{ + playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" ); + self playSound ( level.heli_sound[self.team]["hitsecondary"] ); + + wait ( 3.0 ); + + if ( !isDefined( self ) ) + return; + + playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" ); + self playSound ( level.heli_sound[self.team]["hitsecondary"] ); +} + +// self spin at one rev per 2 sec +heli_spin( speed ) +{ + self endon( "death" ); + + // play hit sound immediately so players know they got it + self playSound ( level.heli_sound[self.team]["hit"] ); + + // play heli crashing spinning sound + self thread spinSoundShortly(); + + // spins until death + self setyawspeed( speed, speed, speed ); + while ( isdefined( self ) ) + { + self settargetyaw( self.angles[1]+(speed*0.9) ); + wait ( 1 ); + } +} + + +spinSoundShortly() +{ + self endon("death"); + + wait .25; + + self stopLoopSound(); + wait .05; + self playLoopSound( level.heli_sound[self.team]["spinloop"] ); + wait .05; + self playLoopSound( level.heli_sound[self.team]["spinstart"] ); +} + + +// crash explosion +heli_explode( altStyle ) +{ + self notify( "death" ); + + if ( isDefined( altStyle ) && isDefined( level.chopper_fx["explode"]["air_death"][self.heli_type] ) ) + { + deathAngles = self getTagAngles( "tag_deathfx" ); + + playFx( level.chopper_fx["explode"]["air_death"][self.heli_type], self getTagOrigin( "tag_deathfx" ), anglesToForward( deathAngles ), anglesToUp( deathAngles ) ); + //playFxOnTag( level.chopper_fx["explode"]["air_death"][self.heli_type], self, "tag_deathfx" ); + } + else + { + org = self.origin; + forward = ( self.origin + ( 0, 0, 1 ) ) - self.origin; + playFx( level.chopper_fx["explode"]["death"][self.heli_type], org, forward ); + } + + + // play heli explosion sound + self playSound( level.heli_sound[self.team]["crash"] ); + + // give "death" notify time to process + wait ( 0.05 ); + self delete(); +} + + +fire_missile( sMissileType, iShots, eTarget ) +{ + if ( !isdefined( iShots ) ) + iShots = 1; + assert( self.health > 0 ); + + weaponName = undefined; + weaponShootTime = undefined; + defaultWeapon = "cobra_20mm_mp"; + tags = []; + switch( sMissileType ) + { + case "ffar": + weaponName = "harrier_FFAR_mp"; + + tags[ 0 ] = "tag_store_r_2"; + break; + default: + assertMsg( "Invalid missile type specified. Must be ffar" ); + break; + } + assert( isdefined( weaponName ) ); + assert( tags.size > 0 ); + + weaponShootTime = weaponfiretime( weaponName ); + assert( isdefined( weaponShootTime ) ); + + self setVehWeapon( weaponName ); + nextMissileTag = -1; + for( i = 0 ; i < iShots ; i++ ) // I don't believe iShots > 1 is properly supported; we don't set the weapon each time + { + nextMissileTag++; + if ( nextMissileTag >= tags.size ) + nextMissileTag = 0; + + self setVehWeapon( "harrier_FFAR_mp" ); + + if ( isdefined( eTarget ) ) + { + eMissile = self fireWeapon( tags[ nextMissileTag ], eTarget ); + eMissile Missile_SetFlightmodeDirect(); + eMissile Missile_SetTargetEnt( eTarget ); + } + else + { + eMissile = self fireWeapon( tags[ nextMissileTag ] ); + eMissile Missile_SetFlightmodeDirect(); + eMissile Missile_SetTargetEnt( eTarget ); + } + + if ( i < iShots - 1 ) + wait weaponShootTime; + } + // avoid calling setVehWeapon again this frame or the client doesn't hear about the original weapon change +} + +// checks if owner is valid, returns false if not valid +check_owner() +{ + if ( !isdefined( self.owner ) || !isdefined( self.owner.pers["team"] ) || self.owner.pers["team"] != self.team ) + { + self thread heli_leave(); + + return false; + } + + return true; +} + + +heli_leave_on_disconnect( owner ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + owner waittill( "disconnect" ); + + self thread heli_leave(); +} + +heli_leave_on_changeTeams( owner ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + owner waittill_any( "joined_team", "joined_spectators" ); + + self thread heli_leave(); +} + +heli_leave_on_spawned( owner ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + owner waittill( "spawned" ); + + self thread heli_leave(); +} + +heli_leave_on_gameended( owner ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + level waittill ( "game_ended" ); + + self thread heli_leave(); +} + +heli_leave_on_timeout( timeOut ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeOut ); + + self thread heli_leave(); +} + +attack_targets() +{ + //self thread turret_kill_players(); + self thread attack_primary(); + self thread attack_secondary(); +} + + +// missile only +attack_secondary() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + for( ;; ) + { + if ( isdefined( self.secondaryTarget ) ) + { + self.secondaryTarget.antithreat = undefined; + self.missileTarget = self.secondaryTarget; + + antithreat = 0; + + while( isdefined( self.missileTarget ) && isalive( self.missileTarget ) ) + { + // if selected target is not in missile hit range, skip + if( self missile_target_sight_check( self.missileTarget ) ) + self thread missile_support( self.missileTarget, level.heli_missile_rof); + else + break; + + self waittill( "missile ready" ); + + // target might disconnect or change during last assault cycle + if ( !isdefined( self.secondaryTarget ) || ( isdefined( self.secondaryTarget ) && self.missileTarget != self.secondaryTarget ) ) + break; + } + // reset the antithreat factor + if ( isdefined( self.missileTarget ) ) + self.missileTarget.antithreat = undefined; + } + self waittill( "secondary acquired" ); + + // check if owner has left, if so, leave + self check_owner(); + } +} + +// check if missile is in hittable sight zone +missile_target_sight_check( missiletarget ) +{ + heli2target_normal = vectornormalize( missiletarget.origin - self.origin ); + heli2forward = anglestoforward( self.angles ); + heli2forward_normal = vectornormalize( heli2forward ); + + heli_dot_target = vectordot( heli2target_normal, heli2forward_normal ); + + if ( heli_dot_target >= level.heli_missile_target_cone ) + { + debug_print3d_simple( "Missile sight: " + heli_dot_target, self, ( 0,0,-40 ), 40 ); + return true; + } + return false; +} + +// if wait for turret turning is too slow, enable missile assault support +missile_support( target_player, rof ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + if ( isdefined( target_player ) ) + { + if ( level.teambased ) + { + if ( isDefined( target_player.owner ) && target_player.team != self.team ) + { + self fire_missile( "ffar", 1, target_player ); + self notify( "missile fired" ); + } + } + else + { + if ( isDefined( target_player.owner ) && target_player.owner != self.owner ) + { + self fire_missile( "ffar", 1, target_player ); + self notify( "missile fired" ); + } + } + } + + wait ( rof ); + self notify ( "missile ready" ); + + return; +} + +// mini-gun with missile support +attack_primary() +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + while ( 1 ) + { + wait ( 0.05 ); + + if ( !isAlive( self.primaryTarget ) ) + continue; + + currentTarget = self.primaryTarget; + + currentTarget.antithreat = 0; + + if ( randomInt(5) < 3 ) + angle = currentTarget.angles[1] + randomFloatRange( -30, 30 ); + else + angle = randomInt( 360 ); + + radiusOffset = 96; + + xOffset = cos( angle ) * radiusOffset; + yOffset = sin( angle ) * radiusOffset; + + self setTurretTargetEnt( currentTarget, (xOffset,yOffset,40) ); + + self waitOnTargetOrDeath( currentTarget, 3.0 ); + + if ( !isAlive( currentTarget ) || !self Vehicle_CanTurretTargetPoint( currentTarget.origin+(0,0,40) ) ) + continue; + + weaponShootTime = weaponFireTime( "cobra_20mm_mp" ); + + convergenceMod = 1; + shotsSinceLastSighting = 0; + + self playLoopSound( "weap_cobra_20mm_fire_npc" ); + for ( i = 0; i < level.heli_turretClipSize; i++ ) + { + self setVehWeapon( "cobra_20mm_mp" ); + self fireWeapon( "tag_flash" ); + + if ( i < level.heli_turretClipSize - 1 ) + wait weaponShootTime; + + if ( !isDefined( currentTarget ) ) + break; + + if ( self Vehicle_CanTurretTargetPoint( currentTarget.origin+(0,0,40), 1, self ) ) + { + convergenceMod = max( convergenceMod - 0.05, 0 ); + shotsSinceLastSighting = 0; + } + else + { + shotsSinceLastSighting++; + } + + if ( shotsSinceLastSighting > 10 ) + break; + + targetPos = ( (xOffset*convergenceMod)+randomFloatRange( -6, 6 ),(yOffset*convergenceMod)+randomFloatRange( -6, 6 ),40+randomFloatRange( -6, 6 ) ); + + self setTurretTargetEnt( currentTarget, targetPos ); + } + self stopLoopSound(); + + // lower the target's threat since already assaulted on + if ( isAlive( currentTarget ) ) + currentTarget.antithreat += 100; + + wait ( randomFloatRange( 0.5, 2.0 ) ); + } +} + +waitOnTargetOrDeath( target, timeOut ) +{ + self endon ( "death" ); + self endon ( "helicopter_done" ); + + target endon ( "death" ); + target endon ( "disconnect" ); + + self waittill_notify_or_timeout( "turret_on_target", timeOut ); +} + + +fireMissile( missileTarget ) +{ + self endon( "death" ); + self endon( "crashing" ); + self endon( "leaving" ); + + assert( self.health > 0 ); + + if ( !isdefined( missileTarget ) ) + return; + + if ( Distance2D(self.origin, missileTarget.origin ) < 512 ) + return; + + self setVehWeapon( "harrier_FFAR_mp" ); + missile = self fireWeapon( "tag_flash", missileTarget ); + missile Missile_SetFlightmodeDirect(); + missile Missile_SetTargetEnt( missileTarget ); +} + + +// ==================================================================================== +// Helicopter Pathing Logic +// ==================================================================================== + +getOriginOffsets( goalNode ) +{ + startOrigin = self.origin; + endOrigin = goalNode.origin; + + numTraces = 0; + maxTraces = 40; + + traceOffset = (0,0,-196); + + traceOrigin = physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ); + + while ( distance( traceOrigin, endOrigin+traceOffset ) > 10 && numTraces < maxTraces ) + { + println( "trace failed: " + distance( physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ), endOrigin+traceOffset ) ); + + if ( startOrigin[2] < endOrigin[2] ) + { + startOrigin += (0,0,128); + } + else if ( startOrigin[2] > endOrigin[2] ) + { + endOrigin += (0,0,128); + } + else + { + startOrigin += (0,0,128); + endOrigin += (0,0,128); + } + + //thread draw_line( startOrigin+traceOffset, endOrigin+traceOffset, (0,1,9), 200 ); + numTraces++; + + traceOrigin = physicsTrace( startOrigin+traceOffset, endOrigin+traceOffset ); + } + + offsets = []; + offsets["start"] = startOrigin; + offsets["end"] = endOrigin; + return offsets; +} + + +travelToNode( goalNode ) +{ + originOffets = getOriginOffsets( goalNode ); + + if ( originOffets["start"] != self.origin ) + { + // motion change via node + if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) + { + heli_speed = goalNode.script_airspeed; + heli_accel = goalNode.script_accel; + } + else + { + heli_speed = 30+randomInt(20); + heli_accel = 15+randomInt(15); + } + + self Vehicle_SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( originOffets["start"] + (0,0,30), 0 ); + // calculate ideal yaw + self setgoalyaw( goalNode.angles[ 1 ] + level.heli_angle_offset ); + + //println( "setting goal to startOrigin" ); + + self waittill ( "goal" ); + } + + if ( originOffets["end"] != goalNode.origin ) + { + // motion change via node + if( isdefined( goalNode.script_airspeed ) && isdefined( goalNode.script_accel ) ) + { + heli_speed = goalNode.script_airspeed; + heli_accel = goalNode.script_accel; + } + else + { + heli_speed = 30+randomInt(20); + heli_accel = 15+randomInt(15); + } + + self Vehicle_SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( originOffets["end"] + (0,0,30), 0 ); + // calculate ideal yaw + self setgoalyaw( goalNode.angles[ 1 ] + level.heli_angle_offset ); + + //println( "setting goal to endOrigin" ); + + self waittill ( "goal" ); + } +} + + +heli_fly_simple_path( startNode ) +{ + self endon ( "death" ); + self endon ( "leaving" ); + + // only one thread instance allowed + self notify( "flying"); + self endon( "flying" ); + + heli_reset(); + + currentNode = startNode; + while ( isDefined( currentNode.target ) ) + { + nextNode = getEnt( currentNode.target, "targetname" ); + assertEx( isDefined( nextNode ), "Next node in path is undefined, but has targetname" ); + + if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) + { + heli_speed = currentNode.script_airspeed; + heli_accel = currentNode.script_accel; + } + else + { + heli_speed = 30 + randomInt(20); + heli_accel = 15 + randomInt(15); + } + + self Vehicle_SetSpeed( heli_speed, heli_accel ); + + // end of the path + if ( !isDefined( nextNode.target ) ) + { + self setVehGoalPos( nextNode.origin+(self.zOffset), true ); + self waittill( "near_goal" ); + } + else + { + self setVehGoalPos( nextNode.origin+(self.zOffset), false ); + self waittill( "near_goal" ); + + self setGoalYaw( nextNode.angles[ 1 ] ); + + self waittillmatch( "goal" ); + } + + currentNode = nextNode; + } + + printLn( currentNode.origin ); + printLn( self.origin ); +} + + +heli_fly_loop_path( startNode ) +{ + self endon ( "death" ); + self endon ( "crashing" ); + self endon ( "leaving" ); + + // only one thread instance allowed + self notify( "flying"); + self endon( "flying" ); + + heli_reset(); + + self thread heli_loop_speed_control( startNode ); + + currentNode = startNode; + while ( isDefined( currentNode.target ) ) + { + nextNode = getEnt( currentNode.target, "targetname" ); + assertEx( isDefined( nextNode ), "Next node in path is undefined, but has targetname" ); + + if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) + { + self.desired_speed = currentNode.script_airspeed; + self.desired_accel = currentNode.script_accel; + } + else + { + self.desired_speed = 30 + randomInt( 20 ); + self.desired_accel = 15 + randomInt( 15 ); + } + + if ( self.heliType == "flares" ) + { + self.desired_speed *= 0.5; + self.desired_accel *= 0.5; + } + + if ( isDefined( nextNode.script_delay ) && isDefined( self.primaryTarget ) && !self heli_is_threatened() ) + { + self setVehGoalPos( nextNode.origin+(self.zOffset), true ); + self waittill( "near_goal" ); + + wait ( nextNode.script_delay ); + } + else + { + self setVehGoalPos( nextNode.origin+(self.zOffset), false ); + self waittill( "near_goal" ); + + self setGoalYaw( nextNode.angles[ 1 ] ); + + self waittillmatch( "goal" ); + } + + currentNode = nextNode; + } +} + + +heli_loop_speed_control( currentNode ) +{ + self endon ( "death" ); + self endon ( "crashing" ); + self endon ( "leaving" ); + + if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) ) + { + self.desired_speed = currentNode.script_airspeed; + self.desired_accel = currentNode.script_accel; + } + else + { + self.desired_speed = 30 + randomInt( 20 ); + self.desired_accel = 15 + randomInt( 15 ); + } + + lastSpeed = 0; + lastAccel = 0; + + while ( 1 ) + { + goalSpeed = self.desired_speed; + goalAccel = self.desired_accel; + + if ( self.heliType != "flares" && isDefined( self.primaryTarget ) && !self heli_is_threatened() ) + goalSpeed *= 0.25; + + if ( lastSpeed != goalSpeed || lastAccel != goalAccel ) + { + self Vehicle_SetSpeed( goalSpeed, goalAccel ); + + lastSpeed = goalSpeed; + lastAccel = goalAccel; + } + + wait ( 0.05 ); + } +} + + +heli_is_threatened() +{ + if ( self.recentDamageAmount > 50 ) + return true; + + if ( self.currentState == "heavy smoke" ) + return true; + + return false; +} + + +heli_fly_well( destNodes ) +{ + self notify( "flying"); + self endon( "flying" ); + + self endon ( "death" ); + self endon ( "crashing" ); + self endon ( "leaving" ); + + for ( ;; ) + { + currentNode = self get_best_area_attack_node( destNodes ); + + travelToNode( currentNode ); + + // motion change via node + if( isdefined( currentNode.script_airspeed ) && isdefined( currentNode.script_accel ) ) + { + heli_speed = currentNode.script_airspeed; + heli_accel = currentNode.script_accel; + } + else + { + heli_speed = 30+randomInt(20); + heli_accel = 15+randomInt(15); + } + + self Vehicle_SetSpeed( heli_speed, heli_accel ); + self setvehgoalpos( currentNode.origin + self.zOffset, 1 ); + self setgoalyaw( currentNode.angles[ 1 ] + level.heli_angle_offset ); + + if ( level.heli_forced_wait != 0 ) + { + self waittill( "near_goal" ); //self waittillmatch( "goal" ); + wait ( level.heli_forced_wait ); + } + else if ( !isdefined( currentNode.script_delay ) ) + { + self waittill( "near_goal" ); //self waittillmatch( "goal" ); + + wait ( 5 + randomInt( 5 ) ); + } + else + { + self waittillmatch( "goal" ); + wait ( currentNode.script_delay ); + } + } +} + + +get_best_area_attack_node( destNodes ) +{ + return updateAreaNodes( destNodes ); +} + + +// helicopter leaving parameter, can not be damaged while leaving +heli_leave() +{ + self notify( "leaving" ); + + leaveNode = level.heli_leave_nodes[ randomInt( level.heli_leave_nodes.size ) ]; + + self heli_reset(); + self Vehicle_SetSpeed( 100, 45 ); + self setvehgoalpos( leaveNode.origin, 1 ); + self waittillmatch( "goal" ); + self notify( "death" ); + + // give "death" notify time to process + wait ( 0.05 ); + self delete(); +} + + +// ==================================================================================== +// DEBUG INFORMATION +// ==================================================================================== + +debug_print3d( message, color, ent, origin_offset, frames ) +{ + if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) + self thread draw_text( message, color, ent, origin_offset, frames ); +} + +debug_print3d_simple( message, ent, offset, frames ) +{ + if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) + { + if( isdefined( frames ) ) + thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, frames ); + else + thread draw_text( message, ( 0.8, 0.8, 0.8 ), ent, offset, 0 ); + } +} + +debug_line( from, to, color, frames ) +{ + if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 && !isdefined( frames ) ) + { + thread draw_line( from, to, color ); + } + else if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 ) + thread draw_line( from, to, color, frames); +} + +draw_text( msg, color, ent, offset, frames ) +{ + //level endon( "helicopter_done" ); + if( frames == 0 ) + { + while ( isdefined( ent ) ) + { + print3d( ent.origin+offset, msg , color, 0.5, 4 ); + wait 0.05; + } + } + else + { + for( i=0; i < frames; i++ ) + { + if( !isdefined( ent ) ) + break; + print3d( ent.origin+offset, msg , color, 0.5, 4 ); + wait 0.05; + } + } +} + +draw_line( from, to, color, frames ) +{ + //level endon( "helicopter_done" ); + if( isdefined( frames ) ) + { + for( i=0; i