#using scripts\codescripts\struct; #using scripts\shared\array_shared; #using scripts\shared\colors_shared; #using scripts\shared\flag_shared; #using scripts\shared\flagsys_shared; #using scripts\shared\scene_shared; #using scripts\shared\util_shared; #using scripts\shared\vehicle_ai_shared; #using scripts\shared\ai\systems\init; #using scripts\shared\ai\systems\shared; #using scripts\shared\ai\archetype_utility; #using scripts\shared\ai\systems\weaponList; #using scripts\shared\ai\systems\ai_interface; #namespace ai; /@ "Name: set_ignoreme( )" "Summary: Sets an actor's .ignoreme value. If 'true', other entities will ignore him." "Module: AI" "CallOn: an actor" "Example: guy set_ignoreme( true );" "MandatoryArg: : Boolean" "SPMP: singleplayer" @/ function set_ignoreme( val ) { assert( IsSentient( self ), "Non ai tried to set ignoreme" ); self.ignoreme = val; } /@ "Name: set_ignoreall( )" "Summary: Sets an actor's .ignoreall value" "Module: AI" "CallOn: an actor" "Example: guy set_ignoreall( true );" "MandatoryArg: : Boolean" "SPMP: singleplayer" @/ function set_ignoreall( val ) { assert( isSentient( self ), "Non ai tried to set ignoraell" ); self.ignoreall = val; } /@ "Name: set_pacifist( )" "Summary: Sets an actor's .pacifist value. If 'true', he'll only fire back if fired upon first." "Module: AI" "CallOn: an actor" "Example: guy set_pacifist( true );" "MandatoryArg: : Boolean" "SPMP: singleplayer" @/ function set_pacifist( val ) { assert( IsSentient( self ), "Non ai tried to set pacifist" ); self.pacifist = val; } /@ "Name: disable_pain()" "Summary: Disables pain on the AI" "Module: Utility" "CallOn: An ai" "Example: level.zakhaev disable_pain();" "SPMP: singleplayer" @/ function disable_pain() { assert( isalive( self ), "Tried to disable pain on a non ai" ); self.allowPain = false; } /@ "Name: enable_pain()" "Summary: Enables pain on the AI" "Module: Utility" "CallOn: An ai" "Example: level.zakhaev enable_pain();" "SPMP: singleplayer" @/ function enable_pain() { assert( isalive( self ), "Tried to enable pain on a non ai" ); self.allowPain = true; } /@ "Name: gun_remove()" "Summary: Removed the gun from the given AI. Often used for scripted sequences where you dont want the AI to carry a weapon." "Module: AI" "CallOn: An AI" "Example: level.price gun_remove();" "SPMP: singleplayer" @/ function gun_remove() { self shared::placeWeaponOn( self.weapon, "none" ); self.gun_removed = true; } /@ "Name: gun_switchto()" "Summary: Switches the given AI's gun to the one specified." "Module: AI" "CallOn: An AI" "MandatoryArg: : The weapontype name you want the AI to switch to." "MandatoryArg: : Which hand to put the weapon in." "Example: level.zeitzev gun_switchto( GetWeapon( "ppsh" ), "right" );" "SPMP: singleplayer" @/ function gun_switchto( weapon, whichHand ) { self shared::placeWeaponOn( weapon, whichHand ); } /@ "Name: gun_recall()" "Summary: Give the AI his gun back." "Module: AI" "CallOn: An AI" "Example: level.price gun_recall();" "SPMP: singleplayer" @/ function gun_recall() { self shared::placeWeaponOn( self.primaryweapon, "right" ); self.gun_removed = undefined; } /@ "Name: set_behavior_attribute()" "Summary: Call on an AI to change a behavior interface attribute." + "Available attributes are archetype specific and located in the archetype's interface gsc." + "For example, robot attributes are in archetype_robot_interface.gsc" "Module: AI" "CallOn: an actor" "MandatoryArg: : The interface attribute to modify." "MandatoryArg: : Value to set." "Example: guy set_behavior_attribute( "sprint", true );" @/ function set_behavior_attribute( attribute, value ) { if( ( SessionModeIsCampaignZombiesGame() ) ) { if( has_behavior_attribute( attribute ) ) SetAiAttribute( self, attribute, value ); } else { SetAiAttribute( self, attribute, value ); } } /@ "Name: get_behavior_attribute()" "Summary: Call on an AI to return the current value of a behavior interface attribute." + "Available attributes are archetype specific and located in the archetype's interface gsc." + "For example, robot attributes are in archetype_robot_interface.gsc" "Module: AI" "CallOn: an actor" "MandatoryArg: : The interface attribute to retrieve." "Example: guy get_behavior_attribute( "sprint" );" @/ function get_behavior_attribute( attribute ) { return GetAiAttribute( self, attribute ); } /@ "Name: has_behavior_attribute()" "Summary: Call on an AI to return whether the actor has a particular attribute defined." "Module: AI" "CallOn: an actor" "MandatoryArg: : The interface attribute to retrieve." "Example: guy has_behavior_attribute( "sprint" );" @/ function has_behavior_attribute( attribute ) { return HasAiAttribute( self, attribute ); } /@ "Name: is_dead_sentient()" "Summary: Checks to see if the AI is not defined, not sentient, and dead" "CallOn: AI" "Example: if ( guy ai::is_dead_sentient() )" @/ function is_dead_sentient() { if ( IsSentient( self ) && !IsAlive( self ) ) { return true; } else { return false; } } /@ "Name: waittill_dead( , , )" "Summary: Waits until all the AI in array < guys > are dead." "Module: AI" "CallOn: " "MandatoryArg: : Array of actors to wait until dead" "OptionalArg: : Number of seconds before this function times out and continues" "Example: waittill_dead( GetAITeamArray( "axis" ) );" "SPMP: singleplayer" @/ function waittill_dead( guys, num, timeoutLength ) { // verify the living - ness of the ai allAlive = true; for( i = 0;i < guys.size;i++ ) { if( isalive( guys[ i ] ) ) { continue; } allAlive = false; break; } assert( allAlive, "Waittill_Dead was called with dead or removed AI in the array, meaning it will never pass." ); if( !allAlive ) { newArray = []; for( i = 0;i < guys.size;i++ ) { if( isalive( guys[ i ] ) ) { newArray[ newArray.size ] = guys[ i ]; } } guys = newArray; } ent = SpawnStruct(); if( isdefined( timeoutLength ) ) { ent endon( "thread_timed_out" ); ent thread waittill_dead_timeout( timeoutLength ); } ent.count = guys.size; if( isdefined( num ) && num < ent.count ) { ent.count = num; } array::thread_all( guys,&waittill_dead_thread, ent ); while( ent.count > 0 ) { ent waittill( "waittill_dead guy died" ); } } /@ "Name: waittill_dead_or_dying( , , )" "Summary: Similar to waittill_dead(). Waits until all the AI in array < guys > are dead OR dying (long deaths)." "Module: AI" "CallOn: " "MandatoryArg: : Array of actors to wait until dead or dying" "OptionalArg: : Number of guys that must die or be dying for this function to continue" "OptionalArg: : Number of seconds before this function times out and continues" "Example: waittill_dead_or_dying( GetAITeamArray( "axis" ) );" "SPMP: singleplayer" @/ function waittill_dead_or_dying( guys, num, timeoutLength ) { // verify the living - ness and healthy - ness of the ai newArray = []; for( i = 0;i < guys.size;i++ ) { if( isalive( guys[ i ] ) && !guys[ i ].ignoreForFixedNodeSafeCheck ) { newArray[ newArray.size ] = guys[ i ]; } } guys = newArray; ent = spawnStruct(); if( isdefined( timeoutLength ) ) { ent endon( "thread_timed_out" ); ent thread waittill_dead_timeout( timeoutLength ); } ent.count = guys.size; // optional override on count if( isdefined( num ) && num < ent.count ) { ent.count = num; } array::thread_all( guys,&waittill_dead_or_dying_thread, ent ); while( ent.count > 0 ) { ent waittill( "waittill_dead_guy_dead_or_dying" ); } } function private waittill_dead_thread( ent ) { self waittill( "death" ); ent.count-- ; ent notify( "waittill_dead guy died" ); } function waittill_dead_or_dying_thread( ent ) { self util::waittill_either( "death", "pain_death" ); ent.count-- ; ent notify( "waittill_dead_guy_dead_or_dying" ); } function waittill_dead_timeout( timeoutLength ) { wait( timeoutLength ); self notify( "thread_timed_out" ); } //internal function called by shoot_at_target. This will ensure that we start computing the duration of shoot_at_target only after the first shot function private wait_for_shoot() { self endon( "stop_shoot_at_target" ); if( IsVehicle( self ) ) { self waittill( "weapon_fired" ); } else { self waittill("shoot"); } self.start_duration_comp = true; } /@ "Name: shoot_at_target(mode, target, tag, duration)" "Summary: Force AI to aim and shoot at given target" "Module: AI" "CallOn: An AI" "MandatoryArg: The mode of firing. The three modes are 'normal', 'shoot_until_target_dead', 'kill_within_time'" "MandatoryArg: The target entity to shoot at" "OptionalArg: The tag of the entity to shoot at" "OptionalArg: The duraton of firing. Leave undefined for one shot only." "OptionalArg: If defined, sets the health of the target." "OptionalArg: If true, will not wait before calculating the duration." "Example: ai_friendly shoot_at_target("normal", enemy, undefined, 5); This fires at the enemy for 5 seconds." + "ai_friendly shoot_at_target("shoot_until_target_dead", enemy, "j_head"); This fires at the enemy till he dies. Also, the tag is set to aim for the head." + "ai_friendly shoot_at_target("kill_within_time", enemy, undefined, 3); This ensures that the ai will kill the target within 3 seconds." "SPMP: singleplayer" @/ function shoot_at_target( mode, target, tag, duration, setHealth, ignoreFirstShotWait ) { self endon("death"); self endon("stop_shoot_at_target"); Assert( IsDefined( target ), "shoot_at_target was passed an undefined target" ); Assert( IsDefined( mode ), "Undefined mode. A mode must be passed to shoot_at_target" ); mode_flag = ( mode === "normal" ) || ( mode === "shoot_until_target_dead" ) || ( mode === "kill_within_time" ); Assert( mode_flag, "Unsupported mode. 'Mode must be normal', 'shoot_until_target_dead' or 'kill_within_time'" ); if( isdefined( duration ) ) { Assert( duration >= 0, "Duration must be a zero or a positive quantity" ); } else { duration = 0; } if ( isdefined(setHealth) && isdefined(target) ) { target.health = setHealth; } if ( !isdefined( target ) || ( ( ( mode === "shoot_until_target_dead" ) ) && ( target.health <= 0 ) ) ) { return; // undefined target or target already dead } if( IsDefined(tag) && tag != "" ) { self SetEntityTarget( target, 1, tag ); } else { self SetEntityTarget( target, 1 ); } // make sure the AI shoots it, even if not visible self.cansee_override = true; self.start_duration_comp = false; switch( mode ) { case "normal": break; case "shoot_until_target_dead": duration = -1; break; case "kill_within_time": target DamageMode( "next_shot_kills" ); break; } if( IsVehicle( self ) ) { // Reset vehicle firing cool downs so we can fire right away self vehicle_ai::ClearAllCooldowns(); } // wait for first shot if(ignoreFirstShotWait === true) { self.start_duration_comp = true; } else { self thread wait_for_shoot(); } // fire for duration if( isdefined(duration) && isdefined( target ) && target.health > 0 ) { if( duration >= 0) { elapsed = 0; while( isdefined(target) && target.health > 0 && elapsed <= duration ) { elapsed += 0.05; if( !( isdefined( self.start_duration_comp ) && self.start_duration_comp ) ) elapsed = 0; {wait(.05);}; } if( isdefined(target) && mode == "kill_within_time" ) { self.perfectaim = true; self.aim_set_by_shoot_at_target = true; target waittill( "death" ); } } else if (duration == -1) { target waittill( "death" ); } } stop_shoot_at_target(); } /@ "Name: stop_shoot_at_target()" "Summary: Give the AI his gun back." "Module: AI" "CallOn: An AI" "Example: level.price stop_shoot_at_target();" "SPMP: singleplayer" @/ function stop_shoot_at_target() { self ClearEntityTarget(); if( ( isdefined( self.aim_set_by_shoot_at_target ) && self.aim_set_by_shoot_at_target ) ) { self.perfectaim = false; self.aim_set_by_shoot_at_target = false; } self.cansee_override = false; self notify("stop_shoot_at_target"); } function wait_until_done_speaking() { self endon( "death" ); while ( self.isSpeaking ) { {wait(.05);}; } } /@ "Name: set_goal( , [key = "targetname"], [b_force] )" "Summary: Set's the ai's goal based on KVP." "Module: AI" "CallOn: An AI" "MandatoryArg: : Either a vector, or a KVP value of a node, ent, struct to set the goal to" "OptionalArg: [key]: If the value is a KVP, use this key" "OptionalArg: [b_force]: force goal param passed to SetGoal()" "Example: guy ai::set_goal( value, key );" "SPMP: singleplayer" @/ function set_goal( value, key = "targetname", b_force = false ) { goal = GetNode( value, key ); if ( isdefined( goal ) ) { self SetGoal( goal, b_force ); } else { goal = GetEnt( value, key ); if ( isdefined( goal ) ) { self SetGoal( goal, b_force ); } else { goal = struct::get( value, key ); if ( isdefined( goal ) ) { self SetGoal( goal.origin, b_force ); } } } return goal; } /@ "Name: force_goal(, [n_radius], [b_shoot], [str_end_on], [b_keep_colors])" "Summary: Force an AI to go to goal by temporarily disabling AI features." "Module: AI" "MandatoryArg: /// - position, node, entity or volume to go to" "OptionalArg: [n_radius] : Option goal radius. AI will be considered at goal within this distance from goal." "OptionalArg: [b_shoot] : Enable/Disable shoot while moving (defaults to true)." "OptionalArg: [str_end_on] : The endon string that will set this AI back to normal (defaults to 'goal')." "OptionalArg: [b_keep_colors] : If colors will be enabled again after force goal (defaults to 'false')." "Example: self thread ai::force_goal( node, 20, true );" "SPMP: singleplayer" @/ function force_goal( goto, n_radius, b_shoot = true, str_end_on, b_keep_colors = false, b_should_sprint = false ) { self endon( "death" ); s_tracker = SpawnStruct(); self thread _force_goal( s_tracker, goto, n_radius, b_shoot, str_end_on, b_keep_colors, b_should_sprint ); s_tracker waittill( "done" ); } function _force_goal( s_tracker, goto, n_radius, b_shoot = true, str_end_on, b_keep_colors = false, b_should_sprint = false ) { self endon( "death" ); self notify( "new_force_goal" ); flagsys::wait_till_clear( "force_goal" ); // let any previous force goal thread cleanup first flagsys::set( "force_goal" ); goalradius = self.goalradius; if ( IsDefined( n_radius ) ) { Assert( ( IsFloat( n_radius ) || IsInt( n_radius ) ), "ai_shared::force_goal expects n_radius to be an int or a float." ); self.goalradius = n_radius; } color_enabled = false; if ( !b_keep_colors ) { if ( IsDefined( colors::get_force_color() ) ) { color_enabled = true; self colors::disable(); } } allowpain = self.allowpain; ignoreall = self.ignoreall; ignoreme = self.ignoreme; ignoresuppression = self.ignoresuppression; grenadeawareness = self.grenadeawareness; if ( !b_shoot ) { self set_ignoreall( true ); } if( b_should_sprint ) { self set_behavior_attribute( "sprint", true ); } self.ignoresuppression = true; self.grenadeawareness = 0; self set_ignoreme( true ); self disable_pain(); self PushPlayer( true ); if ( isdefined( goto ) ) { if ( IsDefined( n_radius ) ) { Assert( ( IsFloat( n_radius ) || IsInt( n_radius ) ), "ai_shared::force_goal expects n_radius to be an int or a float." ); self SetGoal( goto ); } else { self SetGoal( goto, true ); } } self util::waittill_any( "goal", "new_force_goal", str_end_on ); if ( color_enabled ) { colors::enable(); } self PushPlayer( false ); // assume we want this off once we have reached goal self ClearForcedGoal(); self.goalradius = goalradius; self set_ignoreall( ignoreall ); self set_ignoreme( ignoreme ); if ( allowpain ) { self enable_pain(); } self set_behavior_attribute( "sprint", false ); self.ignoresuppression = ignoresuppression; self.grenadeawareness = grenadeawareness; flagsys::clear( "force_goal" ); s_tracker notify( "done" ); } /@ "Name: stopPainWaitInterval()" "Summary: Removes pain block" "Module: AI" "Example: self thread ai::allowPainWithMinimumInterval( 5000 );" "SPMP: singleplayer" @/ function stopPainWaitInterval() { self notify("painWaitIntervalRemove"); } function private _allowPainRestore() { self endon("death"); self util::waittill_any("painWaitIntervalRemove","painWaitInterval"); self.allowPain = true; } /@ "Name: painWaitInterval()" "Summary: Block pain reaction for mSec. AI still takes damage but doesn't play pain animations" "Module: AI" "MandatoryArg: - number of milliseconds to block pain" "Example: self thread ai::allowPainWithMinimumInterval( 5000 );" "SPMP: singleplayer" @/ function painWaitInterval(mSec) { self endon("death"); self notify("painWaitInterval"); self endon("painWaitInterval"); self endon("painWaitIntervalRemove"); self thread _allowPainRestore(); if(!isDefined(mSec) || mSec < 20 ) mSec = 20; while(isAlive(self)) { self waittill("pain"); self.allowPain = false; wait (mSec/1000); self.allowPain = true; } } /@ "Name: patrol( start_path_node )" "Summary: Sets a human to patrol along the points of a path, stopping to play scenes or wait for a short period of time at each path node. Behavior ends when actor gets a target, or hits the end of the path. Be sure to set Alert on Spawn to false on the actor's spawner" "Summary: Set self.should_stop_patrolling to true to end the patrol behavior early." "Module: patrol_human" "Mandatory Aarg: start_path_node - the first path node for the path the actor should follow" "Example: guy patrol_human::patrol( begin_node)" "SPMP: SP" @/ function patrol( start_path_node ) { self endon( "death" ); self endon( "stop_patrolling" ); assert( isDefined( start_path_node ), self.targetname + " has not been assigned a valid node or scene scriptbundle for his patrol path to start on" ); if ( start_path_node.type == "BAD NODE" ) { /# errorMsg = "ERROR: patrol node '" + start_path_node.targetname + "' (" + int(start_path_node.origin[0]) + "," + int(start_path_node.origin[1]) + "," + int(start_path_node.origin[2]) + ") is 'BAD NODE'"; IPrintLn( errorMsg ); LogPrint( errorMsg ); #/ return; } assert( start_path_node.type == "Path" || isdefined( start_path_node.scriptbundlename ), "The starting point '" + start_path_node.targetname + "' for a patrol path is not a path node or scene script bundle" ); self notify( "go_to_spawner_target"); self.target = undefined; self.old_goal_radius = self.goalradius; self thread end_patrol_on_enemy_targetting(); self.currentgoal = start_path_node; self.patroller = true; While( 1 ) { if( isDefined( self.currentgoal.type) && self.currentgoal.type == "Path" )//handle case where current goal is a path node { if ( self ai::has_behavior_attribute( "patrol" ) ) self ai::set_behavior_attribute( "patrol", true ); self setgoal( self.currentgoal, true ); self waittill( "goal" ); if( isDefined( self.currentgoal.script_notify )) { self notify ( self.currentgoal.script_notify ); level notify (self.currentgoal.script_notify ); } if (isDefined( self.currentgoal.script_flag_set )) { flag = self.currentgoal.script_flag_set; if ( !isdefined( level.flag[ flag ] ) ) { level flag::init( flag ); } level flag::set( flag ); } if( !isDefined( self.currentgoal.script_wait_min )) { self.currentgoal.script_wait_min = 0; } if( !isDefined( self.currentgoal.script_wait_max )) { self.currentgoal.script_wait_max = 0; } assert( self.currentgoal.script_wait_min <= self.currentgoal.script_wait_max , "Patrol max wait is less than the min wait on " + self.currentgoal.targetname ); if( !isdefined( self.currentgoal.scriptbundlename) ) { wait_variability = self.currentgoal.script_wait_max - self.currentgoal.script_wait_min; wait_time = self.currentgoal.script_wait_min + randomfloat( wait_variability ); self notify( "patrol_goal", self.currentgoal ); wait wait_time; } else { self scene::play( self.currentgoal.scriptbundlename , self ); } } else //handle case where the current goal is a scene scriptbundle { self.currentgoal scene::play( self ); } //once current goal handling is done, select a new goal from the targets of the current one self patrol_next_node(); } } function patrol_next_node( ) { //get current goal targets in an array if we have a next target target_nodes = []; target_scenes = []; if ( isdefined( self.currentgoal.target ) ) { target_nodes = getnodearray( self.currentgoal.target, "targetname" ); target_scenes = struct::get_array( self.currentgoal.target , "targetname" ); } if( target_nodes.size == 0 && target_scenes.size == 0 ) { self end_and_clean_patrol_behaviors(); } else { if( target_nodes.size != 0) { self.currentgoal = array::random( target_nodes ); } else { self.currentgoal = array::random( target_scenes ); } } } function end_patrol_on_enemy_targetting() { self endon( "death" ); self endon( "stop_patrolling" ); While(1) { if( isdefined( self.enemy) || self.should_stop_patrolling === true ) { self end_and_clean_patrol_behaviors(); } wait 0.1; } } function end_and_clean_patrol_behaviors() { if ( isdefined( self.currentgoal ) && isdefined( self.currentgoal.scriptbundlename ) && isDefined( self._o_scene ) ) self._o_scene cscene::stop(); if ( self ai::has_behavior_attribute( "patrol" ) ) self ai::set_behavior_attribute( "patrol", false ); if ( isDefined( self.old_goal_radius ) ) self.goalradius = self.old_goal_radius; self ClearForcedGoal(); self notify( "stop_patrolling" ); self.patroller = undefined; } /@ "Name: bloody_death([n_delay], [hit_loc])" "Summary: Kills the AI by damaging the given hit_loc. If not, then a random hit_loc is chosen." "Module: AI" "OptionalArg: : delay before death." "OptionalArg: : hit location to do the damage at." "Example: self ai::bloody_death( 3, "neck" );" "SPMP: singleplayer" @/ function bloody_death( n_delay, hit_loc ) { self endon( "death" ); if( !IsDefined( self ) ) { return; } assert( IsActor( self ) ); assert( IsAlive( self ) ); if( ( isdefined( self.__bloody_death ) && self.__bloody_death ) ) return; self.__bloody_death = true; if( IsDefined( n_delay ) ) { wait n_delay; } if( !IsDefined( self ) || !IsAlive( self ) ) { return; } if( IsDefined( hit_loc ) ) { assert( IsInArray( array( "helmet", "head", "neck", "torso_upper", "torso_mid", "torso_lower", "right_arm_upper", "left_arm_upper", "right_arm_lower", "left_arm_lower", "right_hand", "left_hand", "right_leg_upper", "left_leg_upper", "right_leg_lower", "left_leg_lower", "right_foot", "left_foot", "gun", "riotshield" ), hit_loc ), "bloody_death() : Invalid hit location. Check the list of allowed hit location in blackboard.gsh, HITLOC_ALL." ); } else { hit_loc = array::random( array( "helmet", "head", "neck", "torso_upper", "torso_mid", "torso_lower", "right_arm_upper", "left_arm_upper", "right_arm_lower", "left_arm_lower", "right_hand", "left_hand", "right_leg_upper", "left_leg_upper", "right_leg_lower", "left_leg_lower", "right_foot", "left_foot", "gun", "riotshield" ) ); } self DoDamage( self.health + 100, self.origin, undefined, undefined, hit_loc ); } /@ "Name: shouldRegisterClientFieldForArchetype([archetype])" "Summary: returns true if clientfields should be registered for a given archetype. Need level.clientFieldAICheck set in level for this function to work." "Module: AI" "Mandatory Arg: archetype" "Example: shouldRegisterClientFieldForArchetype(ARCHETYPE_WARLORD);" "SPMP: both" @/ function shouldRegisterClientFieldForArchetype( archetype ) { if( ( isdefined( level.clientFieldAICheck ) && level.clientFieldAICheck ) && !IsArchetypeLoaded( archetype ) ) return false; return true; }