1557 lines
60 KiB
Plaintext
1557 lines
60 KiB
Plaintext
#using scripts\shared\callbacks_shared;
|
|
#using scripts\shared\challenges_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\system_shared;
|
|
#using scripts\shared\array_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\scoreevents_shared;
|
|
#using scripts\shared\killstreaks_shared;
|
|
#using scripts\shared\vehicle_ai_shared;
|
|
#using scripts\shared\vehicle_shared;
|
|
#using scripts\shared\vehicles\_raps;
|
|
#using scripts\shared\weapons\_smokegrenade;
|
|
|
|
#using scripts\mp\gametypes\_battlechatter;
|
|
#using scripts\mp\gametypes\_globallogic_audio;
|
|
#using scripts\mp\gametypes\_spawning;
|
|
#using scripts\mp\gametypes\_spawnlogic;
|
|
#using scripts\mp\teams\_teams;
|
|
#using scripts\mp\killstreaks\_helicopter;
|
|
#using scripts\mp\killstreaks\_killstreak_bundles;
|
|
#using scripts\mp\killstreaks\_killstreak_detect;
|
|
#using scripts\mp\killstreaks\_killstreak_hacking;
|
|
#using scripts\mp\killstreaks\_killstreakrules;
|
|
#using scripts\mp\killstreaks\_killstreaks;
|
|
#using scripts\mp\killstreaks\_airsupport;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#namespace raps_mp;
|
|
|
|
|
|
|
|
|
|
#precache( "string", "KILLSTREAK_DESTROYED_RAPS_DEPLOY_SHIP");
|
|
#precache( "string", "KILLSTREAK_EARNED_RAPS" );
|
|
#precache( "string", "KILLSTREAK_RAPS_NOT_AVAILABLE" );
|
|
#precache( "string", "KILLSTREAK_RAPS_NOT_PLACEABLE" );
|
|
#precache( "string", "KILLSTREAK_RAPS_INBOUND" );
|
|
#precache( "string", "KILLSTREAK_RAPS_HACKED" );
|
|
#precache( "eventstring", "mpl_killstreak_raps" );
|
|
#precache( "fx", "killstreaks/fx_heli_raps_exp_sm" );
|
|
#precache( "fx", "killstreaks/fx_heli_raps_exp_trail" );
|
|
#precache( "fx", "killstreaks/fx_heli_raps_exp_lg" );
|
|
|
|
function init()
|
|
{
|
|
level.raps_settings = level.scriptbundles[ "vehiclecustomsettings" ][ "rapssettings_mp" ];
|
|
assert( isdefined( level.raps_settings ) );
|
|
|
|
level.raps = [];
|
|
level.raps_helicopters = [];
|
|
|
|
level.raps_force_get_enemies = &ForceGetEnemies;
|
|
|
|
killstreaks::register( "raps", "raps", "killstreak_raps", "raps_used", &ActivateRapsKillstreak, true );
|
|
killstreaks::register_strings( "raps", &"KILLSTREAK_EARNED_RAPS", &"KILLSTREAK_RAPS_NOT_AVAILABLE", &"KILLSTREAK_RAPS_INBOUND", undefined, &"KILLSTREAK_RAPS_HACKED" );
|
|
killstreaks::register_dialog( "raps", "mpl_killstreak_raps", "rapsHelicopterDialogBundle", "rapsHelicopterPilotDialogBundle", "friendlyRaps", "enemyRaps", "enemyRapsMultiple", "friendlyRapsHacked", "enemyRapsHacked", "requestRaps", "threatRaps" );
|
|
killstreaks::allow_assists( "raps", true );
|
|
killstreaks::register_dev_debug_dvar( "raps" );
|
|
|
|
killstreak_bundles::register_killstreak_bundle( "raps_drone" );
|
|
|
|
InitHelicopterPositions();
|
|
|
|
callback::on_connect( &OnPlayerConnect );
|
|
|
|
clientfield::register( "vehicle", "monitor_raps_drop_landing", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "raps_heli_low_health", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "raps_heli_extra_low_health", 1, 1, "int" );
|
|
|
|
// level thread RapsHelicopterDynamicAvoidance(); // aku: disabling avoidance for now because it's avoidance technique does not "look right", going for different z heights for now
|
|
|
|
level.raps_helicopter_drop_tag_names = [];
|
|
level.raps_helicopter_drop_tag_names[0] = "tag_raps_drop_left";
|
|
level.raps_helicopter_drop_tag_names[1] = "tag_raps_drop_right";
|
|
}
|
|
|
|
function OnPlayerConnect()
|
|
{
|
|
self.entNum = self getEntityNumber();
|
|
level.raps[ self.entNum ] = spawnstruct();
|
|
level.raps[ self.entNum ].killstreak_id = (-1);
|
|
level.raps[ self.entNum ].raps = [];
|
|
level.raps[ self.entNum ].helicopter = undefined;
|
|
}
|
|
|
|
/* RapsHelicopterDynamicAvoidance
|
|
*
|
|
* This method supports a simple avoidance system for the RAPS Helicopter (RAPS deploy ship).
|
|
*
|
|
* The RAPS helicopters are required to fly at the same hight. To prevent overlapping, this system checks
|
|
* the helicopters relative to each other and changes driving behavior based on different distances.
|
|
* The system will choose another deploy point based on distance, last pick time, and other factors.
|
|
*
|
|
* Note: tuning vars in _killstreaks.gsh using RAPS_HELAV where HELAV is short for Helicopter Avoidance
|
|
*
|
|
* The RAPS helicopter avoidance has been designed to function with at most two RAPS helicopters for now.
|
|
*
|
|
* Key concepts in use:
|
|
* a. Forward Reference Point -- distances are measured relative to this forward reference point ( RAPS_HELAV_FORWARD_OFFSET )
|
|
* b. Other Forward Ref Point -- this is the reference point used when testing distances from another helicopter ( RAPS_HELAV_OTHER_FORWARD_OFFSET )
|
|
* c. Stop Distance -- the helicopter stops when another helicopter is within this distance
|
|
* d. Slow Down Distance -- the helicopter slows down when another helicopter is within this distance
|
|
* e. Pick New Goal Distance -- the helicopter selects a new drop point when the other helicopter is within this distance
|
|
* f. Backing Off -- if a helicopter stops and the other helicopter is in front of it, it will pick a random point opposite
|
|
* the direction behind it and can pick a new goal (drop point) to go to after it backs off
|
|
* g. Drive Mode -- there are four different drive modes: expedient, cautious, more cautious, and stop.
|
|
* Each has different speed, acceleration, and deceleration.
|
|
*
|
|
*/
|
|
function RapsHelicopterDynamicAvoidance()
|
|
{
|
|
level endon( "game_ended" );
|
|
|
|
index_to_update = 0;
|
|
|
|
while( true )
|
|
{
|
|
RapsHelicopterDynamicAvoidanceUpdate( index_to_update );
|
|
|
|
index_to_update++;
|
|
if ( index_to_update >= level.raps_helicopters.size )
|
|
index_to_update = 0;
|
|
|
|
wait( ( 0.05 ) );
|
|
}
|
|
}
|
|
|
|
function RapsHelicopterDynamicAvoidanceUpdate( index_to_update )
|
|
{
|
|
helicopterRefOrigin = ( 0, 0, 0 );
|
|
otherHelicopterRefOrigin = ( 0, 0, 0 );
|
|
|
|
ArrayRemoveValue( level.raps_helicopters, undefined );
|
|
|
|
if ( index_to_update >= level.raps_helicopters.size )
|
|
index_to_update = 0;
|
|
|
|
if( level.raps_helicopters.size >= 2 )
|
|
{
|
|
helicopter = level.raps_helicopters[index_to_update];
|
|
/# helicopter.__action_just_made = false; #/
|
|
|
|
for( i = 0; i < level.raps_helicopters.size; i++ )
|
|
{
|
|
if ( i == index_to_update )
|
|
continue;
|
|
|
|
if ( helicopter.droppingRaps )
|
|
continue;
|
|
|
|
if ( !isdefined( helicopter.lastNewGoalTime ) )
|
|
helicopter.lastNewGoalTime = GetTime();
|
|
|
|
helicopterForward = AnglesToForward( helicopter GetAngles() );
|
|
helicopterRefOrigin = helicopter.origin + ( helicopterForward * ( 500 ) );
|
|
otherHelicopterForward = AnglesToForward( level.raps_helicopters[i] GetAngles() );
|
|
otherHelicopterRefOrigin = level.raps_helicopters[i].origin + ( otherHelicopterForward * ( 100 ) );
|
|
deltaToOther = otherHelicopterRefOrigin - helicopterRefOrigin;
|
|
otherInFront = ( VectorDot( helicopterForward, VectorNormalize( deltaToOther ) ) > ( 0.707 ) );
|
|
distanceSqr = Distance2DSquared( helicopterRefOrigin, otherHelicopterRefOrigin);
|
|
|
|
if ( (distanceSqr < ( ( 200 + ( 1200 ) ) * ( 200 + ( 1200 ) ) ) || helicopter GetSpeed() == 0 )
|
|
&& (GetTime() - helicopter.lastNewGoalTime) > ( 5000 ) )
|
|
{
|
|
//
|
|
// pick a new goal based on distance, speed, and the last time picked
|
|
//
|
|
/# helicopter.__last_dynamic_avoidance_action = 20; /* new goal */ #/
|
|
/# helicopter.__action_just_made = true; #/
|
|
|
|
helicopter UpdateHelicopterSpeed();
|
|
if ( helicopter.isLeaving )
|
|
{
|
|
self.leaveLocation = GetRandomHelicopterLeaveOrigin( /*self.assigned_fly_height*/ 0, self.origin );
|
|
helicopter setVehGoalPos( self.leaveLocation, 0 );
|
|
}
|
|
else
|
|
{
|
|
self.targetDropLocation = GetRandomHelicopterPosition( self.lastDropLocation );
|
|
helicopter setVehGoalPos( self.targetDropLocation, 1 );
|
|
}
|
|
helicopter.lastNewGoalTime = GetTime();
|
|
}
|
|
else if ( distanceSqr < ( ( 1200 ) * ( 1200 ) )
|
|
&& otherInFront
|
|
&& (GetTime() - helicopter.lastStopTime) > ( 500 )
|
|
)
|
|
{
|
|
//
|
|
// do a full stop if the other helicopter is in front and is too close
|
|
//
|
|
/# helicopter.__last_dynamic_avoidance_action = 10; /* stop */ #/
|
|
/# helicopter.__action_just_made = true; #/
|
|
|
|
helicopter StopHelicopter();
|
|
}
|
|
else if ( helicopter GetSpeed() == 0 && otherInFront && distanceSqr < ( ( 1200 ) * ( 1200 ) ) )
|
|
{
|
|
//
|
|
// after a full stop, have the helicopter back off if the other helicopter is in front and too close
|
|
// and a new drop location may be picked based on the tuning vars
|
|
//
|
|
/# helicopter.__last_dynamic_avoidance_action = 50; /* back off */ #/
|
|
/# helicopter.__action_just_made = true; #/
|
|
|
|
delta = otherHelicopterRefOrigin - helicopterRefOrigin;
|
|
newGoalPosition = helicopter.origin -
|
|
( deltaToOther[0] * RandomFloatRange( ( 0.7 ), ( 2.5 ) ),
|
|
deltaToOther[1] * RandomFloatRange( ( 0.7 ), ( 2.5 )), 0 );
|
|
helicopter UpdateHelicopterSpeed();
|
|
helicopter setVehGoalPos( newGoalPosition, 0 );
|
|
|
|
// pick a new drop location for use after the "back off" goal is reached
|
|
if ( ( true ) || (GetTime() - helicopter.lastNewGoalTime) > ( 5000 ) )
|
|
{
|
|
/# helicopter.__last_dynamic_avoidance_action = 51; /* back off + new goal */ #/
|
|
helicopter.targetDropLocation = GetClosestRandomHelicopterPosition( newGoalPosition, 8 );
|
|
helicopter.lastNewGoalTime = GetTime();
|
|
}
|
|
}
|
|
else if ( distanceSqr < ( ( 1000 + ( 200 + ( 1200 ) ) ) * ( 1000 + ( 200 + ( 1200 ) ) ) ) && helicopter.driveModeSpeedScale == 1.0 )
|
|
{
|
|
//
|
|
// slow down the helicopter if within the configured distances and at full speed
|
|
// there is a cautious and a more cautious speed based on if the other helicopter is in front
|
|
//
|
|
/# helicopter.__last_dynamic_avoidance_action = (( otherInFront ) ? 31 : 30); /* cautious */ #/
|
|
/# helicopter.__action_just_made = true; #/
|
|
|
|
helicopter UpdateHelicopterSpeed( ( (otherInFront) ? 2 : 1) );
|
|
}
|
|
else if ( distanceSqr >= ( ( 1000 + ( 200 + ( 1200 ) ) ) * ( 1000 + ( 200 + ( 1200 ) ) ) ) && helicopter.driveModeSpeedScale < 1.0 )
|
|
{
|
|
//
|
|
// speed the helicopter back up if we are beyond the slow down distance and set to drive at full speed
|
|
//
|
|
/# helicopter.__last_dynamic_avoidance_action = 40; /* expedient */ #/
|
|
/# helicopter.__action_just_made = true; #/
|
|
|
|
helicopter UpdateHelicopterSpeed( 0 );
|
|
}
|
|
else if ( helicopter GetSpeed() == 0 && (GetTime() - helicopter.lastStopTime) > ( 500 ) )
|
|
{
|
|
//
|
|
// resume moving -- start mmoving again if we have stopped for too long
|
|
//
|
|
// devblock to report last action made intentionally left out.
|
|
|
|
helicopter UpdateHelicopterSpeed();
|
|
}
|
|
}
|
|
|
|
/#
|
|
//================================================================================================
|
|
//
|
|
// this code section is meant for visual debuggingof the RAPS Helicopter dynamic avoidance system
|
|
//
|
|
//------------------------------------------------------------------------------------------------
|
|
//
|
|
if ( GetDvarInt( "scr_raps_helav_debug" ) )
|
|
{
|
|
if ( isdefined( helicopter ) )
|
|
{
|
|
server_frames_to_persist = INT( (( 0.05 ) * 2) / .05 );
|
|
|
|
Sphere( helicopterRefOrigin, 10, ( 0, 0, 1 ), 1, false, 10, server_frames_to_persist );
|
|
Sphere( otherHelicopterRefOrigin, 10, ( 1, 0, 0 ), 1, false, 10, server_frames_to_persist );
|
|
|
|
circle( helicopterRefOrigin, ( 1000 + ( 200 + ( 1200 ) ) ), ( 1, 1, 0 ), true, true, server_frames_to_persist );
|
|
circle( helicopterRefOrigin, ( 200 + ( 1200 ) ), ( 0, 0, 0 ), true, true, server_frames_to_persist );
|
|
circle( helicopterRefOrigin, ( 1200 ), ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
|
|
Print3d( helicopter.origin, "Speed: " + INT( helicopter GetSpeedMPH() ), (1,1,1), 1, 2.5, server_frames_to_persist );
|
|
|
|
action_debug_color = ( 0.8, 0.8, 0.8 );
|
|
debug_action_string = "";
|
|
if ( helicopter.__action_just_made )
|
|
action_debug_color = ( 0, 1, 0 );
|
|
|
|
switch ( helicopter.__last_dynamic_avoidance_action )
|
|
{
|
|
case 0: break; // do nothing
|
|
case 10: debug_action_string = "stop"; break;
|
|
case 20: debug_action_string = "new goal"; break;
|
|
case 30: debug_action_string = "cautious"; break;
|
|
case 31: debug_action_string = "more cautious"; break;
|
|
case 40: debug_action_string = "expedient"; break;
|
|
case 50: debug_action_string = "back off"; break;
|
|
case 51: debug_action_string = "back off + new goal"; break;
|
|
default: debug_action_string = "unknown action"; break;
|
|
}
|
|
|
|
// display last action taken
|
|
Print3d( helicopter.origin + ( 0, 0, -50 ), debug_action_string, action_debug_color, 1, 2.5, server_frames_to_persist );
|
|
|
|
}
|
|
}
|
|
//
|
|
//------------------------------------------------------------------------------------------------
|
|
//
|
|
// end of visual debug section
|
|
//
|
|
//================================================================================================
|
|
#/
|
|
}
|
|
}
|
|
|
|
function ActivateRapsKillstreak( hardpointType )
|
|
{
|
|
player = self;
|
|
|
|
if ( !player killstreakrules::isKillstreakAllowed( "raps", player.team ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( game["raps_helicopter_positions"].size <= 0 )
|
|
{
|
|
/# IPrintLnBold( "RAPS helicopter position error, check NavMesh." ); #/
|
|
self iPrintLnBold( &"KILLSTREAK_RAPS_NOT_AVAILABLE" );
|
|
return false;
|
|
}
|
|
|
|
killstreakId = player killstreakrules::killstreakStart( "raps", player.team );
|
|
if( killstreakId == (-1) )
|
|
{
|
|
player iPrintLnBold( &"KILLSTREAK_RAPS_NOT_AVAILABLE" );
|
|
return false;
|
|
}
|
|
|
|
player thread teams::WaitUntilTeamChange( player, &OnTeamChanged, player.entNum, "raps_complete" );
|
|
|
|
level thread WatchRapsKillstreakEnd( killstreakId, player.entNum, player.team );
|
|
|
|
helicopter = player SpawnRapsHelicopter( killstreakId );
|
|
helicopter.killstreakId = killstreakId;
|
|
|
|
player killstreaks::play_killstreak_start_dialog( "raps", player.team, killstreakId );
|
|
player AddWeaponStat( GetWeapon( "raps" ), "used", 1 );
|
|
|
|
helicopter killstreaks::play_pilot_dialog_on_owner( "arrive", "raps", killstreakId );
|
|
|
|
level.raps[ player.entNum ].helicopter = helicopter;
|
|
if ( !isdefined( level.raps_helicopters ) ) level.raps_helicopters = []; else if ( !IsArray( level.raps_helicopters ) ) level.raps_helicopters = array( level.raps_helicopters ); level.raps_helicopters[level.raps_helicopters.size]=level.raps[ player.entNum ].helicopter;;
|
|
level thread UpdateKillstreakOnHelicopterDeath( level.raps[ player.entNum ].helicopter, player.entNum );
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_raps_debug_auto_reactivate" ) )
|
|
{
|
|
level thread AutoReactivateRapsKillstreak( player.entNum, player, hardpointType );
|
|
}
|
|
#/
|
|
|
|
return true;
|
|
}
|
|
|
|
/#
|
|
function AutoReactivateRapsKillstreak( ownerEntNum, player, hardpointType )
|
|
{
|
|
while( true )
|
|
{
|
|
level waittill( "raps_updated_" + ownerEntNum );
|
|
|
|
if( isdefined( level.raps[ ownerEntNum ].helicopter ) )
|
|
continue;
|
|
|
|
wait ( RandomFloatRange( 2.0, 5.0 ) );
|
|
player thread ActivateRapsKillstreak( hardpointType );
|
|
|
|
return;
|
|
}
|
|
}
|
|
#/
|
|
|
|
function WatchRapsKillstreakEnd( killstreakId, ownerEntNum, team )
|
|
{
|
|
while( true )
|
|
{
|
|
level waittill( "raps_updated_" + ownerEntNum );
|
|
|
|
if( isdefined( level.raps[ ownerEntNum ].helicopter ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
killstreakrules::killstreakStop( "raps", team, killstreakId );
|
|
return;
|
|
}
|
|
}
|
|
|
|
function UpdateKillstreakOnHelicopterDeath( helicopter, ownerEntEnum )
|
|
{
|
|
helicopter waittill( "death" );
|
|
|
|
level notify( "raps_updated_" + ownerEntEnum );
|
|
}
|
|
|
|
function OnTeamChanged( entNum, event )
|
|
{
|
|
abandoned = true;
|
|
DestroyAllRaps( entNum, abandoned );
|
|
}
|
|
|
|
function OnEMP( attacker, ownerEntNum )
|
|
{
|
|
DestroyAllRaps( ownerEntNum );
|
|
}
|
|
|
|
function NoVehicleFaceThread( mapCenter, radius )
|
|
{
|
|
level endon ("game_ended");
|
|
wait 3; // wait arbitrary time so moving platform can be initialized
|
|
MarkNoVehicleNavMeshFaces( mapCenter, radius, 21 );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//HELICOPTER
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
function InitHelicopterPositions()
|
|
{
|
|
|
|
// - - - - -
|
|
//
|
|
// -- try to find a reasonable center point on the nav mesh as a starting point to start querying for more points
|
|
//
|
|
startSearchPoint = airsupport::GetMapCenter();
|
|
mapCenter = GetClosestPointOnNavMesh( startSearchPoint, ( 1024 ) );
|
|
|
|
if ( !isdefined( mapCenter ) )
|
|
{
|
|
startSearchPoint = ( startSearchPoint[0], startSearchPoint[1], 0 );
|
|
}
|
|
|
|
remaining_attempts = 10;
|
|
while ( !isdefined( mapCenter ) && remaining_attempts > 0 )
|
|
{
|
|
startSearchPoint += ( 100, 100, 0 );
|
|
mapCenter = GetClosestPointOnNavMesh( startSearchPoint, ( 1024 ) );
|
|
remaining_attempts -= 1;
|
|
}
|
|
|
|
if( !isdefined( mapCenter ) )
|
|
{
|
|
mapCenter = airsupport::GetMapCenter();
|
|
}
|
|
|
|
// - - - - -
|
|
//
|
|
// -- now query the nav mesh for some random, reasonably-spaced-out points
|
|
//
|
|
radius = airsupport::GetMaxMapWidth();
|
|
if ( radius < 1 )
|
|
radius = 1;
|
|
|
|
// don't re-generate the points if they are already there
|
|
if ( IsDefined( game["raps_helicopter_positions"] ) )
|
|
return;
|
|
|
|
lots_of_height = 1024;
|
|
randomNavMeshPoints = util::PositionQuery_PointArray( mapCenter, ( 0 ), radius * 3, lots_of_height, ( 132 ) );
|
|
// Hack fix for when the mapCenter cannot be found (mp_veiled / mp_sentosa)
|
|
if ( randomNavMeshPoints.size == 0 )
|
|
{
|
|
mapCenter = ( 0, 0, 39 );
|
|
randomNavMeshPoints = util::PositionQuery_PointArray( mapCenter, ( 0 ), radius, 70, ( 132 ) );
|
|
}
|
|
|
|
/# position_query_drop_location_count = randomNavMeshPoints.size; #/
|
|
|
|
// add level specific raps drop locations
|
|
if ( isdefined( level.add_raps_drop_locations ) )
|
|
{
|
|
[[ level.add_raps_drop_locations ]]( randomNavMeshPoints );
|
|
}
|
|
|
|
/#
|
|
// debug draw level specific points
|
|
if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) )
|
|
{
|
|
boxHalfWidth = ( 220 ) * 0.25; // draw a smaller box
|
|
|
|
for( i = position_query_drop_location_count; i < randomNavMeshPoints.size; i++ )
|
|
{
|
|
// shows a short orange box
|
|
Box( randomNavMeshPoints[ i ], (-boxHalfWidth, -boxHalfWidth, 0), (boxHalfWidth, boxHalfWidth, 8.88 ), 0, ( 1.0, 0.53, 0.0 ), 0.9, false, 9999999 );
|
|
}
|
|
}
|
|
#/
|
|
|
|
// get any level specific omit points
|
|
omit_locations = [];
|
|
if ( isdefined( level.add_raps_omit_locations ) )
|
|
{
|
|
[[ level.add_raps_omit_locations ]]( omit_locations ); // don't add too many of these.
|
|
}
|
|
|
|
/#
|
|
// debug draw level specific omit points
|
|
if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) )
|
|
{
|
|
debug_radius = ( 220 ) * 0.5; // draw a smaller box
|
|
|
|
foreach( omit_location in omit_locations )
|
|
{
|
|
// shows a few dark grey circles
|
|
Circle( omit_location, debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 );
|
|
Circle( omit_location + ( 0, 0, 4 ), debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 );
|
|
Circle( omit_location + ( 0, 0, 8 ), debug_radius, ( 0.05, 0.05, 0.05 ), false, true, 9999999 );
|
|
}
|
|
}
|
|
#/
|
|
|
|
// - - - - -
|
|
//
|
|
// -- collect the random points that can be used to drop raps (test points using box traces, etc.)
|
|
//
|
|
game["raps_helicopter_positions"] = [];
|
|
minFlyHeight = INT( airsupport::getMinimumFlyHeight() + ( 1000 ) );
|
|
test_point_radius = 12;
|
|
fit_radius = ( 220 ) * 0.5;
|
|
fit_radius_corner = fit_radius * 0.7071;
|
|
omit_radius = ( 220 ) * 0.5;
|
|
|
|
foreach( point in randomNavMeshPoints )
|
|
{
|
|
// skip points in water
|
|
start_water_trace = point + ( 0, 0, 6 );
|
|
stop_water_trace = point + ( 0, 0, 8 );
|
|
trace = physicstrace( start_water_trace, stop_water_trace, ( -2, -2, -2 ), ( 2, 2, 2 ) , undefined, (1 << 2) );
|
|
if( trace["fraction"] < 1.0 )
|
|
{
|
|
/#
|
|
if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) )
|
|
{
|
|
DebugBoxWidth = ( 220 ) * 0.5;
|
|
DebugBoxHeight = 10;
|
|
|
|
// draw a blue box where water was found
|
|
Box( start_water_trace, ( -DebugBoxWidth, -DebugBoxWidth, 0 ), ( DebugBoxWidth, DebugBoxWidth, DebugBoxHeight ), 0, ( 0.0, 0, 1.0 ), 0.9, false, 9999999 );
|
|
Box( start_water_trace, ( -2, -2, -2 ), ( 2, 2, 2 ), 0, ( 0.0, 0, 1.0 ), 0.9, false, 9999999 );
|
|
}
|
|
#/
|
|
continue;
|
|
}
|
|
|
|
// skip avoid points
|
|
should_omit = false;
|
|
|
|
foreach( omit_location in omit_locations )
|
|
{
|
|
if ( DistanceSquared( omit_location, point ) < ( omit_radius * omit_radius ) )
|
|
{
|
|
should_omit = true;
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) )
|
|
{
|
|
DebugBoxWidth = ( 220 ) * 0.5;
|
|
DebugBoxHeight = 10;
|
|
|
|
// draw a dark grey box for omitted boxes
|
|
Box( point, ( -DebugBoxWidth, -DebugBoxWidth, 0 ), ( DebugBoxWidth, DebugBoxWidth, DebugBoxHeight ), 0, ( 0.05, 0.05, 0.05 ), 1.0, false, 9999999 );
|
|
}
|
|
#/
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (should_omit)
|
|
continue;
|
|
|
|
// for each random nav mesh point, test a few points near it to see if it works as a drop point
|
|
randomTestPoints = util::PositionQuery_PointArray( point, 0, ( 128 ), lots_of_height, test_point_radius );
|
|
max_attempts = 12;
|
|
point_added = false;
|
|
for ( i = 0; !point_added && i < max_attempts && i < randomTestPoints.size; i++ )
|
|
{
|
|
test_point = randomTestPoints[ i ];
|
|
|
|
//can_fit_on_nav_mesh = IsPointOnNavMesh( test_point, RAPS_HELICOPTER_NAV_SPACIOUS_POINT_BOUNDARY ); // this line should "work", but it doesn't, so we test some points individually
|
|
can_fit_on_nav_mesh = ( IsPointOnNavMesh( test_point + ( 0, fit_radius, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( 0, -fit_radius, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( fit_radius, 0, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( -fit_radius, 0, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( fit_radius_corner, fit_radius_corner, 0 ), 0 ) // also include corners as there are cases where the above four are not sufficient for raps drones
|
|
&& IsPointOnNavMesh( test_point + ( fit_radius_corner, -fit_radius_corner, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( -fit_radius_corner, fit_radius_corner, 0 ), 0 )
|
|
&& IsPointOnNavMesh( test_point + ( -fit_radius_corner, -fit_radius_corner, 0 ), 0 )
|
|
);
|
|
|
|
if ( can_fit_on_nav_mesh )
|
|
{
|
|
point_added = TryAddPointForHelicopterPosition( test_point, minFlyHeight );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( game["raps_helicopter_positions"].size == 0 )
|
|
{
|
|
/# IPrintLnBold( "Error Finding Valid RAPS Helicopter Positions, Using Default Random NavMesh Points" ); #/
|
|
game["raps_helicopter_positions"] = randomNavMeshPoints;
|
|
}
|
|
|
|
// find helicopter position closest to mapCenter to use as flood fill start point
|
|
flood_fill_start_point = undefined;
|
|
flood_fill_start_point_distance_squared = 9999999;
|
|
foreach( point in game["raps_helicopter_positions"] )
|
|
{
|
|
if ( !isdefined( point ) )
|
|
continue;
|
|
|
|
distance_squared = DistanceSquared( point, mapCenter );
|
|
if ( distance_squared < flood_fill_start_point_distance_squared )
|
|
{
|
|
flood_fill_start_point_distance_squared = distance_squared;
|
|
flood_fill_start_point = point;
|
|
}
|
|
}
|
|
|
|
if ( !isdefined( flood_fill_start_point ) )
|
|
flood_fill_start_point = mapCenter;
|
|
|
|
level thread NoVehicleFaceThread( flood_fill_start_point, radius * 2 );
|
|
|
|
|
|
force_debug_draw = false; // NOTE: never check this in as true !!!!!
|
|
|
|
/#
|
|
if ( killstreaks::should_draw_debug( "raps" ) || force_debug_draw ) // something is wrong with "scr_raps_debug", no time to troubleshoot this now, just force draw when needed
|
|
{
|
|
time = 9999999;
|
|
Sphere( mapCenter, 20, ( 1, 1, 0 ), 1, 0, 10, time );
|
|
Circle( mapCenter, airsupport::GetMaxMapWidth(), ( 0, 1, 0 ), true, true, time );
|
|
Box( mapCenter, (-4, -4, 0 ), ( 4, 4, 5000 ), 0, ( 1, 1, 0 ), 0.6, false, time );
|
|
|
|
// flood fill ceenter
|
|
Sphere( flood_fill_start_point, 20, ( 0, 1, 1 ), 1, 0, 10, time );
|
|
Box( flood_fill_start_point, (-4, -4, 0 ), ( 4, 4, 4200 ), 0, ( 0, 1, 1 ), 0.6, false, time );
|
|
|
|
foreach( point in randomNavMeshPoints )
|
|
{
|
|
Sphere( ( point + ( 0, 0, 950 ) ), 10, ( 0, 0, 1 ), 1, 0, 10, time );
|
|
Circle( point, ( 128 ), ( 1, 0, 0 ), true, true, time );
|
|
}
|
|
|
|
foreach( point in game["raps_helicopter_positions"] )
|
|
{
|
|
Sphere( ( point + ( 0, 0, 1000 ) ), 10, ( 0, 1, 0 ), 1, 0, 10, time );
|
|
Circle( point + ( 0, 0, 2 ), ( 128 ), ( 0, 1, 0 ), true, true, time );
|
|
airsupport::debug_cylinder( point, 8, 1000, ( 0, 0.8, 0 ), 16, time );
|
|
Box( point, (-4, -4, 0 ), ( 4, 4, 1000 ), 0, ( 0, 0.7, 0 ), 0.6, false, time );
|
|
|
|
halfBoxWidth = ( 220 ) * 0.5;
|
|
Box( point, (-halfBoxWidth, -halfBoxWidth, 2), (halfBoxWidth, halfBoxWidth, 300), 0, ( 0, 0, 0.6 ), 0.6, false, time );
|
|
}
|
|
}
|
|
#/
|
|
}
|
|
|
|
function TryAddPointForHelicopterPosition( spaciousPoint, minFlyHeight )
|
|
{
|
|
traceHeight = minFlyHeight + ( 500 );
|
|
traceBoxHalfWidth = ( 220 ) * 0.5;
|
|
|
|
if ( IsTraceSafeForRapsDroneDropFromHelicopter( spaciousPoint, traceHeight, traceBoxHalfWidth ) )
|
|
{
|
|
if ( !isdefined( game["raps_helicopter_positions"] ) ) game["raps_helicopter_positions"] = []; else if ( !IsArray( game["raps_helicopter_positions"] ) ) game["raps_helicopter_positions"] = array( game["raps_helicopter_positions"] ); game["raps_helicopter_positions"][game["raps_helicopter_positions"].size]=spaciousPoint;;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function IsTraceSafeForRapsDroneDropFromHelicopter( spaciousPoint, traceHeight, traceBoxHalfWidth )
|
|
{
|
|
start = ( spaciousPoint[0], spaciouspoint[1], traceHeight );
|
|
end = ( spaciousPoint[0], spaciouspoint[1], spaciouspoint[2] + ( 36 ) );
|
|
|
|
trace = PhysicsTrace( start, end, ( -traceBoxHalfWidth, -traceBoxHalfWidth, 0 ), ( traceBoxHalfWidth, traceBoxHalfWidth, traceBoxHalfWidth * 2.0 ), undefined, (1 << 0) );
|
|
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_raps_nav_point_trace_debug" ) )
|
|
{
|
|
if (trace["fraction"] < 1.0 )
|
|
{
|
|
// shows the first trace hit, but from the end
|
|
Box( end, (-traceBoxHalfWidth, -traceBoxHalfWidth, 0), (traceBoxHalfWidth, traceBoxHalfWidth, (start[2] - end[2]) * (1.0 - trace["fraction"])), 0, ( 1.0, 0, 0.0 ), 0.6, false, 9999999 );
|
|
}
|
|
else
|
|
{
|
|
// shows a small green box
|
|
Box( end, (-traceBoxHalfWidth, -traceBoxHalfWidth, 0), (traceBoxHalfWidth, traceBoxHalfWidth, 8.88), 0, ( 0.0, 1.0, 0.0 ), 0.6, false, 9999999 );
|
|
}
|
|
}
|
|
#/
|
|
|
|
return ( trace["fraction"] == 1.0 && trace["surfacetype"] == "none" );
|
|
}
|
|
|
|
function GetRandomHelicopterStartOrigin( fly_height, firstDropLocation )
|
|
{
|
|
best_node = helicopter::getValidRandomStartNode( firstDropLocation );
|
|
return best_node.origin + ( 0, 0, fly_height );
|
|
}
|
|
|
|
function GetRandomHelicopterLeaveOrigin( fly_height, startLocationToLeaveFrom )
|
|
{
|
|
best_node = helicopter::getValidRandomLeaveNode( startLocationToLeaveFrom );
|
|
return best_node.origin + ( 0, 0, fly_height );
|
|
}
|
|
|
|
function GetInitialHelicopterFlyHeight()
|
|
{
|
|
// Note A: call this only once for each helicopter when spawned
|
|
// Note B: this technique only works for two RAPS helicopters in play at any give time
|
|
// Note C: this works regardless of team based or not
|
|
|
|
ArrayRemoveValue( level.raps_helicopters, undefined ); // clean up array first
|
|
|
|
minimum_fly_height = airsupport::getMinimumFlyHeight();
|
|
|
|
if ( level.raps_helicopters.size > 0 )
|
|
{
|
|
already_assigned_height = level.raps_helicopters[0].assigned_fly_height;
|
|
|
|
if ( already_assigned_height == ( minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ) ) )
|
|
return minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ) + ( 400 );
|
|
}
|
|
|
|
return minimum_fly_height + INT( airsupport::getMinimumFlyHeight() + ( 1000 ) );
|
|
}
|
|
|
|
function ConfigureChopperTeamPost( owner, isHacked )
|
|
{
|
|
helicopter = self;
|
|
helicopter thread WatchOwnerDisconnect( owner );
|
|
helicopter thread CreateRapsHelicopterInfluencer();
|
|
}
|
|
|
|
function SpawnRapsHelicopter( killstreakId )
|
|
{
|
|
player = self;
|
|
|
|
assigned_fly_height = GetInitialHelicopterFlyHeight();
|
|
prePickedDropLocation = PickNextDropLocation( undefined, 0, player.origin, assigned_fly_height );
|
|
spawnOrigin = GetRandomHelicopterStartOrigin( /*fly_height*/ 0, prePickedDropLocation ); // update this
|
|
|
|
helicopter = SpawnHelicopter( player, spawnOrigin, ( 0, 0, 0 ), "heli_raps_mp", "veh_t7_mil_vtol_dropship_raps" );
|
|
helicopter.prePickedDropLocation = prePickedDropLocation;
|
|
helicopter.assigned_fly_height = assigned_fly_height;
|
|
|
|
helicopter killstreaks::configure_team( "raps", killstreakId, player, undefined, undefined, &ConfigureChopperTeamPost );
|
|
helicopter killstreak_hacking::enable_hacking( "raps" );
|
|
|
|
helicopter.droppingRaps = false;
|
|
helicopter.isLeaving = false;
|
|
helicopter.droppedRaps = false;
|
|
helicopter.driveModeSpeedScale = 3.0;
|
|
helicopter.driveModeAccel = ( 20 ) * 5;
|
|
helicopter.driveModeDecel = ( 20 ) * 5;
|
|
helicopter.lastStopTime = 0;
|
|
helicopter.targetDropLocation = ( -9999999, -9999999, -9999999 );
|
|
helicopter.lastDropLocation = ( -9999999, -9999999, -9999999 );
|
|
helicopter.firstDropReferencePoint = ( player.origin[0], player.origin[1], INT( airsupport::getMinimumFlyHeight() + ( 1000 ) ));
|
|
/# helicopter.__last_dynamic_avoidance_action = 0; #/
|
|
|
|
helicopter clientfield::set( "enemyvehicle", 1 );
|
|
|
|
helicopter.health = 99999999;
|
|
helicopter.maxhealth = killstreak_bundles::get_max_health( "raps" );
|
|
helicopter.lowhealth = killstreak_bundles::get_low_health( "raps" );
|
|
helicopter.extra_low_health = helicopter.lowhealth * 0.5; // hand craft for ship (no need to be tunable now per design, it's locked in)
|
|
helicopter.extra_low_health_callback = &OnExtraLowHealth;
|
|
|
|
helicopter SetCanDamage( true );
|
|
helicopter thread killstreaks::MonitorDamage( "raps", helicopter.maxhealth, &OnDeath, helicopter.lowhealth, &OnLowHealth, 0, undefined, true );
|
|
|
|
helicopter.rocketDamage = helicopter.maxhealth / ( 4 ) + 1;
|
|
helicopter.remoteMissileDamage = helicopter.maxhealth / ( 1 ) + 1;
|
|
helicopter.hackerToolDamage = helicopter.maxhealth / ( 2 ) + 1;
|
|
helicopter.DetonateViaEMP = &raps::detonate_damage_monitored;
|
|
|
|
Target_Set( helicopter, ( 0, 0, 100 ) );
|
|
helicopter SetDrawInfrared( true );
|
|
helicopter thread WaitForHelicopterShutdown();
|
|
helicopter thread HelicopterThink();
|
|
helicopter thread WatchGameEnded();
|
|
/# helicopter thread HelicopterThinkDebugVisitAll(); #/
|
|
|
|
return helicopter;
|
|
}
|
|
|
|
function WaitForHelicopterShutdown()
|
|
{
|
|
helicopter = self;
|
|
helicopter waittill( "raps_helicopter_shutdown", killed );
|
|
|
|
level notify( "raps_updated_" + helicopter.ownerEntNum );
|
|
|
|
if ( Target_IsTarget( helicopter ) )
|
|
{
|
|
Target_Remove( helicopter );
|
|
}
|
|
|
|
if( killed )
|
|
{
|
|
wait( RandomFloatRange( 0.1, 0.2 ) );
|
|
|
|
helicopter FirstHeliExplo();
|
|
helicopter HeliDeathTrails();
|
|
|
|
helicopter thread Spin();
|
|
GoalX = RandomFloatRange( 650, 700 );
|
|
GoalY = RandomFloatRange( 650, 700 );
|
|
|
|
if ( RandomIntRange ( 0, 2 ) > 0 )
|
|
GoalX = -GoalX;
|
|
|
|
if ( RandomIntRange ( 0, 2 ) > 0 )
|
|
GoalY = -GoalY;
|
|
|
|
helicopter setVehGoalPos( helicopter.origin + ( GoalX, GoalY, -RandomFloatRange( 285, 300 ) ), false );
|
|
wait( RandomFloatRange( 3.0, 4.0 ) );
|
|
|
|
helicopter FinalHeliDeathExplode();
|
|
|
|
// fx will not work if we delete too soon
|
|
wait 0.1; // ghost only after fx has covered up the drop ship
|
|
helicopter ghost();
|
|
self notify( "stop_death_spin" );
|
|
wait 0.5;
|
|
}
|
|
else
|
|
{
|
|
helicopter HelicopterLeave();
|
|
}
|
|
|
|
helicopter delete();
|
|
}
|
|
|
|
function WatchOwnerDisconnect( owner )
|
|
{
|
|
self notify( "WatchOwnerDisconnect_singleton" );
|
|
self endon ( "WatchOwnerDisconnect_singleton" );
|
|
|
|
helicopter = self;
|
|
helicopter endon( "raps_helicopter_shutdown" );
|
|
owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" );
|
|
helicopter notify( "raps_helicopter_shutdown", false );
|
|
}
|
|
|
|
function WatchGameEnded( )
|
|
{
|
|
helicopter = self;
|
|
helicopter endon( "raps_helicopter_shutdown" );
|
|
helicopter endon( "death" );
|
|
level waittill("game_ended");
|
|
helicopter notify( "raps_helicopter_shutdown", false );
|
|
}
|
|
|
|
function OnDeath( attacker, weapon )
|
|
{
|
|
helicopter = self;
|
|
|
|
if ( isdefined( attacker ) && ( !isdefined( helicopter.owner ) || helicopter.owner util::IsEnemyPlayer( attacker ) ) )
|
|
{
|
|
challenges::destroyedAircraft( attacker, weapon, false );
|
|
attacker challenges::addFlySwatterStat( weapon, self );
|
|
scoreevents::processscoreevent( "destroyed_raps_deployship", attacker, helicopter.owner, weapon );
|
|
if ( isdefined( helicopter.droppedRaps ) && helicopter.droppedRaps == false )
|
|
{
|
|
attacker addplayerstat( "destroy_raps_before_drop", 1 );
|
|
}
|
|
LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_RAPS_DEPLOY_SHIP", attacker.entnum );
|
|
helicopter notify( "raps_helicopter_shutdown", true );
|
|
}
|
|
|
|
if ( helicopter.isleaving !== true )
|
|
{
|
|
helicopter killstreaks::play_pilot_dialog_on_owner( "destroyed", "raps" );
|
|
helicopter killstreaks::play_destroyed_dialog_on_owner( "raps", self.killstreakId );
|
|
}
|
|
}
|
|
|
|
function OnLowHealth( attacker, weapon )
|
|
{
|
|
helicopter = self;
|
|
|
|
helicopter killstreaks::play_pilot_dialog_on_owner( "damaged", "raps", helicopter.killstreakId );
|
|
|
|
helicopter HeliLowHealthFx();
|
|
}
|
|
|
|
function OnExtraLowHealth( attacker, weapon )
|
|
{
|
|
helicopter = self;
|
|
helicopter HeliExtraLowHealthFx();
|
|
}
|
|
|
|
function GetRandomHelicopterPosition( avoidPoint = ( -9999999, -9999999, -9999999 ), otherAvoidPoint = ( -9999999, -9999999, -9999999 ), avoidRadiusSqr = ( ( 1800 ) * ( 1800 ) ) )
|
|
{
|
|
flyHeight = INT( airsupport::getMinimumFlyHeight() + ( 1000 ) );
|
|
found = false;
|
|
tries = 0;
|
|
|
|
// try picking a location outside the avoid circle, if not possible, reduce the circle size and try again
|
|
for( i = 0; i <= ( 3 ); i++ ) // intentionally using "<=" to get N+1 attemmpts
|
|
{
|
|
// for the very last attempt, make radius negative to make any point valid as a fail safe
|
|
if ( i == ( 3 ) )
|
|
avoidRadiusSqr = -1.0;
|
|
|
|
/# if ( GetDvarInt( "scr_raps_hedeps_debug" ) > 0 )
|
|
{
|
|
server_frames_to_persist = INT( 3.0 / .05 );
|
|
circle( avoidPoint, ( 1800 ), ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
circle( avoidPoint, ( 1800 ) - 1, ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
circle( avoidPoint, ( 1800 ) - 2, ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
|
|
circle( otherAvoidPoint, ( 1800 ), ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
circle( otherAvoidPoint, ( 1800 ) - 1, ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
circle( otherAvoidPoint, ( 1800 ) - 2, ( 1, 0, 0 ), true, true, server_frames_to_persist );
|
|
}
|
|
#/
|
|
|
|
while( !found && tries < game["raps_helicopter_positions"].size )
|
|
{
|
|
index = RandomIntRange( 0, game["raps_helicopter_positions"].size );
|
|
randomPoint = ( game["raps_helicopter_positions"][ index ][0], game["raps_helicopter_positions"][ index ][1], flyHeight );
|
|
found = ( ( Distance2DSquared( randomPoint, avoidPoint ) > avoidRadiusSqr ) && ( Distance2DSquared( randomPoint, otherAvoidPoint ) > avoidRadiusSqr ) );
|
|
tries++;
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
avoidRadiusSqr *= 0.25;
|
|
tries = 0;
|
|
}
|
|
}
|
|
|
|
// note: the -1 avoid radius should force the selection of a point
|
|
Assert( found, "Failed to find a RAPS deploy point!" );
|
|
|
|
return randomPoint;
|
|
}
|
|
|
|
function GetClosestRandomHelicopterPosition( refPoint, pickCount, avoidPoint = ( -9999999, -9999999, -9999999 ), otherAvoidPoint = ( -9999999, -9999999, -9999999 ) )
|
|
{
|
|
bestPosition = GetRandomHelicopterPosition( avoidPoint, otherAvoidPoint );
|
|
bestDistanceSqr = Distance2DSquared( bestPosition, refPoint );
|
|
|
|
for ( i = 1; i < pickCount; i++ )
|
|
{
|
|
candidatePosition = GetRandomHelicopterPosition( avoidPoint, otherAvoidPoint );
|
|
candidateDistanceSqr = Distance2DSquared( candidatePosition, refPoint );
|
|
|
|
if ( candidateDistanceSqr < bestDistanceSqr )
|
|
{
|
|
bestPosition = candidatePosition;
|
|
bestDistanceSqr = candidateDistanceSqr;
|
|
}
|
|
}
|
|
|
|
return bestPosition;
|
|
}
|
|
|
|
function WaitForStoppingMoveToExpire()
|
|
{
|
|
elapsedTimeStopping = GetTime() - self.lastStopTime;
|
|
if ( elapsedTimeStopping < ( 2000 ) )
|
|
{
|
|
wait ( (( 2000 ) - elapsedTimeStopping) * 0.001 );
|
|
}
|
|
}
|
|
|
|
function GetOtherHelicopterPointToAvoid() //self == raps helicopter
|
|
{
|
|
avoid_point = undefined;
|
|
|
|
ArrayRemoveValue( level.raps_helicopters, undefined ); // clean up array first
|
|
|
|
foreach( heli in level.raps_helicopters )
|
|
{
|
|
if ( heli != self )
|
|
{
|
|
avoid_point = heli.targetDropLocation;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return avoid_point;
|
|
}
|
|
|
|
function PickNextDropLocation( heli, drop_index, firstDropReferencePoint, assigned_fly_height, lastDropLocation )
|
|
{
|
|
avoid_point = self GetOtherHelicopterPointToAvoid();
|
|
|
|
// if we have a pre-picked drop location, use that first and reset it
|
|
if ( isdefined( heli ) && isdefined( heli.prePickedDropLocation ) )
|
|
{
|
|
targetDropLocation = heli.prePickedDropLocation;
|
|
heli.prePickedDropLocation = undefined;
|
|
return targetDropLocation;
|
|
}
|
|
|
|
targetDropLocation = ( ( drop_index == 0 ) ? GetClosestRandomHelicopterPosition(
|
|
firstDropReferencePoint,
|
|
INT(game["raps_helicopter_positions"].size * (( 66.6 ) / 100.0) + 1),
|
|
avoid_point )
|
|
: GetRandomHelicopterPosition( lastDropLocation, avoid_point ) );
|
|
|
|
targetDropLocation = ( targetDropLocation[0], targetDropLocation[1], assigned_fly_height );
|
|
|
|
return targetDropLocation;
|
|
}
|
|
|
|
function HelicopterThink()
|
|
{
|
|
/#
|
|
if ( GetDvarInt( "scr_raps_debug_visit_all" ) )
|
|
return;
|
|
#/
|
|
|
|
self endon( "raps_helicopter_shutdown" );
|
|
|
|
for( i = 0; i < ( 3 ); i++ )
|
|
{
|
|
self.targetDropLocation = PickNextDropLocation( self, i, self.firstDropReferencePoint, self.assigned_fly_height, self.lastDropLocation );
|
|
|
|
while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) )
|
|
{
|
|
self WaitForStoppingMoveToExpire();
|
|
self UpdateHelicopterSpeed();
|
|
self setVehGoalPos( self.targetDropLocation, 1 );
|
|
self waittill( "goal" );
|
|
}
|
|
|
|
if ( isdefined( self.owner ) )
|
|
{
|
|
if ( ( i + 1 ) < ( 3 ) )
|
|
{
|
|
self killstreaks::play_pilot_dialog_on_owner( "waveStart", "raps", self.killstreakId );
|
|
}
|
|
else
|
|
{
|
|
self killstreaks::play_pilot_dialog_on_owner( "waveStartFinal", "raps", self.killstreakId );
|
|
}
|
|
}
|
|
|
|
enemy = self.owner battlechatter::get_closest_player_enemy( self.origin, true );
|
|
enemyRadius = battlechatter::mpdialog_value( "rapsDropRadius", 0 );
|
|
|
|
if ( isdefined( enemy ) && Distance2DSquared( self.origin, enemy.origin ) < enemyRadius * enemyRadius )
|
|
{
|
|
enemy battlechatter::play_killstreak_threat( "raps" );
|
|
}
|
|
|
|
self DropRaps();
|
|
|
|
wait( ( i + 1 >= ( 3 ) )
|
|
? ( 2.0 ) + RandomFloatRange( -( 1.0 ), ( 1.0 ) )
|
|
: ( 2.0 ) + RandomFloatRange( -( 2.0 ) , ( 2.0 ) ) );
|
|
}
|
|
|
|
self notify( "raps_helicopter_shutdown", false );
|
|
}
|
|
|
|
/#
|
|
function HelicopterThinkDebugVisitAll()
|
|
{
|
|
self endon( "death" );
|
|
|
|
if ( GetDvarInt( "scr_raps_debug_visit_all" ) == 0 )
|
|
return;
|
|
|
|
for( i = 0; i < 100; i++ )
|
|
{
|
|
for( j = 0; j < game["raps_helicopter_positions"].size; j++ )
|
|
{
|
|
self.targetDropLocation = ( game["raps_helicopter_positions"][ j ][0], game["raps_helicopter_positions"][ j ][1], self.assigned_fly_height );
|
|
|
|
while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) )
|
|
{
|
|
self WaitForStoppingMoveToExpire();
|
|
self UpdateHelicopterSpeed();
|
|
self setVehGoalPos( self.targetDropLocation, 1 );
|
|
self waittill( "goal" );
|
|
}
|
|
|
|
self DropRaps();
|
|
|
|
wait( 1.0 );
|
|
|
|
if ( GetDvarInt( "scr_raps_debug_visit_all_fake_leave" ) > 0 )
|
|
{
|
|
if ( (j+1) % 3 == 0 )
|
|
{
|
|
|
|
// fake a leave and then return
|
|
self.targetDropLocation = GetRandomHelicopterStartOrigin( self.assigned_fly_height, self.origin ); //TODO: make this debug function work at some point, not now, too close to ship
|
|
while ( Distance2DSquared( self.origin, self.targetDropLocation ) > ( 5 * 5 ) )
|
|
{
|
|
self WaitForStoppingMoveToExpire();
|
|
self UpdateHelicopterSpeed();
|
|
self setVehGoalPos( self.targetDropLocation, 1 );
|
|
self waittill( "goal" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self notify( "raps_helicopter_shutdown", false );
|
|
}
|
|
#/
|
|
|
|
function DropRaps()
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
|
|
self.droppingRaps = true;
|
|
self.lastDropLocation = self.origin;
|
|
|
|
// reposition raps to a more precise drap location
|
|
preciseDropLocation = 0.5 * ( self GetTagOrigin( level.raps_helicopter_drop_tag_names[0] ) + self GetTagOrigin( level.raps_helicopter_drop_tag_names[1] ) );
|
|
preciseGoalLocation = self.targetDropLocation + (self.targetDropLocation - preciseDropLocation);
|
|
preciseGoalLocation = ( preciseGoalLocation[0], preciseGoalLocation[1], self.targetDropLocation[2] );
|
|
self setVehGoalPos( preciseGoalLocation, 1 );
|
|
self waittill( "goal" );
|
|
self.droppedRaps = true;
|
|
for( i = 0; i < level.raps_settings.spawn_count; i++ )
|
|
{
|
|
spawn_tag = level.raps_helicopter_drop_tag_names[ i % level.raps_helicopter_drop_tag_names.size ];
|
|
|
|
origin = self GetTagOrigin( spawn_tag );
|
|
angles = self GetTagAngles( spawn_tag );
|
|
|
|
if ( !isdefined( origin ) || !isdefined( angles ) )
|
|
{
|
|
origin = self.origin;
|
|
angles = self.angles;
|
|
}
|
|
|
|
self.owner thread SpawnRaps( origin, angles );
|
|
self playsound( "veh_raps_launch" );
|
|
wait( ( 1 ) );
|
|
}
|
|
|
|
self.droppingRaps = false;
|
|
}
|
|
|
|
function Spin()
|
|
{
|
|
self endon( "stop_death_spin" );
|
|
|
|
speed = RandomIntRange( 180, 220 );
|
|
self setyawspeed( speed, speed * 0.25, speed );
|
|
|
|
if ( RandomIntRange ( 0, 2 ) > 0 )
|
|
speed = -speed;
|
|
|
|
while ( isdefined( self ) )
|
|
{
|
|
self settargetyaw( self.angles[1]+(speed*0.4) );
|
|
wait ( 1 );
|
|
}
|
|
}
|
|
|
|
function FirstHeliExplo()
|
|
{
|
|
PlayFxOnTag( "killstreaks/fx_heli_raps_exp_sm", self, "tag_fx_engine_exhaust_back" );
|
|
self PlaySound( level.heli_sound["crash"] );
|
|
}
|
|
|
|
function HeliLowHealthFx()
|
|
{
|
|
self clientfield::set( "raps_heli_low_health", 1 );
|
|
}
|
|
|
|
function HeliExtraLowHealthFx()
|
|
{
|
|
self clientfield::set( "raps_heli_extra_low_health", 1 );
|
|
}
|
|
|
|
function HeliDeathTrails()
|
|
{
|
|
PlayFxOnTag( "killstreaks/fx_heli_raps_exp_trail", self, "tag_fx_engine_exhaust_back" );
|
|
}
|
|
|
|
function FinalHeliDeathExplode()
|
|
{
|
|
PlayFxOnTag( "killstreaks/fx_heli_raps_exp_lg", self, "tag_fx_death" );
|
|
self PlaySound( level.heli_sound["crash"] );
|
|
}
|
|
|
|
function HelicopterLeave()
|
|
{
|
|
self.isLeaving = true;
|
|
|
|
self killstreaks::play_pilot_dialog_on_owner( "timeout", "raps" );
|
|
self killstreaks::play_taacom_dialog_response_on_owner( "timeoutConfirmed", "raps" );
|
|
|
|
self.leaveLocation = GetRandomHelicopterLeaveOrigin( /* self.assigned_fly_height */ 0, self.origin );
|
|
while ( Distance2DSquared( self.origin, self.leaveLocation ) > ( 600 * 600 ) )
|
|
{
|
|
self UpdateHelicopterSpeed();
|
|
self setVehGoalPos( self.leaveLocation, 0 );
|
|
self waittill( "goal" );
|
|
}
|
|
}
|
|
|
|
function UpdateHelicopterSpeed( driveMode )
|
|
{
|
|
if ( isdefined( driveMode ) )
|
|
{
|
|
switch ( driveMode )
|
|
{
|
|
case 0:
|
|
self.driveModeSpeedScale = 1.0;
|
|
self.driveModeAccel = ( 20 );
|
|
self.driveModeDecel = ( 20 );
|
|
break;
|
|
|
|
case 1:
|
|
case 2:
|
|
self.driveModeSpeedScale = ((driveMode == 2) ? ( 0.2 ) : ( 0.5 ));
|
|
self.driveModeAccel = ( 12 );
|
|
self.driveModeDecel = ( 100 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
desiredSpeed = (self GetMaxSpeed() / 17.6) * self.driveModeSpeedScale;
|
|
|
|
// use Decel as Accel when the desired speed is less than the current speed; (it's a side effect of the system)
|
|
if ( desiredspeed < self GetSpeedMPH() )
|
|
{
|
|
self SetSpeed( desiredSpeed, self.driveModeDecel, self.driveModeDecel );
|
|
}
|
|
else
|
|
{
|
|
self SetSpeed( desiredSpeed, self.driveModeAccel, self.driveModeDecel );
|
|
}
|
|
}
|
|
|
|
function StopHelicopter()
|
|
{
|
|
//self SetSpeed( 0, RAPS_HELAV_FULL_STOP_MODE_ACCEL, RAPS_HELAV_FULL_STOP_MODE_DECEL );
|
|
self SetSpeed( 0, ( 500 ), ( 500 ) ); // using DECEL as accel due to way the current system works
|
|
self.lastStopTime = GetTime();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// RAPS
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
function SpawnRaps( origin, angles )
|
|
{
|
|
originalOwner = self;
|
|
originalOwnerEntNum = originalOwner.entNum;
|
|
|
|
raps = SpawnVehicle( "spawner_bo3_raps_mp", origin, angles, "dynamic_spawn_ai" );
|
|
|
|
if ( !isdefined( raps ) )
|
|
return;
|
|
|
|
raps.forceOneMissile = true;
|
|
raps.drop_deploying = true;
|
|
raps.hurt_trigger_immune_end_time = GetTime() + (isdefined(level.raps_hurt_trigger_immune_duration_ms)?level.raps_hurt_trigger_immune_duration_ms:5000);
|
|
|
|
if ( !isdefined( level.raps[ originalOwnerEntNum ].raps ) ) level.raps[ originalOwnerEntNum ].raps = []; else if ( !IsArray( level.raps[ originalOwnerEntNum ].raps ) ) level.raps[ originalOwnerEntNum ].raps = array( level.raps[ originalOwnerEntNum ].raps ); level.raps[ originalOwnerEntNum ].raps[level.raps[ originalOwnerEntNum ].raps.size]=raps;;
|
|
|
|
raps killstreaks::configure_team( "raps", "raps", originalOwner, undefined, undefined, &ConfigureTeamPost );
|
|
raps killstreak_hacking::enable_hacking( "raps" );
|
|
raps clientfield::set( "enemyvehicle", 1 );
|
|
raps.soundmod = "raps";
|
|
raps.ignore_vehicle_underneath_splash_scalar = true;
|
|
raps.detonate_sides_disabled = true;
|
|
raps.treat_owner_damage_as_friendly_fire = true;
|
|
raps.ignore_team_kills = true;
|
|
|
|
raps SetInvisibleToAll();
|
|
raps thread AutoSetVisibleToAll();
|
|
|
|
raps vehicle::toggle_sounds( 0 );
|
|
//raps thread sndAndRumbleWaitUntilLanding( originalOwner ); // now in client script as monitor_drop_landing
|
|
|
|
raps thread WatchRapsKills( originalOwner );
|
|
raps thread WatchRapsDeath( originalOwner );
|
|
raps thread killstreaks::WaitForTimeout( "raps", raps.settings.max_duration * 1000, &OnRapsTimeout, "death" );
|
|
}
|
|
|
|
|
|
function ConfigureTeamPost( owner, isHacked )
|
|
{
|
|
raps = self;
|
|
raps thread CreateRapsInfluencer();
|
|
raps thread InitEnemySelection( owner );
|
|
raps thread WatchRapsTippedOver( owner );
|
|
}
|
|
|
|
|
|
|
|
function AutoSetVisibleToAll()
|
|
{
|
|
self endon( "death" );
|
|
|
|
// intent: hide the visual glitches when first spawning raps mid air
|
|
|
|
{wait(.05);};
|
|
{wait(.05);};
|
|
|
|
self SetVisibleToAll();
|
|
}
|
|
|
|
function OnRapsTimeout()
|
|
{
|
|
self SelfDestruct( self.owner );
|
|
}
|
|
|
|
function SelfDestruct( attacker ) // self == raps
|
|
{
|
|
self.selfDestruct = true;
|
|
self raps::detonate( attacker );
|
|
}
|
|
|
|
function WatchRapsKills( originalOwner )
|
|
{
|
|
originalOwner endon( "raps_complete" );
|
|
self endon( "death" );
|
|
|
|
if( self.settings.max_kill_count == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
while( true )
|
|
{
|
|
self waittill( "killed", victim );
|
|
|
|
if( isdefined( victim ) && IsPlayer( victim ) )
|
|
{
|
|
if( !isdefined( self.killCount ) )
|
|
{
|
|
self.killCount = 0;
|
|
}
|
|
|
|
self.killCount++;
|
|
if( self.killCount >= self.settings.max_kill_count )
|
|
{
|
|
self raps::detonate( self.owner );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function WatchRapsTippedOver( owner )
|
|
{
|
|
owner endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
// if the raps manage to tip over and get stuck, it should detonate
|
|
while( true )
|
|
{
|
|
wait 3.5;
|
|
|
|
if ( Abs( self.angles[2] ) > 75 )
|
|
{
|
|
self raps::detonate( owner );
|
|
}
|
|
}
|
|
}
|
|
|
|
function WatchRapsDeath( originalOwner )
|
|
{
|
|
originalOwnerEntNum = originalOwner.entnum;
|
|
self waittill( "death", attacker, damageFromUnderneath, weapon );
|
|
|
|
attacker = self [[ level.figure_out_attacker ]]( attacker );
|
|
|
|
if( isdefined( attacker ) && isPlayer( attacker ) )
|
|
{
|
|
if( isdefined( self.owner ) && self.owner != attacker && ( self.owner.team != attacker.team ) )
|
|
{
|
|
scoreevents::processScoreEvent( "killed_raps", attacker );
|
|
attacker challenges::destroyScoreStreak( weapon, true );
|
|
attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon );
|
|
|
|
if( isdefined( self.attackers ) )
|
|
{
|
|
foreach( player in self.attackers )
|
|
{
|
|
if( isPlayer( player ) && ( player != attacker ) && ( player != self.owner ) )
|
|
{
|
|
scoreevents::processScoreEvent( "killed_raps_assist", player );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayRemoveValue( level.raps[ originalOwnerEntNum ].raps, self );
|
|
}
|
|
|
|
function InitEnemySelection( owner ) //self == raps
|
|
{
|
|
owner endon( "disconnect" );
|
|
self endon( "death" );
|
|
self endon( "hacked" );
|
|
|
|
self vehicle_ai::set_state( "off" );
|
|
util::wait_network_frame(); // wait needed to get drop deploy mode to work
|
|
util::wait_network_frame(); // need two to make sure fast forward works
|
|
self SetVehicleForDropDeploy();
|
|
self clientfield::set( "monitor_raps_drop_landing", 1 );
|
|
wait( ( 3 ) );
|
|
if ( self InitialWaitUntilSettled() )
|
|
{
|
|
self ResetVehicleFromDropDeploy();
|
|
self SetGoal( self.origin );
|
|
self vehicle_ai::set_state( "combat" );
|
|
self vehicle::toggle_sounds( 1 );
|
|
|
|
self.drop_deploying = undefined;
|
|
self.hurt_trigger_immune_end_time = undefined;
|
|
Target_Set( self );
|
|
|
|
// try not to target the same enemy
|
|
for( i = 0; i < level.raps[ owner.entNum ].raps.size; i++ )
|
|
{
|
|
raps = level.raps[ owner.entNum ].raps[ i ];
|
|
if( isdefined( raps ) && isdefined( raps.enemy ) && isdefined( self ) && isdefined( self.enemy ) && ( raps != self ) && ( raps.enemy == self.enemy ) )
|
|
{
|
|
self SetPersonalThreatBias( self.enemy, -2000, 5.0 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// could not settle, then self destruct
|
|
self SelfDestruct( self.owner );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function InitialWaitUntilSettled()
|
|
{
|
|
// settle z speed first
|
|
waitTime = 0;
|
|
while ( Abs( self.velocity[2] ) > ( 0.1 ) && waitTime < ( 5.0 ) )
|
|
{
|
|
wait ( 0.2 );
|
|
waitTime += ( 0.2 );
|
|
}
|
|
|
|
// wait until settled on nav mesh
|
|
while( ( !IsPointOnNavMesh( self.origin, ( 36 ) ) || ( Abs( self.velocity[2] ) > ( 0.1 ) ) ) && waitTime < ( ( 5.0 ) + 5.0 ) )
|
|
{
|
|
wait ( 0.2 );
|
|
waitTime += ( 0.2 );
|
|
}
|
|
|
|
/#
|
|
if ( ( false ) )
|
|
waitTime += ( ( 5.0 ) + 5.0 );
|
|
#/
|
|
|
|
// return true if raps settled without timing out
|
|
return ( waitTime < ( ( 5.0 ) + 5.0 ) );
|
|
}
|
|
|
|
|
|
function DestroyAllRaps( entNum, abandoned = false )
|
|
{
|
|
foreach( raps in level.raps[ entNum ].raps )
|
|
{
|
|
if( IsAlive( raps ) )
|
|
{
|
|
raps.owner = undefined;
|
|
raps.abandoned = abandoned; // note: abandoned vehicles do not cause damage radius damage
|
|
raps raps::detonate( raps );
|
|
}
|
|
}
|
|
}
|
|
|
|
//Override for scripts/shared/vehicles/_raps.gsc:force_get_enemies()
|
|
function ForceGetEnemies()
|
|
{
|
|
foreach( player in level.players )
|
|
{
|
|
if( isdefined( self.owner ) && self.owner util::IsEnemyPlayer( player ) && ( !player smokegrenade::IsInSmokeGrenade() ) && !player hasPerk( "specialty_nottargetedbyraps" ) )
|
|
{
|
|
self GetPerfectInfo( player );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function CreateRapsHelicopterInfluencer()
|
|
{
|
|
level endon( "game_ended" );
|
|
|
|
helicopter = self;
|
|
|
|
if ( isdefined( helicopter.influencerEnt ) )
|
|
{
|
|
helicopter.influencerEnt Delete();
|
|
}
|
|
|
|
influencerEnt = spawn( "script_model", helicopter.origin - ( 0, 0, self.assigned_fly_height ) );
|
|
helicopter.influencerEnt = influencerEnt;
|
|
helicopter.influencerEnt.angles = ( 0, 0, 0 );
|
|
helicopter.influencerEnt LinkTo( helicopter );
|
|
|
|
preset = GetInfluencerPreset( "helicopter" );
|
|
if( !IsDefined( preset ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
enemy_team_mask = helicopter spawning::get_enemy_team_mask( helicopter.team );
|
|
helicopter.influencerEnt spawning::create_entity_influencer( "helicopter", enemy_team_mask );
|
|
|
|
helicopter waittill( "death" );
|
|
if ( isdefined( influencerEnt ) )
|
|
{
|
|
influencerEnt delete();
|
|
}
|
|
}
|
|
|
|
function CreateRapsInfluencer()
|
|
{
|
|
raps = self;
|
|
|
|
preset = GetInfluencerPreset( "raps" );
|
|
if( !IsDefined( preset ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
enemy_team_mask = raps spawning::get_enemy_team_mask( raps.team );
|
|
raps spawning::create_entity_influencer( "raps", enemy_team_mask );
|
|
}
|