2021-08-30 17:31:44 -04:00
|
|
|
/*/////////////////////// [Include - Heartbeat] ////////////////////////////////
|
|
|
|
Filename: J_INC_Heartbeat
|
|
|
|
///////////////////////// [Include - Heartbeat] ////////////////////////////////
|
2021-08-29 23:34:48 -04:00
|
|
|
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.
|
2021-08-30 17:31:44 -04:00
|
|
|
///////////////////////// [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.
|
2021-08-29 23:34:48 -04:00
|
|
|
|
2021-08-30 17:31:44 -04:00
|
|
|
- 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] //////////////////////////////////////////
|
2021-08-29 23:34:48 -04:00
|
|
|
Arguments: N/A
|
2021-08-30 17:31:44 -04:00
|
|
|
///////////////////////// [Include - Heartbeat] //////////////////////////////*/
|
2021-08-29 23:34:48 -04:00
|
|
|
|
|
|
|
#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();
|
2021-08-30 17:31:44 -04:00
|
|
|
// Cast fleeing spells.
|
|
|
|
// - Invisiblity (best)
|
|
|
|
// - Haste/Expeditious Retreat
|
|
|
|
void ActionCastMoveToCombatSpells();
|
2021-08-29 23:34:48 -04:00
|
|
|
|
2021-08-30 17:31:44 -04:00
|
|
|
// Attempt to cast nSpell. TRUE if true.
|
|
|
|
// Searching and fleeing spells use this.
|
|
|
|
int HeartbeatSpellCast(int nSpell);
|
2021-08-29 23:34:48 -04:00
|
|
|
|
2021-08-30 17:31:44 -04:00
|
|
|
// 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.
|
2021-08-29 23:34:48 -04:00
|
|
|
int JumpOutOfHeartBeat()
|
|
|
|
{
|
|
|
|
// What to return
|
2021-08-30 17:31:44 -04:00
|
|
|
int bReturn = FALSE;
|
2021-08-29 23:34:48 -04:00
|
|
|
// 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);
|
2021-08-30 17:31:44 -04:00
|
|
|
int nEffectType;
|
|
|
|
while(GetIsEffectValid(eCheck) && bReturn == FALSE)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
nEffectType = GetEffectType(eCheck);
|
2021-08-29 23:34:48 -04:00
|
|
|
// Sleep is special
|
2021-08-30 17:31:44 -04:00
|
|
|
if(nEffectType == EFFECT_TYPE_SLEEP)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
bReturn = 2;// This immediantly breaks.
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
// ALL these stop heartbeat.
|
2021-08-30 17:31:44 -04:00
|
|
|
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)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
bReturn = 1;// 1 = No Zzz. We continue to check for Zzz as well.
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
eCheck = GetNextEffect(OBJECT_SELF);
|
|
|
|
}
|
|
|
|
// Do we fire the heartbeat event?
|
2021-08-30 17:31:44 -04:00
|
|
|
if(bReturn != FALSE)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
|
|
|
// If it is sleep... Zzzzz sometimes.
|
2021-08-30 17:31:44 -04:00
|
|
|
if(bReturn == 2 && d6() == 1)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
|
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT,
|
|
|
|
EffectVisualEffect(VFX_IMP_SLEEP),
|
|
|
|
OBJECT_SELF);
|
|
|
|
}
|
2021-08-30 17:31:44 -04:00
|
|
|
// Fire event 1001
|
|
|
|
FireUserEvent(AI_FLAG_UDE_HEARTBEAT_EVENT, EVENT_HEARTBEAT_EVENT);
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
2021-08-30 17:31:44 -04:00
|
|
|
return bReturn;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
int nAction = GetCurrentSetAction();
|
2021-08-29 23:34:48 -04:00
|
|
|
object oTarget = GetAttackTarget();
|
|
|
|
object oRunTarget;
|
2021-08-30 17:31:44 -04:00
|
|
|
switch(nAction)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
|
|
|
// - 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();
|
2021-08-30 17:31:44 -04:00
|
|
|
AISpeakString(AI_SHOUT_HELP_MY_FRIEND);
|
2021-08-29 23:34:48 -04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Else run to them
|
|
|
|
if(GetObjectHeard(oRunTarget))
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
AISpeakString(AI_SHOUT_HELP_MY_FRIEND);
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
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();
|
2021-08-30 17:31:44 -04:00
|
|
|
ActionForceFollowObject(oRunTarget, 3.0);
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
else if(GetObjectSeen(oRunTarget))
|
|
|
|
{
|
|
|
|
// If we see the flee target, reset targets
|
|
|
|
ResetCurrentAction();
|
2021-08-30 17:31:44 -04:00
|
|
|
|
2021-08-29 23:34:48 -04:00
|
|
|
// We will delete the local int (set to TRUE) which we
|
2021-08-30 17:31:44 -04:00
|
|
|
// stopped fleeing spells from being used
|
2021-08-29 23:34:48 -04:00
|
|
|
DeleteAIInteger(AI_HEARTBEAT_FLEE_SPELLS);
|
|
|
|
// Speak to allies to come :-)
|
2021-08-30 17:31:44 -04:00
|
|
|
AISpeakString(AI_SHOUT_HELP_MY_FRIEND);
|
|
|
|
|
|
|
|
// Also reset visual effect
|
|
|
|
RemoveFleeingVisual();
|
|
|
|
|
|
|
|
// And attack/heal self
|
|
|
|
ClearAllActions();
|
|
|
|
DetermineCombatRound();
|
|
|
|
// Return TRUE, we attacked
|
|
|
|
return TRUE;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Else flee!
|
|
|
|
if(GetObjectHeard(oRunTarget))
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
AISpeakString(AI_SHOUT_HELP_MY_FRIEND);
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
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
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
// 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)
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
|
2021-08-29 23:34:48 -04:00
|
|
|
if(!GetIsObjectValid(oRunTarget))
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
oRunTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD, CREATURE_TYPE_IS_ALIVE, TRUE);
|
2021-08-29 23:34:48 -04:00
|
|
|
if(!GetIsObjectValid(oRunTarget))
|
|
|
|
{
|
|
|
|
oRunTarget = GetLastHostileActor();
|
|
|
|
if(!GetIsObjectValid(oRunTarget) || GetIsDead(oRunTarget))
|
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
// If we do not have anyone to run from, stop
|
2021-08-29 23:34:48 -04:00
|
|
|
ResetCurrentAction();
|
2021-08-30 17:31:44 -04:00
|
|
|
// 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;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-30 17:31:44 -04:00
|
|
|
// Run from enemy (1.4: Was oTarget, now oRunTarget)
|
2021-08-29 23:34:48 -04:00
|
|
|
ClearAllActions();
|
2021-08-30 17:31:44 -04:00
|
|
|
ActionMoveAwayFromObject(oRunTarget, TRUE, 50.0);
|
2021-08-29 23:34:48 -04:00
|
|
|
return TRUE;
|
|
|
|
}
|
2021-08-30 17:31:44 -04:00
|
|
|
// If we see the flee target, reset targets
|
2021-08-29 23:34:48 -04:00
|
|
|
ResetCurrentAction();
|
2021-08-30 17:31:44 -04:00
|
|
|
// 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;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2021-08-30 17:31:44 -04:00
|
|
|
// If this is set, we are usually in combat - and must move out of an AOE.
|
2021-08-29 23:34:48 -04:00
|
|
|
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;
|
2021-08-30 17:31:44 -04:00
|
|
|
// 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;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
// 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
|
2021-08-30 17:31:44 -04:00
|
|
|
if(HeartbeatSpellCast(SPELL_IMPROVED_INVISIBILITY)) return;
|
|
|
|
if(HeartbeatSpellCast(SPELL_INVISIBILITY)) return;
|
2021-08-29 23:34:48 -04:00
|
|
|
|
|
|
|
// Haste
|
2021-08-30 17:31:44 -04:00
|
|
|
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;
|
2021-08-29 23:34:48 -04:00
|
|
|
}
|
|
|
|
|
2021-08-30 17:31:44 -04:00
|
|
|
// Returns TRUE if any of the animation settings are on.
|
|
|
|
int GetHasValidAnimations()
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
2021-08-30 17:31:44 -04:00
|
|
|
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))
|
2021-08-29 23:34:48 -04:00
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|