/* ******************************************************************************************** ai_darkmod_base.script Defines the basic characteristics of all AI in The Dark Mod. It sets up a VERY basic sentry that can be modified as necessary. Copyright (c) The Dark Mod, 2005 ******************************************************************************************** */ /* !*!*!*!*!*!*!*!*!*!*!*!*!*!*! Notes: Torso_QuickMelee and Torso_LongMelee are exactly the same at this point due to the lack of animation. combat_quickmelee and combat_longmelee basically only differ in damage due to the lack of animation. The monster_base function "wait_for_enemy()" will have to be rewritten since the enemies in darkmod don't react to the same triggers. Ishtvan: See the comments in the ai def file for explanation of the other variables that you can set from the def. !*!*!*!*!*!*!*!*!*!*!*!*!*!*! */ #ifndef __AI_DARKMOD_BASE__ #define __AI_DARKMOD_BASE__ #include "script/darkmod_ai_constants.script" #include "script/ai_base.script" #include "script/darkmod_stim_response.script" #include "script/tdm_lights.script" #ifndef ATTACK_QUICK_MELEE #define ATTACK_QUICK_MELEE 101 #endif #ifndef ATTACK_LONG_MELEE #define ATTACK_LONG_MELEE 102 #endif // Minimum distance to run during a search #define MINIMUM_SEARCH_RUN_DISTANCE 100.0 #define MINIMUM_SEARCH_LONG_RUN_DISTANCE 500.0 // Considered cause radius around a tactile event #define TACTILE_ALERT_RADIUS 10.0 #define TACTILE_SEARCH_VOLUME '40.0 40.0 40.0' // Considered cause radius around a visual event #define VISUAL_ALERT_RADIUS 25.0 #define VISUAL_SEARCH_VOLUME '100.0 100.0 100.0' // Considered cause radius around an audio event #define AUDIO_ALERT_RADIUS 50.0 #define AUDIO_SEARCH_VOLUME '200.0 200.0 200.0' // Area searched around last sighting after losing an enemy #define LOST_ENEMY_ALERT_RADIUS 200.0 #define LOST_ENEMY_SEARCH_VOLUME '200.0 200.0 200.0' // The minimum number of seconds to leave between "stimulus barks". Keep in sync with SDK please! #define MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS 15 // The probability on a scale of 0 to 1, of turning the head randomly to look around // in various situations. The check is done once per second #define SEARCHING_RANDOM_HEAD_TURN_CHANCE_PER_SECOND 0.20 // Keep IDLE_RANDOM_HEAD_TURN_CHANCE_PER_SECOND in sync with SDK please! #define IDLE_RANDOM_HEAD_TURN_CHANCE_PER_SECOND 0.10 // SZ: This is multiplied by the base idle chance of turning the head // if the AI settles into a non-0 AI_AlertNum agitated state. Keep in sync with SDK please! #define SLIGHTLY_AGITATED_HEAD_TURN_CHANCE_MULTIPLIER 2.0 // SZ: Minimum count evidence of intruders to turn on all lights encountered #define MIN_EVIDENCE_OF_INTRUDERS_TO_TURN_ON_ALL_LIGHTS 2 // SZ: Minimum count of evidence of intruders to communicate suspicion to others #define MIN_EVIDENCE_OF_INTRUDERS_TO_COMMUNICATE_SUSPICION 3 // ported // SZ: Maximum amount of time since last visual or audio contact with a friendly person to use // group stimulous barks, in seconds #define MAX_FRIEND_SIGHTING_SECONDS_FOR_ACCOMPANIED_ALERT_BARK 10.0 // SZ: Someone hearing a distress call won't bother to shout that it is coming to their assisitance unless // it is at least this far away. This is to simulate more natural human behaivior. #define MIN_DISTANCE_TO_ISSUER_TO_SHOUT_COMING_TO_ASSISTANCE 200.0 // AI communication types #define Greeting_MessageType 1 #define FriendlyJoke_MessageType 2 #define Insult_MessageType 3 #define RequestForHelp_MessageType 4 #define RequestForMissileHelp_MessageType 5 #define RequestForMeleeHelp_MessageType 6 #define RequestForLight_MessageType 7 #define DetectedSomethingSuspicious_MessageType 8 #define DetectedEnemy_MessageType 9 #define FollowOrder_MessageType 10 #define GuardLocationOrder_MessageType 11 #define GuardEntityOrder_MessageType 12 #define PatrolOrder_MessageType 13 #define SearchOrder_MessageType 14 #define AttackOrder_MessageType 15 #define ConveyWarning_EvidenceOfIntruders_MessageType 16 #define ConveyWarning_ItemsHaveBeenStolen_MessageType 17 #define ConveyWarning_EnemiesHaveBeenSeen_MessageType 18 // Stim radii for various communication styles #define YELL_STIM_RADIUS 400 #define TALK_STIM_RADIUS 200 //#define DEBUG_PRINT sys.println #define DEBUG_PRINT object ai_darkmod_base : ai { float taskQueue; // greebo: When the health of the AI is dropping below this // value, the AI flees (or takes another appropriate action). float health_critical; // D3 variables float run_distance; // dis1tance to trigger running when chasing enemy float walk_turn; // max turn amount allowed when walking boolean ambush; boolean ignoreEnemies; // used to disable enemy checks during attack_path boolean stay_on_attackpath;// used to disable enemy checks during attack_path boolean idle_sight_fov; float customBlendOut; entity current_path; entity next_path; boolean resurrect; boolean ignore_lostcombat; boolean blocked; boolean ignore_sight; // The last position the enemy was seen // greebo: Note: Currently this is filled in before fleeing only. vector m_LastEnemyPos; // set to 1 if the AI can draw a weapon before combat boolean m_DrawsWeapon; // set to 1 if the AI's weapon is drawn boolean m_WeaponDrawn; // set to 1 if the AI has a ranged weapon available boolean m_HasRangedWeapon; // alert thresholds float thresh_1; // aroused float thresh_2; // investigating float thresh_3; // agitated investigating float thresh_combat; // hunting // de-alert times float atime1; float atime2; float atime3; // wander times during search algorithm for given alertstate float wtime1; float wtime2; float wtime3; // fractional amount to increase the wander each time // the AI wanders and comes back to m_alertPos float winc1; float winc2; float winc3; // actual wander time is wandertime + rand(randomness)*wandertime float wrand1; float wrand2; float wrand3; // repeat rate of chatter while idle/patrolling float chatterRepeat; // position to return to when AI goes back to idle state and // was not originally patrolling. vector idlePos; // angles to return to when returning to idle state (NYI) vector idleAngles; // The last time a check for randomly turning the head was done float m_lastRandomHeadTurnCheckTime; // position of alert causing stimulus vector m_alertPos; // radius of alert causing stimulus (depends on the type and distance) float m_alertRadius; // A string that indicates the last type of alert // v is visual, t is tactile, s is sound string m_alertType; // A search area vector that is m_alertRadius on each side vector m_alertSearchVolume; // An area within the search volume that is to be ignored. It is used for expanding // radius searches that don't re-search the inner points. vector m_alertSearchExclusionVolume; // The time of the last visual stim response bark float m_timeOfLastVisualStimBark; // Entity that is being interacted with in a non-combat fashion, such as a device or torch entity m_interactEnt; // Cory's combat vars float parry_rate; // rate of parries in percent float melee_pref; // preference to melee attacks float range_pref; // preference to ranged attacks /*! * TODO * @author Sebultura */ entity head; /*! * TODO * @author Sebultura */ boolean can_talk; /*! * TODO * @author Sebultura */ boolean no_cower; /*! * TODO * @author Sebultura */ boolean no_cower_saved; /*! * TODO * @author Sebultura */ boolean skipOutOfPath; /*! * TODO * @author Sebultura */ character listening_to_character; /*! * TODO * @author Sebultura */ character next_listener; /*! * This is the time at which the current hiding spot * list search started * @author SophisticatedZombie */ float currentHidingSpotListSearchStartTime; /*! * This is the maximum duration of the current * hiding spot list search * @author SophisticatedZombie */ float currentHidingSpotListSearchMaxDuration; /*! * This is the number of hiding spots fromt he current * hiding spot list which have been searched * @author SophisticatedZombie */ float numPossibleHidingSpotsSearched; /*! * This is true if the original alert position is to be searched */ boolean b_stimulusLocationItselfShouldBeSearched; /*! * This is the time at which the current hiding spot * search started * @author SophisticatedZombie */ float currentSpotSearchStartTime; /*! * This is the maximum duratoin of the current * hiding spot search * @author SophisticatedZombie */ float currentSpotSearchMaxDuration; /*! * This flag indicates if a hiding spot test was started * @author SophisticatedZombie */ boolean hidingSpotTestStarted; /*! * This flag idnicates if a hiding spot was chosen */ boolean hidingSpotSearchDone; /*! * This flag indicates if the search is due to a communication */ boolean b_searchingDueToCommunication; /*! * This holds the hiding spot which is the current hiding spot * search target. * @author SophisticatedZombie */ vector chosenHidingSpot; float firstChosenHidingSpotIndex; float currentChosenHidingSpotIndex; /*! * These hold the current spot search target, regardless of whether * or not it is a hiding spot search or some other sort of spot search */ vector currentSearchSpot; /*! * This holds the name of the state that a device interaction state should return to * when the device interaction is done */ string deviceInteractionReturnState; /*! * This indicates the last time the "looking around animation" was played */ float timeOfLastAnimation_LookAround; // methods // Standard D3 methods void init(); void task_Begin(); void monster_begin(); void archvile_minion(); boolean can_resurrect(); void monster_resurrect( entity enemy ); void wait_for_enemy(); void task_Killed(); void task_Dead(); void task_KnockedOut(); void task_Unconscious(); void wake_on_enemy(); void wake_on_trigger(); void walk_on_trigger(); void wake_on_attackcone(); void sight_enemy(); void enemy_dead(); void task_LostCombat(); void task_Spawner(); void monster_base_lost_combat(); boolean check_blocked(); boolean combat_chase(); void combat_turret_node( entity node ); void combat_attack_cone( entity node ); void combat_ainode( entity node ); void combat_lost(); void combat_wander(); void playCustomCycle( string animname, float blendTime ); void playCustomAnim( string animname, float blendIn, float blendOut ); void checkNodeAnim( entity node, string prefix, string anim ); void trigger_wakeup_targets(); boolean checkForEnemy( float use_fov ); void task_FollowAlternatePath(); void follow_alternate_path1(); void follow_alternate_path2(); void follow_alternate_path3(); // Conversation methods void Anim_Disable(); void Head_Idle(); float playHeadAnim( string animname, float blend_frames ); void endHeadAnim( float blend_out ); void finishPathCommand(); void cancelPathCommand(); void check_cower(); void break_out_of_conversation(); void skip_conversation(); /** * @author SophisticatedZombie: * * This method is needed because getState returns a fully class qualified name * including "::" operators but setState does not accept such names. If you want * to store the name for later calling setState, call this function instead, which * removes the class qualifier. */ string getCurrentStateCallableName(); // torso animation void Torso_Idle(); void Torso_Pain(); void Torso_Death(); void Torso_KO(); void Torso_Sight(); void Torso_Blinded(); void Torso_CustomCycle(); void Torso_CustomAnim(); void Torso_QuickMelee(); void Torso_LongMelee(); // leg animation void Legs_Idle(); void Legs_Walk(); void Legs_Run(); void Legs_Death(); void Legs_KO(); void Legs_Blinded(); void Patrol( entity pathnode ); /** * Overloaded path_ functions for patrolling and checking alerts **/ void path_corner(); void path_anim(); void path_cycleanim(); void path_turn(); void path_wait(); void path_waitfortrigger(); void path_hide(); void path_show(); void path_attack(); // TODO: Add these and all associated member vars and functions // Seb void path_conversation_listen(); void path_conversation(); /* void path_conversation_listen(); void path_conversation(); void path_headanim(); void path_talk(); void path_talk_triggered(); void path_talk_primary(); void path_talk_secondary(); void path_lookat(); */ /** * Returns true if the AI has been alerted above alertthresh_1 this frame, * resets the alert back down to 0 if it was alerted but it did not go above * thresh1. **/ boolean checkAlerted(); /* * Play a voice sound, returns the length of the sound in seconds. */ float bark(string name); /** * Play the patrol bark **/ void PatrolBark(); boolean m_stopPatrol; float m_patrolChatTimer; float m_patrolBarkRepeat; /** * greebo: Removes the task with the given name from the AI's queue. * * @returns: TRUE if the task was found in the queue, FALSE otherwise */ boolean removeTask(string taskName); // tasks void task_Begin(); void task_Idle(); void task_Combat(); void task_Flee(); void task_FleeDone(); // greebo: script event for Blinded task void task_Blinded(); // greebo: This is active during the investigation of a point where an enemy was detected. void task_Investigate_Enemy_Position(); /** * greebo: Use this to blind this entity. Pass the * originator for the occlusion check. * By passing skipVisibilityCheck == 0 the * occlusion test is omitted, so that the AI * can forcedly set to "blind". */ void blind(entity originator, float skipVisibilityCheck); /** * This state is where the AI decides what to do in reaction * to a stimulus. For an alert_1 stimulus level, it will * look at the stimulus location and maybe vocalize. For * an alert_2 or higher it will think about possible good hiding spots * near the stimulus. For a visual stimulus it will immediately * search the stimulus location first while thinking about where * to search after it is done with that point. * For combat alerts, it tries to initiate combat. */ void task_ReactingToStimulus(); /** * This method searches the spot specified by the currentSearchSpot * vector for up to the currentSpotSearchMaxDuration. */ void task_SearchingSpot(); /** * This stands around and waits for thinking about hiding spots * to complete (background hiding spot search is in progress and * we don't know what else to do but wait for it to finish. */ void task_WaitingForHidingSpotThinkingToComplete(); /** * This searches the current spot in the list by setting * parameters and jumping to task_SearchingSPot. */ void task_SearchingHidingSpotList(); /** * This moves to the next hiding spot returned from the list. */ void task_IteratingHidingSpotList(); /* * This is entered if the target is detectable but moves to * where the AI can't path to it. */ void task_TargetCannotBeReached(); /*! * This is entered if the target no longer knows where the * targets position is. */ void task_LostTrackOfEnemy(); /*! * This is entered if the AI is trying to light a torch */ void task_LightTorch(); /*! * This is entered if the AI is trying to turn on a switched light */ void task_TurnOnSwitchLight(); /*! * This is entered if the AI is trying to turn on a magical light */ void task_TurnOnMagicLight(); // attacks void DrawWeapon(); void SheathWeapon(); float check_attacks(); void do_attack( float attack_flags); void combat_parry(); void combat_quickmelee(); void combat_longmelee(); /** * greebo: Returns non-zero value if the AI state is critical * enough to flee. Usually this means that the current health * is below the health_critical threshold. */ float check_flee(); // searching and alert state helper functions /** * This method performs a visual scan and responds to any alerts * generated by it or other things up until this point for * this frame. * * If the alert level is driven to the combat threshold * then combat will be attempted * * Otherwise, either nothing will be done or the stimuli * source will be searched for, depending on the value * of b_searchNewStimuli * * @param b_searchNewStimuli If true, then a stimuli that * doesn't raise the alert level to combat standards but does * raise it to thresh_1 or higher will get a stimulus response * from the actor consisting of pausing and maybe searching. * */ void subFrameTask_canSwitchState_sensoryScan ( boolean b_searchNewStimuli ); /** * This method handles a random chance of turning the AIs head * (visual gaze movement) * * @param percentChance Gives a value from 0 to 1 indicating the * probability of turning the head * * @param minClockwiseYaw The minimum clockwise yaw angle to turn the head * from the current body facing * * @param maxClockwiseYaw The maximum clockwise yaw angle to turn the head * from the current body facing. * * @param minPitch The minimum pitch angle, with - being down, to turn the * head up and down from the current body facing. * * @param maxPitch The maximum pitch angle, with + being down, to turn the * head up and down from the current body facing. * * @param minDurationSeconds The minimum time the head stays turned in seconds * * @param maxDurationSecodns The maximum time the head stays turned in seconds * */ void subFrameTask_randomHeadTurn // ported to IdleSensoryTask::PerformRandomHeadCheck() ( float percentChance, float minClockwiseYaw, float maxClockwiseYaw, float minPitch, float maxPitch, float minDurationSeconds, float maxDurationSeconds ); /** * This method tests the alert level timer and * transitions the alert level downward * (using subFrameTask_reflectNewAlertLevel * along the way) if the timer has expired. * */ void subFrameTask_testAlertStateTimer(); // ported to BasicMind::TestAlertStateTimer() /** * This method looks at the alert level and determines * the duration to set for the currentHidingSpotListSearchMaxDuration * variable that controls the length of an ensuing search. * * @return Returns the duration that was also set in the * currentHidingSpotListSearchMaxDuration varibale of this AI */ float subFrameTask_determineSearchDuration(); /** * This method tests to see if the target can be reached. * If it can't, then the AI chooses another action. * * This method often switches the AI to a new "task_" * state. * */ void subFrameTask_canSwitchState_initiateCombat(); /** * This method spawns a throwable projectile and tries to * throw it at the current enemy * */ void subFrameTask_throwRandomObject (); /** * This method calls out for an archer or other missile * capable friendly unit * */ void subFrameTask_callForMissileHelp(); /** * This method calls out that something suspicious has been * noticed * */ void subFrameTask_yellNoticedSomethingSuspicious(); /* * This method stands around waiting for the target to become reachable. * Stone-throwing is handled here, as is taking cover. * * This method never returns. It will always switch state eventually. * * @param firstTime If true, a bark will be played, a stone * will be thrown immediately, and the AI will seek cover. If false, * it simply stands around throwing stones every so often; it will * not bark or seek cover. * */ void subFrameTask_willSwitchState_handleUnreachableTarget(boolean firstTime); /** * This method handles a multi-frame action of wandering in a given location * * Syntax: multiFrameTask_wanderInLocation( initial location, maxDistanceAllowed, * total time for any wander, minimum wander time in a given direction, fraction of * wandertime that is randomly added in a given direction, alert threshold to return, * fractional increase in time per wander ) * * Starts by going to the position specified in the first argument, then begins * wandering for time wanderDuration + random( randomness* wanderDuration ) * * After the wander time expires, it returns back to the initial location * When it gets back to the initial location it wanders again. * * Each time it does this, the wander time increases by some fractional increase, * to represent a search of increasing radius. * * If at any time the AI alert is increased by the alert threshold, the wander exits * Wander will also exit when the maxTaskTime (in seconds) expires. * **/ void multiFrameTask_wanderInLocation ( vector centerPoint, float maxDistanceFromCenterPoint, float maxTaskTime, float wanderDuration, float randomMultiplier, float alertThresh, float wanderDurationIncrement ); /** * setTarget should only be called when the combat threshold has been * reached and the AI needs to try to attack something. * The only alert that does not set a target is sound. * In the case of sound, the AI will keep running at the source of the noise * and executing the running search until it sees or bumps into the player * * * @return true if a target was found * @return false if no target found * * SophisticatedZombie Note: Note that this method and setTarget are mutually exclusive * as they clear flags that the other needs. * **/ boolean setTarget(); /** * Checks if the alert was Visual, Audible or Tactile * Sets the position of the place the AI should start searching * according to different rules for each. * * Clears the bool variables after it checks, so that * they may be set again proplery by the hardcode. **/ void setAlertPos(); /*! * DEPRECATED: DO NOT USE * * This method should be called on a background thread. * It runs a hiding spot test and randomly picks a hiding spot. * * @author SophisticatedZombie */ void chooseRandomHidingSpot(vector hideFromPoint, vector searchPoint, vector searchRadius); /*! * This method is used by subFrameTask_startNewHidingSpotSearch and subFrameTask_continueExistingHidingSpotSearch * When the search completes, this picks the first spot from the search to be used. * * @author SophisticatedZombie */ void subFrameTask_chooseFirstSpotToSearch(); /*! * This method is used to start a new hiding spot search. Any existing search in progress is replaced. * * @author SophisticatedZombie * * @param hideFromPoint The "eye" position from which hiding spots are being found * * @param searchPoint The point around which the search is centered * * @param searchRadius The radii in each dimension around the search point in which spots should be found * */ void subFrameTask_startNewHidingSpotSearch(vector hideFromPoint, vector searchPoint, vector searchVolume, vector searchExclusionVolume); /*! * This method is used to continue a hiding spot search in progress. */ void subFrameTask_continueInProgressHidingSpotSearch(); /*! * This method is used to choose the next hiding spot from the last search. * * @sideEffect If no more hiding spots are available, the variable hidingSpotSearchDone * will be FALSE when this method returns. Otherwise it will be true. * * @sideEffect Sets the chosenHidingSpot vector to the selected hiding spot. * This is only valid if the hidingSpotSearchDone member is true. * * @author SophisticatedZombie */ void chooseNextHidingSpot(); /*! * Communications event responses */ void response_communication (entity Result, float ThreadNum, float MessageType, entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_Greeting ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_FriendlyJoke ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_Insult ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_RequestForHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_RequestForMissileHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_RequestForMeleeHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_RequestForLight ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_DetectedSomethingSuspicious ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_DetectedEnemy ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_FollowOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_GuardLocationOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_GuardEntityOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_GuardEntityOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_PatrolOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_SearchOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_AttackOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_conveyWarningEvidenceOfIntruders (entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_conveyWarningItemsStolen (entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); void responseTo_conveyWarningEnemiesSeen (entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ); /*! * Visual contact stimulus responses */ void response_visualStim(entity stimSource, float ThreadNum); /*! * These functions are called by response_visualStim based on the AIUse and other characteristics * of the entity that is the source of the stim. * */ void response_visualStim_Weapon(entity stimSource, float ThreadNum); void response_visualStim_Person(entity stimSource, float ThreadNum); void response_visualStim_Blood(entity stimSource, float ThreadNum); void response_visualStim_OpenDoor(entity stimSource, float ThreadNum); void response_visualStim_LightSource(entity stimSource, float ThreadNum); void response_visualStim_MissingItem(entity stimSource, float ThreadNum); /*! * This function can be called when a person is spotted and they are * dead */ void response_visualStim_DeadPerson(entity stimSource, float ThreadNum); /*! * This function can be called when a person is spotted and they are * unconscious */ void response_visualStim_UnconsciousPerson(entity stimSource, float ThreadNum); /*! * These functions are called by response_visualStim_Person based on the type of person. * * @return true if the stimulus should be ingored from now on * * @return false if the stimulus should not be ignored from now on */ boolean response_visualStim_Person_Enemy(entity stimSource, float ThreadNum); boolean response_visualStim_Person_Neutral(entity stimSource, float ThreadNum); boolean response_visualStim_Person_Friend(entity stimSource, float ThreadNum); }; /* ******************************************************************************************** Animation ******************************************************************************************** */ /////////////////////////////////////// // darkmod_guard_base::Torso_Idle /////////////////////////////////////// void ai_darkmod_base::Torso_Idle() { // play basic standing animation idleAnim( ANIMCHANNEL_TORSO, "idle" ); // if in pain, jump to TorsoPain() eachFrame { if( AI_PAIN ) { animState( ANIMCHANNEL_TORSO, "Torso_Pain", 3 ); } } } /////////////////////////////////////// // darkmod_guard_base::Torso_Pain /////////////////////////////////////// void ai_darkmod_base::Torso_Pain() { string anim_name; float next_pain; float current_time; // get the appropriate pain animation, depending on hit location anim_name = "pain"; // play the animation playAnim( ANIMCHANNEL_TORSO, anim_name ); next_pain = sys.getTime() + 0.25; // if we take pain while animating, wait a second then recursively run this method while( !animDone( ANIMCHANNEL_TORSO, 6 ) ) { if( AI_PAIN ) { current_time = sys.getTime(); if( current_time > next_pain ) animState( ANIMCHANNEL_TORSO, "Torso_Pain", 3 ); } waitFrame(); } // finish up and start idling again finishAction( "pain" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 6 ); } /////////////////////////////////////// // darkmod_guard_base::Torso_QuickMelee /////////////////////////////////////// void ai_darkmod_base::Torso_QuickMelee() { // play the attacking animation playAnim( ANIMCHANNEL_TORSO, "melee_attack" ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { waitFrame(); } // finish up and start idling again finishAction( "melee_attack" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } /////////////////////////////////////// // darkmod_guard_base::Torso_LongMelee /////////////////////////////////////// void ai_darkmod_base::Torso_LongMelee() { // play the attacking animation playAnim( ANIMCHANNEL_TORSO, "melee_attack" ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { waitFrame(); } // finish up and start idling again finishAction( "melee_attack" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } void ai_darkmod_base::Torso_Throw() { // play the attacking animation playAnim( ANIMCHANNEL_TORSO, "throw" ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { waitFrame(); } // finish up and start idling again finishAction( "throw" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } void ai_darkmod_base::Legs_Throw() { // play the attacking animation playAnim( ANIMCHANNEL_LEGS, "throw" ); while( !animDone( ANIMCHANNEL_LEGS, 4 ) ) { waitFrame(); } // finish up and start idling again finishAction( "throw" ); animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); } void ai_darkmod_base::Torso_Death() { // TODO : Play the torso death animation, if any finishAction( "dead" ); } void ai_darkmod_base::Torso_Blinded() { // play the blinded animation playAnim( ANIMCHANNEL_TORSO, "walk_blind" ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { waitFrame(); } finishAction( "blinded" ); //animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } void ai_darkmod_base::Torso_KO() { // TODO : Play the torso KO animation, if any finishAction( "knockedout" ); } void ai_darkmod_base::Legs_Death() { // TODO: Play the legs death animation, if any } void ai_darkmod_base::Legs_KO() { // TODO: Play the legs KO animation, if any } void ai_darkmod_base::Legs_Blinded() { // play the attacking animation /*playAnim( ANIMCHANNEL_LEGS, "idle1" ); while( !animDone( ANIMCHANNEL_LEGS, 4 ) ) { waitFrame(); }*/ //animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); } void ai_darkmod_base::Torso_CustomCycle() { eachFrame { ; } } void ai_darkmod_base::Torso_CustomAnim() { while( !animDone( ANIMCHANNEL_TORSO, customBlendOut ) ) { waitFrame(); } finishAction( "customAnim" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", customBlendOut ); } void ai_darkmod_base::Torso_Sight() { string animname; float blendFrames; if ( !getIntKey( "walk_on_sight" ) ) { overrideAnim( ANIMCHANNEL_LEGS ); } animname = self.getKey( "on_activate" ); if ( self.getKey( "on_activate_blend" ) ) { blendFrames = self.getIntKey( "on_activate_blend" ); } else { blendFrames = 4; } if ( animname != "" ) { if ( hasAnim( ANIMCHANNEL_TORSO, animname ) ) { playAnim( ANIMCHANNEL_TORSO, animname ); while( !animDone( ANIMCHANNEL_TORSO, blendFrames ) ) { waitFrame(); } } } finishAction( "sight" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); } /////////////////////////////////////// // darkmod_guard_base::Legs_Idle /////////////////////////////////////// void ai_darkmod_base::Legs_Idle() { idleAnim( ANIMCHANNEL_LEGS, "idle" ); // if we need to run or walk, play the appropriate animation eachFrame { if ( AI_RUN && AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Run", 4 ); } if ( AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Walk", 4 ); } } } /////////////////////////////////////// // darkmod_guard_base::Legs_Walk /////////////////////////////////////// void ai_darkmod_base::Legs_Walk() { playCycle( ANIMCHANNEL_LEGS, "walk" ); // if we need to run or idle, play the appropriate animation eachFrame { if ( AI_RUN && AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Run", 4 ); } if ( !AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); } } } /////////////////////////////////////// // darkmod_guard_base::Legs_Run /////////////////////////////////////// void ai_darkmod_base::Legs_Run() { playCycle( ANIMCHANNEL_LEGS, "run" ); // if we need to walk or idle, play the appropriate animation eachFrame { if ( !AI_RUN && AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Walk", 4 ); } if ( !AI_FORWARD ) { animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); } } } /* ******************************************************************************************** AI ******************************************************************************************** */ void ai_darkmod_base::init() { ignoreEnemies = false; run_distance = 0; walk_turn = 360; ambush = getIntKey( "ambush" ); ignore_lostcombat = getIntKey( "ignore_lostcombat" ); stay_on_attackpath = getIntKey( "stay_on_attackpath" ); idle_sight_fov = true; // TODO: Set this darkmod flag m_HasRangedWeapon = false; AI_lastAlertPosSearched = '0 0 0'; m_alertSearchVolume = '10 10 10'; // Setting these to large negative values lets them occur right away if needed AI_timeOfLastStimulusBark = -1000.0; m_timeOfLastVisualStimBark = -1000.0; timeOfLastAnimation_LookAround = -1000.0; setNeverDormant( getFloatKey( "neverdormant" ) ); // Get the spawnargs parry_rate = getIntKey( "parry_rate" ); melee_pref = getIntKey( "melee_pref" ); m_DrawsWeapon = getIntKey( "draws_weapon" ); //TODO: Make spawnarg to start AI out with their weapon drawn m_WeaponDrawn = false; AI_bMeleeWeapDrawn = false; AI_bRangedWeapDrawn = false; thresh_1 = getFloatKey( "alert_thresh1" ); thresh_2 = getFloatKey( "alert_thresh2" ); thresh_3 = getFloatKey( "alert_thresh3" ); thresh_combat = getFloatKey( "alert_thresh_combat" ); atime1 = getFloatKey( "alert_time1" ); atime2 = getFloatKey( "alert_time2" ); atime3 = getFloatKey( "alert_time3" ); // state 1 does not wander by default wtime1 = 0; wtime2 = getFloatKey( "alert_wander2" ); wtime3 = getFloatKey( "alert_wander3" ); // wandertime randomness for each state wrand1 = 0; wrand2 = getFloatKey( "alert_rand2" ); wrand3 = getFloatKey( "alert_rand3" ); // fractional wander increase for each state winc1 = 0; winc2 = getFloatKey( "alert_wanderinc2" ); winc3 = getFloatKey( "alert_wanderinc3" ); // make sure the time between barks is never zero if( getFloatKey( "dm_chatter_repeat" ) ) chatterRepeat = getFloatKey( "dm_chatter_repeat" ); else { chatterRepeat = 30; } idlePos = getVectorKey( "origin" ); range_pref = 0; // Cory: this is only temporary since I'm not employing ranged attacks yet run_distance = 0; // initialize bools m_stopPatrol = false; AI_CROUCH = false; AI_RUN = false; AI_CREEP = false; // No hiding spot test started hidingSpotSearchDone = false; hidingSpotTestStarted = false; firstChosenHidingSpotIndex = 0; currentChosenHidingSpotIndex = -1; // Chance to turn head while patrolling or waiting if (AI_AlertNum > 0.0) { AI_chancePerSecond_RandomLookAroundWhileIdle = IDLE_RANDOM_HEAD_TURN_CHANCE_PER_SECOND * SLIGHTLY_AGITATED_HEAD_TURN_CHANCE_MULTIPLIER; } else { AI_chancePerSecond_RandomLookAroundWhileIdle = IDLE_RANDOM_HEAD_TURN_CHANCE_PER_SECOND; } // Chance to turn head while searching AI_chancePerSecond_RandomLookAroundWhileSearching = SEARCHING_RANDOM_HEAD_TURN_CHANCE_PER_SECOND; // Init first head turn check time m_lastRandomHeadTurnCheckTime = sys.getTime(); stateOfMind_count_evidenceOfIntruders = 0.0; stateOfMind_b_enemiesHaveBeenSeen = false; stateOfMind_b_itemsHaveBeenStolen = false; stateOfMind_lastTimeFriendlyAISeen = -1000.0; // No device interaction return state active deviceInteractionReturnState = ""; DEBUG_PRINT("Adding task task_Begin"); // DEBUG health_critical = getFloatKey("health_critical"); // Set up task queue taskQueue = sys.pqNew(); attachTaskQueue(taskQueue); //pushStateIfHigherPriority("task_Begin", PRIORITY_STARTLIVING); } boolean ai_darkmod_base::removeTask(string taskName) { return sys.pqRemoveTask(taskQueue, taskName); } /* ******************************************************************************************** States ******************************************************************************************** */ string ai_darkmod_base::getCurrentStateCallableName() { string currentStateName = self.getState(); // Must remove class type from state name, leave only portion after "::" float colonPos; colonPos = sys.strLength (currentStateName) -1; while (colonPos >= 0) { if (sys.strMid(currentStateName,colonPos,1) == ":") { currentStateName = sys.strSkip(currentStateName, colonPos+1); colonPos = -1; } else { colonPos = colonPos - 1; } } // Done return currentStateName; } /* =================================== ai_darkmod_base::task_Begin() =================================== */ void ai_darkmod_base::task_Begin() { // set animations to idle animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); pushState(STATE_IDLE); } /* =================================== ai_darkmod_base::task_Idle() =================================== */ void ai_darkmod_base::task_Idle() { float timeChat = sys.getTime(); float timeStart = sys.getTime(); entity path; // Ensure this task always stays around as the final fallback pushState(STATE_IDLE); AI_RUN = false; m_stopPatrol = false; DEBUG_PRINT (STATE_IDLE); // Not currently searching anything currentHidingSpotListSearchMaxDuration = 0.0; hidingSpotSearchDone = false; hidingSpotTestStarted = true; // sheathe weapon if appropriate SheathWeapon(); // Move back to alert position moveToPosition(idlePos); // -- ported begin // Decide what sound it is appropriate to play string soundName = ""; if (AI_AlertNum <= 0) { soundName = "snd_relaxed"; } else if (stateOfMind_b_enemiesHaveBeenSeen) { soundName = "snd_alertdown0SeenEvidence"; } else if (stateOfMind_b_itemsHaveBeenStolen) { soundName = "snd_alertdown0SeenEvidence"; } else if (stateOfMind_count_evidenceOfIntruders >= MIN_EVIDENCE_OF_INTRUDERS_TO_COMMUNICATE_SUSPICION) { soundName = "snd_alertdown0SeenEvidence"; } else { // Play its idle sound soundName = "snd_alertdown0SeenNoEvidence"; } // Play sound if there is one if ((soundName != "TODO") && (soundName != "None")) { bark(soundName); } // -- ported end eachFrame { // ported begin -- // quick hack for dynamic patrolling if ( getIntKey( "patrol" ) ) { if (current_path) path = current_path; else { path = randomPath(); } if ( path ) { Patrol( path ); } } // ported end -- // Do sensory routine subFrameTask_canSwitchState_sensoryScan(true); // Chance of turning head (ported) subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -40.0, 40.0, 1.0, 6.0); if ((sys.getTime() - timeChat) > chatterRepeat ) { // Only play relaxed sound if not suspicious at all if (AI_AlertNum <= 0) { bark( "snd_relaxed" ); } timeChat = sys.getTime(); } // Tick down alert timer subFrameTask_testAlertStateTimer(); // ported to BasicMind // This is an eachframe statement loop, so don't put waitframe() here // or it will only run once every 2 frames } } /* ******************************************************************************************** Search for enemies: utility functions ******************************************************************************************** */ /* ===================== subFrameTask_chooseFirstSpotToSearch ===================== */ void ai_darkmod_base::subFrameTask_chooseFirstSpotToSearch() { // Test state if (!hidingSpotSearchDone) { float emptyStatementProtected = 0; DEBUG_PRINT ("Error: Hiding spot search generation is not yet completed"); } // Get number of spots float numSpots = getNumHidingSpots(); DEBUG_PRINT ("Found hidings spots: " + numSpots); // Choose randomly if (numSpots >= 1) { /* // Get visual acuity float visAcuity = getAcuity("vis"); // Since earlier hiding spots are "better" (ie closer to stimulus and darker or more occluded) // higher visual acuity should bias toward earlier in the list float bias = 1.0 - visAcuity; if (bias < 0.0) { bias = 0.0; } */ float spotIndex = 0; // Remember which hiding spot we have chosen at start firstChosenHidingSpotIndex = spotIndex; // Note currently chosen hiding spot currentChosenHidingSpotIndex = spotIndex; // Get location chosenHidingSpot = getNthHidingSpotLocation (spotIndex); DEBUG_PRINT ("First spot chosen is index " + firstChosenHidingSpotIndex + " of " + numSpots + " spots"); } else { DEBUG_PRINT ("Didn't find any hiding spots near stimulus"); firstChosenHidingSpotIndex = -1; currentChosenHidingSpotIndex = -1; chosenHidingSpot = '0 0 0'; } } /* ===================== subFrameTask_startNewHidingSpotSearch ===================== */ void ai_darkmod_base::subFrameTask_startNewHidingSpotSearch(vector hideFromPoint, vector searchPoint, vector searchVolume, vector searchExclusionVolume) { vector minBounds; vector maxBounds; vector minExclusionBounds; vector maxExclusionBounds; // Close any previous search closeHidingSpotSearch(); // Make search bounds maxBounds = searchPoint; maxBounds += searchVolume; minBounds = searchPoint; minBounds -= searchVolume; // make exclusion bounds maxExclusionBounds = searchPoint; maxExclusionBounds += searchExclusionVolume; minExclusionBounds = searchPoint; minExclusionBounds -= searchExclusionVolume; // Hiding spot test now started hidingSpotSearchDone = false; hidingSpotTestStarted = true; // Start search float res = startSearchForHidingSpotsWithExclusionArea (hideFromPoint, minBounds, maxBounds, minExclusionBounds, maxExclusionBounds, 255, self); if (res < 0.9) { // Search completed on first round hidingSpotSearchDone = true; subFrameTask_chooseFirstSpotToSearch(); } // End of method } /* ===================== subFrameTask_continueInProgressHidingSpotSearch ===================== */ void ai_darkmod_base::subFrameTask_continueInProgressHidingSpotSearch() { float res = continueSearchForHidingSpots(); if (res < 0.9) { // search completed hidingSpotSearchDone = true; subFrameTask_chooseFirstSpotToSearch(); } } /* ===================== chooseRandomHidingSpot ===================== */ void ai_darkmod_base::chooseRandomHidingSpot(vector hideFromPoint, vector searchPoint, vector searchRadius) { vector minBounds; vector maxBounds; // Close any previous search closeHidingSpotSearch(); // Make search bounds maxBounds = searchPoint; maxBounds += searchRadius; minBounds = searchPoint; minBounds -= searchRadius; // Hiding spot test now started hidingSpotSearchDone = false; hidingSpotTestStarted = true; // Do search float res = startSearchForHidingSpots (hideFromPoint, minBounds, maxBounds, 255, self); while (res > 0.9) { // Wait a frame waitFrame(); // Continue search res = continueSearchForHidingSpots(); } // Get number of spots float numSpots = getNumHidingSpots(); DEBUG_PRINT ("Found hidings spots: " + numSpots); // Choose randomly if (numSpots >= 1) { /* // Get visual acuity float visAcuity = getAcuity("vis"); // Since earlier hiding spots are "better" (ie closer to stimulus and darker or more occluded) // higher visual acuity should bias toward earlier in the list float bias = 1.0 - visAcuity; if (bias < 0.0) { bias = 0.0; } */ float spotIndex = 0; // Remember which hiding spot we have chosen at start firstChosenHidingSpotIndex = spotIndex; // Note currently chosen hiding spot currentChosenHidingSpotIndex = spotIndex; // Get location chosenHidingSpot = getNthHidingSpotLocation (spotIndex); DEBUG_PRINT ("First spot chosen is index " + firstChosenHidingSpotIndex + " of " + numSpots + " spots"); } else { DEBUG_PRINT ("Didn't find any hiding spots near stimulus"); firstChosenHidingSpotIndex = -1; currentChosenHidingSpotIndex = -1; chosenHidingSpot = searchPoint; } // Done hidingSpotSearchDone = true; }; /*------------------------------------------------------------------------------*/ void ai_darkmod_base::chooseNextHidingSpot() // greebo: Ported to SDK { // Get number of spots float numSpots = getNumHidingSpots(); if (numSpots <= 0) { // No hiding spots, sorry hidingSpotSearchDone = false; return; } // Up hiding spot index, chance of skipping hiding spots based // on visual acuity float visAcuity = getAcuity("vis"); // Make sure we stay in bounds currentChosenHidingSpotIndex ++; if (currentChosenHidingSpotIndex >= numSpots) { currentChosenHidingSpotIndex = 0; } // Have we wrapped around to first one searched? if ((currentChosenHidingSpotIndex == firstChosenHidingSpotIndex) || (currentChosenHidingSpotIndex < 0)) { // No more hiding spots DEBUG_PRINT ("No more hiding spots to search"); hidingSpotSearchDone = false; chosenHidingSpot = '0 0 0'; currentChosenHidingSpotIndex = -1; } else { chosenHidingSpot = getNthHidingSpotLocation (currentChosenHidingSpotIndex); DEBUG_PRINT ("Next spot chosen is index " + currentChosenHidingSpotIndex + " of " + numSpots + " spots, first was " + firstChosenHidingSpotIndex); hidingSpotSearchDone = true; } // Done }; /* =================================== task_ReactingToStimulus =================================== */ void ai_darkmod_base::task_ReactingToStimulus() // greebo: Ported to SDK { DEBUG_PRINT (STATE_REACTING_TO_STIMULUS); // We are reacting to this alert position AI_lastAlertPosSearched = m_alertPos; // Look toward the apparent position of the stimulus lookAtPosition( m_alertPos, 2.0 ); // TODO: Animate slight disturbance // Wait a frame here incase we are getting a constant stimulus, which could // loop around through sensory scan back to here again waitFrame(); // Handle each frame's behavior eachFrame { // Do sensory routine and allow new stimuli to interrupt us subFrameTask_canSwitchState_sensoryScan(true); // Move on to searching if we are stimulated enough if (AI_AlertNum >= thresh_2 ) { DEBUG_PRINT ("Alert enough to search"); break; } // Tick down our current alert level subFrameTask_testAlertStateTimer(); if (AI_AlertNum < thresh_1) { // Go back to idle state, but with a bit of suspicsxcscscion setAlertLevel(thresh_1 / 2.0); return; } } // stop current movement stopMove(); // Look at the location of the alert stimulus. Turn to it if we are really agitated // (body preparedness for combat) if (AI_AlertNum < thresh_3) { lookAtPosition( m_alertPos, 3.0 ); } else { lookAtPosition( m_alertPos, 3.0 ); turnToPos (m_alertPos); } // Start a new hiding spot search /* if (getNumHidingSpots() > 10) { // If we already have a search, reprioritize points for new stimulous resortHidingSpotSearch (m_alertPos, m_alertSearchVolume); } else*/ { subFrameTask_startNewHidingSpotSearch (self.getEyePos(), m_alertPos, m_alertSearchVolume, m_alertSearchExclusionVolume); } // No current search completed that we know of numPossibleHidingSpotsSearched = 0; currentHidingSpotListSearchMaxDuration = -1.0; // If we are supposed to search the stimulus location do that instead of just standing around while the search // completes if (b_stimulusLocationItselfShouldBeSearched) { // Spot search should go to a state to wait for thinking to complete // when done (may transition out of that instantaneously if thinking // is done) pushState("task_WaitingForHidingSpotThinkingToComplete"); currentSearchSpot = AI_lastAlertPosSearched; // Determine the search duration subFrameTask_determineSearchDuration(); float timeLeftForSearching = currentHidingSpotListSearchMaxDuration / 2.0; pushState("task_SearchingSpot"); } else { pushState("task_WaitingForHidingSpotThinkingToComplete"); } } /* =================================== task_WaitingForHidingSpotThinkingToComplete =================================== */ void ai_darkmod_base::task_WaitingForHidingSpotThinkingToComplete() // greebo: Ported to SDK { // remove all previous waitingFor... tasks. while (removeTask("task_WaitingForHidingSpotThinkingToComplete")) { continue; } // This counts the number of frames we have been thinking, in case // we have a problem with hiding spot searches not returning float thinkFrameCount; //DEBUG_PRINT ("task_WaitingForHidingSpotThinkingToComplete"); // Loop until thinking completes. // itself, do that immediately. thinkFrameCount = 0; eachFrame { // Do sensory routine subFrameTask_canSwitchState_sensoryScan(true); // Tick down our current alert level subFrameTask_testAlertStateTimer(); // Check if test done if (hidingSpotSearchDone) { float searchTimeSpan; // Hiding spot test is done hidingSpotTestStarted = false; // Here we transition to the state for handling the behavior of // the AI once it thinks it knows where the stimulus may have // come from // For now, starts searching for the stimulus. We probably want // a way for different AIs to act differently // TODO: Morale check etc... // Determine the search duration subFrameTask_determineSearchDuration(); // Yell that you noticed something if you are responding directly to a stimulus if (!b_searchingDueToCommunication) { subFrameTask_yellNoticedSomethingSuspicious(); // Wait a frame to let others respond waitFrame(); } // Get location chosenHidingSpot = getNthHidingSpotLocation (currentChosenHidingSpotIndex); // Set time search is starting currentHidingSpotListSearchStartTime = sys.getTime(); // Switch state waitFrame(); pushState("task_SearchingHidingSpotList"); return; } else { // Continue the search subFrameTask_continueInProgressHidingSpotSearch(); } // One more frame of thinkning thinkFrameCount = thinkFrameCount + 1; // If thinking frame count times out, cut search for points short /* if (thinkFrameCount > 1000) { sys.warning ("Hiding spot search thinkFrameCount " + thinkFrameCount); } */ } // end of eachframe loop } /* =================================== task_SearchingHidingSpotList =================================== */ void ai_darkmod_base::task_SearchingHidingSpotList() { float numPossibleHidingSpots; //DEBUG_PRINT ("task_SearchingHidingSpotList"); // How many hiding spots are in our list? numPossibleHidingSpots = getNumHidingSpots(); while ( ( ( sys.getTime() - currentHidingSpotListSearchStartTime) < currentHidingSpotListSearchMaxDuration) && (numPossibleHidingSpotsSearched < numPossibleHidingSpots) ) { float timeLeftForSearching = currentHidingSpotListSearchMaxDuration - ( sys.getTime() - currentHidingSpotListSearchStartTime); // How much time should we alot to each search spot? // Realistically, it should spend an amount of time that is greater for early points // searched, and then get more hurried as it goes on. // This can be done by having the search time for the spot be related to how much // total search time is left, and how many spots there are left. currentSpotSearchMaxDuration = timeLeftForSearching / 2; //DEBUG_PRINT ("Time left for searching = " + timeLeftForSearching); if (currentSpotSearchMaxDuration < 3.0) { // Not enough time to divy up any further currentSpotSearchMaxDuration = timeLeftForSearching; } // Search it //DEBUG_PRINT ("searching spot " + numPossibleHidingSpotsSearched + " of " + numPossibleHidingSpots + " for max time " + currentSpotSearchMaxDuration); // Spot search should go to list iterator state when done pushState("task_IteratingHidingSpotList"); currentSearchSpot = chosenHidingSpot; pushState("task_SearchingSpot"); return; } if (numPossibleHidingSpots > 0) { m_alertSearchExclusionVolume = m_alertSearchVolume; m_alertRadius = m_alertRadius * 3.0; m_alertSearchVolume = m_alertSearchVolume * 3.0; DEBUG_PRINT ("Current search volume of " + m_alertSearchExclusionVolume + " exhausted, trying wider search volume of " + m_alertSearchVolume); pushState(STATE_REACTING_TO_STIMULUS); return; } // TODO: Vocalize exasperation timeLeftForSearching = currentHidingSpotListSearchMaxDuration - ( sys.getTime() - currentHidingSpotListSearchStartTime); // Maybe wander instead? DEBUG_PRINT ("I don't see where they could be, let me just wander around"); float wanderTime; float wanderIncrement; float wanderRandomFactor; /* Determine wandering based on level */ if (AI_AlertNum >= thresh_3) { wanderTime = wtime3; wanderRandomFactor = wrand3; wanderIncrement = winc3; } else { wanderTime = wtime2; wanderRandomFactor = wrand2; wanderIncrement = winc2; } // Wander within alert radius of alert position multiFrameTask_wanderInLocation ( AI_lastAlertPosSearched, m_alertRadius, timeLeftForSearching, wanderTime, wanderRandomFactor, 3, wanderIncrement ); } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::task_SearchingSpot() { float wanderTime; float wanderIncrement; float wanderRandomFactor; float interSpotPauseStartTime; float interSpotPauseLength; float randomTurnYaw; float randomChance; boolean bool_observingFromDistance; //DEBUG_PRINT ("task_SearchingSpot"); float distanceToGoal; float timeOfLastDistanceCheck; /* Determine wandering based on level */ if (AI_AlertNum >= thresh_3) { wanderTime = wtime3; wanderRandomFactor = wrand3; wanderIncrement = winc3; } else { wanderTime = wtime2; wanderRandomFactor = wrand2; wanderIncrement = winc2; } // IMPORTANT: wrand1 and such are not initialized in the .def variable // loader. Don't use them // TODO: Try to shine light on it if it is dark // Stop //stopMove(); // Move to the hiding spot float visAcuityZeroToOne = getAcuity("vis") / 100.0; vector goalPosition; // Are we just quickly scanning the point because it can be seen clearly? boolean bool_quickScan = false; // Are we moving at all? boolean bool_moveRequired = true; // Try reducing the number of observation position searches by having the AI just move to the // spot if it is far enough away, rather than just looking around. if (canSeePositionExt (currentSearchSpot, 0.0, 1.0)) { DEBUG_PRINT ("I can see there from here"); goalPosition = self.getOrigin(); bool_quickScan = true; // Just quickly scan the point bool_moveRequired = false; // Don't need to move } else if (sys.vecLength(currentSearchSpot - self.getOrigin()) < 500.0) { DEBUG_PRINT ("I'm going to walk up to the point since its far away"); goalPosition = currentSearchSpot; } else { DEBUG_PRINT ("I'm going to find a place to observe it from"); goalPosition = getObservationPosition (currentSearchSpot, visAcuityZeroToOne); } // Are we observing from a distance? bool_observingFromDistance = (goalPosition != currentSearchSpot); // Start move if move required if (bool_moveRequired) { moveToPosition (goalPosition); timeOfLastDistanceCheck = sys.getTime(); distanceToGoal = travelDistanceToPoint(goalPosition); } // If distance has not changed significantly we need to give up to prevent // Doom3 pathing from running in circles while ( (bool_moveRequired) && (!AI_MOVE_DONE) && (!AI_DEST_UNREACHABLE) ) { // Tick down our current alert level subFrameTask_testAlertStateTimer(); // There may be a hiding spot search going on, continue it if necessary if (!hidingSpotSearchDone) { // Continue the search subFrameTask_continueInProgressHidingSpotSearch(); } // Compute distance to the point float distanceToSpot = travelDistanceToPoint (goalPosition); if ( ( (distanceToSpot >= MINIMUM_SEARCH_RUN_DISTANCE) && (AI_AlertNum >= thresh_3) ) || ( (distanceToSpot >= MINIMUM_SEARCH_LONG_RUN_DISTANCE) && (AI_AlertNum >= thresh_2) ) ) { AI_RUN = true; } else { AI_RUN = false; } // Every second, make sure we aren't running in circles if ( (sys.getTime() - timeOfLastDistanceCheck) > 1.0) { timeOfLastDistanceCheck = sys.getTime(); if (abs(distanceToSpot - distanceToGoal) < 1.0) { //DEBUG_PRINT ("Distance to spot " + distanceToSpot + ", distance to spot last time " + distanceToGoal); //DEBUG_PRINT ("I'm not getting any closer to the goal position, so I give up"); bool_observingFromDistance = true; stopMove(); break; } else { distanceToGoal = distanceToSpot; } } // Do sensory routine and allow new searches to override this one subFrameTask_canSwitchState_sensoryScan(true); // Chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -60.0, 60.0, -40.0, 40.0, 1.0, 3.0); // Wait for a frame to prevent busywait waitFrame(); } currentSpotSearchStartTime = sys.getTime(); // Look at the position lookAtPosition(currentSearchSpot, 1.0); // Chance of pausing before moving on randomChance = sys.random(1.0); if ((!bool_quickScan) && (randomChance < 0.1)) { // We reached the spot! Stop move and record time of spot search start stopMove(); // Pause for up to a few seconds and look around interSpotPauseStartTime = sys.getTime(); if (bool_observingFromDistance) { interSpotPauseLength = sys.random (3.0); } else { interSpotPauseLength = sys.random (5.0); } //DEBUG_PRINT ("Decided to pause near hiding spot for time " + interSpotPauseLength); // Play the looking around animation if it has been long enough if ( (sys.getTime() - timeOfLastAnimation_LookAround) > 10.0) { timeOfLastAnimation_LookAround = sys.getTime(); // SZ: Playing these makes their arms paralyzed at their sides for a minute or so. I'm not sure how to // restart the walking arm animation playAnim( ANIMCHANNEL_TORSO, "look_around" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 10 ); } // Chance of randomly turning around randomChance = sys.random(1.0); if ((randomChance < 0.3) && (!bool_observingFromDistance)) { // Look at a random angle randomTurnYaw = sys.random (360.0); //DEBUG_PRINT ("Decided to look at random yaw " + randomTurnYaw); turnTo( randomTurnYaw ); } // Handle pause while ( (sys.getTime() - interSpotPauseStartTime) < interSpotPauseLength) { // There may be a hiding spot search going on, continue it if necessary if (!hidingSpotSearchDone) { // Continue the search subFrameTask_continueInProgressHidingSpotSearch(); } // Tick down our current alert level subFrameTask_testAlertStateTimer(); // Do sensory routine and allow new searches to override this one subFrameTask_canSwitchState_sensoryScan(true); // Chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -60.0, 60.0, -40.0, 40.0, 1.0, 3.0); // Next frame waitFrame(); } } else if ((!bool_quickScan) && (randomChance < 0.3)) { float maxWanderTime; // Wander at hiding spot //DEBUG_PRINT ("Decided to wander near hiding spot"); // How much time is left maxWanderTime = 5.0; multiFrameTask_wanderInLocation ( currentSearchSpot, 150.0, maxWanderTime, wanderTime, wanderRandomFactor, 3, wanderIncrement ); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::task_IteratingHidingSpotList() { // One more hiding spot searched numPossibleHidingSpotsSearched ++; // select next hiding spot chooseNextHidingSpot(); // Wait a frame waitFrame(); // Search it pushState("task_SearchingHidingSpotList"); } /* =================================== task_LostTrackOfEnemy =================================== */ void ai_darkmod_base::task_LostTrackOfEnemy() { float waitStartTime; DEBUG_PRINT (STATE_LOST_TRACK_OF_ENEMY); // Stop moving stopMove(); vector lastEnemyPos = getEnemyPos(); // Have the last enemy pos be the cause of alarm m_alertPos = lastEnemyPos; m_alertRadius = LOST_ENEMY_ALERT_RADIUS; // Large radius m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; // Clear enemy clearEnemy(); // Play the sound bark( "snd_lostTrackOfEnemy" ); // Wait a frame waitFrame(); // Now go to frantic searching alert level setAlertLevel(thresh_3 + (thresh_combat - thresh_3) / 2.0); // Pause for a few seconds float pauseSec = sys.random (2.0); waitStartTime = sys.getTime(); while ( (sys.getTime() - waitStartTime) < pauseSec) { // Do sensory routine and allow new searches to override this one subFrameTask_canSwitchState_sensoryScan(true); // Next frame waitFrame(); } // Go on to search m_alertPos = lastEnemyPos; m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; clearEnemy(); b_searchingDueToCommunication = false; b_stimulusLocationItselfShouldBeSearched = true; pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); } /* =================================== task_TargetCannotBeReached =================================== */ void ai_darkmod_base::task_TargetCannotBeReached() { DEBUG_PRINT ("task_TargetCannotBeReached"); subFrameTask_willSwitchState_handleUnreachableTarget(true); } /* =================================== subFrameTask_willSwitchState_handleUnreachableTarget =================================== */ void ai_darkmod_base::subFrameTask_willSwitchState_handleUnreachableTarget(boolean firstTime) { float lastAIBumpDownTime; float waitForEnemyStartTime; float waitForEnemyDuration; float stoneThrowDelayMin; float stoneThrowDelayMax; float nextThrowStoneTime; AI_RUN = true; // draw weapon if appropriate DrawWeapon(); // Play frustration sound if (firstTime) { bark( "snd_cantReachTarget" ); } // Call for Missile help if (getEnemy()) subFrameTask_callForMissileHelp(); lastAIBumpDownTime = sys.getTime(); // Try to wait for the enemy to come down for a little while waitForEnemyStartTime = sys.getTime(); waitForEnemyDuration = 30.0; // TODO: Get from def variable // Initialise stone-throwing timer stoneThrowDelayMin = getFloatKey("outofreach_projectile_delay_min"); stoneThrowDelayMax = getFloatKey("outofreach_projectile_delay_max"); if (firstTime) { nextThrowStoneTime = 0; // Throw a stone immediately } else { // Wait before throwing a stone nextThrowStoneTime = sys.getTime() + sys.random(stoneThrowDelayMax-stoneThrowDelayMin)+stoneThrowDelayMin; } // Wait a bit to see if the enemy makes a move eachFrame { // Throw something at the player every so often if (nextThrowStoneTime < sys.getTime() && getFloatKey("outofreach_projectile_enabled")!=0) { //DEBUG_PRINT ("throwing a random object..."); subFrameTask_throwRandomObject(); nextThrowStoneTime = sys.getTime() + sys.random(stoneThrowDelayMax-stoneThrowDelayMin)+stoneThrowDelayMin; } // Handle can reach the enemy if ( ( canReachEnemy() ) || (testMeleeAttack() ) ) { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); return; } // Handle if the enemy dies if ( AI_ENEMY_DEAD ) { //TODO: Make sure no other enemies are nearby before going back to idle. setAlertLevel(thresh_1 / 2.0); return; } // If we can see the enemy, look at them. faceEnemy(); // If we can no longer see the enemy, then lose track of them if ( canSeeExt(getEnemy(), true, true) ) { lookAtEnemy( 1 ); } else { // Lost track of enemy pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); return; } // If alerted, wait for enemy timer resets if (AI_ALERTED) { //DEBUG_PRINT ("task_TargetCannotBeReached::AI alerted, waitForEnemy timer is reset"); waitForEnemyStartTime = sys.getTime(); } // Done waiting? if ( ( sys.getTime() - waitForEnemyStartTime) >= waitForEnemyDuration) { // Lost track of enemy pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); return; } // Take cover if this AI knows how to take cover, and if the enemy could attack us from a distance if (firstTime && getFloatKey("taking_cover_enabled")!=0) { entity enemy = getEnemy(); if (getFloatKey("taking_cover_only_from_archers")==0 || !enemy || enemy.rangedThreatTo(self)) { pushStateIfHigherPriority(STATE_TAKE_COVER, PRIORITY_TAKINGCOVER); return; } } } // eachFrame } /* =================================== task_TakingCover =================================== */ void ai_darkmod_base::task_TakingCover() { entity newEnemy; entity lastEnemy; float takeCoverDelayMin = getFloatKey("emerge_from_cover_delay_min"); float takeCoverDelayMax = getFloatKey("emerge_from_cover_delay_max"); float emergeTime; vector enemyPosition; float enemyPositionZOffset = 32; DEBUG_PRINT(STATE_TAKE_COVER); // Record the enemy's position entity enemy = getEnemy(); enemyPosition = enemy.getOrigin(); enemyPosition_z += enemyPositionZOffset; // Add a small offset so we're not looking straight at the ground // If we just threw a projectile, wait for current animation to finish. // As a failsafe, don't wait for more than 5 seconds. float maxWaitTime = sys.getTime() + 5; if (getFloatKey("outofreach_projectile_enabled")!=0) { while (!animDone(ANIMCHANNEL_TORSO, 0) && sys.getTime() < maxWaitTime) { waitFrame(); } } // Take cover lastEnemy = getEnemy(); vector oldPosition = getOrigin(); AI_RUN = true; AI_FORWARD = true; moveToCoverFrom(lastEnemy); // Wait for move to be done eachFrame { if (AI_DEST_UNREACHABLE) { //DEBUG_PRINT ("moveToCover destination is unreachable"); // Since we didn't find somewhere to hide, just stand here looking pretty and throw stuff subFrameTask_willSwitchState_handleUnreachableTarget(false); break; } else if (AI_MOVE_DONE) { //DEBUG_PRINT ("moveToCoverFrom is done"); break; } // Look for new enemies, or old enemies in a very different location if (getEnemy()) { // Find the distance between the new enemy position and the old one enemy = getEnemy(); vector newEnemyPosition = enemy.getOrigin(); newEnemyPosition_z += enemyPositionZOffset; // Add the offset float distance = sys.vecLength(newEnemyPosition - enemyPosition); // If we're a certain number of body lengths distant, then this is a significant move; // focus on the new target vector size = getSize(); if (distance > size_x*20) { subFrameTask_canSwitchState_initiateCombat(); return; } } } if (getOrigin() == oldPosition) { // We didn't actually move, so don't wait to emerge; just go back and throw stuff instead. subFrameTask_willSwitchState_handleUnreachableTarget(false); return; } // Turn towards enemy again faceEnemy(); emergeTime = sys.getTime() + sys.random(takeCoverDelayMax - takeCoverDelayMin) + takeCoverDelayMin; // Wait to emerge from cover and take a peek eachFrame { // Handle can reach the enemy if ( ( canReachEnemy() ) || (testMeleeAttack() ) ) { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); return; } // Handle if the enemy dies if ( AI_ENEMY_DEAD ) { //TODO: Make sure no other enemies are nearby before going back to idle. setAlertLevel(thresh_1 / 2.0); return; } subFrameTask_canSwitchState_sensoryScan(true); // Look for enemies // Possibly turn head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -60.0, 60.0, -40.0, 40.0, 1.0, 3.0); if (emergeTime < sys.getTime()) { // Time to have a look. For now, just go back to where we were before. //DEBUG_PRINT("Time to emerge from cover."); moveToPosition(oldPosition); while (!AI_MOVE_DONE && !AI_DEST_UNREACHABLE) { waitFrame(); subFrameTask_canSwitchState_sensoryScan(true); // Look for enemies } //DEBUG_PRINT("Finished emerging from cover."); // Make sure we got there if (AI_DEST_UNREACHABLE) { //DEBUG_PRINT("Couldn't find anywhere from which to observe player.\n"); pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); return; } // Look at where we think the enemy is lookAtPosition(enemyPosition, 1); turnToPos(enemyPosition); // If we have no enemy, re-acquire the old one if (!getEnemy()) setEnemy(lastEnemy); // Deal with the enemy if (canReachEnemy() || testMeleeAttack()) { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); return; } else if (canSeeExt(getEnemy(), true, true)) { subFrameTask_willSwitchState_handleUnreachableTarget(true); return; } else { pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); return; } } } } /* =================================== task_FleeDone =================================== */ void ai_darkmod_base::task_FleeDone() { // greebo: At this point we should be at a presumably safe place, // start looking for allies float yaw = getCurrentYaw(); float turnSteps = 0; boolean searchForFriendDone = false; entity friendlyAI; while (!searchForFriendDone) { friendlyAI = findFriendlyAI(-1); DEBUG_PRINT("Found friendly AI: " + friendlyAI.getKey("name")); if (friendlyAI == $null_entity) { // None found, turn around a bit yaw += 80; turnTo(yaw); turnSteps++; } else { searchForFriendDone = true; break; } if (turnSteps > 4) { searchForFriendDone = true; break; } sys.wait(0.5); } // Turn towards that friendly AI if (friendlyAI != $null_entity) { turnToEntity(friendlyAI); sys.wait(0.3); float distanceToFriend = sys.vecLength(friendlyAI.getOrigin() - getOrigin()); DEBUG_PRINT("Please, help me, " + friendlyAI.getKey("name")); bark("snd_help"); // Cry for help issueCommunication_IR_DOE(DetectedEnemy_MessageType, distanceToFriend*1.2, friendlyAI, getEnemy(), m_LastEnemyPos); } else { // No friendly AI in range ; } // Now wait a decent amount of time before going back to idle state eachFrame { // Do a sensory scan, in case the threat is coming near again subFrameTask_canSwitchState_sensoryScan(true); // Wait a few frames, the sensory scan doesn't have to happen every frame waitFrame(); waitFrame(); waitFrame(); waitFrame(); } } /* =================================== task_Flee =================================== */ void ai_darkmod_base::task_Flee() { // Kill any previous flee tasks while (removeTask("task_Flee")) { sys.println("Removing previous task_Flee..."); continue; } faceEnemy(); sys.wait(0.5); float helpCryLength = bark("snd_flee"); // greebo: Setup the next time the AI cries for help (during running) float lastCryForHelpTime = sys.getTime(); float nextCryForHelpTime = lastCryForHelpTime + helpCryLength + 6 + sys.random(4) - 2; if (helpCryLength <= 0) { // No help cry length, disable the next cry nextCryForHelpTime = -1; } // Flee entity lastEnemy = getEnemy(); sys.println("Enemy is: " + lastEnemy.getKey("name")); vector oldPosition = getOrigin(); AI_RUN = true; AI_FORWARD = true; float escapeSearchLevel = 3; // 3 means FIND_FRIENDLY_GUARDED float minThreatDistance = 500; // This is used for escapeLevel 1 only DEBUG_PRINT("Fleeing!"); boolean fleeingDone = false; float failureCount = 0; while (!fleeingDone) { if (escapeSearchLevel >= 3) { DEBUG_PRINT("Trying to find escape route - FIND_FRIENDLY_GUARDED."); // Flee to the nearest friendly guarded escape point flee(lastEnemy, EP_FIND_FRIENDLY_GUARDED, EP_DIST_NEAREST); } else if (escapeSearchLevel == 2) { // Try to find another escape route DEBUG_PRINT("Trying alternate escape route - FIND_FRIENDLY."); // Find another escape route to ANY friendly escape point flee(lastEnemy, EP_FIND_FRIENDLY, EP_DIST_NEAREST); } else // escapeSearchLevel is 1 { DEBUG_PRINT("Searchlevel = 1, ZOMG, Panic mode, gotta run now!"); // Get the distance to the enemy float enemyDistance = travelDistanceToEntity(lastEnemy); DEBUG_PRINT("Enemy is as near as " + enemyDistance); if (enemyDistance < 500) { // Increase the fleeRadius (the nearer the enemy, the more) // The enemy is still near, run further if (!flee(lastEnemy, EP_FIND_AAS_AREA_FAR_FROM_THREAT, minThreatDistance)) { // No point could be found. failureCount++; if (failureCount > 5) { fleeingDone = true; } } } else { fleeingDone = true; } } eachFrame // Loop till the AI stops moving { // greebo: Yell HELP in random intervals, if enabled if (nextCryForHelpTime > 0 && sys.getTime() >= nextCryForHelpTime) { helpCryLength = bark("snd_flee"); // Cry for help issueCommunication(DetectedEnemy_MessageType, YELL_STIM_RADIUS, m_LastEnemyPos); nextCryForHelpTime = sys.getTime() + helpCryLength + 6 + sys.random(4) - 2; } if (AI_DEST_UNREACHABLE) { DEBUG_PRINT ("flee destination is unreachable."); if (escapeSearchLevel > 1) { // Decrease the escape search level escapeSearchLevel--; } break; } else if (AI_MOVE_DONE) { DEBUG_PRINT ("move is done"); if (escapeSearchLevel > 1) { // Did we move at all? if (getOrigin() == oldPosition) { // search level 2 or higher failed, decrease escapeSearchLevel--; } else { // we may exit, searchLevel 2 or 3 was successful fleeingDone = true; } } break; } // Do a sensory scan, in case the threat is coming near again subFrameTask_canSwitchState_sensoryScan(false); // Some time padding waitFrame(); waitFrame(); } // eachFrame (while running) } // eachFrame (escape route lookup) // greebo: Fleeing movement is done here DEBUG_PRINT("Fleeing movement done, what next?"); // Switch to the FleeDone task pushState("task_FleeDone"); // TODO: The hiding spot search tasks should be cleared at this point // to prevent the AI from running back to and investigating the area // where the player was encountered. This makes fleeing kind of pointless } void ai_darkmod_base::task_Investigate_Enemy_Position() { float attack_flags; AI_RUN = true; DEBUG_PRINT("I'm on my way, I'll look into that incident."); bark( "snd_investigate" ); // draw weapon if appropriate DrawWeapon(); // set our annoying bools back to false. AI_VISALERT = false; AI_HEARDSOUND = false; AI_TACTALERT = false; DEBUG_PRINT("Last alert position was " + m_alertPos); // Alert position should already have been set by the communication response function moveToPosition(m_alertPos); while (!AI_MOVE_DONE) { subFrameTask_canSwitchState_sensoryScan(false); // Wait two frames, the sensory scan doesn't have to happen every frame waitFrame(); } // Done, the entity either has reached the position, or has already engaged on the enemy pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); } /* =================================== task_Combat =================================== */ void ai_darkmod_base::task_Combat() { float attack_flags; AI_RUN = true; bark( "snd_combat" ); // draw weapon if appropriate DrawWeapon(); // set our annoying bools back to false. AI_VISALERT = false; AI_HEARDSOUND = false; AI_TACTALERT = false; entity lastEnemy = getEnemy(); eachFrame { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } if ( sys.influenceActive() ) { waitFrame(); continue; } if ( AI_ENEMY_DEAD ) { //TODO: Make sure no other enemies are nearby before going back to idle. setAlertLevel(thresh_1 / 2.0); return; } if (check_flee()) { // greebo: check_flee says we should flee, so RUN! DEBUG_PRINT("ARGH! This is too hot, I'm out of here."); // Push the task and exit this one pushStateIfHigherPriority("task_Flee", PRIORITY_FLEE); return; } attack_flags = check_attacks(); if ( attack_flags ) { // angua: turn to face player vector diff = lastEnemy.getOrigin() - getOrigin(); vector angles = sys.VecToAngles(diff); turnTo(angles_y); do_attack( attack_flags ); continue; } if ( !combat_chase() ) { locateEnemy(); if ( !combat_chase() ) { clearEnemy(); setAlertLevel(thresh_3); // Lost the target pushStateIfHigherPriority(STATE_LOST_TRACK_OF_ENEMY, PRIORITY_LOSTTARGET); return; } } } } /* ******************************************************************************************** Combat ******************************************************************************************** */ /////////////////////////////////////// // darkmod_guard_base::check_attacks // Decides which attack to perform based on preference /////////////////////////////////////// float ai_darkmod_base::check_attacks() { float attack_flags = 0; float attack = random( 10 ); if( attack <= melee_pref ) { if( testMeleeAttack() ) { float melee_attack = random( 2 ); if( melee_attack <= 1 ) { attack_flags |= ATTACK_QUICK_MELEE; } else { attack_flags |= ATTACK_LONG_MELEE; } } } else { attack_flags |= ATTACK_MISSILE; } return attack_flags; } /////////////////////////////////////// // darkmod_guard_base::do_attack // Performs the appropriate attack /////////////////////////////////////// void ai_darkmod_base::do_attack( float attack_flags ) { if( attack_flags & ATTACK_QUICK_MELEE ) { combat_quickmelee(); } else if( attack_flags & ATTACK_LONG_MELEE ) { combat_longmelee(); } else if( attack_flags & ATTACK_MISSILE ) { ; // not applying ranged attack yet } } /////////////////////////////////////// // darkmod_guard_base::combat_quickmelee /////////////////////////////////////// void ai_darkmod_base::combat_quickmelee() { lookAtEnemy( 100 ); animState( ANIMCHANNEL_TORSO, "Torso_QuickMelee", 5 ); waitAction( "melee_attack" ); lookAtEnemy( 1 ); stopMove(); } /////////////////////////////////////// // darkmod_guard_base::combat_longmelee /////////////////////////////////////// void ai_darkmod_base::combat_longmelee() { lookAtEnemy( 100 ); animState( ANIMCHANNEL_TORSO, "Torso_LongMelee", 5 ); waitAction( "melee_attack" ); lookAtEnemy( 1 ); stopMove(); } /////////////////////////////////////// // darkmod_guard_base::combat_parry /////////////////////////////////////// void ai_darkmod_base::combat_parry() { } float ai_darkmod_base::check_flee() { return (getHealth() < health_critical); // Flee if health below threshold } /** * Standard D3 functions from ai_monster_base **/ /* ===================== ai_darkmod_base::playCustomCycle ===================== */ void ai_darkmod_base::playCustomCycle( string animname, float blendTime ) { animState( ANIMCHANNEL_TORSO, "Torso_CustomCycle", blendTime ); overrideAnim( ANIMCHANNEL_LEGS ); playCycle( ANIMCHANNEL_TORSO, animname ); } /* ===================== ai_darkmod_base::playCustomAnim ===================== */ void ai_darkmod_base::playCustomAnim( string animname, float blendIn, float blendOut ) { customBlendOut = blendOut; animState( ANIMCHANNEL_TORSO, "Torso_CustomAnim", blendIn ); overrideAnim( ANIMCHANNEL_LEGS ); playAnim( ANIMCHANNEL_TORSO, animname ); } /* ===================== ai_darkmod_base::trigger_wakeup_targets ===================== */ void ai_darkmod_base::trigger_wakeup_targets() { string key; string name; entity ent; key = getNextKey( "wakeup_target", "" ); while( key != "" ) { name = getKey( key ); ent = sys.getEntity( name ); if ( !ent ) { sys.warning( "Unknown wakeup_target '" + name + "' on entity '" + getName() + "'" ); } else { sys.trigger( ent ); } key = getNextKey( "wakeup_target", key ); } } /* ===================== ai_darkmod_base::checkForEnemy ===================== */ boolean ai_darkmod_base::checkForEnemy( float use_fov ) { entity enemy; vector size; float dist; if ( sys.influenceActive() ) { return false; } if ( AI_PAIN ) { // get out of ambush mode when shot ambush = false; } if ( ignoreEnemies ) { // while we're following paths, we only respond to enemies on pain, or when close enough to them if ( stay_on_attackpath ) { // don't exit attack_path when close to enemy return false; } enemy = getEnemy(); if ( !enemy ) { enemy = findEnemy( false ); } if ( !enemy ) { return false; } size = getSize(); dist = ( size_x * 1.414 ) + 16; // diagonal distance plus 16 units if ( enemyRange() > dist ) { return false; } } else { if ( getEnemy() ) { // we were probably triggered (which sets our enemy) return true; } if ( !ignore_sight ) { enemy = findEnemy( use_fov ); } if ( !enemy ) { if ( ambush ) { return false; } enemy = heardSound( true ); if ( !enemy ) { return false; } } } if (!getEnemy()) setEnemy(enemy); ignoreEnemies = false; // once we've woken up, get out of ambush mode ambush = false; // don't use the fov for sight anymore idle_sight_fov = false; //setEnemy( enemy ); return true; } /* ===================== ai_darkmod_base::task_Spawner ===================== */ void ai_darkmod_base::task_Spawner() { entity ent; float triggerCount; float maxSpawn; float i; string name; maxSpawn = getIntKey( "spawner" ); name = getName(); hide(); triggerCount = 0; AI_ACTIVATED = false; while( 1 ) { if ( AI_ACTIVATED ) { triggerCount++; AI_ACTIVATED = false; } if ( triggerCount ) { if ( canBecomeSolid() ) { for( i = 0; i < maxSpawn; i++ ) { ent = sys.getEntity( name + i ); if ( !ent ) { break; } } if ( i < maxSpawn ) { triggerCount--; sys.copySpawnArgs( self ); sys.setSpawnArg( "spawner", "0" ); sys.setSpawnArg( "name", name + i ); if ( getKey( "spawn_target" ) != "" ) { sys.setSpawnArg( "target", getKey( "spawn_target" ) ); } ent = sys.spawn( getKey( "classname" ) ); sys.trigger( ent ); } } } waitFrame(); } } /* ===================== ai_darkmod_base::monster_begin Don't think we need this for now. Can copy it over from ai_monster_base later on. ===================== */ void ai_darkmod_base::monster_begin() { float donothing=0; } /* ===================== ai_darkmod_base::archvile_minion ===================== */ void ai_darkmod_base::archvile_minion() { hide(); resurrect = true; AI_DEAD = true; } /* ===================== ai_darkmod_base::can_resurrect ===================== */ boolean ai_darkmod_base::can_resurrect() { if ( !AI_DEAD ) { return false; } if ( !isHidden() ) { return false; } if ( !canBecomeSolid() ) { return false; } return true; } /* ===================== ai_darkmod_base::task_Resurrect ===================== */ void ai_darkmod_base::task_Resurrect() { float health; vector ang; AI_DEAD = false; stopMove(); hide(); stopRagdoll(); restorePosition(); waitUntil( canBecomeSolid() ); health = getFloatKey( "health" ); setHealth( health ); ang_y = getFloatKey( "angle" ); setAngles( ang ); turnTo( ang_y ); clearBurn(); allowDamage(); pushState("task_Begin"); } /* ===================== ai_darkmod_base::monster_resurrect ===================== */ void ai_darkmod_base::monster_resurrect( entity enemy ) { // mark them as not dead so we don't get resurrected twice this frame AI_DEAD = false; if ( enemy ) { setEnemy( enemy ); } else { //setEnemy( $player1 ); float donothing=0; } pushState("task_Resurrect"); } /* ===================== ai_darkmod_base::wait_for_enemy ===================== */ void ai_darkmod_base::wait_for_enemy() { // prevent an infinite loop when in notarget AI_PAIN = false; stopMove(); while( !AI_PAIN && !getEnemy() ) { if ( checkForEnemy( idle_sight_fov ) ) { break; } waitFrame(); } } /* ===================== ai_darkmod_base::task_Killed // ported to ai::DeadState ===================== */ void ai_darkmod_base::task_Killed() { stopMove(); animState( ANIMCHANNEL_TORSO, "Torso_Death", 0 ); animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 ); waitAction( "dead" ); pushState("task_Dead"); } /* ===================== ai_darkmod_base::task_KnockedOut // ported to ai::KnockedOutState ===================== */ void ai_darkmod_base::task_KnockedOut() { stopMove(); animState( ANIMCHANNEL_TORSO, "Torso_KO", 0 ); animState( ANIMCHANNEL_LEGS, "Legs_KO", 0 ); waitAction( "knockedout" ); pushState("task_Unconscious"); } void ai_darkmod_base::blind(entity originator, float skipVisibilityCheck) { if (!skipVisibilityCheck) { // Perform visibility check if (canSeeExt(originator, 1, 0)) { DEBUG_PRINT("AI blinded."); pushStateIfHigherPriority("task_Blinded", PRIORITY_BLINDED); } else { DEBUG_PRINT("AI can't see the flash."); } } else { // Skip visibility check pushStateIfHigherPriority("task_Blinded", PRIORITY_BLINDED); } } /* ===================== ai_darkmod_base::task_Blinded ===================== */ void ai_darkmod_base::task_Blinded() { stopMove(); animState( ANIMCHANNEL_TORSO, "Torso_Blinded", 0 ); animState( ANIMCHANNEL_LEGS, "Legs_Blinded", 0 ); // Wait till the "blinded" waitAction is finished (is set by Torso_Blinded()) waitAction("blinded"); sys.wait(5); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); sys.wait(1); pushState("task_SearchingHidingSpotList"); } /* ===================== ai_darkmod_base::task_Dead Keep bodies around if burnDelay is set to -1 ===================== */ void ai_darkmod_base::task_Dead() { float burnDelay = getFloatKey( "burnaway" ); // Do not call preburn if burnDelay < 0 if ( burnDelay > 0 ) { preBurn(); sys.wait( burnDelay ); burn(); startSound( "snd_burn", SND_CHANNEL_BODY, false ); } sys.wait( 3 ); if ( resurrect ) { hide(); stopRagdoll(); restorePosition(); // wait until we're resurrected waitUntil( 0 ); } // Keep bodies around if burnDelay is set to -1 if (burnDelay >= 0) { remove(); } // Never return while(1) { sys.wait(10000000); } } /* ===================== ai_darkmod_base::task_Unconscious ===================== */ void ai_darkmod_base::task_Unconscious() { // Never return while(1) { sys.wait(10000000); } } /* ===================== ai_darkmod_base::sight_enemy ===================== */ void ai_darkmod_base::sight_enemy() { string animname; faceEnemy(); animname = self.getKey( "on_activate" ); if ( animname != "" ) { // don't go dormant during on_activate anims since they // may end up floating in air during no gravity anims. setNeverDormant( true ); if ( getIntKey( "walk_on_sight" ) ) { moveToEnemy(); } animState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 ); waitAction( "sight" ); setNeverDormant( getFloatKey( "neverdormant" ) ); } } /* ===================== ai_darkmod_base::enemy_dead NOTE: This will probably have to be changed to preserve the ragdoll Look at how they did it with the marines ===================== */ void ai_darkmod_base::enemy_dead() { AI_ENEMY_DEAD = false; checkForEnemy( false ); if ( !getEnemy() ) { waitFrame(); // avoid infinite loops return; } else { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); } } /** * Walk on trigger now calls Patrol **/ /* ===================== ai_darkmod_base::walk_on_trigger ===================== */ void ai_darkmod_base::walk_on_trigger() { string animname; entity path; allowMovement( false ); // sit in our idle anim till we're activated animname = self.getKey( "anim" ); playCustomCycle( animname, 4 ); waitUntil( AI_ACTIVATED || AI_PAIN ); if ( AI_ACTIVATED ) { clearEnemy(); AI_ACTIVATED = false; } animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); allowMovement( true ); // follow a path path = randomPath(); if ( path ) { Patrol( path ); } if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) { // sit in our idle anim till we're activated allowMovement( false ); playCustomCycle( animname, 4 ); while( !AI_PAIN && !AI_ACTIVATED ) { if ( checkAlerted( ) ) { break; } waitFrame(); } allowMovement( true ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } trigger_wakeup_targets(); //sight_enemy(); } /* ===================== ai_darkmod_base::wake_on_trigger ===================== */ void ai_darkmod_base::wake_on_trigger() { string animname; entity path; if ( !getIntKey( "attack_path" ) ) { path = randomPath(); if ( path ) { Patrol( path ); } } if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) { // sit in our idle anim till we're activated allowMovement( false ); animname = self.getKey( "anim" ); playCustomCycle( animname, 4 ); waitUntil( AI_ACTIVATED || AI_PAIN ); allowMovement( true ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } trigger_wakeup_targets(); if ( getIntKey( "attack_path" ) ) { // follow a path and fight player at end path = randomPath(); if ( path ) { ignoreEnemies = true; AI_RUN = true; Patrol( path ); ignoreEnemies = false; } } else { sight_enemy(); } } /* ===================== ai_darkmod_base::wake_on_enemy TODO: Replace this with darkmd alert style (Is this method really necessary?) ===================== */ void ai_darkmod_base::wake_on_enemy() { string animname; entity path; if ( !getIntKey( "attack_path" ) ) { path = randomPath(); if ( path ) { Patrol( path ); } } if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) { // sit in our idle anim till we're activated allowMovement( false ); animname = self.getKey( "anim" ); playCustomCycle( animname, 4 ); while( !AI_PAIN && !AI_ACTIVATED ) { if ( checkForEnemy( true ) ) { break; } waitFrame(); } } allowMovement( true ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); trigger_wakeup_targets(); if ( getIntKey( "attack_path" ) ) { // follow a path and fight player at end path = randomPath(); if ( path ) { AI_RUN = true; ignoreEnemies = true; Patrol( path ); ignoreEnemies = false; } } else { sight_enemy(); } } /* ===================== ai_darkmod_base::wake_on_attackcone NOTE: Also not sure if this method is necessary... ===================== */ void ai_darkmod_base::wake_on_attackcone() { string animname; entity path; entity enemy; if ( !getIntKey( "attack_path" ) ) { path = randomPath(); if ( path ) { Patrol( path ); AI_RUN = path.getIntKey( "run" ); while( !AI_MOVE_DONE && !AI_ACTIVATED && !AI_PAIN ) { enemy = findEnemyInCombatNodes(); if ( enemy ) { //setEnemy( enemy ); break; } waitFrame(); } } } if ( !getEnemy() && !AI_ACTIVATED && !AI_PAIN ) { // sit in our idle anim till we're activated allowMovement( false ); animname = self.getKey( "anim" ); playCustomCycle( animname, 4 ); while( !AI_ACTIVATED && !AI_PAIN ) { enemy = findEnemyInCombatNodes(); if ( enemy ) { //setEnemy( enemy ); break; } waitFrame(); } allowMovement( true ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } trigger_wakeup_targets(); if ( getIntKey( "attack_path" ) ) { // follow a path and fight player at end path = randomPath(); if ( path ) { AI_RUN = true; ignoreEnemies = true; Patrol( path ); ignoreEnemies = false; } } else { sight_enemy(); } } /* ===================== Patrol, replacing idle_followpathentities ===================== */ void ai_darkmod_base::Patrol( entity pathnode ) { string nodeaction; string triggername; entity triggerent; m_patrolChatTimer = sys.getTime(); m_patrolBarkRepeat = getFloatKey("bark_repeat_patrol"); //set a default patrol bark repeat time if(!m_patrolBarkRepeat) m_patrolBarkRepeat = 45.0; bark( "snd_idle" ); current_path = pathnode; do { next_path = current_path.randomPath(); nodeaction = current_path.getKey( "classname" ); if ( hasFunction( nodeaction ) ) { callFunction( nodeaction ); } else { sys.warning( "'" + getName() + "' encountered an unsupported path entity '" + nodeaction + "' on entity '" + current_path.getName() + "'\n" ); return; } if (m_stopPatrol) { break; } // trigger any entities the path had targeted triggername = current_path.getKey( "trigger" ); if ( triggername != "" ) { triggerent = sys.getEntity( triggername ); if ( triggerent ) { triggerent.activate( self ); } } current_path = next_path; } while( !( !current_path ) ); } /* ===================== ai_darkmod_base::check_blocked returns true when an attack was called, since the move command may be different from when entering the function. ===================== */ boolean ai_darkmod_base::check_blocked() { entity obstacle; float attack_flags; ai_darkmod_base monster; float endTime; boolean oldrun; oldrun = AI_RUN; if ( AI_BLOCKED ) { //sys.print( sys.getTime() + " : " + getName() + " is stuck in place\n" ); saveMove(); AI_RUN = true; wander(); endTime = sys.getTime() + 2; while( sys.getTime() < endTime ) { attack_flags = check_attacks(); if ( attack_flags ) { restoreMove(); do_attack( attack_flags ); AI_RUN = oldrun; return true; } waitFrame(); } restoreMove(); } else if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) { float force = getFloatKey( "kick_force" ); if ( !force ) { force = 60; } kickObstacles( getObstacle(), force ); } else if ( moveStatus() > MOVE_STATUS_BLOCKED_BY_OBJECT ) { // just wait for the path to be clear obstacle = getObstacle(); monster = obstacle; if ( monster ) { if ( monster.blocked ) { AI_RUN = oldrun; return false; } } blocked = true; saveMove(); while( moveStatus() > MOVE_STATUS_BLOCKED_BY_OBJECT ) { AI_RUN = true; wander(); endTime = sys.getTime() + 1; while( sys.getTime() < endTime ) { if ( AI_MOVE_DONE ) { faceEnemy(); } attack_flags = check_attacks(); if ( attack_flags ) { blocked = false; restoreMove(); do_attack( attack_flags ); AI_RUN = oldrun; return true; } waitFrame(); } restoreMove(); } blocked = false; } AI_RUN = oldrun; return false; } /* ===================== ai_darkmod_base::combat_chase ===================== */ boolean ai_darkmod_base::combat_chase() { float delta; boolean do_run; float range; float attack_flags; if ( !AI_ENEMY_VISIBLE || ( enemyRange() > run_distance ) ) { do_run = true; } else { do_run = false; } moveToEnemy(); if ( AI_MOVE_DONE ) { return false; } waitFrame(); if ( AI_MOVE_DONE ) { attack_flags = check_attacks(); if ( attack_flags ) { do_attack( attack_flags ); return true; } return false; } while( !AI_MOVE_DONE && !AI_DEST_UNREACHABLE ) { if ( AI_ENEMY_DEAD ) { enemy_dead(); } if ( sys.influenceActive() ) { return true; } if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } attack_flags = check_attacks(); if ( attack_flags ) { do_attack( attack_flags ); return true; } if ( check_blocked() ) { return true; } range = enemyRange(); if ( !AI_ENEMY_VISIBLE || ( range > run_distance ) ) { do_run = true; } delta = getTurnDelta(); if ( ( delta > walk_turn ) || ( delta < -walk_turn ) ) { AI_RUN = false; } else { AI_RUN = do_run; } waitFrame(); } return true; } /* ===================== ai_darkmod_base::checkTurretAttack tests if monster can still do a turret attack from current location NOTE: This probably doesn't apply to DarkMod, but just in case.. ===================== */ boolean ai_darkmod_base::checkTurretAttack() { return canHitEnemy(); } /* ===================== ai_darkmod_base::combat_turret_node ===================== */ void ai_darkmod_base::combat_turret_node( entity node ) { vector pos; float min_wait; float max_wait; float num_shots; float wait_end; float current_time; float count; boolean exit; boolean do_melee; min_wait = node.getFloatKey( "min_wait" ); max_wait = node.getFloatKey( "max_wait" ); num_shots = node.getFloatKey( "num_shots" ); pos = node.getOrigin(); exit = false; while( !AI_ENEMY_DEAD ) { if ( !node ) { // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed break; } // run to the combat node allowMovement( true ); AI_RUN = true; moveToEntity( node ); while( !AI_MOVE_DONE ) { if ( sys.influenceActive() ) { stopMove(); while( sys.influenceActive() ) { waitFrame(); } moveToEntity( node ); } if ( !node ) { // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed break; } if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } // exit out if we can't get there or we can do a melee attack do_melee = testMeleeAttack(); if ( AI_DEST_UNREACHABLE || do_melee ) { animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); return; } waitFrame(); } if ( !AI_ENEMY_VISIBLE ) { waitFrame(); continue; } if ( !checkTurretAttack() ) { waitFrame(); continue; } if ( !node ) { // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed break; } faceEnemy(); allowMovement( false ); // make sure he's precisely at the node slideTo( pos, 0.25 ); waitUntil( AI_MOVE_DONE ); faceEnemy(); // do our attack allowMovement( false ); count = int( sys.random( num_shots ) ) + 1; while( count > 0 ) { if ( sys.influenceActive() ) { break; } if ( !node ) { // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed exit = true; break; } animState( ANIMCHANNEL_TORSO, "Torso_TurretAttack", 4 ); while( inAnimState( ANIMCHANNEL_TORSO, "Torso_TurretAttack" ) ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } waitFrame(); } if ( testMeleeAttack() || AI_ENEMY_DEAD ) { exit = true; break; } count--; } if ( exit ) { break; } faceEnemy(); current_time = sys.getTime(); wait_end = current_time + min_wait + sys.random( max_wait - min_wait ); while( sys.influenceActive() || ( sys.getTime() < wait_end ) ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } // exit out if we can do a melee attack or enemy is dead // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed if ( testMeleeAttack() || AI_ENEMY_DEAD || !node ) { exit = true; break; } waitFrame(); } if ( exit ) { break; } } if ( node ) { // we're done with the node, so get rid of it so we don't use it again node.remove(); } allowMovement( true ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } /* ===================== ai_darkmod_base::checkNodeAnim ===================== */ void ai_darkmod_base::checkNodeAnim( entity node, string prefix, string anim ) { if ( !hasAnim( ANIMCHANNEL_TORSO, prefix + "_" + anim ) ) { sys.error( "AI node '" + node.getName() + "' specifies missing anim '" + prefix + "_" + anim + "' on monster '" + getName() + "'" ); } } /* ===================== ai_darkmod_base::combat_attack_cone ===================== */ void ai_darkmod_base::combat_attack_cone( entity node ) { vector ang; vector pos; float min_wait; float max_wait; float num_shots; float wait_end; float current_time; float count; boolean exit; boolean attack; boolean do_melee; boolean dont_wait; string prefix; // run to the combat node AI_RUN = true; moveToEntity( node ); while( !AI_MOVE_DONE ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } // exit out if we can't get there or we can do a melee attack do_melee = testMeleeAttack(); if ( AI_DEST_UNREACHABLE || do_melee || !enemyInCombatCone( node, false ) || AI_ENEMY_DEAD || !node ) { animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); return; } waitFrame(); } if ( !node ) { // level designers sometimes remove the combat node, so check for it so the thread doesn't get killed animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); return; } // set our anim prefix prefix = node.getKey( "anim" ); if ( prefix == "" ) { sys.error( "Missing 'anim' key on entity '" + node.getName() + "'" ); } setAnimPrefix( prefix ); checkNodeAnim( node, prefix, "out" ); checkNodeAnim( node, prefix, "fire" ); checkNodeAnim( node, prefix, "in" ); checkNodeAnim( node, prefix, "wait" ); min_wait = node.getFloatKey( "min_wait" ); max_wait = node.getFloatKey( "max_wait" ); num_shots = node.getFloatKey( "num_shots" ); dont_wait = getIntKey( "wake_on_attackcone" ); if ( dont_wait ) { setKey( "wake_on_attackcone", "0" ); } // face the direction the node points ang = node.getAngles(); turnTo( ang_y ); exit = false; attack = false; pos = node.getOrigin(); while( !( !node ) && enemyInCombatCone( node, true ) ) { allowMovement( false ); // play the wait animation playCustomCycle( "wait", 4 ); // make sure he's precisely at the node slideTo( pos, 0.5 ); current_time = sys.getTime(); if ( dont_wait ) { dont_wait = false; wait_end = current_time + 0.5; } else { wait_end = current_time + min_wait + sys.random( max_wait - min_wait ); } while( sys.influenceActive() || !AI_MOVE_DONE || !facingIdeal() || ( current_time < wait_end ) ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } // exit out if we can do a melee attack or enemy has left the combat cone if ( testMeleeAttack() ) { exit = true; break; } if ( !node || !enemyInCombatCone( node, false ) ) { exit = true; break; } waitFrame(); current_time = sys.getTime(); } if ( exit ) { break; } // do our attack allowMovement( true ); playCustomAnim( "out", 4, 0 ); while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } if ( testMeleeAttack() ) { exit = true; break; } if ( !node || !enemyInCombatCone( node, true ) ) { exit = true; break; } waitFrame(); } if ( exit ) { break; } AI_DAMAGE = false; allowMovement( false ); count = int( sys.random( num_shots ) ) + 1; while( !AI_DAMAGE && ( count > 0 ) ) { if ( sys.influenceActive() ) { break; } playCustomAnim( "fire", 0, 0 ); while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) { if ( sys.influenceActive() ) { break; } if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } if ( testMeleeAttack() ) { exit = true; break; } if ( !node || !enemyInCombatCone( node, true ) ) { exit = true; break; } waitFrame(); } attack = true; if ( testMeleeAttack() ) { exit = true; break; } if ( !node || !enemyInCombatCone( node, true ) ) { exit = true; break; } count--; } AI_DAMAGE = false; if ( exit ) { break; } allowMovement( true ); playCustomAnim( "in", 0, 4 ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } if ( testMeleeAttack() ) { exit = true; break; } if ( !node || !enemyInCombatCone( node, true ) ) { exit = true; break; } waitFrame(); } if ( exit ) { break; } } if ( !( !node ) && attack ) { // mark the node as used in case it's single use node.markUsed(); } allowMovement( true ); setAnimPrefix( "" ); animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); } /* ===================== ai_darkmod_base::combat_ainode ===================== */ void ai_darkmod_base::combat_ainode( entity node ) { if ( node.getKey( "classname" ) == "ai_attackcone_turret" ) { combat_turret_node( node ); } else { combat_attack_cone( node ); } } /* ===================== ai_darkmod_base::combat_lost ===================== */ void ai_darkmod_base::combat_lost() { if ( !ignore_lostcombat ) { pushStateIfHigherPriority("task_LostCombat", PRIORITY_COMBAT); } } /* ===================== ai_darkmod_base::task_LostCombat ===================== */ void ai_darkmod_base::task_LostCombat() { monster_base_lost_combat(); } /* ===================== ai_darkmod_base::monster_base_lost_combat TODO: Rewrite this! ===================== */ void ai_darkmod_base::monster_base_lost_combat() { entity node; vector ang; float dist; float yaw; float attack_flags; entity possibleEnemy; float allow_attack; float lost_time; lost_time = sys.getTime() + sys.getFrameTime(); allow_attack = sys.getTime() + 4; node = getClosestHiddenTarget( "ai_lostcombat" ); if ( node ) { dist = distanceTo( node ); if ( dist < 40 ) { // fixes infinite loops when close to lost combat node waitFrame(); } else { AI_RUN = true; moveToEntity( node ); while( !AI_MOVE_DONE ) { if ( sys.influenceActive() ) { break; } /** * Commmented out, don't use heardSound **/ /* possibleEnemy = heardSound( true ); if ( possibleEnemy ) { if ( canReachEntity( possibleEnemy ) ) { //setEnemy( possibleEnemy ); break; } } */ if ( canReachEnemy() ) { if ( AI_ENEMY_IN_FOV || AI_PAIN ) { break; } } if ( check_blocked() ) { break; } waitFrame(); // allow attacks when enemy is outside of fov if ( sys.getTime() > allow_attack ) { AI_ENEMY_IN_FOV = AI_ENEMY_VISIBLE; } attack_flags = check_attacks(); if ( attack_flags ) { do_attack( attack_flags ); pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); return; } } ang = node.getAngles(); turnTo( ang_y ); while( !AI_MOVE_DONE ) { if ( canReachEnemy() ) { if ( heardSound( true ) ) { break; } if ( AI_ENEMY_IN_FOV || AI_PAIN ) { break; } } waitFrame(); } } } else { AI_RUN = true; moveToCover(); if ( AI_DEST_UNREACHABLE ) { combat_wander(); } // if we're not already in cover if ( !AI_MOVE_DONE ) { while( !AI_MOVE_DONE ) { if ( sys.influenceActive() ) { break; } // allow attacks when enemy is outside of fov if ( sys.getTime() > allow_attack ) { AI_ENEMY_IN_FOV = AI_ENEMY_VISIBLE; } attack_flags = check_attacks(); if ( attack_flags ) { do_attack( attack_flags ); pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); return; } /* possibleEnemy = heardSound( true ); if ( possibleEnemy ) { if ( canReachEntity( possibleEnemy ) ) { //setEnemy( possibleEnemy ); break; } } */ if ( canReachEnemy() ) { if ( AI_ENEMY_IN_FOV || AI_PAIN ) { break; } } if ( check_blocked() ) { break; } waitFrame(); } if ( !sys.influenceActive() ) { if ( AI_ENEMY_VISIBLE ) { faceEnemy(); } else if ( AI_MOVE_DONE ) { // turn around to face the way we came yaw = getCurrentYaw(); turnTo( yaw + 180 ); } } } } // wait at least 1 frame to avoid loops waitFrame(); if ( lost_time >= sys.getTime() ) { combat_wander(); } if ( AI_ENEMY_VISIBLE || sys.influenceActive() ) { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); } else { clearEnemy(); return; } } /* ===================== ai_darkmod_base::combat_wander TODO: Rewrite this so it doesn't use HeardSound ===================== */ void ai_darkmod_base::combat_wander() { float endtime; float mintime; mintime = sys.getTime() + 0.2; endtime = sys.getTime() + 3; wander(); while( sys.getTime() < endtime ) { if ( sys.influenceActive() ) { break; } if ( check_attacks() ) { break; } if ( sys.getTime() > mintime ) { if ( canReachEnemy() ) { if ( heardSound( true ) ) { break; } if ( AI_ENEMY_IN_FOV || AI_PAIN ) { break; } } } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -40.0, 40.0, 1.0, 6.0); waitFrame(); } // wait at least 1 frame to avoid loops waitFrame(); pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); } /* ===================== ai_darkmod_base::task_FollowAlternatePath ===================== */ void ai_darkmod_base::task_FollowAlternatePath() { if ( inAnimState( ANIMCHANNEL_TORSO, "Torso_CustomCycle" ) || inAnimState( ANIMCHANNEL_TORSO, "Torso_CustomAnim" ) ) { animState( ANIMCHANNEL_TORSO, "Torso_Idle", 8 ); } ignoreEnemies = true; Patrol( current_path ); ignoreEnemies = false; return; } /* ===================== ai_darkmod_base::follow_alternate_path1 ===================== */ void ai_darkmod_base::follow_alternate_path1() { current_path = getEntityKey( "alt_path1" ); pushState("task_FollowAlternatePath"); } /* ===================== ai_darkmod_base::follow_alternate_path2 ===================== */ void ai_darkmod_base::follow_alternate_path2() { current_path = getEntityKey( "alt_path2" ); pushState("task_FollowAlternatePath"); } /* ===================== ai_darkmod_base::follow_alternate_path3 ===================== */ void ai_darkmod_base::follow_alternate_path3() { current_path = getEntityKey( "alt_path2" ); pushState("task_FollowAlternatePath"); } // Seb void ai_darkmod_base::Anim_Disable() { } void ai_darkmod_base::Head_Idle() { idleAnim( ANIMCHANNEL_HEAD, "stand" ); } /* ===================== ai_darkmod_base::playHeadAnim ===================== */ float ai_darkmod_base::playHeadAnim( string animname, float blend_frames ) { string anim; float headlength; float bodylength; animState( ANIMCHANNEL_LEGS, "Anim_Disable", blend_frames ); setBlendFrames( ANIMCHANNEL_LEGS, blend_frames ); idleAnim( ANIMCHANNEL_LEGS, "stand" ); waitFrame(); anim = chooseAnim( ANIMCHANNEL_HEAD, animname ); if ( anim != "" ) { setBlendFrames( ANIMCHANNEL_HEAD, blend_frames ); playAnim( ANIMCHANNEL_HEAD, anim ); headlength = animLength( ANIMCHANNEL_HEAD, anim ); anim = chooseAnim( ANIMCHANNEL_LEGS, animname ); if ( anim == "" ) { animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_frames ); } else { bodylength = animLength( ANIMCHANNEL_LEGS, anim ); if ( bodylength < headlength ) { playCycle( ANIMCHANNEL_LEGS, anim ); } else { playAnim( ANIMCHANNEL_LEGS, anim ); } } return ANIMCHANNEL_HEAD; } else { // play on legs if ( blend_frames >= 0 ) { setBlendFrames( ANIMCHANNEL_LEGS, blend_frames ); } anim = chooseAnim( ANIMCHANNEL_LEGS, animname ); if ( anim == "" ) { playAnim( ANIMCHANNEL_LEGS, "stand" ); } else { playAnim( ANIMCHANNEL_LEGS, animname ); } return ANIMCHANNEL_LEGS; } } /* ===================== ai_darkmod_base::endHeadAnim ===================== */ void ai_darkmod_base::endHeadAnim( float blend_out ) { if ( getHead() ) { stopAnim( ANIMCHANNEL_HEAD, blend_out ); } setBlendFrames( ANIMCHANNEL_LEGS, blend_out ); idleAnim( ANIMCHANNEL_LEGS, "stand" ); animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out ); if ( getHead() ) { animState( ANIMCHANNEL_HEAD, "Head_Idle", blend_out ); } } /* ===================== ai_darkmod_base::finishPathCommand ===================== */ void ai_darkmod_base::finishPathCommand() { string triggername; entity triggerent; // trigger any entities the path had targeted triggername = current_path.getKey( "trigger" ); if ( triggername != "" ) { triggerent = sys.getEntity( triggername ); if ( triggerent ) { triggerent.activate( self ); } } current_path = next_path; skipOutOfPath = false; if ( can_talk ) { setTalkState( TALK_OK ); } no_cower = no_cower_saved; } /* ===================== character::cancelPathCommand ===================== */ void ai_darkmod_base::cancelPathCommand() { if ( skipOutOfPath ) { finishPathCommand(); } skipOutOfPath = false; if ( can_talk ) { setTalkState( TALK_OK ); } no_cower = no_cower_saved; } /* ===================== ai_darkmod_base::check_cower ===================== */ void ai_darkmod_base::check_cower() { if ( !no_cower ) { if ( heardSound( false ) ) { endHeadAnim( 6 ); cancelPathCommand(); setState( "state_Cower" ); } } } /** * Path_* functions rewritten for DarkMod **/ /* ===================== monster_base::path_corner // ported to PathCornerTask ===================== */ void ai_darkmod_base::path_corner() { string customAnim; if ( current_path.getKey( "run" ) != "" ) { AI_RUN = current_path.getIntKey( "run" ); } customAnim = current_path.getKey( "anim" ); while( 1 ) { if ( customAnim != "" ) { playCustomCycle( customAnim, 4 ); } moveToEntity( current_path ); waitFrame(); while( !AI_MOVE_DONE ) { if ( sys.influenceActive() ) { break; } PatrolBark(); if ( checkAlerted() ) { m_stopPatrol = true; } if (m_stopPatrol) { break; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } if ( customAnim != "" ) { animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); } if (m_stopPatrol) { break; } if ( sys.influenceActive() ) { stopMove(); while( sys.influenceActive() ) { waitFrame(); } continue; } break; } if ( AI_DEST_UNREACHABLE ) { // Can't reach sys.logString(LC_AI, LT_WARNING, "entity '" + getName() + "' couldn't reach path_corner '" + current_path.getName() + "'"); sys.wait(2); } } /* ===================== ai_darkmod_base::path_anim ===================== */ void ai_darkmod_base::path_anim() { string animname; float ang; float blend_in; float blend_out; animname = current_path.getKey( "anim" ); blend_in = current_path.getIntKey( "blend_in" ); blend_out = current_path.getIntKey( "blend_out" ); if ( current_path.getKey( "angle" ) != "" ) { ang = current_path.getFloatKey( "angle" ); turnTo( ang ); while( !facingIdeal() ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } } playCustomAnim( animname, blend_in, blend_out ); while( !animDone( ANIMCHANNEL_TORSO, blend_out ) ) { PatrolBark(); if ( checkAlerted() ) { break; } waitFrame(); } animState( ANIMCHANNEL_TORSO, "Torso_Idle", blend_out ); } /* ===================== ai_darkmod_base::path_cycleanim ===================== */ void ai_darkmod_base::path_cycleanim() { string animname; vector ang; float blend_in; float blend_out; float waittime; animname = current_path.getKey( "anim" ); blend_in = current_path.getIntKey( "blend_in" ); blend_out = current_path.getIntKey( "blend_out" ); ang = current_path.getAngles(); turnTo( ang_y ); while( !facingIdeal() ) { PatrolBark(); if ( checkAlerted() ) { return; } waitFrame(); } playCustomCycle( animname, blend_in ); waittime = current_path.getFloatKey( "wait" ); if ( waittime ) { waittime += sys.getTime(); while( sys.getTime() < waittime ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } } else { AI_ACTIVATED = false; while( !AI_ACTIVATED ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } } animState( ANIMCHANNEL_TORSO, "Torso_Idle", blend_out ); } /* ===================== ai_darkmod_base::path_turn // ported to PathCornerTask ===================== */ void ai_darkmod_base::path_turn() { vector ang; ang = current_path.getAngles(); turnTo( ang_y ); while( !facingIdeal() ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } } /* ===================== ai_darkmod_base::path_wait // ported to PathCornerTask ===================== */ void ai_darkmod_base::path_wait() { float waittime, waitmax; waittime = current_path.getFloatKey( "wait" ); waitmax = current_path.getFloatKey( "wait_max" ); // add in random wait if(waitmax > 0) waittime += (waitmax - waittime) * sys.random(1.0); waittime += sys.getTime(); while( sys.getTime() < waittime ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } } /* ===================== ai_darkmod_base::path_waitfortrigger ===================== */ void ai_darkmod_base::path_waitfortrigger() { AI_ACTIVATED = false; while( !AI_ACTIVATED ) { PatrolBark(); if ( checkAlerted() ) { return; } // Random chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileIdle, -60.0, 60.0, -45.0, 45.0, 1.0, 6.0); waitFrame(); } AI_ACTIVATED = false; } /* ===================== ai_darkmod_base::path_hide ===================== */ void ai_darkmod_base::path_hide() { hide(); } /* ===================== ai_darkmod_base::path_show ===================== */ void ai_darkmod_base::path_show() { waitUntil( canBecomeSolid() ); show(); } /* ===================== ai_darkmod_base::path_attack ===================== */ void ai_darkmod_base::path_attack() { entity enemy; float delta; boolean do_run; float range; float attack_flags; enemy = current_path.getEntityKey( "enemy" ); if ( !enemy ) { return; } //setEnemy( enemy ); locateEnemy(); AI_ACTIVATED = false; while( !AI_ACTIVATED && !( !enemy ) ) { stopMove(); while( sys.influenceActive() ) { waitFrame(); } if ( enemyRange() > run_distance ) { do_run = true; } else { do_run = false; } // set our enemy again in case we were shot by the player //setEnemy( enemy ); if ( AI_ENEMY_DEAD ) { break; } moveToEnemy(); if ( AI_MOVE_DONE ) { locateEnemy(); moveToEnemy(); if ( AI_MOVE_DONE ) { // prevent runaway loops if monster can't reach enemy waitFrame(); } } while( !AI_ACTIVATED && !AI_MOVE_DONE && !AI_DEST_UNREACHABLE && !( !enemy ) ) { // set our enemy again in case we were shot by the player //setEnemy( enemy ); if ( AI_ENEMY_DEAD ) { break; } moveToEnemy(); if ( AI_ENEMY_IN_FOV ) { lookAtEnemy( 1 ); } if ( sys.influenceActive() ) { break; } attack_flags = check_attacks(); if ( attack_flags ) { do_attack( attack_flags ); } range = enemyRange(); if ( range > run_distance ) { do_run = true; } delta = getTurnDelta(); if ( ( delta > walk_turn ) || ( delta < -walk_turn ) ) { AI_RUN = false; } else { AI_RUN = do_run; } waitFrame(); } stopMove(); } } // Seb /* ===================== ai_darkmod_base::skip_conversation ===================== */ void ai_darkmod_base::skip_conversation() { string classname; classname = current_path.getKey( "classname" ); while( sys.strLeft( classname, 17 ) == "path_conversation" ) { current_path = next_path; next_path = next_path.randomPath(); if ( !next_path ) { break; } classname = current_path.getKey( "classname" ); } stopSound( SND_CHANNEL_VOICE, false ); if ( head ) { head.stopSound( SND_CHANNEL_VOICE, false ); } } /* ===================== ai_darkmod_base::break_out_of_conversation ===================== */ void ai_darkmod_base::break_out_of_conversation() { character listener; // tell our listeners to skip out while( next_listener ) { listener = next_listener; next_listener = listener.next_listener; listener.skip_conversation(); listener.next_listener = $null_entity; listener.listening_to_character = $null_entity; } skip_conversation(); } /* ===================== ai_darkmod_base::path_conversation_listen ===================== */ void ai_darkmod_base::path_conversation_listen() { string focus_character; string anim; boolean no_look; float blend_in; float blend_out; stopMove(); focus_character = current_path.getKey( "focus" ); listening_to_character = sys.getEntity( focus_character ); anim = current_path.getKey( "anim" ); if ( anim != "" ) { blend_in = current_path.getIntKey( "blend_in" ); blend_out = current_path.getIntKey( "blend_out" ); playCustomCycle( anim, blend_in ); } no_look = current_path.getIntKey( "no_look" ); // link ourselves into the focus character's listeners next_listener = listening_to_character.next_listener; listening_to_character.next_listener = self; while( listening_to_character ) { if ( AI_TALK ) { listening_to_character.break_out_of_conversation(); cancelPathCommand(); setState( "state_Idle" ); } check_cower(); if ( !no_look ) { lookAt( listening_to_character, 0.1 ); } waitFrame(); } if ( anim != "" ) { animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out ); } } /* ===================== ai_darkmod_base::path_conversation ===================== */ void ai_darkmod_base::path_conversation() { string dialog; string anim; string focus_character; float waitTime; entity listener; character char_listener; float blend_in; float blend_out; float channel; stopMove(); anim = current_path.getKey( "anim" ); focus_character = current_path.getKey( "focus" ); waitTime = current_path.getFloatKey( "wait" ); listener = sys.getEntity( focus_character ); blend_in = current_path.getIntKey( "blend_in" ); blend_out = current_path.getIntKey( "blend_out" ); // say the conversation line dialog = current_path.getKey( "snd_dialog" ); if ( dialog != "" ) { startSoundShader( dialog, SND_CHANNEL_VOICE ); } setBlendFrames( ANIMCHANNEL_LEGS, blend_in ); if ( getHead() ) { animState( ANIMCHANNEL_HEAD, "Anim_Disable", blend_in ); } channel = playHeadAnim( anim, blend_in ); while( !animDone( channel, blend_out ) ) { if ( AI_TALK ) { break; } check_cower(); lookAt( listener, 0.1 ); waitFrame(); } endHeadAnim( blend_out ); if ( AI_TALK ) { break_out_of_conversation(); cancelPathCommand(); setState( "state_Idle" ); } if ( waitTime ) { // wait for a bit waitTime += sys.getTime(); while( waitTime > sys.getTime() ) { if ( AI_TALK ) { break_out_of_conversation(); cancelPathCommand(); setState( "state_Idle" ); } check_cower(); lookAt( listener, 0.1 ); waitFrame(); } } // tell our listeners we're done while( next_listener ) { char_listener = next_listener; next_listener = char_listener.next_listener; char_listener.next_listener = $null_entity; char_listener.listening_to_character = $null_entity; } } /* ===================== ai_darkmod_base::bark // ported to SingleBarkTask ===================== */ float ai_darkmod_base::bark( string name ) { return playAndLipSync( name, "talk1" ); } /* ===================== ai_darkmod_base::subFrameTask_determineSearchDuration() ===================== */ float ai_darkmod_base::subFrameTask_determineSearchDuration() // greebo: Ported to SDK { // Determine how much time we should spend searching based on the alert // times float searchTimeSpan = 0.0; if (AI_AlertNum >= thresh_1) { searchTimeSpan += atime1; } if (AI_AlertNum >= thresh_2) { searchTimeSpan += atime2; } if (AI_AlertNum >= thresh_3) { searchTimeSpan += atime3; } // Randomize duration by up to 20% increase currentHidingSpotListSearchMaxDuration = searchTimeSpan + (searchTimeSpan * sys.random(0.2)); //DEBUG_PRINT ("Search duration set to " + currentHidingSpotListSearchMaxDuration); // Done return currentHidingSpotListSearchMaxDuration; } /* ===================== ai_darkmod_base::subFrameTask_yellNoticedSomethingSuspicious() ===================== */ void ai_darkmod_base::subFrameTask_yellNoticedSomethingSuspicious() // greebo: Ported to SDK { //bark( "snd_NoticedSomethingOdd" ); // Issue a communication stim to call any other AIs for Missile help //DEBUG_PRINT ("Hey over here! I spotted something suspicious!"); issueCommunication ( DetectedSomethingSuspicious_MessageType, YELL_STIM_RADIUS, m_alertPos ); } /* ===================== ai_darkmod_base::subFrameTask_callForMissileHelp(); ===================== */ void ai_darkmod_base::subFrameTask_callForMissileHelp() { // bark( "snd_callForMissileHelp" ); // Issue a communication stim to call any other AIs for Missile help //DEBUG_PRINT ("Calling for missile help"); issueCommunication_DOE ( RequestForMissileHelp_MessageType, YELL_STIM_RADIUS, getEnemy(), getOrigin() ); } /* ===================== ai_darkmod_base::subFrameTask_throwRandomObject ===================== */ void ai_darkmod_base::subFrameTask_throwRandomObject () { // Throwing object at the target // Play throw animation playAnim( ANIMCHANNEL_TORSO, "throw" ); playAnim( ANIMCHANNEL_LEGS, "throw" ); } /* ===================== ai_darkmod_base::subFrameTask_canSwitchState_sensoryScan ===================== */ void ai_darkmod_base::subFrameTask_canSwitchState_sensoryScan // greebo: Ported to SDK ( boolean b_searchNewStimuli ) { vector newAlertDeltaFromLastOneSearched; vector tempVector; float alertDeltaLength; float xComponent; float yComponent; float zComponent; // Test if alerted if (AI_ALERTED) { // Process alert flags for combat or stimulus location (both destroy flag values)? if (AI_AlertNum >= thresh_combat) { // This reflects the alert level inside it as well //DEBUG_PRINT ("Initiating combat due to stim"); subFrameTask_canSwitchState_initiateCombat(); } // If it was not a combat level alert, or we returned here because there // was no target, set the alert position setAlertPos(); // Are we searching out new alerts if (b_searchNewStimuli) { // Is this alert far enough away from the last one we reacted to to // consider it a new alert? Visual alerts are highly compelling and // are always considered new newAlertDeltaFromLastOneSearched = (m_alertPos - AI_lastAlertPosSearched); alertDeltaLength = sys.vecLength(newAlertDeltaFromLastOneSearched); if ((m_alertType == "v") || (alertDeltaLength > m_alertRadius)) { // This is a new alert /* // SZ Dec 30, 2006 // Note changed this from thresh_1 to thresh_2 to match thresh designers intentions */ if (AI_AlertNum >= thresh_2) { if (m_alertType == "v") { // Visual stimuli are locatable enough that we should // search the exact stim location first b_stimulusLocationItselfShouldBeSearched = true; } else { // Don't bother to search direct stim location as we don't know // exactly where the stim is b_stimulusLocationItselfShouldBeSearched = false; } // One more piece of evidence of something out of place stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 1.0; // Do new reaction to stimulus waitFrame(); b_searchingDueToCommunication = false; pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); return; } } // Not too close to last stimulus or is visual stimulus } // Not ignoring new stimuli } // Done } /* ===================== ai_darkmod_base::subFrameTask_canSwitchState_initiateCombat ===================== */ void ai_darkmod_base::subFrameTask_canSwitchState_initiateCombat() // greebo: Ported to SDK (Fleeing is WIP) { boolean targetFound = false; // Check for an enemy, if this returns TRUE, we have an enemy targetFound = setTarget(); if(targetFound) { //DEBUG_PRINT ("COMBAT NOW!"); // Spotted an enemy stateOfMind_b_enemiesHaveBeenSeen = true; entity enemy = getEnemy(); m_LastEnemyPos = enemy.getOrigin(); issueCommunication_DOE(DetectedEnemy_MessageType, YELL_STIM_RADIUS, enemy, m_LastEnemyPos); // greebo: Check for weapons and flee if we are unarmed. if (getNumMeleeWeapons() == 0 && getNumRangedWeapons() == 0) { DEBUG_PRINT("I'm unarmed, I'm afraid!"); pushStateIfHigherPriority("task_Flee", PRIORITY_FLEE); return; } // greebo: Check for civilian AI, which will always flee in face of a combat (this is a temporary query) if (getFloatKey("is_civilian")) { DEBUG_PRINT("I'm civilian. I'm afraid."); pushStateIfHigherPriority("task_Flee", PRIORITY_FLEE); return; } // Try to set up movement path to enemy moveToEnemy(); if( !AI_DEST_UNREACHABLE && canReachEnemy() ) { pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); } else { // TODO: find alternate path, etc // Do we have a ranged weapon? if (m_HasRangedWeapon) { // Just use ranged weapon pushStateIfHigherPriority(STATE_COMBAT, PRIORITY_COMBAT); } else { // Can't reach the target pushStateIfHigherPriority("task_TargetCannotBeReached", PRIORITY_CANNOTREACHTARGET); } } return; } // If we got here there is no target //DEBUG_PRINT ("no Target to justify combat alert level, lowering to agitated search"); // Lower alert level from combat to agitated search setAlertLevel(thresh_combat - 0.01); } /* ===================== ai_darkmod_base::subFrameTask_randomHeadTurn (ported > IdleSensoryTask) ===================== */ void ai_darkmod_base::subFrameTask_randomHeadTurn ( float percentChance, float minClockwiseYaw, float maxClockwiseYaw, float minPitch, float maxPitch, float minDurationSeconds, float maxDurationSeconds ) { // SZ: Dec 30, 2006 // The time between random head turns is now affected by how many out of place things // they have witnessed. In other words, they get more agitated and nervous and look around // more if they have seen too many things out of place. float timeMultiplier = stateOfMind_count_evidenceOfIntruders; if (timeMultiplier > 5.0) { timeMultiplier = 5.0; } else if (timeMultiplier < 1.0) { timeMultiplier = 1.0; } // Check if it is time to see if we should turn our head float nowTime = sys.getTime(); if ( ((nowTime - m_lastRandomHeadTurnCheckTime) * timeMultiplier) < 1.0) { return; } m_lastRandomHeadTurnCheckTime = nowTime; // Chance to turn head float chance = sys.random (1); if (chance >= percentChance) { // Nope return; } // Generate yaw angle in degrees float range = maxClockwiseYaw - minClockwiseYaw; float headYawAngle = sys.random (range) + minClockwiseYaw; // Generate pitch angle in degrees range = maxPitch - minPitch; float headPitchAngle = sys.random (range) + minPitch; // Generate duration in seconds range = maxDurationSeconds - minDurationSeconds; float durationInSeconds = sys.random (range) + minDurationSeconds; // Call event lookAtAngles (headYawAngle, headPitchAngle, 0.0, durationInSeconds); } /* ===================== ai_darkmod_base::subFrameTask_testAlertStateTimer (ported to BasicMind::TestAlertStateTimer) ===================== */ void ai_darkmod_base::subFrameTask_testAlertStateTimer() { float newAlertLevel; // restart the de-alert timer if we get another alert if (AI_ALERTED) { //DEBUG_PRINT ("Restarting alert state timer"); AI_currentAlertLevelStartTime = sys.getTime(); return; } if (AI_currentAlertLevelDuration < 0) { return; } if ( (sys.getTime() - AI_currentAlertLevelStartTime) >= AI_currentAlertLevelDuration) { // This alert level has expired, drop the alert level down halfway up the // next lower category (to reflect nervousness in next lower category) if (AI_AlertNum > thresh_3) { //DEBUG_PRINT ("Dropping to alert level 2.5 after alert duration expired, duration " + AI_currentAlertLevelDuration); newAlertLevel = thresh_2 + ((thresh_3 - thresh_2) / 2.0); } else if (AI_AlertNum > thresh_2) { //DEBUG_PRINT ("Dropping to alert level 1.5 after alert duration expired, duration " + AI_currentAlertLevelDuration); newAlertLevel = thresh_1 + ((thresh_2 - thresh_1) / 2.0); } else if (AI_AlertNum > (thresh_1 / 2.0)) { //DEBUG_PRINT ("Dropping to alert level 0.5 after alert duration expired, duration " + AI_currentAlertLevelDuration); newAlertLevel = (thresh_1/ 2.0) - 0.01; // To prevent floating point comparison error // Alert level is changing setAlertLevel(newAlertLevel); // Go Idle return; } else { // Alert level is not changing if already < halfway between thresh_1 and thresh_2 //DEBUG_PRINT ("Alert level already at 1.5 " + AI_currentAlertLevelDuration); return; } // Alert timer has expired AI_currentAlertLevelDuration = -1; // Alert level is changing setAlertLevel(newAlertLevel); } } //----------------------------------------------------------------------------------- void ai_darkmod_base::task_LightTorch() { //DEBUG_PRINT ("task_LightTorch"); string returnState = deviceInteractionReturnState; // Need entity to interact with if (!m_interactEnt) { waitFrame(); deviceInteractionReturnState = ""; setState (returnState); } waitFrame(); // Walk up to the torch vector goalPosition = m_interactEnt.getOrigin(); // TODO: Project position down to floor moveToPosition (goalPosition); // If distance has not changed significantly we need to give up to prevent // Doom3 pathing from running in circles float timeOfLastDistanceCheck = sys.getTime(); float distanceToGoal = travelDistanceToPoint(goalPosition); while ( (!AI_MOVE_DONE) && (!AI_DEST_UNREACHABLE) ) { // Compute distance to the point float distanceToSpot = travelDistanceToPoint (goalPosition); if ( ( (distanceToSpot >= MINIMUM_SEARCH_RUN_DISTANCE) && (AI_AlertNum >= thresh_3) ) || ( (distanceToSpot >= MINIMUM_SEARCH_LONG_RUN_DISTANCE) && (AI_AlertNum >= thresh_2) ) ) { AI_RUN = true; } else { AI_RUN = false; } /* // Every half-second, make sure we aren't running in circles if ( (sys.getTime() - timeOfLastDistanceCheck) > 0.5) { timeOfLastDistanceCheck = sys.getTime(); if ((distanceToGoal - distanceToSpot) < 1.0) { //DEBUG_PRINT ("Distance to spot " + distanceToSpot + ", distance to spot last time " + distanceToGoal); //DEBUG_PRINT ("I'm not getting any closer to the light position, so I give up"); stopMove(); break; } else { distanceToGoal = distanceToSpot; } } */ // Do sensory routine subFrameTask_canSwitchState_sensoryScan(true); // Chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -60.0, 60.0, -40.0, 40.0, 1.0, 3.0); // Wait for a frame to prevent busywait waitFrame(); } // We reached the spot! Stop move stopMove(); // Look at the position lookAtPosition(goalPosition, 1.0); // Pause for a second or so float pauseStartTime = sys.getTime(); while ( (sys.getTime() - pauseStartTime) < 3.0) { // Sensory routine subFrameTask_canSwitchState_sensoryScan(true); // Next frame waitFrame(); } // Perform action distanceToSpot = distanceToPoint (goalPosition); if (distanceToSpot < 250.0) { // Frob ignite it light_moving_ext dalight = m_interactEnt; dalight.frob_ignite(); waitFrame(); } else { // Cou'dn't find it bark( "snd_noRelightTorch" ); } deviceInteractionReturnState = ""; setState (returnState); } //----------------------------------------------------------------------------------- void ai_darkmod_base::task_TurnOnSwitchLight() { //DEBUG_PRINT ("task_TurnOnSwitchLight"); string returnState = deviceInteractionReturnState; // Need entity to interact with if (!m_interactEnt) { waitFrame(); deviceInteractionReturnState = ""; setState (returnState); } waitFrame(); // We need to find the switch entity that controls the light. Check the // spawnargs for the correct key. string switchEntityName = m_interactEnt.getKey (AIUSE_LIGHTSWITCH_NAME_KEY); //DEBUG_PRINT ("Finding switch entity with name " + switchEntityName); entity switchEntity = sys.getEntity(switchEntityName); if (!switchEntity) { //DEBUG_PRINT ("There is no switch with name " + switchEntityName); deviceInteractionReturnState = ""; setState (returnState); } //DEBUG_PRINT ("Found switch " + switchEntityName); // Walk up to the switch vector goalPosition = switchEntity.getOrigin(); // TODO: Project position down to floor moveToPosition (goalPosition); // If distance has not changed significantly we need to give up to prevent // Doom3 pathing from running in circles float timeOfLastDistanceCheck = sys.getTime(); float distanceToGoal = travelDistanceToPoint(goalPosition); while ( (!AI_MOVE_DONE) && (!AI_DEST_UNREACHABLE) ) { // Compute distance to the point float distanceToSpot = travelDistanceToPoint (goalPosition); if ( ( (distanceToSpot >= MINIMUM_SEARCH_RUN_DISTANCE) && (AI_AlertNum >= thresh_3) ) || ( (distanceToSpot >= MINIMUM_SEARCH_LONG_RUN_DISTANCE) && (AI_AlertNum >= thresh_2) ) ) { AI_RUN = true; } else { AI_RUN = false; } /* // Every half-second, make sure we aren't running in circles if ( (sys.getTime() - timeOfLastDistanceCheck) > 1.0) { timeOfLastDistanceCheck = sys.getTime(); if ((distanceToGoal - distanceToSpot) < 1.0) { //DEBUG_PRINT ("Distance to switch spot " + distanceToSpot + ", distance to spot last time " + distanceToGoal + ", difference = " + (distanceToGoal - distanceToSpot) ); //DEBUG_PRINT ("I'm not getting any closer to the switch position, so I give up"); break; } else { distanceToGoal = distanceToSpot; } } */ // Do sensory routine subFrameTask_canSwitchState_sensoryScan(true); // Chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -90.0, 90.0, -45.0, 45.0, 1.0, 3.0); // Wait for a frame to prevent busywait waitFrame(); } // We reached the spot! Stop move stopMove(); // Look at the position lookAtPosition(goalPosition, 1.0); distanceToSpot = distanceToPoint (goalPosition); if (distanceToSpot < 250.0) { // Frob the switch //DEBUG_PRINT ("Telling switch to activate targets"); switchEntity.activateTargets(self); //switchEntity.frob_action(); //DEBUG_PRINT ("Frobbed the switch"); waitFrame(); } // Go back to device interaction return state deviceInteractionReturnState = ""; setState (returnState); } //----------------------------------------------------------------------------------- void ai_darkmod_base::task_TurnOnMagicLight() { // TODO: Implement this state //DEBUG_PRINT ("task_TurnOnMagicLight"); waitFrame(); setState (deviceInteractionReturnState); } //----------------------------------------------------------------------------------- /* ===================== ai_darkmod_base::multiFrameTask_wanderInLocation ===================== */ void ai_darkmod_base::multiFrameTask_wanderInLocation ( vector centerPoint, float maxDistanceFromCenterPoint, float maxTaskTime, float wanderDuration, float randomMultiplier, float alertThresh, float wanderDurationIncrement ) { float taskStartTime; float currentWanderDuration; float currentWanderStartTime; float interDirectionChangeDuration; float initAlert = AI_AlertNum; taskStartTime = sys.getTime(); stopMove(); // Turn toward the initial location turnToPos( centerPoint ); // Move toward the initial loation moveToPosition ( centerPoint ); // initialize the wanderTimer currentWanderDuration = wanderDuration + sys.random( wanderDuration * randomMultiplier ); currentWanderStartTime = sys.getTime(); interDirectionChangeDuration = 3.0; while ( ( (AI_AlertNum - initAlert) < alertThresh ) && ((sys.getTime() - taskStartTime) < maxTaskTime) ) { // Do sensory routine and allow new searches to override this one subFrameTask_canSwitchState_sensoryScan(true); // Test alert state timer every frame subFrameTask_testAlertStateTimer(); // Chance of turning head subFrameTask_randomHeadTurn (AI_chancePerSecond_RandomLookAroundWhileSearching, -60.0, 60.0, -40.0, 40.0, 1.0, 3.0); // if we are approaching or past maximum distance, next wander is back toward the initial position if (sys.vecLength(self.getOrigin() - centerPoint) >= maxDistanceFromCenterPoint) { moveToPosition(centerPoint); currentWanderStartTime = sys.getTime(); } else if ( (AI_MOVE_DONE) || ((sys.getTime() - currentWanderStartTime) >= currentWanderDuration) ) { // Wander in a new direction if wander phase time expired or we got stopped by something wander(); currentWanderStartTime = sys.getTime(); // Set the next random wander length currentWanderDuration = wanderDuration + sys.random( wanderDuration * randomMultiplier ); } // Wait for next frame waitFrame(); } // Done wandering } /* ===================== ai_darkmod_base::setTarget() // ported to BasicMind::SetTarget() ===================== */ boolean ai_darkmod_base::setTarget( ) { entity target; //NOTE: To work properly, the priority here must be: check tactile first, then sight. //DEBUG_PRINT ("Trying to find a target..."); // Done if we already have a target if (getEnemy() ) { //DEBUG_PRINT ("Target already assigned, using that one"); return true; } // If the AI touched you, you're a target if ( AI_TACTALERT ) { target = getTactEnt(); /** * If the entity that bumped the AI is an inanimate object, isEnemy will return 0, * so the AI will not try to attack an inanimate object. **/ if(isEnemy(target)) { setEnemy(target); //DEBUG_PRINT ("Set tactile alert enemy to entity " + target.getName() ); // set the bool back AI_TACTALERT = false; return true; } else { // They bumped into a non entity, so they should ignore it and not set an // alert from it. // set the bool back (man, this is annoying) AI_TACTALERT = false; return false; } } // If the AI saw you, you're a target else if( AI_VISALERT ) { target = findEnemy(0); if (target) { setEnemy(target); return true; } else { target = findEnemyAI(0); if (target) { setEnemy(target); return true; } } //DEBUG_PRINT ("No target"); return false; } /* * Sound is the only thing that does not guarantee combat * The AI will just stay in the highest alert state and * run at the sound location until it bumps into something * (No cheating here!) */ else if (AI_HEARDSOUND) { // do not set HEARDSOUND to false here because we still want to use it after exit return false; } // something weird must have happened return false; } /* ===================== ai_darkmod_base::setAlertPos( void ) SophisticatedZombie Note: Note that this method and setTarget are mutually exclusive as they clear flags that the other needs Set the location in space to investigate due to an alert. TODO: Add some randomness for sounds. AI shouldn't know exactly where it came from. It's hard to add randomness without risking going outside the map though, and that would be bad because the AI would just stand in place and not move at all if its destination is unreachable. I guess we could keep picking random points until we find one inside the map. This part of it won't be run every frame, only when it's getting the target for where to investigate, so that might be an okay algorithm. (random vector offsets, throwing out ones that go to an unreachable area) ===================== */ void ai_darkmod_base::setAlertPos( ) { // NOTE: If several alerts happen in the same frame, // the priority is tactile, visual, then sound // Stim bark type // 1.0 is saw // 2.0 is heard float stimBarkType = 0.0; //DEBUG_PRINT ("setAlertPos"); if( AI_TACTALERT ) { //DEBUG_PRINT ("AI_TACTALERT is signalled"); /** * FIX: They should not try to MOVE to the tactile alert position * because if it's above their head, they can't get to it and will * just run in circles. * * Instead, turn to this position manually, then set the search target * to the AI's own origin, to execute a search starting from where it's standing * * TODO later: Predict where the thrown object might have come from, and search * in that direction (requires a "directed search" algorithm) ***/ entity target = getTactEnt(); if(isEnemy(target)) { setEnemy(target); // execute the turn manually here turnToPos( target.getOrigin() ); m_alertType = "t"; m_alertPos = getOrigin(); m_alertRadius = TACTILE_ALERT_RADIUS; m_alertSearchVolume = TACTILE_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_TACTALERT = false; //DEBUG_PRINT ("Tactile alert pos " + m_alertPos); } else { DEBUG_PRINT ("No tactile alert entity was set"); } } else if( AI_VISALERT ) { m_alertType = "v"; m_alertPos = getVisDir(); m_alertRadius = VISUAL_ALERT_RADIUS; m_alertSearchVolume = VISUAL_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; stimBarkType = 1.0; //DEBUG_PRINT ("Visual alert pos " + m_alertPos); } else if( AI_HEARDSOUND ) { m_alertType = "s"; m_alertPos = getSndDir(); // Search within radius of stimulus that is 1/3 the distance from the // observer to the point at the time heard float distanceToStim = sys.vecLength(self.getOrigin() - m_alertPos); float searchVolModifier = (distanceToStim/3.0) / 200.0; if (searchVolModifier < 0.01) { searchVolModifier = 0.01; } m_alertRadius = AUDIO_ALERT_RADIUS; m_alertSearchVolume = AUDIO_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; m_alertSearchVolume = m_alertSearchVolume * searchVolModifier; AI_HEARDSOUND = false; stimBarkType = 2.0; //DEBUG_PRINT ("Audio alert pos " + m_alertPos); } // Handle stimulus "barks" if ( (stimBarkType >= 1.0) && ( (sys.getTime() - AI_timeOfLastStimulusBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) ) { AI_timeOfLastStimulusBark = sys.getTime(); // Check for any friends nearby we might want to talk to boolean b_friendNearby = false; if ( (sys.getTime() - stateOfMind_lastTimeFriendlyAISeen) <= (MAX_FRIEND_SIGHTING_SECONDS_FOR_ACCOMPANIED_ALERT_BARK) ) { DEBUG_PRINT ("Time since friend is " + (sys.getTime() - stateOfMind_lastTimeFriendlyAISeen)); b_friendNearby = true; } if ((stimBarkType >= 1.9) && (stimBarkType <= 2.1)) { // Play speech: heard something if (!b_friendNearby) { if (AI_AlertNum >= thresh_2) { bark( "snd_alert2h" ); } else if (AI_AlertNum >= thresh_1) { bark( "snd_alert1h" ); } } else { if (AI_AlertNum >= thresh_2) { bark( "snd_alert2ch" ); } else if (AI_AlertNum >= thresh_1) { bark( "snd_alert1ch" ); } } } else if ((stimBarkType >= 0.9) && (stimBarkType <= 1.1)) { // Play speech: saw something if (!b_friendNearby) { if (AI_AlertNum >= thresh_3) { bark ("snd_alert3s" ); } if (AI_AlertNum >= thresh_2) { bark( "snd_alert2s" ); } else if (AI_AlertNum >= thresh_1) { bark( "snd_alert1s" ); } } else { if (AI_AlertNum >= thresh_2) { bark( "snd_alert2cs" ); } else if (AI_AlertNum >= thresh_1) { bark( "snd_alert1cs" ); } } } } // Done stimulus barks } void ai_darkmod_base::PatrolBark( ) // Ported to Idle Bark Task { if ((sys.getTime() - m_patrolChatTimer) > m_patrolBarkRepeat ) { bark("snd_patrol_idle"); m_patrolChatTimer = sys.getTime(); } } boolean ai_darkmod_base::checkAlerted( ) { boolean returnval; if (AI_ALERTED) { if(AI_AlertNum >= thresh_1) { return true; } /* May want them to stay at somewhat suspicious level: SZ else { setAlertLevel(0); } */ } return false; } void ai_darkmod_base::DrawWeapon( ) { if (!m_DrawsWeapon || m_WeaponDrawn) return; // play anim, wait for it to finish if ( hasAnim( ANIMCHANNEL_TORSO, "draw" ) ) { playCustomAnim( "draw", 4, 4 ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) { waitFrame(); } finishAction( "draw" ); } finishAction( "draw" ); m_WeaponDrawn = 1; // TODO: Later handle drawing ranged weapons, for now assume it is always melee AI_bMeleeWeapDrawn = true; } void ai_darkmod_base::SheathWeapon( ) { if (!m_DrawsWeapon || !m_WeaponDrawn) return; // play anim, wait for it to finish if (hasAnim( ANIMCHANNEL_TORSO, "sheath" )) { playAnim( ANIMCHANNEL_TORSO, "sheath" ); playAnim( ANIMCHANNEL_LEGS, "sheath" ); while( !animDone( ANIMCHANNEL_TORSO, 4 ) || !animDone(ANIMCHANNEL_LEGS, 4) ) { waitFrame(); } finishAction( "sheath" ); } m_WeaponDrawn = 0; // TODO: Later handle drawing ranged weapons, for now assume it is always melee AI_bMeleeWeapDrawn = false; } /* ******************************************************************************************** * Communications reaction handlers ******************************************************************************************** */ void ai_darkmod_base::responseTo_Greeting ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; // If not too upset, look at them if (AI_AlertNum < thresh_2) { lookAt(issuingEntity, 3.0); // 3 seconds } // Have seen a friend stateOfMind_lastTimeFriendlyAISeen = sys.getTime(); // Greet the person back, but not as an event // TODO: This } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_FriendlyJoke ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; // Have seen a friend stateOfMind_lastTimeFriendlyAISeen = sys.getTime(); if (directObjectEntity == self) { DEBUG_PRINT ("Hah, yer no better!"); } else { DEBUG_PRINT ("Ha, yer right, they be an ass"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_Insult ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { if (directObjectEntity == self) { DEBUG_PRINT ("Same to you, buddy"); } else if (isEnemy (directObjectEntity)) { DEBUG_PRINT ("Hah!"); } else { DEBUG_PRINT ("I'm not gettin' involved"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_RequestForHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; if (isFriend(issuingEntity)) { // Do we already have a target we are dealing with? if (getEnemy()) { DEBUG_PRINT ("I'm too busy, I have a target!"); return; } DEBUG_PRINT("Ok, I'm helping you."); bark( "snd_assistFriend" ); setEnemy(directObjectEntity); subFrameTask_canSwitchState_initiateCombat(); } else if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_RequestForMissileHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { DEBUG_PRINT ("Somebody needs help with an opponent at range..."); // Respond if they are a friend and we have a ranged weapon if ( (isFriend(issuingEntity)) && (m_HasRangedWeapon) ) { // Do we already have a target we are dealing with? if (getEnemy()) { DEBUG_PRINT ("I'm too busy, I have a target!"); return; } DEBUG_PRINT ("I'll attack it with my ranged weapon!"); bark( "snd_assistFriend" ); setEnemy(directObjectEntity); subFrameTask_canSwitchState_initiateCombat(); } else { DEBUG_PRINT ("I don't have a ranged weapon or I am not getting involved"); if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_RequestForMeleeHelp ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; if (isFriend(issuingEntity)) { // Do we already have a target we are dealing with? if (getEnemy()) { DEBUG_PRINT ("I'm too busy, I have a target!"); return; } bark( "snd_assistFriend" ); setEnemy(directObjectEntity); subFrameTask_canSwitchState_initiateCombat(); } else if (AI_AlertNum < thresh_1 / 2.0) { DEBUG_PRINT ("I'm not fightin' for them!"); setAlertLevel(thresh_1 / 2.0); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_RequestForLight ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; DEBUG_PRINT ("I don't know how to bring light!"); } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_DetectedSomethingSuspicious ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { DEBUG_PRINT ("Somebody else noticed something suspicious..."); if ( getEnemy() ) { DEBUG_PRINT ("I'm too busy with my own target!"); return; } if (isFriend(issuingEntity)) { // If not already searching something else if ( ( sys.getTime() - currentHidingSpotListSearchStartTime) < currentHidingSpotListSearchMaxDuration ) { DEBUG_PRINT ("I'm too busy searching something else"); return; } DEBUG_PRINT ("They're my friend, I'll look too!"); // Get some search points from them. float numSpots = getSomeOfOtherEntitiesHidingSpotList (issuingEntity); if (numSpots >= 1) { // What is the distance to the friend. If it is greater than a certain amount, shout intention // to come help float distanceToIssuer = sys.vecLength( issuingEntity.getOrigin() - self.getOrigin() ); if (distanceToIssuer >= MIN_DISTANCE_TO_ISSUER_TO_SHOUT_COMING_TO_ASSISTANCE) { bark( "snd_assistFriend" ); } // If AI that called out has a higher alert num, raise ours // to match theres due to urgency in their voice float otherAlertNum; otherAlertNum = getAlertNumOfOtherAI (issuingEntity); DEBUG_PRINT ("The AI who noticed something has an alert num of " + otherAlertNum); if (otherAlertNum > AI_AlertNum) { setAlertLevel(otherAlertNum); } // Set time of search subFrameTask_determineSearchDuration(); // Set time search is starting currentHidingSpotListSearchStartTime = sys.getTime(); float spotIndex = 0; // Remember which hiding spot we have chosen at start firstChosenHidingSpotIndex = spotIndex; // Note currently chosen hiding spot currentChosenHidingSpotIndex = spotIndex; // Get location chosenHidingSpot = getNthHidingSpotLocation (spotIndex); numPossibleHidingSpotsSearched = 0; waitFrame(); b_searchingDueToCommunication = true; pushStateIfHigherPriority("task_SearchingHidingSpotList", PRIORITY_SEARCH_THINKING); return; } else { DEBUG_PRINT ("Hmpfh, no spots to help them with :("); } } else if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_DetectedEnemy ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { DEBUG_PRINT ("Somebody spotted an enemy... (" + directObjectEntity.getKey("name") + ")"); if (getEnemy()) { DEBUG_PRINT ("I'm too busy with my own target!"); return; } if (isFriend(issuingEntity) && (isEnemy(directObjectEntity)) ) { setAlertLevel(thresh_combat); DEBUG_PRINT ("They're my friend, I'll attack it too!"); m_alertPos = directObjectLocation; pushState("task_Investigate_Enemy_Position"); // greebo: Don't set the enemy yet, this would be cheating, as the AI would // run right to the current location of the player. /*setEnemy(directObjectEntity); subFrameTask_canSwitchState_initiateCombat();*/ } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_FollowOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { float notYet = 1.0; if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("But I don't know how to follow somebody!"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_GuardLocationOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("But I don't know how to guard a location!"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_GuardEntityOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("But I don't know how to guard an entity!"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_PatrolOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("But I don't know how to switch my patrol route!"); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_SearchOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("But I don't know how to switch my patrol route!"); // Set alert pos to the position we were ordered to search m_alertPos = directObjectLocation; chosenHidingSpot = directObjectLocation; numPossibleHidingSpotsSearched = 0; // Search that position pushStateIfHigherPriority("task_SearchingSpot", PRIORITY_SEARCHSPOT); } } /*-----------------------------------------------------------------------------------*/ void ai_darkmod_base::responseTo_AttackOrder ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { // Set this as our enemy and enter combat if ( (intendedRecipientEntity == self) && (isFriend(issuingEntity) ) ) { DEBUG_PRINT ("Yes sir! Attacking your specified target"); setEnemy(directObjectEntity); moveToEnemy(); subFrameTask_canSwitchState_initiateCombat(); } else if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } //--------------------------------------------------------------------------------------------- void ai_darkmod_base::responseTo_conveyWarningEvidenceOfIntruders ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { // Note: We deliberately don't care if the issuer is a friend or not float warningAmount = getVariableFromOtherAI (issuingEntity, "stateOfMind_count_evidenceOfIntruders"); if (stateOfMind_count_evidenceOfIntruders < warningAmount) { DEBUG_PRINT ("I've been warned about evidence of intruders."); stateOfMind_count_evidenceOfIntruders = warningAmount; if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } } //--------------------------------------------------------------------------------------------- void ai_darkmod_base::responseTo_conveyWarningItemsStolen ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { // Note: We deliberately don't care if the issuer is a friend or not if (!stateOfMind_b_itemsHaveBeenStolen) { DEBUG_PRINT ("I've been warned that items have been stolen."); stateOfMind_b_itemsHaveBeenStolen = true; if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } } //--------------------------------------------------------------------------------------------- void ai_darkmod_base::responseTo_conveyWarningEnemiesSeen ( entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { // Note: We deliberately don't care if the issuer is a friend or not if (!stateOfMind_b_enemiesHaveBeenSeen) { DEBUG_PRINT ("I've been warned that enemies have been seen."); stateOfMind_b_enemiesHaveBeenSeen = true; if (AI_AlertNum < thresh_1 / 2.0) { setAlertLevel(thresh_1 / 2.0); } } } /* ******************************************************************************************** * Communications stimulation response function ******************************************************************************************** */ void ai_darkmod_base::response_communication ( entity Result, float ThreadNum, float MessageType, entity issuingEntity, entity intendedRecipientEntity, entity directObjectEntity, vector directObjectLocation ) { // If dead or knocked out, can't respond // TODO: Probably faster to handle this inside the game dll rather than calling all the way // into the script if ((AI_DEAD) || (AI_KNOCKEDOUT)) { return; } //DEBUG_PRINT ("Inside ai_darkmod_base::response_communication, messageType = " + MessageType); if (MessageType == Greeting_MessageType) { responseTo_Greeting (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == FriendlyJoke_MessageType) { responseTo_FriendlyJoke (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == ConveyWarning_EvidenceOfIntruders_MessageType) { responseTo_conveyWarningEvidenceOfIntruders (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == ConveyWarning_ItemsHaveBeenStolen_MessageType) { responseTo_conveyWarningItemsStolen (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == ConveyWarning_EnemiesHaveBeenSeen_MessageType) { responseTo_conveyWarningEnemiesSeen (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == Insult_MessageType) { responseTo_Insult (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == RequestForHelp_MessageType) { responseTo_RequestForHelp (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == RequestForMissileHelp_MessageType) { responseTo_RequestForMissileHelp (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == RequestForMeleeHelp_MessageType) { responseTo_RequestForMeleeHelp (issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == RequestForLight_MessageType) { responseTo_RequestForLight(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == DetectedSomethingSuspicious_MessageType) { responseTo_DetectedSomethingSuspicious(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == DetectedEnemy_MessageType) { responseTo_DetectedEnemy(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == FollowOrder_MessageType) { responseTo_FollowOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == GuardLocationOrder_MessageType) { responseTo_GuardLocationOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == GuardEntityOrder_MessageType) { responseTo_GuardEntityOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == PatrolOrder_MessageType) { responseTo_PatrolOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == SearchOrder_MessageType) { responseTo_SearchOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } else if (MessageType == AttackOrder_MessageType) { responseTo_AttackOrder(issuingEntity, intendedRecipientEntity, directObjectEntity, directObjectLocation); } // Done //DEBUG_PRINT ("Exiting ai_darkmod_base::response_communication"); } /* ******************************************************************************************** * Visual contact stimulation response functions ******************************************************************************************** */ void ai_darkmod_base::response_visualStim_Blood (entity stimSource, float ThreadNum) { // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); // Vocalize that see something out of place DEBUG_PRINT ("Is that blood!"); if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS ) { m_timeOfLastVisualStimBark = sys.getTime(); bark( "snd_foundBlood" ); } // One more piece of evidence of something out of place stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 1.0; // Raise alert level if (AI_AlertNum < (thresh_combat-0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Do search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } // Do new reaction to stimulus b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // Switch state pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); // Done } //----------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_OpenDoor (entity stimSource, float ThreadNum) { // Is it supposed to be closed? if (stimSource.getIntKey (AIUSE_SHOULDBECLOSED_KEY) == 0) { return; } // Vocalize that see something out of place DEBUG_PRINT ("That door isn't supposed to be open!"); if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); bark( "snd_foundOpenDoor" ); } // One more piece of evidence of something out of place stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 1.0; // Raise alert level if (AI_AlertNum < (thresh_3-0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Do search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } // Do new reaction to stimulus b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // Switch state pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); // Done } //----------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_LightSource (entity stimSource, float ThreadNum) { // This variable tracks if we want to turn this light on boolean b_turnLightOn = false; boolean b_shouldBeOn = false; // If we are already in a device interaction state, we cannot enter another one if (deviceInteractionReturnState != "") { return; } // Is it on? if (stimSource.getLightLevel() > 0.0) { // We've seen this light and it is on. // Don't respond to it again until it changes state and clears // its ignore list stimSource.ResponseIgnore(STIM_VISUAL, self); return; } // What type of light is it? string lightType = stimSource.getKey (AIUSE_LIGHTTYPE_KEY); // Is it supposed to be on? b_shouldBeOn = (stimSource.getIntKey (AIUSE_SHOULDBEON_KEY) != 0); if (b_shouldBeOn) { // Vocalize that see something out of place because this light is supposed to be on DEBUG_PRINT ("Hey who turned of the light" + stimSource.getName()); if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); if (lightType == AIUSE_LIGHTTYPE_TORCH) { bark( "snd_foundTorchOut" ); } else { bark ("snd_foundLightsOff" ); } } // One more piece of evidence of something out of place stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 1.0; // Raise alert level if (AI_AlertNum < (thresh_3 - 0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Preapare search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } // Do new reaction to stimulus after relighting b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // We will be turning the light on b_turnLightOn = true; } // Check abilities to turn lights on if ( (lightType == AIUSE_LIGHTTYPE_TORCH) && (getIntKey("canLightTorches") <= 0.0) ) { b_turnLightOn = false; } else if (getIntKey("canOperateSwitchLights") <= 0.0) { b_turnLightOn = false; } else if ( (stateOfMind_b_enemiesHaveBeenSeen) || (stateOfMind_b_itemsHaveBeenStolen) || (stateOfMind_count_evidenceOfIntruders >= MIN_EVIDENCE_OF_INTRUDERS_TO_TURN_ON_ALL_LIGHTS) ) { DEBUG_PRINT ("For my safety, I should turn on the light " + stimSource.getName()); b_turnLightOn = true; } // Can't turn on a light if already involved in trying to turn on the same or a different light string currentStateName = getCurrentStateCallableName(); if ( (currentStateName == "task_LightTorch") || (currentStateName == "task_TurnOnSwitchLight") || (currentStateName == "task_TurnOnMagicLight") ) { b_turnLightOn = false; } // Turning the light on? if (b_turnLightOn) { // Does it have a switch? entity switchEntity; string switchEntityName = stimSource.getKey (AIUSE_LIGHTSWITCH_NAME_KEY); if (switchEntityName != "") { DEBUG_PRINT ("The light '" + stimSource.getName() + "' has a switch named '" + switchEntityName + "'" ); // It supposedly has a switch m_interactEnt = stimSource; // If it should be on, returning to state to start thinking about stimulus source, otherwise keep current state if (b_shouldBeOn) { deviceInteractionReturnState = STATE_REACTING_TO_STIMULUS; } else { deviceInteractionReturnState = getCurrentStateCallableName(); } pushStateIfHigherPriority("task_TurnOnSwitchLight", PRIORITY_MENIALTASK); } else if ((lightType == AIUSE_LIGHTTYPE_TORCH) || (lightType == AIUSE_LIGHTTYPE_GASLAMP)) { // Only don't relight if there is no cause for alarm if ( (!stateOfMind_b_enemiesHaveBeenSeen) && (stateOfMind_count_evidenceOfIntruders < MIN_EVIDENCE_OF_INTRUDERS_TO_TURN_ON_ALL_LIGHTS) && (sys.random(1.0) < 0.10) ) { if (b_shouldBeOn) { bark( "snd_noRelightTorch" ); } } else { m_interactEnt = stimSource; // If it should be on, returning to state to start thinking about stimulus source, otherwise keep current state if (b_shouldBeOn) { deviceInteractionReturnState = STATE_REACTING_TO_STIMULUS; } else { deviceInteractionReturnState = getCurrentStateCallableName(); } if (b_shouldBeOn) { if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); DEBUG_PRINT ("I'm going to relight the torch " + stimSource.getName()); bark( "snd_yesRelightTorch" ); } } pushStateIfHigherPriority("task_LightTorch", PRIORITY_MENIALTASK); } } else if (lightType == AIUSE_LIGHTTYPE_MAGIC) { m_interactEnt = stimSource; // If it should be on, returning to state to start thinking about stimulus source, otherwise keep current state if (b_shouldBeOn) { deviceInteractionReturnState = STATE_REACTING_TO_STIMULUS; } else { deviceInteractionReturnState = getCurrentStateCallableName(); } pushStateIfHigherPriority("task_TurnOnMagicLight", PRIORITY_MENIALTASK); } else { DEBUG_PRINT ("I don't know what to do with that light, I'm ignoring it"); stimSource.ResponseIgnore(STIM_VISUAL, self); } return; } // Done } //----------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_Weapon(entity stimSource, float ThreadNum) { // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); // Is it a friendly weapon? To find out we need to get its owner. entity objectOwner = stimSource; while (objectOwner) { if (isFriend(objectOwner)) { //DEBUG_PRINT ("Ignoring visual stim from weapon with friendly owner"); return; } objectOwner = objectOwner.getOwner(); } // Vocalize that see something out of place DEBUG_PRINT ("Hmm, that isn't right! A weapon!"); if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); bark( "snd_somethingSuspicious" ); } // TWO more piece of evidence of something out of place: A weapon is not a good thing stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 2.0; // Raise alert level if (AI_AlertNum < (thresh_combat-0.1)) { setAlertLevel(thresh_combat - 0.1); } m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Do search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; // Do new reaction to stimulus b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // Switch state DEBUG_PRINT("Test"); pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); //pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); // Done } //-------------------------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_DeadPerson(entity stimSource, float ThreadNum) { DEBUG_PRINT ("I see dead people!"); // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); // Three more piece of evidence of something out of place: A dead body is a REALLY bad thing stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 3.0; // Determine what to say string soundName; string personGender = stimSource.getKey (PERSONGENDER_KEY); if ( stimSource.getKey (PERSONTYPE_KEY) == self.getKey(PERSONTYPE_KEY) ) { soundName = "snd_foundComradeBody"; } else if (personGender == PERSONGENDER_FEMALE) { soundName = "snd_foundDeadFemale"; } else { soundName = "snd_foundDeadMale"; } // Speak a reaction if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); bark( soundName ); } // Raise alert level if (AI_AlertNum < (thresh_combat-0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Do search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } // Do new reaction to stimulus b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // Callback for objectives foundBody( stimSource ); // Switch state pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); // Done } //-------------------------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_UnconsciousPerson(entity stimSource, float ThreadNum) { DEBUG_PRINT ("I see unconscious people!"); // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); // Determine what to say string soundName; string personGender = stimSource.getKey (PERSONGENDER_KEY); if ( stimSource.getKey (PERSONTYPE_KEY) == self.getKey(PERSONTYPE_KEY) ) { soundName = "snd_foundComradeBody"; } else if (personGender == PERSONGENDER_FEMALE) { soundName = "snd_foundUnconsciousFemale"; } else { soundName = "snd_foundUnconsciousMale"; } // Speak a reaction //DEBUG_PRINT ("Sound name is " + soundName); if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); bark( soundName ); } // Raise alert level if (AI_AlertNum < (thresh_combat-0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Do search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } // Do new reaction to stimulus b_stimulusLocationItselfShouldBeSearched = true; b_searchingDueToCommunication = false; // Callback for objectives foundBody( stimSource ); // Switch state pushStateIfHigherPriority(STATE_REACTING_TO_STIMULUS, PRIORITY_REACTTOSTIMULUS); // Done } //-------------------------------------------------------------------------------------------------- boolean ai_darkmod_base::response_visualStim_Person_Friend(entity stimSource, float ThreadNum) { ai otherAI = stimSource; // Are they dead or unconscious? if (otherAI.AI_DEAD) { // React to finding body response_visualStim_DeadPerson(stimSource, ThreadNum); // Seen that return true; } else if (otherAI.AI_KNOCKEDOUT) { // React to finding unconscious person response_visualStim_UnconsciousPerson(stimSource, ThreadNum); // Seen that return true; } // Remember last time a friendly AI was seen stateOfMind_lastTimeFriendlyAISeen = sys.getTime(); // Get the type of person string personType = stimSource.getKey (PERSONTYPE_KEY); string soundName; // Issue a communication stim to the friend we spotted. // We can issue warnings, greetings, etc... if (stateOfMind_b_enemiesHaveBeenSeen) { if (getVariableFromOtherAI(stimSource, "stateOfMind_b_enemiesHaveBeenSeen") < 0.9) { DEBUG_PRINT ("I see a friend, I'm going to warn them that enemies have been seen"); issueCommunication_DOE ( ConveyWarning_EnemiesHaveBeenSeen_MessageType, TALK_STIM_RADIUS, stimSource, self.getOrigin() ); soundName = "snd_warnSawEnemy"; } } else if (stateOfMind_b_itemsHaveBeenStolen) { if (getVariableFromOtherAI(stimSource, "stateOfMind_b_itemsHaveBeenStolen") < 0.9) { DEBUG_PRINT ("I see a friend, I'm going to warn them that items have been stolen"); issueCommunication_DOE ( ConveyWarning_ItemsHaveBeenStolen_MessageType, TALK_STIM_RADIUS, stimSource, self.getOrigin() ); soundName = "snd_warnMissingItem"; } } else if (stateOfMind_count_evidenceOfIntruders >= MIN_EVIDENCE_OF_INTRUDERS_TO_COMMUNICATE_SUSPICION) { if ( getVariableFromOtherAI(stimSource, "stateOfMind_count_evidenceOfIntruders") < stateOfMind_count_evidenceOfIntruders) { DEBUG_PRINT ("I see a friend, I'm going to warn them of evidence I'm concerned about"); issueCommunication_DOE ( ConveyWarning_EvidenceOfIntruders_MessageType, TALK_STIM_RADIUS, stimSource, self.getOrigin() ); soundName = "snd_warnSawEvidence"; } } else { // Small chance of saying hello if (sys.random(1.0) > 0.025) { return false; } DEBUG_PRINT ("I see a friend, I'm going to say hello."); issueCommunication_DOE ( Greeting_MessageType, TALK_STIM_RADIUS, stimSource, self.getOrigin() ); if (personType == PERSONTYPE_NOBLE) { string personGender = stimSource.getKey (PERSONGENDER_KEY); if (personGender == PERSONGENDER_FEMALE) { DEBUG_PRINT ("proper greeting is 'Hello your ladyship.'"); soundName = "snd_greeting_nobleFemale"; } else { DEBUG_PRINT ("proper greeting is 'Hello your lordship.'"); soundName = "snd_greeting_nobleMale"; } } else if (personType == PERSONTYPE_PAGAN) { DEBUG_PRINT ("proper greeting is 'Hello your hippieness.'"); soundName = "snd_greeting_pagan"; } else if (personType == PERSONTYPE_MERC_PROGUARD) { DEBUG_PRINT ("proper greeting is 'Hello mercenary guard.'"); soundName = "snd_greeting_guard"; } else if (personType == PERSONTYPE_CITYWATCH) { DEBUG_PRINT ("proper greeting is 'Hello city watch.'"); soundName = "snd_greeting_guard"; } else if (personType == PERSONTYPE_BUILDER) { DEBUG_PRINT ("proper greeting is 'Hello builder.'"); soundName = "snd_greeting_builder"; } else { DEBUG_PRINT ("proper greeting is 'Hello generic person.'"); soundName = "snd_greeting_generic"; } } // Speak the chosen sound if ((soundName != "") && ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS)) { DEBUG_PRINT ("Speaking to friendly AI: sound name is " + soundName); m_timeOfLastVisualStimBark = sys.getTime(); bark( soundName ); } // Don't ignore in future return false; } //-------------------------------------------------------------------------------------------------- boolean ai_darkmod_base::response_visualStim_Person_Neutral(entity stimSource, float ThreadNum) { ai otherAI = stimSource; // Are they dead or unconscious? if (otherAI.AI_DEAD) { // React to finding body response_visualStim_DeadPerson(stimSource, ThreadNum); // Seen that return true; } else if (otherAI.AI_KNOCKEDOUT) { // React to finding unconscious person response_visualStim_UnconsciousPerson(stimSource, ThreadNum); // Seen that return true; } // Seen that return true; } //-------------------------------------------------------------------------------------------------- boolean ai_darkmod_base::response_visualStim_Person_Enemy(entity stimSource, float ThreadNum) { ai otherAI = stimSource; // Are they dead or unconscious? if (otherAI.AI_DEAD) { // TODO: What to do here is tricky, its context sensitive. // Seen that return true; } else if (otherAI.AI_KNOCKEDOUT) { // TODO: What to do here is tricky, its context sensitive. // Seen that return true; } DEBUG_PRINT ("I see an enemy!"); setEnemy(stimSource); AI_VISALERT = true; subFrameTask_canSwitchState_initiateCombat(); // An enemy should not be ignored in the future return false; } //-------------------------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_Person(entity stimSource, float ThreadNum) { boolean b_ignoreStimulusFromNowOn = true; // Branch to handle enemies, friends, and neutrals if (isEnemy(stimSource)) { b_ignoreStimulusFromNowOn = response_visualStim_Person_Enemy(stimSource, ThreadNum); } else if (isFriend (stimSource)) { b_ignoreStimulusFromNowOn = response_visualStim_Person_Friend(stimSource, ThreadNum); } else { b_ignoreStimulusFromNowOn = response_visualStim_Person_Neutral(stimSource, ThreadNum); } if (b_ignoreStimulusFromNowOn) { // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); } } //-------------------------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim_MissingItem (entity stimSource, float ThreadNum) { // We've seen this object, don't respond to it again stimSource.ResponseIgnore(STIM_VISUAL, self); // Can we notice missing items if (getIntKey ("chanceNoticeMissingItem") <= 0.0) { return; } // Does it belong to a friendly team if (!isFriend(stimSource)) { // Its not something we know about DEBUG_PRINT ("The missing item wasn't on my team"); return; } DEBUG_PRINT ("Something is missing from over there!"); // Determine what to say string soundName = "snd_foundMissingItem"; if ( (sys.getTime() - m_timeOfLastVisualStimBark) >= MINIMUM_SECONDS_BETWEEN_STIMULUS_BARKS) { m_timeOfLastVisualStimBark = sys.getTime(); if (soundName) { bark( soundName ); } } // One more piece of evidence of something out of place stateOfMind_b_itemsHaveBeenStolen = true; stateOfMind_count_evidenceOfIntruders = stateOfMind_count_evidenceOfIntruders + 1.0; // Raise alert level if (AI_AlertNum < (thresh_3 - 0.1)) { m_alertPos = stimSource.getOrigin(); m_alertType = "v"; // visual // Preapare search as if there is an enemy that has escaped m_alertRadius = LOST_ENEMY_ALERT_RADIUS; m_alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; m_alertSearchExclusionVolume = '0 0 0'; AI_VISALERT = false; setAlertLevel(thresh_combat - 0.1); } } //-------------------------------------------------------------------------------------------------- void ai_darkmod_base::response_visualStim(entity stimSource, float ThreadNum) { // If dead or knocked out, can't respond // TODO: Probably faster to handle this inside the game dll rather than calling all the way // into the script if ((AI_DEAD) || (AI_KNOCKEDOUT)) { return; } // Only respond if we aren't busy with combat if (getEnemy()) { return; } // Get AI use of the stim string aiUse = stimSource.getKey("AIUse"); //DEBUG_PRINT ("AIUse type of seen entity is " + aiUse); // Only respond if we can actually see it if (aiUse == AIUSE_LIGHTSOURCE) { // Special case for lights, we know it is off if there is no light. Also we can notice it // if we are not looking right at it. if (!canSeeExt(stimSource, 0, 0 )) { return; } } else if (!canSee (stimSource)) { //DEBUG_PRINT ("I can't see the " + aiUse); return; } // Random chance float chance = sys.random(1.0); float chanceToNotice = 0.0; if (aiUse == AIUSE_WEAPON) { chanceToNotice = getIntKey ("chanceNoticeWeapon"); if (chance < chanceToNotice) { response_visualStim_Weapon (stimSource, ThreadNum); } } else if (aiUse == AIUSE_PERSON) { chanceToNotice = getIntKey ("chanceNoticePerson"); if (chance < chanceToNotice) { response_visualStim_Person (stimSource, ThreadNum); } } else if (aiUse == AIUSE_BLOOD_EVIDENCE) { chanceToNotice = getIntKey ("chanceNoticeBlood"); if (chance < chanceToNotice) { response_visualStim_Blood (stimSource, ThreadNum); } } else if (aiUse == AIUSE_LIGHTSOURCE) { chanceToNotice = getIntKey ("chanceNoticeLight"); if (chance < chanceToNotice) { response_visualStim_LightSource (stimSource, ThreadNum); } } else if (aiUse == AIUSE_MISSING_ITEM_MARKER) { chanceToNotice = getIntKey ("chanceNoticeMissingItem"); if (chance < chanceToNotice) { response_visualStim_MissingItem (stimSource, ThreadNum); } } else if (aiUse == AIUSE_DOOR) { chanceToNotice = getIntKey ("chanceNoticeDoor"); if (chance < chanceToNotice) { response_visualStim_OpenDoor (stimSource, ThreadNum); } } // TODO: Add other AI visual stimulus interactions here } #endif //__AI_DARKMOD_BASE__