3247 lines
101 KiB
Plaintext
3247 lines
101 KiB
Plaintext
#using scripts\shared\challenges_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\damagefeedback_shared;
|
|
#using scripts\shared\hostmigration_shared;
|
|
#using scripts\shared\killstreaks_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\scoreevents_shared;
|
|
#using scripts\shared\tweakables_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\weapons\_weaponobjects;
|
|
#using scripts\shared\weapons\_heatseekingmissile;
|
|
|
|
#using scripts\mp\_challenges;
|
|
#using scripts\mp\_util;
|
|
#using scripts\mp\gametypes\_globallogic_audio;
|
|
#using scripts\mp\gametypes\_globallogic_player;
|
|
#using scripts\mp\gametypes\_hostmigration;
|
|
#using scripts\mp\gametypes\_spawning;
|
|
#using scripts\mp\killstreaks\_airsupport;
|
|
#using scripts\mp\killstreaks\_dogs;
|
|
#using scripts\mp\killstreaks\_flak_drone;
|
|
#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_animtree ( "mp_vehicles" );
|
|
|
|
#namespace helicopter;
|
|
|
|
#precache( "locationselector", "compass_objpoint_helicopter" );
|
|
#precache( "string", "MP_DESTROYED_HELICOPTER");
|
|
#precache( "string", "KILLSTREAK_DESTROYED_HELICOPTER_GUNNER");
|
|
#precache( "string", "KILLSTREAK_EARNED_HELICOPTER_COMLINK" );
|
|
#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_NOT_AVAILABLE" );
|
|
#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_INBOUND" );
|
|
#precache( "string", "KILLSTREAK_HELICOPTER_COMLINK_HACKED" );
|
|
#precache( "string", "KILLSTREAK_DESTROYED_SUPPLY_DROP_DEPLOY_SHIP" );
|
|
#precache( "string", "mpl_killstreak_heli" );
|
|
#precache( "fx", "killstreaks/fx_heli_exp_lg" );
|
|
#precache( "fx", "killstreaks/fx_heli_exp_md" );
|
|
#precache( "fx", "killstreaks/fx_vtol_exp" );
|
|
#precache( "fx", "killstreaks/fx_heli_exp_sm" );
|
|
#precache( "fx", "killstreaks/fx_heli_smk_trail_engine_33" );
|
|
#precache( "fx", "killstreaks/fx_heli_smk_trail_engine_66" );
|
|
#precache( "fx", "killstreaks/fx_heli_smk_trail_tail" );
|
|
#precache( "fx", "killstreaks/fx_heli_smk_trail_engine" );
|
|
#precache( "fx", "killstreaks/fx_sc_lights_grn" );
|
|
#precache( "fx", "killstreaks/fx_sc_lights_red" );
|
|
|
|
|
|
|
|
|
|
|
|
function precachehelicopter(model,type)
|
|
{
|
|
if(!isdefined(type))
|
|
type = "blackhawk";
|
|
|
|
level.vehicle_deathmodel[model] = model;
|
|
|
|
/******************************************************/
|
|
/* SETUP WEAPON TAGS */
|
|
/******************************************************/
|
|
|
|
// helicopter sounds:
|
|
level.heli_sound["hit"] = "evt_helicopter_hit";
|
|
level.heli_sound["hitsecondary"] = "evt_helicopter_hit";
|
|
level.heli_sound["damaged"] = "null";
|
|
level.heli_sound["spinloop"] = "evt_helicopter_spin_loop";
|
|
level.heli_sound["spinstart"] = "evt_helicopter_spin_start";
|
|
level.heli_sound["crash"] = "evt_helicopter_midair_exp";
|
|
level.heli_sound["missilefire"] = "wpn_hellfire_fire_npc";
|
|
}
|
|
|
|
function useKillstreakHelicopter( hardpointType )
|
|
{
|
|
if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false)
|
|
return false;
|
|
|
|
if ( (!isdefined( level.heli_paths ) || !level.heli_paths.size) )
|
|
{
|
|
/#iprintlnbold("Need to add helicopter paths to the level");#/
|
|
return false;
|
|
}
|
|
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
{
|
|
result = self selectHelicopterLocation( hardpointType );
|
|
if ( !isdefined(result) || result == false )
|
|
return false;
|
|
}
|
|
|
|
destination = 0;
|
|
missilesEnabled = false;
|
|
if ( hardpointType == "helicopter_x2" )
|
|
{
|
|
missilesEnabled = true;
|
|
}
|
|
|
|
assert( level.heli_paths.size > 0, "No non-primary helicopter paths found in map" );
|
|
|
|
random_path = randomint( level.heli_paths[destination].size );
|
|
|
|
startnode = level.heli_paths[destination][random_path];
|
|
|
|
protectLocation = undefined;
|
|
armored = false;
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
{
|
|
protectLocation = (level.helilocation[0], level.helilocation[1], int(airsupport::getMinimumFlyHeight()) -200 );
|
|
armored = false;
|
|
|
|
startnode = getValidProtectLocationStart(random_path, protectLocation, destination );
|
|
}
|
|
|
|
killstreak_id = self killstreakrules::killstreakStart( hardpointType, self.team );
|
|
if ( killstreak_id == -1 )
|
|
return false;
|
|
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
{
|
|
self challenges::calledInComlinkChopper();
|
|
}
|
|
|
|
self killstreaks::play_killstreak_start_dialog( hardpointType, self.team, killstreak_id );
|
|
self thread announceHelicopterInbound( hardpointType );
|
|
|
|
thread heli_think( self, startnode, self.team, missilesEnabled, protectLocation, hardpointType, armored, killstreak_id );
|
|
|
|
return true;
|
|
}
|
|
|
|
function announceHelicopterInbound(hardpointType)
|
|
{
|
|
team = self.team;
|
|
|
|
self AddWeaponStat( killstreaks::get_killstreak_weapon( hardpointtype ), "used", 1 );
|
|
}
|
|
|
|
// generate path graph from script_origins
|
|
function heli_path_graph()
|
|
{
|
|
// collecting all start nodes in the map to generate path arrays
|
|
path_start = getentarray( "heli_start", "targetname" ); // start pointers, point to the actual start node on path
|
|
path_dest = getentarray( "heli_dest", "targetname" ); // dest pointers, point to the actual dest node on path
|
|
loop_start = getentarray( "heli_loop_start", "targetname" ); // start pointers for loop path in the map
|
|
gunner_loop_start = getentarray( "heli_gunner_loop_start", "targetname" ); // start pointers for gunner loop path in the map
|
|
leave_nodes = getentarray( "heli_leave", "targetname" ); // points where the helicopter leaves to
|
|
crash_start = getentarray( "heli_crash_start", "targetname" ); // start pointers, point to the actual start node on crash path
|
|
|
|
assert( ( isdefined( path_start ) && isdefined( path_dest ) ), "Missing path_start or path_dest" );
|
|
|
|
// for each destination, loop through all start nodes in level to populate array of start nodes that leads to this destination
|
|
for (i=0; i<path_dest.size; i++)
|
|
{
|
|
startnode_array = [];
|
|
isPrimaryDest = false;
|
|
|
|
// destnode is the final destination from multiple start nodes
|
|
destnode_pointer = path_dest[i];
|
|
destnode = getent( destnode_pointer.target, "targetname" );
|
|
|
|
// for each start node, traverse to its dest., if same dest. then append to startnode_array
|
|
for ( j=0; j<path_start.size; j++ )
|
|
{
|
|
toDest = false;
|
|
currentnode = path_start[j];
|
|
// traverse through path to dest.
|
|
while( isdefined( currentnode.target ) )
|
|
{
|
|
nextnode = getent( currentnode.target, "targetname" );
|
|
if ( nextnode.origin == destnode.origin )
|
|
{
|
|
toDest = true;
|
|
break;
|
|
}
|
|
|
|
// debug ==============================================================
|
|
airsupport::debug_print3d_simple( "+", currentnode, ( 0, 0, -10 ) );
|
|
if( isdefined( nextnode.target ) )
|
|
airsupport::debug_line( nextnode.origin, getent(nextnode.target, "targetname" ).origin, ( 0.25, 0.5, 0.25 ), 5);
|
|
if( isdefined( currentnode.script_delay ) )
|
|
airsupport::debug_print3d_simple( "Wait: " + currentnode.script_delay , currentnode, ( 0, 0, 10 ) );
|
|
|
|
currentnode = nextnode;
|
|
}
|
|
if ( toDest )
|
|
{
|
|
startnode_array[startnode_array.size] = getent( path_start[j].target, "targetname" ); // the actual start node on path, not start pointer
|
|
if ( isdefined( path_start[j].script_noteworthy ) && ( path_start[j].script_noteworthy == "primary" ) )
|
|
isPrimaryDest = true;
|
|
}
|
|
}
|
|
assert( ( isdefined( startnode_array ) && startnode_array.size > 0 ), "No path(s) to destination" );
|
|
|
|
// load the array of start nodes that lead to this destination node into level.heli_paths array as an element
|
|
if ( isPrimaryDest )
|
|
level.heli_primary_path = startnode_array;
|
|
else
|
|
level.heli_paths[level.heli_paths.size] = startnode_array;
|
|
}
|
|
|
|
// loop paths array
|
|
for (i=0; i<loop_start.size; i++)
|
|
{
|
|
startnode = getent( loop_start[i].target, "targetname" );
|
|
level.heli_loop_paths[level.heli_loop_paths.size] = startnode;
|
|
}
|
|
assert( isdefined( level.heli_loop_paths[0] ), "No helicopter loop paths found in map" );
|
|
|
|
// chopper gunner paths
|
|
for ( i = 0 ; i < gunner_loop_start.size ; i++ )
|
|
{
|
|
startnode = getent( gunner_loop_start[i].target, "targetname" );
|
|
startnode.isGunnerPath = true;
|
|
level.heli_loop_paths[level.heli_loop_paths.size] = startnode;
|
|
}
|
|
|
|
// start nodes
|
|
for (i=0; i<path_start.size; i++)
|
|
{
|
|
// do not consider "primary" starts as a path_start for supply drops
|
|
if ( isdefined( path_start[ i ].script_noteworthy ) && ( path_start[ i ].script_noteworthy == "primary" ) )
|
|
continue;
|
|
|
|
level.heli_startnodes[level.heli_startnodes.size] = path_start[i];
|
|
}
|
|
assert( isdefined( level.heli_startnodes[0] ), "No helicopter start nodes found in map" );
|
|
|
|
// leave nodes
|
|
for (i=0; i<leave_nodes.size; i++)
|
|
level.heli_leavenodes[level.heli_leavenodes.size] = leave_nodes[i];
|
|
assert( isdefined( level.heli_leavenodes[0] ), "No helicopter leave nodes found in map" );
|
|
|
|
// crash paths
|
|
for (i=0; i<crash_start.size; i++)
|
|
{
|
|
crash_start_node = getent( crash_start[i].target, "targetname" );
|
|
level.heli_crash_paths[level.heli_crash_paths.size] = crash_start_node;
|
|
}
|
|
assert( isdefined( level.heli_crash_paths[0] ), "No helicopter crash paths found in map" );
|
|
}
|
|
|
|
function init()
|
|
{
|
|
path_start = getentarray( "heli_start", "targetname" ); // start pointers, point to the actual start node on path
|
|
loop_start = getentarray( "heli_loop_start", "targetname" ); // start pointers for loop path in the map
|
|
|
|
//dvars
|
|
/#debug_refresh=true;#/
|
|
thread heli_update_global_dvars(debug_refresh);
|
|
|
|
level.chaff_offset["attack"] = ( -130, 0, -140 );
|
|
|
|
level.chopperComlinkFriendly = "veh_t7_drone_hunter";
|
|
level.chopperComlinkEnemy = "veh_t7_drone_hunter";
|
|
level.chopperRegular = "veh_t7_drone_hunter";
|
|
|
|
precachehelicopter( level.chopperRegular );
|
|
precachehelicopter( level.chopperComlinkFriendly );
|
|
precachehelicopter( level.chopperComlinkEnemy );
|
|
|
|
clientfield::register( "helicopter", "heli_comlink_bootup_anim", 1, 1, "int");
|
|
clientfield::register( "helicopter", "heli_warn_targeted", 1, 1, "int" );
|
|
clientfield::register( "helicopter", "heli_warn_locked", 1, 1, "int" );
|
|
clientfield::register( "helicopter", "heli_warn_fired", 1, 1, "int" );
|
|
|
|
clientfield::register( "helicopter", "active_camo", 1, 3, "int" );
|
|
|
|
|
|
clientfield::register( "vehicle", "heli_comlink_bootup_anim", 1, 1, "int");
|
|
clientfield::register( "vehicle", "heli_warn_targeted", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "heli_warn_locked", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "heli_warn_fired", 1, 1, "int" );
|
|
|
|
clientfield::register( "vehicle", "active_camo", 1, 3, "int" );
|
|
|
|
|
|
// array of paths, each element is an array of start nodes that all leads to a single destination node
|
|
level.heli_paths = [];
|
|
level.heli_loop_paths = [];
|
|
level.heli_startnodes = [];
|
|
level.heli_leavenodes = [];
|
|
level.heli_crash_paths = [];
|
|
|
|
level.last_start_node_index = 0;
|
|
|
|
// helicopter fx
|
|
level.chopper_fx["explode"]["death"] = "killstreaks/fx_heli_exp_lg";
|
|
level.chopper_fx["explode"]["guard"] = "killstreaks/fx_heli_exp_md";
|
|
level.chopper_fx["explode"]["gunner"] = "killstreaks/fx_vtol_exp";
|
|
level.chopper_fx["explode"]["large"] = "killstreaks/fx_heli_exp_sm";
|
|
//level.chopper_fx["explode"]["medium"] = "_debug/fx_debug_deleted_fx";
|
|
level.chopper_fx["damage"]["light_smoke"] = "killstreaks/fx_heli_smk_trail_engine_33";
|
|
level.chopper_fx["damage"]["heavy_smoke"] = "killstreaks/fx_heli_smk_trail_engine_66";
|
|
level.chopper_fx["smoke"]["trail"] = "killstreaks/fx_heli_smk_trail_tail";
|
|
//level.chopper_fx["fire"]["trail"]["medium"] = "_debug/fx_debug_deleted_fx";
|
|
level.chopper_fx["fire"]["trail"]["large"] = "killstreaks/fx_heli_smk_trail_engine";
|
|
// level.coptermainrotor_fx = "_debug/fx_debug_deleted_fx";
|
|
// level.coptertailrotor_fx = "_debug/fx_debug_deleted_fx";
|
|
// level.coptertailrotordamaged_fx = "_debug/fx_debug_deleted_fx";
|
|
|
|
level._effect["heli_comlink_light"]["friendly"] = "_debug/fx_debug_deleted_fx";
|
|
level._effect["heli_comlink_light"]["enemy"] = "_debug/fx_debug_deleted_fx";
|
|
|
|
level.heliComlinkBootupAnim = %veh_anim_future_heli_gearup_bay_open;
|
|
|
|
killstreaks::register("helicopter_comlink", "helicopter_comlink", "killstreak_helicopter_comlink", "helicopter_used",&useKillstreakHelicopter, true );
|
|
killstreaks::register_strings("helicopter_comlink", &"KILLSTREAK_EARNED_HELICOPTER_COMLINK", &"KILLSTREAK_HELICOPTER_COMLINK_NOT_AVAILABLE", &"KILLSTREAK_HELICOPTER_COMLINK_INBOUND", undefined, &"KILLSTREAK_HELICOPTER_COMLINK_HACKED" );
|
|
killstreaks::register_dialog("helicopter_comlink", "mpl_killstreak_heli", "helicopterDialogBundle", "helicopterPilotDialogBundle", "friendlyHelicopter", "enemyHelicopter", "enemyHelicopterMultiple", "friendlyHelicopterHacked", "enemyHelicopterHacked", "requestHelicopter", "threatHelicopter" );
|
|
killstreaks::register_alt_weapon("helicopter_comlink", "cobra_20mm_comlink" );
|
|
killstreaks::set_team_kill_penalty_scale( "helicopter_comlink", 0.0 );
|
|
|
|
// TODO: Move to killstreak data
|
|
level.killstreaks["helicopter_comlink"].threatOnKill = true;
|
|
level.killstreaks["inventory_helicopter_comlink"].threatOnKill = true;
|
|
|
|
if ( !path_start.size && !loop_start.size)
|
|
return;
|
|
|
|
heli_path_graph();
|
|
}
|
|
|
|
// update helicopter dvars realtime
|
|
function heli_update_global_dvars(debug_refresh)
|
|
{
|
|
do
|
|
{
|
|
// heli_update_dvar( dvar, default ) returns value
|
|
level.heli_loopmax = heli_get_dvar_int( "scr_heli_loopmax", "2" ); // how many times helicopter will circle the map before it leaves
|
|
level.heli_missile_rof = heli_get_dvar_int( "scr_heli_missile_rof", "2" ); // missile rate of fire, one every this many seconds per target, could fire two at the same time to different targets
|
|
level.heli_armor = heli_get_dvar_int( "scr_heli_armor", "500" ); // armor points, after this much damage is taken, helicopter is easily damaged, and health degrades
|
|
//level.heli_rage_missile = heli_get_dvar( "scr_heli_rage_missile", "50" ); // higher the value, more frequent the missile assault
|
|
level.heli_maxhealth = heli_get_dvar_int( "scr_heli_maxhealth", "2000" ); // max health of the helicopter (used for stealth heli and heli guard)
|
|
level.heli_amored_maxhealth = heli_get_dvar_int( "scr_heli_armored_maxhealth", "1500" ); // max health of the helicopter (used for chopper gunner and reaper)
|
|
level.heli_missile_max = heli_get_dvar_int( "scr_heli_missile_max", "20" ); // max number of missiles helicopter can carry
|
|
level.heli_dest_wait = heli_get_dvar_int( "scr_heli_dest_wait", "8" ); // time helicopter waits (hovers) after reaching a destination
|
|
level.heli_debug = heli_get_dvar_int( "scr_heli_debug", "0" ); // debug mode, draws debugging info on screen
|
|
level.heli_debug_crash = heli_get_dvar_int( "scr_heli_debug_crash", "0" ); // debug mode, draws debugging info on screen
|
|
|
|
level.heli_targeting_delay = heli_get_dvar( "scr_heli_targeting_delay", "0.6" ); // targeting delay
|
|
level.heli_turretReloadTime = heli_get_dvar( "scr_heli_turretReloadTime", "0.5" ); // mini-gun reload time
|
|
level.heli_turretClipSize = heli_get_dvar_int( "scr_heli_turretClipSize", "80" ); // mini-gun clip size, rounds before reload
|
|
level.heli_visual_range = (isdefined(level.heli_visual_range_override)?level.heli_visual_range_override:heli_get_dvar_int( "scr_heli_visual_range", "3500" )); // distance radius helicopter will acquire targets (see)
|
|
level.heli_missile_range = heli_get_dvar_int( "scr_heli_missile_range", "100000" ); // distance radius helicopter will acquire targets (see)
|
|
level.heli_health_degrade = heli_get_dvar_int( "scr_heli_health_degrade", "0" ); // health degradation after injured, health descrease per second for heavy injury, half for light injury
|
|
|
|
level.heli_turret_angle_tan = heli_get_dvar_int( "scr_heli_turret_angle_tan", "1" ); // tangent of the max angle that the turrent can shoot off the forward direction off the chopper
|
|
level.heli_turret_target_cone = heli_get_dvar( "scr_heli_turret_target_cone", "0.6" ); // dot product of vector target to helicopter forward, 0.5 is in 90 range, bigger the number, smaller the cone
|
|
level.heli_target_spawnprotection = heli_get_dvar_int( "scr_heli_target_spawnprotection", "5" );// players are this many seconds safe from helicopter after spawn
|
|
level.heli_missile_regen_time = heli_get_dvar( "scr_heli_missile_regen_time", "10" ); // gives one more missile to helicopter every interval - seconds
|
|
level.heli_turret_spinup_delay = heli_get_dvar( "scr_heli_turret_spinup_delay", "1.0" ); // seconds it takes for the helicopter mini-gun to spin up before shots fired
|
|
level.heli_target_recognition = heli_get_dvar( "scr_heli_target_recognition", "0.3" ); // percentage of the player's body the helicopter sees before it labels him as a target
|
|
level.heli_missile_friendlycare = heli_get_dvar_int( "scr_heli_missile_friendlycare", "512" ); // if friendly is within this distance of the target, do not shoot missile
|
|
level.heli_missile_target_cone = heli_get_dvar( "scr_heli_missile_target_cone", "0.6" ); // dot product of vector target to helicopter forward, 0.5 is in 90 range, bigger the number, smaller the cone
|
|
level.heli_valid_target_cone = heli_get_dvar( "scr_heli_missile_valid_target_cone", "0.7" ); // dot product of vector target to helicopter forward, 0.5 is in 90 range, bigger the number, smaller the cone
|
|
level.heli_armor_bulletdamage = heli_get_dvar( "scr_heli_armor_bulletdamage", "0.5" ); // damage multiplier to bullets onto helicopter's armor
|
|
|
|
level.heli_attract_strength = heli_get_dvar( "scr_heli_attract_strength", "1000" );
|
|
level.heli_attract_range = heli_get_dvar( "scr_heli_attract_range", "20000" );
|
|
|
|
level.helicopterTurretMaxAngle = heli_get_dvar_int( "scr_helicopterTurretMaxAngle", "35" );
|
|
|
|
level.heli_protect_time = heli_get_dvar( "scr_heli_protect_time", "60" ); // time in air for helicopter protection killstreak
|
|
level.heli_protect_pos_time = heli_get_dvar( "scr_heli_protect_pos_time", "12" ); // time at each position
|
|
level.heli_protect_radius = heli_get_dvar_int( "scr_heli_protect_radius", "2000" ); // max distance from chosen position to set temp protect point
|
|
|
|
level.heli_missile_reload_time = heli_get_dvar( "scr_heli_missile_reload_time", "5.0" ); // time it takes the gunship missiles to reload
|
|
level.heli_warning_distance = heli_get_dvar_int( "scr_heli_warning_distance", "500" ); // distance to the edge of the height mesh that you will start getting the warning sound
|
|
wait 1;
|
|
}
|
|
while (isdefined(debug_refresh));
|
|
}
|
|
|
|
function heli_get_dvar_int( dvar, def )
|
|
{
|
|
return int( heli_get_dvar( dvar, def ) );
|
|
}
|
|
|
|
// dvar set/fetch/check
|
|
function heli_get_dvar( dvar, def )
|
|
{
|
|
if ( GetDvarString( dvar ) != "" )
|
|
return getdvarfloat( dvar );
|
|
else
|
|
{
|
|
SetDvar( dvar, def );
|
|
return Float( def );
|
|
}
|
|
}
|
|
|
|
function set_goal_pos( goalPos, stop )
|
|
{
|
|
self.heliGoalPos = goalpos;
|
|
self setvehgoalpos( goalPos, stop );
|
|
}
|
|
|
|
function spawn_helicopter( owner, origin, angles, model, targetname, target_offset, hardpointType, killstreak_id )
|
|
{
|
|
chopper = spawnHelicopter( owner, origin, angles, model, targetname );
|
|
chopper.owner = owner;
|
|
chopper clientfield::set( "enemyvehicle", 1 );
|
|
chopper.attackers = [];
|
|
chopper.attackerData = [];
|
|
chopper.attackerDamage = [];
|
|
chopper.flareAttackerDamage = [];
|
|
chopper.destroyFunc =&destroyHelicopter;
|
|
chopper.hardpointType = hardpointType;
|
|
chopper.killstreak_id = killstreak_id;
|
|
chopper.pilotIsTalking = false;
|
|
chopper SetDrawInfrared( true );
|
|
chopper.allowContinuedLockonAfterInvis = true;
|
|
chopper.soundmod = "heli";
|
|
|
|
if ( !isdefined( target_offset ) )
|
|
target_offset = (0,0,0);
|
|
|
|
chopper.target_offset = target_offset;
|
|
|
|
Target_Set(chopper, target_offset);
|
|
|
|
if( ( hardpointtype == "helicopter_comlink" ) || ( hardpointtype == "inventory_helicopter_comlink" ) )
|
|
{
|
|
chopper.allowHackingAfterCloak = true;
|
|
chopper.flak_drone = flak_drone::spawn( chopper, &OnFlakDroneDestroyed );
|
|
chopper.treat_owner_damage_as_friendly_fire = true;
|
|
chopper.ignore_team_kills = true;
|
|
chopper init_active_camo();
|
|
}
|
|
|
|
return chopper;
|
|
}
|
|
|
|
function OnFlakDroneDestroyed()
|
|
{
|
|
chopper = self;
|
|
|
|
if ( !isdefined( chopper ) )
|
|
{
|
|
return;
|
|
}
|
|
chopper.numFlares = 0;
|
|
chopper killstreaks::play_pilot_dialog_on_owner( "weaponDestroyed", "helicopter_comlink", chopper.killstreak_id );
|
|
|
|
chopper thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile("crashing", "death");
|
|
}
|
|
|
|
function explodeOnContact( hardpointtype )
|
|
{
|
|
self endon ( "death" );
|
|
wait (10);
|
|
for (;;)
|
|
{
|
|
self waittill("touch");
|
|
self thread heli_explode();
|
|
}
|
|
}
|
|
|
|
function getValidProtectLocationStart(random_path, protectLocation, destination )
|
|
{
|
|
startnode = level.heli_paths[destination][random_path];
|
|
path_index = ( random_path + 1 ) % level.heli_paths[destination].size;
|
|
|
|
inNoFly = airsupport::crossesNoFlyZone( protectLocation + (0,0,1), protectLocation );
|
|
if( isdefined(inNoFly))
|
|
{
|
|
protectLocation = (protectLocation[0], protectLocation[1], level.noFlyZones[inNoFly].origin[2] + level.noFlyZones[inNoFly].height);
|
|
}
|
|
|
|
noFlyZone = airsupport::crossesNoFlyZone( startnode.origin, protectLocation );
|
|
while ( isdefined(noFlyZone) && path_index != random_path )
|
|
{
|
|
startnode = level.heli_paths[destination][path_index];
|
|
|
|
if( isDefined(noFlyZone) )
|
|
{
|
|
path_index = ( path_index + 1 ) % level.heli_paths[destination].size;
|
|
}
|
|
}
|
|
|
|
// if we dont have a valid position at this point just return
|
|
return level.heli_paths[destination][path_index];
|
|
}
|
|
|
|
function getValidRandomLeaveNode( start )
|
|
{
|
|
if( self === level.vtol )
|
|
{
|
|
foreach( node in level.heli_leavenodes )
|
|
{
|
|
if ( isdefined( node.script_noteworthy ) && ( node.script_noteworthy == "primary" ) )
|
|
return node;
|
|
}
|
|
}
|
|
|
|
random_leave_node = randomInt( level.heli_leavenodes.size );
|
|
leavenode = level.heli_leavenodes[random_leave_node];
|
|
path_index = ( random_leave_node + 1 ) % level.heli_leavenodes.size;
|
|
noFlyZone = airsupport::crossesNoFlyZone( leavenode.origin, start );
|
|
isPrimary = ( leavenode.script_noteworthy === "primary" );
|
|
|
|
while( ( isdefined( noFlyZone ) || isPrimary ) && ( path_index != random_leave_node ) )
|
|
{
|
|
leavenode = level.heli_leavenodes[path_index];
|
|
noFlyZone = airsupport::crossesNoFlyZone( leavenode.origin, start );
|
|
isPrimary = ( leavenode.script_noteworthy === "primary" );
|
|
path_index = ( path_index + 1 ) % level.heli_leavenodes.size;
|
|
}
|
|
|
|
// if we dont have a valid position at this point just return
|
|
return level.heli_leavenodes[path_index];
|
|
}
|
|
|
|
function getValidRandomStartNode( dest )
|
|
{
|
|
path_index = randomInt( level.heli_startnodes.size );
|
|
best_index = path_index;
|
|
|
|
count = 0;
|
|
for( i = 0; i < level.heli_startnodes.size; i++ )
|
|
{
|
|
startnode = level.heli_startnodes[path_index];
|
|
noFlyZone = airsupport::crossesNoFlyZone( startnode.origin, dest );
|
|
|
|
if( !isdefined( noFlyZone ) ) // we didnt hit any noflyzones
|
|
{
|
|
best_index = path_index;
|
|
if( path_index != level.last_start_node_index ) // if possible, we dont want to pickup the previous index
|
|
break;
|
|
}
|
|
path_index = ( path_index + 1 ) % level.heli_startnodes.size;
|
|
}
|
|
|
|
level.last_start_node_index = best_index;
|
|
return level.heli_startnodes[best_index];
|
|
}
|
|
|
|
function getValidRandomCrashNode( start )
|
|
{
|
|
random_leave_node = randomInt( level.heli_crash_paths.size );
|
|
leavenode = level.heli_crash_paths[random_leave_node];
|
|
path_index = ( random_leave_node + 1 ) % level.heli_crash_paths.size;
|
|
|
|
noFlyZone = airsupport::crossesNoFlyZone( leavenode.origin, start );
|
|
while ( isdefined(noFlyZone) && path_index != random_leave_node )
|
|
{
|
|
leavenode = level.heli_crash_paths[path_index];
|
|
noFlyZone = airsupport::crossesNoFlyZone( leavenode.origin, start );
|
|
path_index = ( path_index + 1 ) % level.heli_crash_paths.size;
|
|
}
|
|
|
|
// if we dont have a valid position at this point just return
|
|
return level.heli_crash_paths[path_index];
|
|
}
|
|
|
|
function ConfigureTeamPost( owner, isHacked )
|
|
{
|
|
chopper = self;
|
|
owner thread watchForEarlyLeave( chopper );
|
|
}
|
|
|
|
function HackedCallbackPost( hacker )
|
|
{
|
|
heli = self;
|
|
if ( isdefined ( heli.flak_drone ) )
|
|
{
|
|
heli.flak_drone flak_drone::configureteam( heli, true );
|
|
}
|
|
heli.endTime = getTime() + killstreak_bundles::get_hack_timeout() * 1000;
|
|
heli.killstreakEndTime = int( heli.endTime );
|
|
}
|
|
|
|
|
|
// spawn helicopter at a start node and monitors it
|
|
function heli_think( owner, startnode, heli_team, missilesEnabled, protectLocation, hardpointType, armored, killstreak_id )
|
|
{
|
|
heliOrigin = startnode.origin;
|
|
heliAngles = startnode.angles;
|
|
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
{
|
|
chopperModelFriendly = level.chopperComlinkFriendly;
|
|
chopperModelEnemy = level.chopperComlinkEnemy;
|
|
}
|
|
else
|
|
{
|
|
chopperModelFriendly = level.chopperRegular;
|
|
chopperModelEnemy = level.chopperRegular;
|
|
}
|
|
|
|
chopper = spawn_helicopter( owner, heliOrigin, heliAngles, "heli_ai_mp", chopperModelFriendly, (0,0,100), hardpointType, killstreak_id );
|
|
chopper.harpointType = hardpointType;
|
|
|
|
chopper killstreaks::configure_team( hardpointType, killstreak_id, owner, "helicopter", undefined, &ConfigureTeamPost );
|
|
|
|
if ( hardpointType == "helicopter_comlink" || hardpointType == "inventory_helicopter_comlink" )
|
|
{
|
|
chopper killstreak_hacking::enable_hacking( "helicopter_comlink", undefined, &HackedCallbackPost );
|
|
}
|
|
|
|
chopper setEnemyModel(chopperModelEnemy);
|
|
|
|
chopper thread watchForEMP();
|
|
|
|
//Delay the SAM turret targeting the chopper until it has had a chance to enter the play space.
|
|
Target_SetTurretAquire( chopper, false );
|
|
chopper thread SAMTurretWatcher();
|
|
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
chopper.defaultWeapon = GetWeapon( "cobra_20mm_comlink" );
|
|
else
|
|
chopper.defaultWeapon = GetWeapon( "cobra_20mm" );
|
|
|
|
chopper.requiredDeathCount = owner.deathCount;
|
|
chopper.chaff_offset = level.chaff_offset["attack"];
|
|
|
|
minigun_snd_ent = spawn( "script_origin", chopper GetTagOrigin( "tag_flash" ) );
|
|
minigun_snd_ent LinkTo( chopper, "tag_flash", (0,0,0), (0,0,0) );
|
|
chopper.minigun_snd_ent = minigun_snd_ent;
|
|
minigun_snd_ent thread AutoStopSound();
|
|
|
|
chopper thread heli_existance();
|
|
|
|
level.chopper = chopper;
|
|
|
|
//chopper.crashType = "explode"; //want attack helicopters and chopper gunners to spin out
|
|
|
|
chopper.reached_dest = false; // has helicopter reached destination
|
|
if ( armored )
|
|
chopper.maxhealth = level.heli_amored_maxhealth;// max armored health
|
|
else
|
|
chopper.maxhealth = level.heli_maxhealth; // max health
|
|
|
|
chopper.rocketDamageOneShot = level.heli_maxhealth + 1; // Make it so the heatseeker blows it up in one hit
|
|
chopper.rocketDamageTwoShot = (level.heli_maxhealth / 2) + 1; // Make it so the heatseeker blows it up in two hit
|
|
|
|
if ( ( hardpointType == "helicopter_comlink" ) || ( hardpointType == "inventory_helicopter_comlink" ) )
|
|
{
|
|
chopper.numFlares = 0; // no flares, let the flak drone do the work of intercepting the missile
|
|
}
|
|
else if( hardpointType == "helicopter_guard")
|
|
{
|
|
chopper.numFlares = 1;
|
|
}
|
|
else
|
|
{
|
|
chopper.numFlares = 2; // 2 flares for chopper gunner, 1 for attack/guard
|
|
}
|
|
|
|
chopper.flareOffset = (0,0,-256); // offset from vehicle to start the flares
|
|
chopper.waittime = level.heli_dest_wait; // the time helicopter will stay stationary at destination
|
|
chopper.loopcount = 0; // how many times helicopter circled the map
|
|
chopper.evasive = false; // evasive manuvering
|
|
chopper.health_bulletdamageble = level.heli_armor; // when damage taken is above this value, helicopter can be damage by bullets to its full amount
|
|
chopper.health_evasive = level.heli_armor; // when damage taken is above this value, helicopter performs evasive manuvering
|
|
chopper.targeting_delay = level.heli_targeting_delay; // delay between per targeting scan - in seconds
|
|
chopper.primaryTarget = undefined; // primary target ( player )
|
|
chopper.secondaryTarget = undefined; // secondary target ( player )
|
|
chopper.attacker = undefined; // last player that shot the helicopter
|
|
chopper.missile_ammo = level.heli_missile_max; // initial missile ammo
|
|
chopper.currentstate = "ok"; // health state
|
|
chopper.lastRocketFireTime = -1;
|
|
|
|
// helicopter loop threads
|
|
if ( isdefined(protectLocation) )
|
|
{
|
|
chopper thread heli_protect( startNode, protectLocation, hardpointType, heli_team );
|
|
chopper clientfield::set( "heli_comlink_bootup_anim", 1 );
|
|
}
|
|
else
|
|
{
|
|
chopper thread heli_fly( startnode, 2.0, hardpointType ); // fly heli to given node and continue on its path
|
|
}
|
|
|
|
chopper thread heli_damage_monitor( hardpointtype ); // monitors damage
|
|
chopper thread wait_for_killed(); // monitors player kills
|
|
chopper thread heli_health( hardpointType ); // display helicopter's health through smoke/fire
|
|
chopper thread attack_targets( missilesEnabled, hardpointType ); // enable attack
|
|
chopper thread heli_targeting( missilesEnabled, hardpointType ); // targeting logic
|
|
chopper thread heli_missile_regen(); // regenerates missile ammo
|
|
|
|
if( hardpointtype != "helicopter_comlink" )
|
|
{
|
|
chopper thread heatseekingmissile::MissileTarget_ProximityDetonateIncomingMissile("crashing", "death"); // fires chaff if needed
|
|
chopper thread create_flare_ent( (0,0,-100) );
|
|
}
|
|
}
|
|
|
|
function AutoStopSound()
|
|
{
|
|
self endon( "death" );
|
|
level waittill( "game_ended" );
|
|
|
|
self StopLoopSound();
|
|
}
|
|
|
|
function heli_existance()
|
|
{
|
|
self waittill( "leaving" );
|
|
|
|
self spawning::remove_influencers();
|
|
}
|
|
|
|
function create_flare_ent( offset )
|
|
{
|
|
self.flare_ent = spawn( "script_model", self GetTagOrigin("tag_origin") );
|
|
self.flare_ent SetModel( "tag_origin" );
|
|
self.flare_ent LinkTo( self, "tag_origin", offset );
|
|
}
|
|
|
|
function heli_missile_regen()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
for( ;; )
|
|
{
|
|
airsupport::debug_print3d( "Missile Ammo: " + self.missile_ammo, ( 0.5, 0.5, 1 ), self, ( 0, 0, -100 ), 0 );
|
|
|
|
if( self.missile_ammo >= level.heli_missile_max )
|
|
self waittill( "missile fired" );
|
|
else
|
|
{
|
|
// regenerates faster when damaged
|
|
if ( self.currentstate == "heavy smoke" )
|
|
wait( level.heli_missile_regen_time/4 );
|
|
else if ( self.currentstate == "light smoke" )
|
|
wait( level.heli_missile_regen_time/2 );
|
|
else
|
|
wait( level.heli_missile_regen_time );
|
|
}
|
|
if( self.missile_ammo < level.heli_missile_max )
|
|
self.missile_ammo++;
|
|
}
|
|
}
|
|
|
|
// helicopter targeting logic
|
|
function heli_targeting( missilesEnabled, hardpointType )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
// targeting sweep cycle
|
|
for ( ;; )
|
|
{
|
|
// array of helicopter's targets
|
|
targets = [];
|
|
targetsMissile = [];
|
|
// scan for all players in game
|
|
players = level.players;
|
|
for (i = 0; i < players.size; i++)
|
|
{
|
|
player = players[i];
|
|
|
|
if ( self canTargetPlayer_turret( player, hardpointType ) )
|
|
{
|
|
if( isdefined( player ) )
|
|
targets[targets.size] = player;
|
|
}
|
|
if ( missilesEnabled && ( self canTargetPlayer_missile( player, hardpointType ) ) )
|
|
{
|
|
if( isdefined( player ) )
|
|
targetsMissile[targetsMissile.size] = player;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
dogs = dogs::dog_manager_get_dogs();
|
|
|
|
foreach( dog in dogs )
|
|
{
|
|
if ( self canTargetDog_turret( dog ) )
|
|
{
|
|
targets[targets.size] = dog;
|
|
}
|
|
if ( missilesEnabled && (self canTargetDog_missile( dog ) ) )
|
|
{
|
|
targetsMissile[targetsMissile.size] = dog;
|
|
}
|
|
}
|
|
|
|
tanks = GetEntArray( "talon", "targetname" );
|
|
tanks = ArrayCombine( tanks, GetEntArray( "siegebot", "targetname" ), false, false );
|
|
|
|
foreach( tank in tanks )
|
|
{
|
|
if ( self canTargetTank_turret( tank ) )
|
|
{
|
|
targets[targets.size] = tank;
|
|
}
|
|
}
|
|
|
|
actors = GetActorArray();
|
|
foreach( actor in actors )
|
|
{
|
|
if( isdefined( actor ) && isdefined( actor.isAiClone ) && IsAlive( actor ) )
|
|
{
|
|
if ( self canTargetActor_turret( actor, hardpointType ) )
|
|
{
|
|
targets[targets.size] = actor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// no targets found
|
|
if ( targets.size == 0 && targetsMissile.size == 0 )
|
|
{
|
|
self.primaryTarget = undefined;
|
|
self.secondaryTarget = undefined;
|
|
debug_print_target(); // debug
|
|
self SetGoalYaw(RandomInt(360));
|
|
wait ( self.targeting_delay );
|
|
continue;
|
|
}
|
|
|
|
if( targets.size == 1 )
|
|
{
|
|
if( isdefined( targets[0].isAiClone ) )
|
|
{
|
|
killstreaks::update_actor_threat( targets[0] );
|
|
}
|
|
else if( isdefined( targets[0].type ) && (targets[0].type == "dog" || targets[0].type == "tank_drone"))
|
|
{
|
|
killstreaks::update_dog_threat( targets[0] );
|
|
}
|
|
else if ( isdefined( targets[0].killstreakType ) )
|
|
{
|
|
killstreaks::update_non_player_threat( targets[0] );
|
|
}
|
|
else
|
|
{
|
|
killstreaks::update_player_threat( targets[0] );
|
|
}
|
|
|
|
self.primaryTarget = targets[0]; // primary only
|
|
self notify( "primary acquired" );
|
|
self.secondaryTarget = undefined;
|
|
debug_print_target(); // debug
|
|
}
|
|
else if ( targets.size > 1 )
|
|
assignPrimaryTargets( targets );
|
|
|
|
if( targetsMissile.size == 1 )
|
|
{
|
|
if( !isdefined( targetsMissile[0].type ) || targetsMissile[0].type != "dog" || targets[0].type == "tank_drone")
|
|
{
|
|
self killstreaks::update_missile_player_threat( targetsMissile[0] );
|
|
}
|
|
else if( targetsMissile[0].type == "dog" )
|
|
{
|
|
self killstreaks::update_missile_dog_threat( targetsMissile[0] );
|
|
}
|
|
|
|
self.secondaryTarget = targetsMissile[0]; // primary only
|
|
self notify( "secondary acquired" );
|
|
debug_print_target(); // debug
|
|
}
|
|
else if( targetsMissile.size > 1 )
|
|
assignSecondaryTargets( targetsMissile );
|
|
|
|
wait ( self.targeting_delay );
|
|
|
|
debug_print_target(); //debug
|
|
}
|
|
}
|
|
|
|
// targetability
|
|
function canTargetPlayer_turret( player, hardpointType )
|
|
{
|
|
canTarget = true;
|
|
|
|
if ( !isalive( player ) || player.sessionstate != "playing" )
|
|
return false;
|
|
|
|
if ( player.ignoreme === true )
|
|
return false;
|
|
|
|
if ( player == self.owner )
|
|
{
|
|
self check_owner( hardpointType );
|
|
return false;
|
|
}
|
|
|
|
if ( player airsupport::canTargetPlayerWithSpecialty() == false )
|
|
return false;
|
|
|
|
if ( distance( player.origin, self.origin ) > level.heli_visual_range )
|
|
return false;
|
|
|
|
if ( !isdefined( player.team ) )
|
|
return false;
|
|
|
|
if ( level.teamBased && player.team == self.team )
|
|
return false;
|
|
|
|
if ( player.team == "spectator" )
|
|
return false;
|
|
|
|
if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= level.heli_target_spawnprotection )
|
|
return false;
|
|
|
|
heli_centroid = self.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( self.angles );
|
|
heli_turret_point = heli_centroid + 144*heli_forward_norm;
|
|
|
|
visible_amount = player sightConeTrace( heli_turret_point, self);
|
|
|
|
if ( visible_amount < level.heli_target_recognition )
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
function canTargetActor_turret( actor, hardpointType )
|
|
{
|
|
helicopter = self;
|
|
|
|
canTarget = true;
|
|
|
|
if( !isalive( actor ) )
|
|
return actor;
|
|
|
|
if( !isdefined( actor.team ) )
|
|
return false;
|
|
|
|
if( level.teamBased && actor.team == helicopter.team )
|
|
return false;
|
|
|
|
if( DistanceSquared( actor.origin, helicopter.origin ) > ( level.heli_visual_range * level.heli_visual_range ) )
|
|
return false;
|
|
|
|
heli_centroid = helicopter.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( helicopter.angles );
|
|
heli_turret_point = heli_centroid + 144 * heli_forward_norm;
|
|
|
|
visible_amount = actor sightConeTrace( heli_turret_point, helicopter );
|
|
if( visible_amount < level.heli_target_recognition )
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
function getVerticalTan( startOrigin, endOrigin )
|
|
{
|
|
vector = endOrigin - startOrigin;
|
|
|
|
opposite = startOrigin[2] - endOrigin[2];
|
|
if ( opposite < 0 )
|
|
opposite *= 1;
|
|
|
|
adjacent = distance2d( startOrigin, endOrigin );
|
|
|
|
if ( adjacent < 0 )
|
|
adjacent *= 1;
|
|
|
|
if ( adjacent < 0.01 )
|
|
adjacent = 0.01;
|
|
|
|
tangent = opposite / adjacent;
|
|
|
|
return tangent;
|
|
}
|
|
|
|
|
|
|
|
// targetability
|
|
function canTargetPlayer_missile( player, hardpointType )
|
|
{
|
|
canTarget = true;
|
|
|
|
if ( !isalive( player ) || player.sessionstate != "playing" )
|
|
return false;
|
|
|
|
if ( player.ignoreme === true )
|
|
return false;
|
|
|
|
if ( player == self.owner )
|
|
{
|
|
self check_owner( hardpointType );
|
|
return false;
|
|
}
|
|
|
|
if ( player airsupport::canTargetPlayerWithSpecialty() == false )
|
|
return false;
|
|
|
|
if ( distance( player.origin, self.origin ) > level.heli_missile_range )
|
|
return false;
|
|
|
|
if ( !isdefined( player.team ) )
|
|
return false;
|
|
|
|
if ( level.teamBased && player.team == self.team )
|
|
return false;
|
|
|
|
if ( player.team == "spectator" )
|
|
return false;
|
|
|
|
if ( isdefined( player.spawntime ) && ( gettime() - player.spawntime )/1000 <= level.heli_target_spawnprotection )
|
|
return false;
|
|
|
|
if ( self target_cone_check( player, level.heli_missile_target_cone ) == false )
|
|
return false;
|
|
|
|
heli_centroid = self.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( self.angles );
|
|
heli_turret_point = heli_centroid + 144*heli_forward_norm;
|
|
|
|
if (!isdefined(player.lastHit))
|
|
player.lastHit = 0;
|
|
|
|
player.lastHit = self HeliTurretSightTrace( heli_turret_point, player, player.lastHit );
|
|
if (player.lastHit != 0)
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
// targetability
|
|
function canTargetDog_turret( dog )
|
|
{
|
|
canTarget = true;
|
|
|
|
if ( !isdefined( dog ) )
|
|
return false;
|
|
|
|
if ( distance( dog.origin, self.origin ) > level.heli_visual_range )
|
|
return false;
|
|
|
|
if ( !isdefined( dog.team ) )
|
|
return false;
|
|
|
|
if ( level.teamBased && (dog.team == self.team) )
|
|
return false;
|
|
|
|
if ( isdefined(dog.script_owner) && self.owner == dog.script_owner )
|
|
return false;
|
|
|
|
heli_centroid = self.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( self.angles );
|
|
heli_turret_point = heli_centroid + 144*heli_forward_norm;
|
|
|
|
if (!isdefined(dog.lastHit))
|
|
dog.lastHit = 0;
|
|
|
|
dog.lastHit = self HeliTurretDogTrace( heli_turret_point, dog, dog.lastHit );
|
|
if ( dog.lastHit != 0 )
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
// targetability
|
|
function canTargetDog_missile( dog )
|
|
{
|
|
canTarget = true;
|
|
|
|
if ( !isdefined( dog ) )
|
|
return false;
|
|
|
|
if ( distance( dog.origin, self.origin ) > level.heli_missile_range )
|
|
return false;
|
|
|
|
if ( !isdefined( dog.team ) )
|
|
return false;
|
|
|
|
if ( level.teamBased && (dog.team == self.team) )
|
|
return false;
|
|
|
|
if ( isdefined(dog.script_owner) && self.owner == dog.script_owner )
|
|
return false;
|
|
|
|
// TODO
|
|
//should do a view cone to cut down on processing
|
|
|
|
heli_centroid = self.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( self.angles );
|
|
heli_turret_point = heli_centroid + 144*heli_forward_norm;
|
|
|
|
if (!isdefined(dog.lastHit))
|
|
dog.lastHit = 0;
|
|
|
|
dog.lastHit = self HeliTurretDogTrace( heli_turret_point, dog, dog.lastHit );
|
|
if (dog.lastHit != 0)
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
|
|
// targetability
|
|
function canTargetTank_turret( tank )
|
|
{
|
|
canTarget = true;
|
|
|
|
if ( !isdefined( tank ) )
|
|
return false;
|
|
|
|
if ( tank.ignoreme === true )
|
|
return false;
|
|
|
|
if ( distance( tank.origin, self.origin ) > level.heli_visual_range )
|
|
return false;
|
|
|
|
if ( !isdefined( tank.team ) )
|
|
return false;
|
|
|
|
if ( level.teamBased && (tank.team == self.team) )
|
|
return false;
|
|
|
|
if ( isdefined(tank.owner) && self.owner == tank.owner )
|
|
return false;
|
|
|
|
return canTarget;
|
|
}
|
|
|
|
// assign targets to primary and secondary
|
|
function assignPrimaryTargets( targets )
|
|
{
|
|
for( idx=0; idx<targets.size; idx++ )
|
|
{
|
|
if( isdefined( targets[idx].isAiClone ) )
|
|
{
|
|
killstreaks::update_actor_threat( targets[idx] );
|
|
}
|
|
else if ( isdefined( targets[idx].type ) && targets[idx].type == "dog" )
|
|
{
|
|
killstreaks::update_dog_threat( targets[idx] );
|
|
}
|
|
else if ( IsPlayer( targets[idx] ) )
|
|
{
|
|
killstreaks::update_player_threat( targets[idx] );
|
|
}
|
|
else
|
|
{
|
|
killstreaks::update_non_player_threat( targets[idx] );
|
|
}
|
|
}
|
|
|
|
assert( targets.size >= 2, "Not enough targets to assign primary and secondary" );
|
|
|
|
// find primary target, highest threat level
|
|
highest = 0;
|
|
second_highest = 0;
|
|
primaryTarget = undefined;
|
|
|
|
// find max and second max, 2n
|
|
for( idx=0; idx<targets.size; idx++ )
|
|
{
|
|
assert( isdefined( targets[idx].threatlevel ), "Target player does not have threat level" );
|
|
if( targets[idx].threatlevel >= highest )
|
|
{
|
|
highest = targets[idx].threatlevel;
|
|
primaryTarget = targets[idx];
|
|
}
|
|
}
|
|
|
|
assert( isdefined( primaryTarget ), "Targets exist, but none was assigned as primary" );
|
|
self.primaryTarget = primaryTarget;
|
|
self notify( "primary acquired" );
|
|
}
|
|
|
|
// assign targets to primary and secondary
|
|
function assignSecondaryTargets( targets )
|
|
{
|
|
for( idx = 0; idx < targets.size; idx++ )
|
|
{
|
|
if( !isdefined( targets[idx].type ) || targets[idx].type != "dog" )
|
|
{
|
|
self killstreaks::update_missile_player_threat ( targets[idx] );
|
|
}
|
|
else if( targets[idx].type == "dog" || targets[0].type == "tank_drone")
|
|
{
|
|
killstreaks::update_missile_dog_threat( targets[idx] );
|
|
}
|
|
}
|
|
|
|
assert( targets.size >= 2, "Not enough targets to assign primary and secondary" );
|
|
|
|
// find primary target, highest threat level
|
|
highest = 0;
|
|
second_highest = 0;
|
|
primaryTarget = undefined;
|
|
secondaryTarget = undefined;
|
|
|
|
// find max and second max, 2n
|
|
for( idx=0; idx<targets.size; idx++ )
|
|
{
|
|
assert( isdefined( targets[idx].missilethreatlevel ), "Target player does not have threat level" );
|
|
if( targets[idx].missilethreatlevel >= highest )
|
|
{
|
|
highest = targets[idx].missilethreatlevel;
|
|
secondaryTarget = targets[idx];
|
|
}
|
|
}
|
|
|
|
assert( isdefined( secondaryTarget ), "1+ targets exist, but none was assigned as secondary" );
|
|
self.secondaryTarget = secondaryTarget;
|
|
self notify( "secondary acquired" );
|
|
|
|
|
|
//TODO Maybe missiles do not target the ones on the primary list?
|
|
//assert( self.secondaryTarget != self.primaryTarget, "Primary and secondary targets are the same" );
|
|
}
|
|
|
|
// resets helicopter's motion values
|
|
function heli_reset()
|
|
{
|
|
self clearTargetYaw();
|
|
self clearGoalYaw();
|
|
self setspeed( 60, 25 );
|
|
self setyawspeed( 75, 45, 45 );
|
|
//self setjitterparams( (30, 30, 30), 4, 6 );
|
|
self setmaxpitchroll( 30, 30 );
|
|
self setneargoalnotifydist( 256 );
|
|
self setturningability(0.9);
|
|
}
|
|
|
|
function heli_wait( waittime )
|
|
{
|
|
self endon ( "death" );
|
|
self endon ( "crashing" );
|
|
self endon ( "evasive" );
|
|
|
|
self thread heli_hover();
|
|
wait( waittime );
|
|
heli_reset();
|
|
|
|
self notify( "stop hover" );
|
|
}
|
|
|
|
// hover movements
|
|
function heli_hover()
|
|
{
|
|
// stop hover when anything at all happens
|
|
self endon( "death" );
|
|
self endon( "stop hover" );
|
|
self endon( "evasive" );
|
|
self endon( "leaving" );
|
|
self endon( "crashing" );
|
|
randInt = randomint(360);
|
|
self setgoalyaw( self.angles[1]+randInt );
|
|
|
|
}
|
|
|
|
function wait_for_killed()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
self.bda = 0;
|
|
|
|
while(1)
|
|
{
|
|
self waittill( "killed", victim );
|
|
|
|
if ( !isdefined( self.owner ) || !isdefined( victim ) )
|
|
continue;
|
|
|
|
if ( self.owner == victim ) // killed himself
|
|
continue;
|
|
|
|
// no kill confirm on team kill. May want another VO.
|
|
if ( level.teamBased && self.owner.team == victim.team )
|
|
continue;
|
|
|
|
self thread wait_for_bda_timeout();
|
|
}
|
|
}
|
|
|
|
function wait_for_bda_timeout()
|
|
{
|
|
self endon( "killed" );
|
|
|
|
wait( 2.5 );
|
|
|
|
if ( !isdefined( self ) )
|
|
return;
|
|
|
|
self play_bda_dialog();
|
|
}
|
|
|
|
function play_bda_dialog( )
|
|
{
|
|
if (self.bda == 1)
|
|
{
|
|
bdaDialog = "kill1";
|
|
}
|
|
else if (self.bda == 2)
|
|
{
|
|
bdaDialog = "kill2";
|
|
}
|
|
else if (self.bda == 3)
|
|
{
|
|
bdaDialog = "kill3";
|
|
}
|
|
else if (self.bda > 3)
|
|
{
|
|
bdaDialog = "killMultiple";
|
|
}
|
|
|
|
self killstreaks::play_pilot_dialog_on_owner( bdaDialog, self.killstreakType, self.killstreak_id );
|
|
|
|
self notify( "bda_dialog", bdaDialog );
|
|
|
|
self.bda = 0;
|
|
}
|
|
|
|
function heli_hacked_health_update( hacker )
|
|
{
|
|
helicopter = self;
|
|
hackedDamageTaken = helicopter.maxhealth - helicopter.hackedHealth;
|
|
assert ( hackedDamageTaken > 0 );
|
|
if ( hackedDamageTaken > helicopter.damageTaken )
|
|
{
|
|
helicopter.damageTaken = hackedDamageTaken;
|
|
}
|
|
}
|
|
|
|
function heli_damage_monitor( hardpointtype )
|
|
{
|
|
helicopter = self;
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
|
|
self.damageTaken = 0;
|
|
|
|
last_hit_vo = 0;
|
|
hit_vo_spacing = 6000;
|
|
|
|
tableHealth = killstreak_bundles::get_max_health( hardpointtype );
|
|
if ( isdefined( tableHealth ) )
|
|
{
|
|
self.maxhealth = tableHealth;
|
|
}
|
|
|
|
helicopter.hackedHealthUpdateCallback = &heli_hacked_health_update;
|
|
helicopter.hackedHealth = killstreak_bundles::get_hacked_health( hardpointtype );
|
|
|
|
if ( !isdefined( self.attackerData ) )
|
|
{
|
|
self.attackers = [];
|
|
self.attackerData = [];
|
|
self.attackerDamage = [];
|
|
self.flareAttackerDamage = [];
|
|
}
|
|
|
|
for( ;; )
|
|
{
|
|
// this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is.
|
|
self waittill( "damage", damage, attacker, direction, point, type, modelName, tagName, partname, weapon, flags, inflictor, chargeLevel );
|
|
|
|
if( !isdefined( attacker ) || !isplayer( attacker ) )
|
|
continue;
|
|
|
|
heli_friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker );
|
|
// skip damage if friendlyfire is disabled
|
|
if( !heli_friendlyfire )
|
|
continue;
|
|
|
|
if ( !level.hardcoreMode )
|
|
{
|
|
if( isdefined( self.owner ) && attacker == self.owner )
|
|
continue;
|
|
|
|
if ( level.teamBased )
|
|
isValidAttacker = (isdefined( attacker.team ) && attacker.team != self.team);
|
|
else
|
|
isValidAttacker = true;
|
|
|
|
if ( !isValidAttacker )
|
|
continue;
|
|
}
|
|
|
|
self.attacker = attacker;
|
|
|
|
weapon_damage = killstreak_bundles::get_weapon_damage( hardpointType, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel );
|
|
|
|
if ( !isdefined( weapon_damage ) )
|
|
{
|
|
if ( type == "MOD_RIFLE_BULLET" || type == "MOD_PISTOL_BULLET" )
|
|
{
|
|
hasFMJ = attacker HasPerk( "specialty_armorpiercing" );
|
|
|
|
if ( hasFMJ )
|
|
{
|
|
damage += int( damage * level.cac_armorpiercing_data );
|
|
}
|
|
|
|
damage *= level.heli_armor_bulletdamage;
|
|
}
|
|
else if( type == "MOD_PROJECTILE" || type == "MOD_PROJECTILE_SPLASH" || type == "MOD_EXPLOSIVE" )
|
|
{
|
|
shouldUpdateDamage = ( ( weapon.statIndex != level.weaponPistolEnergy.statIndex )
|
|
&& ( weapon.statIndex != level.weaponSpecialCrossbow.statIndex )
|
|
&& ( weapon.statIndex != level.weaponSmgNailGun.statIndex ) );
|
|
|
|
if ( shouldUpdateDamage )
|
|
{
|
|
switch ( weapon.name )
|
|
{
|
|
case "tow_turret":
|
|
if( isdefined( self.rocketDamageTwoShot ) )
|
|
{
|
|
// 2 shot kill
|
|
damage = self.rocketDamageTwoShot;
|
|
}
|
|
else if( isdefined( self.rocketDamageOneShot ) )
|
|
{
|
|
// 1 shot kill
|
|
damage = self.rocketDamageOneShot;
|
|
}
|
|
break;
|
|
default:
|
|
if( isdefined( self.rocketDamageOneShot ) )
|
|
{
|
|
// 1 shot kill
|
|
damage = self.rocketDamageOneShot;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
weapon_damage = damage;
|
|
}
|
|
|
|
if ( weapon_damage > 0 )
|
|
self challenges::trackAssists( attacker, weapon_damage, false );
|
|
|
|
self.damageTaken += weapon_damage;
|
|
|
|
playerControlled = false;
|
|
|
|
if( self.damageTaken > self.maxhealth && !isdefined(self.xpGiven) )
|
|
{
|
|
self.xpGiven = true;
|
|
|
|
switch( hardpointtype )
|
|
{
|
|
case "helicopter_gunner":
|
|
playerControlled = true;
|
|
event = "destroyed_vtol_mothership";
|
|
break;
|
|
case "helicopter_comlink":
|
|
case "inventory_helicopter_comlink":
|
|
event = "destroyed_helicopter_comlink";
|
|
if ( self.leaving !== true )
|
|
{
|
|
self killstreaks::play_destroyed_dialog_on_owner( self.killstreakType, self.killstreak_id );
|
|
}
|
|
break;
|
|
case "supply_drop":
|
|
case "supply_drop_combat_robot":
|
|
if( isdefined( helicopter.killstreakWeaponName ) )
|
|
{
|
|
switch( helicopter.killstreakWeaponName )
|
|
{
|
|
case "inventory_ai_tank_drop":
|
|
case "ai_tank_drop":
|
|
case "inventory_ai_tank_marker":
|
|
case "ai_tank_drop_marker":
|
|
case "ai_tank_marker":
|
|
{
|
|
event = "destroyed_helicopter_agr_drop";
|
|
}
|
|
break;
|
|
case "combat_robot_drop":
|
|
case "inventory_combat_robot_drop":
|
|
case "combat_robot_marker":
|
|
case "inventory_combat_robot_marker":
|
|
{
|
|
event = "destroyed_helicopter_giunit_drop";
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
event = "destroyed_helicopter_supply_drop";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
event = "destroyed_helicopter_supply_drop";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( isdefined( event ) )
|
|
{
|
|
if( isdefined( self.owner ) && self.owner util::IsEnemyPlayer( attacker ) )
|
|
{
|
|
challenges::destroyedHelicopter( attacker, weapon, type, false );
|
|
challenges::destroyedAircraft( attacker, weapon, playerControlled );
|
|
scoreevents::processScoreEvent( event, attacker, self.owner, weapon );
|
|
attacker challenges::addFlySwatterStat( weapon, self );
|
|
if ( playerControlled == true )
|
|
{
|
|
attacker challenges::destroyedPlayerControlledAircraft();
|
|
}
|
|
if ( hardpointtype == "helicopter_player_gunner" )
|
|
{
|
|
attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Destroyed Friendly Killstreak
|
|
}
|
|
}
|
|
|
|
// do give stats to killstreak weapons
|
|
// we need the kill stat so that we know how many kills the weapon has gotten (even on helicopters) for challenges
|
|
// we need the destroyed stat for the same reason as the kill stat and these have different challenges associated
|
|
//attacker _properks::destroyedKillstreak();
|
|
|
|
weaponStatName = "destroyed";
|
|
switch( weapon.name )
|
|
{
|
|
// SAM Turrets keep the kills stat for shooting things down because we used destroyed for when you destroy a SAM Turret
|
|
case "auto_tow":
|
|
case "tow_turret":
|
|
case "tow_turret_drop":
|
|
weaponStatName = "kills";
|
|
break;
|
|
}
|
|
attacker AddWeaponStat( weapon, weaponStatName, 1 );
|
|
|
|
notifyString = undefined;
|
|
killstreakReference = undefined;
|
|
switch( hardpointtype )
|
|
{
|
|
case "helicopter_gunner":
|
|
killstreakReference = "killstreak_helicopter_gunner";
|
|
break;
|
|
case "helicopter_player_gunner":
|
|
killstreakReference = "killstreak_helicopter_player_gunner";
|
|
break;
|
|
case "helicopter_player_firstperson":
|
|
killstreakReference = "killstreak_helicopter_player_firstperson";
|
|
break;
|
|
case "helicopter_comlink":
|
|
case "inventory_helicopter_comlink":
|
|
case "helicopter":
|
|
case "helicopter_x2":
|
|
notifyString = &"KILLSTREAK_DESTROYED_HELICOPTER";
|
|
killstreakReference = "killstreak_helicopter_comlink";
|
|
break;
|
|
case "supply_drop":
|
|
notifyString = &"KILLSTREAK_DESTROYED_SUPPLY_DROP_DEPLOY_SHIP";
|
|
killstreakReference = "killstreak_supply_drop";
|
|
break;
|
|
case "helicopter_guard":
|
|
killstreakReference = "killstreak_helicopter_guard";
|
|
}
|
|
|
|
// increment the destroyed stat for this, we aren't using the weaponStatName variable from above because it could be "kills" and we don't want that
|
|
if( isdefined( killstreakReference ) )
|
|
{
|
|
level.globalKillstreaksDestroyed++;
|
|
attacker AddWeaponStat( GetWeapon( hardpointtype ), "destroyed", 1 );
|
|
}
|
|
|
|
if( hardpointtype == "helicopter_player_gunner" )
|
|
{
|
|
self.owner SendKillstreakDamageEvent( 600 );
|
|
}
|
|
|
|
if ( isdefined( notifyString ) )
|
|
{
|
|
LUINotifyEvent( &"player_callout", 2, notifyString, attacker.entnum );
|
|
}
|
|
|
|
if ( isdefined( self.attackers ) )
|
|
{
|
|
for ( j = 0; j < self.attackers.size; j++ )
|
|
{
|
|
player = self.attackers[j];
|
|
|
|
if ( !isdefined( player ) )
|
|
continue;
|
|
|
|
if ( player == attacker )
|
|
continue;
|
|
|
|
flare_done = self.flareAttackerDamage[player.clientId];
|
|
if ( isdefined ( flare_done ) && flare_done == true )
|
|
{
|
|
scoreevents::processScoreEvent( "aircraft_flare_assist", player );
|
|
}
|
|
else
|
|
{
|
|
damage_done = self.attackerDamage[player.clientId];
|
|
player thread processCopterAssist( self, damage_done);
|
|
}
|
|
}
|
|
self.attackers = [];
|
|
}
|
|
attacker notify( "destroyed_helicopter" );
|
|
|
|
if( Target_IsTarget( self ) )
|
|
{
|
|
Target_remove( self );
|
|
}
|
|
}
|
|
else if ( isdefined( self.owner ) && IsPlayer( self.owner ) )
|
|
{
|
|
if ( last_hit_vo + hit_vo_spacing < GetTime() )
|
|
{
|
|
if ( type == "MOD_PROJECTILE" || RandomIntRange(0,3) == 0 )
|
|
{
|
|
// TODO CDC - change to new pilot dialog
|
|
//self.owner PlayLocalSound(level.heli_vo[self.team]["hit"]);
|
|
last_hit_vo = GetTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ( hardpointtype == "helicopter_comlink" ) || ( hardpointtype == "inventory_helicopter_comlink" ) )
|
|
{
|
|
self thread heli_active_camo_damage_update( damage );
|
|
}
|
|
}
|
|
}
|
|
|
|
function init_active_camo()
|
|
{
|
|
heli = self;
|
|
heli.active_camo_supported = true;
|
|
heli.active_camo_damage = 0;
|
|
heli.active_camo_disabled = false;
|
|
heli.camo_state = ( 0 );
|
|
heli_set_active_camo_state( ( 1 ) );
|
|
|
|
if( isdefined( heli.flak_drone ) )
|
|
{
|
|
heli.flak_drone flak_drone::SetCamoState( ( 1 ) );
|
|
}
|
|
}
|
|
|
|
function heli_set_active_camo_state( state )
|
|
{
|
|
heli = self;
|
|
|
|
if( !isdefined( heli.active_camo_supported ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( state == ( 0 ) )
|
|
{
|
|
//Target_Set( heli, heli.target_offset );
|
|
heli clientfield::set( "toggle_lights", 1 );
|
|
if( heli.camo_state == ( 1 ) )
|
|
heli playsound ("veh_hind_cloak_off");
|
|
heli.camo_state = ( 0 );
|
|
heli.camo_state_switch_time = gettime();
|
|
}
|
|
else if( state == ( 1 ) )
|
|
{
|
|
if( heli.active_camo_disabled )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//if( Target_IsTarget( heli ) )
|
|
//{
|
|
//Target_Remove( heli );
|
|
//}
|
|
|
|
heli clientfield::set( "toggle_lights", 0 );
|
|
|
|
if( heli.camo_state == ( 0 ) )
|
|
heli playsound ("veh_hind_cloak_on");
|
|
heli.camo_state = ( 1 );
|
|
heli.camo_state_switch_time = gettime();
|
|
|
|
if ( isdefined( heli.owner ) )
|
|
{
|
|
if ( isdefined( heli.play_camo_dialog ) && heli.play_camo_dialog )
|
|
{
|
|
heli killstreaks::play_pilot_dialog_on_owner( "activateCounter", "helicopter_comlink", self.killstreak_id );
|
|
// Only play the cloak line once
|
|
heli.play_camo_dialog = false;
|
|
}
|
|
else if ( !isdefined( heli.play_camo_dialog ) )
|
|
{
|
|
// Don't play camo dialog when initializing the copter
|
|
heli.play_camo_dialog = true;
|
|
}
|
|
}
|
|
}
|
|
else if( state == ( 2 ) )
|
|
{
|
|
heli clientfield::set( "toggle_lights", 1 );
|
|
}
|
|
|
|
if( isdefined( heli.flak_drone ) )
|
|
{
|
|
heli.flak_drone flak_drone::SetCamoState( state );
|
|
}
|
|
|
|
heli clientfield::set( "active_camo", state );
|
|
}
|
|
|
|
function heli_active_camo_damage_update( damage )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
|
|
heli = self;
|
|
|
|
heli.active_camo_damage += damage;
|
|
|
|
if( heli.active_camo_damage > ( 100 ) )
|
|
{
|
|
heli.active_camo_disabled = true;
|
|
heli thread heli_active_camo_damage_disable();
|
|
}
|
|
else
|
|
{
|
|
heli heli_set_active_camo_state( ( 2 ) );
|
|
wait( ( 1 ) );
|
|
heli heli_set_active_camo_state( ( 1 ) );
|
|
}
|
|
}
|
|
|
|
function heli_active_camo_damage_disable()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
|
|
heli = self;
|
|
heli notify( "heli_active_camo_damage_disable" );
|
|
heli endon( "heli_active_camo_damage_disable" );
|
|
|
|
heli heli_set_active_camo_state( ( 0 ) );
|
|
|
|
wait( ( 10 ) );
|
|
|
|
heli.active_camo_damage = 0;
|
|
heli.active_camo_disabled = false;
|
|
heli heli_set_active_camo_state( ( 1 ) );
|
|
}
|
|
|
|
function heli_health( hardpointType, playerNotify )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
|
|
self.currentstate = "ok";
|
|
self.laststate = "ok";
|
|
self setdamagestage( 3 );
|
|
damageState = 3;
|
|
|
|
tableHealth = killstreak_bundles::get_max_health( hardpointType );
|
|
|
|
if ( isdefined( tableHealth ) )
|
|
{
|
|
self.maxhealth = tableHealth;
|
|
}
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill( "damage", damage, attacker, direction, point, type, modelName, tagName, partname, weapon );
|
|
{wait(.05);};
|
|
|
|
if( self.damageTaken > self.maxhealth )
|
|
{
|
|
damageState = 0;
|
|
self setDamageStage( damageState );
|
|
|
|
self heli_set_active_camo_state( ( 0 ) );
|
|
|
|
self thread heli_crash( hardpointType, self.owner, playerNotify );
|
|
}
|
|
else if ( self.damageTaken >= (self.maxhealth * 0.66) && damageState >= 2 )
|
|
{
|
|
self killstreaks::play_pilot_dialog_on_owner( "damaged", "helicopter_comlink", self.killstreak_id );
|
|
|
|
//self setdamagestage( 1 );
|
|
if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" )
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_origin");
|
|
}
|
|
else
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["damage"]["heavy_smoke"], self, "tag_main_rotor");
|
|
}
|
|
damageState = 1;
|
|
self.currentstate = "heavy smoke";
|
|
self.evasive = true;
|
|
self notify("damage state");
|
|
}
|
|
else if ( self.damageTaken >= (self.maxhealth * 0.33) && damageState == 3 )
|
|
{
|
|
//self setdamagestage( 2 );
|
|
if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" )
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_origin");
|
|
}
|
|
else
|
|
{
|
|
PlayFXOnTag( level.chopper_fx["damage"]["light_smoke"], self, "tag_main_rotor");
|
|
}
|
|
damageState = 2;
|
|
self.currentstate = "light smoke";
|
|
self notify("damage state");
|
|
}
|
|
|
|
// debug =================================
|
|
if( self.damageTaken <= level.heli_armor )
|
|
airsupport::debug_print3d_simple( "Armor: " + (level.heli_armor-self.damageTaken), self, ( 0,0,100 ), 20 );
|
|
else
|
|
airsupport::debug_print3d_simple( "Health: " + ( self.maxhealth - self.damageTaken ), self, ( 0,0,100 ), 20 );
|
|
|
|
}
|
|
}
|
|
|
|
// evasive manuvering - helicopter circles the map for awhile then returns to path
|
|
function heli_evasive( hardpointType )
|
|
{
|
|
// only one instance allowed
|
|
self notify( "evasive" );
|
|
|
|
self.evasive = true;
|
|
|
|
// set helicopter path to circle the map level.heli_loopmax number of times
|
|
loop_startnode = level.heli_loop_paths[0];
|
|
|
|
gunnerPathFound = true;
|
|
if ( hardpointType == "helicopter_gunner" )
|
|
{
|
|
gunnerPathFound = false;
|
|
for ( i = 0 ; i < level.heli_loop_paths.size ; i++ )
|
|
{
|
|
if ( isdefined( level.heli_loop_paths[i].isGunnerPath ) && level.heli_loop_paths[i].isGunnerPath )
|
|
{
|
|
loop_startnode = level.heli_loop_paths[i];
|
|
gunnerPathFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert( gunnerPathFound, "No chopper gunner loop paths found in map" );
|
|
|
|
startwait = 2;
|
|
if ( isdefined( self.doNotStop ) && self.doNotStop )
|
|
startwait = 0;
|
|
|
|
self thread heli_fly( loop_startnode, startwait, hardpointType );
|
|
}
|
|
|
|
function notify_player( player, playerNotify, delay )
|
|
{
|
|
if ( !isdefined(player) )
|
|
return;
|
|
|
|
if ( !isdefined(playerNotify) )
|
|
return;
|
|
|
|
player endon( "disconnect" );
|
|
player endon( playerNotify );
|
|
|
|
wait (delay);
|
|
|
|
player notify( playerNotify );
|
|
}
|
|
|
|
function play_going_down_vo( delay )
|
|
{
|
|
self.owner endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
wait (delay);
|
|
}
|
|
|
|
// attach helicopter on crash path
|
|
function heli_crash( hardpointType, player, playerNotify )
|
|
{
|
|
self endon( "death" );
|
|
self notify( "crashing" );
|
|
|
|
self spawning::remove_influencers();
|
|
|
|
self stoploopsound(0);
|
|
if( isdefined( self.minigun_snd_ent ) )
|
|
{
|
|
self.minigun_snd_ent StopLoopSound();
|
|
}
|
|
if( isdefined( self.alarm_snd_ent ) )
|
|
{
|
|
self.alarm_snd_ent StopLoopSound();
|
|
}
|
|
|
|
//TODO : Play crash alert vo CDC
|
|
|
|
// these types are for the chopper and player controlled heli
|
|
crashTypes = [];
|
|
crashTypes[0] = "crashOnPath";
|
|
crashTypes[1] = "spinOut";
|
|
|
|
crashType = crashTypes[randomInt(2)];
|
|
|
|
// ai chopper should just explode
|
|
if ( isdefined( self.crashType ) )
|
|
crashType = self.crashType;
|
|
|
|
/#
|
|
if ( level.heli_debug_crash )
|
|
{
|
|
switch( level.heli_debug_crash )
|
|
{
|
|
case 1:
|
|
crashType = "explode";
|
|
break;
|
|
case 2:
|
|
crashType = "crashOnPath";
|
|
break;
|
|
case 3:
|
|
crashType = "spinOut";
|
|
break;
|
|
default:
|
|
};
|
|
}
|
|
#/
|
|
|
|
switch (crashType)
|
|
{
|
|
case "explode":
|
|
{
|
|
thread notify_player( player, playerNotify, 0 );
|
|
self thread heli_explode();
|
|
}
|
|
break;
|
|
case "crashOnPath":
|
|
{
|
|
if ( isdefined( player ) )
|
|
self thread play_going_down_vo( 0.5 );
|
|
|
|
thread notify_player( player, playerNotify, 4 );
|
|
self clear_client_flags();
|
|
self thread crashOnNearestCrashPath( hardpointType );
|
|
}
|
|
break;
|
|
case "spinOut":
|
|
{
|
|
if ( isdefined( player ) )
|
|
self thread play_going_down_vo( 0.5 );
|
|
thread notify_player( player, playerNotify, 4 );
|
|
self clear_client_flags();
|
|
|
|
heli_reset();
|
|
|
|
heli_speed = 30+randomInt(50);
|
|
heli_accel = 10+randomInt(25);
|
|
|
|
// helicopter leaves randomly towards one of the leave origins
|
|
leavenode = getValidRandomCrashNode( self.origin );
|
|
// movement change due to damage
|
|
self setspeed( heli_speed, heli_accel );
|
|
self set_goal_pos( (leavenode.origin), 0 );
|
|
|
|
rateOfSpin = 45 + randomint(90);
|
|
|
|
thread heli_secondary_explosions();
|
|
|
|
// helicopter losing control and spins
|
|
self thread heli_spin( rateOfSpin );
|
|
//TODO : pilot call in VO
|
|
|
|
|
|
self util::waittill_any_timeout( RandomIntRange(4, 6), "near_goal" ); //self waittillmatch( "goal" );
|
|
|
|
if ( isdefined( player ) && isdefined( playerNotify ) )
|
|
player notify( playerNotify ); // make sure
|
|
self thread heli_explode();
|
|
}
|
|
break;
|
|
}
|
|
|
|
self thread explodeOnContact( hardpointtype );
|
|
|
|
time = randomIntRange(4, 6);
|
|
self thread waitThenExplode( time );
|
|
}
|
|
|
|
function damagedRotorFX()
|
|
{
|
|
self endon ( "death" );
|
|
self SetRotorSpeed( 0.6 );
|
|
}
|
|
|
|
function waitThenExplode( time )
|
|
{
|
|
self endon( "death" );
|
|
|
|
wait( time );
|
|
|
|
self thread heli_explode();
|
|
}
|
|
|
|
function crashOnNearestCrashPath( hardpointType )
|
|
{
|
|
crashPathDistance = -1;
|
|
crashPath = level.heli_crash_paths[0];
|
|
for ( i = 0; i < level.heli_crash_paths.size; i++ )
|
|
{
|
|
currentDistance = distance(self.origin, level.heli_crash_paths[i].origin);
|
|
if ( crashPathDistance == -1 || crashPathDistance > currentDistance )
|
|
{
|
|
crashPathDistance = currentDistance;
|
|
crashPath = level.heli_crash_paths[i];
|
|
}
|
|
}
|
|
|
|
heli_speed = 30+randomInt(50);
|
|
heli_accel = 10+randomInt(25);
|
|
|
|
// movement change due to damage
|
|
self setspeed( heli_speed, heli_accel );
|
|
|
|
thread heli_secondary_explosions();
|
|
|
|
// fly to crash path
|
|
self thread heli_fly( crashPath, 0, hardpointType );
|
|
|
|
rateOfSpin = 45 + randomint(90);
|
|
|
|
// helicopter losing control and spins
|
|
self thread heli_spin( rateOfSpin );
|
|
|
|
// wait until helicopter is on the crash path
|
|
self waittill ( "path start" );
|
|
|
|
|
|
self waittill( "destination reached" );
|
|
self thread heli_explode();
|
|
}
|
|
|
|
//This is a temporary solution for playing fx while we are switching to new models with new tag naming conventions.
|
|
function CheckHelicopterTag( tagName )
|
|
{
|
|
if( isdefined( self.model ) )
|
|
{
|
|
if( self.model == "veh_t7_drone_hunter" )
|
|
{
|
|
switch( tagName )
|
|
{
|
|
case "tag_engine_left":
|
|
return "tag_fx_exhaust2";
|
|
case "tag_engine_right":
|
|
return "tag_fx_exhaust1";
|
|
case "tail_rotor_jnt":
|
|
return "tag_fx_tail";
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tagName;
|
|
}
|
|
|
|
function heli_secondary_explosions()
|
|
{
|
|
self endon( "death" );
|
|
|
|
playFxOnTag( level.chopper_fx["explode"]["large"], self, self CheckHelicopterTag( "tag_engine_left" ) );
|
|
// self playSound ( level.heli_sound["hitsecondary"] );
|
|
self playSound ( level.heli_sound["hit"] );
|
|
|
|
// form smoke trails on tail after explosion
|
|
if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" )
|
|
{
|
|
self thread trail_fx( level.chopper_fx["smoke"]["trail"], self CheckHelicopterTag( "tag_engine_right" ), "stop tail smoke" );
|
|
}
|
|
else
|
|
{
|
|
self thread trail_fx( level.chopper_fx["smoke"]["trail"], self CheckHelicopterTag( "tail_rotor_jnt" ), "stop tail smoke" );
|
|
}
|
|
|
|
self setdamagestage( 0 );
|
|
// form fire smoke trails on body after explosion
|
|
self thread trail_fx( level.chopper_fx["fire"]["trail"]["large"], self CheckHelicopterTag( "tag_engine_left" ), "stop body fire" );
|
|
|
|
wait ( 3.0 );
|
|
|
|
if ( !isdefined( self ) )
|
|
return;
|
|
|
|
playFxOnTag( level.chopper_fx["explode"]["large"], self, self CheckHelicopterTag( "tag_engine_left" ) );
|
|
self playSound ( level.heli_sound["hitsecondary"] );
|
|
}
|
|
|
|
// self spin at one rev per 2 sec
|
|
function heli_spin( speed )
|
|
{
|
|
self endon( "death" );
|
|
|
|
// tail explosion that caused the spinning
|
|
// playfxontag( level.chopper_fx["explode"]["medium"], self, "tail_rotor_jnt" );
|
|
// play hit sound immediately so players know they got it
|
|
|
|
// play heli crashing spinning sound
|
|
self thread spinSoundShortly();
|
|
|
|
// spins until death
|
|
self setyawspeed( speed, speed / 3 , speed / 3 );
|
|
while ( isdefined( self ) )
|
|
{
|
|
self settargetyaw( self.angles[1]+(speed*0.9) );
|
|
wait ( 1 );
|
|
}
|
|
}
|
|
|
|
function spinSoundShortly()
|
|
{
|
|
self endon( "death" );
|
|
|
|
wait .25;
|
|
|
|
self stopLoopSound();
|
|
wait .05;
|
|
self playLoopSound( level.heli_sound["spinloop"] );
|
|
wait .05;
|
|
self playSound( level.heli_sound["spinstart"] );
|
|
}
|
|
|
|
// TO DO: Robert will replace the for-loop to use geotrails for smoke trail fx
|
|
// this plays single smoke trail puff on origin per 0.05
|
|
// trail_fx is the fx string, trail_tag is the tag string
|
|
function trail_fx( trail_fx, trail_tag, stop_notify )
|
|
{
|
|
// only one instance allowed
|
|
// self notify( stop_notify );
|
|
// self endon( stop_notify );
|
|
// self endon( "death" );
|
|
|
|
// for ( ;; )
|
|
{
|
|
playfxontag( trail_fx, self, trail_tag );
|
|
// WAIT_SERVER_FRAME;
|
|
}
|
|
}
|
|
|
|
function destroyHelicopter()
|
|
{
|
|
team = self.originalteam;
|
|
|
|
if ( Target_IsTarget(self) )
|
|
Target_remove( self );
|
|
|
|
self spawning::remove_influencers();
|
|
|
|
if( isdefined( self.interior_model ) )
|
|
{
|
|
self.interior_model Delete();
|
|
self.interior_model = undefined;
|
|
}
|
|
if( isdefined( self.minigun_snd_ent ) )
|
|
{
|
|
self.minigun_snd_ent StopLoopSound();
|
|
self.minigun_snd_ent Delete();
|
|
self.minigun_snd_ent = undefined;
|
|
}
|
|
if( isdefined( self.alarm_snd_ent ) )
|
|
{
|
|
self.alarm_snd_ent Delete();
|
|
self.alarm_snd_ent = undefined;
|
|
}
|
|
if ( isdefined( self.flare_ent ) )
|
|
{
|
|
self.flare_ent Delete();
|
|
self.flare_ent = undefined;
|
|
}
|
|
|
|
killstreakrules::killstreakStop( self.hardpointType, team, self.killstreak_id );
|
|
|
|
self delete();
|
|
}
|
|
|
|
// crash explosion
|
|
function heli_explode()
|
|
{
|
|
self endon( "death" );
|
|
|
|
forward = ( self.origin + ( 0, 0, 100 ) ) - self.origin;
|
|
if( isdefined(self.helitype) && self.helitype == "littlebird" )
|
|
{
|
|
playfx( level.chopper_fx["explode"]["guard"], self.origin, forward );
|
|
}
|
|
else if ( isdefined( self.vehicletype ) && self.vehicletype == "heli_player_gunner_mp" )
|
|
{
|
|
playfx( level.chopper_fx["explode"]["gunner"], self.origin, forward );
|
|
}
|
|
else
|
|
{
|
|
playfx( level.chopper_fx["explode"]["death"], self.origin, forward );
|
|
}
|
|
|
|
// play heli explosion sound
|
|
self PlaySound ( level.heli_sound["crash"] );
|
|
|
|
wait( 0.1 );
|
|
|
|
assert( isdefined( self.destroyFunc ) );
|
|
self [[self.destroyFunc]]();
|
|
}
|
|
|
|
function clear_client_flags()
|
|
{
|
|
self clientfield::set( "heli_warn_fired", 0 );
|
|
self clientfield::set( "heli_warn_targeted", 0 );
|
|
self clientfield::set( "heli_warn_locked", 0 );
|
|
}
|
|
|
|
// helicopter leaving parameter, can not be damaged while leaving
|
|
function heli_leave()
|
|
{
|
|
self notify( "destintation reached" );
|
|
self notify( "leaving" );
|
|
|
|
hardpointType = self.hardpointType;
|
|
|
|
self.leaving = true;
|
|
|
|
if( !( isdefined( self.detroyScoreEventGiven ) && self.detroyScoreEventGiven ) )
|
|
{
|
|
self killstreaks::play_pilot_dialog_on_owner( "timeout", hardpointType );
|
|
self killstreaks::play_taacom_dialog_response_on_owner( "timeoutConfirmed", hardpointType );
|
|
}
|
|
|
|
//self thread playpilotdialog ("attackheli_leave", 2.5 );
|
|
|
|
//self clear_client_flags();
|
|
|
|
// helicopter leaves randomly towards one of the leave origins
|
|
leavenode = getValidRandomLeaveNode( self.origin );
|
|
|
|
heli_reset();
|
|
self ClearLookAtEnt();
|
|
exitAngles = VectorToAngles(leavenode.origin - self.origin);
|
|
self SetGoalYaw( exitAngles[1] );
|
|
wait ( 1.5 );
|
|
|
|
if ( !isdefined( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self setspeed( 180, 65 );
|
|
|
|
/# self util::debug_slow_heli_speed(); #/
|
|
|
|
self set_goal_pos( self.origin + ( leavenode.origin - self.origin ) / 2 + ( 0, 0, 1000 ), false );
|
|
self waittill( "near_goal" );
|
|
if( isdefined( self ) )
|
|
{
|
|
self set_goal_pos( leavenode.origin, true );
|
|
self waittillmatch( "goal" );
|
|
if( isdefined( self ) )
|
|
{
|
|
self stoploopsound(1);
|
|
self util::death_notify_wrapper();
|
|
if( isdefined( self.alarm_snd_ent ) )
|
|
{
|
|
self.alarm_snd_ent StopLoopSound();
|
|
self.alarm_snd_ent Delete();
|
|
self.alarm_snd_ent = undefined;
|
|
}
|
|
|
|
assert( isdefined( self.destroyFunc ) );
|
|
self [[self.destroyFunc]]();
|
|
}
|
|
}
|
|
}
|
|
|
|
// flys helicopter from given start node to a destination on its path
|
|
function heli_fly( currentnode, startwait, hardpointType )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "leaving" );
|
|
|
|
// only one thread instance allowed
|
|
self notify( "flying" );
|
|
self endon( "flying" );
|
|
|
|
// if owner switches teams, helicopter should leave
|
|
self endon( "abandoned" );
|
|
|
|
self.reached_dest = false;
|
|
heli_reset();
|
|
|
|
pos = self.origin;
|
|
wait( startwait );
|
|
|
|
while ( isdefined( currentnode.target ) )
|
|
{
|
|
nextnode = getent( currentnode.target, "targetname" );
|
|
assert( isdefined( nextnode ), "Next node in path is undefined, but has targetname" );
|
|
|
|
// offsetted
|
|
pos = nextnode.origin+(0,0,30);
|
|
|
|
// motion change via node
|
|
if( isdefined( currentnode.script_airspeed ) && isdefined( currentnode.script_accel ) )
|
|
{
|
|
heli_speed = currentnode.script_airspeed;
|
|
heli_accel = currentnode.script_accel;
|
|
}
|
|
else
|
|
{
|
|
heli_speed = 30+randomInt(20);
|
|
heli_accel = 10+randomInt(5);
|
|
}
|
|
|
|
if ( isdefined( self.pathSpeedScale ) )
|
|
{
|
|
heli_speed *= self.pathSpeedScale;
|
|
heli_accel *= self.pathSpeedScale;
|
|
}
|
|
|
|
// fly nonstop until final destination
|
|
if ( !isdefined( nextnode.target ) )
|
|
stop = 1;
|
|
else
|
|
stop = 0;
|
|
|
|
// debug ==============================================================
|
|
airsupport::debug_line( currentnode.origin, nextnode.origin, ( 1, 0.5, 0.5 ), 200 );
|
|
|
|
// if in damaged state, do not stop at any node other than destination
|
|
if( self.currentstate == "heavy smoke" || self.currentstate == "light smoke" )
|
|
{
|
|
// movement change due to damage
|
|
self setspeed( heli_speed, heli_accel );
|
|
self set_goal_pos( (pos), stop );
|
|
|
|
self waittill( "near_goal" ); //self waittillmatch( "goal" );
|
|
self notify( "path start" );
|
|
}
|
|
else
|
|
{
|
|
// if the node has helicopter stop time value, we stop
|
|
if( isdefined( nextnode.script_delay ) && !isdefined( self.doNotStop ) )
|
|
stop = 1;
|
|
|
|
self setspeed( heli_speed, heli_accel );
|
|
self set_goal_pos( (pos), stop );
|
|
|
|
if ( !isdefined( nextnode.script_delay ) || isdefined( self.doNotStop ) )
|
|
{
|
|
self waittill( "near_goal" ); //self waittillmatch( "goal" );
|
|
self notify( "path start" );
|
|
}
|
|
else
|
|
{
|
|
// post beta addition --- (
|
|
self setgoalyaw( nextnode.angles[1] );
|
|
// post beta addition --- )
|
|
|
|
self waittillmatch( "goal" );
|
|
heli_wait( nextnode.script_delay );
|
|
}
|
|
}
|
|
|
|
// increment loop count when helicopter is circling the map
|
|
for( index = 0; index < level.heli_loop_paths.size; index++ )
|
|
{
|
|
if ( level.heli_loop_paths[index].origin == nextnode.origin )
|
|
self.loopcount++;
|
|
}
|
|
if( self.loopcount >= level.heli_loopmax )
|
|
{
|
|
self thread heli_leave();
|
|
return;
|
|
}
|
|
currentnode = nextnode;
|
|
}
|
|
|
|
self setgoalyaw( currentnode.angles[1] );
|
|
self.reached_dest = true; // sets flag true for helicopter circling the map
|
|
self notify ( "destination reached" );
|
|
// wait at destination
|
|
if ( isdefined( self.waittime ) && self.waittime > 0 )
|
|
heli_wait( self.waittime );
|
|
|
|
// if still alive, switch to evasive manuvering
|
|
if( isdefined( self ) )
|
|
self thread heli_evasive( hardpointType );
|
|
}
|
|
|
|
function heli_random_point_in_radius( protectDest, nodeHeight )
|
|
{
|
|
min_distance = Int(level.heli_protect_radius * .2);
|
|
direction = randomintrange(0,360);
|
|
distance = randomintrange(min_distance, level.heli_protect_radius);
|
|
|
|
x = cos(direction);
|
|
y = sin(direction);
|
|
x = x * distance;
|
|
y = y * distance;
|
|
|
|
return (protectDest[0] + x, protectDest[1] + y, nodeHeight);
|
|
}
|
|
|
|
function heli_get_protect_spot(protectDest, nodeHeight)
|
|
{
|
|
protect_spot = heli_random_point_in_radius( protectDest, nodeHeight );
|
|
|
|
tries = 10;
|
|
noFlyZone = airsupport::crossesNoFlyZone( protectDest, protect_spot );
|
|
while( tries != 0 && isdefined( noFlyZone ) )
|
|
{
|
|
protect_spot = heli_random_point_in_radius( protectDest, nodeHeight );
|
|
tries--;
|
|
noFlyZone = airsupport::crossesNoFlyZone( protectDest, protect_spot );
|
|
}
|
|
|
|
noFlyZoneHeight = airsupport::getNoFlyZoneHeightCrossed( protectDest, protect_spot, nodeHeight );
|
|
return ( protect_spot[0], protect_spot[1], noFlyZoneHeight );
|
|
}
|
|
|
|
function wait_or_waittill( time, msg1, msg2, msg3 )
|
|
{
|
|
self endon( msg1 );
|
|
self endon( msg2 );
|
|
self endon( msg3 );
|
|
wait( time );
|
|
return true;
|
|
}
|
|
|
|
function set_heli_speed_normal()
|
|
{
|
|
self setmaxpitchroll( 30, 30 );
|
|
heli_speed = 30+randomInt(20);
|
|
heli_accel = 10+randomInt(5);
|
|
self setspeed( heli_speed, heli_accel );
|
|
self setyawspeed( 75, 45, 45 );
|
|
}
|
|
|
|
function set_heli_speed_evasive()
|
|
{
|
|
self setmaxpitchroll( 30, 90 );
|
|
heli_speed = 50+randomInt(20);
|
|
heli_accel = 30+randomInt(5);
|
|
self setspeed( heli_speed, heli_accel );
|
|
self setyawspeed( 100, 75, 75 );
|
|
}
|
|
|
|
function set_heli_speed_hover()
|
|
{
|
|
self setmaxpitchroll( 0, 90 );
|
|
self setspeed( 20, 10 );
|
|
self setyawspeed( 55, 25, 25 );
|
|
}
|
|
|
|
|
|
function is_targeted()
|
|
{
|
|
if ( isdefined(self.locking_on) && self.locking_on )
|
|
return true;
|
|
|
|
if ( isdefined(self.locked_on) && self.locked_on )
|
|
return true;
|
|
|
|
if ( isdefined(self.locking_on_hacking) && self.locking_on_hacking )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function heli_mobilespawn( protectDest )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self notify( "flying" );
|
|
self endon( "flying" );
|
|
|
|
self endon( "abandoned" );
|
|
|
|
IPrintLnBold( "PROTECT ORIGIN: ("+protectDest[0]+","+protectDest[1]+","+protectDest[2]+")\n" );
|
|
|
|
heli_reset();
|
|
|
|
self SetHoverParams( 50, 100, 50 );
|
|
|
|
wait( 2 );
|
|
|
|
set_heli_speed_normal();
|
|
|
|
self set_goal_pos( protectDest, 1 );
|
|
|
|
self waittill( "near_goal" );
|
|
|
|
set_heli_speed_hover();
|
|
}
|
|
|
|
// flys helicopter from given start node to a destination on its path
|
|
function heli_protect( startNode, protectDest, hardpointType, heli_team )
|
|
{
|
|
self endon( "death" );
|
|
|
|
// only one thread instance allowed
|
|
self notify( "flying" );
|
|
self endon( "flying" );
|
|
|
|
// if owner switches teams, helicopter should leave
|
|
self endon( "abandoned" );
|
|
|
|
self.reached_dest = false;
|
|
heli_reset();
|
|
|
|
self SetHoverParams( 50, 100, 50);
|
|
|
|
wait( 2 );
|
|
|
|
currentDest = protectDest;
|
|
|
|
nodeHeight = protectDest[2];
|
|
|
|
nextnode = startNode;
|
|
|
|
heightOffset = 0;
|
|
if ( heli_team == "axis" )
|
|
{
|
|
heightOffset = 400;
|
|
}
|
|
|
|
protectDest = ( protectDest[0], protectDest[1], nodeHeight );
|
|
noFlyZoneHeight = airsupport::getNoFlyZoneHeight( protectDest );
|
|
protectDest = ( protectDest[0], protectDest[1], noFlyZoneHeight + heightOffset );
|
|
currentDest = protectDest;
|
|
startTime = gettime();
|
|
self.endTime = startTime + ( level.heli_protect_time * 1000 );
|
|
self.killstreakEndTime = int( self.endTime );
|
|
|
|
self SetSpeed( 150, 80 );
|
|
|
|
/# self util::debug_slow_heli_speed(); #/
|
|
|
|
self set_goal_pos( self.origin + ( currentDest - self.origin ) / 3 + ( 0, 0, 1000 ), false );
|
|
self waittill( "near_goal" );
|
|
|
|
heli_speed = 30+randomInt(20);
|
|
heli_accel = 10+randomInt(5);
|
|
|
|
self thread updateTargetYaw();
|
|
|
|
mapEnter = true;
|
|
|
|
while ( getTime() < self.endTime )
|
|
{
|
|
stop = 1;
|
|
|
|
if( !mapEnter )
|
|
{
|
|
self updateSpeed();
|
|
}
|
|
else
|
|
{
|
|
mapEnter = false;
|
|
}
|
|
|
|
// movement change due to damage
|
|
self set_goal_pos( (currentDest), stop );
|
|
|
|
self thread updateSpeedOnLock();
|
|
self util::waittill_any( "near_goal", "locking on", "locking on hacking" );
|
|
hostmigration::waitTillHostMigrationDone();
|
|
self notify( "path start" );
|
|
|
|
if ( !self is_targeted() )
|
|
{
|
|
|
|
waittillframeend;
|
|
|
|
time = level.heli_protect_pos_time;
|
|
|
|
if ( self.evasive == true )
|
|
{
|
|
time = 2.0;
|
|
}
|
|
|
|
set_heli_speed_hover();
|
|
|
|
wait_or_waittill ( time, "locking on", "locking on hacking", "damage state" );
|
|
}
|
|
else
|
|
{
|
|
wait 2;
|
|
}
|
|
|
|
prevDest = currentDest;
|
|
currentDest = heli_get_protect_spot(protectDest, nodeHeight);
|
|
noFlyZoneHeight = airsupport::getNoFlyZoneHeight( currentDest );
|
|
currentDest = ( currentDest[0], currentDest[1], noFlyZoneHeight + heightOffset );
|
|
noFlyZones = airsupport::crossesNoFlyZones( prevDest, currentDest );
|
|
if ( isdefined( noFlyZones ) && ( noFlyZones.size > 0 ) )
|
|
{
|
|
currentDest = prevDest;
|
|
}
|
|
}
|
|
|
|
self heli_set_active_camo_state( ( 1 ) );
|
|
self thread heli_leave();
|
|
}
|
|
|
|
function updateSpeedOnLock()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
self util::waittill_any( "near_goal", "locking on", "locking on hacking" );
|
|
|
|
self updateSpeed();
|
|
}
|
|
|
|
function updateSpeed()
|
|
{
|
|
if ( self is_targeted() || ( isdefined(self.evasive) && self.evasive ) )
|
|
{
|
|
set_heli_speed_evasive();
|
|
}
|
|
else
|
|
{
|
|
set_heli_speed_normal();
|
|
}
|
|
}
|
|
|
|
function updateTargetYaw()
|
|
{
|
|
self notify( "endTargetYawUpdate" );
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
self endon( "endTargetYawUpdate" );
|
|
|
|
for(;;)
|
|
{
|
|
if ( isdefined( self.primaryTarget ) )
|
|
{
|
|
yaw = math::get_2d_yaw( self.origin, self.primaryTarget.origin );
|
|
self setTargetYaw( yaw );
|
|
}
|
|
|
|
wait( 1 );
|
|
}
|
|
}
|
|
|
|
function fire_missile( sMissileType, iShots, eTarget )
|
|
{
|
|
if ( !isdefined( iShots ) )
|
|
iShots = 1;
|
|
assert( self.health > 0 );
|
|
|
|
weapon = undefined;
|
|
weaponShootTime = undefined;
|
|
tags = [];
|
|
switch( sMissileType )
|
|
{
|
|
case "ffar":
|
|
weapon = GetWeapon( "hind_FFAR" );
|
|
tags[ 0 ] = "tag_store_r_2";
|
|
break;
|
|
default:
|
|
assertMsg( "Invalid missile type specified. Must be ffar" );
|
|
break;
|
|
}
|
|
assert( isdefined( weapon ) );
|
|
assert( tags.size > 0 );
|
|
|
|
weaponShootTime = weapon.fireTime;
|
|
assert( isdefined( weaponShootTime ) );
|
|
|
|
self setVehWeapon( weapon );
|
|
nextMissileTag = -1;
|
|
for( i = 0 ; i < iShots ; i++ ) // I don't believe iShots > 1 is properly supported; we don't set the weapon each time
|
|
{
|
|
nextMissileTag++;
|
|
if ( nextMissileTag >= tags.size )
|
|
nextMissileTag = 0;
|
|
|
|
eMissile = self fireWeapon( 0, eTarget );
|
|
eMissile.killcament = self;
|
|
self.lastRocketFireTime = gettime();
|
|
|
|
if ( i < iShots - 1 )
|
|
wait weaponShootTime;
|
|
}
|
|
// avoid calling setVehWeapon again this frame or the client doesn't hear about the original weapon change
|
|
}
|
|
|
|
function check_owner( hardpointType )
|
|
{
|
|
if ( !isdefined( self.owner ) || !isdefined( self.owner.team ) || self.owner.team != self.team )
|
|
{
|
|
self notify ( "abandoned" );
|
|
self thread heli_leave();
|
|
}
|
|
}
|
|
|
|
function attack_targets( missilesEnabled, hardpointType )
|
|
{
|
|
//self thread turret_kill_players();
|
|
self thread attack_primary( hardpointType );
|
|
if ( missilesEnabled )
|
|
self thread attack_secondary( hardpointType );
|
|
}
|
|
|
|
// missile only
|
|
function attack_secondary( hardpointType )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
for( ;; )
|
|
{
|
|
if ( isdefined( self.secondaryTarget ) )
|
|
{
|
|
self.secondaryTarget.antithreat = undefined;
|
|
self.missileTarget = self.secondaryTarget;
|
|
|
|
antithreat = 0;
|
|
|
|
while( isdefined( self.missileTarget ) && isalive( self.missileTarget ) )
|
|
{
|
|
// if selected target is not in missile hit range, skip
|
|
if( self target_cone_check( self.missileTarget, level.heli_missile_target_cone ) )
|
|
self thread missile_support( self.missileTarget, level.heli_missile_rof, true, undefined );
|
|
else
|
|
break;
|
|
|
|
// lower targets threat after shooting
|
|
antithreat += 100;
|
|
self.missileTarget.antithreat = antithreat;
|
|
|
|
wait level.heli_missile_rof;
|
|
|
|
// target might disconnect or change during last assault cycle
|
|
if ( !isdefined( self.secondaryTarget ) || ( isdefined( self.secondaryTarget ) && self.missileTarget != self.secondaryTarget ) )
|
|
break;
|
|
}
|
|
// reset the antithreat factor
|
|
if ( isdefined( self.missileTarget ) )
|
|
self.missileTarget.antithreat = undefined;
|
|
}
|
|
self waittill( "secondary acquired" );
|
|
|
|
// check if owner has left, if so, leave
|
|
self check_owner( hardpointType );
|
|
}
|
|
}
|
|
|
|
function turret_target_check( turretTarget, attackAngle )
|
|
{
|
|
|
|
targetYaw = math::get_2d_yaw( self.origin, turretTarget.origin );
|
|
chopperYaw = self.angles[1];
|
|
|
|
if ( targetYaw < 0 )
|
|
targetYaw = targetYaw * -1;
|
|
|
|
targetYaw = int( targetYaw ) % 360;
|
|
|
|
if ( chopperYaw < 0 )
|
|
chopperYaw = chopperYaw * -1;
|
|
|
|
chopperYaw = int( chopperYaw ) % 360;
|
|
|
|
if ( chopperYaw > targetYaw )
|
|
difference = chopperYaw - targetYaw;
|
|
else
|
|
difference = targetYaw - chopperYaw;
|
|
|
|
return ( difference <= attackAngle );
|
|
}
|
|
|
|
// check if missile is in hittable sight zone
|
|
function target_cone_check( target, coneCosine )
|
|
{
|
|
heli2target_normal = vectornormalize( target.origin - self.origin );
|
|
heli2forward = anglestoforward( self.angles );
|
|
heli2forward_normal = vectornormalize( heli2forward );
|
|
|
|
heli_dot_target = vectordot( heli2target_normal, heli2forward_normal );
|
|
|
|
if ( heli_dot_target >= coneCosine )
|
|
{
|
|
airsupport::debug_print3d_simple( "Cone sight: " + heli_dot_target, self, ( 0,0,-40 ), 40 );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// if wait for turret turning is too slow, enable missile assault support
|
|
function missile_support( target_player, rof, instantfire, endon_notify )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
|
|
if ( isdefined ( endon_notify ) )
|
|
self endon( endon_notify );
|
|
|
|
self.turret_giveup = false;
|
|
|
|
if ( !instantfire )
|
|
{
|
|
wait( rof );
|
|
self.turret_giveup = true;
|
|
self notify( "give up" );
|
|
}
|
|
|
|
if ( isdefined( target_player ) )
|
|
{
|
|
if ( level.teambased )
|
|
{
|
|
// if target near friendly, do not shoot missile, target already has lower threat level at this stage
|
|
for (i = 0; i < level.players.size; i++)
|
|
{
|
|
player = level.players[i];
|
|
if ( isdefined( player.team ) && player.team == self.team && distance( player.origin, target_player.origin ) <= level.heli_missile_friendlycare )
|
|
{
|
|
airsupport::debug_print3d_simple( "Missile omitted due to nearby friendly", self, ( 0,0,-80 ), 40 );
|
|
self notify ( "missile ready" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player = self.owner;
|
|
if ( isdefined( player ) && isdefined( player.team ) && player.team == self.team && distance( player.origin, target_player.origin ) <= level.heli_missile_friendlycare )
|
|
{
|
|
airsupport::debug_print3d_simple( "Missile omitted due to nearby friendly", self, ( 0,0,-80 ), 40 );
|
|
self notify ( "missile ready" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self.missile_ammo > 0 && isdefined( target_player ) )
|
|
{
|
|
self fire_missile( "ffar", 1, target_player );
|
|
self.missile_ammo--;
|
|
self notify( "missile fired" );
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( instantfire )
|
|
{
|
|
wait ( rof );
|
|
self notify ( "missile ready" );
|
|
}
|
|
}
|
|
|
|
// mini-gun with missile support
|
|
function attack_primary( hardpointType )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
level endon( "game_ended" );
|
|
|
|
for( ;; )
|
|
{
|
|
if ( isdefined( self.primaryTarget ) )
|
|
{
|
|
self.primaryTarget.antithreat = undefined;
|
|
self.turretTarget = self.primaryTarget;
|
|
antithreat = 0;
|
|
last_pos = undefined;
|
|
|
|
while( isdefined( self.turretTarget ) && isalive( self.turretTarget ) )
|
|
{
|
|
|
|
//Look at target
|
|
if ( (hardpointType == "helicopter_comlink") || (hardpointType == "inventory_helicopter_comlink") )
|
|
self SetLookAtEnt( self.turretTarget );
|
|
|
|
helicopterTurretMaxAngle = heli_get_dvar_int( "scr_helicopterTurretMaxAngle", level.helicopterTurretMaxAngle );
|
|
while ( isdefined( self.turretTarget ) && isalive( self.turretTarget ) && self turret_target_check( self.turretTarget, helicopterTurretMaxAngle ) == false )
|
|
wait( 0.1 );
|
|
|
|
if ( !isdefined( self.turretTarget ) || !isalive( self.turretTarget ) )
|
|
break;
|
|
|
|
// shoots one clip of mini-gun non stop
|
|
self setTurretTargetEnt( self.turretTarget, ( 0, 0, 50 ) );
|
|
|
|
self waittill( "turret_on_target" );
|
|
hostmigration::waitTillHostMigrationDone();
|
|
|
|
/*
|
|
self util::waittill_any( "turret_on_target", "give up" );
|
|
if( isdefined( self.turret_giveup ) && self.turret_giveup )
|
|
break;
|
|
*/
|
|
|
|
self notify( "turret_on_target" );
|
|
//play some targeting Dialog CDC
|
|
if (!self.pilotIsTalking)
|
|
{
|
|
//self thread PlayPilotDialog ("attackheli_target");
|
|
}
|
|
|
|
self thread turret_target_flag( self.turretTarget );
|
|
|
|
wait 1;
|
|
|
|
self heli_set_active_camo_state( ( 0 ) );
|
|
|
|
// wait for turret to spinup and fire
|
|
wait( level.heli_turret_spinup_delay );
|
|
|
|
// fire gun =================================
|
|
weaponShootTime = self.defaultWeapon.fireTime;
|
|
self setVehWeapon( self.defaultWeapon );
|
|
|
|
// shoot full clip at target, if target lost, shoot at the last position recorded, if target changed, sweep onto next target
|
|
for( i = 0 ; i < level.heli_turretClipSize ; i++ )
|
|
{
|
|
// if turret on primary target, keep last position of the target in case target lost
|
|
if ( isdefined( self.turretTarget ) && isdefined( self.primaryTarget ) )
|
|
{
|
|
if ( self.primaryTarget != self.turretTarget )
|
|
self setTurretTargetEnt( self.primaryTarget, ( 0, 0, 40 ) );
|
|
}
|
|
else
|
|
{
|
|
if ( isdefined( self.targetlost ) && self.targetlost && isdefined( self.turret_last_pos ) )
|
|
{
|
|
//println( "Target lost ---- shooting last pos: " + self.turret_last_pos ); // debug
|
|
self setturrettargetvec( self.turret_last_pos );
|
|
}
|
|
else
|
|
{
|
|
self clearturrettarget();
|
|
}
|
|
}
|
|
if ( gettime() != self.lastRocketFireTime )
|
|
{
|
|
// fire one bullet
|
|
self setVehWeapon( self.defaultWeapon );
|
|
miniGun = self fireWeapon();
|
|
//self.minigun_snd_ent PlayLoopSound( "wpn_attack_chopper_minigun_fire_loop_npc" );//now in gdt
|
|
}
|
|
|
|
// wait for RoF
|
|
if ( i < level.heli_turretClipSize - 1 )
|
|
wait weaponShootTime;
|
|
}
|
|
//self.minigun_snd_ent StopLoopSound();
|
|
//self.minigun_snd_ent PlaySound("wpn_attack_chopper_minigun_fire_loop_ring_npc");//now in gdt
|
|
self notify( "turret reloading" );
|
|
// end fire gun ==============================
|
|
|
|
// wait for turret reload
|
|
wait( level.heli_turretReloadTime );
|
|
|
|
wait( 3 ); // cooldown before recloaking
|
|
|
|
self heli_set_active_camo_state( ( 1 ) );
|
|
|
|
// lower the target's threat since already assaulted on
|
|
if ( isdefined( self.turretTarget ) && isalive( self.turretTarget ) )
|
|
{
|
|
antithreat += 100;
|
|
self.turretTarget.antithreat = antithreat;
|
|
}
|
|
|
|
// primary target might disconnect or change during last assault cycle, if so, find new target
|
|
if ( !isdefined( self.primaryTarget ) || ( isdefined( self.turretTarget ) && isdefined( self.primaryTarget ) && self.primaryTarget != self.turretTarget ) )
|
|
break;
|
|
}
|
|
// reset the antithreat factor
|
|
if ( isdefined( self.turretTarget ) )
|
|
self.turretTarget.antithreat = undefined;
|
|
}
|
|
self waittill( "primary acquired" );
|
|
|
|
// check if owner has left, if so, leave
|
|
self check_owner( hardpointType );
|
|
}
|
|
}
|
|
|
|
// target lost flaging
|
|
function turret_target_flag( turrettarget )
|
|
{
|
|
// forcing single thread instance
|
|
self notify( "flag check is running" );
|
|
self endon( "flag check is running" );
|
|
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
self endon( "turret reloading" );
|
|
|
|
// ends on target player death or undefined
|
|
if ( isdefined( turrettarget ) )
|
|
{
|
|
turrettarget endon( "death" );
|
|
turrettarget endon( "disconnect" );
|
|
}
|
|
|
|
self.targetlost = false;
|
|
self.turret_last_pos = undefined;
|
|
|
|
while( isdefined( turrettarget ) )
|
|
{
|
|
heli_centroid = self.origin + ( 0, 0, -160 );
|
|
heli_forward_norm = anglestoforward( self.angles );
|
|
heli_turret_point = heli_centroid + 144*heli_forward_norm;
|
|
|
|
sight_rec = turrettarget sightconetrace( heli_turret_point, self );
|
|
if ( sight_rec < level.heli_target_recognition )
|
|
break;
|
|
|
|
{wait(.05);};
|
|
}
|
|
|
|
if( isdefined( turrettarget ) && isdefined( turrettarget.origin ) )
|
|
{
|
|
assert( isdefined( turrettarget.origin ), "turrettarget.origin is undefined after isdefined check" );
|
|
self.turret_last_pos = turrettarget.origin + ( 0, 0, 40 );
|
|
assert( isdefined( self.turret_last_pos ), "self.turret_last_pos is undefined after setting it #1" );
|
|
self setturrettargetvec( self.turret_last_pos );
|
|
assert( isdefined( self.turret_last_pos ), "self.turret_last_pos is undefined after setting it #2" );
|
|
airsupport::debug_print3d_simple( "Turret target lost at: " + self.turret_last_pos, self, ( 0,0,-70 ), 60 );
|
|
self.targetlost = true;
|
|
}
|
|
else
|
|
{
|
|
self.targetlost = undefined;
|
|
self.turret_last_pos = undefined;
|
|
}
|
|
}
|
|
|
|
// debug on screen elements ===========================================================
|
|
function debug_print_target()
|
|
{
|
|
if ( isdefined( level.heli_debug ) && level.heli_debug == 1.0 )
|
|
{
|
|
// targeting debug print
|
|
if( isdefined( self.primaryTarget ) && isdefined( self.primaryTarget.threatlevel ) )
|
|
{
|
|
if ( isdefined(self.primaryTarget.type) && self.primaryTarget.type == "dog" )
|
|
name = "dog";
|
|
else
|
|
name = self.primaryTarget.name;
|
|
primary_msg = "Primary: " + name + " : " + self.primaryTarget.threatlevel;
|
|
}
|
|
else
|
|
primary_msg = "Primary: ";
|
|
|
|
if( isdefined( self.secondaryTarget ) && isdefined( self.secondaryTarget.threatlevel ) )
|
|
{
|
|
if ( isdefined(self.secondaryTarget.type) && self.secondaryTarget.type == "dog" )
|
|
name = "dog";
|
|
else
|
|
name = self.secondaryTarget.name;
|
|
secondary_msg = "Secondary: " + name + " : " + self.secondaryTarget.threatlevel;
|
|
}
|
|
else
|
|
secondary_msg = "Secondary: ";
|
|
|
|
frames = int( self.targeting_delay*20 )+1;
|
|
|
|
thread airsupport::draw_text( primary_msg, (1, 0.6, 0.6), self, ( 0, 0, 40), frames );
|
|
thread airsupport::draw_text( secondary_msg, (1, 0.6, 0.6), self, ( 0, 0, 0), frames );
|
|
}
|
|
}
|
|
|
|
function waittill_confirm_location()
|
|
{
|
|
self endon( "emp_jammed" );
|
|
self endon( "emp_grenaded" );
|
|
|
|
self waittill( "confirm_location", location );
|
|
|
|
return location;
|
|
}
|
|
|
|
function selectHelicopterLocation(hardpointtype)
|
|
{
|
|
self beginLocationComlinkSelection( "compass_objpoint_helicopter", 1500 );
|
|
self.selectingLocation = true;
|
|
|
|
self thread airsupport::endSelectionThink();
|
|
|
|
location = self waittill_confirm_location();
|
|
|
|
if ( !isdefined( location ) )
|
|
{
|
|
// selection was cancelled
|
|
return false;
|
|
}
|
|
|
|
if ( self killstreakrules::isKillstreakAllowed( hardpointType, self.team ) == false )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
level.helilocation = location;
|
|
|
|
return airsupport::finishHardpointLocationUsage( location, undefined );
|
|
}
|
|
|
|
function processCopterAssist( destroyedCopter, damagedone )
|
|
{
|
|
self endon( "disconnect" );
|
|
destroyedCopter endon( "disconnect" );
|
|
|
|
wait .05;
|
|
|
|
if ( !isdefined( level.teams[self.team] ) )
|
|
return;
|
|
|
|
if ( self.team == destroyedCopter.team )
|
|
return;
|
|
|
|
assist_level = "aircraft_destruction_assist";
|
|
|
|
assist_level_value = int( ceil( ( damagedone.damage / destroyedCopter.maxhealth ) * 4 ) );
|
|
|
|
if ( assist_level_value > 0 )
|
|
{
|
|
if ( assist_level_value > 3 )
|
|
{
|
|
assist_level_value = 3;
|
|
}
|
|
assist_level = assist_level + "_" + ( assist_level_value * 25 );
|
|
}
|
|
|
|
scoreevents::processScoreEvent( assist_level, self );
|
|
}
|
|
|
|
//Allow the SAM turret to attack choppers
|
|
function SAMTurretWatcher()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "crashing" );
|
|
self endon( "leaving" );
|
|
level endon( "game_ended" );
|
|
|
|
self util::waittill_any( "turret_on_target", "path start", "near_goal" );
|
|
|
|
Target_SetTurretAquire( self, true );
|
|
}
|
|
|
|
function PlayPilotDialog( dialog, time, voice, shouldWait )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "remote_end" );
|
|
|
|
if (isdefined(time))
|
|
{
|
|
wait time;
|
|
}
|
|
if (!isdefined(self.pilotVoiceNumber))
|
|
{
|
|
self.pilotVoiceNumber = 0;
|
|
}
|
|
if (isdefined(voice))
|
|
{
|
|
voicenumber = voice;
|
|
}
|
|
else
|
|
{
|
|
voicenumber = self.pilotVoiceNumber;
|
|
}
|
|
soundAlias = level.teamPrefix[self.team] + voicenumber + "_" + dialog;
|
|
|
|
if ( isdefined ( self.owner ) )
|
|
{
|
|
self.owner playPilotTalking( shouldWait, soundAlias );
|
|
}
|
|
}
|
|
|
|
function playPilotTalking( shouldWait, soundAlias )
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "joined_team" );
|
|
self endon( "joined_spectators" );
|
|
|
|
tryCounter = 0;
|
|
while( isdefined(self.pilotTalking) && self.pilotTalking && tryCounter < 10 )
|
|
{
|
|
if ( isdefined( shouldWait ) && !shouldWait )
|
|
return;
|
|
wait 1;
|
|
tryCounter++;
|
|
}
|
|
self.pilotTalking = true;
|
|
self playLocalSound(soundAlias);
|
|
wait 3;
|
|
self.pilotTalking = false;
|
|
}
|
|
|
|
function watchForEarlyLeave( chopper )
|
|
{
|
|
chopper notify( "watchForEarlyLeave_helicopter" );
|
|
chopper endon( "watchForEarlyLeave_helicopter" );
|
|
chopper endon( "death" );
|
|
self endon( "heli_timeup" );
|
|
|
|
self util::waittill_any( "joined_team", "disconnect" );
|
|
|
|
if ( isdefined( chopper ) )
|
|
chopper thread heli_leave();
|
|
|
|
if ( isdefined( self ) )
|
|
self notify( "heli_timeup" );
|
|
}
|
|
|
|
function watchForEMP()
|
|
{
|
|
heli = self;
|
|
|
|
heli endon( "death" );
|
|
heli endon( "heli_timeup" );
|
|
|
|
heli.owner waittill( "emp_jammed" );
|
|
heli thread heli_explode();
|
|
}
|