/*/////////////////////// [Include - Heartbeat] //////////////////////////////// Filename: J_INC_Heartbeat ///////////////////////// [Include - Heartbeat] //////////////////////////////// This contains any heartbeat function calls. Note that the heartbeat uses ExecuteScript for larget behaviours that are better split up so the heartbeat is as tiny as possible. ///////////////////////// [History] //////////////////////////////////////////// 1.3 - After Beta - Added 1.4 - TO DO - Add in some function (see rest script) that resets if we are not in combat - Some more of the things we should do even if interrupted not the heartbeat. - Have moved "after combat searching" into here. It isn't long - but it is more reliable. The special action is cancled if there is combat going on, of course. ///////////////////////// [Workings] /////////////////////////////////////////// This is included in nw_c2_default1 and J_AI_OnHeartbeat. Contains things like in J_INC_OTHER_AI, but only for the heartbeat event. Keeps it cleaner to read. ///////////////////////// [Arguments] ////////////////////////////////////////// Arguments: N/A ///////////////////////// [Include - Heartbeat] //////////////////////////////*/ #include "J_INC_CONSTANTS" // Bioware walk waypoints condition name const string sWalkwayVarname = "NW_WALK_CONDITION"; // Walk waypoint constant set in the SoU waypoint include const int NW_WALK_FLAG_CONSTANT = 0x00000002; // Checks: // * No valid location // * Petrified, paralised, ETC. // Note: If sleep is found, it may apply Zzzz randomly, as well as stopping. int JumpOutOfHeartBeat(); // This checks fleeing, door bashing and so on, to stop the heartbeat // and perform the override special action, rather then run normal behaviour. int PerformSpecialAction(); // Get whether the condition is set // * Bioware SoU Waypoint call. int GetWalkCondition(int nCondition, object oCreature=OBJECT_SELF); // Cast fleeing spells. // - Invisiblity (best) // - Haste/Expeditious Retreat void ActionCastFleeingSpells(); // Cast fleeing spells. // - Invisiblity (best) // - Haste/Expeditious Retreat void ActionCastMoveToCombatSpells(); // Attempt to cast nSpell. TRUE if true. // Searching and fleeing spells use this. int HeartbeatSpellCast(int nSpell); // Used in Search(). This apply Trueseeing, See invisibility, or Invisiblity purge // if we have neither of the 3 on us. void SearchSpells(); // Returns TRUE if any of the animation settings are on. int GetHasValidAnimations(); // Checks: // * No valid location // * Petrified, paralised, ETC. // Note: If sleep is found, it may apply Zzzz randomly, as well as stopping. int JumpOutOfHeartBeat() { // What to return int bReturn = FALSE; // Checks: // * No valid location // * Petrified, paralised, ETC. // Note: If sleep is found, it may apply Zzzz randomly, as well as stopping. // Effect checking effect eCheck = GetFirstEffect(OBJECT_SELF); int nEffectType; while(GetIsEffectValid(eCheck) && bReturn == FALSE) { nEffectType = GetEffectType(eCheck); // Sleep is special if(nEffectType == EFFECT_TYPE_SLEEP) { bReturn = 2;// This immediantly breaks. } // ALL these stop heartbeat. else if(nEffectType == EFFECT_TYPE_PARALYZE || nEffectType == EFFECT_TYPE_STUNNED || nEffectType == EFFECT_TYPE_FRIGHTENED || /* Removed sleep above */ nEffectType == EFFECT_TYPE_TURNED || nEffectType == EFFECT_TYPE_PETRIFY || nEffectType == EFFECT_TYPE_DAZED || nEffectType == EFFECT_TYPE_TIMESTOP || nEffectType == EFFECT_TYPE_DISAPPEARAPPEAR || nEffectType == EFFECT_TYPE_CHARMED || nEffectType == EFFECT_TYPE_DOMINATED || nEffectType == EFFECT_TYPE_CONFUSED) { bReturn = 1;// 1 = No Zzz. We continue to check for Zzz as well. } eCheck = GetNextEffect(OBJECT_SELF); } // Do we fire the heartbeat event? if(bReturn != FALSE) { // If it is sleep... Zzzzz sometimes. if(bReturn == 2 && d6() == 1) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SLEEP), OBJECT_SELF); } // Fire event 1001 FireUserEvent(AI_FLAG_UDE_HEARTBEAT_EVENT, EVENT_HEARTBEAT_EVENT); } return bReturn; } // This checks fleeing, door bashing and so on, to stop the heartbeat // and perform the override special action, rather then run normal behaviour. int PerformSpecialAction() { int nAction = GetCurrentSetAction(); object oTarget = GetAttackTarget(); object oRunTarget; switch(nAction) { // - Leader has made me a runner. I must run to a nearby group calling // for help to get more men case AI_SPECIAL_ACTIONS_ME_RUNNER: { oRunTarget = GetAIObject(AI_RUNNER_TARGET); if(GetIsObjectValid(oRunTarget)) { if(GetObjectSeen(oRunTarget)) { // Stop thinking we are a runner if we can see the run target ResetCurrentAction(); AISpeakString(AI_SHOUT_HELP_MY_FRIEND); return FALSE; } else { // Else run to them if(GetObjectHeard(oRunTarget)) { AISpeakString(AI_SHOUT_HELP_MY_FRIEND); } ClearAllActions(); ActionMoveToObject(oRunTarget, TRUE); return TRUE; } } } break; // - I am fleeing. case AI_SPECIAL_ACTIONS_FLEE: { oRunTarget = GetAIObject(AI_FLEE_TO); if(GetIsObjectValid(oRunTarget)) { // If they are a leader, and seen, and they are running, we // obviously follow only. if(GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_GROUP_LEADER, AI_OTHER_COMBAT_MASTER, oRunTarget) || GetLocalInt(oRunTarget, AI_CURRENT_ACTION) == AI_SPECIAL_ACTIONS_FLEE) { ClearAllActions(); // New - cast fleeing spells. Important (and only used // at higher intelligence) things like Expeditious retreat. // - Only used once - one invisibility or haste. Deleted above. ActionCastFleeingSpells(); ActionForceFollowObject(oRunTarget, 3.0); } else if(GetObjectSeen(oRunTarget)) { // If we see the flee target, reset targets ResetCurrentAction(); // We will delete the local int (set to TRUE) which we // stopped fleeing spells from being used DeleteAIInteger(AI_HEARTBEAT_FLEE_SPELLS); // Speak to allies to come :-) AISpeakString(AI_SHOUT_HELP_MY_FRIEND); // Also reset visual effect RemoveFleeingVisual(); // And attack/heal self ClearAllActions(); DetermineCombatRound(); // Return TRUE, we attacked return TRUE; } else { // Else flee! if(GetObjectHeard(oRunTarget)) { AISpeakString(AI_SHOUT_HELP_MY_FRIEND); } ClearAllActions(); // New - cast fleeing spells. Important (and only used // at higher intelligence) things like Expeditious retreat. // - Only used once - one invisibility or haste. Deleted above. ActionCastFleeingSpells(); ActionMoveToObject(oRunTarget, TRUE); return TRUE; } } else { // Check if we have bad intellgence, if we have, we will run away // from the nearest enemy we can see or hear. if(GetAIInteger(AI_INTELLIGENCE) <= 3) { oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE); if(!GetIsObjectValid(oRunTarget)) { oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD, CREATURE_TYPE_IS_ALIVE, TRUE); if(!GetIsObjectValid(oRunTarget)) { oRunTarget = GetLastHostileActor(); if(!GetIsObjectValid(oRunTarget) || GetIsDead(oRunTarget)) { // If we do not have anyone to run from, stop ResetCurrentAction(); // Speak to allies to come :-) AISpeakString(AI_SHOUT_HELP_MY_FRIEND); // Also reset visual effect RemoveFleeingVisual(); // And attack/heal self ClearAllActions(); DetermineCombatRound(); // Return TRUE, we attacked return TRUE; } } } // Run from enemy (1.4: Was oTarget, now oRunTarget) ClearAllActions(); ActionMoveAwayFromObject(oRunTarget, TRUE, 50.0); return TRUE; } // If we see the flee target, reset targets ResetCurrentAction(); // Speak to allies to come :-) AISpeakString(AI_SHOUT_HELP_MY_FRIEND); // Also reset visual effect RemoveFleeingVisual(); // And attack/heal self ClearAllActions(); DetermineCombatRound(); // Return TRUE, we attacked/healed return TRUE; } } break; // If this is set, we are usually in combat - and must move out of an AOE. case AI_SPECIAL_ACTIONS_MOVE_OUT_OF_AOE: { // We must be X distance away from a cirtain AOE, if we are not, we // move. oRunTarget = GetAIObject(AI_AOE_FLEE_FROM); // If not valid, or already far enough away, delete special action // and return false. if(!GetIsObjectValid(oRunTarget) || GetLocalFloat(OBJECT_SELF, AI_AOE_FLEE_FROM_RANGE) < GetDistanceToObject(oRunTarget)) { ResetCurrentAction(); return FALSE; } else { // Valid and still in range // - Run away ClearAllActions(); ActionMoveAwayFromLocation(GetLocation(oRunTarget), TRUE, GetLocalFloat(OBJECT_SELF, AI_AOE_FLEE_FROM_RANGE)); return TRUE; } } break; // If this is the one, we will search around for enemies - usually done // at the end of a combat round, it is more reliable here. case AI_SPECIAL_ACTIONS_SEARCH_AROUND: { // If we are in combat, delete this special thing, and return FALSE if(GetIsObjectValid(GetAttemptedSpellTarget()) || GetIsObjectValid(GetAttemptedAttackTarget()) || GetIsObjectValid(GetAttackTarget())) { // Reset, and return FALSE. ResetCurrentAction(); return FALSE; } // Added this so special actions do not get ignored (EG: healkitting) // It will not do anything, but no heartbeat will be performed. These // kind of actions happen at the end of combat (healing self of damage ETC) // So, basically, will keep in mind it's still searching, but will leave // it until no busy actions are being done. else if(GetIsBusyWithAction()) { return TRUE; } // We search for a cirtain number of rounds, set in the generic AI // file, when we first start searching, or restart even. The generic // AI will not actually do search actions, and if it finds no enemy, // will probably just increase the integer to do more search rounds. // * Will be intelligence + 2 to start. int nRoundsRemaining = GetAIInteger(AI_SEARCH_ROUNDS_REMAINING); // Decrease rounds remaining nRoundsRemaining--; // Set new one onto us to use next time SetAIInteger(AI_SEARCH_ROUNDS_REMAINING, nRoundsRemaining); // * Note: If nRoundsRemaining is 0 at the end of this function, we // will remove this action as the current special one. // Get the target to move to/around // * Can be invalid, but usually the creature we just killed or noticed // lying on the ground. object oTarget = GetAIObject(AI_SEARCH_TARGET); // Stop now (Small amounts of movement each time seem more cautious) ClearAllActions(); // Check some spells. Cast one if we have no true seeing ETC. SearchSpells(); // Stealth/search. int bStealth = GetStealthMode(OBJECT_SELF); int bSearch = GetDetectMode(OBJECT_SELF); // We perfere to hide again if we search if set to...sneaky! if(GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_FORCE_HIDING, AI_OTHER_COMBAT_MASTER)) { if(bStealth != STEALTH_MODE_ACTIVATED) { SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE); } } else { // If we are hiding, stop to search (we shouldn't be - who knows?) if(bStealth == STEALTH_MODE_ACTIVATED) { SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, FALSE); } // And search! if(bSearch != DETECT_MODE_ACTIVE && !GetHasFeat(FEAT_KEEN_SENSE)) { SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, TRUE); } } // We check around the target, if there is one. if(GetIsObjectValid(oTarget)) { // Move to the location of oTarget ActionMoveToLocation(GetLocation(oTarget)); // If it is a chest ETC. We close it. if(GetIsOpen(oTarget)) { if(GetObjectType(oTarget) == OBJECT_TYPE_DOOR) { ActionCloseDoor(oTarget); } else { // Close it ActionDoCommand(DoPlaceableObjectAction(oTarget, PLACEABLE_ACTION_USE)); } } } // We will get nearest enemy at the very least else { // Use nearest heard object oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD); if(GetIsObjectValid(oEnemy)) { // Move to location ActionMoveToLocation(GetLocation(oEnemy)); } } // Note: Here, we will return to spawn location after moving to the // object, if it is a valid setting, else we do the normal randomwalk if(GetSpawnInCondition(AI_FLAG_OTHER_RETURN_TO_SPAWN_LOCATION, AI_OTHER_MASTER)) { ActionMoveToLocation(GetAILocation(AI_RETURN_TO_POINT)); } else { // 72: "[Search] Searching, No one to attack. [Rounds Remaining] " + IntToString(nRoundsRemaining) + ". [Possible target] " + GetName(oTarget) DebugActionSpeakByInt(72, oTarget, nRoundsRemaining); // Randomly walk. ActionRandomWalk(); } // If we have 0 rounds left of searching time, we turn of this special // action, walk waypoints, and probably rest. if(nRoundsRemaining == 0) { // Rest after combat? if(GetSpawnInCondition(AI_FLAG_OTHER_REST_AFTER_COMBAT, AI_OTHER_MASTER)) { // 71: "[Search] Resting" DebugActionSpeakByInt(71); // Yes - we use ActionRest(). It can possibly still fail if // enemies are still around! ActionRest(); ActionWait(1.0); } else { // Else, just execute Walk Waypoints ExecuteScript(FILE_WALK_WAYPOINTS, OBJECT_SELF); } // Delete this special action ResetCurrentAction(); } // If we havn't bailed out early and returned FALSE (do normal hb) we // will return TRUE, we have something to do at least. return TRUE; } break; // Move to combat - we buff (and only buff again after 1 minute of running) // and either follow the person who wants us to help them, or we will run to the // location set. // * Set only by "AI_HELP_MY_FRIEND_CONSTANT" at the moment. case AI_SPECIAL_ACTIONS_MOVE_TO_COMBAT: { // We get a location to move to first location lTarget = GetAILocation(AI_MOVE_TO_COMBAT_LOCATION); object oObject = GetAreaFromLocation(lTarget); // Check if the location is valid if(GetIsObjectValid(oObject) && GetArea(OBJECT_SELF) == oObject) { // Just move, rapidly, to lTarget. ClearAllActions(); // Buff up an action ActionCastMoveToCombatSpells(); // Move (fast) to that location ActionMoveToLocation(lTarget, TRUE); // If we see/hear combat, we'll attack return TRUE; } else { // Get who we should "follow" or move to. oObject = GetAIObject(AI_MOVE_TO_COMBAT_OBJECT); if(GetIsObjectValid(oObject)) { // Just move, rapidly, to oTarget. It isn't "real" following, // but paced. Means it looks OK. Need to test - but should be OK. ClearAllActions(); // Buff up an action ActionCastMoveToCombatSpells(); // Move (fast) to that location ActionMoveToObject(oTarget, TRUE); // If we see/hear combat, we'll attack return TRUE; } else { // Remove the special action and return FALSE ResetCurrentAction(); return FALSE; } } } break; } // Return false to carry on a normal heartbeat return FALSE; } // Get whether the condition is set // * Bioware SoU Waypoint call. int GetWalkCondition(int nCondition, object oCreature=OBJECT_SELF) { return (GetLocalInt(oCreature, sWalkwayVarname) & nCondition); } // Cast fleeing spells. // - Invisiblity (best) // - Haste/Expeditious Retreat void ActionCastFleeingSpells() { // Not got local if(GetAIInteger(AI_HEARTBEAT_FLEE_SPELLS)) return; // Set local SetAIInteger(AI_HEARTBEAT_FLEE_SPELLS, TRUE); // Invisibilities if(HeartbeatSpellCast(SPELL_IMPROVED_INVISIBILITY)) return; if(HeartbeatSpellCast(SPELL_INVISIBILITY)) return; // Haste if(HeartbeatSpellCast(SPELL_MASS_HASTE)) return; if(HeartbeatSpellCast(SPELL_HASTE)) return; if(HeartbeatSpellCast(SPELL_EXPEDITIOUS_RETREAT)) return; } // Cast fleeing spells. // - Invisiblity (best) // - Haste/Expeditious Retreat void ActionCastMoveToCombatSpells() { // Timer to stop too many spells at once if(GetLocalTimer(AI_TIMER_MOVE_TO_COMBAT_BUFF)) return; // We first will cast a preperation spell before jumping in! // This is used once per minute. SetLocalTimer(AI_TIMER_MOVE_TO_COMBAT_BUFF, 60.0); // We possibly cast a few spell first - stoneskin range, see // invisible range, and invisibility range. // Protection things // * Cast 1 spell! if(HeartbeatSpellCast(SPELL_PREMONITION)) return; if(HeartbeatSpellCast(SPELL_GREATER_STONESKIN)) return; if(HeartbeatSpellCast(SPELL_STONESKIN)) return; // Invisibility range if(HeartbeatSpellCast(SPELL_ETHEREALNESS)) return; if(HeartbeatSpellCast(SPELL_IMPROVED_INVISIBILITY)) return; if(HeartbeatSpellCast(SPELL_INVISIBILITY_SPHERE)) return; if(HeartbeatSpellCast(SPELL_INVISIBILITY)) return; // See invisible things if(HeartbeatSpellCast(SPELL_TRUE_SEEING)) return; if(HeartbeatSpellCast(SPELL_SEE_INVISIBILITY)) return; // Stealth! Only if we are good at it, of course. // Spawn in conditions for it if(!GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_NO_HIDING, AI_OTHER_COMBAT_MASTER)) { // Need skill or force on if((GetSkillRank(SKILL_HIDE) - 4 >= GetHitDice(OBJECT_SELF)) || GetSpawnInCondition(AI_FLAG_OTHER_COMBAT_FORCE_HIDING, AI_OTHER_COMBAT_MASTER)) { // Use hide SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE); return; } } } // Used in Search(). This apply Trueseeing, See invisibility, or Invisiblity purge // if we have neither of the 3 on us. void SearchSpells() { effect eCheck = GetFirstEffect(OBJECT_SELF); int nEffectType, bBreak; while(GetIsEffectValid(eCheck) && bBreak == FALSE) { nEffectType = GetEffectType(eCheck); if(nEffectType == EFFECT_TYPE_TRUESEEING || nEffectType == EFFECT_TYPE_SEEINVISIBLE) { bBreak = TRUE; } eCheck = GetNextEffect(OBJECT_SELF); } // We have effects, stop. if(bBreak == TRUE || GetHasSpellEffect(SPELL_INVISIBILITY_PURGE)) { return; } // Else we apply the best spell we have. if(HeartbeatSpellCast(SPELL_TRUE_SEEING)) return; if(HeartbeatSpellCast(SPELL_SEE_INVISIBILITY)) return; if(HeartbeatSpellCast(SPELL_INVISIBILITY_PURGE)) return; } // Attempt to cast nSpell. TRUE if true. int HeartbeatSpellCast(int nSpell) { // 1.4: added check to see if has effect already if(GetHasSpell(nSpell) && !GetHasSpellEffect(nSpell, OBJECT_SELF)) { ActionCastSpellAtObject(nSpell, OBJECT_SELF); return TRUE; } return FALSE; } // Returns TRUE if any of the animation settings are on. int GetHasValidAnimations() { int nCheck = GetLocalInt(OBJECT_SELF, NW_GENERIC_MASTER); if((nCheck & NW_FLAG_AMBIENT_ANIMATIONS) || (nCheck & NW_FLAG_AMBIENT_ANIMATIONS_AVIAN) || (nCheck & NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS)) { return TRUE; } return FALSE; }