1487 lines
31 KiB
Plaintext
1487 lines
31 KiB
Plaintext
#include maps\_utility;
|
|
#include common_scripts\utility;
|
|
#include maps\_hud_util;
|
|
#include maps\_specialops;
|
|
#include maps\_specialops_code;
|
|
#include maps\so_download_arcadia;
|
|
|
|
// make sure this is the same as in arcadia_code.gsc
|
|
STRYKER_SUPPRESSION_RADIUS = 1500 * 1500;
|
|
|
|
// -----------------------
|
|
// --- DOWNLOAD STUFF ---
|
|
// -----------------------
|
|
so_download_objective_init( objIdx, objStr )
|
|
{
|
|
level.downloadObjectiveStr = objStr;
|
|
level.downloadObjectiveIdx = objIdx;
|
|
level.downloadsComplete = 0;
|
|
|
|
level.downloads = GetStructArray( "download", "targetname" );
|
|
|
|
foreach( index, download in level.downloads )
|
|
{
|
|
download.objPos = index;
|
|
}
|
|
|
|
// first one sets the objective
|
|
Objective_Add( level.downloadObjectiveIdx, "current", objStr, level.downloads[ 0 ].origin );
|
|
|
|
// the rest are added as additional positions
|
|
for( i = 1; i < level.downloads.size; i++ )
|
|
{
|
|
download = level.downloads[ i ];
|
|
Objective_AdditionalPosition( level.downloadObjectiveIdx, download.objPos, download.origin );
|
|
}
|
|
|
|
array_thread( level.players, ::ent_flag_init, "download_hint_on" );
|
|
array_thread( level.downloads, ::download_obj_setup );
|
|
}
|
|
|
|
// sets up each DSM group and starts it thinking
|
|
download_obj_setup()
|
|
{
|
|
computer = Spawn( "script_model", self.origin );
|
|
computer SetModel( "com_laptop_rugged_open" );
|
|
computer.angles = self.angles;
|
|
|
|
dsmSpot = GetStruct( self.target, "targetname" );
|
|
dsm = Spawn( "script_model", dsmSpot.origin );
|
|
dsm SetModel( "mil_wireless_dsm" );
|
|
dsm.angles = dsmSpot.angles;
|
|
|
|
dsm_obj = Spawn( "script_model", dsm.origin );
|
|
dsm_obj SetModel( "mil_wireless_dsm_obj" );
|
|
dsm_obj.angles = dsm.angles;
|
|
|
|
dsm Hide();
|
|
|
|
self.computer = computer;
|
|
self.dsm = dsm;
|
|
self.dsm_obj = dsm_obj;
|
|
|
|
// self.chargeTarget = Spawn( "script_origin", self.origin ); // for AIs to target
|
|
|
|
self.trig = GetEnt( self.target, "targetname" );
|
|
self.trig download_trig_sethintstring();
|
|
|
|
self thread download_obj_think();
|
|
}
|
|
|
|
download_trig_sethintstring()
|
|
{
|
|
self SetHintString( &"SO_DOWNLOAD_ARCADIA_DSM_USE_HINT" );
|
|
}
|
|
|
|
download_trig_clearhintstring()
|
|
{
|
|
self SetHintString( "" );
|
|
}
|
|
|
|
download_obj_think()
|
|
{
|
|
self ent_flag_init( "download_stopped" );
|
|
self.downloading = false;
|
|
self.firstDownload = true;
|
|
self.downloadTimeElapsed = 0;
|
|
self.filesDone = 0;
|
|
self.download_complete = false;
|
|
|
|
while( 1 )
|
|
{
|
|
self.trig waittill( "trigger" );
|
|
|
|
// swap the models
|
|
self.dsm_obj Hide();
|
|
self.dsm Show();
|
|
|
|
self.trig download_trig_clearhintstring();
|
|
|
|
// downloads can be interrupted by enemies
|
|
success = self download_files();
|
|
|
|
// if we downloaded successfully then break out
|
|
if( success )
|
|
{
|
|
self.dsm Hide();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
self.trig download_trig_sethintstring();
|
|
self.dsm Hide();
|
|
self.dsm_obj Show();
|
|
}
|
|
}
|
|
|
|
// this one's done
|
|
level.downloadsComplete++;
|
|
self.download_complete = true;
|
|
|
|
thread download_obj_dialogue();
|
|
|
|
// if we got them all, complete the objective
|
|
if( level.downloadsComplete >= level.downloads.size )
|
|
{
|
|
Objective_Complete( level.downloadObjectiveIdx );
|
|
flag_set( "all_downloads_finished" );
|
|
}
|
|
// otherwise remove this position from the objective
|
|
else
|
|
{
|
|
Objective_AdditionalPosition( level.downloadObjectiveIdx, self.objPos, ( 0, 0, 0 ) );
|
|
|
|
// give "objective updated" message and ping locations
|
|
Objective_String( level.downloadObjectiveIdx, level.downloadObjectiveStr );
|
|
Objective_Ring( level.downloadObjectiveIdx );
|
|
}
|
|
}
|
|
|
|
download_obj_dialogue()
|
|
{
|
|
downloadsLeft = level.downloads.size - level.downloadsComplete;
|
|
|
|
if( downloadsLeft == 2 )
|
|
{
|
|
// "Good job, Hunter Two-One. Our intel indicates that there are two more laptops in the area - go find them and get their data."
|
|
so_radio_dialogue( "so_dwnld_hqr_gofindthem" );
|
|
return;
|
|
}
|
|
else if( downloadsLeft == 1 )
|
|
{
|
|
// "Stay frosty, Hunter Two-One, there's one laptop left."
|
|
so_radio_dialogue( "so_dwnld_hqr_onelaptop" );
|
|
return;
|
|
}
|
|
else if( downloadsLeft == 0 )
|
|
{
|
|
// "Nice work, Hunter Two-One. Now get back to the Stryker, we're pulling you out of the area."
|
|
so_radio_dialogue( "so_dwnld_hqr_pullingyouout" );
|
|
|
|
bugWaitTime = 30;
|
|
|
|
aliases = [];
|
|
// "Hunter Two-One, get back to the Stryker for extraction!"
|
|
aliases[ 0 ] = "so_dwnld_hqr_extraction";
|
|
// "Hunter Two-One, return to the Stryker to complete your mission!"
|
|
aliases[ 1 ] = "so_dwnld_hqr_completemission";
|
|
|
|
level endon( "stryker_extraction_done" );
|
|
|
|
while( 1 )
|
|
{
|
|
foreach( alias in aliases )
|
|
{
|
|
wait( bugWaitTime );
|
|
so_radio_dialogue( alias );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
so_radio_dialogue( alias )
|
|
{
|
|
if ( !flag( "special_op_terminated" ) )
|
|
{
|
|
radio_dialogue( alias );
|
|
}
|
|
}
|
|
|
|
so_character_dialogue( alias )
|
|
{
|
|
if ( !flag( "special_op_terminated" ) )
|
|
{
|
|
self dialogue_queue( alias );
|
|
}
|
|
}
|
|
|
|
download_files()
|
|
{
|
|
downloadTime = level.DOWNLOAD_TIME - self.downloadTimeElapsed;
|
|
|
|
if( !flag( "first_download_started" ) )
|
|
{
|
|
flag_set( "first_download_started" );
|
|
|
|
// "Hunter Two-One, there are hostiles in the area that can wirelessly disrupt the data transfer."
|
|
thread so_radio_dialogue( "so_dwnld_hqr_wirelesslydisrupt" );
|
|
}
|
|
|
|
self.downloading = true;
|
|
self notify( "downloading" );
|
|
|
|
success = false;
|
|
|
|
startTime = GetTime();
|
|
endTime = GetTime() + milliseconds( downloadTime );
|
|
|
|
// only spawn the defenders once, in case the download gets interrupted
|
|
if( !IsDefined( self.defenders_spawned ) )
|
|
{
|
|
self.defenders_spawned = true;
|
|
self.waveDead = false;
|
|
self thread download_files_enemies_attack( downloadTime );
|
|
}
|
|
|
|
self delaythread( 0.1, ::download_files_wave_death ); // delay so we can start waiting for the notify before it fires
|
|
self thread download_files_catch_interrupt();
|
|
|
|
self ent_flag_clear( "download_stopped" );
|
|
|
|
// This function returns the 'new' timeLeft from within it since it has a lengthy intro
|
|
downloadTime = download_files_hud_countdown( downloadTime );
|
|
|
|
// If the wave is dead and the player triggered the download, then it'll do a SUPER fast download
|
|
// so we need to wait for the time instead of the interrupt.
|
|
if ( self.waveDead )
|
|
{
|
|
wait( downloadTime );
|
|
}
|
|
else
|
|
{
|
|
self waittill_any_timeout( downloadTime, "download_interrupted" );
|
|
}
|
|
|
|
self notify( "downloading_stopped" );
|
|
self.downloading = false;
|
|
|
|
self.downloadTimeElapsed += seconds( GetTime() - startTime );
|
|
|
|
// if we killed all the enemies or used all the time, it was successful
|
|
if( self.waveDead || GetTime() >= endTime )
|
|
{
|
|
success = true;
|
|
}
|
|
|
|
self ent_flag_set( "download_stopped" );
|
|
|
|
if( !success )
|
|
{
|
|
self thread download_files_hud_countdown_abort();
|
|
thread download_interrupt_dialogue();
|
|
}
|
|
else
|
|
{
|
|
self thread download_files_hud_finish();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
download_interrupt_dialogue()
|
|
{
|
|
wait( 1 );
|
|
|
|
lines = [];
|
|
|
|
// "Hunter Two-One, the download has been interrupted! You'll have to restart the data transfer manually."
|
|
lines[ 0 ] = "so_dwnld_hqr_restartmanually";
|
|
|
|
// "Hunter Two-One, hostiles have interrupted the download! Get back there and manually resume the transfer!"
|
|
lines[ 1 ] = "so_dwnld_hqr_getbackrestart";
|
|
|
|
if( !IsDefined( level.interruptLineIndex ) || ( level.interruptLineIndex >= lines.size ) )
|
|
{
|
|
level.interruptLineIndex = 0;
|
|
}
|
|
|
|
so_radio_dialogue( lines[ level.interruptLineIndex ] );
|
|
|
|
level.interruptLineIndex++;
|
|
}
|
|
|
|
download_files_wave_death()
|
|
{
|
|
self endon( "downloading_stopped" );
|
|
|
|
while( !self.waveDead )
|
|
{
|
|
wait( 0.05 );
|
|
}
|
|
|
|
self notify( "download_interrupted" );
|
|
}
|
|
|
|
dsm_wait( hudelem, time )
|
|
{
|
|
hudelem SetValue( 0 );
|
|
wait( 1 );
|
|
|
|
delay = 0.1;
|
|
steps = time / delay;
|
|
for( i = 0; i < steps; i++ )
|
|
{
|
|
num = clamp( int( ( i / steps ) * 100 ), 0, 100 );
|
|
hudelem SetValue( num );
|
|
wait( delay );
|
|
}
|
|
|
|
hudelem SetValue( 100 );
|
|
wait( 0.5 );
|
|
}
|
|
|
|
download_files_hud_countdown( timeLeft )
|
|
{
|
|
functionStartTime = GetTime();
|
|
|
|
x = -200;
|
|
y_offset = 50;
|
|
if ( IsSplitscreen() )
|
|
{
|
|
x = -220;
|
|
y_offset = 120;
|
|
}
|
|
|
|
if( self.firstDownload )
|
|
{
|
|
y = y_offset + ( level.hud_download_count * 20 );
|
|
level.hud_download_count++;
|
|
self.hudelem_y = y;
|
|
|
|
// SETUP
|
|
hudelem = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem.alignX = "right";
|
|
hudelem SetPulseFX( 30, 900000, 700 );
|
|
hudelem.label = &"SO_DOWNLOAD_ARCADIA_DSM_FRAME"; // DSM v6.04
|
|
wait 0.65;
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status SetPulseFX( 30, 900000, 700 );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DSM_INIT"; // Initiliazing
|
|
|
|
dsm_wait( hudelem_status, 2.5 );
|
|
|
|
hudelem_status Destroy();
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status SetPulseFX( 30, 900000, 700 );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DSM_CONNECTING"; // Connecting
|
|
|
|
dsm_wait( hudelem_status, 0.9 );
|
|
|
|
hudelem_status Destroy();
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status SetPulseFX( 30, 900000, 700 );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DSM_LOGIN"; // Bypassing Login
|
|
|
|
dsm_wait( hudelem_status, 3.75 );
|
|
|
|
hudelem_status Destroy();
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status SetPulseFX( 30, 900000, 700 );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DSM_LOCATE"; // Locating Files
|
|
|
|
dsm_wait( hudelem_status, 1.5 );
|
|
|
|
hudelem Destroy();
|
|
hudelem_status Destroy();
|
|
|
|
self.firstDownload = false;
|
|
}
|
|
|
|
// START COUNTDOWN
|
|
// old -205
|
|
x = -250;
|
|
x_offset = x + 170;
|
|
hudelem = get_countdown_hud( x, self.hudelem_y, undefined, true );
|
|
hudelem SetPulseFX( 30, 900000, 700 );
|
|
hudelem.label = &"SO_DOWNLOAD_ARCADIA_DSM_PROGRESS"; // Files copied:
|
|
|
|
hudelem_status = get_download_state_hud( x_offset, self.hudelem_y, undefined, true );
|
|
hudelem_status.alignX = "right";
|
|
hudelem_status SetPulseFX( 30, 900000, 700 );
|
|
|
|
hudelem_status_total = get_countdown_hud( x_offset, self.hudelem_y, undefined, true );
|
|
hudelem_status_total SetPulseFX( 30, 900000, 700 );
|
|
|
|
if( !IsDefined( self.totalFiles ) )
|
|
{
|
|
self.totalFiles = RandomIntRange( level.NUM_FILES_MIN, level.NUM_FILES_MAX );
|
|
}
|
|
|
|
hudelem_status_total.label = &"SO_DOWNLOAD_ARCADIA_DSM_TOTALFILES";
|
|
hudelem_status_total SetValue( self.totalfiles );
|
|
|
|
timeLeft -= seconds( GetTime() - functionStartTime );
|
|
|
|
// If area is clear, SUPER fast download
|
|
if ( self.waveDead && timeLeft > 3 )
|
|
{
|
|
timeLeft = 3;
|
|
}
|
|
|
|
self thread download_files_update( hudelem, hudelem_status, hudelem_status_total, timeLeft );
|
|
|
|
return timeLeft;
|
|
}
|
|
|
|
download_files_update( hudelem, hudelem_status, hudelem_status_total, timeLeft )
|
|
{
|
|
// POLISH: it would be cool if some files were faster/slower to download
|
|
endtime = GetTime() + milliseconds( timeLeft );
|
|
filesLeft = self.totalfiles - self.filesDone;
|
|
incrementTime = 0.05; // timeLeft / filesLeft
|
|
file_inc = incrementTime / ( timeLeft / filesLeft );
|
|
|
|
while( GetTime() < endtime && !self ent_flag( "download_stopped" ) )
|
|
{
|
|
self.filesDone += file_inc;
|
|
hudelem_status SetValue( int( self.filesDone ) );
|
|
wait( incrementTime );
|
|
}
|
|
|
|
if ( !self ent_flag( "download_stopped" ) )
|
|
{
|
|
hudelem_status SetValue( self.totalfiles );
|
|
}
|
|
|
|
hudelem Destroy();
|
|
hudelem_status Destroy();
|
|
hudelem_status_total Destroy();
|
|
}
|
|
|
|
download_files_hud_countdown_abort()
|
|
{
|
|
x = -200;
|
|
if ( IsSplitscreen() )
|
|
{
|
|
x = -220;
|
|
}
|
|
hudelem = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem.alignx = "right";
|
|
//hudelem.label = "";
|
|
//hudelem SetText( "ERROR: DOWNLOAD INTERRUPTED!" );
|
|
hudelem.label = &"SO_DOWNLOAD_ARCADIA_DSM_FRAME"; // DSM v6.04
|
|
hudelem.fontScale = 1.4;
|
|
hudelem set_hud_red();
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DOWNLOAD_INTERRUPTED"; // DOWNLOAD INTERRUPTED
|
|
hudelem_status.fontScale = 1.4;
|
|
hudelem_status set_hud_red();
|
|
|
|
hudelem thread hud_blink();
|
|
hudelem_status thread hud_blink();
|
|
|
|
self waittill_any_timeout( 25, "downloading" );
|
|
hudelem Destroy();
|
|
hudelem_status Destroy();
|
|
}
|
|
|
|
download_files_hud_finish()
|
|
{
|
|
x = -200;
|
|
if ( IsSplitscreen() )
|
|
{
|
|
x = -220;
|
|
}
|
|
hudelem = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem.alignx = "right";
|
|
hudelem.label = &"SO_DOWNLOAD_ARCADIA_DSM_FRAME"; // DSM v6.04
|
|
hudelem.fontScale = 1.4;
|
|
hudelem set_hud_blue();
|
|
|
|
hudelem_status = get_countdown_hud( x, self.hudelem_y );
|
|
hudelem_status.label = &"SO_DOWNLOAD_ARCADIA_DOWNLOAD_COMPLETE"; // DOWNLOAD COMPLETE
|
|
hudelem_status.fontScale = 1.4;
|
|
hudelem_status set_hud_blue();
|
|
|
|
hudelem thread hud_blink();
|
|
hudelem_status thread hud_blink();
|
|
|
|
wait( 10 );
|
|
hudelem Destroy();
|
|
hudelem_status Destroy();
|
|
}
|
|
|
|
|
|
hud_blink( maxFontScale )
|
|
{
|
|
self endon( "death" );
|
|
|
|
fadeTime = 0.1;
|
|
stateTime = 0.5;
|
|
|
|
while( 1 )
|
|
{
|
|
self FadeOverTime( fadeTime );
|
|
self.alpha = 1;
|
|
wait( stateTime );
|
|
|
|
self FadeOverTime( fadeTime );
|
|
self.alpha = 0;
|
|
wait( stateTime );
|
|
}
|
|
}
|
|
|
|
download_files_enemies_attack( downloadTime )
|
|
{
|
|
self.spawned = [];
|
|
self.totalDefenders = 0;
|
|
|
|
self.totalDefenders = self download_files_nearby_defenders_charge();
|
|
self.totalDefenders += self download_files_spawn_chargers();
|
|
|
|
self thread download_enemies_attack_dialogue();
|
|
|
|
while( self.spawned.size < self.totalDefenders )
|
|
{
|
|
wait( 0.05 );
|
|
}
|
|
|
|
waittill_dead_or_dying( self.spawned, ( self.spawned.size - level.NUM_ENEMIES_LEFT_TOLERANCE ), downloadTime );
|
|
|
|
self.waveDead = true;
|
|
}
|
|
|
|
download_enemies_attack_dialogue()
|
|
{
|
|
alias = undefined;
|
|
waitTime = undefined;
|
|
alias2 = undefined;
|
|
|
|
switch( self.script_parameters )
|
|
{
|
|
case "download_1_charger":
|
|
// "Hunter Two-One, ten-plus foot-mobiles approaching from the east!"
|
|
alias = "so_dwnld_stk_tenfootmobiles";
|
|
break;
|
|
|
|
case "download_2_charger":
|
|
// "We've got activity to the west, they're coming from the light brown mansion!"
|
|
alias = "so_dwnld_stk_brownmansion";
|
|
break;
|
|
|
|
case "download_3_charger":
|
|
// "Hostiles spotted across the street, they're moving to your position!"
|
|
alias = "so_dwnld_stk_acrossstreet";
|
|
|
|
waitTime = 10;
|
|
|
|
// "Hunter Two-One, you got movement right outside your location!"
|
|
alias2 = "so_dwnld_stk_gotmovement";
|
|
|
|
break;
|
|
}
|
|
|
|
if( IsDefined( alias ) )
|
|
{
|
|
so_radio_dialogue( alias );
|
|
}
|
|
|
|
if( IsDefined( waitTime ) )
|
|
{
|
|
wait( waitTime );
|
|
}
|
|
|
|
if( IsDefined( alias2 ) )
|
|
{
|
|
so_radio_dialogue( alias2 );
|
|
}
|
|
}
|
|
|
|
download_files_nearby_defenders_charge()
|
|
{
|
|
axis = GetAiArray( "axis" );
|
|
close = [];
|
|
|
|
foreach( guy in axis )
|
|
{
|
|
if( Distance( guy.origin, self.origin ) <= level.NEARBY_CHARGE_RADIUS )
|
|
{
|
|
close[ close.size ] = guy;
|
|
self.spawned[ self.spawned.size ] = guy;
|
|
}
|
|
}
|
|
|
|
array_thread( close, ::defender_charge_dsm, self );
|
|
|
|
return close.size;
|
|
}
|
|
|
|
download_files_spawn_chargers()
|
|
{
|
|
chargers = GetEntArray( self.script_parameters, "targetname" );
|
|
chargers = array_randomize( chargers );
|
|
array_thread( chargers, ::download_files_spawn_charger, self );
|
|
|
|
return chargers.size;
|
|
}
|
|
|
|
download_files_spawn_charger( download )
|
|
{
|
|
self script_delay();
|
|
|
|
wait( RandomFloat( 8 ) ); // we don't want all of a group of guys rolling exactly together
|
|
|
|
guy = self spawn_ai();
|
|
|
|
if( spawn_failed( guy ) )
|
|
{
|
|
download.totalDefenders--; // don't count him toward wave completion if he couldn't spawn
|
|
return;
|
|
}
|
|
|
|
download.spawned[ download.spawned.size ] = guy;
|
|
guy thread defender_charge_dsm( download );
|
|
}
|
|
|
|
defender_charge_dsm( download )
|
|
{
|
|
goal_ent = GetEnt( download.script_linkto, "script_linkname" );
|
|
|
|
self.goalradius = 1800;
|
|
self SetGoalEntity( goal_ent );
|
|
self thread ai_delayed_seek_think( download, goal_ent );
|
|
}
|
|
|
|
ai_delayed_seek_think( download, goal_ent )
|
|
{
|
|
self endon( "death" );
|
|
|
|
// player1_max_seekers = 0;
|
|
// player2_max_seekers = 0;
|
|
//
|
|
// foreach ( player in level.players )
|
|
// {
|
|
// num = 3;
|
|
// switch ( player.gameskill )
|
|
// {
|
|
// case 0:
|
|
// case 1:
|
|
// num = 3;
|
|
// break;
|
|
// case 2:
|
|
// case 3:
|
|
// num = 5;
|
|
// break;
|
|
// }
|
|
//
|
|
// if ( player == level.player )
|
|
// {
|
|
// player1_max_seekers = num;
|
|
// }
|
|
// else
|
|
// {
|
|
// player2_max_seekers = num;
|
|
// }
|
|
// }
|
|
|
|
|
|
player1_max_seekers = 3;
|
|
player2_max_seekers = 3;
|
|
|
|
// secondaries = GetEntArray( primary.target, "targetname" );
|
|
|
|
while ( IsAlive( self ) )
|
|
{
|
|
if ( self.goalradius > 200 )
|
|
{
|
|
self.goalradius -= 200;
|
|
}
|
|
|
|
if ( self.goalradius < 200 )
|
|
{
|
|
self.goalradius = 200;
|
|
}
|
|
|
|
count = 0;
|
|
player1_seek_count = 0;
|
|
player2_seek_count = 0;
|
|
ais = GetAiArray( "axis" );
|
|
foreach ( ai in ais )
|
|
{
|
|
if ( IsDefined( ai.seeking_player ) )
|
|
{
|
|
if ( ai.seeking_player == level.player )
|
|
{
|
|
player1_seek_count++;
|
|
}
|
|
else if( IsDefined( level.player2 ) && ai.seeking_player == level.player2 )
|
|
{
|
|
player2_seek_count++;
|
|
}
|
|
}
|
|
|
|
if ( ai == self )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( DistanceSquared( ai.origin, goal_ent.origin ) < 250 * 250 )
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if ( count > 2 || download.download_complete )
|
|
{
|
|
if ( !IsDefined( self.seeking_player ) )
|
|
{
|
|
array = level.players;
|
|
array = array_randomize( array );
|
|
|
|
goal_player = undefined;
|
|
|
|
foreach ( player in array )
|
|
{
|
|
if ( player == level.player && player1_seek_count < player1_max_seekers )
|
|
{
|
|
goal_player = level.player;
|
|
break;
|
|
}
|
|
else if ( IsDefined( level.player2 ) && player == level.player2 && player2_seek_count < player2_max_seekers )
|
|
{
|
|
goal_player = level.player2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( IsDefined( goal_player ) )
|
|
{
|
|
self.seeking_player = goal_player;
|
|
|
|
// Let's seek out the player
|
|
self SetGoalEntity( goal_player );
|
|
self.goalradius = 1000;
|
|
}
|
|
}
|
|
|
|
// The AI still does not have a proper goal, then increase their goalradius
|
|
if ( !IsDefined( self.seek_player ) )
|
|
{
|
|
self.goalradius = 1000;
|
|
}
|
|
}
|
|
|
|
wait( RandomFloatRange( 6, 9 ) );
|
|
}
|
|
}
|
|
|
|
download_files_catch_interrupt()
|
|
{
|
|
self endon( "downloading_stopped" );
|
|
|
|
while( 1 )
|
|
{
|
|
axis = GetAiArray( "axis" );
|
|
|
|
foreach( guy in axis )
|
|
{
|
|
if( self ai_near_download( guy ) )
|
|
{
|
|
self notify( "download_interrupted" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
wait( 2 );
|
|
}
|
|
}
|
|
|
|
ai_near_download( guy )
|
|
{
|
|
verticalDist = abs( guy GetTagOrigin( "J_SpineLower" )[ 2 ] - self.origin[ 2 ] );
|
|
if( verticalDist > 50 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( Distance( guy.origin, self.origin ) < level.DOWNLOAD_INTERRUPT_RADIUS )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// ----------------
|
|
// --- STRYKER ---
|
|
// ----------------
|
|
stryker_think()
|
|
{
|
|
stryker = maps\_vehicle::spawn_vehicle_from_targetname( "stryker" );
|
|
ASSERT( IsDefined( stryker ) );
|
|
level.stryker = stryker;
|
|
|
|
// hack so we don't get sound asserts
|
|
org = Spawn( "script_origin", stryker.origin );
|
|
org LinkTo( stryker );
|
|
org.animname = "foley";
|
|
level.foley = org;
|
|
|
|
CreateThreatBiasGroup( "stryker" );
|
|
CreateThreatBiasGroup( "stryker_ignoreme" );
|
|
stryker.threatBiasGroup = "stryker";
|
|
SetIgnoreMeGroup( "stryker_ignoreme", "stryker" );
|
|
|
|
stryker.target = "stryker_pathstart";
|
|
pathStart = GetVehicleNode( stryker.target, "targetname" );
|
|
stryker AttachPath( pathStart );
|
|
|
|
stryker.veh_pathtype = "follow";
|
|
stryker vehPhys_DisableCrashing();
|
|
stryker maps\_vehicle::godon();
|
|
stryker setVehicleLookAtText( "Honey Badger", &"" );
|
|
|
|
foreach( player in level.players )
|
|
{
|
|
// Workaround for setting the C4 actionslot, setting player.remotemissile_actionslot swaps the actionslot
|
|
player.remotemissile_actionslot = 4;
|
|
level thread maps\arcadia_code::laser_targeting_device( player );
|
|
}
|
|
|
|
level.stryker.lastTarget = level.stryker;
|
|
stryker maps\arcadia_stryker::setup_stryker_modes();
|
|
stryker thread maps\arcadia_stryker::stryker_setmode_ai();
|
|
stryker thread stryker_so_download_arcadia_laser_reminder_dialogue();
|
|
|
|
stryker thread stryker_greenlight_enemies_in_suppression_zone();
|
|
stryker thread stryker_disable_laser_reminder_thread();
|
|
|
|
// first move: go to the end of the covered bridge when player moves out of the way
|
|
waittill_both_players_touch_targetname( "trig_bridge_end" );
|
|
stryker StartPath();
|
|
firstMoveNode = GetVehicleNode( "vnode_bridge", "script_noteworthy" );
|
|
stryker stryker_move_to_node( firstMoveNode, false );
|
|
|
|
stryker thread stryker_move_with_players();
|
|
stryker thread stryker_extraction();
|
|
}
|
|
|
|
stryker_so_download_arcadia_laser_reminder_dialogue()
|
|
{
|
|
foreach ( player in level.players )
|
|
{
|
|
player thread so_laser_input();
|
|
}
|
|
|
|
flag_wait( "intro_dialogue_done" );
|
|
|
|
self thread stryker_laser_reminder_dialog_prethink();
|
|
|
|
// "All Hunter units, Badger One will not engage targets without your explicit authorization."
|
|
so_radio_dialogue( "so_dwnld_stk_explicitauth" );
|
|
|
|
self thread so_laser_hint_print();
|
|
// self thread maps\arcadia_code::laser_hint_print();
|
|
self thread maps\arcadia_code::stryker_dialog();
|
|
|
|
if( flag( "used_laser" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
level endon( "used_laser" );
|
|
self endon( "laser_coordinates_received" );
|
|
|
|
wait( 45 );
|
|
|
|
while ( flag( "no_living_enemies" ) )
|
|
{
|
|
wait( 5 );
|
|
}
|
|
|
|
// "Hunter Two-One, I repeat, Badger One is not authorized to engage targets that you haven't designated."
|
|
so_radio_dialogue( "so_dwnld_stk_designated" );
|
|
|
|
wait( 45 );
|
|
|
|
while ( flag( "no_living_enemies" ) )
|
|
{
|
|
wait( 5 );
|
|
}
|
|
|
|
// "Hunter Two-One, we can't fire on enemies without your authorization!"
|
|
so_radio_dialogue( "so_dwnld_stk_cantfire" );
|
|
}
|
|
|
|
so_laser_hint_print()
|
|
{
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( !IsAlive( player ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
player thread so_laser_hint_print_internal();
|
|
}
|
|
}
|
|
|
|
so_laser_input()
|
|
{
|
|
self NotifyOnPlayerCommand( "use_laser", "+actionslot 4" );
|
|
self NotifyOnPlayerCommand( "fired_laser", "+attack" );
|
|
|
|
if ( flag( "used_laser" ) )
|
|
{
|
|
self ent_flag_set( "used_laser1" );
|
|
self ent_flag_set( "used_laser2" );
|
|
}
|
|
|
|
self waittill_either( "use_laser", "used_laser" );
|
|
self ent_flag_set( "used_laser1" );
|
|
|
|
if ( flag( "used_laser" ) )
|
|
{
|
|
self ent_flag_set( "used_laser1" );
|
|
self ent_flag_set( "used_laser2" );
|
|
}
|
|
|
|
self waittill_either( "fired_laser", "used_laser" );
|
|
self ent_flag_set( "used_laser2" );
|
|
}
|
|
|
|
so_laser_hint_print_internal()
|
|
{
|
|
self thread display_hint( "use_laser1" );
|
|
self ent_flag_wait( "used_laser1" );
|
|
|
|
wait( 0.1 );
|
|
|
|
self thread display_hint( "use_laser2" );
|
|
}
|
|
|
|
so_stop_laser_hint1()
|
|
{
|
|
if ( flag( "used_laser" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( self ent_flag( "used_laser1" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
so_stop_laser_hint2()
|
|
{
|
|
if ( flag( "used_laser" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( self ent_flag( "used_laser2" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
stryker_laser_reminder_dialog_prethink()
|
|
{
|
|
self waittill( "laser_coordinates_received" );
|
|
|
|
wait( 30 );
|
|
self thread maps\arcadia_stryker::stryker_laser_reminder_dialog();
|
|
}
|
|
|
|
stryker_greenlight_enemies_in_suppression_zone()
|
|
{
|
|
while( 1 )
|
|
{
|
|
if( !IsDefined( self.targetSearchOrigin ) )
|
|
{
|
|
wait( 0.1 );
|
|
continue;
|
|
}
|
|
|
|
axis = GetAiArray( "axis" );
|
|
|
|
foreach( guy in axis )
|
|
{
|
|
if( !IsAlive( guy ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( DistanceSquared( guy.origin, self.targetSearchOrigin ) <= STRYKER_SUPPRESSION_RADIUS )
|
|
{
|
|
// he's in the suppression area
|
|
if( guy GetThreatBiasGroup() == "stryker_ignoreme" )
|
|
{
|
|
guy SetThreatBiasGroup( "axis" );
|
|
self thread stryker_enemy_reset_to_ignore( guy );
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( 0.25 );
|
|
}
|
|
}
|
|
|
|
stryker_disable_laser_reminder_thread()
|
|
{
|
|
while ( 1 )
|
|
{
|
|
wait( 0.5 );
|
|
|
|
count = 0;
|
|
enemies = GetAiArray( "axis" );
|
|
count = enemies.size;
|
|
|
|
if ( !count )
|
|
{
|
|
flag_set( "no_living_enemies" );
|
|
}
|
|
else
|
|
{
|
|
flag_clear( "no_living_enemies" );
|
|
}
|
|
}
|
|
}
|
|
|
|
stryker_enemy_reset_to_ignore( guy )
|
|
{
|
|
guy endon( "death" );
|
|
|
|
while( IsDefined( self.targetSearchOrigin ) )
|
|
{
|
|
wait( 0.1 );
|
|
}
|
|
|
|
if( IsAlive( guy ) )
|
|
{
|
|
guy SetThreatBiasGroup( "stryker_ignoreme" );
|
|
}
|
|
}
|
|
|
|
|
|
stryker_move_with_players()
|
|
{
|
|
level endon( "all_downloads_finished" );
|
|
|
|
trig1 = GetEnt( "trig_stryker_house1", "targetname" );
|
|
node1 = GetVehicleNode( "vnode_house1", "script_noteworthy" );
|
|
trig2 = GetEnt( "trig_stryker_house2", "targetname" );
|
|
node2 = GetVehicleNode( "vnode_house2", "script_noteworthy" );
|
|
trig3 = GetEnt( "trig_stryker_house3", "targetname" );
|
|
node3 = GetVehicleNode( "vnode_house3", "script_noteworthy" );
|
|
|
|
trigs[ 0 ] = trig1;
|
|
trigs[ 1 ] = trig2;
|
|
trigs[ 2 ] = trig3;
|
|
nodes[ 0 ] = node1;
|
|
nodes[ 1 ] = node2;
|
|
nodes[ 2 ] = node3;
|
|
|
|
timeInTrig = 5;
|
|
|
|
currNode = undefined;
|
|
|
|
while( 1 )
|
|
{
|
|
activeTrigIndex = undefined;
|
|
foreach( index, trig in trigs )
|
|
{
|
|
if( all_players_istouching( trig ) )
|
|
{
|
|
endTime = GetTime() + milliseconds( timeInTrig );
|
|
|
|
while( GetTime() < endTime )
|
|
{
|
|
if( !all_players_istouching( trig ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
|
|
if( GetTime() >= endTime )
|
|
{
|
|
// success
|
|
activeTrigIndex = index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( IsDefined( activeTrigIndex ) )
|
|
{
|
|
thenode = nodes[ activeTrigIndex ];
|
|
|
|
if( !IsDefined( currNode ) || ( IsDefined( currNode ) && currNode != thenode ) )
|
|
{
|
|
currNode = thenode;
|
|
self stryker_move_to_node( thenode );
|
|
}
|
|
}
|
|
|
|
wait( 0.5 );
|
|
}
|
|
}
|
|
|
|
stryker_move_to_node( node, doDialogue )
|
|
{
|
|
goalradius = 96;
|
|
|
|
// move forward or backward?
|
|
if( node.origin[ 0 ] < self.origin[ 0 ] )
|
|
{
|
|
self.veh_pathdir = "reverse";
|
|
self.veh_transmission = "reverse";
|
|
self maps\_vehicle::vehicle_wheels_backward();
|
|
}
|
|
else
|
|
{
|
|
self.veh_pathdir = "forward";
|
|
self.veh_transmission = "forward";
|
|
self maps\_vehicle::vehicle_wheels_forward();
|
|
}
|
|
|
|
if( !IsDefined( doDialogue ) || ( IsDefined( doDialogue ) && doDialogue ) )
|
|
{
|
|
self notify( "resuming speed" );
|
|
}
|
|
|
|
self maps\_vehicle::vehicle_setspeed_wrapper( 10, 5, "stryker_rolling" );
|
|
|
|
//waittill at node
|
|
while( Distance( self.origin, node.origin ) > goalradius )
|
|
{
|
|
wait( 0.05 );
|
|
}
|
|
|
|
self maps\_vehicle::vehicle_setspeed_wrapper( 0, 5, "stryker_stopping" );
|
|
|
|
if( !IsDefined( doDialogue ) || ( IsDefined( doDialogue ) && doDialogue ) )
|
|
{
|
|
self notify( "wait for gate" );
|
|
}
|
|
}
|
|
|
|
stryker_extraction()
|
|
{
|
|
flag_wait( "all_downloads_finished" );
|
|
|
|
//array_thread( level.players, ::laser_targeting_device_remove );
|
|
// level.stryker thread maps\arcadia_stryker::stryker_setmode_ai();
|
|
|
|
node = GetVehicleNode( "vnode_house1", "script_noteworthy" );
|
|
level.stryker thread stryker_move_to_node( node );
|
|
|
|
strykerObjIdx = level.downloadObjectiveIdx + 1;
|
|
Objective_Add( strykerObjIdx, "current", &"SO_DOWNLOAD_ARCADIA_OBJ_EXTRACT", self.origin );
|
|
Objective_OnEntity( strykerObjIdx, self );
|
|
|
|
thread stryker_extraction_enemies();
|
|
|
|
while( !all_players_closeto( self, 280 ) )
|
|
{
|
|
wait( 0.05 );
|
|
}
|
|
|
|
flag_set( "stryker_extraction_done" );
|
|
|
|
// Stop the Stryker dialogue
|
|
level notify( "golf_course_mansion" );
|
|
|
|
fade_challenge_out();
|
|
}
|
|
|
|
stryker_extraction_enemies()
|
|
{
|
|
org = level.players[ 0 ].origin;
|
|
|
|
// get the average origin if there's more than one player
|
|
if( level.players.size > 1 )
|
|
{
|
|
for( i = 1; i < level.players.size; i++ )
|
|
{
|
|
org += level.players[ i ].origin;
|
|
}
|
|
|
|
org /= level.players.size;
|
|
}
|
|
|
|
spawners = GetSpawnerTeamArray( "axis" );
|
|
spawners = get_array_of_farthest( org, spawners );
|
|
arr = [];
|
|
|
|
foreach( index, spawner in spawners )
|
|
{
|
|
if( index > 15 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
spawner.count = 3;
|
|
arr[ arr.size ] = spawner;
|
|
}
|
|
|
|
array_thread( spawners, ::stryker_extraction_spawn_enemy );
|
|
}
|
|
|
|
stryker_extraction_spawn_enemy()
|
|
{
|
|
wait( RandomFloat( 10 ) );
|
|
|
|
guy = self spawn_ai();
|
|
|
|
if( spawn_failed( guy ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
guy endon( "death" );
|
|
|
|
guy SetThreatBiasGroup( "axis" );
|
|
guy SetGoalPos( level.stryker.origin );
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------
|
|
// --- LASER TARGETING ---
|
|
// ------------------------
|
|
|
|
// Handles the usability of the laser weapon
|
|
player_laser_targeting_think()
|
|
{
|
|
is_laser_disabled = false;
|
|
|
|
while ( 1 )
|
|
{
|
|
disable_laser = should_laser_disabled();
|
|
|
|
if ( disable_laser )
|
|
{
|
|
if ( !is_laser_disabled )
|
|
{
|
|
is_laser_disabled = true;
|
|
self laser_targeting_device_remove();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( is_laser_disabled )
|
|
{
|
|
is_laser_disabled = false;
|
|
level thread maps\arcadia_code::laser_targeting_device( self );
|
|
}
|
|
}
|
|
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
should_laser_disabled()
|
|
{
|
|
// if ( self GetCurrentWeapon() == "claymore" )
|
|
// {
|
|
// return true;
|
|
// }
|
|
|
|
if ( !self ent_flag_exist( "coop_downed" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Disable the player if downed part2.
|
|
return self ent_flag( "coop_downed" ) && IsDefined( self.down_part2_proc_ran );
|
|
}
|
|
|
|
// self = a player
|
|
laser_targeting_device_remove()
|
|
{
|
|
self notify( "remove_laser_targeting_device" );
|
|
|
|
self SetWeaponHudIconOverride( "actionslot4", "none" );
|
|
|
|
if ( !self.laserForceOn )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self notify( "cancel_laser" );
|
|
self laserForceOff();
|
|
self allowFire( true );
|
|
self.laserForceOn = false;
|
|
}
|
|
|
|
// -----------------
|
|
// --- AI STUFF ---
|
|
// -----------------
|
|
so_download_arcadia_enemy_setup()
|
|
{
|
|
level.enemies = [];
|
|
|
|
allspawners = GetSpawnerTeamArray( "axis" );
|
|
array_thread( allspawners, ::add_spawn_function, ::so_download_arcadia_enemy_spawnfunc );
|
|
|
|
insideGuys = [];
|
|
outsideGuys = [];
|
|
|
|
foreach( spawner in allspawners )
|
|
{
|
|
if( !IsDefined( spawner.targetname ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( IsSubStr( spawner.targetname, "inside" ) )
|
|
{
|
|
insideGuys[ insideGuys.size ] = spawner;
|
|
}
|
|
else if( IsSubStr( spawner.targetname, "outside" ) )
|
|
{
|
|
outsideGuys[ outsideGuys.size ] = spawner;
|
|
}
|
|
}
|
|
|
|
array_thread( outsideGuys, ::add_spawn_function, ::so_download_arcadia_outside_enemy_spawnfunc );
|
|
|
|
level.enemyspawners = allspawners;
|
|
|
|
// DEPRECATED
|
|
//trigs = GetEntArray( "enemy_spawn", "targetname" );
|
|
//array_thread( trigs, ::so_download_arcadia_enemy_spawn_manager );
|
|
}
|
|
|
|
so_download_arcadia_enemy_spawnfunc()
|
|
{
|
|
if( RandomInt( 100 ) > 10 )
|
|
{
|
|
self SetThreatBiasGroup( "stryker_ignoreme" );
|
|
}
|
|
|
|
self pathrandompercent_set( 800 );
|
|
|
|
level.activeEnemies[ level.enemies.size ] = self;
|
|
self thread so_download_arcadia_enemy_deathcleanup();
|
|
}
|
|
|
|
so_download_arcadia_enemy_deathcleanup()
|
|
{
|
|
self waittill( "death" );
|
|
level.activeEnemies = array_remove( level.enemies, self );
|
|
}
|
|
|
|
so_download_arcadia_outside_enemy_spawnfunc()
|
|
{
|
|
self endon( "death" );
|
|
|
|
if( IsDefined( self.script_linkto ) && IsDefined( self.script_parameters ) )
|
|
{
|
|
retreatTrig = GetEnt( self.script_linkto, "script_linkname" );
|
|
retreatVol = GetEnt( self.script_parameters, "targetname" );
|
|
|
|
ASSERT( IsDefined( retreatTrig ) && IsDefined( retreatVol ) );
|
|
|
|
retreatTrig waittill( "trigger" );
|
|
|
|
self.combatMode = "ambush";
|
|
self SetGoalVolumeAuto( retreatVol );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// -------------------
|
|
// --- MISC UTILS ---
|
|
// -------------------
|
|
all_players_closeto( ent, dist )
|
|
{
|
|
foundOne = false;
|
|
foreach( player in level.players )
|
|
{
|
|
if( Distance( player.origin, ent.origin ) > dist)
|
|
{
|
|
foundOne = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( foundOne )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
waittill_both_players_touch_targetname( tn )
|
|
{
|
|
trig = GetEnt( tn, "targetname" );
|
|
touchers = [];
|
|
|
|
while( touchers.size < level.players.size )
|
|
{
|
|
trig waittill( "trigger", other );
|
|
|
|
if( IsPlayer( other ) && !is_in_array( touchers, other ) )
|
|
{
|
|
touchers[ touchers.size ] = other;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO refactor to use the _utility version
|
|
// kinda rough method to test if an AI is in view of the player - only checks three points (low, mid, high)
|
|
any_player_can_see_ai( ai )
|
|
{
|
|
feetOrigin = ai.origin;
|
|
if ( any_player_can_see_origin( feetOrigin ) )
|
|
return true;
|
|
|
|
midOrigin = ai GetTagOrigin( "J_SpineLower" );
|
|
if ( any_player_can_see_origin( midOrigin ) )
|
|
return true;
|
|
|
|
eyeOrigin = ai GetEye();
|
|
if ( any_player_can_see_origin( eyeOrigin ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
any_player_can_see_origin( origin )
|
|
{
|
|
foreach( player in level.players )
|
|
{
|
|
player.canSeeOrigin = true;
|
|
|
|
// FOV check
|
|
if( !level.player animscripts\battlechatter::pointInFov( origin ) )
|
|
{
|
|
player.canSeeOrigin = false;
|
|
}
|
|
|
|
// sight trace check
|
|
if( !SightTracePassed( player GetEye(), origin, true, player ) )
|
|
{
|
|
player.canSeeOrigin = false;
|
|
}
|
|
}
|
|
|
|
foreach( player in level.players )
|
|
{
|
|
if( player.canSeeOrigin )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
milliseconds( seconds )
|
|
{
|
|
return seconds * 1000;
|
|
}
|
|
|
|
seconds( milliseconds )
|
|
{
|
|
return milliseconds / 1000;
|
|
} |