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