cod4-sdk/raw/maps/_stealth_behavior.gsc

2314 lines
76 KiB
Text
Raw Normal View History

2008-01-19 00:00:00 +00:00
#include common_scripts\utility;
#include maps\_utility;
#include maps\_anim;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/*
CONFUSED? NEED HELP?
come grab me: mohammad alavi
badmofo@infinityward.com
extention: 8044
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/*
B A S I C S
HOW TO USE STEALTH SYSTEM
( follow in this order )
1. call maps\_stealth_logic::main(); in your main level script
2. call maps\_stealth_behavior::main(); in your main level script
3. you're going to have a couple fastfile errors for animations and
missing rawfiles, fix those.
4. call maps\_utility::stealth_ai on all ai and the player
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
USEFUL FUNCTIONS
( documentation in _utility and script docs ):
// knowing when AI are alerted to something
maps\_utility::stealth_enemy_waittill_alert
maps\_utility::stealth_enemy_endon_alert
// setting up custom idle and alerted reaction animations
maps\_utility::stealth_ai_idle_and_react
maps\_utility::stealth_ai_reach_idle_and_react
maps\_utility::stealth_ai_reach_and_arrive_idle_and_react
maps\_utility::stealth_ai_clear_custom_idle_and_react
maps\_utility::stealth_ai_clear_custom_react
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/*
A D V A N C E D
HOW TO CREATE CUSTOM STEALTH BEHAVIOR
the stealth code is split up into 2 major systems: logic and behavior.
the logic code is independent of the behavior code, which means it can
run by itself. The behavior code however is dependant on messages from
the logic code and cannot run by itself. Both the level and all ai have
a spawnstruct '._stealth'. Inside this struct are 2 more structs
'.logic', and '.behavior'. Everything to do with logic goes into
'._stealth.logic' and with behavior goes into '._stealth.behavior'
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
CONCEPT OF LOGIC CODE:
the logic code doesn't actually tell the AI to do anything, it just
detects whether the friendlies have been spotted, corpses have been
found, etc, etc. It then sets flags / sends notifies to ai.
Code gives axis ai an enemy on a couple different factors that the logic
code tweaks( such as the distance an AI can hear footsteps, gunshots,
teammates dying, etc ). The logic code also dictates how far an axis ai
can even see an enemy based on a score calculated by the enemy's stance,
movement speed, and current level of awareness of the axis team.
So the logic code in a nutshell, is a simple loop that detects when
an axis ai receives an enemy, and then clears that enemy. this keeps
the axis from just opening fire at first sight. It instead raises his
awareness level and possibly the whole teams if the enemy is spotted
more than once. -- > flag_set( "_stealth_alert" );
If friendlies or the player are spotted enough times - OR - something
VERY bad happens like an death or a gunshot, then we skip
intermediate levels of detection and go straight to full level of
awareness. -- > flag_set( "_stealth_spotted" );
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
CHANGING THE BEHAVIOR CODE:
METHOD 1:
there are 2 ways to change the behavior code, one would be to write
your own from scratch...this is not recommended. However if you do
want to do this, you basically need the following:
1. a system loop which handles global ai settings based on the 3 level
flag messages: 'hidden', 'alert', and 'spotted'; The messages
for this are sent from all over, but they can all be traced back
to the function:
_stealth_logic::enemy_threat_logic();
2. a loop for individual ai based on team which handles their behavior
based on the same 3 flag messages.
3. functions for axis ai which handle thir 3 personal levels of
awareness and what to do when they see / find a corpse. This is the
the "huh", "what happened", "oh shit enemy" behavior. The messages
for this are sent in the functions:
_stealth_logic::enemy_alert_level_change( type, enemy );
_stealth_logic::enemy_corpse_logic();
4. functions to handle awareness of certain events as well as reactionary
animations for such events...the notifies for these often overlap
the functions above so you'll need more animation functions than
behavioral...the messages for these are sent by:
_stealth_logic::enemy_event_awareness( type )
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
METHOD 2:
the easier and recommended way to change the behavior is to replace
certain key functions the behavior code is running with your own.
These functions can easily be replaced when calling the init functions
for behavior:
_stealth_behavior::system_init( state_functions );
_stealth_behavior::enemy_logic( state_funcs, alert_funcs, corpse_funcs, awareness_funcs );
_stealth_behavior::friendly_logic( state_functions );
the utility function which should be called on all ai is where you should
most likely be passing the parameters. It handles both enemy_logic
and friendly_logic( documentation in _utility and script docs ):
- _utility::stealth_ai( state_funcs, alert_funcs, corpse_funcs )
- note = you should generally not send the same state_funcion to both
- axis ai and friendly ai...
all the parameters are arrays of function pointers( and are optional )
- state_functions is an array of 3 with keys 'hidden', 'alert', and
'spotted'.
- For ::system_init(), it's an array of 3 functions that handle global
state changes for all AI.
example = _stealth_behavior::system_state_alert()
- For ::enemy_logic() and ::friendly_logic, they handle individual
changes per AI based on state.
example1 = _stealth_behavior::enemy_state_spotted()
example2 = _stealth_behavior::friendly_state_spotted()
- notice friendly_logic() only takes the state_functions array
and not the other 2...that's because it's assumed friendly behavior
only changes based on global state of awereness for the axis team,
not indivual states of awareness for axis ai.
- alert_functions is an array of 4 with keys "reset", "alerted_once",
"alerted_again", and "attack" that represent individual states of
awareness of axis ai.
- this is where the bread and butter of the "huh", "what's that"
behavior is derived. highly suggested to look at the function:
_stealth_logic::enemy_alert_level_logic( enemy ) to see how
these awareness states are reached in the logic code. Also for
examples of the behavior aspect of this code look here:
example1 = _stealth_behavior::enemy_alert_level_alerted_once( enemy )
example2 = _stealth_behavior::enemy_alert_level_alerted_again( enemy )
example3 = _stealth_behavior::enemy_alert_level_attack( enemy )
example4 = _stealth_behavior::enemy_alert_level_lostem( enemy )
- corpse_functions is an array 2 with keys 'saw' and 'found' that
represent individual and team states of awareness of a corpse
- these slightly tie into the enemy_alert_level * functions above
in the behavior code, so probably a good idea to look at these
examples:
example1 = enemy_saw_corpse_logic();
example2 = enemy_found_corpse_loop();
you can also set these functions after init is called( documentation in
_utility and script docs ):
maps\_utility::stealth_ai_state_functions_set
maps\_utility::stealth_ai_state_functions_default
maps\_utility::stealth_ai_alert_functions_set
maps\_utility::stealth_ai_alert_functions_default
maps\_utility::stealth_ai_corpse_functions_set
maps\_utility::stealth_ai_corpse_functions_default
maps\_utility::stealth_ai_awareness_functions_set
maps\_utility::stealth_ai_awareness_functions_default
and here are some usefull functions which change the detection distances
that enemy ai will see you or your friendlies
maps\_utility::stealth_detect_ranges_set
maps\_utility::stealth_detect_ranges_default
maps\_utility::stealth_friendly_movespeed_scale_set
maps\_utility::stealth_friendly_movespeed_scale_default
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
lastly...the friendlies have a section of code in behavior which
is a smart stance handler( they'll go into crouch or prone or lay
still ) depending on a couple factors...if you want to turn this off
just use the ent_flag_clear( "_stealth_stance_handler" ) on the ai
here are some usefull functions that change the distances at which
your friendlies use their smart stance logic to switch stances or
lay still
maps\_utility::stealth_friendly_stance_handler_distances_set
maps\_utility::stealth_friendly_stance_handler_distances_default
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
main( state_functions )
{
system_init( state_functions );
thread system_message_loop();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* STEALTH BEHAVIOR UTILITIES...good calls for specific tweakage */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
friendly_default_stance_handler_distances()
{
// i do this because the player doesn't look as bad sneaking up on the enemies
// friendlies however don't look as good getting so close
hidden = [];
hidden[ "looking_away" ][ "stand" ] = 500;
hidden[ "looking_away" ][ "crouch" ] = -400;
hidden[ "looking_away" ][ "prone" ] = 0;
hidden[ "neutral" ][ "stand" ] = 500;
hidden[ "neutral" ][ "crouch" ] = 200;
hidden[ "neutral" ][ "prone" ] = 50;
hidden[ "looking_towards" ][ "stand" ] = 800;
hidden[ "looking_towards" ][ "crouch" ] = 400;
hidden[ "looking_towards" ][ "prone" ] = 100;
alert = [];
alert[ "looking_away" ][ "stand" ] = 800;
alert[ "looking_away" ][ "crouch" ] = 300;
alert[ "looking_away" ][ "prone" ] = 100;
alert[ "neutral" ][ "stand" ] = 900;
alert[ "neutral" ][ "crouch" ] = 700;
alert[ "neutral" ][ "prone" ] = 500;
alert[ "looking_towards" ][ "stand" ] = 1100;
alert[ "looking_towards" ][ "crouch" ] = 900;
alert[ "looking_towards" ][ "prone" ] = 700;
friendly_set_stance_handler_distances( hidden, alert );
}
friendly_set_stance_handler_distances( hidden, alert )
{
if ( isdefined( hidden ) )
{
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_away" ][ "stand" ] = hidden[ "looking_away" ][ "stand" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_away" ][ "crouch" ] = hidden[ "looking_away" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_away" ][ "prone" ] = hidden[ "looking_away" ][ "prone" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "neutral" ][ "stand" ] = hidden[ "neutral" ][ "stand" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "neutral" ][ "crouch" ] = hidden[ "neutral" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "neutral" ][ "prone" ] = hidden[ "neutral" ][ "prone" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_towards" ][ "stand" ] = hidden[ "looking_towards" ][ "stand" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_towards" ][ "crouch" ] = hidden[ "looking_towards" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "hidden" ][ "looking_towards" ][ "prone" ] = hidden[ "looking_towards" ][ "prone" ];
}
if ( isdefined( alert ) )
{
self._stealth.behavior.stance_handler[ "alert" ][ "looking_away" ][ "stand" ] = alert[ "looking_away" ][ "stand" ];
self._stealth.behavior.stance_handler[ "alert" ][ "looking_away" ][ "crouch" ] = alert[ "looking_away" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "alert" ][ "looking_away" ][ "prone" ] = alert[ "looking_away" ][ "prone" ];
self._stealth.behavior.stance_handler[ "alert" ][ "neutral" ][ "stand" ] = alert[ "neutral" ][ "stand" ];
self._stealth.behavior.stance_handler[ "alert" ][ "neutral" ][ "crouch" ] = alert[ "neutral" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "alert" ][ "neutral" ][ "prone" ] = alert[ "neutral" ][ "prone" ];
self._stealth.behavior.stance_handler[ "alert" ][ "looking_towards" ][ "stand" ] = alert[ "looking_towards" ][ "stand" ];
self._stealth.behavior.stance_handler[ "alert" ][ "looking_towards" ][ "crouch" ] = alert[ "looking_towards" ][ "crouch" ];
self._stealth.behavior.stance_handler[ "alert" ][ "looking_towards" ][ "prone" ] = alert[ "looking_towards" ][ "prone" ];
}
}
enemy_try_180_turn( pos )
{
if ( self._stealth.logic.dog )
return;
vec1 = anglestoforward( self.angles );
vec2 = vectornormalize( pos - self.origin );
// if the goal is behind him - do a 180 anim
if ( vectordot( vec1, vec2 ) < - .8 )
{
// is there an obstacle in his way
start = self.origin + ( 0, 0, 16 );
end = pos + ( 0, 0, 16 );
spot = physicstrace( start, end );
if ( spot == end )
self anim_generic( self, "patrol_turn180" );
}
}
enemy_go_back( spot )
{
self notify( "going_back" );
self endon( "death" );
self notify( "stop_loop" );
self enemy_try_180_turn( spot );
if ( isdefined( self.script_patroller ) )
{
if ( isdefined( self.last_patrol_goal ) )
self.target = self.last_patrol_goal.targetname;
self thread maps\_patrol::patrol();
}
else
{
if ( !self._stealth.logic.dog )
{
self set_generic_run_anim( "patrol_walk", true );
self.disablearrivals = true;
self.disableexits = true;
}
if ( ! self MayMoveToPoint( spot ) )
{
nodes = enemy_get_closest_pathnodes( 128, spot );
if( !nodes.size )
return;
nodes = get_array_of_closest( spot, nodes );
spot = nodes[ 0 ].origin;
}
self setgoalpos( spot );
self.goalradius = 40;
}
}
ai_create_behavior_function( name, key, function, array )
{
self._stealth.behavior.ai_functions[ name ][ key ] = function;
}
ai_change_ai_functions( name, functions )
{
if ( !isdefined( functions ) )
return;
keys = getarraykeys( functions );
for ( i = 0; i < keys.size; i++ )
{
key = keys[ i ];
self ai_change_behavior_function( name, key, functions[ key ] );
}
}
ai_change_behavior_function( name, key, function, array )
{
/#
if ( !isdefined( self._stealth.behavior.ai_functions[ name ][ key ] ) )
{
// draw whatever you like
msg = "";
if ( isdefined( array ) )
{
keys = getarraykeys( array );
for ( i = 0; i < keys.sise; i++ )
{
msg += keys[ i ];
msg += ", ";
}
assertmsg( "array index with key: " + key + " is not valid. valid array keys are: " + msg );
}
else
{
assertmsg( "Failed to add stealth function " + key );
}
return;
}
#/
self ai_create_behavior_function( name, key, function, array );
}
ai_clear_custom_animation_reaction()
{
self._stealth.behavior.event.custom_animation = undefined;
}
ai_clear_custom_animation_reaction_and_idle()
{
// 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" );
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 );
}
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 );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* GLOBAL STEALTH BEHAVIOR FOR FRIENDLIES AND ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
system_init( state_functions )
{
assertEX( isdefined( level._stealth ), "There is no level._stealth struct. You ran stealth behavior before running the detection logic. Run _stealth_logic::main() in your level load first" );
// this is our behavior struct inside of _stealth...everything we do will go in here.
level._stealth.behavior = spawnstruct();
// bools to see if a sound has just been played
level._stealth.behavior.sound = [];
level._stealth.behavior.sound[ "huh" ] = false;
level._stealth.behavior.sound[ "hmph" ] = false;
level._stealth.behavior.sound[ "wtf" ] = false;
level._stealth.behavior.sound[ "spotted" ] = false;
level._stealth.behavior.sound[ "corpse" ] = false;
level._stealth.behavior.sound[ "alert" ] = false;
level._stealth.behavior.corpse = spawnstruct();
level._stealth.behavior.corpse.last_pos = ( 0, 0, -100000 );
level._stealth.behavior.corpse.search_radius = 512;
level._stealth.behavior.corpse.node_array = undefined;
level._stealth.behavior.event_explosion_index = 5;
level._stealth.behavior.system_state_functions = [];
system_init_state_functions( state_functions );
maps\_stealth_anims::main();
flag_init( "_stealth_searching_for_nodes" );
flag_init( "_stealth_getallnodes" );
flag_init( "_stealth_event" );
}
system_init_state_functions( state_functions )
{
custom_state_functions = false;
if ( isdefined( state_functions ) )
{
if ( isdefined( state_functions[ "hidden" ] ) &&
isdefined( state_functions[ "alert" ] ) &&
isdefined( state_functions[ "spotted" ] ) )
{
custom_state_functions = true;
}
else
{
assertmsg( "you sent _stealth_behavior::main( <option_state_function_array> ) a variable but it was invalid. The variable needs to be an array of 3 indicies with values 'hidden', 'alert' and 'spotted'. These indicies must be function pointers to the system functions you wish to handle those 3 states." );
}
}
if ( !custom_state_functions )
{
system_set_state_function( "hidden", ::system_state_hidden );
system_set_state_function( "alert", ::system_state_alert );
system_set_state_function( "spotted", ::system_state_spotted );
}
else
{
system_set_state_function( "hidden", state_functions[ "hidden" ] );
system_set_state_function( "alert", state_functions[ "alert" ] );
system_set_state_function( "spotted", state_functions[ "spotted" ] );
}
}
system_message_loop()
{
funcs = level._stealth.behavior.system_state_functions;
// these handle global "what to do" based on current state
thread system_message_handler( "_stealth_hidden", funcs[ "hidden" ] );
thread system_message_handler( "_stealth_alert", funcs[ "alert" ] );
thread system_message_handler( "_stealth_spotted", funcs[ "spotted" ] );
}
system_message_handler( _flag, function )
{
while ( 1 )
{
flag_wait( _flag );
thread [[ function ]]();
flag_waitopen( _flag );
}
}
system_state_spotted()
{
battlechatter_on( "axis" );
}
system_state_alert()
{
level._stealth.behavior.sound[ "spotted" ] = false;
battlechatter_off( "axis" );
setDvar( "bcs_stealth", "" );
// battlechatter_off( "allies" );
}
system_state_hidden()
{
level._stealth.behavior.sound[ "spotted" ] = false;
battlechatter_off( "axis" );
setDvar( "bcs_stealth", "1" );
animscripts\battlechatter::resetNextSayTimes( "allies", "threat" );
// battlechatter_off( "allies" );
}
system_set_state_function( detection_state, function )
{
if ( !( detection_state == "hidden" || detection_state == "alert" || detection_state == "spotted" ) )
assertmsg( "you sent _stealth_behavior::system_set_state_function( <detection_state> , <function> ) a bad detection state. Only valid states are 'hidden', 'alert', and 'spotted'" );
level._stealth.behavior.system_state_functions[ detection_state ] = function;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* INDIVIDUAL STEALTH BEHAVIOR FOR ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
enemy_logic( state_functions, alert_functions, corpse_functions, awareness_functions )
{
self enemy_init( state_functions, alert_functions, corpse_functions, awareness_functions );
self thread enemy_Message_Loop();// state functions effect this
self thread enemy_Threat_Loop();// alert functinos effect this
self thread enemy_Awareness_Loop();
self thread enemy_Animation_Loop();
self thread enemy_Corpse_Loop();// corpse functions effect this
}
enemy_init( state_functions, alert_functions, corpse_functions, awareness_functions )
{
assertEX( isdefined( self._stealth ), "There is no self._stealth struct. You ran stealth behavior before running the detection logic. Run _stealth_logic::enemy_init() on this AI first" );
// this is our behavior struct inside of _stealth...everything we do will go in here.
self._stealth.behavior = spawnstruct();
self._stealth.behavior.sndnum = randomintrange( 1, 4 );// this gives our ai a unique persistant voice actor
// AI FUNCTIONS
self._stealth.behavior.ai_functions = [];
self._stealth.behavior.ai_functions[ "state" ] = [];
self._stealth.behavior.ai_functions[ "alert" ] = [];
self._stealth.behavior.ai_functions[ "corpse" ] = [];
self._stealth.behavior.ai_functions[ "awareness" ] = [];
self enemy_default_ai_functions( "state" );
self enemy_default_ai_functions( "alert" );
self enemy_default_ai_functions( "corpse" );
self enemy_default_ai_functions( "awareness" );
self enemy_default_ai_functions( "animation" );
self ai_change_ai_functions( "state", state_functions );
self ai_change_ai_functions( "alert", alert_functions );
self ai_change_ai_functions( "corpse", corpse_functions );
self ai_change_ai_functions( "awareness", awareness_functions );
self ent_flag_init( "_stealth_enemy_alert_level_action" );
self ent_flag_init( "_stealth_running_to_corpse" );
self ent_flag_init( "_stealth_behavior_reaction_anim" );
self ent_flag_init( "_stealth_behavior_first_reaction" );
self ent_flag_init( "_stealth_behavior_reaction_anim_in_progress" );
self._stealth.behavior.event = spawnstruct();
if ( self._stealth.logic.dog )
self enemy_dog_init();
}
enemy_default_ai_functions( name )
{
switch( name )
{
case "state":
// state functions
self ai_create_behavior_function( "state", "hidden", ::enemy_state_hidden );
self ai_create_behavior_function( "state", "alert", ::enemy_state_alert );
self ai_create_behavior_function( "state", "spotted", ::enemy_state_spotted );
break;
case "alert":
// alert level functions
self ai_create_behavior_function( "alert", "reset", ::enemy_alert_level_lostem );
self ai_create_behavior_function( "alert", "alerted_once", ::enemy_alert_level_alerted_once );
self ai_create_behavior_function( "alert", "alerted_again", ::enemy_alert_level_alerted_again );
self ai_create_behavior_function( "alert", "attack", ::enemy_alert_level_attack );
break;
case "corpse":
// corpse functions
self ai_create_behavior_function( "corpse", "saw", ::enemy_corpse_saw_behavior );
self ai_create_behavior_function( "corpse", "found", ::enemy_corpse_found_behavior );
break;
case "awareness":
// awareness functions
self ai_create_behavior_function( "awareness", "heard_scream", ::enemy_awareness_reaction_heard_scream );
self ai_create_behavior_function( "awareness", "doFlashBanged", ::enemy_awareness_reaction_flashbang );
self ai_create_behavior_function( "awareness", "explode", ::enemy_awareness_reaction_explosion );
break;
case "animation":
self ai_create_behavior_function( "animation", "wrapper", ::enemy_animation_wrapper );
if ( self._stealth.logic.dog )
{
self ai_create_behavior_function( "animation", "reset", ::enemy_animation_nothing );
self ai_create_behavior_function( "animation", "attack", ::dog_animation_generic );
self ai_create_behavior_function( "animation", "heard_scream", ::dog_animation_generic );
self ai_create_behavior_function( "animation", "bulletwhizby", ::dog_animation_wakeup_fast );
self ai_create_behavior_function( "animation", "projectile_impact", ::dog_animation_wakeup_slow );
self ai_create_behavior_function( "animation", "explode", ::dog_animation_wakeup_fast );
}
else
{
self ai_create_behavior_function( "animation", "reset", ::enemy_animation_nothing );
self ai_create_behavior_function( "animation", "alerted_once", ::enemy_animation_nothing );
self ai_create_behavior_function( "animation", "alerted_again", ::enemy_animation_nothing );
self ai_create_behavior_function( "animation", "attack", ::enemy_animation_attack );
self ai_create_behavior_function( "animation", "heard_scream", ::enemy_animation_generic );
self ai_create_behavior_function( "animation", "heard_corpse", ::enemy_animation_generic );
self ai_create_behavior_function( "animation", "saw_corpse", ::enemy_animation_sawcorpse );
self ai_create_behavior_function( "animation", "found_corpse", ::enemy_animation_foundcorpse );
self ai_create_behavior_function( "animation", "bulletwhizby", ::enemy_animation_whizby );
self ai_create_behavior_function( "animation", "projectile_impact", ::enemy_animation_whizby );
self ai_create_behavior_function( "animation", "explode", ::enemy_animation_generic );
}
self ai_create_behavior_function( "animation", "doFlashBanged", ::enemy_animation_nothing );
break;
}
}
enemy_dog_init()
{
if ( threatbiasgroupexists( "dog" ) )
self setthreatbiasgroup( "dog" );
if ( isdefined( self.enemy ) || isdefined( self.favoriteenemy ) )
return;
self.ignoreme = true;
self.ignoreall = true;
self.allowdeath = true;
// we do this because we assume dogs are sleeping...
self thread anim_generic_loop( self, "_stealth_dog_sleeping", undefined, "stop_loop" );
}
enemy_Message_Loop()
{
// these handle global "what to do" based on current state
self thread ai_message_handler( "_stealth_hidden", "hidden" );
self thread ai_message_handler( "_stealth_alert", "alert" );
self thread ai_message_handler( "_stealth_spotted", "spotted" );
}
ai_message_handler( _flag, name )
{
self endon( "death" );
self endon( "pain_death" );
while ( 1 )
{
flag_wait( _flag );
function = self._stealth.behavior.ai_functions[ "state" ][ name ];
self thread [[ function ]]();
flag_waitopen( _flag );
}
}
enemy_state_hidden()
{
level endon( "_stealth_detection_level_change" );
self.fovcosine = .5;// 60 degrees to either side...120 cone...2 / 3 of the default
self.favoriteenemy = undefined;
if ( self._stealth.logic.dog )
return;
self.dieQuietly = true;
if ( !isdefined( self.old_baseAccuracy ) )
self.old_baseAccuracy = self.baseaccuracy;
if ( !isdefined( self.old_Accuracy ) )
self.old_Accuracy = self.accuracy;
self.baseAccuracy = self.old_baseAccuracy;
self.Accuracy = self.old_Accuracy;
self.fixednode = true;
self clearenemy();
}
enemy_state_alert()
{
level endon( "_stealth_detection_level_change" );
}
// 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_state_spotted()
{
_flag = "_stealth_spotted";
ender = "_stealth_detection_level_change" + _flag;
thread state_change_ender( _flag, ender );
level endon( ender );
self endon( "death" );
self.fovcosine = .01;// 90 degrees to either side...180 cone...default view cone
self.ignoreall = false;
if ( !self._stealth.logic.dog )
{
self.dieQuietly = false;
self clear_run_anim();
self.baseAccuracy *= 3;
self.Accuracy *= 3;
self.fixednode = false;
self enemy_stop_current_behavior();
}
if ( !isalive( self.enemy ) )
self waittill_notify_or_timeout( "enemy", randomfloatrange( 1, 3 ) );
if ( self._stealth.logic.dog )
self.favoriteenemy = level.player;
else if ( randomint( 100 ) > 25 )
self.favoriteenemy = level.player;// 75% chance that favorite enemy is the player
self thread enemy_spotted_clear_favorite();
}
enemy_spotted_clear_favorite()
{
self endon ( "death" );
wait 1.5;
self.favoriteenemy = undefined;
}
state_change_ender( _flag, ender )
{
flag_waitopen( _flag );
level notify( ender );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* ANIMATION REACTION CODE FOR ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
enemy_Animation_Loop()
{
self endon( "death" );
self endon( "pain_death" );
while ( 1 )
{
self waittill( "event_awareness", type );
//put inside the loop so we can check every time
wrapper_func = self._stealth.behavior.ai_functions[ "animation" ][ "wrapper" ];// enemy_animation_wrapper
self thread [[ wrapper_func ]]( type );
}
}
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 )
{
self endon( "death" );
self endon( "pain_death" );
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();
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 "alerted_once":
case "alerted_again":
// 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:
self ent_flag_set( "_stealth_behavior_first_reaction" );
self ent_flag_set( "_stealth_behavior_reaction_anim" );
break;
}
self ent_flag_set( "_stealth_behavior_reaction_anim_in_progress" );
return false;
}
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" );
}
enemy_animation_whizby( type )
{
self.allowdeath = true;
anime = "_stealth_behavior_whizby_" + randomint( 5 );
self thread anim_generic_custom_animmode( self, "gravity", anime );
wait 1.5;
self notify( "stop_animmode" );
}
enemy_animation_attack( type )
{
enemy = self._stealth.logic.event.awareness[ type ];
if ( distance( enemy.origin, self.origin ) < 256 )
anime = "_stealth_behavior_spotted_short";
else
anime = "_stealth_behavior_spotted_long";
self.allowdeath = true;
self thread anim_generic_custom_animmode( self, "gravity", anime );
self waittill_notify_or_timeout( anime, randomfloatrange( 1.5, 3 ) );
self notify( "stop_animmode" );
}
enemy_animation_nothing( type )
{
// these dont actually do anything, however their existance
// allows for custom reaction animations to be played even
// at this alert stage
}
enemy_animation_generic( type )
{
self.allowdeath = true;
target = level.player;
if ( isdefined( self.enemy ) )
target = self.enemy;
else if ( isdefined( self.favoriteenemy ) )
target = self.favoriteenemy;
dist = ( distance( self.origin, target.origin ) );
max = 4;
range = 1024;
for ( i = 1; i < max; i++ )
{
test = range * ( i / max );
if ( dist < test )
break;
}
anime = "_stealth_behavior_generic" + i;
self anim_generic_custom_animmode( self, "gravity", anime );
}
enemy_animation_sawcorpse( type )
{
self.allowdeath = true;
anime = "_stealth_behavior_saw_corpse";
self anim_generic_custom_animmode( self, "gravity", anime );
}
enemy_animation_foundcorpse( type )
{
self.allowdeath = true;
anime = "_stealth_find_jog";
self anim_generic_custom_animmode( self, "gravity", anime );
}
dog_animation_generic( type )
{
self.allowdeath = true;
anime = undefined;
if ( randomint( 100 ) < 50 )
anime = "_stealth_dog_wakeup_fast";
else
anime = "_stealth_dog_wakeup_slow";
self anim_generic_custom_animmode( self, "gravity", anime );
}
dog_animation_wakeup_fast( type )
{
self.allowdeath = true;
anime = "_stealth_dog_wakeup_fast";
self anim_generic_custom_animmode( self, "gravity", anime );
}
dog_animation_wakeup_slow( type )
{
self.allowdeath = true;
anime = "_stealth_dog_wakeup_slow";
self anim_generic_custom_animmode( self, "gravity", anime );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* AWARENESS BEHAVIOR CODE FOR ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
enemy_Awareness_Loop()
{
self endon( "death" );
self endon( "pain_death" );
while ( 1 )
{
self waittill( "event_awareness", type );
if( flag( "_stealth_spotted" ) )
continue;
//put this in the loop so that it can check for the function
//every time...that way we can change the functions on the fly
func = self._stealth.behavior.ai_functions[ "awareness" ];
if ( isdefined( func[ type ] ) )
self thread [[ func[ type ] ]]( type );
}
}
enemy_awareness_reaction_heard_scream( type )
{
if ( self._stealth.logic.dog )
return;
self endon( "_stealth_enemy_alert_level_change" );
level endon( "_stealth_spotted" );
self endon( "another_enemy_awareness_reaction" );
msg = "scream_kill_safety_check";
self thread enemy_awareness_reaction_safety( type, msg );
enemy_reaction_state_alert();
spot = self enemy_find_original_goal();
self enemy_stop_current_behavior();
self endon( "death" );
origin = self._stealth.logic.event.awareness[ type ];
origin = self enemy_find_nodes_at_origin( origin ); //1
self enemy_investigate_explosion( origin );
self thread enemy_announce_hmph();
self notify( msg );
self enemy_go_back( spot );
}
enemy_awareness_reaction_flashbang( type )
{
if ( self._stealth.logic.dog )
return;
self endon( "_stealth_enemy_alert_level_change" );
level endon( "_stealth_spotted" );
self endon( "another_enemy_awareness_reaction" );
msg = "flashbang_kill_safety_check";
self thread enemy_awareness_reaction_safety( type, msg );
enemy_reaction_state_alert();
spot = self enemy_find_original_goal();
self enemy_stop_current_behavior();
self endon( "death" );
enemy = self._stealth.logic.event.awareness[ type ];
origin = enemy.origin;
self waittill("stop_flashbang_effect");
origin = self enemy_find_nodes_at_origin( origin ); //2
self thread enemy_investigate_explosion( origin );
if( origin != ( 0, 0, 0 ) )
{
wait 1.05;
self waittill( "goal" );
self thread enemy_announce_wtf();
self thread enemy_announce_spotted_bring_team( origin );
}
self thread enemy_announce_hmph();
self notify( msg );
self enemy_go_back( spot );
}
enemy_awareness_reaction_explosion( type )
{
self endon( "_stealth_enemy_alert_level_change" );
if ( !self._stealth.logic.dog )
{
self endon( "_stealth_saw_corpse" );
level endon( "_stealth_found_corpse" );
}
level endon( "_stealth_spotted" );
self endon( "another_enemy_awareness_reaction" );
msg = "explostion_kill_safety_check";
self thread enemy_awareness_reaction_safety( type, msg );
enemy_reaction_state_alert();
spot = self enemy_find_original_goal();
self enemy_stop_current_behavior();
self endon( "death" );
origin = self._stealth.logic.event.awareness[ type ];
origin = self enemy_find_nodes_at_origin( origin ); //3
self thread enemy_announce_wtf();
self enemy_investigate_explosion( origin );
self thread enemy_announce_hmph();
self notify( msg );
self enemy_go_back( spot );
}
// so it doesn't end on itself
enemy_awareness_reaction_safety( type, msg )
{
self endon( "death" );
self endon( msg );
while ( 1 )
{
self waittill( "enemy_awareness_reaction", _type );
if ( type == _type )
continue;
else
break;
}
self notify( "another_enemy_awareness_reaction" );
}
enemy_find_nodes_at_origin( origin )
{
array = enemy_get_closest_pathnodes( 512, origin );
original = origin;
if( isdefined( array ) && array.size )
{
for ( i = 0 ; i < array.size; i++ )
{
if ( isdefined( array[ i ]._stealth_corpse_behavior_taken ) )
continue;
origin = array[ i ].origin;
array[ i ] thread enemy_corpse_reaction_takenode();
tooclose = get_array_of_closest( array[ i ].origin, array, undefined, undefined, 40 );
array_thread( tooclose, ::enemy_corpse_reaction_takenode );
break;
}
}
else
origin = (0,0,0);
if( original == origin )
origin = (0,0,0);
return origin;
}
enemy_investigate_explosion( origin )
{
if ( origin != ( 0, 0, 0 ) )
{
wait randomfloat( 1 );
self thread enemy_runto_and_lookaround( origin );
self.disablearrivals = false;
self.disableexits = false;
self waittill( "goal" );
wait randomfloatrange( 30, 50 );
}
else
wait randomfloatrange( 1, 4 );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* THREAT BEHAVIOR CODE FOR ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
enemy_Threat_Loop()
{
self endon( "death" );
self endon( "pain_death" );
if ( self._stealth.logic.dog )
self thread enemy_threat_logic_dog();
while ( 1 )
{
self waittill( "_stealth_enemy_alert_level_change" );
type = self._stealth.logic.alert_level.lvl;
enemy = self._stealth.logic.alert_level.enemy;
self thread enemy_alert_level_change( type, enemy );
}
}
// we do this because we assume dogs are sleeping...if so, then they'll never get an enemy
// because to make that assumption we set their ignorall to true in their init...so we need
// to give them the ability to find an enemy once they take damage...we might extend this in
// the future to also do the same thing if an ai gets too close or makes too much noise
enemy_threat_logic_dog()
{
self endon( "death" );
self endon( "pain_death" );
self waittill( "pain" );
self.ignoreall = false;
}
enemy_alert_level_change( type, enemy )
{
self ent_flag_set( "_stealth_enemy_alert_level_action" );
funcs = self._stealth.behavior.ai_functions[ "alert" ];
self thread [[ funcs[ type ] ]]( enemy );
}
enemy_alert_level_normal()
{
self endon( "_stealth_enemy_alert_level_change" );
self endon( "death" );
self endon( "pain_death" );
spot = self enemy_find_original_goal();
self enemy_stop_current_behavior();
self waittill( "normal" );
self ent_flag_clear( "_stealth_enemy_alert_level_action" );
self ent_flag_waitopen( "_stealth_saw_corpse" );
//to make sure found corpse is set
wait .05;
flag_waitopen( "_stealth_found_corpse" );
// if ( !isdefined( self._stealth.logic.corpse.corpse_entity ) )
self thread enemy_announce_hmph();
self enemy_go_back( spot );
}
enemy_find_original_goal()
{
if ( isdefined( self.last_set_goalnode ) )
return self.last_set_goalnode.origin;
if ( isdefined( self.last_set_goalpos ) )
return self.last_set_goalpos;
return self.origin;
}
enemy_alert_level_lostem( enemy )
{
// the ONLY time you'll ever recieve a 0 as a type of alert and get sent to this function is if
// the allies were spotted, probably had a big fire fight, and managed to actually get away and
// hide while there are still enemy ai alive.
// if this happens - we don't want them to chase the player down, but we dont want them to act like
// nothing ever happened before...so we need a special function for this case
self notify( "normal" );
}
enemy_alert_level_alerted_once( enemy )
{
self endon( "_stealth_enemy_alert_level_change" );
level endon( "_stealth_spotted" );
self endon( "death" );
self endon( "pain_death" );
self thread enemy_announce_huh();
self thread enemy_alert_level_normal();
if ( isdefined( self.script_patroller ) )
{
// we can call patrol walk and not stealth walk because we know the _patrol code is running
self set_generic_run_anim( "patrol_walk", true );
self.disablearrivals = true;
self.disableexits = true;
}
vec = vectornormalize( enemy.origin - self.origin );
dist = distance( enemy.origin, self.origin );
dist *= .25;
if ( dist < 64 )
dist = 64;
if ( dist > 128 )
dist = 128;
vec = vector_multiply( vec, dist );
spot = self.origin + vec + ( 0, 0, 16 );
end = spot + ( ( 0, 0, -96 ) );
spot = physicstrace( spot, end );
if ( spot == end )
return;
self setgoalpos( spot );
self.goalradius = 4;
// we do the timeout - because sometimes this puts his goal into a postion that
// is invalid and he'll never actually hit his goal...so we time out after 2 seconds
self waittill_notify_or_timeout( "goal", 2 );
wait 3;
self notify( "normal" );
}
enemy_alert_level_alerted_again( enemy )
{
self endon( "_stealth_enemy_alert_level_change" );
level endon( "_stealth_spotted" );
self endon( "death" );
self endon( "pain_death" );
self thread enemy_announce_huh();
self thread enemy_alert_level_normal();
self set_generic_run_anim( "_stealth_patrol_jog" );
self.disablearrivals = false;
self.disableexits = false;
lastknownspot = enemy.origin;
distance = distance( lastknownspot, self.origin );
self setgoalpos( lastknownspot );
self.goalradius = distance * .5;
self waittill( "goal" );
self set_generic_run_anim( "_stealth_patrol_walk", true );
/* anime = "_stealth_patrol_search_";
if ( randomint( 100 > 50 ) )
anime += "a";
else
anime += "b";
self set_generic_run_anim( anime );*/
self setgoalpos( lastknownspot );
self.goalradius = 64;
self.disablearrivals = true;
self.disableexits = true;
self waittill( "goal" );
wait 12;
self set_generic_run_anim( "_stealth_patrol_walk", true );
self notify( "normal" );
}
enemy_alert_level_attack( enemy )
{
self endon( "death" );
self endon( "pain_death" );
self endon( "_stealth_stop_stealth_behavior" );
self thread enemy_announce_spotted( self.origin );
self thread enemy_close_in_on_target();
}
enemy_close_in_on_target()
{
radius = 2048;
self.goalradius = radius;
if ( isdefined( self.script_stealth_dontseek ) )
return;
self endon( "death" );
self endon( "_stealth_stop_stealth_behavior" );
while ( isdefined( self.enemy ) )
{
self setgoalpos( self.enemy.origin );
self.goalradius = radius;
if ( radius > 600 )
radius *= .75;
wait 15;
}
}
enemy_announce_wtf()
{
if ( !( self enemy_announce_snd( "wtf" ) ) )
return;
self playsound( "RU_0_reaction_casualty_generic" );
}
enemy_announce_huh()
{
if ( !( self enemy_announce_snd( "huh" ) ) )
return;
alias = "scoutsniper_ru" + self._stealth.behavior.sndnum + "_huh";
self playsound( alias );
}
enemy_announce_hmph()
{
if ( !( self enemy_announce_snd( "hmph" ) ) )
return;
alias = "scoutsniper_ru" + self._stealth.behavior.sndnum + "_hmph";
self playsound( alias );
}
enemy_announce_spotted( pos )
{
self endon( "death" );
// self endon( "pain_death" ); -- > actually want to be able to still call out to buddies
// 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.
flag_wait( "_stealth_spotted" );
if ( !( self enemy_announce_snd( "spotted" ) ) )
return;
self thread enemy_announce_spotted_bring_team( pos );
if ( self._stealth.logic.dog )
return;
self playsound( "RU_0_reaction_casualty_generic" );
}
enemy_announce_spotted_bring_team( pos )
{
ai = getaispeciesarray( "axis", "all" );
for ( i = 0; i < ai.size; i++ )
{
if ( ai[ i ] == self )
continue;
if ( isdefined( ai[ i ].enemy ) )
continue;
if ( isdefined( ai[ i ].favoriteenemy ) )
continue;
ai[ i ] notify( "heard_scream", pos );
}
}
enemy_announce_corpse()
{
if ( isdefined( self.found_corpse_wait ) )
{
self endon( "death" );
wait( self.found_corpse_wait );
}
if ( !( self enemy_announce_snd( "corpse" ) ) )
return;
self playsound( "RU_0_reaction_casualty_generic" );
}
enemy_announce_snd( type )
{
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 )
{
if ( type == "spotted" )
return;
wait 3;
level._stealth.behavior.sound[ type ] = false;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* CORPSE BEHAVIOR CODE FOR ENEMIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
enemy_Corpse_Loop()
{
if ( self._stealth.logic.dog )
return;
self endon( "death" );
self endon( "pain_death" );
self thread enemy_found_corpse_loop();
while ( 1 )
{
self waittill( "_stealth_saw_corpse" );
self enemy_saw_corpse_logic();
}
}
enemy_saw_corpse_logic()
{
if ( flag( "_stealth_spotted" ) )
return;
level endon( "_stealth_spotted" );
level endon( "_stealth_found_corpse" );
self thread enemy_announce_huh();
while ( 1 )
{
self ent_flag_waitopen( "_stealth_enemy_alert_level_action" );
self enemy_corpse_saw_wrapper();
// the only reason we failed - and didn't end this function
// is if we had an alert change so wait to go back to normal
// and then resume looking for the corpse
self waittill( "normal" );
}
}
enemy_stop_current_behavior()
{
if ( !self ent_flag( "_stealth_behavior_reaction_anim" ) )
{
self stopanimscripted();
self notify( "stop_animmode" );
self notify( "stop_loop" );// for dogs
}
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();
}
enemy_corpse_saw_wrapper()
{
// if he maybe saw an enemy - that's more important
self endon( "enemy_alert_level_change" );
funcs = self._stealth.behavior.ai_functions[ "corpse" ];
self [[ funcs[ "saw" ] ]]();// ::enemy_corpse_saw_behavior()
}
enemy_corpse_saw_behavior()
{
self enemy_stop_current_behavior();
ent_flag_set( "_stealth_running_to_corpse" );
self.disableArrivals = false;
self.disableExits = false;
self set_generic_run_anim( "_stealth_combat_jog" );
self.goalradius = 80;// the animation of finding the corpse
self setgoalpos( self._stealth.logic.corpse.corpse_entity.origin );
}
enemy_found_corpse_loop()
{
self endon( "death" );
self endon( "pain_death" );
if ( isdefined( self.no_corpse_caring ) )
return;
if ( flag( "_stealth_spotted" ) )
return;
level endon( "_stealth_spotted" );
funcs = self._stealth.behavior.ai_functions[ "corpse" ];
while ( 1 )
{
flag_wait( "_stealth_found_corpse" );
while ( flag( "_stealth_found_corpse" ) )
{
if ( self ent_flag( "_stealth_found_corpse" ) )
self thread enemy_announce_corpse();
else
self notify( "heard_corpse", ( 0, 0, 0 ) );
self enemy_reaction_state_alert();
self [[ funcs[ "found" ] ]]();// ::enemy_corpse_found_behavior()
level waittill( "_stealth_found_corpse" );
}
}
}
enemy_corpse_found_behavior()
{
self enemy_stop_current_behavior();
if ( level._stealth.logic.corpse.last_pos != level._stealth.behavior.corpse.last_pos )
{
radius = level._stealth.behavior.corpse.search_radius;
origin = level._stealth.logic.corpse.last_pos;
level._stealth.behavior.corpse.node_array = enemy_get_closest_pathnodes( radius, origin );
level._stealth.behavior.corpse.last_pos = level._stealth.logic.corpse.last_pos;
}
array = level._stealth.behavior.corpse.node_array;
for ( i = 0 ; i < array.size; i++ )
{
if ( isdefined( array[ i ]._stealth_corpse_behavior_taken ) )
continue;
self thread enemy_runto_and_lookaround( array[ i ].origin );
array[ i ] thread enemy_corpse_reaction_takenode();
tooclose = get_array_of_closest( array[ i ].origin, array, undefined, undefined, 40 );
array_thread( tooclose, ::enemy_corpse_reaction_takenode );
break;
}
}
enemy_runto_and_lookaround( pos )
{
self notify( "enemy_runto_and_lookaround" );
self endon( "enemy_runto_and_lookaround" );
self endon( "death" );
self endon( "_stealth_enemy_alert_level_change" );
if ( !self._stealth.logic.dog )
self endon( "_stealth_saw_corpse" );
level endon( "_stealth_spotted" );
self setgoalpos( pos );
self.goalradius = 4;
self waittill( "goal" );
wait .5;
if ( !self._stealth.logic.dog )
self anim_generic_loop( self, "_stealth_look_around", undefined, "stop_loop" );
}
enemy_corpse_reaction_takenode()
{
self._stealth_corpse_behavior_taken = true;
// everything is going to happen within the first frame
// so waiting a second is more than enough, to pass the
// check for the other ai
wait .25;
self._stealth_corpse_behavior_taken = undefined;
}
enemy_get_closest_pathnodes( radius, origin )
{
if( !flag( "_stealth_getallnodes" ) )
{
//there's no reason why this should be called more than once every frame
if( !flag( "_stealth_searching_for_nodes" ) )
{
flag_set( "_stealth_searching_for_nodes" );
waittillframeend;//to make sure everyone has a chance to drop to the else statement
nodes = getallnodes();
pathnodes = [];
radus2rd = radius * radius;
for ( i = 0; i < nodes.size; i++ )
{
if ( nodes[ i ].type != "Path" )
continue;
if ( distancesquared( nodes[ i ].origin, origin ) > radus2rd )
continue;
pathnodes[ pathnodes.size ] = nodes[ i ];
}
level._stealth.behavior.search_nodes_array = pathnodes;
waittillframeend;//to make sure the flag isn't set and cleared in the same frame;
flag_clear( "_stealth_searching_for_nodes" );
}
else
flag_waitopen( "_stealth_searching_for_nodes" );
level delaythread( .2, ::flag_clear, "_stealth_getallnodes" );
level flag_set( "_stealth_getallnodes" );
}
return level._stealth.behavior.search_nodes_array;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* INDIVIDUAL STEALTH BEHAVIOR FOR FRIENDLIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
friendly_logic( state_functions )
{
self friendly_init( state_functions );
self thread friendly_Message_Loop();
self thread friendly_stance_handler();
}
friendly_init( state_functions )
{
assertEX( isdefined( self._stealth ), "There is no self._stealth struct. You ran stealth behavior before running the detection logic. Run _stealth_logic::friendly_init() on this AI first" );
// this is our behavior struct inside of _stealth...everything we do will go in here.
self._stealth.behavior = spawnstruct();
self._stealth.behavior.accuracy = [];
self._stealth.behavior.goodAccuracy = 50;
self._stealth.behavior.badAccuracy = 0;
self._stealth.behavior.old_baseAccuracy = self.baseAccuracy;
self._stealth.behavior.old_Accuracy = self.Accuracy;
// AI FUNCTIONS
self._stealth.behavior.ai_functions = [];
self._stealth.behavior.ai_functions[ "state" ] = [];
self friendly_default_ai_functions( "state" );
self ai_change_ai_functions( "state", state_functions );
self ent_flag_init( "_stealth_custom_anim" );
}
friendly_default_ai_functions( name )
{
switch( name )
{
case "state":
// state functions
self ai_create_behavior_function( name, "hidden", ::friendly_state_hidden );
self ai_create_behavior_function( name, "alert", ::friendly_state_alert );
self ai_create_behavior_function( name, "spotted", ::friendly_state_spotted );
break;
}
}
friendly_Message_Loop()
{
funcs = self._stealth.behavior.ai_functions[ "state" ];
// these handle global "what to do" based on current state
self thread ai_message_handler( "_stealth_hidden", "hidden" );
self thread ai_message_handler( "_stealth_alert", "alert" );
self thread ai_message_handler( "_stealth_spotted", "spotted" );
}
friendly_state_hidden()
{
level endon( "_stealth_detection_level_change" );
self.baseAccuracy = self._stealth.behavior.goodaccuracy;
self.Accuracy = self._stealth.behavior.goodaccuracy;
self._stealth.behavior.oldgrenadeammo = self.grenadeammo;
self.grenadeammo = 0;
self.forceSideArm = false;
self.ignoreall = true;
self.ignoreme = true;
self disable_ai_color();
waittillframeend;
self.fixednode = false;
}
friendly_state_alert()
{
level endon( "_stealth_detection_level_change" );
// same as hidden - but without ignoreme turned off
self.baseAccuracy = self._stealth.behavior.goodaccuracy;
self.Accuracy = self._stealth.behavior.goodaccuracy;
self._stealth.behavior.oldgrenadeammo = self.grenadeammo;
self.grenadeammo = 0;
self.forceSideArm = false;
self.ignoreall = true;
// self.ignoreme = true;
self disable_ai_color();
waittillframeend;
self.fixednode = false;
}
friendly_state_spotted()
{
level endon( "_stealth_detection_level_change" );
self thread friendly_spotted_getup_from_prone();
self.baseAccuracy = self._stealth.behavior.badaccuracy;
self.Accuracy = self._stealth.behavior.badaccuracy;
self.grenadeammo = self._stealth.behavior.oldgrenadeammo;
self allowedstances( "prone", "crouch", "stand" );
self stopanimscripted();
self.ignoreall = false;
self.ignoreme = false;
self disable_cqbwalk();
self enable_ai_color();
self.disablearrivals = true;
self.disableexits = true;
self pushplayer( false );
}
friendly_spotted_getup_from_prone( angles )
{
self endon( "death" );
if ( self._stealth.logic.stance != "prone" )
return;
self ent_flag_set( "_stealth_custom_anim" );
anime = "prone_2_run_roll";
if ( isdefined( angles ) )
self orientMode( "face angle", angles[ 1 ] + 20 );
// self thread friendly_spotted_getup_from_prone_rotate( angles, anime );
self thread anim_generic_custom_animmode( self, "gravity", anime );
length = getanimlength( getanim_generic( anime ) );
wait( length - .2 );
self notify( "stop_animmode" );
self ent_flag_clear( "_stealth_custom_anim" );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
/* SMART STANCE LOGIC FOR FRIENDLIES */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * **/
friendly_stance_handler()
{
self endon( "death" );
self endon( "pain_death" );
self friendly_stance_handler_init();
while ( 1 )
{
while ( self ent_flag( "_stealth_stance_handler" ) && !flag( "_stealth_spotted" ) )
{
self friendly_stance_handler_set_stance_up();
stances = [];
stances = friendly_stance_handler_check_mightbeseen( stances );
// this means we're currently visible we need to drop a stance or stay still
if ( stances[ self._stealth.logic.stance ] )
self thread friendly_stance_handler_change_stance_down();
// ok coast is clear - we can go again if we were staying still
else if ( self ent_flag( "_stealth_stay_still" ) )
self thread friendly_stance_handler_resume_path();
// this means we can actually go one stance up
else if ( ! stances[ self._stealth.behavior.stance_up ] )
self thread friendly_stance_handler_change_stance_up();
// so - we're not stancing up, we're not stancing down, or staying still...lets notify
// ourselves that we should stay in the same stance( just in case we're about to stance up )
else if ( self ent_flag( "_stealth_stance_change" ) )
self notify( "_stealth_stance_dont_change" );
wait .05;
}
self.moveplaybackrate = 1;
self allowedstances( "stand", "crouch", "prone" );
self thread friendly_stance_handler_resume_path();
self ent_flag_wait( "_stealth_stance_handler" );
flag_waitopen( "_stealth_spotted" );
}
}
friendly_stance_handler_stay_still()
{
if ( self ent_flag( "_stealth_stay_still" ) )
return;
self ent_flag_set( "_stealth_stay_still" );
badplace_cylinder( "_stealth_" + self.ai_number + "_prone", 0, self.origin, 30, 90, "axis" );
self notify( "stop_loop" );
self thread anim_generic_loop( self, "_stealth_prone_idle", undefined, "stop_loop" );
}
friendly_stance_handler_resume_path()
{
self ent_flag_clear( "_stealth_stay_still" );
badplace_delete( "_stealth_" + self.ai_number + "_prone" );
self notify( "stop_loop" );
}
friendly_stance_handler_change_stance_down()
{
self.moveplaybackrate = 1;
self notify( "_stealth_stance_down" );
switch( self._stealth.logic.stance )
{
case "stand":
self.moveplaybackrate = .7;
self allowedstances( "crouch" );
break;
case "crouch":
self allowedstances( "prone" );
break;
case "prone":
friendly_stance_handler_stay_still();
break;
}
}
friendly_stance_handler_change_stance_up()
{
self endon( "_stealth_stance_down" );
self endon( "_stealth_stance_dont_change" );
self endon( "_stealth_stance_handler" );
if ( self ent_flag( "_stealth_stance_change" ) )
return;
// we wait a sec before deciding to actually stand up - just like a real player
self ent_flag_set( "_stealth_stance_change" );
wait 1.5;
// now check again
self ent_flag_clear( "_stealth_stance_change" );
self.moveplaybackrate = 1;
switch( self._stealth.logic.stance )
{
case "prone":
self allowedstances( "crouch" );
break;
case "crouch":
self allowedstances( "stand" );
break;
case "stand":
break;
}
}
friendly_stance_handler_check_mightbeseen( stances )
{
// not using species because we dont care about dogs...
// when they're awake - we're already not in stealth mode anymore
ai = getaispeciesarray( "axis", "all" );
stances[ self._stealth.logic.stance ] = 0;
stances[ self._stealth.behavior.stance_up ] = 0;
for ( i = 0; i < ai.size; i++ )
{
// this is how much to add based on a fast sight trace
dist_add_curr = self friendly_stance_handler_return_ai_sight( ai[ i ], self._stealth.logic.stance );
dist_add_up = self friendly_stance_handler_return_ai_sight( ai[ i ], self._stealth.behavior.stance_up );
// this is the score for both the current stance and the next one up
score_current = ( self maps\_stealth_logic::friendly_compute_score() ) + dist_add_curr;
score_up = ( self maps\_stealth_logic::friendly_compute_score( self._stealth.behavior.stance_up ) ) + dist_add_up;
if ( distance( ai[ i ].origin, self.origin ) < score_current )
{
stances[ self._stealth.logic.stance ] = score_current;
break;
}
if ( distance( ai[ i ].origin, self.origin ) < score_up )
stances[ self._stealth.behavior.stance_up ] = score_up;
}
return stances;
}
friendly_stance_handler_set_stance_up()
{
// figure out what the next stance up is
switch( self._stealth.logic.stance )
{
case "prone":
self._stealth.behavior.stance_up = "crouch";
break;
case "crouch":
self._stealth.behavior.stance_up = "stand";
break;
case "stand":
self._stealth.behavior.stance_up = "stand";// can't leave it as undefined
break;
}
}
friendly_stance_handler_return_ai_sight( ai, stance )
{
// check to see where the ai is facing
vec1 = anglestoforward( ai.angles );// this is the direction the ai is facing
vec2 = vectornormalize( self.origin - ai.origin );// this is the direction from him to us
// comparing the dotproduct of the 2 will tell us if he's facing us and how much so..
// 0 will mean his direction is exactly perpendicular to us,
// 1 will mean he's facing directly at us
// - 1 will mean he's facing directly away from us
vecdot = vectordot( vec1, vec2 );
state = level._stealth.logic.detection_level;
// is the ai facing us?
if ( vecdot > .3 )
return self._stealth.behavior.stance_handler[ state ][ "looking_towards" ][ stance ];
// is the ai facing away from us
else if ( vecdot < - .7 )
return self._stealth.behavior.stance_handler[ state ][ "looking_away" ][ stance ];
// the ai is kinda not facing us or away
else
return self._stealth.behavior.stance_handler[ state ][ "neutral" ][ stance ];
}
#using_animtree( "generic_human" );
friendly_stance_handler_init()
{
self ent_flag_init( "_stealth_stance_handler" );
self ent_flag_init( "_stealth_stay_still" );
self ent_flag_init( "_stealth_stance_change" );
level.scr_anim[ "generic" ][ "_stealth_prone_idle" ][ 0 ] = %prone_aim_idle;
self._stealth.behavior.stance_up = undefined;
self._stealth.behavior.stance_handler = [];
self friendly_default_stance_handler_distances();
}
/************************************************************************************************************/
/* GROUP STEALTH AWARENESS EVENT BEHAVIOR FOR FRIENDLIES */
/************************************************************************************************************/
default_event_awareness( dialogue_func, ender1, ender2, ender3 )
{
level notify ( "event_awareness_handler" );
level endon ( "event_awareness_handler" );
level endon ( "default_event_awareness_enders" );
thread default_event_awareness_enders( ender1, ender2, ender3 );
array_thread( getaiarray( "allies" ), ::default_event_awareness_ended_cleanup );
thread default_event_awareness_killed_cleanup();
while( 1 )
{
type = default_event_awareness_wait();
array_thread( getaiarray( "allies" ), ::default_event_awareness_setup );
waittillframeend;//makesure setup finishes
array_thread( getaiarray( "allies" ), ::default_event_awareness_handle_changes );
flag_set( "_stealth_event" );
wait 2;
[[ dialogue_func ]]();
default_event_awareness_waitclear( type );
array_thread( getaiarray( "allies" ), ::default_event_awareness_cleanup );
flag_clear( "_stealth_event" );
}
}
default_event_awareness_enders( ender1, ender2, ender3 )
{
level endon( "default_event_awareness_enders" );
level endon ( "event_awareness_handler" );
if( isdefined( ender1 ) && isdefined( level.flag[ ender1 ] ) && flag( ender1 ) )
level notify( "default_event_awareness_enders" );
if( isdefined( ender2 ) && isdefined( level.flag[ ender2 ] ) && flag( ender2 ) )
level notify( "default_event_awareness_enders" );
if( isdefined( ender3 ) && isdefined( level.flag[ ender3 ] ) && flag( ender3 ) )
level notify( "default_event_awareness_enders" );
if( flag( "_stealth_spotted" ) )
level notify( "default_event_awareness_enders" );
level waittill_any( "end_event_awareness_handler", "_stealth_spotted", ender1, ender2, ender3 );
level notify( "default_event_awareness_enders" );
}
default_event_awareness_killed_cleanup()
{
level waittill_either( "event_awareness_handler", "default_event_awareness_enders" );
flag_clear( "_stealth_event" );
}
default_event_awareness_ended_cleanup()
{
level endon ( "event_awareness_handler" );
level waittill( "default_event_awareness_enders" );
self ent_flag_clear( "_stealth_stance_handler" );
if( flag( "_stealth_spotted" ) )
return;
if( isdefined( self._stealth.behavior.alreadyignoreme ) && self._stealth.behavior.alreadyignoreme )
self.ignoreme = true;
}
default_event_awareness_setup()
{
self._stealth.behavior.alreadysmartstance = self ent_flag( "_stealth_stance_handler" );
self._stealth.behavior.alreadyignoreme = self.ignoreme;
self ent_flag_set( "_stealth_stance_handler" );
self.ignoreme = false;
}
default_event_awareness_handle_changes()
{
self endon( "default_event_awareness_cleanup" );
level endon ( "default_event_awareness_enders" );
while( 1 )
{
self waittill( "_stealth_stance_handler" );
self._stealth.behavior.alreadysmartstance = self ent_flag( "_stealth_stance_handler" );
if( !self ent_flag( "_stealth_stance_handler" ) )
{
self ent_flag_set( "_stealth_stance_handler" );
wait .05;
}
}
}
default_event_awareness_cleanup()
{
self notify( "default_event_awareness_cleanup" );
if( !self._stealth.behavior.alreadysmartstance )
self ent_flag_clear( "_stealth_stance_handler" );
if( isdefined( self._stealth.behavior.alreadyignoreme ) && self._stealth.behavior.alreadyignoreme )
self.ignoreme = true;
}
default_event_awareness_wait()
{
level endon( "_stealth_found_corpse" );
while( 1 )
{
level waittill( "event_awareness", type );
switch( type )
{
case "found_corpse":
return type;
case "heard_corpse":
return type;
case "heard_scream":
return type;
// case "alerted_again":
// return type;
case "explode":
return type;
default:
continue;
}
}
}
default_event_awareness_waitclear( type )
{
if( isdefined( type ) )
{
ai = getaispeciesarray( "axis", "all" );
dist = level._stealth.logic.detect_range[ "alert" ][ "crouch" ];
array_thread( ai, ::default_event_awareness_waitclear_ai, dist );
array_wait( ai, "default_event_awareness_waitclear_ai" );
}
if( flag( "_stealth_found_corpse" ) )
{
ai = getaispeciesarray( "axis", "all" );
dist = level._stealth.logic.detect_range[ "alert" ][ "stand" ];
array_thread( ai, ::default_event_awareness_waitclear_ai, dist );
array_wait( ai, "default_event_awareness_waitclear_ai" );
}
}
default_event_awareness_waitclear_ai( dist )
{
self default_event_awareness_waitclear_ai_proc( dist );
self notify( "default_event_awareness_waitclear_ai" );
}
default_event_awareness_waitclear_ai_proc( dist )
{
self endon( "death" );
waittillframeend;//make sure these flag's are set;
check1 = false;
if( isdefined( self.ent_flag[ "_stealth_behavior_first_reaction" ] ) )
check1 = self ent_flag( "_stealth_behavior_first_reaction" );
check2 = false;
if( isdefined( self.ent_flag[ "_stealth_behavior_reaction_anim" ] ) )
check1 = self ent_flag( "_stealth_behavior_reaction_anim" );
if( !check1 && !check2 )
return;
self add_wait( ::waittill_msg, "death" );
self add_wait( ::waittill_msg, "going_back" );
level add_wait( ::flag_wait, "_stealth_found_corpse" );
do_wait_any();
self endon( "goal" );
distsquared = dist * dist;
while( distancesquared( self.origin, level.price.origin ) < distsquared )
wait 1;
}