#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" ); self notifyOnPlayerCommand( "switch thermal", "+usereload" ); 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