#include maps\mp\_utility; #include common_scripts\utility; /* A10 killstreak: the player uses the mini-map selector to select a place to strafe */ KS_NAME = "a10_strafe"; kTransitionTime = 0.75; kLockIconOffset = (0, 0, -70); init() { precacheLocationSelector( "map_artillery_selector" ); config = SpawnStruct(); config.modelNames = []; config.modelNames[ "allies" ] = "vehicle_a10_warthog_iw6_mp"; config.modelNames[ "axis" ] = "vehicle_a10_warthog_iw6_mp"; config.vehicle = "a10_warthog_mp"; config.inboundSfx = "veh_mig29_dist_loop"; //config.inboundSfx = "veh_aastrike_flyover_loop"; //config.outboundSfx = "veh_aastrike_flyover_outgoing_loop"; //config.compassIconFriendly = "compass_objpoint_a10_friendly"; //config.compassIconEnemy = "compass_objpoint_a10_enemy"; // sonic boom? config.speed = 3000; config.halfDistance = 12500; config.heightRange = 750; config.chooseDirection = true; config.selectLocationVO = "KS_hqr_airstrike"; config.inboundVO = "KS_ast_inbound"; config.cannonFireVfx = LoadFX( "fx/smoke/smoke_trail_white_heli" ); config.cannonRumble = "ac130_25mm_fire"; config.turretName = "a10_30mm_turret_mp"; config.turretAttachPoint = "tag_barrel"; config.rocketModelName = "maverick_projectile_mp"; config.numRockets = 4; config.delayBetweenRockets = 0.125; config.delayBetweenLockon = 0.4; config.lockonIcon = "veh_hud_target_chopperfly"; // "veh_hud_target_lock" config.maxHealth = 1000; config.xpPopup = "destroyed_a10_strafe"; config.callout = "callout_destroyed_a10"; config.voDestroyed = undefined; config.explodeVfx = LoadFX( "fx/explosions/aerial_explosion"); // holy crap, lots of sfx config.sfxCannonFireLoop_1p = "veh_a10_plr_fire_gatling_lp"; config.sfxCannonFireStop_1p = "veh_a10_plr_fire_gatling_cooldown"; config.sfxCannonFireLoop_3p = "veh_a10_npc_fire_gatling_lp"; config.sfxCannonFireStop_3p = "veh_a10_npc_fire_gatling_cooldown"; config.sfxCannonFireBurpTime = 500; config.sfxCannonFireBurpShort_3p = "veh_a10_npc_fire_gatling_short_burst"; config.sfxCannonFireBurpLong_3p = "veh_a10_npc_fire_gatling_long_burst"; config.sfxCannonBulletImpact = "veh_a10_bullet_impact_lp"; // loop, should play on moving entity config.sfxMissileFire_1p = []; config.sfxMissileFire_1p[0] = "veh_a10_plr_missile_ignition_left"; config.sfxMissileFire_1p[1] = "veh_a10_plr_missile_ignition_right"; config.sfxMissileFire_3p = "veh_a10_npc_missile_fire"; config.sfxMissile = "veh_a10_missile_loop"; config.sfxEngine_1p = "veh_a10_plr_engine_lp"; config.sfxEngine_3p = "veh_a10_dist_loop"; level.planeConfigs[ KS_NAME ] = config; level.killstreakFuncs[KS_NAME] = ::onUse; /* a10 sounds a10p_gatling_loop a10p_gatling_tail a10p_missile_launch a10p_impact */ buildAllFlightPathsDefault(); } onUse( lifeId, streakName ) { assert( isDefined( self ) ); if ( IsDefined( level.a10strafeActive ) ) { self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" ); return false; } else if ( self isUsingRemote() || self isKillStreakDenied() ) { return false; } else if ( GetCSplineCount() < 2 ) { PrintLn( "ERROR: need at least two CSpline paths for A10 strafing run. Please add them to your level." ); return false; } else { self thread doStrike( lifeId, KS_NAME ); return true; } } doStrike( lifeId, streakName ) //self == player { self endon ("end_remote"); self endon ("death"); level endon ("game_ended"); pathIndex = getPathIndex(); print( " A10 fly path (" + level.a10SplinesIn[ pathIndex ] + ", " + level.a10SplinesOut[ pathIndex ] + ")\n" ); result = self startStrafeSequence( streakName, lifeId ); if ( result ) { // randomize the order of the whether to pick inbound or outbound? plane = spawnAircraft( streakName, lifeId, level.a10SplinesIn[ pathIndex ] ); if ( isDefined( plane ) ) { plane doOneFlyby(); self switchAircraft( plane, streakName ); plane = spawnAircraft( streakName, lifeId, level.a10SplinesIn[ pathIndex ] ); if ( isDefined( plane ) ) { self thread maps\mp\killstreaks\_killstreaks::clearRideIntro( 1.0, kTransitionTime ); plane doOneFlyby(); plane thread endFlyby( streakName ); self endStrafeSequence( streakName ); } } } } startStrafeSequence( streakName, lifeId ) // self == owner { self setUsingRemote( KS_NAME ); if( GetDvarInt( "camera_thirdPerson" ) ) self setThirdPersonDOF( false ); self.restoreAngles = self.angles; self freezeControlsWrapper( true ); result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( KS_NAME ); if( result != "success" ) { if ( result != "disconnect" ) self clearUsingRemote(); if( IsDefined( self.disabledWeapon ) && self.disabledWeapon ) self _enableWeapon(); self notify( "death" ); return false; } if( self isJuggernaut() && IsDefined( self.juggernautOverlay ) ) { self.juggernautOverlay.alpha = 0; } self freezeControlsWrapper( false ); level.a10strafeActive = true; self.using_remote_a10 = true; level thread teamPlayerCardSplash( "used_" + streakName, self, self.team ); return true; } endStrafeSequence( streakName ) { self clearUsingRemote(); if( GetDvarInt( "camera_thirdPerson" ) ) { self setThirdPersonDOF( true ); } if( self isJuggernaut() && IsDefined( self.juggernautOverlay ) ) { self.juggernautOverlay.alpha = 1; } self SetPlayerAngles( self.restoreAngles ); self.restoreAngles = undefined; self thread a10_FreezeBuffer(); // play outbound vo level.a10strafeActive = undefined; self.using_remote_a10 = undefined; } switchAircraft( plane, streakName ) // self == player { // !!!! hack // we don't want to call clearUsingRemote because we want to stay in remote // but we have to clear this flag so that setUsingRemote during the second pass works self.usingRemote = undefined; self VisionSetNakedForPlayer( "black_bw", kTransitionTime ); self thread set_visionset_for_watching_players( "black_bw", kTransitionTime, kTransitionTime ); wait( kTransitionTime ); if ( IsDefined( plane ) ) { plane thread endFlyby( streakName ); } // play some VO to indicate a 2nd pilot } spawnAircraft( streakName, lifeId, splineId ) { plane = createPlaneAsHeli( streakName, lifeId, splineId ); if ( !isDefined( plane ) ) return undefined; plane.streakName = streakName; // plane endon( "death" ); self RemoteControlVehicle( plane ); plane SetPlaneSplineId( self, splineId ); // plane attachTurret( streakName ); self thread watchIntroCleared( streakName, plane ); config = level.planeConfigs[ streakName ]; plane PlayLoopSound( config.sfxEngine_1p ); // add damage handling plane thread a10_handleDamage(); maps\mp\killstreaks\_plane::startTrackingPlane( plane ); return plane; } attachTurret( streakName ) // self == plane { config = level.planeConfigs[ streakName ]; turretPos = self GetTagOrigin( config.turretAttachPoint ); turret = SpawnTurret( "misc_turret", self.origin + turretPos, config.turretName, false ); turret LinkTo( self, config.turretAttachPoint, ( 0, 0, 0 ), ( 0, 0, 0 ) ); turret SetModel( "vehicle_ugv_talon_gun_mp" ); turret.angles = self.angles; turret.owner = self.owner; // set model? turret MakeTurretInoperable(); turret SetTurretModeChangeWait( false ); turret SetMode( "sentry_offline" ); turret MakeUnusable(); turret SetCanDamage( false ); turret SetSentryOwner( self.owner ); self.owner RemoteControlTurret( turret ); self.turret = turret; } cleanupAircraft() { if ( IsDefined( self.turret ) ) { self.turret Delete(); } foreach ( targetInfo in self.targetList ) { if ( IsDefined( targetInfo["icon"] ) ) { targetInfo["icon"] Destroy(); targetInfo["icon"] = undefined; } } self Delete(); } getPathIndex() { return ( RandomInt(level.a10SplinesIn.size ) ); } doOneFlyby() { self endon( "death" ); level endon( "game_ended" ); while ( true ) { // also wait for death of plane self waittill ( "splinePlaneReachedNode", nodeLabel ); if ( IsDefined( nodeLabel ) && nodeLabel == "End" ) { self notify( "a10_end_strafe" ); break; } } } endFlyby( streakName ) { if( !IsDefined( self ) ) return; // disconnect the player from the plane self.owner RemoteControlVehicleOff( self ); if ( IsDefined( self.turret ) ) { self.owner RemoteControlTurretOff( self.turret ); } self notify( "end_remote" ); self.owner SetClientOmnvar( "ui_a10", false ); self.owner ThermalVisionFOFOverlayOff(); config = level.planeConfigs[ streakName ]; self StopLoopSound( config.sfxCannonFireLoop_1p ); maps\mp\killstreaks\_plane::stopTrackingPlane( self ); // let it fly away wait( 5 ); if (IsDefined( self ) ) { self StopLoopSound( config.sfxEngine_1p ); self cleanupAircraft(); // self notify("delete"); } } createPlaneAsHeli( streakName, lifeId, splineId ) // self == player { // get plane config config = level.planeConfigs[ streakName ]; // get the start pos and tangent of the spline startPos = GetCSplinePointPosition( splineId, 0 ); startTangent = GetCSplinePointTangent( splineId, 0 ); // calculate start angles startAngles = VectorToAngles( startTangent ); // spawn plane plane = SpawnHelicopter( self, startPos, startAngles, config.vehicle, config.modelNames[ self.team ] ); if ( !IsDefined( plane ) ) return undefined; // set plane to be solid plane MakeVehicleSolidCapsule( 18, -9, 18 ); // set plane owner/team plane.owner = self; plane.team = self.team; // set plane life id plane.lifeId = lifeId; // start fx plane thread maps\mp\killstreaks\_plane::playPlaneFX(); // return the plane return plane; } handleDeath() // self == plane { level endon( "game_ended" ); self endon( "delete" ); self waittill( "death" ); // not sure if this will even work // self.owner stopPilot( self ); level.a10strafeActive = undefined; self.owner.using_remote_a10 = undefined; self delete(); } a10_FreezeBuffer() { self endon( "disconnect" ); self endon( "death" ); level endon( "game_ended" ); self freezeControlsWrapper( true ); wait( 0.5 ); self freezeControlsWrapper( false ); } monitorRocketFire( streakName, plane ) // self == player { plane endon( "end_remote" ); plane endon( "death" ); self endon( "death" ); level endon( "game_ended" ); config = level.planeConfigs[ streakName ]; plane.numRocketsLeft = config.numRockets; self NotifyOnPlayerCommand( "rocket_fire_pressed", "+speed_throw" ); self NotifyOnPlayerCommand( "rocket_fire_pressed", "+ads_akimbo_accessible" ); if( !level.console ) { self NotifyOnPlayerCommand( "rocket_fire_pressed", "+toggleads_throw" ); } self SetClientOmnvar( "ui_a10_rocket", plane.numRocketsLeft ); while (plane.numRocketsLeft > 0) { self waittill( "rocket_fire_pressed" ); plane onFireRocket( streakName ); wait( config.delayBetweenRockets ); } } monitorRocketFire2(streakName, plane) { plane endon( "end_remote" ); plane endon( "death" ); self endon( "death" ); level endon( "game_ended" ); config = level.planeConfigs[ streakName ]; plane.numRocketsLeft = config.numRockets; self NotifyOnPlayerCommand( "rocket_fire_pressed", "+speed_throw" ); self NotifyOnPlayerCommand( "rocket_fire_pressed", "+ads_akimbo_accessible" ); if( !level.console ) { self NotifyOnPlayerCommand( "rocket_fire_pressed", "+toggleads_throw" ); } plane.targetList = []; self SetClientOmnvar( "ui_a10_rocket", plane.numRocketsLeft ); while ( plane.numRocketsLeft > 0 ) { if ( !(self AdsButtonPressed()) ) { self waittill( "rocket_fire_pressed" ); } plane missileAcquireTargets(); if ( plane.targetList.size > 0 ) { plane thread fireMissiles(); } } } missileGetBestTarget() // self == plane { candidateList = []; foreach (player in level.players) { if (self missileIsGoodTarget(player)) { candidateList[ candidateList.size ] = player; } } foreach (uplink in level.uplinks) { if (self missileIsGoodTarget(uplink)) { candidateList[ candidateList.size ] = uplink; } } // satcoms? // ugvs? if ( candidateList.size > 0 ) { sortedCandidateList = SortByDistance(candidateList, self.origin); return sortedCandidateList[0]; } return undefined; } missileIsGoodTarget( target ) // self == plane { return ( IsAlive(target) && target.team != self.owner.team && !(self isMissileTargeted( target )) && (IsPlayer( target ) && !(target _hasPerk( "specialty_blindeye" ))) // && (self.owner WorldPointInReticle_Circle(target.origin, 65, 200)) && self missileTargetAngle( target ) > 0.25 ); } // this needs to be optimized missileTargetAngle( target ) // self == plane { dirToTarget = VectorNormalize( target.origin - self.origin ); facingDir = AnglesToForward( self.angles ); return VectorDot( dirToTarget, facingDir ); } missileAcquireTargets() { self endon ("death"); self endon( "end_remote" ); level endon ("game_ended"); self endon ("a10_missiles_fired"); config = level.planeConfigs[ self.streakName ]; self.owner SetClientOmnvar( "ui_a10_rocket_lock", true ); self thread missileWaitForTriggerRelease(); currentTarget = undefined; while ( self.targetList.size < self.numRocketsLeft ) { if ( !IsDefined( currentTarget ) ) { currentTarget = self missileGetBestTarget(); if ( IsDefined( currentTarget ) ) { self thread missileLockTarget( currentTarget ); wait (config.delayBetweenLockon); currentTarget = undefined; continue; } } wait (0.1); } self.owner SetClientOmnvar( "ui_a10_rocket_lock", false ); self notify( "a10_missiles_fired" ); } missileWaitForTriggerRelease() { self endon( "end_remote" ); self endon( "death" ); level endon( "game_ended" ); self endon ("a10_missiles_fired"); owner = self.owner; owner NotifyOnPlayerCommand( "rocket_fire_released", "-speed_throw" ); owner NotifyOnPlayerCommand( "rocket_fire_released", "-ads_akimbo_accessible" ); if( !level.console ) { owner NotifyOnPlayerCommand( "rocket_fire_released", "-toggleads_throw" ); } self.owner waittill( "rocket_fire_released" ); owner SetClientOmnvar( "ui_a10_rocket_lock", false ); self notify( "a10_missiles_fired" ); } missileLockTarget( target ) // self == plane { config = level.planeConfigs[ self.streakName ]; info = []; // veh_hud_target_marked info["icon"] = target maps\mp\_entityheadIcons::setHeadIcon( self.owner, config.lockonIcon, kLockIconOffset, 10, 10, false, 0.05, true, false, false, false ); info["target"] = target; self.targetList[ target GetEntityNumber() ] = info; self.owner PlayLocalSound( "recondrone_lockon" ); // need to handle case where target dies before } isMissileTargeted( target ) // self == plane { return ( IsDefined( self.targetList[ target GetEntityNumber() ] ) ); } fireMissiles() // self == plane { self endon( "death" ); level endon( "game_ended" ); config = level.planeConfigs[ self.streakName ]; foreach ( targetInfo in self.targetList ) { if ( self.numRocketsLeft > 0 ) { // fire at one target missile = self onFireHomingMissile( self.streakName, targetInfo["target"], kLockIconOffset ); if ( IsDefined( targetInfo["icon"] ) ) { missile.icon = targetInfo["icon"]; targetInfo["icon"] = undefined; } wait (config.delayBetweenRockets); } else { break; } } targetList = []; } onFireHomingMissile( streakName, target, targetOffset ) // self == plane { side = self.numRocketsLeft % 2; tagName = "tag_missile_" + (side + 1); rocketPos = self GetTagOrigin( tagName ); if ( IsDefined( rocketPos ) ) { owner = self.owner; config = level.planeConfigs[ streakName ]; /* eye_pos = owner GetEye(); eye_fwd = AnglesToForward( owner GetPlayerAngles() ); eye_trace = BulletTrace( eye_pos + eye_fwd * 360, eye_pos + eye_fwd * MISSILE_IMPACT_DIST_MAX, false, self ); eye_end_dist = max( MISSILE_IMPACT_DIST_MIN, eye_trace[ "fraction" ] * MISSILE_IMPACT_DIST_MAX ); rocket = MagicBullet( projectileName, rocketPos, rocketPos + eye_end_dist * eye_fwd, self.owner ); */ rocket = MagicBullet( config.rocketModelName, rocketPos, rocketPos + 100 * AnglesToForward(self.angles), self.owner ); rocket thread a10_missile_set_target( target, targetOffset ); Earthquake (0.25, 0.05, self.origin, 512); self.numRocketsLeft--; self.owner SetClientOmnvar( "ui_a10_rocket", self.numRocketsLeft ); config = level.planeConfigs[ streakName ]; rocket PlaySoundOnMovingEnt( config.sfxMissileFire_1p[ side ] ); rocket PlayLoopSound( config.sfxMissile ); // self PlaySoundOnMovingEnt( "a10p_missile_launch" ); // HidePart doesn't work with helicopters? // self HidePart( tagName ); // kill cam stuff? return rocket; } return undefined; } MISSILE_IMPACT_DIST_MAX = 15000; MISSILE_IMPACT_DIST_MIN = 1000; onFireRocket( streakName ) // self == plane { tagName = "tag_missile_" + self.numRocketsLeft; rocketPos = self GetTagOrigin( tagName ); if ( IsDefined( rocketPos ) ) { owner = self.owner; config = level.planeConfigs[ streakName ]; /* eye_pos = owner GetEye(); eye_fwd = AnglesToForward( owner GetPlayerAngles() ); eye_trace = BulletTrace( eye_pos + eye_fwd * 360, eye_pos + eye_fwd * MISSILE_IMPACT_DIST_MAX, false, self ); eye_end_dist = max( MISSILE_IMPACT_DIST_MIN, eye_trace[ "fraction" ] * MISSILE_IMPACT_DIST_MAX ); rocket = MagicBullet( projectileName, rocketPos, rocketPos + eye_end_dist * eye_fwd, self.owner ); */ rocket = MagicBullet( config.rocketModelName, rocketPos, rocketPos + 100 * AnglesToForward(self.angles), self.owner ); Earthquake (0.25, 0.05, self.origin, 512); self.numRocketsLeft--; self.owner SetClientOmnvar( "ui_a10_rocket", self.numRocketsLeft ); rocket PlaySoundOnMovingEnt( config.sfxMissileFire_1p[ self.numRocketsLeft ] ); rocket PlayLoopSound( config.sfxMissile ); self PlaySoundOnMovingEnt( "a10p_missile_launch" ); // HidePart doesn't work with helicopters? // self HidePart( tagName ); // kill cam stuff? } } a10_missile_set_target( target, offset ) { self thread a10_missile_cleanup(); wait 0.2; self Missile_SetTargetEnt( target, offset ); // self Missile_SetFlightmodeDirect(); } a10_missile_cleanup() { self waittill( "death" ); if ( IsDefined( self.icon ) ) { self.icon Destroy(); } } CANNON_SHAKE_TIME = 0.5; monitorWeaponFire( streakName, plane ) // self == player { plane endon( "end_remote" ); plane endon( "death" ); self endon( "death" ); level endon( "game_ended" ); config = level.planeConfigs[ streakName ]; plane.ammoCount = 1350; self SetClientOmnvar( "ui_a10_cannon", plane.ammoCount ); self NotifyOnPlayerCommand( "a10_cannon_start", "+attack" ); self NotifyOnPlayerCommand( "a10_cannon_stop", "-attack" ); while ( plane.ammoCount > 0 ) { // IsFiringVehicleTurret if ( !(self AttackButtonPressed()) ) { self waittill( "a10_cannon_start" ); } cannonShortBurstTimeLimit = GetTime() + config.sfxCannonFireBurpTime; plane PlayLoopSound( config.sfxCannonFireLoop_1p ); plane thread updateCannonShake( streakName ); self waittill( "a10_cannon_stop" ); plane StopLoopSound( config.sfxCannonFireLoop_1p ); plane PlaySoundOnMovingEnt( config.sfxCannonFireStop_1p ); if ( GetTime() < cannonShortBurstTimeLimit ) { playSoundAtPos( plane.origin, config.sfxCannonFireBurpShort_3p ); } else { playSoundAtPos( plane.origin, config.sfxCannonFireBurpLong_3p ); } } } // should eventually unify updateCannonShake( streakName ) // self == plane { self.owner endon( "a10_cannon_stop" ); self endon( "death" ); level endon( "game_ended" ); config = level.planeConfigs[ streakName ]; while ( self.ammoCount > 0 ) { Earthquake (0.2, CANNON_SHAKE_TIME, self.origin, 512); self.ammoCount -= 10; self.owner SetClientOmnvar( "ui_a10_cannon", self.ammoCount ); barrelPoint = self GetTagOrigin( "tag_flash_attach" ) + 20 * AnglesToForward( self.angles ); PlayFX( config.cannonFireVFX, barrelPoint ); self PlayRumbleOnEntity( config.cannonRumble ); // this needs to match the cannon's fire rate in the gdt wait( 0.1 ); } self.turret TurretFireDisable(); } ALTITUDE_WARNING_LIMIT = 1000; monitorAltitude( streakName, plane ) { plane endon( "end_remote" ); plane endon( "death" ); self endon( "death" ); level endon( "game_ended" ); self SetClientOmnvar( "ui_a10_alt_warn", false ); while( true ) { // the max is in omnvar alt = Int( Clamp(plane.origin[2], 0, 16383) ); self SetClientOmnvar( "ui_a10_alt", alt ); if (alt <= ALTITUDE_WARNING_LIMIT && !IsDefined( plane.altWarning ) ) { plane.altWarning = true; self SetClientOmnvar( "ui_a10_alt_warn", true ); } else if (alt > ALTITUDE_WARNING_LIMIT && IsDefined( plane.altWarning ) ) { plane.altWarning = undefined; self SetClientOmnvar( "ui_a10_alt_warn", false ); } wait( 0.1 ); } } watchIntroCleared( streakName, plane ) // self == player { self endon( "disconnect" ); level endon( "game_ended" ); self waittill( "intro_cleared" ); self SetClientOmnvar( "ui_a10", true ); // self EnableWeapons(); self thread monitorAltitude( streakname, plane ); self thread monitorRocketFire2( streakName, plane ); self thread monitorWeaponFire( streakName, plane ); self thread watchRoundEnd( plane, streakName ); self ThermalVisionFOFOverlayOn(); /* // pick a path plane.curFlightPath = level.a10FlightPaths[ 0 ]; plane thread followFlightPath(); */ self thread watchEarlyExit( plane ); } watchRoundEnd( plane, streakName ) // self == player { plane endon( "death" ); plane endon( "leaving" ); self endon( "disconnect" ); self endon( "joined_team" ); self endon( "joined_spectators" ); level waittill_any( "round_end_finished", "game_ended" ); // leave plane thread endFlyby( streakName ); self endStrafeSequence( streakName ); self a10_explode(); } buildAllFlightPathsDefault() { // temp - do not check in - should be done per level inBoundList = []; inBoundList[0] = 1; inBoundList[1] = 2; inBoundList[2] = 3; inBoundList[3] = 4; inBoundList[4] = 1; inBoundList[5] = 2; inBoundList[6] = 4; inBoundList[7] = 3; outBoundList = []; outBoundList[0] = 2; outBoundList[1] = 1; outBoundList[2] = 4; outBoundList[3] = 3; outBoundList[4] = 1; outBoundList[5] = 4; outBoundList[6] = 3; outBoundList[7] = 2; buildAllFlightPaths( inBoundList, outBoundList ); } buildAllFlightPaths( inBoundList, outBoundList ) { level.a10SplinesIn = inBoundList; level.a10SplinesOut = outBoundList; } // stolen from a10_proto_script // check it for more audio a10_cockpit_breathing() { level endon("remove_player_control"); for(;;) { wait (RandomFloatRange(3.0, 7.0)); //level.player radio_dialog_add_and_go("a10_breathing_r"); } } watchEarlyExit( veh ) // self == player { level endon( "game_ended" ); veh endon( "death" ); veh endon( "a10_end_strafe" ); veh thread maps\mp\killstreaks\_killstreaks::allowRideKillstreakPlayerExit(); veh waittill("killstreakExit"); self notify("end_remote"); veh thread endFlyby( veh.streakName ); self endStrafeSequence( veh.streakName ); veh a10_explode(); } a10_handleDamage() { self endon( "end_remote" ); config = level.planeConfigs[ self.streakName ]; self maps\mp\gametypes\_damage::monitorDamage( config.maxHealth, "helicopter", ::handleDeathDamage, ::modifyDamage, true // isKillstreak ); } modifyDamage( attacker, weapon, type, damage ) { modifiedDamage = damage; // modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage ); modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker ); // do damage effects? return modifiedDamage; } handleDeathDamage( attacker, weapon, type, damage ) // self == plane { config = level.planeConfigs[ self.streakName ]; // !!! need VO self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, config.voDestroyed, config.xpPopup, config.callout ); self a10_explode(); } // plane explode a10_explode() { config = level.planeConfigs[ self.streakName ]; maps\mp\killstreaks\_plane::stopTrackingPlane( self ); PlayFX ( config.explodeVfx, self.origin ); self Delete(); // self Hide(); }