1795 lines
49 KiB
Plaintext
1795 lines
49 KiB
Plaintext
#include common_scripts\utility;
|
|
#include maps\_utility;
|
|
#include maps\_vehicle;
|
|
#include maps\_anim;
|
|
#include maps\_specialops;
|
|
#include maps\_hud_util;
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
fire_off_exploder( current )
|
|
{
|
|
while( 1 )
|
|
{
|
|
exploder( current.script_prefab_exploder );
|
|
if( !isdefined( current.target ) )
|
|
break;
|
|
next = getent( current.target, "targetname" );
|
|
if( !isdefined( next ) )
|
|
break;
|
|
current = next;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
create_smoke_wave( smoke_tag, dialog_wait )
|
|
{
|
|
// Prevent smoke from happening too frequently
|
|
if ( isdefined( level.smoke_throttle ) )
|
|
{
|
|
if ( !isdefined( level.smoke_wave_time ) )
|
|
level.smoke_wave_time = gettime() - level.smoke_throttle - 1;
|
|
|
|
time_since = gettime() - level.smoke_wave_time;
|
|
if ( time_since <= level.smoke_throttle )
|
|
return;
|
|
|
|
level.smoke_wave_time = gettime();
|
|
}
|
|
|
|
magic_smoke_grenades = getentarray( smoke_tag, "targetname" );
|
|
array_thread( magic_smoke_grenades, ::smoke_wave_play );
|
|
|
|
// Undefined dialog_wait assumes we don't want any. Use 0 for no wait.
|
|
if ( isdefined( dialog_wait ) )
|
|
thread dialog_smoke_wave_alert( dialog_wait );
|
|
}
|
|
|
|
smoke_wave_play()
|
|
{
|
|
playfx( getfx( "smokescreen" ), self.origin );
|
|
self thread play_sound_in_space( "smokegrenade_explode_default" );
|
|
}
|
|
|
|
dialog_smoke_wave_alert( dialog_wait )
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
wait dialog_wait;
|
|
|
|
// Record the time and go go go.
|
|
level.smoke_wave_time = gettime();
|
|
|
|
//Hunter Two-One, Overlord. Advise switching to thermal optics, over.
|
|
radio_dialogue( "so_def_inv_thermaloptics" );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
btr80_level_init()
|
|
{
|
|
if ( isdefined( level.btr80_init ) )
|
|
return;
|
|
|
|
level.btr80_init = true;
|
|
level.btr80_count = 0;
|
|
level.btr80_death_time = gettime();
|
|
|
|
if ( !isdefined( level.btr_kill_value ) )
|
|
level.btr_kill_value = 400;
|
|
|
|
if ( !isdefined( level.btr_min_fighting_range ) )
|
|
level.btr_min_fighting_range = 400;
|
|
|
|
if ( !isdefined( level.btr_max_fighting_range ) )
|
|
level.btr_max_fighting_range = 2400;
|
|
|
|
if ( !isdefined( level.btr_target_fov ) )
|
|
level.btr_target_fov = cos( 50 );
|
|
|
|
level.btr80_building_checks = getentarray( "trigger_multiple_flag_set_touching", "classname" );
|
|
|
|
for ( i = level.btr80_building_checks.size - 1; i >= 0; i-- )
|
|
{
|
|
building = level.btr80_building_checks[ i ];
|
|
if ( !isdefined( building.script_flag ) )
|
|
{
|
|
level.btr80_building_checks[ i ] = undefined;
|
|
continue;
|
|
}
|
|
|
|
switch( building.script_flag )
|
|
{
|
|
case "player_inside_nates" :
|
|
case "player_in_burgertown" :
|
|
case "player_in_diner" :
|
|
// Do nothing, keep in the list.
|
|
break;
|
|
default:
|
|
level.btr80_building_checks[ i ] = undefined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
create_btr80( btr80_tag )
|
|
{
|
|
btr80_level_init();
|
|
|
|
btr80 = spawn_vehicle_from_targetname_and_drive( btr80_tag );
|
|
array_thread( getvehiclenodearray( "new_target", "script_noteworthy" ), ::btr80_new_target_think );
|
|
|
|
btr80 thread btr80_watch_for_player();
|
|
btr80 thread btr80_register_death();
|
|
btr80 thread ent_flag_init( "spotted_player" );
|
|
btr80 thread btr80_turret_spotlight();
|
|
btr80 thread maps\_vehicle::damage_hints();
|
|
btr80 thread dialog_btr80_spotted_you();
|
|
}
|
|
|
|
btr80_watch_for_player()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
self endon( "death" );
|
|
self.turret_busy = false;
|
|
|
|
while( 1 )
|
|
{
|
|
wait .05;
|
|
|
|
if ( self ent_flag( "spotted_player" ) )
|
|
continue;
|
|
|
|
player = btr80_find_available_player();
|
|
if ( !isdefined( player ) )
|
|
continue;
|
|
|
|
tag_flash_angles = self getTagAngles( "tag_flash" );
|
|
if( !within_fov( self.origin, tag_flash_angles, player.origin, level.btr_target_fov ) )
|
|
continue;
|
|
|
|
if( !btr80_can_see_player( player ) )
|
|
continue;
|
|
|
|
self notify( "new_target" ); // Clears ambient target shooting
|
|
self.turret_busy = true;
|
|
self ent_flag_set( "spotted_player" );
|
|
player.btr80_attacker_id = self.unique_id; // Claim this player for myself.
|
|
self Vehicle_SetSpeed( 0, 10 );
|
|
|
|
//saw player, now miss for 2 bursts
|
|
btr80_miss_player( player );
|
|
wait( randomfloatrange( 0.8, 2.4 ) );
|
|
btr80_miss_player( player );
|
|
wait( randomfloatrange( 0.8, 2.4 ) );
|
|
|
|
//if player is still exposed then hit him
|
|
while ( btr80_can_see_player( player ) )
|
|
{
|
|
btr80_fire_at_player( player );
|
|
wait( randomfloatrange( 0.5, 1.5 ) );
|
|
}
|
|
|
|
self clearturrettarget();
|
|
self.turret_busy = false;
|
|
self ent_flag_clear( "spotted_player" );
|
|
player.btr80_attacker_id = undefined;
|
|
self Vehicle_SetSpeed( 10, 1 );
|
|
}
|
|
}
|
|
|
|
btr80_turret_spotlight()
|
|
{
|
|
vehicle_lights_on( "spotlight spotlight_turret" );
|
|
}
|
|
|
|
btr80_fire_at_player( player )
|
|
{
|
|
self endon( "death" );
|
|
|
|
burstsize = randomintrange( 3, 5 );
|
|
fireTime = .2;
|
|
for ( i = 0; i < burstsize; i++ )
|
|
{
|
|
self setturrettargetent( player, randomvector( 20 ) + ( 0, 0, 32 ) );//randomvec was 50
|
|
self fireweapon();
|
|
wait fireTime;
|
|
}
|
|
}
|
|
|
|
btr80_miss_player( player )
|
|
{
|
|
self endon( "death" );
|
|
|
|
//point in front of player
|
|
forward = AnglesToForward( player.angles );
|
|
forwardfar = vector_multiply( forward, 100 );
|
|
miss_vec = forwardfar + randomvector( 50 );
|
|
|
|
burstsize = randomintrange( 4, 6 );
|
|
fireTime = .2;
|
|
for ( i = 0; i < burstsize; i++ )
|
|
{
|
|
offset = randomvector( 15 ) + miss_vec + (0,0,64);
|
|
self setturrettargetent( player, offset );
|
|
self fireweapon();
|
|
wait fireTime;
|
|
}
|
|
}
|
|
|
|
btr80_find_available_player()
|
|
{
|
|
p1_ok = btr80_check_player_available( level.player ) && btr80_check_player_in_range( level.player );
|
|
p2_ok = btr80_check_player_available( level.player2 ) && btr80_check_player_in_range( level.player2 );
|
|
|
|
if ( p1_ok && p2_ok )
|
|
return getclosest( self.origin, level.players );
|
|
|
|
if ( p1_ok )
|
|
return level.player;
|
|
|
|
if ( p2_ok )
|
|
return level.player2;
|
|
|
|
return undefined;
|
|
}
|
|
|
|
btr80_check_player_available( player )
|
|
{
|
|
if ( !isdefined( player ) )
|
|
return false;
|
|
|
|
if ( isdefined( player.btr80_attacker_id ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
btr80_check_player_in_range( player )
|
|
{
|
|
if ( !isdefined( player ) )
|
|
return false;
|
|
|
|
if ( distance( self.origin, player.origin ) > level.btr_max_fighting_range )
|
|
return false;
|
|
|
|
if( distance( self.origin, player.origin ) < level.btr_min_fighting_range )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
btr80_check_player_in_building( player )
|
|
{
|
|
if ( !isdefined( player ) )
|
|
return;
|
|
|
|
foreach ( building in level.btr80_building_checks )
|
|
{
|
|
if ( player istouching( building ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
btr80_can_see_player( player )
|
|
{
|
|
if ( btr80_check_player_in_building( player ) )
|
|
return false;
|
|
|
|
if ( !btr80_check_player_in_range( player ) )
|
|
return false;
|
|
|
|
tag_flash_loc = self getTagOrigin( "tag_flash" );
|
|
player_eye = player geteye();
|
|
if ( SightTracePassed( tag_flash_loc, player_eye, false, self ) )
|
|
{
|
|
if( isdefined( level.debug ) )
|
|
line( tag_flash_loc, player_eye, ( 0.2, 0.5, 0.8 ), 0.5, false, 60 );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
btr80_new_target_think()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
level endon( "btr80s_all_down" );
|
|
|
|
targets = getentarray( self.script_linkto, "script_linkname" );
|
|
while( 1 )
|
|
{
|
|
self waittill( "trigger", vehicle );
|
|
|
|
if( !isalive( vehicle ) )
|
|
return;
|
|
if( vehicle.turret_busy )
|
|
continue;
|
|
|
|
vehicle notify( "new_target" );
|
|
|
|
vehicle setturrettargetent( targets[0] );
|
|
|
|
thread btr80_fire_at_targets( vehicle );
|
|
}
|
|
}
|
|
|
|
btr80_fire_at_targets( vehicle )
|
|
{
|
|
vehicle endon( "new_target" );
|
|
vehicle endon( "death" );
|
|
|
|
vehicle waittill( "turret_on_target" );
|
|
|
|
while( 1 )
|
|
{
|
|
s = randomintrange( 4, 6 );
|
|
for ( j = 0; j < s; j++ )
|
|
{
|
|
vehicle fireWeapon();
|
|
wait .2;
|
|
}
|
|
wait( randomfloatrange( 1, 2 ) );
|
|
}
|
|
}
|
|
|
|
btr80_register_death()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
level.btr80_count++;
|
|
|
|
my_id = self.unique_id;
|
|
|
|
self waittill( "death", attacker );
|
|
|
|
if( self ent_flag( "spotted_player" ) )
|
|
{
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( isdefined( player.btr80_attacker_id ) && ( my_id == player.btr80_attacker_id ) )
|
|
player.btr80_attacker_id = undefined;
|
|
}
|
|
}
|
|
|
|
if ( isplayer( attacker ) )
|
|
{
|
|
attacker.btr80_kills++;
|
|
attacker update_sentry_attackeraccuracy( level.aamod_btr80_kill );
|
|
}
|
|
|
|
level.btr80_count--;
|
|
assertex( ( level.btr80_count >= 0 ), "Somehow the BTR80 population counter dropped below 0. This should never happen." );
|
|
|
|
level notify( "btr80_death" );
|
|
if ( level.btr80_count <= 0 )
|
|
level notify( "btr80s_all_down" );
|
|
|
|
}
|
|
|
|
dialog_btr80_spotted_you()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
self endon( "death" );
|
|
|
|
while( 1 )
|
|
{
|
|
ent_flag_wait( "spotted_player" );
|
|
dialog_btr80_spotted_you_action();
|
|
wait 20;
|
|
}
|
|
}
|
|
|
|
dialog_btr80_spotted_you_action()
|
|
{
|
|
spotted_player = undefined;
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( isdefined( player.btr80_attacker_id ) && ( player.btr80_attacker_id == self.unique_id ) )
|
|
{
|
|
spotted_player = player;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !btr80_can_see_player( spotted_player ) )
|
|
return;
|
|
|
|
// Prevent btr80 dialog from happening too frequently
|
|
if ( isdefined( level.btr80_alert_throttle ) )
|
|
{
|
|
if ( !isdefined( level.btr80_alert_time ) )
|
|
level.btr80_alert_time = gettime() - level.btr80_alert_throttle - 1;
|
|
|
|
time_since = gettime() - level.btr80_alert_time;
|
|
if ( time_since <= level.btr80_alert_throttle )
|
|
return;
|
|
|
|
level.btr80_alert_time = gettime();
|
|
}
|
|
|
|
//Enemy BTR has a visual on you, Hunter Two-One, advise seeking cover, over.
|
|
//Hunter Two-One, be advised enemy BTR is targetting you, over.
|
|
radio_dialogue( "so_def_inv_bmpspottedyou" );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
hunter_enemies_level_init()
|
|
{
|
|
// Always re-init this as it can get overwritten at the end of a wave.
|
|
set_group_advance_to_enemy_parameters( 30000, 2 );
|
|
|
|
if ( isdefined( level.hunters_init ) )
|
|
return;
|
|
|
|
level.hunters_init = true;
|
|
|
|
level.hunters_active = 0;
|
|
if ( !isdefined( level.hunters_all_in ) )
|
|
level.hunters_all_in = 5;
|
|
dialog_hunter_enemies_setup();
|
|
|
|
level.difficultySettings[ "accuracyDistScale" ][ "easy" ] = 0.8;
|
|
level.difficultySettings[ "accuracyDistScale" ][ "normal" ] = 0.8;
|
|
level.difficultySettings[ "accuracyDistScale" ][ "hardened" ] = 0.7;
|
|
level.difficultySettings[ "accuracyDistScale" ][ "veteran" ] = 0.6;
|
|
maps\_gameskill::updateAllDifficulty();
|
|
}
|
|
|
|
create_hunter_enemy_group( enemy_tag, enemy_count )
|
|
{
|
|
hunter_enemies_level_init();
|
|
|
|
if ( !isdefined( level.hunter_group_initialized ) )
|
|
{
|
|
level.hunter_group_initialized = true;
|
|
level.hunter_goals = getentarray( "closest_goal_radius", "targetname" );
|
|
}
|
|
|
|
current_enemies = getentarray( enemy_tag, "targetname" );
|
|
array_thread( current_enemies, ::add_spawn_function, ::create_hunter_enemy );
|
|
|
|
if ( !isdefined( enemy_count ) || ( enemy_count > current_enemies.size ) )
|
|
enemy_count = current_enemies.size;
|
|
|
|
current_enemies = array_randomize( current_enemies );
|
|
enemies_spawned = 0;
|
|
for ( i = 0; i < current_enemies.size; i++ )
|
|
{
|
|
current_enemies[ i ].count = 1;
|
|
guy = current_enemies[ i ] spawn_ai();
|
|
|
|
if ( isdefined( guy ))
|
|
enemies_spawned++;
|
|
|
|
if ( enemies_spawned >= enemy_count )
|
|
break;
|
|
}
|
|
|
|
// Only say something if we spawned at least 10 guys.
|
|
if ( enemies_spawned >= 10 )
|
|
thread dialog_hunter_enemies( enemy_tag, 2.5 );
|
|
|
|
return enemies_spawned;
|
|
}
|
|
|
|
create_hunter_truck_enemies( truck_tag )
|
|
{
|
|
hunter_enemies_level_init();
|
|
|
|
if ( !isdefined( level.truck_group_initialized ) )
|
|
{
|
|
level.truck_group_initialized = true;
|
|
truck_group_enemies = getentarray( "truck_group_enemies", "script_noteworthy" );
|
|
array_thread( truck_group_enemies, ::add_spawn_function, ::create_hunter_enemy, true );
|
|
}
|
|
|
|
truck = thread spawn_vehicle_from_targetname_and_drive( truck_tag );
|
|
truck.veh_pathtype = "constrained";
|
|
}
|
|
|
|
create_hunter_enemy( wait_for_unload )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "special_op_terminated" );
|
|
|
|
thread hunter_register_death();
|
|
|
|
if ( isdefined( wait_for_unload ) && wait_for_unload )
|
|
self waittill( "jumpedout" );
|
|
|
|
thread hunter_enemy_maintain_closest_goal();
|
|
}
|
|
|
|
hunter_enemy_maintain_closest_goal()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "special_op_terminated" );
|
|
|
|
self.hunter_is_bored = false;
|
|
self enable_danger_react( 5 );
|
|
self.goalradius = 2048;
|
|
self.goalheight = 768;
|
|
|
|
// Figure out at what time we'll get bored.
|
|
boredom_time_base = 30000;
|
|
boredom_time_fuzz = 90000;
|
|
boredom_time = gettime() + boredom_time_base + randomint( boredom_time_fuzz );
|
|
|
|
while ( true )
|
|
{
|
|
if ( !hunter_check_become_bored( boredom_time ) )
|
|
{
|
|
self.hunter_is_bored = false;
|
|
closest_player = getclosest( self.origin, level.players );
|
|
closest_goal = getclosest( closest_player.origin, level.hunter_goals );
|
|
if ( !isdefined( self.current_goal ) || ( self.current_goal != closest_goal ) )
|
|
{
|
|
waittillframeend;
|
|
//waittillframeend because you may be in the part of the frame that is before
|
|
//the script has received the "death" notify but after the AI has died.
|
|
|
|
self.current_goal = closest_goal;
|
|
self setgoalpos( self.current_goal.origin );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Bored... so get more aggressive.
|
|
self.hunter_is_bored = true;
|
|
self.aggressivemode = true;
|
|
self setgoalentity( getclosest( self.origin, level.players ) );
|
|
self setEngagementMinDist( 384, 0 );
|
|
self setEngagementMaxDist( 640, 1024 );
|
|
|
|
while ( true )
|
|
{
|
|
// While still refilling the population, don't get ultra aggressive.
|
|
if ( isdefined( level.hunter_refill_active ) )
|
|
{
|
|
wait 1;
|
|
continue;
|
|
}
|
|
|
|
if ( level.hunters_active > level.hunters_all_in )
|
|
{
|
|
wait 1;
|
|
continue;
|
|
}
|
|
|
|
// We are now *really* bored. Shrink engagement distance a lot.
|
|
self.goalradius = 512;
|
|
self.goalheight = 256;
|
|
self setEngagementMinDist( 0, 0 );
|
|
self setEngagementMaxDist( 256, 384 );
|
|
self.combatmode = "no_cover";
|
|
self set_ignoreSuppression( true );
|
|
if ( !isdefined( level.hunters_all_in_active ) )
|
|
{
|
|
// Only need to set this once.
|
|
level.hunters_all_in_active = true;
|
|
set_group_advance_to_enemy_parameters( 2000, level.hunters_all_in );
|
|
}
|
|
// Nothing left to do but die.
|
|
return;
|
|
}
|
|
}
|
|
|
|
wait 1.0;
|
|
}
|
|
}
|
|
|
|
hunter_check_become_bored( bored_time )
|
|
{
|
|
// No more than level.hunters_all_in count can be bored at a time.
|
|
bored_guys = 0;
|
|
enemies = getaiarray( "axis" );
|
|
foreach ( guy in enemies )
|
|
{
|
|
if ( isdefined( guy.hunter_is_bored ) && guy.hunter_is_bored )
|
|
bored_guys++;
|
|
}
|
|
if ( bored_guys >= level.hunters_all_in )
|
|
return false;
|
|
|
|
// If our timer expires, then go go go.
|
|
if ( gettime() >= bored_time )
|
|
return true;
|
|
|
|
// Once the population gets small enough, make EVERYONE bored and charge the player.
|
|
if ( !isdefined( level.hunter_refill_active ) && ( level.hunters_active <= level.hunters_all_in ) )
|
|
return true;
|
|
|
|
// If there is already a random hunter, then no.
|
|
if ( isdefined( level.bored_hunter ) )
|
|
return false;
|
|
|
|
// No bored hunter available, so it's us now!
|
|
level.bored_hunter = self.unique_id;
|
|
return true;
|
|
}
|
|
|
|
hunter_enemies_refill( refill_at, min_fill, max_fill, refill_max )
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
if ( isdefined( level.hunter_refill_active ) && level.hunter_refill_active )
|
|
return;
|
|
|
|
level.hunter_refill_active = true;
|
|
level.hunter_refill_used_smoke = false;
|
|
|
|
if ( !isdefined( refill_at ) || ( refill_at < 0 ) )
|
|
refill_at = 0;
|
|
if ( !isdefined( min_fill ) || ( min_fill < 1 ) )
|
|
min_fill = 1;
|
|
if ( !isdefined( max_fill ) || ( max_fill <= min_fill ) )
|
|
max_fill = min_fill + 1;
|
|
|
|
// This includes any currently active hunters in the level so we can maintain a LEVEl max which is the intent.
|
|
// Namely if a truck was spawned, this will be aware of them.
|
|
// If the truck is spawned after this thread is started then it will not be aware of them.
|
|
if ( isdefined( refill_max ) )
|
|
{
|
|
if ( isdefined( level.hunters_active ) && ( level.hunters_active > 0 ) )
|
|
refill_max -= level.hunters_active;
|
|
}
|
|
|
|
refill_current = 0;
|
|
|
|
spawn_option = undefined;
|
|
while ( isdefined( level.hunter_refill_active ) && level.hunter_refill_active )
|
|
{
|
|
if ( !isdefined( level.hunters_active ) || ( level.hunters_active <= refill_at ) )
|
|
{
|
|
respawn_amount = hunter_enemies_get_spawn_amount( min_fill, max_fill, refill_max, refill_current );
|
|
spawn_option = hunter_enemies_get_spawn_option( spawn_option );
|
|
switch ( spawn_option )
|
|
{
|
|
case "bank": respawn_amount = hunter_enemies_refill_group( "bank_enemies", respawn_amount, "north" ); break;
|
|
case "gas": respawn_amount = hunter_enemies_refill_group( "gas_station_enemies", respawn_amount ); break;
|
|
case "taco": respawn_amount = hunter_enemies_refill_group( "taco_enemies", respawn_amount, "south" ); break;
|
|
case "burger": respawn_amount = hunter_enemies_refill_group( "burger_town_enemies", respawn_amount, "south" ); break;
|
|
default: assertex( false, "hunter_enemies_refill() resulted in an invalid spawn option: " + spawn_option );
|
|
}
|
|
|
|
if ( isdefined( refill_max ) )
|
|
{
|
|
refill_current += respawn_amount;
|
|
if ( refill_current >= refill_max )
|
|
level.hunter_refill_active = undefined;
|
|
}
|
|
}
|
|
|
|
// Give it a moment before checking again.
|
|
if ( isdefined( level.hunter_refill_active ) && level.hunter_refill_active )
|
|
wait 1;
|
|
}
|
|
|
|
level notify( "hunter_refill_complete" );
|
|
}
|
|
|
|
hunter_enemies_get_spawn_amount( min_fill, max_fill, refill_max, refill_current )
|
|
{
|
|
respawn_amount = randomintrange( min_fill, max_fill );
|
|
if ( isdefined( refill_max ) )
|
|
{
|
|
if ( ( refill_current + respawn_amount ) > refill_max )
|
|
respawn_amount = ( refill_max - refill_current );
|
|
}
|
|
|
|
return respawn_amount;
|
|
}
|
|
|
|
hunter_enemies_get_spawn_option( last_spawn )
|
|
{
|
|
if ( !isdefined( last_spawn ) )
|
|
last_spawn = "";
|
|
|
|
spawn_options = [];
|
|
|
|
if ( !flag( "so_player_near_bank" ) )
|
|
spawn_options[ spawn_options.size ] = "bank";
|
|
if ( !flag( "so_player_near_gas_station" ) )
|
|
spawn_options[ spawn_options.size ] = "gas";
|
|
if ( !flag( "so_player_near_taco" ) )
|
|
spawn_options[ spawn_options.size ] = "taco";
|
|
if ( !flag( "so_player_near_burgertown" ) )
|
|
spawn_options[ spawn_options.size ] = "burger";
|
|
|
|
// No "good" options, so just pick a random one.
|
|
if ( spawn_options.size <= 0 )
|
|
{
|
|
spawn_options[ spawn_options.size ] = "bank";
|
|
spawn_options[ spawn_options.size ] = "gas";
|
|
spawn_options[ spawn_options.size ] = "taco";
|
|
spawn_options[ spawn_options.size ] = "burger";
|
|
}
|
|
|
|
// Only try for a new option if we have more than one.
|
|
i = 0;
|
|
if ( spawn_options.size > 1 )
|
|
{
|
|
i = randomint( spawn_options.size );
|
|
if ( spawn_options[ i ] == last_spawn )
|
|
{
|
|
i--;
|
|
if ( i < 0 )
|
|
i = spawn_options.size - 1;
|
|
}
|
|
}
|
|
|
|
return spawn_options[ i ];
|
|
}
|
|
|
|
hunter_enemies_refill_group( enemy_group, respawn_amount, smoke_dir )
|
|
{
|
|
if ( isdefined( smoke_dir ) )
|
|
{
|
|
if ( ( randomfloat( 1.0 ) < level.smoke_chance ) || !level.hunter_refill_used_smoke )
|
|
{
|
|
level.hunter_refill_used_smoke = true;
|
|
switch ( smoke_dir )
|
|
{
|
|
case "north": thread maps\so_defense_invasion::enable_smoke_wave_north( 4 ); break;
|
|
case "south": thread maps\so_defense_invasion::enable_smoke_wave_south( 4 ); break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return create_hunter_enemy_group( enemy_group, respawn_amount );
|
|
}
|
|
|
|
hunter_register_death()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
level.hunters_active++;
|
|
my_id = self.unique_id;
|
|
|
|
thread hunter_register_turret_death();
|
|
|
|
self waittill_any( "death", "pain_death" );
|
|
|
|
if ( isdefined( level.bored_hunter ) )
|
|
{
|
|
if ( level.bored_hunter == my_id )
|
|
level.bored_hunter = undefined;
|
|
}
|
|
|
|
level.hunters_active--;
|
|
assertex( ( level.hunters_active >= 0 ), "Somehow the hunter population counter dropped below 0. This should never happen." );
|
|
|
|
level notify( "hunter_death" );
|
|
if ( hunter_check_wave_complete() )
|
|
level notify( "hunters_all_down" );
|
|
}
|
|
|
|
hunter_register_turret_death()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
self waittill( "death", attacker );
|
|
|
|
if ( hunter_attacker_is_player_turret( attacker ) )
|
|
{
|
|
attacker.owner.turret_kills++;
|
|
attacker.owner update_sentry_attackeraccuracy( level.aamod_sentry_kill );
|
|
}
|
|
else if ( isplayer( attacker ) )
|
|
{
|
|
attacker update_sentry_attackeraccuracy( level.aamod_player_kill );
|
|
}
|
|
}
|
|
|
|
hunter_attacker_is_player_turret( attacker )
|
|
{
|
|
if ( !isdefined( attacker ) )
|
|
return false;
|
|
|
|
if ( !isdefined( attacker.targetname ) )
|
|
return false;
|
|
|
|
if ( attacker.targetname != "sentry_minigun" )
|
|
return false;
|
|
|
|
if ( !isdefined( attacker.owner ) )
|
|
return false;
|
|
|
|
if ( !isplayer( attacker.owner ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
update_sentry_attackeraccuracy( adjust_amount )
|
|
{
|
|
assert( isdefined( adjust_amount ) );
|
|
|
|
sentry_turrets = getentarray( "sentry_minigun", "targetname" );
|
|
foreach ( sentry in sentry_turrets )
|
|
{
|
|
if ( !isdefined( sentry.attackeraccuracy ) )
|
|
continue;
|
|
|
|
if ( !isdefined( sentry.owner ) )
|
|
continue;
|
|
|
|
if ( sentry.owner != self )
|
|
continue;
|
|
|
|
sentry.attackeraccuracy = max( 1.0, sentry.attackeraccuracy + adjust_amount );
|
|
}
|
|
}
|
|
|
|
hunter_check_wave_complete()
|
|
{
|
|
if ( level.hunters_active > 0 )
|
|
return false;
|
|
|
|
if ( isdefined( level.hunter_refill_active ) && level.hunter_refill_active )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
dialog_hunter_enemies( enemy_tag, wait_time )
|
|
{
|
|
// Prevent hunter spawn dialogs from happening too frequently
|
|
if ( isdefined( level.hunter_dialog_throttle ) )
|
|
{
|
|
if ( !isdefined( level.hunter_dialog_time ) )
|
|
level.hunter_dialog_time = gettime() - level.hunter_dialog_throttle - 1;
|
|
|
|
time_since = gettime() - level.hunter_dialog_time;
|
|
if ( time_since <= level.hunter_dialog_throttle )
|
|
return;
|
|
|
|
level.hunter_dialog_time = gettime();
|
|
}
|
|
|
|
if ( isdefined( wait_time ) )
|
|
wait wait_time;
|
|
|
|
assertex( isdefined( level.dialog ), "dialog_hunter_enemies requires level.dialog to be defined before it can play anything." );
|
|
|
|
sound_selection = randomint( level.dialog[ enemy_tag ].size );
|
|
thread radio_dialogue( level.dialog[ enemy_tag ][ sound_selection ] );
|
|
}
|
|
|
|
dialog_hunter_enemies_setup( enemy_tag, wait_time )
|
|
{
|
|
if ( !isdefined( level.dialog ) )
|
|
level.dialog = [];
|
|
|
|
//Hunter Two-One this is Overlord Actual, we're seeing enemy reinforcements to your north, over.
|
|
level.dialog[ "bank_enemies" ][ 0 ] = "inv_hqr_enemynorth";
|
|
//Be advised Hunter Two-One, you got enemy infantry by that bank to the north, over.
|
|
level.dialog[ "bank_enemies" ][ 1 ] = "inv_hqr_banktonorth";
|
|
//Hunter Two-One, be advised, enemy foot-mobiles approaching north of your location, over.
|
|
level.dialog[ "bank_enemies" ][ 2 ] = "inv_hqr_footmobiles";
|
|
|
|
//Hunter Two-One, Hunter Four has a visual on hostiles near the Nova gas station, over.
|
|
level.dialog[ "gas_station_enemies" ][ 0 ] = "inv_hqr_novagasstation";
|
|
//Hunter Two-One, relay from Goliath Two, enemy reinforcements approaching from the west, over.
|
|
level.dialog[ "gas_station_enemies" ][ 1 ] = "inv_hqr_enemywest";
|
|
//Hunter Two-One, tangos approaching near the diner to the west, over.
|
|
level.dialog[ "gas_station_enemies" ][ 2 ] = "inv_hqr_dinerwest";
|
|
|
|
//Hunter Two-One, Overlord. Enemy foot-mobiles approaching you from the southeast, over.
|
|
level.dialog[ "taco_enemies" ][ 0 ] = "inv_hqr_southeast";
|
|
//Hunter Two-One, Goliath One has a visual on hostiles coming from the southeast, over.
|
|
level.dialog[ "taco_enemies" ][ 1 ] = "inv_hqr_visualse";
|
|
//Hunter Two-One, be advised, enemy foot-mobiles have been sighted near the taco joint, over.
|
|
level.dialog[ "taco_enemies" ][ 2 ] = "inv_hqr_tacojoint";
|
|
|
|
//Be advised Hunter Two-One, you have enemy foot mobiles by the Burger Town to the south, over.
|
|
level.dialog[ "burger_town_enemies"][ 0 ] = "so_def_inv_mobilesouth";
|
|
level.scr_radio[ "so_def_inv_mobilesouth" ] = "so_def_inv_mobilesouth";
|
|
//Hunter Two-One, Overlord, be advised potential attackers from the south, over.
|
|
level.dialog[ "burger_town_enemies"][ 1 ] = "so_def_inv_attacksouth";
|
|
level.scr_radio[ "so_def_inv_attacksouth" ] = "so_def_inv_attacksouth";
|
|
//Hunter Two-One, Hunter Four has a identified a large hostile group near the Burger Town, over.
|
|
level.dialog[ "burger_town_enemies"][ 2 ] = "so_def_inv_hostilesouth";
|
|
level.scr_radio[ "so_def_inv_hostilesouth" ] = "so_def_inv_hostilesouth";
|
|
|
|
//Hunter Two-One, enemy predator drone has spotted you, advise taking cover, over.
|
|
level.dialog[ "drone_spotted" ][ 0 ] = "so_def_inv_dronespotted";
|
|
level.scr_radio[ "so_def_inv_dronespotted" ] = "so_def_inv_dronespotted";
|
|
//Hunter Two-One, be advised enemy drone has noticed you, over.
|
|
level.dialog[ "drone_spotted" ][ 1 ] = "so_def_inv_dronenotice";
|
|
level.scr_radio[ "so_def_inv_dronenotice" ] = "so_def_inv_dronenotice";
|
|
|
|
//Hunter Two-One, enemy drone is targetting you, seek cover, over.
|
|
level.dialog[ "drone_shooting" ][ 0 ] = "so_def_inv_dronetarget";
|
|
level.scr_radio[ "so_def_inv_dronetarget" ] = "so_def_inv_dronetarget";
|
|
//Enemy drone is firing directly on your position Hunter Two-One, over.
|
|
level.dialog[ "drone_shooting" ][ 1 ] = "so_def_inv_dronedirect";
|
|
level.scr_radio[ "so_def_inv_dronedirect" ] = "so_def_inv_dronedirect";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
attack_heli_init()
|
|
{
|
|
if ( isdefined( level.attack_heli_init ) )
|
|
return;
|
|
|
|
level.attackheliRange = 7000;
|
|
level.attack_heli_count = 0;
|
|
|
|
level.attack_heli_init = true;
|
|
level.attack_heli_death_time = gettime();
|
|
|
|
dialog_fill_nates_stinger();
|
|
dialog_fill_diner_stinger();
|
|
}
|
|
|
|
create_attack_heli( heli_id, heli_points_id, wait_time )
|
|
{
|
|
assertex( isdefined( heli_id ), "create_attack_heli() requires a valid heli_id." );
|
|
assertex( isdefined( heli_points_id ), "create_attack_heli() requires a valid heli_points_id." );
|
|
|
|
if ( isdefined( wait_time ) )
|
|
wait wait_time;
|
|
|
|
attack_heli_init();
|
|
|
|
eHeli = maps\_vehicle::spawn_vehicle_from_targetname_and_drive( heli_id );
|
|
eHeli.circling = true;
|
|
eHeli.no_attractor = true;
|
|
thread maps\_attack_heli::begin_attack_heli_behavior( eHeli, heli_points_id );
|
|
eHeli thread attack_heli_register_death();
|
|
|
|
thread dialog_attack_heli();
|
|
}
|
|
|
|
attack_heli_register_death()
|
|
{
|
|
level.attack_heli_count++;
|
|
|
|
self waittill( "death", attacker );
|
|
|
|
thread dialog_shot_down_heli();
|
|
|
|
level.attack_heli_count--;
|
|
assertex( ( level.attack_heli_count >= 0 ), "Somehow the Heli population counter dropped below 0. This should never happen." );
|
|
|
|
if ( isplayer( attacker ) )
|
|
{
|
|
attacker.helicopter_kills++;
|
|
attacker update_sentry_attackeraccuracy( level.aamod_heli_kill );
|
|
}
|
|
|
|
level notify( "attack_heli_death" );
|
|
if ( level.attack_heli_count == 0 )
|
|
level notify( "attack_helis_all_down" );
|
|
}
|
|
|
|
dialog_attack_heli()
|
|
{
|
|
//Hunter Two-One, relay from Goliath One: you got an enemy helicopter loaded for bear, approaching your area, over.
|
|
radio_dialogue( "inv_hqr_relaygol1" );
|
|
}
|
|
|
|
dialog_shot_down_heli()
|
|
{
|
|
wait 3;
|
|
//Nice one, over.
|
|
radio_dialogue( "so_def_inv_niceone" );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
// Updated to be generic and not depend on specific exact stingers.
|
|
dialog_get_stinger()
|
|
{
|
|
assertex( isdefined( level.stingers ) && ( level.stingers.size > 0 ), "dialog_get_stinger() requires at least one stinger to function correctly." );
|
|
level endon( "special_op_terminated" );
|
|
|
|
stringer_dialog_throttle_reset();
|
|
|
|
nates_dialog_current = 0;
|
|
diner_dialog_current = 0;
|
|
|
|
while( 1 )
|
|
{
|
|
// Have to wait until we have a stinger available.
|
|
if ( !isdefined( level.stingers ) )
|
|
{
|
|
wait 1;
|
|
continue;
|
|
}
|
|
|
|
// Don't bother if there isn't anyone useful to shoot.
|
|
if ( !stinger_enemy_available() )
|
|
{
|
|
wait 1;
|
|
continue;
|
|
}
|
|
|
|
p1_has_stinger = stinger_player_has( level.player );
|
|
p2_has_stinger = stinger_player_has( level.player2 );
|
|
|
|
// If either player has a stinger, no need to play dialog.
|
|
if ( p1_has_stinger || p2_has_stinger )
|
|
{
|
|
wait 3;
|
|
continue;
|
|
}
|
|
|
|
alert_stinger = getClosest( level.player.origin, level.stingers );
|
|
if ( !isdefined( alert_stinger ) )
|
|
{
|
|
wait 1;
|
|
continue;
|
|
}
|
|
|
|
// When in co-op, find the player closest to a stinger and alert them of that one.
|
|
if ( is_coop() )
|
|
{
|
|
p1_distance = distance( level.player.origin, alert_stinger.origin );
|
|
|
|
p2_stinger = getClosest( level.player2.origin, level.stingers );
|
|
p2_distance = distance( level.player2.origin, p2_stinger.origin );
|
|
|
|
if ( p2_distance < p1_distance )
|
|
alert_stinger = p2_stinger;
|
|
}
|
|
|
|
if ( isdefined( level.stingers[ "diner" ] ) && ( alert_stinger == level.stingers[ "diner" ] ) )
|
|
{
|
|
selected_line = level.diner_dialog[ diner_dialog_current ];
|
|
radio_dialogue( selected_line );
|
|
|
|
diner_dialog_current++;
|
|
if( diner_dialog_current >= level.diner_dialog.size )
|
|
diner_dialog_current = 0;
|
|
}
|
|
else if ( isdefined( level.stingers[ "nates_stinger" ] ) )
|
|
{
|
|
selected_line = level.nates_dialog[ nates_dialog_current ];
|
|
radio_dialogue( selected_line );
|
|
|
|
nates_dialog_current++;
|
|
if( nates_dialog_current >= level.nates_dialog.size )
|
|
nates_dialog_current = 0;
|
|
}
|
|
else
|
|
{
|
|
assertex( false, "dialog_get_stinger() tried to play an alert for a stinger, but no stingers are defined." );
|
|
continue;
|
|
}
|
|
|
|
stringer_dialog_throttle_reset();
|
|
}
|
|
}
|
|
|
|
stringer_dialog_throttle_reset()
|
|
{
|
|
level.stinger_missile_throttle = gettime() + 60000;
|
|
}
|
|
|
|
stinger_player_has( player )
|
|
{
|
|
// If no player, then they definitely don't have a stinger.
|
|
if ( !isdefined( player ) )
|
|
return false;
|
|
|
|
weapons = player GetWeaponsListAll();
|
|
foreach ( weapon in weapons )
|
|
{
|
|
if ( weapon == "at4" )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
stinger_enemy_available()
|
|
{
|
|
if ( level.stinger_missile_throttle > gettime() )
|
|
return false;
|
|
|
|
// If the player has killed one within the last 30 seconds don't remind.
|
|
death_remind_delay = 30000;
|
|
|
|
if ( isdefined( level.attack_heli_count ) && ( level.attack_heli_count > 0 ) )
|
|
{
|
|
if ( level.attack_heli_death_time + death_remind_delay < gettime() )
|
|
return true;
|
|
}
|
|
|
|
if ( isdefined( level.btr80_count ) && ( level.btr80_count > 0 ) )
|
|
{
|
|
if ( level.btr80_death_time + death_remind_delay < gettime() )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
dialog_fill_diner_stinger()
|
|
{
|
|
level.diner_dialog = [];
|
|
|
|
//Be advised Hunter Two One, AT4 rockets located in the diner to the west, over.
|
|
//Hunter Two-One, intel indicates a stockpile of AT4 rockets to the west, over.
|
|
level.diner_dialog[ level.diner_dialog.size ] = "so_def_inv_stingerdiner";
|
|
}
|
|
|
|
dialog_fill_nates_stinger()
|
|
{
|
|
level.nates_dialog = [];
|
|
|
|
//This is Overlord Actual, AT4 rockets at the supply drop on the roof of Nate's restaurant, over.
|
|
//Hunter Two-One, check the roof of Nate's restaurant for AT4 rockets, over.
|
|
level.nates_dialog[ level.nates_dialog.size ] = "so_def_inv_stingernates";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
stinger_maintain_spawn( stinger_id )
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
level.stingers[ stinger_id ] = getent( stinger_id, "script_noteworthy" );
|
|
stinger = level.stingers[ stinger_id ];
|
|
|
|
assertex( isdefined( stinger ), "stinger_keep_available() was unable to find a stinger of script_noteworthy " + stinger_id );
|
|
|
|
stinger_origin = stinger.origin;
|
|
stinger_angles = stinger.angles;
|
|
|
|
garbage_dump = getstruct( "stinger_garbage_dump", "script_noteworthy" );
|
|
|
|
// Remove the existing stinger and turn it into an AT4.
|
|
stinger Delete();
|
|
stinger = stinger_respawn( stinger_id, stinger_origin, stinger_angles );
|
|
level.stingers[ stinger_id ] = stinger;
|
|
|
|
/* while( 1 )
|
|
{
|
|
stinger waittill( "trigger", player, old_weapon );
|
|
|
|
// If players are grabbing them, never need to remind them.
|
|
stringer_dialog_throttle_reset();
|
|
|
|
stinger = undefined;
|
|
level.stingers[ stinger_id ] = undefined;
|
|
|
|
while ( !isdefined( stinger ) )
|
|
{
|
|
wait 5;
|
|
close_players = get_within_range( stinger_origin, level.players, 256 );
|
|
if ( close_players.size > 0 )
|
|
continue;
|
|
|
|
close_players = get_within_range( stinger_origin, level.players, 1024 );
|
|
if ( close_players.size > 0 )
|
|
{
|
|
if ( stinger_player_can_see( stinger_origin ) )
|
|
continue;
|
|
}
|
|
|
|
stinger = stinger_respawn( stinger_id, stinger_origin, stinger_angles );
|
|
level.stingers[ stinger_id ] = stinger;
|
|
if ( isdefined( old_weapon ) )
|
|
old_weapon.origin = garbage_dump.origin;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
stinger_player_can_see( stinger_origin )
|
|
{
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( player can_see_origin( stinger_origin ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
stinger_respawn( stinger_id, origin, angles )
|
|
{
|
|
stinger = spawn( "weapon_at4", origin, 1 );
|
|
stinger.angles = angles;
|
|
stinger ItemWeaponSetAmmo( 1, 0 );
|
|
stinger.script_noteworthy = stinger_id;
|
|
|
|
return stinger;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
semtex_maintain_availability()
|
|
{
|
|
semtex = getentarray( "weapon_semtex_grenade", "classname" );
|
|
array_thread( semtex, ::semtex_maintain_self );
|
|
}
|
|
|
|
semtex_maintain_self()
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
semtex = self;
|
|
semtex_origin = self.origin;
|
|
semtex_angles = self.angles;
|
|
|
|
while( 1 )
|
|
{
|
|
semtex waittill( "trigger", player, old_weapon );
|
|
|
|
// Wait for players to leave proximity, then respawn.
|
|
while( semtex_player_is_close( semtex_origin ) )
|
|
wait 1;
|
|
|
|
semtex = spawn( "weapon_semtex_grenade", semtex_origin, 1 );
|
|
semtex.angles = semtex_angles;
|
|
semtex ItemWeaponSetAmmo( 4, 0 );
|
|
}
|
|
}
|
|
|
|
semtex_player_is_close( semtex_origin )
|
|
{
|
|
close_players = get_within_range( semtex_origin, level.players, 1024 );
|
|
return close_players.size > 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
hellfire_attack_start()
|
|
{
|
|
if ( isdefined( level.hellfire_active ) )
|
|
return;
|
|
|
|
level.hellfire_active = true;
|
|
level.hellfire_paused = false;
|
|
|
|
if ( !isdefined( level.hellfire_time_search ) )
|
|
hellfire_set_time_search( 20, 40 );
|
|
|
|
if ( !isdefined( level.hellfire_time_breather ) )
|
|
hellfire_set_time_breather( 5, 8 );
|
|
|
|
thread hellfire_spawn_player1_uav();
|
|
thread hellfire_spawn_player2_uav();
|
|
}
|
|
|
|
hellfire_spawn_player1_uav()
|
|
{
|
|
level.hellfire_uav = hellfire_spawn_uav( level.player );
|
|
}
|
|
|
|
hellfire_spawn_player2_uav()
|
|
{
|
|
if ( !is_coop() )
|
|
return;
|
|
|
|
level.hellfire_uav_p2 = hellfire_spawn_uav( level.player2, 12 );
|
|
}
|
|
|
|
hellfire_spawn_uav( player, delay )
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
level endon( "hellfire_attack_stop" );
|
|
|
|
if ( isdefined( delay ) )
|
|
wait delay;
|
|
|
|
hellfire_uav = getent( "uav", "targetname" );
|
|
hellfire_uav.target = "so_uav_start";
|
|
hellfire_uav = spawn_vehicle_from_targetname_and_drive( "uav" );
|
|
hellfire_uav playLoopSound( "uav_engine_loop" );
|
|
if ( !level.hellfire_paused )
|
|
hellfire_uav thread hellfire_monitor_player( player );
|
|
|
|
return hellfire_uav;
|
|
}
|
|
|
|
hellfire_attack_pause()
|
|
{
|
|
if ( level.hellfire_paused )
|
|
return;
|
|
|
|
level.hellfire_paused = true;
|
|
level notify( "hellfire_attack_pause" );
|
|
}
|
|
|
|
hellfire_attack_unpause()
|
|
{
|
|
if ( !level.hellfire_paused )
|
|
return;
|
|
|
|
level.hellfire_paused = false;
|
|
level.hellfire_uav thread hellfire_monitor_player( level.player );
|
|
if ( is_coop() && isdefined( level.hellfire_uav_p2 ) )
|
|
level.hellfire_uav_p2 thread hellfire_monitor_player( level.player2 );
|
|
}
|
|
|
|
hellfire_attack_stop()
|
|
{
|
|
level notify( "hellfire_attack_stop" );
|
|
|
|
level.hellfire_active = undefined;
|
|
level.hellfire_paused = undefined;
|
|
level.hellfire_uav Delete();
|
|
if ( is_coop() )
|
|
level.hellfire_uav_p2 Delete();
|
|
}
|
|
|
|
hellfire_monitor_player( player )
|
|
{
|
|
if ( isdefined( level.hellfire_paused ) && level.hellfire_paused )
|
|
return;
|
|
|
|
player endon( "death" );
|
|
level endon( "special_op_terminated" );
|
|
level endon( "hellfire_attack_stop" );
|
|
level endon( "hellfire_attack_pause" );
|
|
|
|
while( 1 )
|
|
{
|
|
// Wait for a while before going after the player.
|
|
wait RandomIntRange( level.hellfire_time_search[ "min" ], level.hellfire_time_search[ "max" ] );
|
|
while( !hellfire_check_player_available( player ) )
|
|
wait 1;
|
|
|
|
// Spotted! Give the player a moment to run...
|
|
hud_warning = hud_display_uav_spotted( player, self.unique_id );
|
|
dialog_hellfire_warn_player( "drone_spotted" );
|
|
|
|
wait 2;
|
|
|
|
// Threaten our player...
|
|
hellfire_threaten_player( player );
|
|
wait RandomIntRange( level.hellfire_time_breather[ "min" ], level.hellfire_time_breather[ "max" ] );
|
|
|
|
// Threaten them again...
|
|
hellfire_threaten_player( player );
|
|
wait RandomIntRange( level.hellfire_time_breather[ "min" ], level.hellfire_time_breather[ "max" ] );
|
|
|
|
// If player is still visible, attack them directly until no longer visible or dead
|
|
if ( hellfire_check_player_available( player ) )
|
|
{
|
|
hud_display_uav_targetting( hud_warning );
|
|
dialog_hellfire_warn_player( "drone_shooting" );
|
|
while( hellfire_check_player_available( player ) )
|
|
{
|
|
hellfire_attack_player( player );
|
|
wait RandomIntRange( level.hellfire_time_breather[ "min" ], level.hellfire_time_breather[ "max" ] );
|
|
}
|
|
}
|
|
|
|
// Once player is hidden, give them one more scare and then move on.
|
|
hellfire_attack_player( player );
|
|
level notify( "hellfire_attack_notarget_" + self.unique_id );
|
|
}
|
|
}
|
|
|
|
dialog_hellfire_warn_player( alias )
|
|
{
|
|
// Don't let these happen in too quick of succession
|
|
if ( isdefined( level.hellfire_warn_time ) )
|
|
{
|
|
if ( level.hellfire_warn_time + 10000 > gettime() )
|
|
return;
|
|
}
|
|
|
|
level.hellfire_warn_time = gettime();
|
|
|
|
index = RandomInt( level.dialog[ alias ].size );
|
|
radio_dialogue( level.dialog[ alias ][ index ] );
|
|
}
|
|
|
|
hellfire_check_player_available( player )
|
|
{
|
|
if ( !isdefined( player ) )
|
|
return false;
|
|
|
|
// Fully incapped players no longer targetted.
|
|
if ( is_coop() && is_player_down_and_out( player ) )
|
|
return false;
|
|
|
|
return SightTracePassed( self.origin, player GetEye(), false, self );
|
|
}
|
|
|
|
hellfire_attack_player( player, num_shots )
|
|
{
|
|
player endon( "death" );
|
|
level endon( "special_op_terminated" );
|
|
level endon( "hellfire_attack_stop" );
|
|
level endon( "hellfire_attack_pause" );
|
|
|
|
if ( !isdefined( num_shots ) )
|
|
num_shots = 2;
|
|
|
|
hellfire_shots = RandomIntrange( 1, num_shots );
|
|
for ( i = 0; i < num_shots; i++ )
|
|
{
|
|
attack_range_x = RandomIntRange( -600, 600 );
|
|
attack_range_y = RandomIntRange( -600, 600 );
|
|
attack_range_z = 0;
|
|
attack_spot = player.origin;
|
|
// On the first attack, always ensure it goes directly at the player.
|
|
if ( i > 0 )
|
|
attack_spot += ( attack_range_x, attack_range_y, attack_range_z );
|
|
hellfire_fire_missile( attack_spot );
|
|
wait ( randomfloatrange( 0.33, 0.66 ) );
|
|
}
|
|
}
|
|
|
|
hellfire_threaten_player( player, max_shots )
|
|
{
|
|
player endon( "death" );
|
|
level endon( "special_op_terminated" );
|
|
level endon( "hellfire_attack_stop" );
|
|
level endon( "hellfire_attack_pause" );
|
|
|
|
targets = getstructarray( "so_hellfire_target", "script_noteworthy" );
|
|
targets = get_within_range( player.origin, targets, 1800 ); // Close enough to feel scary
|
|
targets = get_outside_range( player.origin, targets , 600 ); // Outside explosion radius
|
|
if ( is_coop() )
|
|
{
|
|
other_player = get_other_player( player );
|
|
targets = get_outside_range( other_player.origin, targets, 600 ); // Outside explosion radius
|
|
}
|
|
|
|
if ( !isdefined( max_shots ) )
|
|
max_shots = 4;
|
|
|
|
hellfire_shots = RandomIntRange( 1, max_shots );
|
|
for ( i = 0; i < hellfire_shots; i++ )
|
|
{
|
|
targets = self hellfire_attack_target( player, targets, true );
|
|
wait ( randomfloatrange( 0.25, 0.75 ) );
|
|
}
|
|
}
|
|
|
|
hellfire_attack_target( player, targets, remove_target )
|
|
{
|
|
if ( !isdefined( targets ) || ( targets.size <= 0 ) )
|
|
return;
|
|
|
|
hellfire_index = get_closest_index_to_player_view( targets, player, true );
|
|
hellfire_target = targets[ hellfire_index ];
|
|
hellfire_fire_missile( hellfire_target.origin );
|
|
|
|
if ( isdefined( remove_target ) && remove_target )
|
|
return array_remove_index( targets, hellfire_index );
|
|
}
|
|
|
|
hellfire_fire_missile( target_origin )
|
|
{
|
|
if ( level.hellfire_paused )
|
|
return;
|
|
|
|
MagicBullet( "remote_missile_not_player_invasion", ( self.origin + (0,0,-128) ), target_origin );
|
|
}
|
|
|
|
hellfire_set_time_search( time_min, time_max )
|
|
{
|
|
assertex( isdefined( time_min ), "hellfire_set_time_search() requires a valid time_min" );
|
|
assertex( isdefined( time_max ), "hellfire_set_time_search() requires a valid time_max" );
|
|
assertex( ( time_min < time_max ), "hellfire_set_time_search() requires time_min to be less than time_max" );
|
|
|
|
if ( !isdefined( level.hellfire_time_search ) )
|
|
level.hellfire_time_search = [];
|
|
|
|
level.hellfire_time_search[ "min" ] = time_min;
|
|
level.hellfire_time_search[ "max" ] = time_max;
|
|
}
|
|
|
|
hellfire_set_time_breather( time_min, time_max )
|
|
{
|
|
assertex( isdefined( time_min ), "hellfire_set_time_breather() requires a valid time_min" );
|
|
assertex( isdefined( time_max ), "hellfire_set_time_breather() requires a valid time_max" );
|
|
assertex( ( time_min < time_max ), "hellfire_set_time_breather() requires time_min to be less than time_max" );
|
|
|
|
if ( !isdefined( level.hellfire_time_breather ) )
|
|
level.hellfire_time_breather = [];
|
|
|
|
level.hellfire_time_breather[ "min" ] = time_min;
|
|
level.hellfire_time_breather[ "max" ] = time_max;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
hud_display_wavecount( wave_num )
|
|
{
|
|
// Little delay so the "Wave Starting in..." can be removed
|
|
wait( 1 );
|
|
|
|
foreach ( player in level.players )
|
|
{
|
|
// For now, it looks like there are waves on all difficulties.
|
|
if ( wave_num < 5 )
|
|
{
|
|
player.hud_wave_title = so_create_hud_item( 0, so_hud_ypos(), &"SPECIAL_OPS_WAVENUM", player );
|
|
player.hud_wave_count = so_create_hud_item( 0, so_hud_ypos(), undefined, player );
|
|
player.hud_wave_count.alignx = "left";
|
|
player.hud_wave_count SetValue( wave_num );
|
|
}
|
|
else
|
|
{
|
|
player.hud_wave_title = so_create_hud_item( 0, so_hud_ypos(), &"SPECIAL_OPS_WAVEFINAL", player );
|
|
player.hud_wave_title.alignx = "center";
|
|
}
|
|
}
|
|
}
|
|
|
|
hud_display_wavecount_remove()
|
|
{
|
|
foreach ( player in level.players )
|
|
{
|
|
player.hud_wave_title thread so_remove_hud_item( true );
|
|
|
|
if ( IsDefined( player.hud_wave_count ) )
|
|
{
|
|
player.hud_wave_count thread so_remove_hud_item( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
hud_display_uav_spotted( player, uav_id )
|
|
{
|
|
hudelem = so_create_hud_item( -1, -4, &"SO_DEFENSE_INVASION_UAV_SPOTTED", player );
|
|
hudelem set_hud_yellow();
|
|
thread hud_display_uav_spotted_fade( hudelem, uav_id );
|
|
return hudelem;
|
|
}
|
|
|
|
hud_display_uav_targetting( hudelem )
|
|
{
|
|
if ( !isdefined( hudelem ) )
|
|
return;
|
|
|
|
hudelem set_hud_red();
|
|
hudelem.label = &"SO_DEFENSE_INVASION_UAV_TARGETTING";
|
|
}
|
|
|
|
hud_display_uav_spotted_fade( hudelem, uav_id )
|
|
{
|
|
uav_notarget = "hellfire_attack_notarget_" + uav_id;
|
|
level waittill_any( uav_notarget, "hellfire_attack_stop", "hellfire_attack_pause", "special_op_terminated", "wave_complete" );
|
|
|
|
if ( !isdefined( hudelem ) )
|
|
return;
|
|
|
|
hudelem so_remove_hud_item( false, true );
|
|
}
|
|
|
|
hud_display_wave( title_text, timer )
|
|
{
|
|
hudelems = [];
|
|
list = hud_get_wave_list( title_text );
|
|
for( i = 0; i < list.size; i++ )
|
|
{
|
|
if ( list[ i ] != &"SO_DEFENSE_INVASION_ALERT_BLANK" )
|
|
{
|
|
hudelems[ i ] = hud_create_wave_splash_default( i, list[ i ] );
|
|
hudelems[ i ] SetPulseFX( 60, ( ( timer - 1 ) * 1000 ) - ( i * 1000 ), 1000 );
|
|
}
|
|
wait 1;
|
|
}
|
|
|
|
wait timer - ( list.size * 1 );
|
|
|
|
foreach( hudelem in hudelems )
|
|
hudelem Destroy();
|
|
}
|
|
|
|
hud_create_wave_splash_default( yLine, message )
|
|
{
|
|
hudelem = so_create_hud_item( yLine, 0, message );
|
|
hudelem.alignX = "center";
|
|
hudelem.horzAlign = "center";
|
|
|
|
return hudelem;
|
|
}
|
|
|
|
hud_display_enemies_active( enemy_title, enemy_total, enemy_death )
|
|
{
|
|
if ( !isdefined( level.hud_display_enemies ) )
|
|
level.hud_display_enemies = 0;
|
|
|
|
level.hud_display_enemies++;
|
|
|
|
foreach ( player in level.players )
|
|
player thread hud_display_enemies_active_player( enemy_title, enemy_total, enemy_death );
|
|
}
|
|
|
|
hud_display_enemies_active_player( enemy_title, enemy_total, enemy_death )
|
|
{
|
|
level endon( "special_op_terminated" );
|
|
|
|
hud_line = level.hud_display_enemies + 1;
|
|
hudelem_title = so_create_hud_item( hud_line, so_hud_ypos(), enemy_title, self );
|
|
hudelem_count = so_create_hud_item( hud_line, so_hud_ypos(), undefined, self );
|
|
hudelem_count.alignx = "left";
|
|
|
|
force_pulse = true;
|
|
enemy_max = enemy_total;
|
|
while ( enemy_total > 0 )
|
|
{
|
|
if ( enemy_death == "hunter_death" )
|
|
{
|
|
thread so_dialog_counter_update( enemy_total, enemy_max );
|
|
thread hud_display_enemies_pulse_hunter( hudelem_title, hudelem_count, enemy_total, force_pulse );
|
|
}
|
|
else
|
|
{
|
|
thread hud_display_enemies_pulse_vehicle( hudelem_title, hudelem_count, enemy_total );
|
|
}
|
|
|
|
force_pulse = false;
|
|
level waittill( enemy_death );
|
|
|
|
enemy_total--;
|
|
}
|
|
|
|
hudelem_count so_remove_hud_item( true );
|
|
hudelem_count = so_create_hud_item( hud_line, so_hud_ypos(), &"SPECIAL_OPS_DASHDASH", self );
|
|
hudelem_count.alignx = "left";
|
|
|
|
hudelem_title thread so_hud_pulse_success();
|
|
hudelem_count thread so_hud_pulse_success();
|
|
|
|
level waittill( "wave_complete" );
|
|
|
|
hudelem_title thread so_remove_hud_item();
|
|
hudelem_count thread so_remove_hud_item();
|
|
}
|
|
|
|
hud_display_enemies_pulse_hunter( hudelem_title, hudelem_count, enemy_total, force_pulse )
|
|
{
|
|
hudelem_count SetValue( enemy_total );
|
|
|
|
if ( enemy_total > 5 )
|
|
{
|
|
if ( force_pulse )
|
|
{
|
|
hudelem_title thread so_hud_pulse_default();
|
|
hudelem_count thread so_hud_pulse_default();
|
|
}
|
|
return;
|
|
}
|
|
|
|
hudelem_title thread so_hud_pulse_close();
|
|
hudelem_count thread so_hud_pulse_close();
|
|
}
|
|
|
|
hud_display_enemies_pulse_vehicle( hudelem_title, hudelem_count, enemy_total )
|
|
{
|
|
hudelem_count SetValue( enemy_total );
|
|
|
|
hudelem_title thread so_hud_pulse_default();
|
|
hudelem_count thread so_hud_pulse_default();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
door_diner_open()
|
|
{
|
|
diner_back_door = getent( "diner_back_door", "targetname" );
|
|
diner_back_door rotateyaw( 85, .3 );//counter clockwise
|
|
diner_back_door playsound( "diner_backdoor_slams_open" );
|
|
diner_back_door connectpaths();
|
|
}
|
|
|
|
door_nates_locker_open()
|
|
{
|
|
nates_meat_locker_door = getent( "nates_meat_locker_door", "targetname" );
|
|
nates_meat_locker_door_model = getent( nates_meat_locker_door.target, "targetname" );
|
|
nates_meat_locker_door_model LinkTo( nates_meat_locker_door );
|
|
nates_meat_locker_door rotateyaw( -82, .1, 0, 0 );
|
|
nates_meat_locker_door connectpaths();
|
|
}
|
|
|
|
door_bt_locker_open()
|
|
{
|
|
BT_locker_door = getent( "BT_locker_door", "targetname" );
|
|
BT_locker_door rotateyaw( -172, .1, 0, 0 );
|
|
BT_locker_door connectpaths();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
so_defense_convert_enemies()
|
|
{
|
|
// Convert some additional enemies over to available Gas Station enemies
|
|
convert_enemies = getentarray( "diner_enemy_defenders", "targetname" );
|
|
convert_enemies = array_merge( convert_enemies, getentarray( "diner_enemy_counter_attack", "targetname" ) );
|
|
for ( i = 0; i < convert_enemies.size; i++ )
|
|
convert_enemies[ i ].targetname = "gas_station_enemies";
|
|
|
|
// Convert some additional enemies over to available Burger Town enemies
|
|
convert_enemies = getentarray( "burger_town_nates_attackers", "targetname" );
|
|
convert_enemies = array_merge( convert_enemies, getentarray( "burger_town_enemy_defenders", "targetname" ) );
|
|
for ( i = 0; i < convert_enemies.size; i++ )
|
|
convert_enemies[ i ].targetname = "burger_town_enemies";
|
|
|
|
// Make sure we only have the guys inside the burger joint.
|
|
convert_enemies = getentarray( "burger_town_enemies", "targetname" );
|
|
burger_town_include = getent( "so_burger_town_enemy_include", "script_noteworthy" );
|
|
for ( i = convert_enemies.size - 1; i >= 0; i-- )
|
|
{
|
|
if ( !( convert_enemies[ i ] istouching( burger_town_include ) ) )
|
|
convert_enemies[ i ].targetname = "ignoreme";
|
|
}
|
|
}
|
|
|
|
so_defense_set_enemy_spawner_flags()
|
|
{
|
|
// Clear out some flags on enemies being used in the level.
|
|
convert_enemies = getentarray( "gas_station_enemies", "targetname" );
|
|
convert_enemies = array_merge( convert_enemies, getentarray( "bank_enemies", "targetname" ) );
|
|
convert_enemies = array_merge( convert_enemies, getentarray( "taco_enemies", "targetname" ) );
|
|
convert_enemies = array_merge( convert_enemies, getentarray( "burger_town_enemies", "targetname" ) );
|
|
foreach ( guy in convert_enemies )
|
|
{
|
|
if ( isdefined( guy.script_goalvolume ) )
|
|
guy.script_goalvolume = undefined;
|
|
if ( isdefined( guy.script_forcespawn ) )
|
|
guy.script_forcespawn = undefined;
|
|
}
|
|
}
|
|
|
|
hud_get_wave_list( title_text )
|
|
{
|
|
list = [];
|
|
if ( !isdefined( title_text ) )
|
|
return list;
|
|
|
|
switch( title_text )
|
|
{
|
|
case "SO_DEFENSE_INVASION_WAVE_1":
|
|
list[ 0 ] = &"SO_DEFENSE_INVASION_WAVE_1";
|
|
list[ 1 ] = &"SO_DEFENSE_INVASION_ALERT_20";
|
|
break;
|
|
|
|
case "SO_DEFENSE_INVASION_WAVE_2":
|
|
list[ 0 ] = &"SO_DEFENSE_INVASION_WAVE_2";
|
|
list[ 1 ] = &"SO_DEFENSE_INVASION_ALERT_30";
|
|
list[ 2 ] = &"SO_DEFENSE_INVASION_ALERT_HELLFIRE";
|
|
break;
|
|
|
|
case "SO_DEFENSE_INVASION_WAVE_3":
|
|
list[ 0 ] = &"SO_DEFENSE_INVASION_WAVE_3";
|
|
list[ 1 ] = &"SO_DEFENSE_INVASION_ALERT_40";
|
|
list[ 2 ] = &"SO_DEFENSE_INVASION_ALERT_HELI";
|
|
list[ 3 ] = &"SO_DEFENSE_INVASION_ALERT_HELLFIRE";
|
|
break;
|
|
|
|
case "SO_DEFENSE_INVASION_WAVE_4":
|
|
list[ 0 ] = &"SO_DEFENSE_INVASION_WAVE_4";
|
|
list[ 1 ] = &"SO_DEFENSE_INVASION_ALERT_30_SKILLED";
|
|
list[ 2 ] = &"SO_DEFENSE_INVASION_ALERT_BTR80";
|
|
list[ 3 ] = &"SO_DEFENSE_INVASION_ALERT_HELLFIRE";
|
|
break;
|
|
|
|
case "SO_DEFENSE_INVASION_WAVE_5":
|
|
list[ 0 ] = &"SO_DEFENSE_INVASION_WAVE_5";
|
|
list[ 1 ] = &"SO_DEFENSE_INVASION_ALERT_40_SKILLED";
|
|
list[ 2 ] = &"SO_DEFENSE_INVASION_ALERT_BTR80";
|
|
list[ 3 ] = &"SO_DEFENSE_INVASION_ALERT_HELIS";
|
|
list[ 4 ] = &"SO_DEFENSE_INVASION_ALERT_HELLFIRE";
|
|
break;
|
|
|
|
default:
|
|
assertex( false, "so_defense_build_enemy_list() received an invalid title_text (" + title_text + ")" );
|
|
break;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------- |