#include maps\mp\_utility; #include maps\mp\killstreaks\_harrier; #include maps\mp\gametypes\_hud_util; #include common_scripts\utility; init() { precacheLocationSelector( "map_artillery_selector" ); //level.onfirefx = loadfx ("fx/fire/fire_smoke_trail_L"); level.airstrikefx = loadfx ("fx/explosions/clusterbomb"); level.airstrikessfx = loadfx ("fx/explosions/clusterbomb_no_fount"); level.airstrikeexplosion = loadfx ("fx/explosions/clusterbomb_exp_direct_runner_cheap"); level.mortareffect = loadfx ("fx/explosions/clusterbomb_exp_direct_runner_stealth"); level.bombstrike = loadfx ("fx/explosions/wall_explosion_pm_a"); level.airBurstBomb = loadfx( "fx/explosions/airburst" ); level.harriers = []; level.fx_airstrike_afterburner = loadfx ("fx/fire/jet_afterburner"); level.fx_airstrike_contrail = loadfx ("fx/smoke/jet_contrail"); // airstrike danger area is the circle of radius artilleryDangerMaxRadius // stretched by a factor of artilleryDangerOvalScale in the direction of the incoming airstrike, // moved by artilleryDangerForwardPush * artilleryDangerMaxRadius in the same direction. // use scr_Airstrikedebug to visualize. level.dangerMaxRadius["stealth_airstrike"] = 900; level.dangerMinRadius["stealth_airstrike"] = 750; level.dangerForwardPush["stealth_airstrike"] = 1; level.dangerOvalScale["stealth_airstrike"] = 6.0; level.dangerMaxRadius["airstrike"] = 550; level.dangerMinRadius["airstrike"] = 300; level.dangerForwardPush["airstrike"] = 1.5; level.dangerOvalScale["airstrike"] = 6.0; level.dangerMaxRadius["precision_airstrike"] = 550; level.dangerMinRadius["precision_airstrike"] = 300; level.dangerForwardPush["precision_airstrike"] = 2.0; level.dangerOvalScale["precision_airstrike"] = 6.0; level.dangerMaxRadius["harrier_airstrike"] = 550; level.dangerMinRadius["harrier_airstrike"] = 300; level.dangerForwardPush["harrier_airstrike"] = 1.5; level.dangerOvalScale["harrier_airstrike"] = 6.0; level.artilleryDangerCenters = []; level.killStreakFuncs["airstrike"] = ::tryUseAirstrike; level.killStreakFuncs["precision_airstrike"] = ::tryUseAirstrike; level.killStreakFuncs["super_airstrike"] = ::tryUseAirstrike; level.killStreakFuncs["harrier_airstrike"] = ::tryUseAirstrike; level.killStreakFuncs["stealth_airstrike"] = ::tryUseAirstrike; level.planes = []; } tryUseAirstrike( lifeId, streakName ) { switch( streakName ) { case "precision_airstrike": break; case "stealth_airstrike": break; case "harrier_airstrike": if ( isDefined( level.harrier_incoming ) || level.harriers.size >= 1 ) { self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" ); return false; } break; case "super_airstrike": break; } result = self selectAirstrikeLocation( lifeId, streakName ); if ( !isDefined( result ) || !result ) return false; return true; } /# debugLocation( trace, location ) { level notify( "debug_airstrike" ); level endon( "debug_airstrike" ); while( true ) { if( GetDvarInt( "scr_debugairstrike" ) == 0 ) return; Print3d( level.mapCenter, "Map Center", ( 1, 0, 0 ) ); Print3d( level.mapCenter, "Map Center origin: " + level.mapCenter[0] + ", " + level.mapCenter[1] + ", " + level.mapCenter[2], ( 1, 0, 0 ) ); Print3d( location, "Location", ( 1, 0, 0 ) ); Print3d( location, "Location origin: " + location[0] + ", " + location[1] + ", " + location[2], ( 1, 0, 0 ) ); Print3d( trace["position"], "Trace Position", ( 1, 0, 0 ) ); Print3d( trace["position"], "Trace Position origin: " + trace["position"][0] + ", " + trace["position"][1] + ", " + trace["position"][2], ( 1, 0, 0 ) ); Line( level.mapCenter, trace["position"], ( 0, 0, 1 ) ); wait( 0.05 ); } } debugFlyHeight( planeFlyHeight ) { level endon( "debug_airstrike" ); while( true ) { if( GetDvarInt( "scr_debugairstrike" ) == 0 ) return; anglesForward = AnglesToForward( level.players[0].angles ); scalar = (anglesForward[0] * 200, anglesForward[1] * 200, anglesForward[2] ); Print3d( level.players[0].origin + scalar, "Fly Height: " + planeFlyHeight, ( 1, 0, 0 ) ); wait( 0.05 ); } } #/ doAirstrike( lifeId, origin, yaw, owner, team, streakName ) { assert( isDefined( origin ) ); assert( isDefined( yaw ) ); if ( streakName == "harrier_airstrike" ) level.harrier_incoming = true; if ( isDefined( level.airstrikeInProgress ) ) { while ( isDefined( level.airstrikeInProgress ) ) level waittill ( "begin_airstrike" ); level.airstrikeInProgress = true; wait ( 2.0 ); } if ( !isDefined( owner ) ) { if ( streakName == "harrier_airstrike" ) level.harrier_incoming = undefined; return; } level.airstrikeInProgress = true; trace = bullettrace(origin, origin + (0,0,-1000000), false, undefined); targetpos = trace["position"]; //if ( level.teambased ) //{ // players = level.players; // // for ( i = 0; i < level.players.size; i++ ) // { // player = level.players[i]; // playerteam = player.pers["team"]; // if ( isdefined( playerteam ) ) // { // if ( playerteam == team && streakName != "stealth_airstrike" ) // player iprintln( &"KILLSTREAKS_WAR_AIRSTRIKE_INBOUND", owner ); // } // } //} //else //{ // if ( !level.hardcoreMode ) // { // if ( pointIsInAirstrikeArea( owner.origin, targetpos, yaw, streakName ) ) // owner iprintlnbold(&"KILLSTREAKS_WAR_AIRSTRIKE_INBOUND_NEAR_YOUR_POSITION"); // } //} dangerCenter = spawnstruct(); dangerCenter.origin = targetpos; dangerCenter.forward = anglesToForward( (0,yaw,0) ); dangerCenter.streakName = streakName; dangerCenter.team = team; level.artilleryDangerCenters[ level.artilleryDangerCenters.size ] = dangerCenter; /# level thread debugArtilleryDangerCenters( streakName ); #/ callStrike( lifeId, owner, targetpos, yaw, streakName ); // Make sure the harrier_incoming variable is cleared. (Won't normally be set if the player quits before the harrier appears) if ( streakName == "harrier_airstrike" ) level.harrier_incoming = undefined; wait( 1.0 ); level.airstrikeInProgress = undefined; owner notify ( "begin_airstrike" ); level notify ( "begin_airstrike" ); wait 7.5; found = false; newarray = []; for ( i = 0; i < level.artilleryDangerCenters.size; i++ ) { if ( !found && level.artilleryDangerCenters[i].origin == targetpos ) { found = true; continue; } newarray[ newarray.size ] = level.artilleryDangerCenters[i]; } assert( found ); assert( newarray.size == level.artilleryDangerCenters.size - 1 ); level.artilleryDangerCenters = newarray; } clearProgress( delay ) { wait ( 2.0 ); level.airstrikeInProgress = undefined; } /# debugArtilleryDangerCenters( streakName ) { level notify("debugArtilleryDangerCenters_thread"); level endon("debugArtilleryDangerCenters_thread"); if ( getdvarint("scr_airstrikedebug") != 1 ) { return; } while( level.artilleryDangerCenters.size > 0 ) { for ( i = 0; i < level.artilleryDangerCenters.size; i++ ) { origin = level.artilleryDangerCenters[i].origin; forward = level.artilleryDangerCenters[i].forward; origin += forward * level.dangerForwardPush[streakName] * level.dangerMaxRadius[streakName]; previnnerpos = (0,0,0); prevouterpos = (0,0,0); for ( j = 0; j <= 40; j++ ) { frac = (j * 1.0) / 40; angle = frac * 360; dir = anglesToForward((0,angle,0)); forwardPart = vectordot( dir, forward ) * forward; perpendicularPart = dir - forwardPart; pos = forwardPart * level.dangerOvalScale[streakName] + perpendicularPart; innerpos = pos * level.dangerMinRadius[streakName]; innerpos += origin; outerpos = pos * level.dangerMaxRadius[streakName]; outerpos += origin; if ( j > 0 ) { line( innerpos, previnnerpos, (1, 0, 0) ); line( outerpos, prevouterpos, (1,.5,.5) ); } previnnerpos = innerpos; prevouterpos = outerpos; } } wait .05; } } #/ getAirstrikeDanger( point ) { danger = 0; for ( i = 0; i < level.artilleryDangerCenters.size; i++ ) { origin = level.artilleryDangerCenters[i].origin; forward = level.artilleryDangerCenters[i].forward; streakName = level.artilleryDangerCenters[i].streakName; danger += getSingleAirstrikeDanger( point, origin, forward, streakName ); } return danger; } getSingleAirstrikeDanger( point, origin, forward, streakName ) { center = origin + level.dangerForwardPush[streakName] * level.dangerMaxRadius[streakName] * forward; diff = point - center; diff = (diff[0], diff[1], 0); forwardPart = vectorDot( diff, forward ) * forward; perpendicularPart = diff - forwardPart; circlePos = perpendicularPart + forwardPart / level.dangerOvalScale[streakName]; /* /# if ( getdvar("scr_airstrikedebug") == "1" ) { thread airstrikeLine( center, center + perpendicularPart, (1,1,1), 50 ); thread airstrikeLine( center + perpendicularPart, center + circlePos, (1,1,1), 50 ); thread airstrikeLine( center + circlePos, point, (.5,.5,.5), 50 ); } #/ */ distsq = lengthSquared( circlePos ); if ( distsq > level.dangerMaxRadius[streakName] * level.dangerMaxRadius[streakName] ) return 0; if ( distsq < level.dangerMinRadius[streakName] * level.dangerMinRadius[streakName] ) return 1; dist = sqrt( distsq ); distFrac = (dist - level.dangerMinRadius[streakName]) / (level.dangerMaxRadius[streakName] - level.dangerMinRadius[streakName]); assertEx( distFrac >= 0 && distFrac <= 1, distFrac ); return 1 - distFrac; } pointIsInAirstrikeArea( point, targetpos, yaw, streakName ) { return distance2d( point, targetpos ) <= level.dangerMaxRadius[streakName] * 1.25; // TODO //return getSingleAirstrikeDanger( point, targetpos, yaw ) > 0; } losRadiusDamage( pos, radius, max, min, owner, eInflictor, sWeapon ) { ents = maps\mp\gametypes\_weapons::getDamageableEnts(pos, radius, true); glassRadiusDamage( pos, radius, max, min ); for (i = 0; i < ents.size; i++) { if (ents[i].entity == self) continue; dist = distance(pos, ents[i].damageCenter); if ( ents[i].isPlayer || ( isDefined( ents[i].isSentry ) && ents[i].isSentry ) ) { // check if there is a path to this entity 130 units above his feet. if not, they're probably indoors indoors = !BulletTracePassed( ents[i].entity.origin, ents[i].entity.origin + (0,0,130), false, undefined ); if ( indoors ) { indoors = !BulletTracePassed( ents[i].entity.origin + (0,0,130), pos + (0,0,130 - 16), false, undefined ); if ( indoors ) { // give them a distance advantage for being indoors. dist *= 4; if ( dist > radius ) continue; } } } ents[i].damage = int(max + (min-max)*dist/radius); ents[i].pos = pos; ents[i].damageOwner = owner; ents[i].eInflictor = eInflictor; level.airStrikeDamagedEnts[level.airStrikeDamagedEntsCount] = ents[i]; level.airStrikeDamagedEntsCount++; } thread airstrikeDamageEntsThread( sWeapon ); } airstrikeDamageEntsThread( sWeapon ) { self notify ( "airstrikeDamageEntsThread" ); self endon ( "airstrikeDamageEntsThread" ); for ( ; level.airstrikeDamagedEntsIndex < level.airstrikeDamagedEntsCount; level.airstrikeDamagedEntsIndex++ ) { if ( !isDefined( level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] ) ) continue; ent = level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex]; if ( !isDefined( ent.entity ) ) continue; if ( !ent.isPlayer || isAlive( ent.entity ) ) { ent maps\mp\gametypes\_weapons::damageEnt( ent.eInflictor, // eInflictor = the entity that causes the damage (e.g. a claymore) ent.damageOwner, // eAttacker = the player that is attacking ent.damage, // iDamage = the amount of damage to do "MOD_PROJECTILE_SPLASH", // sMeansOfDeath = string specifying the method of death (e.g. "MOD_PROJECTILE_SPLASH") sWeapon, // sWeapon = string specifying the weapon used (e.g. "claymore_mp") ent.pos, // damagepos = the position damage is coming from vectornormalize(ent.damageCenter - ent.pos) // damagedir = the direction damage is moving in ); level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] = undefined; if ( ent.isPlayer ) wait ( 0.05 ); } else { level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] = undefined; } } } radiusArtilleryShellshock(pos, radius, maxduration, minduration, team ) { players = level.players; foreach ( player in level.players ) { if ( !isAlive( player ) ) continue; if ( player.team == team || player.team == "spectator" ) continue; playerPos = player.origin + (0,0,32); dist = distance( pos, playerPos ); if ( dist > radius ) continue; duration = int(maxduration + (minduration-maxduration)*dist/radius); player thread artilleryShellshock( "default", duration ); } } artilleryShellshock(type, duration) { self endon ( "disconnect" ); if (isdefined(self.beingArtilleryShellshocked) && self.beingArtilleryShellshocked) return; self.beingArtilleryShellshocked = true; self shellshock(type, duration); wait(duration + 1); self.beingArtilleryShellshocked = false; } /# airstrikeLine( start, end, color, duration ) { frames = duration * 20; for ( i = 0; i < frames; i++ ) { line(start,end,color); wait .05; } } traceBomb() { self endon("death"); prevpos = self.origin; while(1) { thread airstrikeLine( prevpos, self.origin, (.5,1,0), 40 ); prevpos = self.origin; wait .2; } } #/ doBomberStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, streakName ) { // plane spawning randomness = up to 125 units, biased towards 0 // radius of bomb damage is 512 if ( !isDefined( owner ) ) return; startPathRandomness = 100; endPathRandomness = 150; pathStart = startPoint + ( (randomfloat(2) - 1)*startPathRandomness, (randomfloat(2) - 1)*startPathRandomness, 4000 ); pathEnd = endPoint + ( (randomfloat(2) - 1)*endPathRandomness , (randomfloat(2) - 1)*endPathRandomness , 4000 ); // Spawn the plane plane = spawnplane( owner, "script_model", pathStart, "compass_objpoint_b2_airstrike_friendly", "compass_objpoint_b2_airstrike_enemy" ); addPlaneToList( plane ); plane thread handleDeath(); plane playLoopSound( "veh_b2_dist_loop" ); plane setModel( "vehicle_b2_bomber" ); plane thread handleEMP( owner ); plane.lifeId = lifeId; plane.angles = direction; forward = anglesToForward( direction ); plane moveTo( pathEnd, flyTime, 0, 0 ); thread stealthBomber_killCam( plane, pathEnd, flyTime, streakName ); thread bomberDropBombs( plane, bombsite, owner ); // Delete the plane after its flyby plane endon( "death" ); // the fly time for this is too long, shortening it because it hangs out on the minimap for way too long wait( flyTime * 0.65 ); removePlaneFromList( plane ); plane notify( "delete" ); plane delete(); } bomberDropBombs( plane, bombSite, owner ) { plane endon( "death" ); while ( !targetIsClose( plane, bombsite, 5000 ) ) wait ( 0.05 ); //playfxontag( level.stealthbombfx, plane, "tag_left_alamo_missile" ); //playfxontag( level.stealthbombfx, plane, "tag_right_alamo_missile" ); showFx = true; sonicBoom = false; plane notify ( "start_bombing" ); //plane thread playBombFx(); bombsDropped = 0; for ( dist = targetGetDist( plane, bombsite ); dist < 5000; dist = targetGetDist( plane, bombsite ) ) { if ( dist < 1500 && !sonicBoom ) { plane PlaySoundOnMovingEnt( "veh_b2_sonic_boom" ); sonicBoom = true; } //showFx = !showFx; if ( dist < 3000 && bombsDropped < 4 ) { plane thread dropParachuteBomb( plane, owner ); bombsDropped++; wait ( randomFloatRange(.15, .3) ); } wait ( 0.1 ); } plane notify ( "stop_bombing" ); } dropParachuteBomb( plane, owner ) { self endon( "stop_bombing" ); self endon( "death" ); bomb = spawn( "script_model", self.origin ); bomb setModel( "parachute_cargo_static" ); bomb.team = owner.team; bomb.owner = owner; bomb SetCanDamage( true ); bombTrace = BulletTrace( bomb.origin, bomb.origin - (0,0, 20000), false, bomb, false, false ); endPosition = bombTrace[ "position"]; bomb moveTo( endPosition, RandomIntRange( 8, 14 ) ); //either get these animated or script some swinging when dropped. bomb thread bombDamageWatcher( plane, endPosition ); bomb thread bombWatcher( plane, endPosition ); } bombDamageWatcher( plane, endPosition ) { bomb = self; self endon ( "death" ); self setCanDamage( true ); // use a health buffer to prevent dying to friendly fire self.health = 999999; // keep it from dying anywhere in code self.maxHealth = 200; // this is the health we'll check self.damageTaken = 0; // how much damage has it taken while( true ) { self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, iDFlags, weapon ); // don't allow people to destroy equipment on their team if FF is off if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) ) continue; if ( !IsDefined( self ) ) return; self.wasDamaged = true; self.damageTaken += damage; if( isPlayer( attacker ) ) { attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "tactical_insertion" ); } if ( self.damageTaken >= self.maxHealth ) { RadiusDamage( bomb.origin, 1024, 600, 65, bomb.owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" ); playFX( level.airBurstBomb, bomb.origin, anglesToForward(bomb.angles), bomb.origin - endPosition ); if ( isDefined( bomb ) ) bomb Delete(); self notify("death"); } } } bombWatcher( plane, endPosition ) { bomb = self; bomb endon( "death" ); while ( bomb.origin[2] > ( endPosition[2] + 600 ) ) { wait (0.1 ); } RadiusDamage( endPosition + (0,0,64), 1024, 600, 65, plane.owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" ); playFX( level.airBurstBomb, bomb.origin, anglesToForward(bomb.angles), bomb.origin - endPosition ); bomb Delete(); } playBombFx() { self endon( "stop_bombing" ); self endon( "death" ); for ( ;; ) { playFxOnTag( level.stealthbombfx, self, "tag_left_alamo_missile" ); playFxOnTag( level.stealthbombfx, self, "tag_right_alamo_missile" ); wait ( 0.5 ); } } stealthBomber_killCam( plane, pathEnd, flyTime, streakName ) { plane waittill ( "start_bombing" ); planedir = anglesToForward( plane.angles ); killCamEnt = spawn( "script_model", plane.origin + (0,0,100) - planedir * 200 ); plane.killCamEnt = killCamEnt; plane.killCamEnt SetScriptMoverKillCam( "airstrike" ); plane.airstrikeType = streakName; killCamEnt.startTime = gettime(); killCamEnt thread deleteAfterTime( 15.0 ); killCamEnt linkTo( plane, "tag_origin", (-256,768,768), ( 0,0,0 ) ); } callStrike_bomb( coord, owner, offset, showFx ) { if ( !isDefined( owner ) || owner isKillStreakDenied() ) { self notify( "stop_bombing" ); return; } accuracyRadius = 512; randVec = ( 0, randomint( 360 ), 0 ); bombPoint = coord + ( AnglesToForward( randVec ) * RandomFloat( accuracyRadius ) ); trace = bulletTrace( bombPoint, bombPoint + (0,0,-10000), false, undefined ); bombPoint = trace["position"]; bombHeight = distance( coord, bombPoint ); if ( bombHeight > 5000 ) return; wait ( 0.85 * (bombHeight / 2000) ); if ( !isDefined( owner ) || owner isKillStreakDenied() ) { self notify( "stop_bombing" ); return; } if ( showFx ) { playFx( level.mortareffect, bombPoint ); level thread maps\mp\gametypes\_shellshock::stealthAirstrike_earthQuake( bombPoint ); } thread playSoundInSpace( "exp_airstrike_bomb", bombPoint ); radiusArtilleryShellshock( bombPoint, 512, 8, 4, owner.team ); losRadiusDamage( bombPoint + (0,0,16), 896, 300, 50, owner, self, "stealth_bomb_mp" ); // targetpos, radius, maxdamage, mindamage, player causing damage } handleHarrierAirstrikeObjectiveIcons() { self endon("death"); self.owner endon("disconnect"); // Wait until the plane is in view wait 2; self maps\mp\killstreaks\_plane::setObjectiveIcons( "hud_minimap_harrier_green", "hud_minimap_harrier_red" ); self thread cleanupHarrierAirstrikeObjectiveIcons(); } cleanupHarrierAirstrikeObjectiveIcons() { friendlyTeamId = self.friendlyTeamId; enemyTeamID = self.enemyTeamID; // Wait until the plane is off the map, or it's destroyed self waittill_any_timeout( 3.5, "death" ); if ( IsDefined( friendlyTeamId ) ) { _objective_delete( friendlyTeamId ); _objective_delete( enemyTeamID ); } } doPlaneStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, streakName ) { // plane spawning randomness = up to 125 units, biased towards 0 // radius of bomb damage is 512 if ( !isDefined( owner ) ) return; startPathRandomness = 100; endPathRandomness = 150; pathStart = startPoint + ( (randomfloat(2) - 1)*startPathRandomness, (randomfloat(2) - 1)*startPathRandomness, 0 ); pathEnd = endPoint + ( (randomfloat(2) - 1)*endPathRandomness , (randomfloat(2) - 1)*endPathRandomness , 0 ); //self thread DrawLine(pathStart, (AnglesToForward( direction ) * 200000), 120, (1,0,1) ); // Spawn the planes //plane = spawnplane( owner, "script_model", pathStart, "compass_objpoint_airstrike_friendly", "compass_objpoint_airstrike_busy" ); //addPlaneToList( plane ); plane = spawn( "script_model", pathStart ); plane.owner = owner; plane.origin = pathStart; plane.angles = direction; plane.team = owner.team; plane thread handleDeath(); if( streakName == "harrier_airstrike" ) { plane setModel( "vehicle_av8b_harrier_jet_mp" ); plane playloopsound( "harrier_fly_in" ); } else { plane setModel( "vehicle_a10_warthog_iw6_mp" ); plane playloopsound( "veh_mig29_dist_loop" ); } plane thread handleEMP( owner ); plane.lifeId = lifeId; plane.angles = direction; forward = anglesToForward( direction ); plane thread playPlaneFx(); plane moveTo( pathEnd, flyTime, 0, 0 ); if( streakName == "harrier_airstrike" ) plane thread handleHarrierAirstrikeObjectiveIcons(); /# if ( getdvar("scr_airstrikedebug") == "1" ) thread airstrikeLine( pathStart, pathEnd, (1,1,1), 20 ); #/ //thread callStrike_planeSound( plane, bombsite ); thread callStrike_bombEffect( plane, pathEnd, flyTime, bombTime - 1.0, owner, requiredDeathCount, streakName ); wait( bombTime - .75 ); plane ScriptModelPlayAnimDeltaMotion( "airstrike_mp_roll" ); // Delete the plane after its flyby plane endon( "death" ); wait (flyTime-bombTime); removePlaneFromList( plane ); plane notify( "delete" ); plane delete(); } handleDeath() // self == plane { level endon( "game_ended" ); self endon( "delete" ); self waittill( "death" ); forward = AnglesToForward( self.angles ) * 200; PlayFX( level.harrier_deathfx, self.origin, forward ); removePlaneFromList( self ); self delete(); } addPlaneToList( plane ) { level.planes[ level.planes.size ] = plane; } removePlaneFromList( plane ) { for( i = 0; i < level.planes.size; i++ ) { if( IsDefined( level.planes[i] ) && level.planes[i] == plane ) { level.planes[i] = undefined; } } } callStrike_bombEffect( plane, pathEnd, flyTime, launchTime, owner, requiredDeathCount, streakName ) { plane endon( "death" ); wait ( launchTime ); if ( !isDefined( owner )|| owner isKillStreakDenied() ) return; if ( streakName == "harrier_airstrike" ) plane PlaySoundOnMovingEnt( "harrier_sonic_boom" ); else plane PlaySoundOnMovingEnt( "veh_mig29_sonic_boom" ); planedir = anglesToForward( plane.angles ); bomb = spawnbomb( plane.origin, plane.angles ); bomb MoveGravity( ( AnglesToForward( plane.angles ) *( 7000 / 1.5 ) ), 3.0 ); bomb.lifeId = requiredDeathCount; killCamEnt = spawn( "script_model", plane.origin + (0,0,100) - planedir * 200 ); bomb.killCamEnt = killCamEnt; bomb.killCamEnt SetScriptMoverKillCam( "airstrike" ); bomb.airstrikeType = streakName; killCamEnt.startTime = gettime(); killCamEnt thread deleteAfterTime( 15.0 ); killCamEnt.angles = planedir; killCamEnt moveTo( pathEnd + (0,0,100), flyTime, 0, 0 ); /# if ( getdvar("scr_airstrikedebug") == "1" ) bomb thread traceBomb(); #/ wait .4; //plane stoploopsound(); killCamEnt moveTo( killCamEnt.origin + planedir * 4000, 1, 0, 0 ); wait .45; killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.2)) * 3500, 2, 0, 0 ); wait ( 0.15 ); newBomb = spawn( "script_model", bomb.origin ); newBomb setModel( "tag_origin" ); newBomb.origin = bomb.origin; newBomb.angles = bomb.angles; bomb setModel( "tag_origin" ); wait (0.10); // wait two server frames before playing fx bombOrigin = newBomb.origin; bombAngles = newBomb.angles; if ( level.splitscreen ) playfxontag( level.airstrikessfx, newBomb, "tag_origin" ); else playfxontag( level.airstrikefx, newBomb, "tag_origin" ); wait .05; killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.25)) * 2500, 2, 0, 0 ); wait .25; killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.35)) * 2000, 2, 0, 0 ); wait .2; killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.45)) * 1500, 2, 0, 0 ); wait ( 0.5 ); repeat = 12; minAngles = 5; maxAngles = 55; angleDiff = (maxAngles - minAngles) / repeat; hitpos = (0,0,0); for( i = 0; i < repeat; i++ ) { traceDir = anglesToForward( bombAngles + (maxAngles-(angleDiff * i),randomInt( 10 )-5,0) ); traceEnd = bombOrigin + ( traceDir * 10000 ); trace = bulletTrace( bombOrigin, traceEnd, false, undefined ); traceHit = trace["position"]; hitpos += traceHit; /# if ( getdvar("scr_airstrikedebug") == "1" ) thread airstrikeLine( bombOrigin, traceHit, (1,0,0), 40 ); #/ playFX( level.airstrikeexplosion, traceHit ); thread losRadiusDamage( traceHit + (0,0,16), 512, 200, 30, owner, bomb, "artillery_mp" ); // targetpos, radius, maxdamage, mindamage, player causing damage, entity that player used to cause damage if ( i%3 == 0 ) { thread playsoundinspace( "exp_airstrike_bomb", traceHit ); level thread maps\mp\gametypes\_shellshock::airstrike_earthQuake( traceHit ); } wait ( 0.05 ); } hitpos = hitpos / repeat + (0,0,128); killCamEnt moveto( bomb.killCamEnt.origin * .35 + hitpos * .65, 1.5, 0, .5 ); wait ( 5.0 ); newBomb delete(); bomb delete(); } spawnbomb( origin, angles ) { bomb = spawn( "script_model", origin ); bomb.angles = angles; bomb setModel( "projectile_cbu97_clusterbomb" ); return bomb; } deleteAfterTime( time ) { self endon ( "death" ); wait ( 10.0 ); self delete(); } playPlaneFx() { self endon ( "death" ); wait( 0.5); playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_right" ); wait( 0.5); playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_left" ); wait( 0.5); playfxontag( level.fx_airstrike_contrail, self, "tag_right_wingtip" ); wait( 0.5); playfxontag( level.fx_airstrike_contrail, self, "tag_left_wingtip" ); } callStrike( lifeId, owner, coord, yaw, streakName ) { heightEnt = undefined; planeBombExplodeDistance = 0; // Get starting and ending point for the plane direction = ( 0, yaw, 0 ); heightEnt = GetEnt( "airstrikeheight", "targetname" ); if ( streakName == "stealth_airstrike" ) { thread teamPlayerCardSplash( "used_stealth_airstrike", owner, owner.team ); planeHalfDistance = 12000; planeFlySpeed = 4000; if ( !isDefined( heightEnt ) )//old system { println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" ); planeFlyHeight = 950; planeBombExplodeDistance = 1500; if ( isdefined( level.airstrikeHeightScale ) ) planeFlyHeight *= level.airstrikeHeightScale; } else { planeFlyHeight = heightEnt.origin[2]; // we need to go slightly higher for this map if( GetDvar( "mapname" ) == "mp_exchange" ) planeFlyHeight += 1024; planeBombExplodeDistance = getExplodeDistance( planeFlyHeight ); } } else { if ( streakName == "harrier_airstrike" ) { thread teamPlayerCardSplash( "used_harrier", owner ); } planeHalfDistance = 24000; planeFlySpeed = 7000; if ( !isDefined( heightEnt ) )//old system { println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" ); planeFlyHeight = 850; planeBombExplodeDistance = 1500; if ( isdefined( level.airstrikeHeightScale ) ) planeFlyHeight *= level.airstrikeHeightScale; } else { planeFlyHeight = heightEnt.origin[2]; planeBombExplodeDistance = getExplodeDistance( planeFlyHeight ); } } /# if( GetDvarInt( "scr_debugairstrike" ) ) { self thread debugFlyHeight( planeFlyHeight ); } #/ owner endon("disconnect"); requiredDeathCount = lifeId; level.airstrikeDamagedEnts = []; level.airStrikeDamagedEntsCount = 0; level.airStrikeDamagedEntsIndex = 0; if ( streakName == "harrier_airstrike" ) { flightPath = getFlightPath( coord, direction, planeHalfDistance, heightEnt, planeFlyHeight, planeFlySpeed, planeBombExplodeDistance, streakName ); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(500)), flightPath["endPoint"]+(0,0,randomInt(500)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); wait randomfloatrange( 1.5, 2.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(200)), flightPath["endPoint"]+(0,0,randomInt(200)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); wait randomfloatrange( 1.5, 2.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(200)), flightPath["endPoint"]+(0,0,randomInt(200)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); wait randomfloatrange( 1.5, 2.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); harrier = beginHarrier( lifeId, flightPath["startPoint"], coord ); owner thread defendLocation( harrier ); //owner thread harrierMissileStrike( flightPath["startPoint"], coord ); } else if ( streakName == "stealth_airstrike" ) { flightPath = getFlightPath( coord, direction, planeHalfDistance, heightEnt, planeFlyHeight, planeFlySpeed, planeBombExplodeDistance, streakName ); level thread doBomberStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(1000)), flightPath["endPoint"]+(0,0,randomInt(1000)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); } else //common airstrike { flightPath = getFlightPath( coord, direction, planeHalfDistance, heightEnt, planeFlyHeight, planeFlySpeed, planeBombExplodeDistance, streakName ); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(500)), flightPath["endPoint"]+(0,0,randomInt(500)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); wait randomfloatrange( 1.5, 2.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(200)), flightPath["endPoint"]+(0,0,randomInt(200)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); wait randomfloatrange( 1.5, 2.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(200)), flightPath["endPoint"]+(0,0,randomInt(200)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); if ( streakName == "super_airstrike" ) { wait randomfloatrange( 2.5, 3.5 ); maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone(); level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, flightPath["startPoint"]+(0,0,randomInt(200)), flightPath["endPoint"]+(0,0,randomInt(200)), flightPath["bombTime"], flightPath["flyTime"], direction, streakName ); } } } getFlightPath( coord, direction, planeHalfDistance, heightEnt, planeFlyHeight, planeFlySpeed, planeBombExplodeDistance, streakName ) { startPoint = coord + ( AnglesToForward( direction ) * ( -1 * planeHalfDistance ) ); if ( isDefined( heightEnt ) )// used in the new height system startPoint *= (1,1,0); startPoint += ( 0, 0, planeFlyHeight ); if ( streakName == "stealth_airstrike" ) endPoint = coord + ( AnglesToForward( direction ) *( planeHalfDistance * 4 ) ); else endPoint = coord + ( AnglesToForward( direction ) * planeHalfDistance ); if ( isDefined( heightEnt ) )// used in the new height system endPoint *= (1,1,0); endPoint += ( 0, 0, planeFlyHeight ); // Make the plane fly by d = length( startPoint - endPoint ); flyTime = ( d / planeFlySpeed ); // bomb explodes planeBombExplodeDistance after the plane passes the center d = abs( d/2 + planeBombExplodeDistance ); bombTime = ( d / planeFlySpeed ); assert( flyTime > bombTime ); flightPath["startPoint"] = startPoint; flightPath["endPoint"] = endPoint; flightPath["bombTime"] = bombTime; flightPath["flyTime"] = flyTime; return flightPath; } getExplodeDistance( height ) { standardHeight = 850; standardDistance = 1500; distanceFrac = standardHeight/height; newDistance = distanceFrac * standardDistance; return newDistance; } targetGetDist( other, target ) { infront = targetisinfront( other, target ); if( infront ) dir = 1; else dir = -1; a = flat_origin( other.origin ); b = a + ( AnglesToForward( flat_angle( other.angles ) ) * ( dir * 100000 ) ); point = pointOnSegmentNearestToPoint(a,b, target); dist = distance(a,point); return dist; } targetisclose(other, target, closeDist) { if ( !isDefined( closeDist ) ) closeDist = 3000; infront = targetisinfront(other, target); if(infront) dir = 1; else dir = -1; a = flat_origin(other.origin); b = a + ( AnglesToForward( flat_angle( other.angles ) ) * ( dir * 100000 ) ); point = pointOnSegmentNearestToPoint(a,b, target); dist = distance(a,point); if (dist < closeDist) return true; else return false; } targetisinfront(other, target) { forwardvec = anglestoforward(flat_angle(other.angles)); normalvec = vectorNormalize(flat_origin(target)-other.origin); dot = vectordot(forwardvec,normalvec); if(dot > 0) return true; else return false; } waitForAirstrikeCancel() { self waittill( "cancel_location" ); self setblurforplayer( 0, 0.3 ); } selectAirstrikeLocation( lifeId, streakname ) { targetSize = level.mapSize / 6.46875; // 138 in 720 if ( level.splitscreen ) targetSize *= 1.5; chooseDirection = false; switch( streakName ) { case "precision_airstrike": chooseDirection = true; self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_hqr_airstrike" ); break; case "stealth_airstrike": chooseDirection = true; self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_hqr_bomber" ); break; } if ( streakName != "harrier_airstrike" ) { self _beginLocationSelection( streakname, "map_artillery_selector", chooseDirection, targetSize ); self endon( "stop_location_selection" ); // wait for the selection. randomize the yaw if we're not doing a precision airstrike. self waittill( "confirm_location", location, directionYaw ); } else { playerPositions = []; //get best point of strike. //maybe test for outliers or find largest clump of players in future. foreach ( player in level.players ) { if (!isDefined( player ) ) continue; if ( !isDefined( player.team ) ) continue; if ( player.team == self.team ) continue; playerPositions[playerPositions.size] = player.origin; } if( playerPositions.size ) strikePos = AveragePoint( playerPositions ); else strikePos = (0,0,0); location = strikePos; directionYaw = randomint(360); } if ( !chooseDirection ) directionYaw = randomint(360); self setblurforplayer( 0, 0.3 ); if ( streakname == "harrier_airstrike" && ( isDefined( level.harrier_incoming ) || level.harriers.size > 1 ) ) { self notify ( "cancel_location" ); self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" ); return false; } self thread airstrikeMadeSelectionVO( streakName ); self maps\mp\_matchdata::logKillstreakEvent( streakName, location ); self thread finishAirstrikeUsage( lifeId, location, directionYaw, streakName ); return true; } finishAirstrikeUsage( lifeId, location, directionYaw, streakName ) { self notify( "used" ); // find underside of top of skybox trace = bullettrace( level.mapCenter + (0,0,1000000), level.mapCenter, false, undefined ); location = (location[0], location[1], trace["position"][2] - 514); /# if( GetDvarInt( "scr_debugairstrike" ) ) { self thread debugLocation( trace, location ); } #/ thread doAirstrike( lifeId, location, directionYaw, self, self.pers["team"], streakName ); } useAirstrike( lifeId, pos, yaw ) { } handleEMP( owner ) // self == plane { self endon ( "death" ); if ( owner isEMPed() ) { self notify( "death" ); return; } for ( ;; ) { level waittill ( "emp_update" ); if ( !owner isEMPed() ) continue; self notify( "death" ); } } airstrikeMadeSelectionVO( streakName ) { self endon( "death" ); self endon( "disconnect" ); switch( streakName ) { case "precision_airstrike": self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_ast_inbound" ); break; case "stealth_airstrike": self PlayLocalSound( game[ "voice" ][ self.team ] + "KS_bmb_inbound" ); break; } }