2023-04-13 17:30:38 +02:00

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 );
}