boiii-scripts/mp/killstreaks/_helicopter.gsc
2023-04-13 17:30:38 +02:00

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