#include maps\mp\_utility; #include common_scripts\utility; // replacement for EMP // only affects aircraft // remarkably similar to aastrike, should probably get rid of that... KS_NAME = "air_superiority"; kProjectileName = "aamissile_projectile_mp"; init() { config = SpawnStruct(); config.modelNames = []; config.modelNames[ "allies" ] = "vehicle_a10_warthog_iw6_mp"; config.modelNames[ "axis" ] = "vehicle_a10_warthog_iw6_mp"; config.inboundSfx = "veh_mig29_dist_loop"; //config.inboundSfx = "veh_aastrike_flyover_loop"; //config.outboundSfx = "veh_aastrike_flyover_outgoing_loop"; config.compassIconFriendly = "compass_objpoint_airstrike_friendly"; config.compassIconEnemy = "compass_objpoint_airstrike_busy"; // sonic boom? config.speed = 4000; config.halfDistance = 20000; config.distFromPlayer = 4000; config.heightRange = 250; //config.attackTime = 2.0; config.numMissileVolleys = 3; config.outboundFlightAnim = "airstrike_mp_roll"; config.sonicBoomSfx = "veh_mig29_sonic_boom"; config.onAttackDelegate = ::attackEnemyAircraft; config.onFlybyCompleteDelegate = ::cleanupFlyby; config.xpPopup = "destroyed_air_superiority"; config.callout = "callout_destroyed_air_superiority"; config.voDestroyed = undefined; config.killCamOffset = (-800, 0, 200); level.planeConfigs[ KS_NAME ] = config; level.killstreakFuncs[KS_NAME] = ::onUse; level.teamAirDenied["axis"] = false; level.teamAirDenied["allies"] = false; } onUse( lifeId, streakName ) { assert( isDefined( self ) ); // check for active air_superiority strikes otherTeam = getOtherTeam( self.team ); if ( (level.teamBased && level.teamAirDenied[ otherTeam] ) || (!level.teamBased && IsDefined( level.airDeniedPlayer ) && level.airDeniedPlayer == self ) ) { self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" ); return false; } else { // scramble the fighters self thread doStrike( lifeId, KS_NAME ); self maps\mp\_matchdata::logKillstreakEvent( "air_superiority", self.origin ); self thread teamPlayerCardSplash( "used_air_superiority", self ); return true; } } doStrike( lifeId, streakName ) { config = level.planeConfigs[ streakName ]; flightPlan = self maps\mp\killstreaks\_plane::getPlaneFlightPlan( config.distFromPlayer ); // play inbound vo wait( 1 ); targetTeam = getOtherTeam(self.team); level.teamAirDenied[targetTeam] = true; level.airDeniedPlayer = self; doOneFlyby( streakName, lifeId, flightPlan.targetPos, flightPlan.flightDir, flightPlan.height); self waittill( "aa_flyby_complete" ); // coming back around vo wait( 2 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); // don't do second flyby if owner has disconnected if ( IsDefined( self ) ) { doOneFlyby( streakName, lifeId, flightPlan.targetPos, -1 * flightPlan.flightDir, flightPlan.height ); self waittill( "aa_flyby_complete" ); } level.teamAirDenied[targetTeam] = false; level.airDeniedPlayer = undefined; // play outbound vo // should check if there are still enemy aircraft in the air and play appropriate vo } doOneFlyby( streakName, lifeId, targetPos, dir, flyHeight ) { config = level.planeConfigs[ streakName ]; // absolute height should be derived from the heightEnt flightPath = maps\mp\killstreaks\_plane::getFlightPath( targetPos, dir, config.halfDistance, true, flyHeight, config.speed, -0.5 * config.halfDistance, streakName ); // may want to break this up into spawn, move, cleanup components // so that we can reuse the plane level thread maps\mp\killstreaks\_plane::doFlyby( lifeId, self, lifeId, flightPath["startPoint"] + (0, 0, randomInt(config.heightRange) ), flightPath["endPoint"] + (0, 0, randomInt(config.heightRange) ), flightPath["attackTime"], flightPath["flyTime"], dir, streakName ); } attackEnemyAircraft( pathEnd, flyTime, beginAttackTime, owner, streakName ) // self == plane { self endon( "death" ); self.owner endon( "killstreak_disowned" ); level endon( "game_ended" ); wait (beginAttackTime); targets = findAllTargets( self.owner, self.team ); config = level.planeConfigs[ streakName ]; numVolleys = config.numMissileVolleys; targetIndex = targets.size - 1; while (targetIndex >= 0 && numVolleys > 0 ) { target = targets[ targetIndex ]; if ( IsDefined( target ) && IsAlive( target ) ) { self fireAtTarget( target ); numVolleys--; wait ( 1 ); } targetIndex--; } } cleanupFlyby( owner, plane, streakName ) { owner notify( "aa_flyby_complete" ); } // curTargetsStruct is a struct that holds the array of targets // !!! use this hack since arrays are passed by value, while structs are passed by reference findTargetsOfType( attacker, victimTeam, checkFunc, candidateList, curTargetsStruct ) { if ( IsDefined( candidateList ) ) { foreach ( target in candidateList ) { if ( [[ checkFunc ]]( attacker, victimTeam, target ) ) { curTargetsStruct.targets[ curTargetsStruct.targets.size ] = target; } } } return curTargetsStruct; } // unlike the aa strike, we only search for targets once // because we block new air strikes from behing launched // also: probably could flip the order that targets are acquired if we want // the jets to go after low-cost killstreaks first findAllTargets( attacker, attackerTeam ) { wrapper = SpawnStruct(); wrapper.targets = []; // ok, I'm sorry for function pointers, but it makes me sad to do unnecessary if checks all the time // isEnemyFunc will test if the target belongs to an enemy isEnemyFunc = undefined; if ( level.teamBased ) { isEnemyFunc = ::isValidTeamTarget; } else { isEnemyFunc = ::isValidFFATarget; } victimTeam = undefined; if ( IsDefined( attackerTeam ) ) { victimTeam = getOtherTeam( attackerTeam ); } // 2013-09-02 wallace: Since arrays are passed by value (or so JoeC tells me) // we will wrap up the targets array in a struct that is past by reference // this means that each call to findTargetsOfType is adding targets to the SAME array, not a copy // Destroy player controlled and higher level-KS's last, so put them in the front of the arraw findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.heli_pilot, wrapper ); if ( IsDefined( level.lbSniper ) ) { if ( [[ isEnemyFunc ]]( attacker, victimTeam, level.lbSniper ) ) { wrapper.targets[ wrapper.targets.size ] = level.lbSniper; } } findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.planes, wrapper ); // 2013-09-03 wallace: ugh, this is stupid. Vanguard puts itself in both remote_uav and littlebird arrays. So, don't use remote_uav as possible targets // findTargetsOfType( attacker, victimTeam, checkFunc, level.remote_uav, wrapper ); findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.littleBirds, wrapper ); findTargetsOfType( attacker, victimTeam, isEnemyFunc, level.helis, wrapper ); return wrapper.targets; } fireAtTarget( curTarget ) // self == plane { if ( !isDefined(curTarget) ) return; // do this check in case the plane's owner disconnects mid flight // we still want this pass to finish owner = undefined; if ( IsDefined( self.owner ) ) owner = self.owner; forwardVec = 384 * AnglesToForward( self.angles ); startpoint = self GetTagOrigin( "tag_missile_1" ) + forwardVec; rocket1 = MagicBullet( kProjectileName, startPoint, startPoint + forwardVec, owner ); rocket1.vehicle_fired_from = self; startpoint = self GetTagOrigin( "tag_missile_2" ) + forwardVec; rocket2 = MagicBullet( kProjectileName, startPoint, startPoint + forwardVec, owner ); rocket2.vehicle_fired_from = self; missiles = [ rocket1, rocket2 ]; curTarget notify( "targeted_by_incoming_missile", missiles ); self thread startMissileGuidance( curTarget, 0.25, missiles ); } startMissileGuidance( curTarget, igniteTime, missileArray ) { wait( igniteTime ); if ( IsDefined( curTarget ) ) { targetPoint = undefined; //AH: HACK: The harrier doesn't have the tag_missile_target, but it does have a tag_body. // The code works fine without this check, but GetTagOrigin throws an SRE if the tag does not exist. if ( curTarget.model != "vehicle_av8b_harrier_jet_mp" ) targetPoint = curTarget GetTagOrigin( "tag_missile_target" ); if ( !IsDefined( targetPoint ) ) { targetPoint = curTarget GetTagOrigin( "tag_body" ); } targetOffset = targetPoint - curTarget.origin; foreach ( missile in missileArray ) { if ( IsValidMissile( missile ) ) { missile Missile_SetTargetEnt( curTarget, targetOffset ); missile Missile_SetFlightmodeDirect(); } } } } destroyActiveVehicles( attacker, victimTeam ) { // thread all of the things that need to get destroyed, this way we can put frame waits in between each destruction so we don't hit the server with a lot at one time maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.helis ); maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.littleBirds ); maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.heli_pilot ); if ( IsDefined( level.lbSniper ) ) { // kind of hack, but destroyTargets does a lot of needed setup tempArray = []; tempArray[0] = level.lbSniper; maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", tempArray ); } maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.remote_uav ); maps\mp\killstreaks\_killstreaks::destroyTargetArray( attacker, victimTeam, "aamissile_projectile_mp", level.planes ); }