634 lines
16 KiB
Plaintext
634 lines
16 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include maps\mp\gametypes\_hud_util;
|
|
#include common_scripts\utility;
|
|
|
|
init()
|
|
{
|
|
level.killstreakFuncs[ "heli_pilot" ] = ::tryUseHeliPilot;
|
|
|
|
level.heli_pilot = [];
|
|
|
|
level.heliPilotSettings = [];
|
|
|
|
level.heliPilotSettings[ "heli_pilot" ] = SpawnStruct();
|
|
level.heliPilotSettings[ "heli_pilot" ].timeOut = 60.0;
|
|
level.heliPilotSettings[ "heli_pilot" ].maxHealth = 2000; // this is what we check against for death
|
|
level.heliPilotSettings[ "heli_pilot" ].streakName = "heli_pilot";
|
|
level.heliPilotSettings[ "heli_pilot" ].vehicleInfo = "heli_pilot_mp";
|
|
level.heliPilotSettings[ "heli_pilot" ].modelBase = level.littlebird_model;
|
|
level.heliPilotSettings[ "heli_pilot" ].teamSplash = "used_heli_pilot";
|
|
|
|
heliPilot_setAirStartNodes();
|
|
|
|
// throw the mesh way up into the air, the gdt entry for the vehicle must match
|
|
level.heli_pilot_mesh = GetEnt( "heli_pilot_mesh", "targetname" );
|
|
if( !IsDefined( level.heli_pilot_mesh ) )
|
|
PrintLn( "heli_pilot_mesh doesn't exist in this level: " + level.script );
|
|
else
|
|
level.heli_pilot_mesh.origin += getHeliPilotMeshOffset();
|
|
|
|
config = SpawnStruct();
|
|
config.xpPopup = "destroyed_helo_pilot";
|
|
// !!! NEED VO
|
|
config.voDestroyed = undefined;
|
|
config.callout = "callout_destroyed_helo_pilot";
|
|
config.samDamageScale = 0.09;
|
|
config.engineVFXtag = "tag_engine_right";
|
|
// xpval = 200
|
|
level.heliConfigs[ "heli_pilot" ] = config;
|
|
|
|
/#
|
|
SetDevDvarIfUninitialized( "scr_helipilot_timeout", 60.0 );
|
|
#/
|
|
}
|
|
|
|
tryUseHeliPilot( lifeId, streakName )
|
|
{
|
|
heliPilotType = "heli_pilot";
|
|
|
|
numIncomingVehicles = 1;
|
|
|
|
if ( IsDefined( self.underWater ) && self.underWater )
|
|
{
|
|
return false;
|
|
}
|
|
else if( exceededMaxHeliPilots( self.team ) )
|
|
{
|
|
self IPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
|
|
return false;
|
|
}
|
|
else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
|
|
{
|
|
self IPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
|
|
return false;
|
|
}
|
|
|
|
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
|
|
incrementFauxVehicleCount();
|
|
|
|
heli = createHeliPilot( heliPilotType );
|
|
|
|
if( !IsDefined( heli ) )
|
|
{
|
|
// decrement the faux vehicle count since this failed to spawn
|
|
decrementFauxVehicleCount();
|
|
|
|
return false;
|
|
}
|
|
|
|
level.heli_pilot[ self.team ] = heli;
|
|
|
|
result = self startHeliPilot( heli );
|
|
|
|
if( !IsDefined( result ) )
|
|
result = false;
|
|
|
|
return result;
|
|
}
|
|
|
|
exceededMaxHeliPilots( team )
|
|
{
|
|
if ( level.gameType == "dm" )
|
|
{
|
|
if ( IsDefined( level.heli_pilot[ team ] ) || IsDefined( level.heli_pilot[ level.otherTeam[ team ] ] ) )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ( IsDefined( level.heli_pilot[ team ] ) )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
// watchHostMigrationFinishedInit
|
|
//============================================
|
|
watchHostMigrationFinishedInit( player )
|
|
{
|
|
player endon( "killstreak_disowned" );
|
|
player endon( "disconnect" );
|
|
level endon( "game_ended" );
|
|
self endon ( "death" );
|
|
|
|
for (;;)
|
|
{
|
|
level waittill( "host_migration_end" );
|
|
|
|
player SetClientOmnvar( "ui_heli_pilot", 1 );
|
|
}
|
|
}
|
|
|
|
|
|
createHeliPilot( heliPilotType )
|
|
{
|
|
closestStartNode = heliPilot_getClosestStartNode( self.origin );
|
|
closestNode = heliPilot_getLinkedStruct( closestStartNode );
|
|
startAng = VectorToAngles( closestNode.origin - closestStartNode.origin );
|
|
|
|
forward = AnglesToForward( self.angles );
|
|
targetPos = closestNode.origin + ( forward * -100 );
|
|
|
|
startPos = closestStartNode.origin;
|
|
|
|
heli = SpawnHelicopter( self, startPos, startAng, level.heliPilotSettings[ heliPilotType ].vehicleInfo, level.heliPilotSettings[ heliPilotType ].modelBase );
|
|
if( !IsDefined( heli ) )
|
|
return;
|
|
|
|
// radius and offset should match vehHelicopterBoundsRadius (GDT) and bg_vehicle_sphere_bounds_offset_z.
|
|
heli MakeVehicleSolidCapsule( 18, -9, 18 );
|
|
|
|
heli maps\mp\killstreaks\_helicopter::addToLittleBirdList();
|
|
heli thread maps\mp\killstreaks\_helicopter::removeFromLittleBirdListOnDeath();
|
|
|
|
heli.maxHealth = level.heliPilotSettings[ heliPilotType ].maxHealth;
|
|
|
|
heli.speed = 40;
|
|
heli.owner = self;
|
|
heli SetOtherEnt(self);
|
|
heli.team = self.team;
|
|
heli.heliType = "littlebird";
|
|
heli.heliPilotType = "heli_pilot";
|
|
heli SetMaxPitchRoll( 45, 45 );
|
|
heli Vehicle_SetSpeed( heli.speed, 40, 40 );
|
|
heli SetYawSpeed( 120, 60 );
|
|
heli SetNearGoalNotifyDist( 32 );
|
|
heli SetHoverParams( 100, 100, 100 );
|
|
heli make_entity_sentient_mp( heli.team );
|
|
|
|
heli.targetPos = targetPos;
|
|
heli.currentNode = closestNode;
|
|
|
|
heli.attract_strength = 10000;
|
|
heli.attract_range = 150;
|
|
heli.attractor = Missile_CreateAttractorEnt( heli, heli.attract_strength, heli.attract_range );
|
|
|
|
// heli thread heliPilot_handleDamage(); // since the model is what players will be shooting at, it should handle the damage
|
|
heli thread maps\mp\killstreaks\_helicopter::heli_damage_monitor( "heli_pilot" );
|
|
heli thread heliPilot_lightFX();
|
|
heli thread heliPilot_watchTimeout();
|
|
heli thread heliPilot_watchOwnerLoss();
|
|
heli thread heliPilot_watchRoundEnd();
|
|
heli thread heliPilot_watchObjectiveCam();
|
|
heli thread heliPilot_watchDeath();
|
|
heli thread watchHostMigrationFinishedInit( self );
|
|
|
|
heli.owner maps\mp\_matchdata::logKillstreakEvent( level.heliPilotSettings[ heli.heliPilotType ].streakName, heli.targetPos );
|
|
|
|
return heli;
|
|
}
|
|
|
|
heliPilot_lightFX()
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["light"]["left"], self, "tag_light_nose" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["belly"], self, "tag_light_belly" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail1" );
|
|
wait ( 0.05 );
|
|
PlayFXOnTag( level.chopper_fx["light"]["tail"], self, "tag_light_tail2" );
|
|
}
|
|
|
|
startHeliPilot( heli ) // self == player
|
|
{
|
|
level endon( "game_ended" );
|
|
heli endon( "death" );
|
|
|
|
self setUsingRemote( heli.heliPilotType );
|
|
|
|
if( GetDvarInt( "camera_thirdPerson" ) )
|
|
self setThirdPersonDOF( false );
|
|
|
|
self.restoreAngles = self.angles;
|
|
|
|
heli thread maps\mp\killstreaks\_flares::ks_setup_manual_flares( 2, "+smoke", "ui_heli_pilot_flare_ammo", "ui_heli_pilot_warn" );
|
|
|
|
self thread watchIntroCleared( heli );
|
|
|
|
self freezeControlsWrapper( true );
|
|
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( heli.heliPilotType );
|
|
if( result != "success" )
|
|
{
|
|
if( IsDefined( self.disabledWeapon ) && self.disabledWeapon )
|
|
self _enableWeapon();
|
|
heli notify( "death" );
|
|
|
|
return false;
|
|
}
|
|
|
|
self freezeControlsWrapper( false );
|
|
/*
|
|
// need to link the player here but not give control yet
|
|
self CameraLinkTo( heli, "tag_player" );
|
|
|
|
// go to pos
|
|
heli SetVehGoalPos( heli.targetPos );
|
|
heli waittill( "near_goal" );
|
|
heli Vehicle_SetSpeed( heli.speed, 60, 30 );
|
|
heli waittill( "goal" );
|
|
*/
|
|
|
|
|
|
// make sure we go to the mesh as we enter the map
|
|
traceOffset = getHeliPilotTraceOffset();
|
|
traceStart = ( heli.currentNode.origin ) + ( getHeliPilotMeshOffset() + traceOffset );
|
|
traceEnd = ( heli.currentNode.origin ) + ( getHeliPilotMeshOffset() - traceOffset );
|
|
traceResult = BulletTrace( traceStart, traceEnd, false, undefined, false, false, true );
|
|
if( !IsDefined( traceResult["entity"] ) )
|
|
{
|
|
/#
|
|
// draw where it thinks this is breaking down
|
|
self thread drawSphere( traceResult[ "position" ] - getHeliPilotMeshOffset(), 32, 10000, ( 1, 0, 0 ) );
|
|
self thread drawSphere( heli.currentNode.origin, 16, 10000, ( 0, 1, 0 ) );
|
|
self thread drawLine( traceStart - getHeliPilotMeshOffset(), traceEnd - getHeliPilotMeshOffset(), 10000, ( 0, 0, 1 ) );
|
|
#/
|
|
AssertMsg( "The trace didn't hit the heli_pilot_mesh. Please grab an MP scripter." );
|
|
}
|
|
|
|
targetOrigin = ( traceResult[ "position" ] - getHeliPilotMeshOffset() ) + ( 0, 0, 250 ); // offset to make sure we're on top of the mesh
|
|
targetNode = Spawn( "script_origin", targetOrigin );
|
|
|
|
// link the heli into the mesh and give them control
|
|
self RemoteControlVehicle( heli );
|
|
|
|
heli thread heliGoToStartPosition( targetNode );
|
|
heli thread heliPilot_watchADS();
|
|
|
|
level thread teamPlayerCardSplash( level.heliPilotSettings[ heli.heliPilotType ].teamSplash, self );
|
|
|
|
heli.killCamEnt = Spawn( "script_origin", self GetViewOrigin() );
|
|
|
|
return true;
|
|
}
|
|
|
|
heliGoToStartPosition( targetNode ) // self == heli
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
self RemoteControlVehicleTarget( targetNode );
|
|
self waittill( "goal_reached" );
|
|
self RemoteControlVehicleTargetOff();
|
|
|
|
targetNode delete();
|
|
}
|
|
|
|
watchIntroCleared( heli ) // self == player
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "joined_team" );
|
|
self endon( "joined_spectators" );
|
|
level endon( "game_ended" );
|
|
heli endon( "death" );
|
|
|
|
self waittill( "intro_cleared" );
|
|
self SetClientOmnvar( "ui_heli_pilot", 1 );
|
|
|
|
// highlight the owner
|
|
id = outlineEnableForPlayer( self, "cyan", self, false, "killstreak" );
|
|
self removeOutline( id, heli );
|
|
|
|
// highlight enemies
|
|
foreach( player in level.participants )
|
|
{
|
|
if( !isReallyAlive( player ) || player.sessionstate != "playing" )
|
|
continue;
|
|
|
|
if( self isEnemy( player ) )
|
|
{
|
|
if( !player _hasPerk( "specialty_noplayertarget" ) )
|
|
{
|
|
id = outlineEnableForPlayer( player, "orange", self, false, "killstreak" );
|
|
player removeOutline( id, heli );
|
|
}
|
|
else
|
|
{
|
|
player thread watchForPerkRemoval( heli );
|
|
}
|
|
}
|
|
}
|
|
|
|
// watch for enemies spawning while the pilot is up
|
|
heli thread watchPlayersSpawning();
|
|
|
|
// do this here to make sure we are in the killstreak before letting them leave, it was causing a bug where the player could get stuck on a black screen
|
|
self thread watchEarlyExit( heli );
|
|
}
|
|
|
|
watchForPerkRemoval( heli ) // self == enemy player
|
|
{
|
|
self notify( "watchForPerkRemoval" );
|
|
self endon( "watchForPerkRemoval" );
|
|
|
|
self endon( "death" );
|
|
|
|
// since we give blindeye and noplayetarget each time a player spawns, we need to wait for it to be removed to turn the outline on
|
|
self waittill( "removed_specialty_noplayertarget" );
|
|
id = outlineEnableForPlayer( self, "orange", heli.owner, false, "killstreak" );
|
|
self removeOutline( id, heli );
|
|
}
|
|
|
|
watchPlayersSpawning() // self == heli
|
|
{
|
|
self endon( "leaving" );
|
|
self endon( "death" );
|
|
|
|
while( true )
|
|
{
|
|
level waittill( "player_spawned", player );
|
|
if( player.sessionstate == "playing" && self.owner isEnemy( player ) )
|
|
player thread watchForPerkRemoval( self );
|
|
}
|
|
}
|
|
|
|
removeOutline( id, heli ) // self == player
|
|
{
|
|
self thread heliRemoveOutline( id, heli );
|
|
self thread playerRemoveOutline( id, heli );
|
|
}
|
|
|
|
heliRemoveOutline( id, heli ) // self == player
|
|
{
|
|
self notify( "heliRemoveOutline" );
|
|
self endon( "heliRemoveOutline" );
|
|
|
|
self endon( "outline_removed" );
|
|
self endon( "disconnect" );
|
|
level endon( "game_ended" );
|
|
|
|
wait_array = [ "leaving", "death" ];
|
|
heli waittill_any_in_array_return_no_endon_death( wait_array );
|
|
|
|
if( IsDefined( self ) )
|
|
{
|
|
outlineDisable( id, self );
|
|
self notify( "outline_removed" );
|
|
}
|
|
}
|
|
|
|
playerRemoveOutline( id, heli ) // self == player
|
|
{
|
|
self notify( "playerRemoveOutline" );
|
|
self endon( "playerRemoveOutline" );
|
|
|
|
self endon( "outline_removed" );
|
|
self endon( "disconnect" );
|
|
level endon( "game_ended" );
|
|
|
|
wait_array = [ "death" ];
|
|
self waittill_any_in_array_return_no_endon_death( wait_array );
|
|
|
|
outlineDisable( id, self );
|
|
self notify( "outline_removed" );
|
|
}
|
|
|
|
//
|
|
// state trackers
|
|
//
|
|
|
|
heliPilot_watchDeath()
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "gone" );
|
|
|
|
self waittill( "death" );
|
|
|
|
if( IsDefined( self.owner ) )
|
|
self.owner heliPilot_EndRide( self );
|
|
|
|
if( IsDefined( self.killCamEnt ) )
|
|
self.killCamEnt delete();
|
|
|
|
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
|
|
}
|
|
|
|
|
|
heliPilot_watchObjectiveCam()
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "gone" );
|
|
self.owner endon( "disconnect" );
|
|
self.owner endon( "joined_team" );
|
|
self.owner endon( "joined_spectators" );
|
|
|
|
level waittill( "objective_cam" );
|
|
|
|
self thread maps\mp\killstreaks\_helicopter::lbOnKilled();
|
|
if( IsDefined( self.owner ) )
|
|
self.owner heliPilot_EndRide( self );
|
|
}
|
|
|
|
|
|
heliPilot_watchTimeout()
|
|
{
|
|
level endon ( "game_ended" );
|
|
self endon( "death" );
|
|
self.owner endon( "disconnect" );
|
|
self.owner endon( "joined_team" );
|
|
self.owner endon( "joined_spectators" );
|
|
|
|
timeout = level.heliPilotSettings[ self.heliPilotType ].timeOut;
|
|
/#
|
|
timeout = GetDvarFloat( "scr_helipilot_timeout" );
|
|
#/
|
|
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeout );
|
|
|
|
self thread heliPilot_leave();
|
|
}
|
|
|
|
|
|
heliPilot_watchOwnerLoss()
|
|
{
|
|
level endon ( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
|
|
self.owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
|
|
|
|
// leave
|
|
self thread heliPilot_leave();
|
|
}
|
|
|
|
heliPilot_watchRoundEnd()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
self.owner endon( "disconnect" );
|
|
self.owner endon( "joined_team" );
|
|
self.owner endon( "joined_spectators" );
|
|
|
|
level waittill_any( "round_end_finished", "game_ended" );
|
|
|
|
// leave
|
|
self thread heliPilot_leave();
|
|
}
|
|
|
|
heliPilot_leave()
|
|
{
|
|
self endon( "death" );
|
|
self notify( "leaving" );
|
|
|
|
if( IsDefined( self.owner ) )
|
|
self.owner heliPilot_EndRide( self );
|
|
|
|
// rise
|
|
flyHeight = self maps\mp\killstreaks\_airdrop::getFlyHeightOffset( self.origin );
|
|
targetPos = self.origin + ( 0, 0, flyHeight );
|
|
self Vehicle_SetSpeed( 140, 60 );
|
|
self SetMaxPitchRoll( 45, 180 );
|
|
self SetVehGoalPos( targetPos );
|
|
self waittill( "goal" );
|
|
|
|
// leave
|
|
targetPos = targetPos + AnglesToForward( self.angles ) * 15000;
|
|
// make sure it doesn't fly away backwards
|
|
endEnt = Spawn( "script_origin", targetPos );
|
|
if( IsDefined( endEnt ) )
|
|
{
|
|
self SetLookAtEnt( endEnt );
|
|
endEnt thread wait_and_delete( 3.0 );
|
|
}
|
|
self SetVehGoalPos( targetPos );
|
|
self waittill( "goal" );
|
|
|
|
// remove
|
|
self notify( "gone" );
|
|
self maps\mp\killstreaks\_helicopter::removeLittlebird();
|
|
}
|
|
|
|
wait_and_delete( waitTime )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
wait( waitTime );
|
|
self delete();
|
|
}
|
|
|
|
heliPilot_EndRide( heli )
|
|
{
|
|
if( IsDefined( heli ) )
|
|
{
|
|
self SetClientOmnvar( "ui_heli_pilot", 0 );
|
|
|
|
heli notify( "end_remote" );
|
|
|
|
if( self isUsingRemote() )
|
|
self clearUsingRemote();
|
|
|
|
if( GetDvarInt( "camera_thirdPerson" ) )
|
|
self setThirdPersonDOF( true );
|
|
|
|
self RemoteControlVehicleOff( heli );
|
|
|
|
self SetPlayerAngles( self.restoreAngles );
|
|
|
|
self thread heliPilot_FreezeBuffer();
|
|
}
|
|
}
|
|
|
|
heliPilot_FreezeBuffer()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
self freezeControlsWrapper( true );
|
|
wait( 0.5 );
|
|
self freezeControlsWrapper( false );
|
|
}
|
|
|
|
heliPilot_watchADS() // self == heli
|
|
{
|
|
self endon( "leaving" );
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
already_set = false;
|
|
while( true )
|
|
{
|
|
if( IsDefined( self.owner ) )
|
|
{
|
|
if( self.owner AdsButtonPressed() )
|
|
{
|
|
if( !already_set )
|
|
{
|
|
self.owner SetClientOmnvar( "ui_heli_pilot", 2 );
|
|
already_set = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( already_set )
|
|
{
|
|
self.owner SetClientOmnvar( "ui_heli_pilot", 1 );
|
|
already_set = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// node funcs
|
|
//
|
|
|
|
heliPilot_setAirStartNodes()
|
|
{
|
|
level.air_start_nodes = getstructarray( "chopper_boss_path_start", "targetname" );
|
|
}
|
|
|
|
heliPilot_getLinkedStruct( struct )
|
|
{
|
|
if( IsDefined( struct.script_linkTo ) )
|
|
{
|
|
linknames = struct get_links();
|
|
for( i = 0; i < linknames.size; i++ )
|
|
{
|
|
ent = getstruct( linknames[ i ], "script_linkname" );
|
|
if( IsDefined( ent ) )
|
|
{
|
|
return ent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
heliPilot_getClosestStartNode( pos )
|
|
{
|
|
// gets the start node that is closest to the position passed in
|
|
closestNode = undefined;
|
|
closestDistance = 999999;
|
|
|
|
foreach( loc in level.air_start_nodes )
|
|
{
|
|
nodeDistance = Distance( loc.origin, pos );
|
|
if ( nodeDistance < closestDistance )
|
|
{
|
|
closestNode = loc;
|
|
closestDistance = nodeDistance;
|
|
}
|
|
}
|
|
|
|
return closestNode;
|
|
}
|
|
|
|
|
|
watchEarlyExit( heli ) // self == player
|
|
{
|
|
level endon( "game_ended" );
|
|
heli endon( "death" );
|
|
self endon ("leaving");
|
|
|
|
heli thread maps\mp\killstreaks\_killstreaks::allowRideKillstreakPlayerExit();
|
|
|
|
heli waittill("killstreakExit");
|
|
|
|
heli thread heliPilot_leave();
|
|
} |