2024-12-11 11:28:08 +01:00

444 lines
12 KiB
Plaintext

#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
// _plane.gsc
// a modular component intended to control a plane flying overhead
// adapted from _airstrike.gsc
kPlaneHealth = 800;
init()
{
if ( !IsDefined( level.planes ) )
{
level.planes = [];
}
if ( !IsDefined( level.planeConfigs ) )
{
level.planeConfigs = [];
}
level.fighter_deathfx = LoadFX( "vfx/gameplay/explosions/vehicle/hind_mp/vfx_x_mphnd_primary" );
level.fx_airstrike_afterburner = loadfx ("vfx/gameplay/mp/killstreaks/vfx_air_superiority_afterburner");
level.fx_airstrike_contrail = loadfx ("vfx/gameplay/mp/killstreaks/vfx_aircraft_contrail");
level.fx_airstrike_wingtip_light_green = LoadFX ( "vfx/gameplay/mp/killstreaks/vfx_acraft_light_wingtip_green" );
level.fx_airstrike_wingtip_light_red = LoadFX ( "vfx/gameplay/mp/killstreaks/vfx_acraft_light_wingtip_red" );
/*
* streakName
* modelNames[] // alllied, axis
* halfDistance
* speed
* initialHeight
* flightSound
* flyTime
* attackTime
* inboundFlightAnim?
* outboundFlightAnim = "airstrike_mp"
* onAttackDelegate
* killcam stuff?
* planeFX?
*/
}
getFlightPath( coord, directionVector, planeHalfDistance, absoluteHeight, planeFlyHeight, planeFlySpeed, attackDistance, streakName )
{
// stealth_airstrike moves this a lot more
startPoint = coord + ( directionVector * ( -1 * planeHalfDistance ) );
endPoint = coord + ( directionVector * planeHalfDistance );
if ( absoluteHeight ) // used in the new height system
{
startPoint *= (1, 1, 0);
endPoint *= (1, 1, 0);
}
startPoint += ( 0, 0, planeFlyHeight );
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( 0.5 * d + attackDistance );
attackTime = ( d / planeFlySpeed );
assert( flyTime > attackTime );
flightPath["startPoint"] = startPoint;
flightPath["endPoint"] = endPoint;
flightPath["attackTime"] = attackTime;
flightPath["flyTime"] = flyTime;
return flightPath;
}
//doPlaneStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, streakName )
doFlyby( lifeId, owner, requiredDeathCount, startPoint, endPoint, attackTime, flyTime, directionVector, streakName )
{
plane = planeSpawn( lifeId, owner, startPoint, directionVector, streakName );
plane endon( "death" );
// plane spawning randomness = up to 125 units, biased towards 0
// radius of bomb damage is 512
endPathRandomness = 150;
pathEnd = endPoint + ( (RandomFloat(2) - 1) * endPathRandomness, (RandomFloat(2) - 1) * endPathRandomness, 0 );
plane planeMove( pathEnd, flyTime, attackTime, streakName );
plane planecleanup();
}
planeSpawn( lifeId, owner, startPoint, directionVector, streakName )
{
if ( !isDefined( owner ) )
return;
startPathRandomness = 100;
pathStart = startPoint + ( (RandomFloat(2) - 1) * startPathRandomness, (RandomFloat(2) - 1) * startPathRandomness, 0 );
//self thread DrawLine(pathStart, (AnglesToForward( direction ) * 200000), 120, (1,0,1) );
configData = level.planeConfigs[ streakName ];
plane = undefined;
plane = Spawn( "script_model", pathStart );
plane.team = owner.team;
plane.origin = pathStart;
plane.angles = VectorToAngles( directionVector );
plane.lifeId = lifeId;
plane.streakName = streakName;
plane.owner = owner;
plane SetModel( configData.modelNames[ owner.team ] );
if ( IsDefined( configData.compassIconFriendly ) )
{
plane setObjectiveIcons(configData.compassIconFriendly, configData.compassIconEnemy );
}
plane thread handleDamage();
plane thread handleDeath();
// handle the nuke instead?
// plane thread handleEMP( owner );
startTrackingPlane( plane );
// stealth bomber doesn't have effects
if ( !IsDefined( configData.noLightFx ) )
{
plane thread playPlaneFx();
}
plane PlayLoopSound( configData.inboundSfx );
plane createKillCam( streakName );
return plane;
}
planeMove( destination, flyTime, attackTime, streakName ) // self == plane
{
configData = level.planeConfigs[ streakName ];
// begin flight
self MoveTo( destination, flyTime, 0, 0 );
// begin attack
//thread callStrike_planeSound( plane, bombsite );
// hmm, don't like the timing of these flybys
if ( IsDefined( configData.onAttackDelegate ) )
{
self thread [[ configData.onAttackDelegate ]]( destination, flyTime, attackTime, self.owner, streakName );
}
if ( IsDefined( configData.sonicBoomSfx ) )
{
self thread playSonicBoom( configData.sonicBoomSfx, 0.5 * flyTime );
}
// fly away
wait( 0.65 * flyTime );
if ( IsDefined( configData.outboundSfx ) )
{
self StopLoopSound();
self PlayLoopSound( configData.outboundSfx );
}
if ( IsDefined( configData.outboundFlightAnim ) )
{
self ScriptModelPlayAnimDeltaMotion( configData.outboundFlightAnim );
}
wait ( 0.35 * flyTime );
}
planeCleanup() // self == plane
{
configData = level.planeConfigs[ self.streakName ];
if ( IsDefined( configData.onFlybyCompleteDelegate ) )
{
thread [[ configData.onFlybyCompleteDelegate ]]( self.owner, self, self.streakName );
}
if ( IsDefined( self.friendlyTeamId ) )
{
_objective_delete( self.friendlyTeamId );
_objective_delete( self.enemyTeamID );
}
if ( IsDefined( self.killCamEnt ) )
{
self.killCamEnt Delete();
}
stopTrackingPlane( self );
self notify( "delete" );
self delete();
}
handleEMP( owner ) // self == plane
{
self endon ( "death" );
while ( true )
{
if ( owner isEMPed() )
{
self notify( "death" );
return;
}
level waittill ( "emp_update" );
}
}
handleDeath() // self == plane
{
level endon( "game_ended" );
self endon( "delete" );
self waittill( "death" );
forward = AnglesToForward( self.angles ) * 200;
PlayFX( level.fighter_deathfx, self.origin, forward );
self thread planeCleanup();
}
handleDamage()
{
self endon( "end_remote" );
self maps\mp\gametypes\_damage::monitorDamage(
kPlaneHealth, // should be defined elsewhere
"helicopter", // should there be a death one?
::handleDeathDamage,
::modifyDamage,
true // isKillstreak
);
}
modifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
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.xpPopup, config.destroyedVO, config.callout );
maps\mp\gametypes\_missions::checkAAChallenges( attacker, self, weapon );
}
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" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_wingtip_light_red, self, "tag_right_wingtip" );
wait( 0.5);
PlayFXOnTag( level.fx_airstrike_wingtip_light_green, self, "tag_left_wingtip" );
}
getPlaneFlyHeight()
{
heightEnt = GetEnt( "airstrikeheight", "targetname" );
if ( IsDefined( heightEnt ) )
{
return heightEnt.origin[2];
}
else
{
println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" );
planeFlyHeight = 950;
if ( isdefined( level.airstrikeHeightScale ) )
planeFlyHeight *= level.airstrikeHeightScale;
return planeFlyHeight;
}
}
// getPlaneFlightPlan
// Summary: On certain levels, we use airstrikeheight's position and orientation to indicate a safe flight path. The entity must have script_noteworthy="fixedposition" and have angles set.
// If not specified, we create one that across the player's current field of view.
getPlaneFlightPlan( distFromPlayer ) // self == player
{
result = SpawnStruct();
result.height = getPlaneFlyHeight();
heightEnt = GetEnt( "airstrikeheight", "targetname" );
if ( IsDefined( heightEnt )
&& IsDefined( heightEnt.script_noteworthy )
&& heightEnt.script_noteworthy == "fixedposition"
)
{
result.targetPos = heightEnt.origin;
result.flightDir = AnglesToForward( heightEnt.angles );
if ( RandomInt(2) == 0 )
result.flightDir *= -1;
}
else
{
forwardVec = AnglesToForward( self.angles );
rightVec = AnglesToRight( self.angles );
result.targetPos = self.origin + distFromPlayer * forwardVec;
result.flightDir = -1 * rightVec;
}
return result;
}
getExplodeDistance( height )
{
standardHeight = 850;
standardDistance = 1500;
distanceFrac = standardHeight/height;
newDistance = distanceFrac * standardDistance;
return newDistance;
}
startTrackingPlane( obj )
{
entNum = obj GetEntityNumber();
level.planes[ entNum ] = obj;
}
stopTrackingPlane( obj )
{
entNum = obj GetEntityNumber();
level.planes[ entNum ] = undefined;
}
selectAirstrikeLocation( lifeId, streakname, doStrikeFn )
{
targetSize = level.mapSize / 6.46875; // 138 in 720
if ( level.splitscreen )
targetSize *= 1.5;
config = level.planeConfigs[ streakname ];
if ( IsDefined( config.selectLocationVO ) )
{
self PlayLocalSound( game[ "voice" ][ self.team ] + config.selectLocationVO );
}
self _beginLocationSelection( streakname, "map_artillery_selector", config.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 );
if ( !config.chooseDirection )
{
directionYaw = randomint(360);
}
self setblurforplayer( 0, 0.3 );
if ( IsDefined( config.inboundVO ) )
{
self PlayLocalSound( game[ "voice" ][ self.team ] + config.inboundVO );
}
// turn off logging for DLC killstreaks (no shipped ks uses this)
// self maps\mp\_matchdata::logKillstreakEvent( streakName, location );
self thread [[ doStrikeFn ]]( lifeId, location, directionYaw, streakName );
return true;
}
setObjectiveIcons( friendlyIcon, enemyIcon ) // self == plane
{
friendlyTeamId = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( friendlyTeamId, "active", (0,0,0), friendlyIcon );
Objective_OnEntityWithRotation( friendlyTeamId, self );
self.friendlyTeamId = friendlyTeamId;
enemyTeamID = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( enemyTeamID, "active", (0,0,0), enemyIcon );
Objective_OnEntityWithRotation( enemyTeamID, self );
self.enemyTeamID = enemyTeamID;
if (level.teamBased)
{
Objective_Team( friendlyTeamId, self.team );
Objective_Team( enemyTeamID, getOtherTeam(self.team) );
}
else
{
ownerEntityNum = self.owner GetEntityNumber();
Objective_PlayerTeam( friendlyTeamId, ownerEntityNum );
Objective_PlayerEnemyTeam( enemyTeamID, ownerEntityNum );
}
}
playSonicBoom( soundName, delay )
{
self endon ("death");
wait ( delay );
self PlaySoundOnMovingEnt( soundName );
}
createKillCam( streakName ) // self == plane
{
configData = level.planeConfigs[ streakName ];
if ( IsDefined( configData.killCamOffset ) )
{
planedir = AnglesToForward( self.angles );
killCamEnt = Spawn( "script_model", self.origin + (0,0,100) - planedir * 200 );
killCamEnt.startTime = GetTime();
killCamEnt SetScriptMoverKillCam( "airstrike" );
killCamEnt LinkTo( self, "tag_origin", configData.killCamOffset, ( 0,0,0 ) );
self.killCamEnt = killCamEnt;
}
}