527 lines
14 KiB
Plaintext
527 lines
14 KiB
Plaintext
|
#include common_scripts\utility;
|
||
|
#include maps\_utility;
|
||
|
#include maps\_anim;
|
||
|
#include maps\_stealth_utility;
|
||
|
#include maps\_stealth_shared_utilities;
|
||
|
#include maps\_stealth_animation_funcs;
|
||
|
|
||
|
stealth_threat_enemy_main()
|
||
|
{
|
||
|
self enemy_init();
|
||
|
|
||
|
self thread enemy_Threat_Loop();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************/
|
||
|
/* LOGIC */
|
||
|
/************************************************************************************************************/
|
||
|
enemy_Threat_Loop()
|
||
|
{
|
||
|
self endon( "death" );
|
||
|
self endon( "pain_death" );
|
||
|
|
||
|
if ( self.type == "dog" )
|
||
|
self thread enemy_threat_logic_dog();
|
||
|
|
||
|
while ( 1 )
|
||
|
{
|
||
|
self waittill( "_stealth_enemy_alert_level_change", type );
|
||
|
|
||
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
||
|
continue;
|
||
|
|
||
|
self enemy_alert_level_change_reponse( type );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_change_reponse( type )
|
||
|
{
|
||
|
self ent_flag_set( "_stealth_enemy_alert_level_action" );
|
||
|
|
||
|
_type = type;
|
||
|
if ( issubstr( type, "warning" ) )
|
||
|
_type = "warning";
|
||
|
|
||
|
switch( _type )
|
||
|
{
|
||
|
case "warning":
|
||
|
self thread enemy_alert_level_warning_wrapper( type );
|
||
|
break;
|
||
|
case "attack":
|
||
|
self thread enemy_alert_level_attack_wrapper();
|
||
|
break;
|
||
|
case "reset":
|
||
|
self thread enemy_alert_level_reset_wrapper();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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" );
|
||
|
|
||
|
if ( !self ent_flag( "_stealth_behavior_asleep" ) )
|
||
|
return;
|
||
|
|
||
|
self enemy_threat_logic_dog_wait();
|
||
|
wait .5;
|
||
|
|
||
|
self delaythread( .6, ::ent_flag_clear, "_stealth_behavior_asleep" );
|
||
|
self.ignoreall = false;
|
||
|
}
|
||
|
|
||
|
enemy_threat_logic_dog_wait()
|
||
|
{
|
||
|
self endon( "pain" );
|
||
|
self endon( "enemy" );
|
||
|
|
||
|
array_thread( level.players, ::enemy_threat_logic_dog_wakeup_dist, self, 128 );
|
||
|
|
||
|
while ( 1 )
|
||
|
{
|
||
|
self waittill( "event_awareness", type );
|
||
|
|
||
|
if ( !self ent_flag( "_stealth_enabled" ) )
|
||
|
continue;
|
||
|
|
||
|
if ( type == "heard_scream" || type == "bulletwhizby" || type == "projectile_impact" || type == "explode" )
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enemy_threat_logic_dog_wakeup_dist( dog, dist )
|
||
|
{
|
||
|
dog endon( "death" );
|
||
|
self endon( "death" );
|
||
|
|
||
|
if ( !dog ent_flag( "_stealth_behavior_asleep" ) )
|
||
|
return;
|
||
|
dog endon( "_stealth_behavior_asleep" );
|
||
|
|
||
|
distsqrd = dist * dist;
|
||
|
|
||
|
while ( distancesquared( self.origin, dog.origin ) > distsqrd && self ent_flag( "_stealth_enabled" ) )
|
||
|
wait .1;
|
||
|
|
||
|
dog.ignoreall = false;
|
||
|
dog.favoriteenemy = self;
|
||
|
wait .1;
|
||
|
dog.favoriteenemy = undefined;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************/
|
||
|
/* THREAT LEVELS */
|
||
|
/************************************************************************************************************/
|
||
|
enemy_alert_level_reset_wrapper()
|
||
|
{
|
||
|
//this function is different than normal because if we want to change the behavior the enemies have
|
||
|
//after losing a known enemy - we can set it up here...also we don't care about any previous knowledge of corpses
|
||
|
//through this wrapper either and we make sure we wait for the spotted flag to clear before going back to normal.
|
||
|
//this why this function exists instead of just using the normal wrapper
|
||
|
|
||
|
self endon( "_stealth_enemy_alert_level_change" );
|
||
|
self endon( "enemy_awareness_reaction" );//we'll end on a new event, but events will also end on a new threat
|
||
|
self endon( "death" );
|
||
|
self endon( "pain_death" );
|
||
|
|
||
|
self stealth_group_spotted_flag_waitopen();//wait for everyone to lose their enemy too
|
||
|
|
||
|
self enemy_stop_current_behavior();
|
||
|
|
||
|
self ent_flag_clear( "_stealth_enemy_alert_level_action" );
|
||
|
if( isdefined( self._stealth.plugins.corpse ) )
|
||
|
{
|
||
|
self ent_flag_clear( "_stealth_saw_corpse" );
|
||
|
self ent_flag_clear( "_stealth_found_corpse" );
|
||
|
}
|
||
|
self ent_flag_clear( "_stealth_attack" );
|
||
|
self ent_flag_set( "_stealth_normal" );
|
||
|
|
||
|
function = ai_get_behavior_function( "threat", "reset" ); // default ::enemy_alert_level_normal
|
||
|
self thread [[ function ]]();
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_warning_wrapper( type )
|
||
|
{
|
||
|
spotted_flag = self group_get_flagname( "_stealth_spotted" );
|
||
|
|
||
|
self endon( "_stealth_enemy_alert_level_change" );
|
||
|
level endon( spotted_flag );
|
||
|
self endon( "death" );
|
||
|
self endon( "pain_death" );
|
||
|
|
||
|
self enemy_find_original_goal();
|
||
|
self enemy_stop_current_behavior();
|
||
|
|
||
|
function = ai_get_behavior_function( "threat", type ); // default ::enemy_alert_level_warning1, 2
|
||
|
self [[ function ]]();
|
||
|
|
||
|
// done with warning response behavior and still in warning state, return to normal
|
||
|
self enemy_alert_level_normal_wrapper();
|
||
|
}
|
||
|
|
||
|
|
||
|
enemy_lookaround_for_time( time )
|
||
|
{
|
||
|
oldfov = self.fovcosine;
|
||
|
self.fovcosine = 0.1;
|
||
|
self set_generic_idle_anim( "_stealth_look_around" );
|
||
|
|
||
|
wait time;
|
||
|
|
||
|
self clear_generic_idle_anim();
|
||
|
self.fovcosine = oldfov;
|
||
|
}
|
||
|
|
||
|
enemy_announce_alert()
|
||
|
{
|
||
|
self endon( "death" );
|
||
|
|
||
|
wait 0.25;
|
||
|
|
||
|
if ( isdefined( self.enemy ) && self cansee( self.enemy ) )
|
||
|
{
|
||
|
self enemy_announce_snd( "huh" );
|
||
|
self thread enemy_announce_attack();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self thread enemy_announce_huh();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_warning1()
|
||
|
{
|
||
|
if ( !isdefined( self.enemy ) )
|
||
|
return;
|
||
|
|
||
|
self thread enemy_announce_alert();
|
||
|
|
||
|
if ( isdefined( self.script_patroller ) )
|
||
|
{
|
||
|
if ( self.type != "dog" )
|
||
|
{
|
||
|
type = "a";
|
||
|
if ( cointoss() )
|
||
|
type = "b";
|
||
|
self set_generic_run_anim( "_stealth_patrol_search_" + type, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self set_dog_walk_anim();
|
||
|
self.script_growl = 1;
|
||
|
}
|
||
|
self.disablearrivals = true;
|
||
|
self.disableexits = true;
|
||
|
}
|
||
|
else if ( self.type == "dog" )
|
||
|
{
|
||
|
self set_dog_walk_anim();
|
||
|
self.script_growl = 1;
|
||
|
self.disablearrivals = true;
|
||
|
self.disableexits = true;
|
||
|
}
|
||
|
|
||
|
vec = vectornormalize( self.enemy.origin - self.origin );
|
||
|
dist = distance( self.enemy.origin, self.origin );
|
||
|
|
||
|
dist *= .25;
|
||
|
|
||
|
dist = clamp( dist, 64, 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 ent_flag_set( "_stealth_override_goalpos" );
|
||
|
self setgoalpos( spot );
|
||
|
self.goalradius = 64;
|
||
|
|
||
|
// 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 );
|
||
|
|
||
|
// if AI can't reach it's goal, at least face it.
|
||
|
if ( !self isInGoal( self.origin ) )
|
||
|
self.shootPosOverride = spot + ( 0, 0, 64 );
|
||
|
|
||
|
enemy_lookaround_for_time( 10 );
|
||
|
|
||
|
self.shootPosOverride = undefined;
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_warning2()
|
||
|
{
|
||
|
if ( !isdefined( self.enemy ) )
|
||
|
return;
|
||
|
|
||
|
self thread enemy_announce_alert();
|
||
|
|
||
|
if ( self.type != "dog" )
|
||
|
self set_generic_run_anim( "_stealth_patrol_cqb" );
|
||
|
else
|
||
|
{
|
||
|
self clear_run_anim();
|
||
|
self.script_nobark = 1;
|
||
|
self.script_growl = 1;
|
||
|
}
|
||
|
self.disablearrivals = false;
|
||
|
self.disableexits = false;
|
||
|
// self.oldfixednode = self.fixednode;
|
||
|
// self.fixednode = true;
|
||
|
|
||
|
lastknownspot = self.enemy.origin;
|
||
|
dist = distance( lastknownspot, self.origin );
|
||
|
|
||
|
self ent_flag_set( "_stealth_override_goalpos" );
|
||
|
self setgoalpos( lastknownspot );
|
||
|
self.goalradius = dist * .5;
|
||
|
self waittill( "goal" );
|
||
|
|
||
|
if ( self.type != "dog" )
|
||
|
{
|
||
|
type = "_stealth_patrol_search_a";
|
||
|
if ( cointoss() )
|
||
|
type = "_stealth_patrol_search_b";
|
||
|
self set_generic_run_anim( type, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self anim_generic_custom_animmode( self, "gravity", "_stealth_dog_stop" );
|
||
|
self set_dog_walk_anim();
|
||
|
}
|
||
|
|
||
|
self setgoalpos( lastknownspot );
|
||
|
self.goalradius = 64;
|
||
|
self.disablearrivals = true;
|
||
|
self.disableexits = true;
|
||
|
|
||
|
self waittill( "goal" );
|
||
|
|
||
|
enemy_lookaround_for_time( 15 );
|
||
|
|
||
|
// self.fixednode = self.oldfixednode;
|
||
|
|
||
|
if ( self.type != "dog" )
|
||
|
{
|
||
|
type = "a";
|
||
|
if ( randomint( 100 ) > 50 )
|
||
|
type = "b";
|
||
|
self set_generic_run_anim( "_stealth_patrol_search_" + type, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self set_dog_walk_anim();
|
||
|
self.script_growl = undefined;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_attack_wrapper()
|
||
|
{
|
||
|
self endon( "death" );
|
||
|
self endon( "pain_death" );
|
||
|
self endon( "_stealth_enemy_alert_level_change" );// - > this might not be a good idea - just added it recently - haven't tested
|
||
|
|
||
|
self notify( "endNewEnemyReactionAnim" );
|
||
|
self notify( "movemode" );
|
||
|
|
||
|
self.disablearrivals = false;
|
||
|
self.disableexits = false;
|
||
|
|
||
|
self enemy_find_original_goal(); //should ALWAYS know what the original goal was just in case...if we dont have any warnings, we catch it here
|
||
|
self ent_flag_set( "_stealth_attack" );
|
||
|
|
||
|
function = ai_get_behavior_function( "threat", "attack" ); // default ::enemy_alert_level_attack
|
||
|
self [[ function ]]();
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_attack()
|
||
|
{
|
||
|
self thread enemy_announce_spotted( self.origin );
|
||
|
|
||
|
if ( isdefined( self.script_goalvolume ) )
|
||
|
self thread maps\_spawner::set_goal_volume();
|
||
|
else
|
||
|
self enemy_close_in_on_target();
|
||
|
}
|
||
|
|
||
|
enemy_close_in_on_target()
|
||
|
{
|
||
|
radius = 2048;
|
||
|
self.goalradius = radius;
|
||
|
|
||
|
if ( isdefined( self.script_stealth_dontseek ) && self.script_stealth_dontseek == true )
|
||
|
return;
|
||
|
|
||
|
self endon( "death" );
|
||
|
|
||
|
self ent_flag_set( "_stealth_override_goalpos" );
|
||
|
|
||
|
while ( isdefined( self.enemy ) && self ent_flag( "_stealth_enabled" ) )
|
||
|
{
|
||
|
self setgoalpos( self.enemy.origin );
|
||
|
self.goalradius = radius;
|
||
|
|
||
|
if ( radius > 600 )
|
||
|
radius *= .75;
|
||
|
else
|
||
|
return;
|
||
|
|
||
|
wait 15;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_normal_wrapper()
|
||
|
{
|
||
|
enemy_set_alert_level( "reset" );
|
||
|
|
||
|
self ent_flag_clear( "_stealth_enemy_alert_level_action" );
|
||
|
|
||
|
if ( self ent_flag_exist( "_stealth_saw_corpse" ) )
|
||
|
self ent_flag_waitopen( "_stealth_saw_corpse" );
|
||
|
|
||
|
//to make sure found corpse is set
|
||
|
wait .05;
|
||
|
|
||
|
if ( self ent_flag_exist( "_stealth_found_corpse" ) )
|
||
|
self ent_flag_waitopen( "_stealth_found_corpse" );
|
||
|
|
||
|
self ent_flag_set( "_stealth_normal" );
|
||
|
|
||
|
function = ai_get_behavior_function( "threat", "normal" ); // default ::enemy_alert_level_normal
|
||
|
self [[ function ]]();
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_normal()
|
||
|
{
|
||
|
self thread enemy_announce_hmph();
|
||
|
|
||
|
self enemy_go_back();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************/
|
||
|
/* SETUP */
|
||
|
/************************************************************************************************************/
|
||
|
|
||
|
enemy_init()
|
||
|
{
|
||
|
self enemy_default_threat_behavior();
|
||
|
self enemy_default_threat_anim_behavior();
|
||
|
|
||
|
self._stealth.plugins.threat = true;
|
||
|
self.script_stealth_dontseek = true;
|
||
|
|
||
|
self.alertLevel = "noncombat";
|
||
|
|
||
|
self.newEnemyReactionDistSq = squared( level._stealth.logic.ai_event[ "ai_eventDistFootstepSprint" ][ "hidden" ] );
|
||
|
}
|
||
|
|
||
|
enemy_default_threat_behavior()
|
||
|
{
|
||
|
array = [];
|
||
|
array[ "reset" ] = ::enemy_alert_level_normal;
|
||
|
array[ "warning1" ] = ::enemy_alert_level_warning1;
|
||
|
array[ "warning2" ] = ::enemy_alert_level_warning2;
|
||
|
array[ "attack" ] = ::enemy_alert_level_attack;
|
||
|
array[ "normal" ] = ::enemy_alert_level_normal;
|
||
|
|
||
|
if ( !isdefined( level._stealth.logic.alert_level_table ) )
|
||
|
{
|
||
|
level._stealth.logic.alert_level_table = [];
|
||
|
level._stealth.logic.alert_level_table[ "reset" ] = "noncombat";
|
||
|
level._stealth.logic.alert_level_table[ "warning" ] = "alert";
|
||
|
level._stealth.logic.alert_level_table[ "attack" ] = "combat";
|
||
|
}
|
||
|
|
||
|
self enemy_set_threat_behavior( array );
|
||
|
}
|
||
|
|
||
|
// set the code alert level
|
||
|
enemy_set_alert_level( type )
|
||
|
{
|
||
|
assertEx( isdefined( level._stealth.logic.alert_level_table[ type ] ), "unsupported alert_level" ); // may need a way to custom alert_level_table
|
||
|
self.alertLevel = level._stealth.logic.alert_level_table[ type ];
|
||
|
}
|
||
|
|
||
|
enemy_set_threat_behavior( array )
|
||
|
{
|
||
|
//clear the array
|
||
|
self._stealth.behavior.ai_functions[ "threat" ] = [];
|
||
|
|
||
|
if ( !isdefined( array[ "reset" ] ) )
|
||
|
array[ "reset" ] = ::enemy_alert_level_normal;
|
||
|
if ( !isdefined( array[ "attack" ] ) )
|
||
|
array[ "attack" ] = ::enemy_alert_level_attack;
|
||
|
if ( !isdefined( array[ "normal" ] ) )
|
||
|
array[ "normal" ] = ::enemy_alert_level_normal;
|
||
|
|
||
|
foreach ( key, function in array )
|
||
|
self ai_create_behavior_function( "threat", key, function );
|
||
|
|
||
|
self._stealth.logic.alert_level.max_warnings = array.size - 3;// sub 2 for reset, normal, and attack
|
||
|
}
|
||
|
|
||
|
enemy_alert_level_change( type )
|
||
|
{
|
||
|
self notify( "_stealth_enemy_alert_level_change", type ); // calls ::enemy_alert_level_change_reponse but one frame later. Must be after enemy_Animation_Loop thread process... messy
|
||
|
|
||
|
if ( !isdefined( self._stealth.plugins.threat ) )
|
||
|
{
|
||
|
// from _stealth_visibility_enemy::enemy_alert_level_nothing()
|
||
|
self.goalradius = level.default_goalradius;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( issubstr( type, "warning" ) )
|
||
|
type = "warning";
|
||
|
|
||
|
enemy_set_alert_level( type );
|
||
|
|
||
|
self notify( "awareness_alert_level", type );
|
||
|
}
|
||
|
|
||
|
enemy_threat_anim_defaults()
|
||
|
{
|
||
|
array = [];
|
||
|
array[ "reset" ] = ::enemy_animation_nothing;
|
||
|
array[ "warning" ] = ::enemy_animation_nothing;
|
||
|
|
||
|
if ( self.type == "dog" )
|
||
|
array[ "attack" ] = ::dog_animation_generic;
|
||
|
else
|
||
|
array[ "attack" ] = ::enemy_animation_attack;
|
||
|
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
enemy_set_threat_anim_behavior( array )
|
||
|
{
|
||
|
def = enemy_threat_anim_defaults();
|
||
|
|
||
|
//set defautls for reset and attack if they're not there
|
||
|
if ( !isdefined( array[ "reset" ] ) )
|
||
|
array[ "reset" ] = def[ "reset" ];
|
||
|
if ( !isdefined( array[ "warning" ] ) )
|
||
|
array[ "warning" ] = def[ "warning" ];
|
||
|
if ( !isdefined( array[ "attack" ] ) )
|
||
|
array[ "attack" ] = def[ "attack" ];
|
||
|
|
||
|
foreach ( key, func in array )
|
||
|
self ai_create_behavior_function( "animation", key, func );
|
||
|
}
|
||
|
|
||
|
enemy_default_threat_anim_behavior()
|
||
|
{
|
||
|
array = enemy_threat_anim_defaults();
|
||
|
self enemy_set_threat_anim_behavior( array );
|
||
|
}
|