2025-05-21 16:23:17 +02:00

1410 lines
37 KiB
Plaintext

#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
CONST_AIRSTRIKE_Z_OFFSET_FROM_ANCHOR = 750;
CONST_AIRSTRIKE_FLY_IN_DIST = 10000;
CONST_AIRSTRIKE_FLY_IN_TIME = 3;
CONST_AIRSTRIKE_FLY_THROUGH_DIST = 10000;
CONST_AIRSTRIKE_FLY_THROUGH_TIME = 10;
CONST_AIRSTRIKE_WING_WIDTH = 380;
CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST = 500;
init()
{
level._effect[ "airstrike_ground" ] = LoadFX( "vfx/explosion/clusterbomb_explode" );
level._effect[ "airstrike_bombs" ] = LoadFX ( "vfx/explosion/vfx_clusterbomb" );
level._effect[ "airstrike_death" ] = LoadFX ( "vfx/explosion/vehicle_warbird_explosion_midair" );
level._effect[ "airstrike_engine" ] = LoadFX ( "vfx/fire/jet_afterburner" );
level._effect[ "airstrike_wingtip" ] = LoadFX ( "vfx/trail/jet_contrail" );
level.harriers = [];
level.planes = [];
level.artilleryDangerCenters = [];
// 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["strafing_run_airstrike"] = 900;
level.dangerMinRadius["strafing_run_airstrike"] = 750;
level.dangerForwardPush["strafing_run_airstrike"] = 1;
level.dangerOvalScale["strafing_run_airstrike"] = 6.0;
level.killStreakFuncs["strafing_run_airstrike"] = ::tryUseStrafingRunAirstrike;
level.killstreakWieldWeapons["stealth_bomb_mp"] = "strafing_run_airstrike";
level.killstreakWieldWeapons["airstrike_missile_mp"] = "strafing_run_airstrike";
level.killstreakWieldWeapons["orbital_carepackage_pod_plane_mp"] = "strafing_run_airstrike";
}
tryUseStrafingRunAirstrike( lifeId, modules )
{
return tryUseAirstrike( lifeId, "strafing_run_airstrike", modules );
}
tryUseAirstrike( lifeId, streakName, modules )
{
if ( IsDefined( level.strafing_run_airstrike ) )
{
self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
return false;
}
result = self selectAirstrikeLocation( lifeId, streakName, modules );
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 ) == 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 ) == 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, modules )
{
assert( isDefined( origin ) );
assert( isDefined( yaw ) );
if ( isDefined( level.airstrikeInProgress ) )
{
while ( isDefined( level.airstrikeInProgress ) )
level waittill ( "begin_airstrike" );
level.airstrikeInProgress = true;
wait ( 2.0 );
}
if( !isDefined( owner ) )
return;
level.airstrikeInProgress = true;
targetpos = dropSiteTrace( origin, owner );
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 ); #/
harrierEnt = callStrike( lifeId, owner, targetpos, yaw, streakName, modules );
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", 0) != 1 && getdvarint("scr_spawnpointdebug", 0) == 0 )
{
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;
}
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;
}
}
#/
bomberDropCarepackges( plane, owner )
{
plane endon( "death" );
plane endon( "crashing" );
dropInfo = SpawnStruct();
dropInfo.usedNodes = [];
dropInfo.crateTypes = [];
waitillAirstrikeOverBombingArea( plane );
wait 0.1;
start = GetTime();
dropInfo.crateTypes[0] = shootDownCarepackage( plane, owner, dropInfo );
end = GetTime();
waitTime = 0.1 - ( ( end - start ) / 1000 );
if ( waitTime > 0 )
wait waitTime;
start = GetTime();
dropInfo.crateTypes[1] = shootDownCarepackage( plane, owner, dropInfo );
end = GetTime();
waitTime = 0.1 - ( ( end - start ) / 1000 );
if ( waitTime > 0 )
wait waitTime;
dropInfo.crateTypes[2] = shootDownCarepackage( plane, owner, dropInfo );
}
shootDownCarepackage( plane, owner, dropInfo )
{
position = dropSiteTrace( plane.origin, plane );
// gently influence the drop points to a safe node
node = findCloseNode( position, plane, dropInfo, plane.dropSite, owner );
if ( !IsDefined( node ) )
{
node = SpawnStruct();
node.origin = position;
}
dropStart = plane.origin + ( 0, 0, -5 );
return maps\mp\killstreaks\_orbital_carepackage::FirePod( "orbital_carepackage_pod_plane_mp", owner, node, "airdrop_assault", [], undefined, dropStart, dropInfo.crateTypes, false );
}
dropSiteTrace( org, ignoreEnt )
{
start = org;
end = start + ( 0, 0, -1000000 );
trace = BulletTrace( start, end, false, ignoreEnt );
// check if we've hit a flying scorestreak, if so continue the trace
ent = trace["entity"];
while ( IsDefined( ent ) && IsDefined( ent.vehicleType ) )
{
waitframe();
start = trace["position"];
trace = BulletTrace( start, end, false, ent );
ent = trace["entity"];
}
return trace["position"];
}
withinOtherCarepackageNodes( position, dropInfo )
{
ORBITAL_TRACE_CP_RADIUS = 26;
dist = ORBITAL_TRACE_CP_RADIUS * 2;
distSqMin = dist * dist;
foreach ( node in dropInfo.usedNodes )
{
distSq = Distance2DSquared( node.origin, position );
if ( distSq < distSqMin )
return true;
}
return false;
}
findCloseNode( position, plane, dropInfo, dropSite, owner )
{
numFrames = 4;
numTracesPerFrame = 5;
start = plane.origin;
nodes = GetNodesInRadiusSorted( dropSite, 300, 0, 1000 );
bestNode = undefined;
foreach ( node in nodes )
{
if ( numFrames <= 0 )
break;
if ( !NodeExposedToSky( node, true ) )
continue;
if ( array_contains( dropInfo.usedNodes, node ) )
continue;
if ( withinOtherCarepackageNodes( node.origin, dropInfo ) )
continue;
end = node.origin + ( 0, 0, 5 );
passEnt = owner;
if ( !IsDefined( passEnt ) )
passEnt = plane;
dropInfo.usedNodes[dropInfo.usedNodes.size] = node;
if ( BulletTracePassed( start, end, false, plane ) && maps\mp\killstreaks\_orbital_util::carepackageTrace( node.origin, passEnt, "carepackage" ) )
{
bestNode = node;
break;
}
numTracesPerFrame--;
if ( numTracesPerFrame <= 0 )
{
numFrames--;
numTracesPerFrame = 5;
waitframe();
}
}
return bestNode;
}
doBomberStrike( lifeId, owner, bombsite, startPoint, direction, streakName, modules )
{
if ( !isDefined( owner ) )
return;
if ( !array_contains( modules, "strafing_run_airstrike_two" ) )
{
thread spawnAirstrikePlane( lifeId, owner, bombsite, startPoint, direction, streakName, modules, true );
}
else
{
startPointStruct = SpawnStruct();
getAdditionalBomberPlaneStarts( startPoint, direction, startPointStruct );
thread spawnAirstrikePlane( lifeId, owner, bombsite, startPointStruct.startPoint1, direction, streakName, modules, true );
wait 1;
thread spawnAirstrikePlane( lifeId, owner, bombsite, startPointStruct.startPoint2, direction, streakName, modules, false );
}
}
getAdditionalBomberPlaneStarts( startPoint, direction, startPointStruct )
{
dirRight = AnglesToRight( direction );
startPointStruct.startPoint1 = startPoint + ( dirRight * CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST );
startPointStruct.startPoint2 = startPoint + ( dirRight * -1 * CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST );
}
spawnAirstrikePlane( lifeId, owner, bombsite, startPoint, direction, streakName, modules, doCoop )
{
enemyIcon = "compass_objpoint_airstrike_busy";
if ( array_contains( modules, "strafing_run_airstrike_stealth" ) )
enemyIcon = "compass_objpoint_b2_airstrike_enemy";
// Spawn the plane
plane = Spawn( "script_model", startPoint );
plane.angles = direction;
plane SetModel( "vehicle_airplane_shrike" );
/#
PrintLn( "Strafing Run: fly height = " + startPoint[2] + ", make nosight clip at least 800 units wide." );
#/
plane.minimapIcon = spawnplane( owner, "script_model", startPoint, "compass_objpoint_airstrike_friendly", enemyIcon );
plane.minimapIcon SetModel( "tag_origin" );
plane.minimapIcon LinkToSynchronizedParent( plane, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) );
plane.modules = modules;
plane.vehicleType = "strafing_run";
addPlaneToList( plane );
plane SetCanDamage( true );
plane SetCanRadiusDamage( true );
plane thread maps\mp\gametypes\_damage::setEntityDamageCallback( 1000, undefined, ::onAirStrikeDeath, maps\mp\killstreaks\_aerial_utility::heli_ModifyDamage, true );
if ( array_contains( plane.modules, "strafing_run_airstrike_flares" ) )
plane thread airstrike_flares_monitor();
plane thread handleDeath();
/# plane thread planeDebugCrash();#/
plane playLoopSound( "bombrun_jet_dist_loop" );
plane.lifeId = lifeId;
plane.owner = owner;
plane.team = owner.team;
plane.dropSite = bombsite;
plane.enteringBombingArea = true;
plane thread planeAnimatePath( bombsite );
plane thread planePlayEffects();
thread stealthBomber_killCam( plane, streakName );
if ( array_contains( plane.modules, "strafing_run_airstrike_package" ) )
thread bomberDropCarepackges( plane, owner );
else
thread bomberDropBombs( plane, owner );
if ( level.teamBased && doCoop )
level thread handleCoopJoining( plane, owner );
plane endon( "death" );
plane endon( "crashing" );
waitillAirstrikeOverBombingArea( plane );
plane.enteringBombingArea = false;
plane waittill( "pathComplete" );
level.strafing_run_airstrike = undefined;
plane notify( "airstrike_complete" );
removePlaneFromList( plane );
plane waittillmatch( "airstrike", "end" );
plane notify( "delete" );
if ( IsDefined( plane.minimapIcon ) )
plane.minimapIcon Delete();
plane delete();
}
planeHandleHostMigration()
{
self endon( "airstrike_complete" );
self endon( "pathComplete" );
while ( true )
{
level waittill( "host_migration_begin" );
self ScriptModelPauseAnim( true );
level waittill( "host_migration_end" );
self ScriptModelPauseAnim( false );
}
}
planeAnimatePath( dropSite ) // self == plane
{
self endon( "airstrike_complete" );
self ScriptModelPlayAnimDeltaMotion( "strafing_run_ks_flyby", "airstrike" );
self thread planeHandleHostMigration();
self.status = "flying_in";
self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_IN_DIST / CONST_AIRSTRIKE_FLY_IN_TIME );
wait CONST_AIRSTRIKE_FLY_IN_TIME;
self.status = "strike";
self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_THROUGH_DIST / CONST_AIRSTRIKE_FLY_THROUGH_TIME );
wait CONST_AIRSTRIKE_FLY_THROUGH_TIME;
self.status = "flying_out";
self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_IN_DIST / CONST_AIRSTRIKE_FLY_IN_TIME );
wait CONST_AIRSTRIKE_FLY_IN_TIME - 0.1;
self notify( "pathComplete" );
}
airstrike_flares_monitor() // self == plane
{
self.numFlares = 4;
self thread maps\mp\killstreaks\_aerial_utility::handleIncomingStinger();
}
onAirStrikeDeath( attacker, weapon, meansOfDeath, damage )
{
self thread crashPlane();
self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, meansOfDeath, damage, "strafing_run_destroyed", undefined, "callout_destroyed_airstrike", true );
}
crashPlane() // self == plane
{
self notify( "crashing" );
self.crashed = true;
}
bomberDropBombs( plane, owner )
{
plane endon( "airstrike_complete" );
while ( !targetIsClose( plane, plane.dropSite, 5000 ) )
wait ( 0.05 );
showFx = true;
sonicBoom = false;
plane notify ( "start_bombing" );
plane thread playBombFx();
for ( dist = targetGetDist( plane, plane.dropSite ); dist < 5000; dist = targetGetDist( plane, plane.dropSite ) )
{
if ( dist < 1500 && !sonicBoom )
{
//plane playSound( "veh_b2_sonic_boom" );
sonicBoom = true;
}
showFx = !showFx;
if ( dist < 4500 )
plane thread callStrike_bomb( plane.origin, owner, (0,0,0), showFx );
wait ( 0.1 );
}
plane notify ( "stop_bombing" );
level.strafing_run_airstrike = undefined;
}
playBombFx() // self == plane
{
self endon( "stop_bombing" );
self endon( "airstrike_complete" );
self.bomb_tag_left = Spawn( "script_model", ( 0, 0, 0 ) );
self.bomb_tag_left SetModel( "tag_origin" );
self.bomb_tag_left LinkTo( self, "bombaydoor_left_jnt", ( 0, 0, 0 ), ( 0, -90, 0 ) );
self.bomb_tag_right = Spawn( "script_model", ( 0, 0, 0 ) );
self.bomb_tag_right SetModel( "tag_origin" );
self.bomb_tag_right LinkTo( self, "bombaydoor_right_jnt", ( 0, 0, 0 ), ( 0, -90, 0 ) );
for ( ;; )
{
playFxOnTag( getfx( "airstrike_bombs" ), self.bomb_tag_left, "tag_origin" );
playFxOnTag( getfx( "airstrike_bombs" ), self.bomb_tag_right, "tag_origin" );
wait ( 0.5 );
}
}
stealthBomber_killCam( plane, streakName )
{
plane endon( "airstrike_complete" );
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( 16.0 );
killCamEnt linkTo( plane, "tag_origin", (-256,768,768), ( 0,0,0 ) );
}
callStrike_bomb( coord, owner, offset, showFx )
{
self endon( "airstrike_complete" );
if ( !isDefined( owner ) || owner isEMPed() || owner isAirDenied() )
{
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, self );
bombPoint = trace["position"];
bombHeight = distance( coord, bombPoint );
if ( bombHeight > 10000 )
return;
wait ( 0.85 * (bombHeight / 2000) );
if ( !isDefined( owner ) || owner isEMPed() || owner isAirDenied() )
{
self notify( "stop_bombing" );
return;
}
if ( showFx )
{
playFx( getfx( "airstrike_ground" ), bombPoint );
level thread maps\mp\gametypes\_shellshock::stealthAirstrike_earthQuake( bombPoint );
}
thread playSoundInSpace( "bombrun_snap", bombPoint );
radiusArtilleryShellshock( bombPoint, 512, 8, 4, owner.team );
self RadiusDamage( bombPoint + (0,0,16), 896, 300, 50, owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" );
if( IsDefined( level.ishorde ) && level.ishorde && isdefined( level.flying_attack_drones ) )
{
foreach( drone in level.flying_attack_drones )
{
if( drone.origin[2] > ( bombPoint[2] - 24 ) && drone.origin[2] < ( bombPoint[2] + 1000 ) && Distance2DSquared( drone.origin, bombPoint ) < ( 90000 ))
drone DoDamage( RandomIntRange( 50,300 ), bombPoint + (0,0,16), owner, owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" );
}
}
}
handleDeath( player ) // self == plane
{
level endon( "game_ended" );
self endon( "delete" );
self waittill_either( "death", "crashing" );
forward = AnglesToForward( self.angles );
PlayFX( getfx( "airstrike_death" ), self.origin, forward );
playSoundinSpace( "bombrun_air_death", self.origin );
self notify( "airstrike_complete" );
removePlaneFromList( self );
level.strafing_run_airstrike = undefined;
if ( IsDefined( self.minimapIcon ) )
self.minimapIcon Delete();
self delete();
}
addPlaneToList( plane )
{
level.planes[ level.planes.size ] = plane;
}
removePlaneFromList( plane )
{
level.planes = array_remove( level.planes, plane );
}
deleteAfterTime( time )
{
self endon ( "death" );
wait time;
self delete();
}
planePlayEffects() // self == plane
{
self endon ( "airstrike_complete" );
waitframe();
playfxontag( getfx( "airstrike_engine" ), self, "tag_engine_right" );
playfxontag( getfx( "airstrike_engine" ), self, "tag_engine_left" );
playfxontag( getfx( "airstrike_wingtip" ), self, "tag_right_wingtip" );
playfxontag( getfx( "airstrike_wingtip" ), self, "tag_left_wingtip" );
}
callStrike( lifeId, owner, coord, yaw, streakName, modules )
{
thread teamPlayerCardSplash( "used_strafing_run_airstrike", owner, owner.team );
planeFlyHeight = getPlaneFlyHeight();
/#
if( GetDvarInt( "scr_debugairstrike", 0 ) )
{
self thread debugFlyHeight( planeFlyHeight );
}
#/
owner endon("disconnect");
direction = ( 0, yaw, 0 );
startPoint = getFlightPath( coord, direction, planeFlyHeight );
level thread doBomberStrike( lifeId, owner, coord, startPoint, direction, streakName, modules );
}
getPlaneFlyHeight()
{
height_modifier = 0;
if ( IsDefined( level.airstrikeoverrides) && IsDefined( level.airstrikeoverrides.spawnHeight ) )
height_modifier = level.airstrikeoverrides.spawnHeight;
heliAnchor = maps\mp\killstreaks\_aerial_utility::getHeliAnchor();
return heliAnchor.origin[2] + CONST_AIRSTRIKE_Z_OFFSET_FROM_ANCHOR + height_modifier;
}
getFlightPath( coord, direction, planeFlyHeight )
{
planeHalfDistance = ( getFlightDistance() ) / 2;
startPoint = coord + ( AnglesToForward( direction ) * ( -1 * planeHalfDistance ) );
startPoint *= (1,1,0);
startPoint += ( 0, 0, planeFlyHeight );
return startPoint;
}
getFlightDistance()
{
return ( CONST_AIRSTRIKE_FLY_IN_DIST + CONST_AIRSTRIKE_FLY_IN_DIST + CONST_AIRSTRIKE_FLY_THROUGH_DIST );
}
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 endon( "location_selection_complete" );
self endon( "disconnect" );
self waittill( "stop_location_selection" );
self setblurforplayer( 0, 0.3 );
self SetClientOmnvar( "ui_map_location_blocked", 0 );
if ( maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone() > 0 )
self SwitchToWeapon( self getLastWeapon() );
level.strafing_run_airstrike = undefined;
}
selectAirstrikeLocation( lifeId, streakname, modules )
{
if( !IsDefined( level.mapSize ) ) // we're probably in a test map
{
level.mapSize = 1024;
}
targetSize = level.mapSize / 6.46875; // 138 in 720
if ( level.splitscreen )
targetSize *= 1.5;
level.strafing_run_airstrike = true;
chooseDirection = true;
numPlanes = 1;
if ( array_contains( modules, "strafing_run_airstrike_two" ) )
numPlanes = 2;
self SetClientOmnvar( "ui_map_location_use_carepackages", array_contains( modules, "strafing_run_airstrike_package" ) );
self SetClientOmnvar( "ui_map_location_num_planes", numPlanes );
self SetClientOmnvar( "ui_map_location_height", getPlaneFlyHeight() );
self _beginLocationSelection( streakname, "map_artillery_selector", chooseDirection, targetSize );
self thread waitForAirstrikeCancel();
self endon( "stop_location_selection" );
self endon( "disconnect" );
dropSite = undefined;
flyYaw = undefined;
validFlightPath = false;
while ( !validFlightPath )
{
// wait for the selection. randomize the yaw if we're not doing a precision airstrike.
self waittill( "confirm_location", location, directionYaw );
if ( !chooseDirection )
directionYaw = 0;
if ( validateFlightLocationAndDirection( location, directionYaw, modules, self ) )
{
dropSite = location;
flyYaw = directionYaw;
self SetClientOmnvar( "ui_map_location_use_carepackages", false );
self SetClientOmnvar( "ui_map_location_num_planes", 0 );
self SetClientOmnvar( "ui_map_location_height", 0 );
break;
}
else
{
self thread showBlockedHUD();
}
}
self setblurforplayer( 0, 0.3 );
self notify( "location_selection_complete" );
self SetClientOmnvar( "ui_map_location_blocked", 0 );
self maps\mp\_matchdata::logKillstreakEvent( streakName, dropSite );
self thread finishAirstrikeUsage( lifeId, [dropSite], [flyYaw], streakName, modules );
return true;
}
showBlockedHUD()
{
self endon( "location_selection_complete" );
self endon( "disconnect" );
self endon( "stop_location_selection" );
self notify( "airstrikeShowBlockedHUD" );
self endon( "airstrikeShowBlockedHUD" );
if ( self GetClientOmnvar( "ui_map_location_blocked" ) == 0 )
self PlayLocalSound( "recon_drn_cloak_notready" );
self SetClientOmnvar( "ui_map_location_blocked", 1 );
wait 1.5;
self SetClientOmnvar( "ui_map_location_blocked", 0 );
}
validateFlightLocationAndDirection( loc, yaw, modules, owner )
{
planeFlyHeight = getPlaneFlyHeight();
numPlanes = 1;
if ( array_contains( modules, "strafing_run_airstrike_two" ) )
numPlanes = 2;
return BombingRunTracePassed( loc, planeFlyHeight, yaw, numPlanes );
}
finishAirstrikeUsage( lifeId, locations, directions, streakName, modules )
{
self notify( "used" );
for ( i = 0; i < locations.size; i++ )
{
location = locations[i];
directionYaw = directions[i];
// 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", 0 ) )
{
self thread debugLocation( trace, location );
}
#/
thread doAirstrike( lifeId, location, directionYaw, self, self.pers["team"], streakName, modules );
}
}
waitillAirstrikeOverBombingArea( plane )
{
plane endon( "airstrike_complete" );
while ( !targetIsClose( plane, plane.dropSite, 200 ) )
waitframe();
}
playerDelayControl() // self == player
{
self endon( "disconnect" );
self freezeControlsWrapper( true );
wait( 0.5 );
self freezeControlsWrapper( false );
}
PlayerDoRideKillstreak( plane )
{
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( "coop", false, 0.5 );
if ( result != "success" || !IsDefined( plane ) )
{
if ( result != "disconnect" )
{
if ( !IsDefined( plane ) )
self thread playerRemoteKillstreakShowHud();
self playerReset( false );
self maps\mp\killstreaks\_coop_util::playerResetAfterCoopStreak();
}
self notify( "initRideKillstreak_complete", false );
return;
}
self notify( "initRideKillstreak_complete", true );
}
handleCoopJoining( plane, player )
{
team = player.team;
if ( player.team == "allies" )
{
assistVO = "SE_1mc_orbitalsupport_buddyrequest";
buddyVO = "SE_1mc_orbitalsupport_buddy";
}
else // == "axis"
{
assistVO = "AT_1mc_orbitalsupport_buddyrequest";
buddyVO = "AT_1mc_orbitalsupport_buddy";
}
waittillOverPlayspace( plane );
if ( !IsDefined( plane ) )
return;
// while ( true )
{
id = maps\mp\killstreaks\_coop_util::promptForStreakSupport( team, &"MP_JOIN_STRAFING_RUN", "strafing_run_airstrike_coop_offensive", assistVO, buddyVO, player );
level thread watchForJoin( id, plane, player );
result = waittillPromptComplete( plane, "buddyJoinedStreak" );
maps\mp\killstreaks\_coop_util::stopPromptForStreakSupport( id );
if ( !IsDefined( result) )
return;
result = waittillPromptComplete( plane, "airstrike_buddy_removed" );
if ( !IsDefined( result ) )
return;
}
}
notifyCoopOver( plane )
{
plane endon( "airstrike_complete" );
if ( plane.enteringBombingArea )
waitillAirstrikeOverBombingArea( plane );
waittillLeftPlayspace( plane, 1.65 );
plane notify( "coopJoinOver" );
}
waittillOverPlayspace( plane )
{
timeSec = 1.65; // 0.2 sec weapon switch, 0.2 sec blackout, 1.25 coop join bar
dir = AnglesToForward( plane.angles );
while ( true )
{
waitframe();
if ( !IsDefined( plane ) )
return;
distToEdge = plane.flyingSpeed * timeSec;
start = plane.origin + ( dir * distToEdge );
end = start + ( 0, 0, -10000 );
trace = BulletTrace( start, end, false, plane );
if ( trace["fraction"] == 1 )
continue;
pos = trace["position"];
nodes = GetNodesInRadius( pos, 300, 0 );
if ( nodes.size > 0 )
{
// Line( start, end, ( 0, 1, 0 ), 1, 0, 1000 );
break;
}
}
}
waittillLeftPlayspace( plane, timeSec )
{
plane endon( "airstrike_complete" );
if ( !IsDefined( timeSec ) )
timeSec = 0;
while ( true )
{
waitframe();
distToEdge = plane.flyingSpeed * timeSec;
dir = AnglesToForward( plane.angles );
start = plane.origin + ( dir * distToEdge );
end = start + ( 0, 0, -10000 );
trace = BulletTrace( start, end, false, plane );
if ( trace["fraction"] == 1 )
break;
pos = trace["position"];
nodes = GetNodesInRadius( pos, 300, 0 );
if ( nodes.size == 0 )
{
// Line( start, end, ( 0, 1, 0 ), 1, 0, 1000 );
break;
}
}
}
waittillFireMissile( plane, player )
{
player endon( "airstrike_fire" );
plane endon( "airstrike_complete" );
if ( plane.enteringBombingArea )
waitillAirstrikeOverBombingArea( plane );
waittillLeftPlayspace( plane );
}
waittillPromptComplete( plane, text )
{
plane endon( "airstrike_complete" );
plane endon( "coopJoinOver" );
plane waittill( text );
return true;
}
watchForJoin( id, plane, primaryPlayer )
{
buddy = waittillBuddyJoinedAirstrike( id, plane );
if ( !IsDefined( buddy ) )
return;
plane notify( "buddyJoinedStreak" );
level notify( "buddyGO" );
buddy thread PlayerDoRideKillstreak( plane );
buddy waittill( "initRideKillstreak_complete", success );
if ( !success )
return;
buddy playerSaveAngles();
buddy setUsingRemote( "strafing_run" );
buddy NotifyOnPlayerCommand( "airstrike_fire", "+attack" );
buddy NotifyOnPlayerCommand( "airstrike_fire", "+attack_akimbo_accessible" );
missileStrikeTurret = SpawnTurret( "misc_turret", plane GetTagOrigin( "tag_origin" ), "sentry_minigun_mp" );
missileStrikeTurret TurretFireDisable();
missileStrikeTurret SetModel( "tag_turret" );
missileStrikeTurret LinkToSynchronizedParent( plane, "tag_origin", ( 0, 0, 0 ), ( 70, 180, 0 ) );
buddy PlayerLinkWeaponViewToDelta( missileStrikeTurret, "tag_player", 0, 180, 180, 5, 15, false );
buddy PlayerLinkedSetViewZNear( false );
buddy PlayerLinkedSetUseBaseAngleForViewClamp( true );
buddy RemoteControlTurret( missileStrikeTurret, 60, 45 );
MissileWeapon = buddy maps\mp\killstreaks\_missile_strike::BuildWeaponSettings( [] );
MissileEyesInit( buddy, MissileWeapon, primaryPlayer );
waittillFireMissile( plane, buddy );
if ( IsDefined( buddy ) )
{
Earthquake( 0.4, 1, buddy GetViewOrigin(), 300 );
fireMissile( buddy, missileStrikeTurret, MissileWeapon );
if ( IsDefined( buddy ) )
{
buddy maps\mp\killstreaks\_coop_util::playerResetAfterCoopStreak();
buddy NotifyOnPlayerCommandRemove( "airstrike_fire", "+attack" );
buddy NotifyOnPlayerCommandRemove( "airstrike_fire", "+attack_akimbo_accessible" );
}
}
missileStrikeTurret Delete();
}
waittillBuddyJoinedAirstrike( id, plane )
{
plane endon( "airstrike_complete" );
plane endon( "coopJoinOver" );
thread notifyCoopOver( plane );
buddy = maps\mp\killstreaks\_coop_util::waittillBuddyJoinedStreak( id );
return buddy;
}
fireMissile( player, turret, MissileWeapon )
{
start = turret GetTagOrigin( "tag_player" );
dir = AnglesToForward( turret GetTagAngles( "tag_player" ) );
end = start + ( dir * 10000 );
rocket = MagicBullet( "airstrike_missile_mp", start, end, player );
rocket.owner = player;
waitframe();
if ( !IsDefined( player ) )
return;
player Unlink();
player RemoteControlTurretOff( turret );
player SetClientOmnvar( "fov_scale", 65.0 / 15.0 );
MissileEyesGo( player, rocket, MissileWeapon );
if ( !IsDefined( player ) )
return;
player SetClientOmnvar( "fov_scale", 1.0 );
}
MissileEyesInit( player, MissileWeapon, primaryPlayer )
{
player thread hudInit( MissileWeapon, primaryPlayer );
player ThermalVisionFOFOverlayOn();
if ( getDvarInt( "camera_thirdPerson" ) )
player setThirdPersonDOF( false );
}
MissileEyesGo( player, rocket, MissileWeapon )
{
player endon ( "joined_team" );
player endon ( "joined_spectators" );
player endon ( "player_control_strike_over" );
player endon ( "disconnect" );
MissileWeapon endon ( "ms_early_exit" );
rocket thread maps\mp\killstreaks\_missile_strike::Rocket_CleanupOnDeath();
player thread maps\mp\killstreaks\_missile_strike::Player_CleanupOnGameEnded( rocket, MissileWeapon );
player thread maps\mp\killstreaks\_missile_strike::Player_CleanupOnTeamChange( rocket, MissileWeapon );
player thread hudGo( rocket, MissileWeapon );
player thread playerWaitReset( MissileWeapon );
player CameraLinkTo( rocket, "tag_origin" );
player ControlsLinkTo( rocket );
player thread maps\mp\killstreaks\_missile_strike::playerWatchForEarlyExit( MissileWeapon );
rocket waittill_notify_or_timeout( "death", 10 ); ///////////////// WAIT DEATH HERE ///////////////////////////////////////////
MissileWeapon notify( "missile_strike_complete" );
// missile strike needs its own stat
// is defined check required because remote missile doesnt handle lifetime explosion gracefully
// instantly deletes its self after an explode and death notify
//if ( isDefined(rocket) )
//player maps\mp\_matchdata::logKillstreakEvent( "predator_missile", rocket.origin );
}
playerWaitReset( MissileWeapon )
{
MissileWeapon waittill_either( "missile_strike_complete", "ms_early_exit" );
self playerReset();
}
playerReset( shouldWait )
{
self endon( "disconnect" );
if ( !IsDefined( shouldWait ) )
shouldWait = true;
self ControlsUnlink();
self freezeControlsWrapper( true );
self SetClientOmnvar( "fov_scale", 1.0 );
self stopMissileBoostSounds(); // Played by code during the boost
self maps\mp\killstreaks\_missile_strike::stopMissileBoostSounds(); // Played by code during the boost
// If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point
if ( !level.gameEnded || isDefined( self.finalKill ) )
self maps\mp\killstreaks\_aerial_utility::playerShowFullStatic();
if ( shouldWait )
{
wait ( 0.5 );
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
}
self maps\mp\killstreaks\_missile_strike::remove_hud();
self ThermalVisionFOFOverlayOff();
self CameraUnlink();
self freezeControlsWrapper( false );
if ( self isUsingRemote() )
self clearUsingRemote();
self playerRestoreAngles();
}
stopMissileBoostSounds()
{
self StopLocalSound( "bombrun_support_mstrike_boost_shot" );
self StopLocalSound( "bombrun_support_mstrike_boost_boom" );
self StopLocalSound( "bombrun_support_mstrike_boost_jet" );
}
hudInit( MissileWeapon, primaryPlayer ) // self == player
{
self endon( "disconnect" );
self SetClientOmnvar( "ui_predator_missile", 2 );
self SetClientOmnvar( "ui_coop_primary_num", primaryPlayer GetEntityNumber() );
waitframe(); // Give the handlers time to get in place before we set the rest
self maps\mp\killstreaks\_missile_strike::hud_update_fire_text( undefined, MissileWeapon );
self maps\mp\killstreaks\_aerial_utility::playerEnableStreakStatic();
}
hudGo( rocket, MissileWeapon ) // self == player
{
self thread maps\mp\killstreaks\_missile_strike::targeting_hud_init();
self thread maps\mp\killstreaks\_missile_strike::targeting_hud_think( rocket, MissileWeapon );
}
/#
planeDebugCrash()
{
self endon( "airstrike_complete" );
while ( true )
{
if ( GetDvar( "scr_airstrike_crash", "0" ) != "0" )
{
self thread crashPlane();
SetDvar( "scr_airstrike_crash", "0" );
}
waitframe();
}
}
#/