1092 lines
28 KiB
Plaintext
1092 lines
28 KiB
Plaintext
#include common_scripts\utility;
|
|
#include maps\_utility;
|
|
#include maps\_anim;
|
|
#include maps\_stealth_utility;
|
|
|
|
/************************************************************************************************************/
|
|
/* SYSTEM UTILITIES */
|
|
/************************************************************************************************************/
|
|
|
|
//called by _stealth_accuracy_friendly
|
|
//called by _stealth_behavior_enemy
|
|
//called by _stealth_behavior_friendly
|
|
//called by _stealth_color_friendly
|
|
ai_message_handler_spotted( function, plugin_override )
|
|
{
|
|
self endon( "death" );
|
|
|
|
assertex( isdefined( plugin_override ), "plugin_override required, modify plugin script -z" );
|
|
plugin_override = plugin_override + "spotted";
|
|
|
|
self notify( plugin_override );
|
|
self endon( plugin_override );
|
|
|
|
switch( self.team )
|
|
{
|
|
case "allies":
|
|
while ( 1 )
|
|
{
|
|
self ent_flag_wait( "_stealth_enabled" );
|
|
|
|
self flag_wait( "_stealth_spotted" );
|
|
|
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
|
continue;
|
|
|
|
self thread [[ function ]]();
|
|
|
|
self flag_waitopen( "_stealth_spotted" );
|
|
}
|
|
break;
|
|
|
|
case "axis":
|
|
case "team3":
|
|
while ( 1 )
|
|
{
|
|
self ent_flag_wait( "_stealth_enabled" );
|
|
|
|
self stealth_group_spotted_flag_wait();
|
|
|
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
|
continue;
|
|
|
|
self thread [[ function ]]();
|
|
|
|
self stealth_group_spotted_flag_waitopen();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//called by _stealth_accuracy_friendly
|
|
//called by _stealth_behavior_enemy
|
|
//called by _stealth_behavior_friendly
|
|
//called by _stealth_color_friendly
|
|
ai_message_handler_hidden( function, plugin_override )
|
|
{
|
|
self endon( "death" );
|
|
|
|
assertex( isdefined( plugin_override ), "plugin_override required, modify plugin script -z" );
|
|
plugin_override = plugin_override + "hidden";
|
|
|
|
self notify( plugin_override );
|
|
self endon( plugin_override );
|
|
|
|
switch( self.team )
|
|
{
|
|
case "allies":
|
|
while ( 1 )
|
|
{
|
|
self ent_flag_wait( "_stealth_enabled" );
|
|
|
|
self flag_waitopen( "_stealth_spotted" );
|
|
|
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
|
continue;
|
|
|
|
self thread [[ function ]]();
|
|
|
|
self flag_wait( "_stealth_spotted" );
|
|
}
|
|
break;
|
|
|
|
case "axis":
|
|
case "team3":
|
|
while ( 1 )
|
|
{
|
|
self ent_flag_wait( "_stealth_enabled" );
|
|
|
|
self stealth_group_spotted_flag_waitopen();
|
|
|
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
|
continue;
|
|
|
|
self thread [[ function ]]();
|
|
|
|
self stealth_group_spotted_flag_wait();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
///ScriptDocBegin
|
|
"Name: ai_create_behavior_function( <name>, <key>, <function> )"
|
|
"Summary: sets up a behavior function that an AI using the stealth system will call in a specific situation, by putting an entry in the AI's ._stealth.behavior.ai_functions array."
|
|
"Module: Entity"
|
|
"MandatoryArg: <name>: the general category, for example, 'threat', 'animation', or 'event' (among others)."
|
|
"MandatoryArg: <key>: the specific subcategory, for example, 'heard_scream' or 'reset' (among others)."
|
|
"MandatoryArg: <function>: the function to run when the stealth script looks for this behavior function."
|
|
"Example: ai_create_behavior_function( "threat", "reset", ::cliffhanger_enemy_alert_level_reset );"
|
|
"SPMP: singleplayer"
|
|
///ScriptDocEnd
|
|
=============
|
|
*/
|
|
ai_create_behavior_function( name, key, function )
|
|
{
|
|
self._stealth.behavior.ai_functions[ name ][ key ] = function;
|
|
}
|
|
|
|
// used internally by the stealth system
|
|
ai_get_behavior_function( name, key )
|
|
{
|
|
return self._stealth.behavior.ai_functions[ name ][ key ];
|
|
}
|
|
|
|
/*
|
|
=============
|
|
///ScriptDocBegin
|
|
"Name: ai_set_goback_override_function( <function> )"
|
|
"Summary: sets up an "event" behavior function that an AI using the stealth system will call in a specific situation, by putting an entry in the AI's ._stealth.behavior.ai_functions[ "event" ] array. This function will be called between the AI forgetting about the threat, and the AI heading back to where he was patrolling previously."
|
|
"Module: Entity"
|
|
"MandatoryArg: <function>: the function to run when the AI forgets about an event, before he heads back to his previous patrol spot."
|
|
"Example: ai_create_event_goback_override_function( ::cliffhanger_enemy_alert_level_reset );"
|
|
"SPMP: singleplayer"
|
|
///ScriptDocEnd
|
|
=============
|
|
*/
|
|
ai_set_goback_override_function( function )
|
|
{
|
|
self._stealth.behavior.goback_startFunc = function;
|
|
}
|
|
|
|
stealth_event_validate( key )
|
|
{
|
|
if ( key == "heard_scream" || key == "doFlashBanged" || key == "explode" )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ASSERTMSG( "valid values for stealth events are 'heard_scream', 'doFlashBanged', and 'explode'. you tried: " + key );
|
|
return false;
|
|
}
|
|
|
|
|
|
stealth_debug_print( msg )
|
|
{
|
|
/#
|
|
if ( !flag( "_stealth_enabled" ) )
|
|
return;
|
|
|
|
type = undefined;
|
|
name = undefined;
|
|
|
|
if ( isdefined( self.script_noteworthy ) )
|
|
{
|
|
type = "Script_noteworthy";
|
|
name = self.script_noteworthy;
|
|
}
|
|
else if ( isdefined( self.targetname ) )
|
|
{
|
|
type = "Targetname";
|
|
name = self.targetname;
|
|
}
|
|
|
|
actor = "Actor -> ID: " + self.unique_id + " Export: " + self.export;
|
|
|
|
if ( isdefined( type ) )
|
|
{
|
|
actor += " " + type + ": " + name;
|
|
}
|
|
txt = "STEALTH DEBUG PRINT: " + actor + "\n " + msg;
|
|
|
|
println( txt );
|
|
|
|
if ( getdvarint( "stealth_debug_prints" ) == 1 )
|
|
self thread debug_message_ai( msg );
|
|
#/
|
|
}
|
|
|
|
enemy_event_debug_print( type )
|
|
{
|
|
setDvarIfUninitialized( "stealth_debug_prints", "0" );
|
|
|
|
if ( !isdefined( getdvar( "stealth_debug_prints" ) ) )
|
|
setdvar( "stealth_debug_prints", "0" );
|
|
|
|
if ( getdvarint( "stealth_debug_prints" ) != 1 )
|
|
return;
|
|
|
|
/#
|
|
self endon( "death" );
|
|
|
|
self waittill( type, subtype );
|
|
|
|
if ( isdefined( subtype ) )
|
|
self stealth_debug_print( "Received an event: " + type + " " + subtype );
|
|
else
|
|
self stealth_debug_print( "Received an event: " + type );
|
|
#/
|
|
}
|
|
|
|
stealth_flag_debug_print( _flag )
|
|
{
|
|
/#
|
|
while ( 1 )
|
|
{
|
|
flag_wait( _flag );
|
|
{
|
|
if ( getdvarint( "stealth_debug_prints" ) == 1 )
|
|
iprintlnbold( _flag );
|
|
else
|
|
println( _flag );
|
|
}
|
|
|
|
flag_waitopen( _flag );
|
|
{
|
|
if ( getdvarint( "stealth_debug_prints" ) == 1 )
|
|
iprintlnbold( "back to normal: " + _flag );
|
|
else
|
|
println( "back to normal: " + _flag );
|
|
}
|
|
}
|
|
#/
|
|
}
|
|
|
|
group_flag_init( _flag )
|
|
{
|
|
assertex( issentient( self ), "an AI must call this function" );
|
|
|
|
if ( isdefined( self.script_stealthgroup ) )
|
|
self.script_stealthgroup = string( self.script_stealthgroup );
|
|
else
|
|
self.script_stealthgroup = "default";
|
|
|
|
name = self group_get_flagname( _flag );
|
|
|
|
if ( !flag_exist( name ) )
|
|
{
|
|
flag_init( name );
|
|
|
|
if ( !isdefined( level._stealth.group.flags[ _flag ] ) )
|
|
level._stealth.group.flags[ _flag ] = [];
|
|
|
|
size = level._stealth.group.flags[ _flag ].size;
|
|
level._stealth.group.flags[ _flag ][ size ] = name;
|
|
}
|
|
}
|
|
|
|
group_add_to_global_list()
|
|
{
|
|
assertex( issentient( self ), "an AI must call this function" );
|
|
|
|
if ( !isdefined( level._stealth.group.groups[ self.script_stealthgroup ] ) )
|
|
{
|
|
level._stealth.group.groups[ self.script_stealthgroup ] = [];
|
|
level._stealth.group notify( self.script_stealthgroup );
|
|
}
|
|
size = level._stealth.group.groups[ self.script_stealthgroup ].size;
|
|
level._stealth.group.groups[ self.script_stealthgroup ][ size ] = self;
|
|
}
|
|
|
|
group_get_flagname( _flag )
|
|
{
|
|
assertex( issentient( self ), "an AI must call this function" );
|
|
|
|
return group_get_flagname_from_group( _flag, self.script_stealthgroup );
|
|
}
|
|
|
|
group_get_flagname_from_group( _flag, group )
|
|
{
|
|
name = _flag + "-Group:" + group;
|
|
return name;
|
|
}
|
|
|
|
group_flag_set( _flag )
|
|
{
|
|
assertex( issentient( self ), "an AI must call this function" );
|
|
|
|
name = self group_get_flagname( _flag );
|
|
flag_set( name );
|
|
flag_set( _flag );
|
|
}
|
|
|
|
group_return_groups_with_flag_set( _flag )
|
|
{
|
|
return_value = [];
|
|
|
|
array = level._stealth.group.groups;
|
|
|
|
foreach ( key, value in array )
|
|
{
|
|
name = group_get_flagname_from_group( _flag, key );
|
|
|
|
if ( flag( name ) )
|
|
return_value[ return_value.size ] = key;
|
|
}
|
|
|
|
return return_value;
|
|
}
|
|
|
|
group_return_ai_with_flag_set( _flag )
|
|
{
|
|
return_value = [];
|
|
|
|
array = level._stealth.group.groups;
|
|
|
|
foreach ( key, value in array )
|
|
{
|
|
name = group_get_flagname_from_group( _flag, key );
|
|
|
|
if ( flag( name ) )
|
|
{
|
|
ai = group_get_ai_in_group( key );
|
|
return_value = array_merge( return_value, ai );
|
|
}
|
|
}
|
|
|
|
return return_value;
|
|
}
|
|
|
|
group_flag_clear( _flag, group )
|
|
{
|
|
name = group_get_flagname_from_group( _flag, group );
|
|
flag_clear( name );
|
|
|
|
array = level._stealth.group.flags[ _flag ];
|
|
clear = true;
|
|
|
|
foreach ( key, value in array )
|
|
{
|
|
if ( flag( value ) )
|
|
return;
|
|
}
|
|
|
|
flag_clear( _flag );
|
|
}
|
|
|
|
group_get_ai_in_group( group_name )
|
|
{
|
|
level._stealth.group.groups[ group_name ] = array_removeDead( level._stealth.group.groups[ group_name ] );
|
|
return level._stealth.group.groups[ group_name ];
|
|
}
|
|
|
|
group_wait_group_spawned( group_name )
|
|
{
|
|
if ( !isdefined( level._stealth.group.groups[ group_name ] ) )
|
|
level._stealth.group waittill( group_name );
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* BEHAVIOR UTILITIES */
|
|
/************************************************************************************************************/
|
|
|
|
ai_stealth_pause_handler()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "pain_death" );
|
|
|
|
while ( 1 )
|
|
{
|
|
self ent_flag_waitopen( "_stealth_enabled" );
|
|
|
|
spotted_func = self._stealth.behavior.ai_functions[ "state" ][ "spotted" ];
|
|
|
|
switch( self.team )
|
|
{
|
|
case "allies":
|
|
self [[ spotted_func ]]();
|
|
break;
|
|
case "axis":
|
|
case "team3":
|
|
self [[ spotted_func ]]( true );
|
|
break;
|
|
}
|
|
|
|
self ent_flag_wait( "_stealth_enabled" );
|
|
|
|
hidden_func = self._stealth.behavior.ai_functions[ "state" ][ "hidden" ];
|
|
self [[ hidden_func ]]();
|
|
}
|
|
}
|
|
|
|
enemy_go_back()
|
|
{
|
|
self notify( "going_back" );
|
|
self endon( "death" );
|
|
self notify( "stop_loop" );
|
|
|
|
if( IsDefined( self._stealth.behavior.goback_startFunc ) )
|
|
{
|
|
self [[ self._stealth.behavior.goback_startFunc ]]();
|
|
}
|
|
|
|
spot = self._stealth.behavior.last_spot;
|
|
|
|
if ( isdefined( spot ) && self.type != "dog" && !isdefined( self.customMoveTransition ) )
|
|
self.customMoveTransition = maps\_patrol::patrol_resume_move_start_func;
|
|
|
|
// stop before moving
|
|
if ( isdefined( self.customMoveTransition ) && isdefined( self.pathGoalPos ) )
|
|
{
|
|
self setgoalpos( self.origin );
|
|
wait 0.05;
|
|
}
|
|
|
|
if ( isdefined( self.script_patroller ) )
|
|
{
|
|
if ( isdefined( self.last_patrol_goal ) )
|
|
{
|
|
self.target = self.last_patrol_goal.targetname;
|
|
}
|
|
|
|
//these guys on the ridge in cliffhanger get alerted and jump down a one way traverse. this gives them a pathable patrol path to go back to.
|
|
if ( isdefined( self.stealth_first_alert_new_patrol_path ) )
|
|
{
|
|
self.target = self.stealth_first_alert_new_patrol_path.targetname;
|
|
self.stealth_first_alert_new_patrol_path = undefined;
|
|
}
|
|
|
|
self thread maps\_patrol::patrol();
|
|
}
|
|
else if ( isalive( self.patrol_master ) )
|
|
{
|
|
self thread maps\_patrol::pet_patrol();
|
|
self set_dog_walk_anim();
|
|
self.script_growl = undefined;
|
|
}
|
|
else if ( isdefined( spot ) )
|
|
{
|
|
if ( self.type != "dog" )
|
|
self set_generic_run_anim( "_stealth_patrol_cqb", true );
|
|
else
|
|
{
|
|
self set_dog_walk_anim();
|
|
self.script_growl = undefined;
|
|
}
|
|
self.disablearrivals = true;
|
|
self.disableexits = true;
|
|
|
|
self setgoalpos( spot );
|
|
self.goalradius = 40;
|
|
}
|
|
|
|
//make sure the AI has been given a new goal before clearing this flag
|
|
waittillframeend;
|
|
self ent_flag_clear( "_stealth_override_goalpos" );
|
|
|
|
if ( isdefined( spot ) )
|
|
self thread enemy_go_back_clear_lastspot( spot );
|
|
}
|
|
|
|
enemy_go_back_clear_lastspot( origin )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "_stealth_enemy_alert_level_change" );
|
|
|
|
self waittill_true_goal( origin );
|
|
self._stealth.behavior.last_spot = undefined;
|
|
}
|
|
|
|
// caches result of search
|
|
enemy_get_nearby_pathnodes( origin, radius, min_radius )
|
|
{
|
|
if ( !isdefined( min_radius ) )
|
|
min_radius = 0;
|
|
|
|
if ( isdefined( level._stealth.node_search.nodes_array ) &&
|
|
distanceSquared( origin, level._stealth.node_search.origin ) < 64 * 64 &&
|
|
radius == level._stealth.node_search.radius &&
|
|
min_radius == level._stealth.node_search.min_radius )
|
|
return level._stealth.node_search.nodes_array;
|
|
|
|
level._stealth.node_search.origin = origin;
|
|
level._stealth.node_search.radius = radius;
|
|
level._stealth.node_search.min_radius = min_radius;
|
|
|
|
level._stealth.node_search.nodes_array = getNodesInRadius( origin, radius, min_radius, 512, "Path" );
|
|
|
|
return level._stealth.node_search.nodes_array;
|
|
}
|
|
|
|
// this is called by some functions like finding a corpse or
|
|
// hearing an explosion that go into alert - but dont want to put into alert
|
|
// state because alert state shares it's behavior with a guy simply
|
|
// thinkgin he saw something twice
|
|
enemy_reaction_state_alert()
|
|
{
|
|
self.fovcosine = .01;// 90 degrees to either side...180 cone...default view cone
|
|
self.ignoreall = false;
|
|
self.dieQuietly = false;
|
|
self clear_run_anim();
|
|
self.fixednode = false;
|
|
}
|
|
|
|
enemy_alert_level_forget( enemy, delay )
|
|
{
|
|
self endon( "death" );
|
|
enemy endon( "death" );
|
|
|
|
if ( !isdefined( delay ) )
|
|
delay = 60; //after 60 seconds - forget about it
|
|
|
|
wait delay;
|
|
|
|
if ( isdefined( enemy._stealth.logic.spotted_list[ self.unique_id ] ) && enemy._stealth.logic.spotted_list[ self.unique_id ] > 0 )
|
|
enemy._stealth.logic.spotted_list[ self.unique_id ] -- ;
|
|
}
|
|
|
|
enemy_stop_current_behavior()
|
|
{
|
|
if ( !self ent_flag( "_stealth_behavior_reaction_anim" ) )
|
|
{
|
|
self anim_stopanimscripted();
|
|
self notify( "stop_animmode" );
|
|
self notify( "stop_loop" );
|
|
}
|
|
if ( isdefined( self.script_patroller ) )
|
|
{
|
|
if ( isdefined( self.last_patrol_goal ) )
|
|
self.last_patrol_goal.patrol_claimed = undefined;
|
|
|
|
self notify( "release_node" );
|
|
self notify( "end_patrol" );
|
|
}
|
|
self notify( "stop_first_frame" );
|
|
self clear_run_anim();
|
|
self clear_generic_idle_anim();
|
|
}
|
|
|
|
enemy_find_original_goal()
|
|
{
|
|
//if we already have an original goal - stick to it
|
|
if ( isdefined( self._stealth.behavior.last_spot ) )
|
|
return;
|
|
|
|
if ( isdefined( self.last_set_goalnode ) )
|
|
self._stealth.behavior.last_spot = self.last_set_goalnode.origin;
|
|
else if ( isdefined( self.last_set_goalent ) )
|
|
self._stealth.behavior.last_spot = self.last_set_goalent.origin;
|
|
else if ( isdefined( self.last_set_goalpos ) )
|
|
self._stealth.behavior.last_spot = self.last_set_goalpos;
|
|
else
|
|
self._stealth.behavior.last_spot = self.origin;
|
|
}
|
|
|
|
enemy_set_original_goal( origin )
|
|
{
|
|
self._stealth.behavior.last_spot = origin;
|
|
}
|
|
|
|
enemy_runto_and_lookaround( node, position )
|
|
{
|
|
self notify( "enemy_runto_and_lookaround" );
|
|
self endon( "enemy_runto_and_lookaround" );
|
|
|
|
self endon( "death" );
|
|
self endon( "_stealth_enemy_alert_level_change" );
|
|
if ( self.type != "dog" )
|
|
self endon( "_stealth_saw_corpse" );
|
|
|
|
spotted_flag = self group_get_flagname( "_stealth_spotted" );
|
|
level endon( spotted_flag );
|
|
|
|
//this is for guys who are already DOING this, and need to do it again
|
|
self notify( "stop_loop" );
|
|
|
|
self ent_flag_set( "_stealth_override_goalpos" );
|
|
|
|
if ( isdefined( node ) )
|
|
{
|
|
self setgoalnode( node );
|
|
}
|
|
else
|
|
{
|
|
assertex( isdefined( position ), "no node or position defined" );
|
|
self setgoalpos( position );
|
|
}
|
|
|
|
self.goalradius = 64;
|
|
|
|
self waittill( "goal" );
|
|
|
|
if ( self.type != "dog" )
|
|
self set_generic_idle_anim( "_stealth_look_around" );
|
|
}
|
|
|
|
enemy_find_free_pathnode_near( origin, radius, min_radius )
|
|
{
|
|
array = enemy_get_nearby_pathnodes( origin, radius, min_radius );
|
|
|
|
if ( !isdefined( array ) || array.size == 0 )
|
|
return;
|
|
|
|
node = array[ randomInt( array.size ) ];
|
|
array = array_remove( array, node );
|
|
|
|
while ( isdefined( node.owner ) )
|
|
{
|
|
if ( array.size == 0 )
|
|
return;
|
|
|
|
node = array[ randomInt( array.size ) ];
|
|
array = array_remove( array, node );
|
|
}
|
|
|
|
level._stealth.node_search.nodes_array = array;
|
|
|
|
return node;
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* ANNOUCEMENTS */
|
|
/************************************************************************************************************/
|
|
enemy_announce_wtf()
|
|
{
|
|
if ( self.type == "dog" )
|
|
return;
|
|
if ( !( self enemy_announce_snd( "wtf" ) ) )
|
|
return;
|
|
|
|
alias = "stealth_" + self.npcID + "_anexplosion";
|
|
self playsound( alias );
|
|
}
|
|
|
|
// Who's there?
|
|
enemy_announce_huh()
|
|
{
|
|
if ( self.type == "dog" )
|
|
return;
|
|
if ( !( self enemy_announce_snd( "huh" ) ) )
|
|
return;
|
|
|
|
alias = "stealth_" + self.npcID + "_huh";
|
|
self playsound( alias );
|
|
}
|
|
|
|
// Didn't find anything, going back to patrol
|
|
enemy_announce_hmph()
|
|
{
|
|
if ( self.type == "dog" )
|
|
return;
|
|
if ( !( self enemy_announce_snd( "hmph" ) ) )
|
|
return;
|
|
|
|
alias = "stealth_" + self.npcID + "_hmph";
|
|
self playsound( alias );
|
|
}
|
|
|
|
|
|
enemy_announce_attack()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "pain_death" );// don't actually want to be able to still call out to buddies - it kinda sucks to take him down and still lose
|
|
|
|
if ( self.type == "dog" )
|
|
return;
|
|
|
|
if ( !( self enemy_announce_snd( "spotted" ) ) )
|
|
return;
|
|
|
|
self playsound( "RU_" + self.npcID + "_stealth_alert" );
|
|
}
|
|
|
|
enemy_announce_spotted( pos )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "pain_death" );// don't actually want to be able to still call out to buddies - it kinda sucks to take him down and still lose
|
|
|
|
// this makes sure that if we're not spotted because we killed
|
|
// this guy before he could set the flag - we dont' bring
|
|
// everyone over for no reason.
|
|
self stealth_group_spotted_flag_wait();
|
|
|
|
if ( self.type == "dog" )
|
|
return;
|
|
|
|
if ( self enemy_announce_snd( "spotted" ) )
|
|
{
|
|
self thread enemy_announce_spotted_bring_group( pos );
|
|
|
|
alias = "RU_" + self.npcID + "_stealth_alert";
|
|
self playsound( alias );
|
|
}
|
|
|
|
if ( self enemy_announce_snd( "acknowledge" ) )
|
|
self thread enemy_announce_spotted_acknowledge( self.origin );
|
|
}
|
|
|
|
enemy_announce_spotted_acknowledge( spotterPos )
|
|
{
|
|
wait 1.5;
|
|
|
|
if ( isdefined( self.npcID ) )
|
|
num = self.npcID;
|
|
else
|
|
num = randomint( 3 );
|
|
|
|
alias = "RU_" + num + "_stealth_alert_r";
|
|
play_sound_in_space( alias, spotterPos );
|
|
}
|
|
|
|
enemy_announce_spotted_bring_group( pos )
|
|
{
|
|
group = group_get_ai_in_group( self.script_stealthgroup );
|
|
|
|
foreach ( key, ai in group )
|
|
{
|
|
if ( ai == self )
|
|
continue;
|
|
|
|
if ( isdefined( ai.enemy ) || isdefined( ai.favoriteenemy ) )
|
|
continue;
|
|
|
|
ai notify( "heard_scream", pos );
|
|
}
|
|
}
|
|
|
|
enemy_announce_corpse()
|
|
{
|
|
self endon( "death" );
|
|
if ( isdefined( self.found_corpse_wait ) )
|
|
wait( self.found_corpse_wait );
|
|
|
|
if ( !( self enemy_announce_snd( "corpse" ) ) )
|
|
return;
|
|
|
|
if ( self.type == "dog" )
|
|
{
|
|
self ent_flag_waitopen( "_stealth_behavior_reaction_anim_in_progress" );
|
|
self notify( "event_awareness", "howl" );
|
|
return;
|
|
}
|
|
|
|
alias = "stealth_" + self.npcID + "_deadbody";
|
|
self playsound( alias );
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* ANNOUCEMENT UTILITIES */
|
|
/************************************************************************************************************/
|
|
enemy_announce_snd( type )
|
|
{
|
|
if ( type == "spotted" )
|
|
{
|
|
if ( level._stealth.behavior.sound[ type ][ self.script_stealthgroup ] )
|
|
return false;
|
|
|
|
level._stealth.behavior.sound[ type ][ self.script_stealthgroup ] = true;
|
|
}
|
|
else
|
|
{
|
|
if ( level._stealth.behavior.sound[ type ] )
|
|
return false;
|
|
|
|
level._stealth.behavior.sound[ type ] = true;
|
|
|
|
self thread enemy_announce_snd_reset( type );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enemy_announce_snd_reset( type )
|
|
{
|
|
wait level._stealth.behavior.sound_reset_time;
|
|
level._stealth.behavior.sound[ type ] = false;
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* ANIMATION SYSTEM */
|
|
/************************************************************************************************************/
|
|
enemy_animation_wrapper( type )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "pain_death" );
|
|
|
|
// ALWAYS RUN THIS UNLESS YOU'RE SURE YOU KNOW WHAT YOU"RE DOING
|
|
if ( self enemy_animation_pre_anim( type ) )
|
|
return;
|
|
|
|
self enemy_animation_do_anim( type );
|
|
|
|
// ALWAYS RUN THIS UNLESS YOU'RE SURE YOU KNOW WHAT YOU"RE DOING
|
|
self enemy_animation_post_anim( type );
|
|
}
|
|
|
|
enemy_animation_do_anim( type )
|
|
{
|
|
if ( isdefined( self._stealth.behavior.event.custom_animation ) )
|
|
{
|
|
self enemy_animation_custom( type );
|
|
return;
|
|
}
|
|
|
|
// do default behavior
|
|
function = self._stealth.behavior.ai_functions[ "animation" ][ type ];
|
|
|
|
self [[ function ]]( type );
|
|
}
|
|
|
|
enemy_animation_custom( type )
|
|
{
|
|
node = self._stealth.behavior.event.custom_animation.node;
|
|
anime = self._stealth.behavior.event.custom_animation.anime;
|
|
tag = self._stealth.behavior.event.custom_animation.tag;
|
|
ender = self._stealth.behavior.event.custom_animation.ender;
|
|
|
|
self ent_flag_set( "_stealth_behavior_reaction_anim" );
|
|
|
|
// wouldn't normally do this - but since this is specific to stealth script
|
|
// i figured it would be ok to set allowdeath.
|
|
self.allowdeath = true;
|
|
|
|
// cut the loop
|
|
node notify( ender );
|
|
|
|
if ( isdefined( self.anim_props ) )
|
|
{
|
|
self.anim_props_animated = true;
|
|
node thread anim_single( self.anim_props, anime );
|
|
}
|
|
if ( type != "doFlashBanged" )
|
|
{
|
|
// this is the reaction
|
|
if ( isdefined( tag ) || isdefined( self.has_delta ) )
|
|
node anim_generic( self, anime, tag );
|
|
else
|
|
node anim_generic_custom_animmode( self, "gravity", anime );
|
|
}
|
|
// once you the the reaction once - you dont wanna ever do it again...especially not off this node
|
|
self ai_clear_custom_animation_reaction();
|
|
}
|
|
|
|
enemy_animation_pre_anim( type )
|
|
{
|
|
self notify( "enemy_awareness_reaction", type );
|
|
|
|
// this means that something really bad happened...a first reaction
|
|
// to the player being spotted or something equally bad like a bullet
|
|
// whizby
|
|
if ( self ent_flag( "_stealth_behavior_first_reaction" ) || self ent_flag( "_stealth_behavior_reaction_anim_in_progress" ) )
|
|
return true;
|
|
|
|
// this function doesn't stop behavior if _stealth_behavior_reaction_anim
|
|
// is set - because stoping behavior means ending current animations
|
|
// and since reacting is an animation - we want to set the flag
|
|
// AFTER we've stopped the current ones
|
|
self enemy_stop_current_behavior();
|
|
|
|
if ( issubstr( type, "warning" ) )
|
|
type = "warning";
|
|
|
|
switch( type )
|
|
{
|
|
case "explode":
|
|
case "heard_corpse":
|
|
case "saw_corpse":
|
|
case "found_corpse":
|
|
// all the cases above this dont break - so they all will do the same thing when they get to this line
|
|
self ent_flag_set( "_stealth_behavior_reaction_anim" );
|
|
break;
|
|
case "reset":
|
|
case "warning":
|
|
// all the cases above this dont break - so they all will do the same thing when they get to this line
|
|
// which is absolutely nothing
|
|
break;
|
|
default:
|
|
if ( !self ent_flag_exist( "_stealth_behavior_asleep" ) || !self ent_flag( "_stealth_behavior_asleep" ) || self stealth_group_spotted_flag() )
|
|
{
|
|
self ent_flag_set( "_stealth_behavior_first_reaction" );
|
|
self thread enemy_animation_pre_anim_dog_special_first_condition();
|
|
}
|
|
self ent_flag_set( "_stealth_behavior_reaction_anim" );
|
|
break;
|
|
}
|
|
|
|
self ent_flag_set( "_stealth_behavior_reaction_anim_in_progress" );
|
|
return false;
|
|
}
|
|
|
|
enemy_animation_pre_anim_dog_special_first_condition()
|
|
{
|
|
spotted_flag = self group_get_flagname( "_stealth_spotted" );
|
|
|
|
self endon( "death" );
|
|
|
|
flag_wait_or_timeout( spotted_flag, 3 );
|
|
if ( flag( spotted_flag ) )
|
|
self ent_flag_set( "_stealth_behavior_first_reaction" );
|
|
}
|
|
|
|
enemy_animation_post_anim( type )
|
|
{
|
|
switch( type )
|
|
{
|
|
default:
|
|
self ent_flag_clear( "_stealth_behavior_reaction_anim" );
|
|
break;
|
|
}
|
|
|
|
self ent_flag_clear( "_stealth_behavior_reaction_anim_in_progress" );
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* ANIMATION UTILITIES */
|
|
/************************************************************************************************************/
|
|
ai_clear_custom_animation_reaction()
|
|
{
|
|
self._stealth.behavior.event.custom_animation = undefined;
|
|
|
|
self.newEnemyReactionDistSq = squared( 512 );
|
|
}
|
|
|
|
ai_clear_custom_animation_reaction_and_idle( waitanimend )
|
|
{
|
|
// could have been cleared by something else
|
|
if ( !isdefined( self._stealth.behavior.event.custom_animation ) )
|
|
return;
|
|
|
|
self._stealth.behavior.event.custom_animation.node notify( "stop_loop" );
|
|
if ( !isdefined( waitanimend ) || waitanimend == false )
|
|
self stopanimscripted();
|
|
|
|
self ai_clear_custom_animation_reaction();
|
|
}
|
|
|
|
ai_set_custom_animation_reaction( node, anime, tag, ender )
|
|
{
|
|
self._stealth.behavior.event.custom_animation = spawnstruct();
|
|
|
|
self._stealth.behavior.event.custom_animation.node = node;
|
|
self._stealth.behavior.event.custom_animation.anime = anime;
|
|
self._stealth.behavior.event.custom_animation.tag = tag;
|
|
self._stealth.behavior.event.custom_animation.ender = ender;
|
|
self thread ai_animate_props_on_death( node, anime, tag, ender );
|
|
|
|
self.newEnemyReactionDistSq = 0;
|
|
}
|
|
|
|
ai_animate_props_on_death( node, anime, tag, ender )
|
|
{
|
|
wait .1;
|
|
if ( !isdefined( self.anim_props ) )
|
|
return;
|
|
|
|
prop = self.anim_props;
|
|
|
|
self waittill( "death" );
|
|
|
|
if ( isdefined( self.anim_props_animated ) )
|
|
return;
|
|
|
|
node thread anim_single( prop, anime );
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* EVENT AWARENESS */
|
|
/************************************************************************************************************/
|
|
|
|
event_awareness_main( dialogue_array, ender_array )
|
|
{
|
|
level notify( "event_awareness_handler" );
|
|
level endon( "event_awareness_handler" );
|
|
level endon( "default_event_awareness_enders" );
|
|
|
|
event_awareness_enders( ender_array );
|
|
|
|
add_wait( ::waittill_msg, "event_awareness_handler" );
|
|
add_wait( ::waittill_msg, "default_event_awareness_enders" );
|
|
add_func( ::flag_clear, "_stealth_event" );
|
|
thread do_wait_any();
|
|
|
|
while ( 1 )
|
|
{
|
|
flag_wait( "_stealth_enabled" );
|
|
|
|
flag_wait( "_stealth_event" );
|
|
|
|
if ( !flag( "_stealth_enabled" ) )
|
|
continue;
|
|
|
|
wait 2;
|
|
|
|
event_awareness_dialogue_wrapper( dialogue_array );
|
|
|
|
flag_waitopen( "_stealth_event" );
|
|
}
|
|
}
|
|
|
|
event_awareness_dialogue_wrapper( array )
|
|
{
|
|
wait randomfloatrange( .5, 1 );
|
|
|
|
if ( !isdefined( array ) )
|
|
return;
|
|
|
|
string = random( array );
|
|
level thread function_stack( ::radio_dialogue, string );
|
|
}
|
|
|
|
event_awareness_enders( ender_array )
|
|
{
|
|
level endon( "default_event_awareness_enders" );
|
|
level endon( "event_awareness_handler" );
|
|
|
|
if ( isdefined( ender_array ) )
|
|
{
|
|
foreach ( string in ender_array )
|
|
{
|
|
if ( flag_exist( string ) && flag( string ) )
|
|
level notify( "default_event_awareness_enders" );
|
|
}
|
|
|
|
//don't want to put into the same loop as above because we dont
|
|
//want to add_wait and then kill this function without every calling
|
|
//do_wait...cause that would cause some nasty bugs
|
|
foreach ( string in ender_array )
|
|
add_wait( ::waittill_msg, string );
|
|
}
|
|
|
|
add_wait( ::flag_wait, "_stealth_spotted" );
|
|
add_wait( ::waittill_msg, "end_event_awareness_handler" );
|
|
add_wait( ::waittill_msg, "event_awareness_handler" );
|
|
add_func( ::send_notify, "default_event_awareness_enders" );
|
|
thread do_wait_any();
|
|
}
|
|
|
|
/************************************************************************************************************/
|
|
/* GLOBAL SCRIPT CALL BACKS */
|
|
/************************************************************************************************************/
|
|
|
|
_autosave_stealthcheck()
|
|
{
|
|
if( !stealth_is_everything_normal() )
|
|
return false;
|
|
|
|
if ( flag( "_stealth_player_nade" ) )
|
|
return false;
|
|
if ( flag_exist( "_radiation_poisoning" ) )
|
|
{
|
|
if ( flag( "_radiation_poisoning" ) )
|
|
return false;
|
|
}
|
|
vehicles = getentarray( "destructible", "classname" );
|
|
foreach ( vehicle in vehicles )
|
|
{
|
|
if ( isDefined( vehicle.healthDrain ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_patrol_endon_spotted_flag()
|
|
{
|
|
flag1 = self stealth_get_group_spotted_flag();
|
|
flag2 = self stealth_get_group_corpse_flag();
|
|
|
|
add_wait( ::flag_wait, flag1 );
|
|
add_wait( ::flag_wait, flag2 );
|
|
self add_abort( ::waittill_msg, "death" );
|
|
self add_func( ::send_notify, "end_patrol" );
|
|
thread do_wait_any();
|
|
}
|
|
|
|
_spawner_stealth_default()
|
|
{
|
|
self thread stealth_default();
|
|
}
|
|
|