Amon_PRC8/_module/nss/hench_o0_ai.nss

1250 lines
44 KiB
Plaintext
Raw Permalink Normal View History

2025-04-03 19:00:46 -04:00
#include "hench_i0_heal"
#include "hench_i0_spells"
#include "hench_i0_ai"
#include "hench_i0_act"
// Threshold challenge rating for buff spells
const float PAUSANIAS_CHALLENGE_THRESHOLD = -2.0;
const float PAUSANIAS_FAMILIAR_THRESHOLD = -2.0;
const float PAUSANIAS_DISTANCE_THRESHOLD = 5.0;
void main()
{
object oIntruder = GetLocalObject(OBJECT_SELF, HENCH_AI_SCRIPT_INTRUDER_OBJ);
int bForce = GetLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_FORCE);
int curIntrudeCount = GetLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
++curIntrudeCount;
SetLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE, curIntrudeCount);
// destroy self if pseudo summons and master not valid
if (GetLocalInt(OBJECT_SELF, sHenchPseudoSummon) && !GetIsObjectValid(GetLocalObject(OBJECT_SELF, sHenchPseudoSummon)))
{
DestroyObject(OBJECT_SELF, 0.1);
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_IMP_UNSUMMON), GetLocation(OBJECT_SELF));
return;
}
// Jug_Debug(GetName(OBJECT_SELF) + " starting det combat int = " + GetName(oIntruder) + " action " + IntToString(GetCurrentAction()));
int iHP = GetPercentageHPLoss(OBJECT_SELF);
int iCheckHealing = iHP < 50;
SetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE, iCheckHealing ? HENCH_HEAL_SELF_CANT : HENCH_HEAL_SELF_WAIT);
int nCurAction = GetCurrentAction();
// ----------------------------------------------------------------------------------------
// Oct 06/2003 - Georg Zoeller,
// Fix for ActionRandomWalk blocking the action queue under certain circumstances
// ----------------------------------------------------------------------------------------
if (nCurAction == ACTION_RANDOMWALK)
{
ClearAllActions();
}
// Auldar: Don't want anything to disturb the Taunt attempt.
if(GetAssociateState(NW_ASC_IS_BUSY) || GetAssociateState(NW_ASC_MODE_DYING) || nCurAction == ACTION_TAUNT || nCurAction == ACTION_HEAL || nCurAction == ACTION_ANIMALEMPATHY)
{
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
return;
}
// Finish casting your spells if you've started.
if ((nCurAction == ACTION_CASTSPELL) || (nCurAction == ACTION_ITEMCASTSPELL))
{
// cancel if target has died
object oLastSpellTarget = GetLocalObject(OBJECT_SELF, sLastSpellTargetObject);
if (GetIsObjectValid(oLastSpellTarget) &&
(GetCurrentHitPoints(oLastSpellTarget) > HENCH_BLEED_NEGHPS))
{
return;
}
}
// MODIFIED FEBRUARY 13 2003
// The associate will not engage in battle if in Stand Ground mode unless
// he takes damage
if(GetAssociateState(NW_ASC_MODE_STAND_GROUND) && !GetIsObjectValid(GetLastHostileActor()))
{
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
return;
}
int iEffectsOnSelf = 0;
effect eCheck = GetFirstEffect(OBJECT_SELF);
while (GetIsEffectValid(eCheck))
{
switch(GetEffectType(eCheck))
{
// Pausanias: sanity check for various effects
case EFFECT_TYPE_PARALYZE:
case EFFECT_TYPE_STUNNED:
case EFFECT_TYPE_FRIGHTENED:
case EFFECT_TYPE_SLEEP:
case EFFECT_TYPE_PETRIFY:
case EFFECT_TYPE_TURNED:
// TODO handle daze - can walk away
case EFFECT_TYPE_DAZED:
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
DeleteLocalObject(OBJECT_SELF, sHenchLastTarget);
return;
case EFFECT_TYPE_ETHEREAL:
iEffectsOnSelf |= HENCH_HAS_ETHEREAL_EFFECT;
break;
case EFFECT_TYPE_CONCEALMENT:
iEffectsOnSelf |= HENCH_HAS_CONCEALMENT_EFFECT;
break;
case EFFECT_TYPE_INVISIBILITY:
case EFFECT_TYPE_IMPROVEDINVISIBILITY:
iEffectsOnSelf |= HENCH_HAS_INVISIBILITY_EFFECT;
break;
case EFFECT_TYPE_SANCTUARY:
iEffectsOnSelf |= HENCH_HAS_SANTUARY_EFFECT;
break;
case EFFECT_TYPE_CONFUSED:
iEffectsOnSelf |= HENCH_HAS_CONFUSED_EFFECT;
break;
case EFFECT_TYPE_CHARMED:
iEffectsOnSelf |= HENCH_HAS_CHARMED_EFFECT;
break;
case EFFECT_TYPE_POLYMORPH:
iEffectsOnSelf |= HENCH_HAS_POLYMORPH_EFFECT;
break;
case EFFECT_TYPE_HASTE:
iEffectsOnSelf |= HENCH_HAS_HASTE_EFFECT;
break;
case EFFECT_TYPE_TIMESTOP:
iEffectsOnSelf |= HENCH_HAS_TIMESTOP_EFFECT;
break;
}
eCheck = GetNextEffect(OBJECT_SELF);
}
// reset action queue
if (nCurAction != ACTION_INVALID)
{
ClearAllActions();
}
object oMaster = GetMaster();
object oRealMaster = GetRealMaster();
int iAmMonster = GetIsEnemy(GetFirstPC());
int iHaveMaster = !iAmMonster && GetIsObjectValid(oMaster);
int iAmHenchman,iAmFamiliar,iAmCompanion;
if (iHaveMaster)
{
int nAssocType = GetAssociateType(OBJECT_SELF);
iAmHenchman = nAssocType == ASSOCIATE_TYPE_HENCHMAN;
iAmFamiliar = nAssocType == ASSOCIATE_TYPE_FAMILIAR;
iAmCompanion = nAssocType == ASSOCIATE_TYPE_ANIMALCOMPANION;
}
// special code for Helmed Horror in Host Tower level 4
if (GetTag(OBJECT_SELF) == "2Q6_HelmHorror")
{
oIntruder = GetNearestObjectByTag("2q6_sanctumgolem");
if (!GetIsObjectValid(oIntruder))
{
ClearAllActions();
DestroyObject(OBJECT_SELF);
return;
}
ActionAttack(oIntruder);
return;
}
if (!iAmMonster)
{
HenchGetDefSettings();
}
object oClosestSeen;
object oClosestHeard;
object oClosestNonActive;
object oClosestSummoned;
int curCount = 1;
while (TRUE)
{
oClosestSeen = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
OBJECT_SELF, curCount, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_IS_ALIVE, TRUE);
if (!GetIsObjectValid(oClosestSeen))
{
break;
}
if (GetPlotFlag(oClosestSeen))
{
// ignore plot creatures
}
else if (GetLocalInt(oClosestSeen, sHenchRunningAway) || GetIsDisabled(oClosestSeen))
{
if (!GetIsObjectValid(oClosestNonActive))
{
oClosestNonActive = oClosestSeen;
}
}
else if (GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) > 9 && !GetIsObjectValid(oClosestSummoned) &&
GetAssociateType(oClosestSeen) != ASSOCIATE_TYPE_HENCHMAN &&
GetAssociateType(oClosestSeen) != ASSOCIATE_TYPE_NONE)
{
oClosestSummoned = oClosestSeen;
}
// never consider dying henchman
else if (!GetAssociateState(NW_ASC_MODE_DYING, oClosestSeen))
{
break;
}
curCount++;
}
curCount = 1;
while (TRUE)
{
oClosestHeard = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
OBJECT_SELF, curCount, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD_AND_NOT_SEEN,
CREATURE_TYPE_IS_ALIVE, TRUE);
if (!GetIsObjectValid(oClosestHeard))
{
break;
}
if (GetPlotFlag(oClosestHeard))
{
// ignore plot creatures
}
else if (GetLocalInt(oClosestHeard, sHenchRunningAway) || GetIsDisabled(oClosestHeard))
{
if (!GetIsObjectValid(oClosestNonActive))
{
oClosestNonActive = oClosestHeard;
}
}
// never consider dying henchman
else if (!GetAssociateState(NW_ASC_MODE_DYING, oClosestHeard))
{
break;
}
curCount++;
}
// find dying creatures to finish off
if (iAmMonster && !GetIsObjectValid(oClosestNonActive))
{
curCount = 1;
while (1)
{
oClosestNonActive = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF,
curCount, CREATURE_TYPE_IS_ALIVE, FALSE);
if (!GetIsObjectValid(oClosestNonActive))
{
break;
}
if (GetPlotFlag(oClosestNonActive))
{
// ignore plot creatures
}
else if (GetDistanceToObject(oClosestNonActive) >= 30.0)
{
oClosestNonActive = OBJECT_INVALID;
break;
}
if (GetCurrentHitPoints(oClosestNonActive) > HENCH_BLEED_NEGHPS)
{
break;
}
curCount++;
}
}
float fDistance = 1000.0;
object oClosestSeenOrHeard;
if (GetIsObjectValid(oClosestSeen))
{
oClosestSeenOrHeard = oClosestSeen;
fDistance = GetDistanceToObject(oClosestSeen);
}
if (GetIsObjectValid(oClosestSummoned))
{
float fTestDistance = GetDistanceToObject(oClosestSummoned);
if (fTestDistance < fDistance)
{
oClosestSeenOrHeard = oClosestSummoned;
fDistance = fTestDistance;
}
}
if (GetIsObjectValid(oClosestHeard))
{
float fTestDistance = GetDistanceToObject(oClosestHeard);
if (fTestDistance < fDistance)
{
oClosestSeenOrHeard = oClosestHeard;
fDistance = fTestDistance;
}
}
int iMeleeAttackers = fDistance <= 5.0 &&
(fabs(GetPosition(OBJECT_SELF).z - GetPosition(oClosestSeenOrHeard).z ) < 2.0) &&
// no one can attack while in time stop
!(iEffectsOnSelf & HENCH_HAS_TIMESTOP_EFFECT);
object oFriend = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND,
OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
CREATURE_TYPE_IS_ALIVE, TRUE);
// this is the first target, use
object oLastTarget = GetLocalObject(OBJECT_SELF, sHenchLastTarget);
if (GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) < 7)
{
oLastTarget = OBJECT_INVALID;
}
object oNotHeardOrSeen = OBJECT_INVALID;
if(!GetIsObjectValid(oLastTarget) ||
(GetIsDead(oLastTarget) && (GetCurrentHitPoints(oLastTarget) <= HENCH_BLEED_NEGHPS)) ||
!GetIsEnemy(oLastTarget) ||
GetLocalInt(oLastTarget, sHenchRunningAway) ||
GetAssociateState(NW_ASC_MODE_DYING, oLastTarget) ||
GetPlotFlag(oLastTarget) ||
GetArea(OBJECT_SELF) != GetArea(oLastTarget) ||
(!GetObjectSeen(oLastTarget) && (!GetObjectHeard(oLastTarget) || !LineOfSightObject(OBJECT_SELF, oLastTarget))) ||
GetIsDisabled(oLastTarget))
{
oLastTarget = OBJECT_INVALID;
}
if(!GetIsObjectValid(oIntruder) ||
GetIsDead(oIntruder) ||
GetLocalInt(oIntruder, sHenchRunningAway) ||
GetAssociateState(NW_ASC_MODE_DYING, oIntruder) ||
GetPlotFlag(oIntruder) ||
GetArea(OBJECT_SELF) != GetArea(oIntruder))
{
oIntruder = OBJECT_INVALID;
}
else if (!GetObjectSeen(oIntruder) && !GetObjectHeard(oIntruder))
{
// don't know where intruder is
oNotHeardOrSeen = oIntruder;
oIntruder = OBJECT_INVALID;
}
if (!bForce)
{
oIntruder = OBJECT_INVALID;
}
if (iHaveMaster)
{
// TODO more checks for allies
if ((GetMaster(oIntruder) == oMaster) || (GetMaster(oIntruder) == oRealMaster))
{
oIntruder = OBJECT_INVALID;
}
}
if (((d2() == 1) && (iEffectsOnSelf & HENCH_HAS_CONFUSED_EFFECT)) ||
(iEffectsOnSelf & HENCH_HAS_CHARMED_EFFECT))
{
if (!GetIsObjectValid(oFriend))
{
// just do nothing for this case
DeleteLocalObject(OBJECT_SELF, sHenchLastTarget);
return;
}
oIntruder = oFriend;
oLastTarget = OBJECT_INVALID;
}
else if (iHaveMaster && oIntruder == oRealMaster)
{
if (GetHenchmanOptions(HENCH_HENAI_NOATTACK) & HENCH_HENAI_NOATTACK)
{
oIntruder = OBJECT_INVALID;
}
}
// Get all enemies within 40 meters and store them in an array.
// Auldar: Lets try 25 meters. 40 causes monsters to go too far past the PC
// Tony K changed - this used to not use seen or heard, used all
// enemies within a set radius (25)
if(iAmMonster && !iMeleeAttackers &&
(GetMonsterOptions(HENCH_MONAI_DISTRIB) & HENCH_MONAI_DISTRIB)
&& !GetIsObjectValid(oIntruder) && !GetIsObjectValid(oLastTarget) &&
GetIsObjectValid(oClosestSeenOrHeard))
{
int perceptionType;
if (GetIsObjectValid(oClosestSeen) || GetIsObjectValid(oClosestSummoned))
{
perceptionType = PERCEPTION_SEEN;
}
else
{
perceptionType = PERCEPTION_HEARD_AND_NOT_SEEN;
}
int iNth = 0;
int iLoop = 1;
while (TRUE)
{
object oNextTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION,
REPUTATION_TYPE_ENEMY, OBJECT_SELF, iLoop,
CREATURE_TYPE_PERCEPTION, perceptionType,
CREATURE_TYPE_IS_ALIVE, TRUE);
if (!GetIsObjectValid(oNextTarget) || GetDistanceToObject(oNextTarget) > 25.)
{
break;
}
++iLoop;
if (!GetLocalInt(oNextTarget, sHenchRunningAway) && !GetAssociateState(NW_ASC_MODE_DYING, oNextTarget))
{
++iNth;
if (Random(iNth) == 0)
{
oIntruder = oNextTarget;
}
}
}
}
// Determine the best target
if (!GetIsObjectValid(oIntruder))
{
if (GetIsObjectValid(oLastTarget) && GetDistanceToObject(oLastTarget) <= 5.0)
{
if (GetObjectSeen(oLastTarget))
{
oIntruder = oLastTarget;
}
else if (GetIsObjectValid(oClosestSeen) && GetDistanceToObject(oClosestSeen) <= 5.0)
{
oIntruder = oClosestSeen;
}
else if (GetIsObjectValid(oClosestSummoned) && GetDistanceToObject(oClosestSummoned) <= 5.0)
{
oIntruder = oClosestSummoned;
}
else
{
oIntruder = oLastTarget;
}
}
else if (GetIsObjectValid(oClosestSeen) && GetDistanceToObject(oClosestSeen) <= 5.0)
{
oIntruder = oClosestSeen;
}
else if (GetIsObjectValid(oClosestSummoned) && GetDistanceToObject(oClosestSummoned) <= 5.0)
{
oIntruder = oClosestSummoned;
}
else if (GetIsObjectValid(oClosestHeard) && GetDistanceToObject(oClosestHeard) <= 5.0)
{
oIntruder = oClosestHeard;
}
else if (GetIsObjectValid(oLastTarget))
{
if (GetObjectSeen(oLastTarget))
{
oIntruder = oLastTarget;
}
else if (GetIsObjectValid(oClosestSeen))
{
oIntruder = oClosestSeen;
}
else if (GetIsObjectValid(oClosestSummoned))
{
oIntruder = oClosestSummoned;
}
else
{
oIntruder = oLastTarget;
}
}
else if (GetIsObjectValid(oClosestSeen))
{
oIntruder = oClosestSeen;
}
else if (GetIsObjectValid(oClosestSummoned))
{
oIntruder = oClosestSummoned;
}
else if (GetIsObjectValid(oClosestHeard))
{
oIntruder = oClosestHeard;
}
else if (GetIsObjectValid(oClosestNonActive))
{
oIntruder = oClosestNonActive;
}
}
object oIntruderSeen;
if (!GetObjectSeen(oIntruder))
{
if (GetIsObjectValid(oClosestSeen))
{
oIntruderSeen = oClosestSeen;
}
else if (GetIsObjectValid(oClosestSummoned))
{
oIntruderSeen = oClosestSummoned;
}
else if (GetIsObjectValid(oClosestNonActive) && GetObjectSeen(oClosestNonActive))
{
oIntruderSeen = oClosestNonActive;
}
}
else
{
oIntruderSeen = oIntruder;
}
// Auldar: If we are still in Search mode when we start to attack the enemy, stop searching.
if (GetIsObjectValid(oIntruderSeen) && GetActionMode(OBJECT_SELF, ACTION_MODE_DETECT))
{
SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, FALSE);
}
if (iAmMonster)
{
// reset scout mode (no wandering enable pursue to open doors)
SetLocalInt(OBJECT_SELF,"ScoutMode", 0);
}
int iSummonHelp = TRUE;
if (GetLocalInt(OBJECT_SELF, sHenchDontSummon))
{
iSummonHelp = FALSE;
}
int iUseMagic = GetLocalInt(OBJECT_SELF, "X2_L_STOPCASTING") != 10;
if (iHaveMaster && !GetIsObjectValid(oIntruder) && !GetIsObjectValid(oNotHeardOrSeen))
{
// Jug_Debug(GetName(OBJECT_SELF) + " checking heal count " + IntToString(GetLocalInt(OBJECT_SELF, henchHealCountStr)));
if (GetLocalInt(OBJECT_SELF, henchHealCountStr))
{
ExecuteScript("hench_o0_heal", OBJECT_SELF);
return;
}
if (GetLocalInt(OBJECT_SELF, henchBuffCountStr))
{
ExecuteScript("hench_o0_enhanc", OBJECT_SELF);
return;
}
if(HenchBashDoorCheck(iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT))
{
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
return;
}
}
// The following tweaks are implemented via Pausanias' dialog mods.
int nClass = HenchDetermineClassToUse();
// Herbivores should escape
// special combat calls
if(GetBehaviorState(NW_FLAG_BEHAVIOR_HERBIVORE) || nClass == CLASS_TYPE_COMMONER)
{
HenchTalentHide(iEffectsOnSelf, nGlobalMeleeAttackers);
if (HenchTalentFlee(oClosestSeenOrHeard)) return;
}
if (GetCombatCondition(X0_COMBAT_FLAG_COWARDLY)
&& SpecialTacticsCowardly(oClosestSeenOrHeard))
{
return;
}
//This check is to see if the master is being attacked and in need of help
if(GetAssociateState(NW_ASC_HAVE_MASTER))
{
if(GetAssociateState(NW_ASC_MODE_DEFEND_MASTER))
{
// Jug_Debug(GetName(OBJECT_SELF) + " checking defend master " + GetName(oRealMaster));
int bFoundTarget = FALSE;
oIntruder = GetLastHostileActor(oRealMaster);
if (!GetIsObjectValid(oIntruder) || (GetIsFriend(oIntruder) || GetFactionEqual(oIntruder)))
{
oIntruder = GetGoingToBeAttackedBy(oRealMaster);
if (!GetIsObjectValid(oIntruder) || (GetIsFriend(oIntruder) || GetFactionEqual(oIntruder)))
{
oIntruder = GetLastHostileActor();
if(!GetIsObjectValid(oIntruder))
{
if (GetAssociateState(NW_ASC_USE_RANGED_WEAPON))
{
if (fDistance > 20.)
{
DeleteLocalObject(OBJECT_SELF, sHenchLastTarget);
return;
}
}
else if (fDistance > 7.0)
{
DeleteLocalObject(OBJECT_SELF, sHenchLastTarget);
return;
}
}
else
{
// Jug_Debug(GetName(OBJECT_SELF) + " found enemy of " + GetName(oIntruder));
bFoundTarget = TRUE;
}
}
else
{
// Jug_Debug(GetName(OBJECT_SELF) + " found going to be attacked by " + GetName(oIntruder));
bFoundTarget = TRUE;
}
}
else
{
// Jug_Debug(GetName(OBJECT_SELF) + " found last hostile master " + GetName(oIntruder));
bFoundTarget = TRUE;
}
if (bFoundTarget)
{
if (!GetObjectSeen(oIntruder) && !GetObjectHeard(oIntruder))
{
// don't know where intruder is
// Jug_Debug("@@@@@@@@@@@@@@" + GetName(OBJECT_SELF) + " setting unseen intruder to " + GetName(oIntruder));
oNotHeardOrSeen = oIntruder;
oIntruder = OBJECT_INVALID;
}
else
{
bForce = TRUE;
}
}
}
}
// NEXT: Do not attack if the master told you not to
if (GetLocalInt(OBJECT_SELF, sHenchDontAttackFlag) &&
!(iEffectsOnSelf & (HENCH_HAS_CONFUSED_EFFECT | HENCH_HAS_CHARMED_EFFECT)))
{
if (iAmMonster)
{
DeleteLocalInt(OBJECT_SELF, sHenchDontAttackFlag);
}
else
{
if (d10() > 7 && !GetLocalInt(OBJECT_SELF, sHenchShouldIAttackMessageGiven) &&
(GetIsObjectValid(oIntruder) || GetIsObjectValid(oNotHeardOrSeen)))
{
if (iAmHenchman)
SpeakString(sHenchHenchmanAskAttack);
else if (iAmFamiliar)
SpeakString(sHenchFamiliarAskAttack);
else if (iAmCompanion)
SpeakString("<" + GetName(OBJECT_SELF) + sHenchAnCompAskAttack);
else
SpeakString(sHenchOtherFollow1 + GetName(OBJECT_SELF) + sHenchOtherAskAttack);
SetLocalInt(OBJECT_SELF, sHenchShouldIAttackMessageGiven, TRUE);
}
ActionForceFollowObject(oRealMaster, GetFollowDistance());
return;
}
}
if (!GetIsObjectValid(oIntruder))
{
// henchmen using ranged weapons will not move to unheard and unseen enemies
if (!bForce && !iAmMonster && GetAssociateState(NW_ASC_USE_RANGED_WEAPON))
{
ActionMoveToObject(oRealMaster, TRUE);
ClearEnemyLocation();
return;
}
else if (GetIsObjectValid(oNotHeardOrSeen))
{
SetEnemyLocation(oNotHeardOrSeen);
}
if (GetLocalInt(OBJECT_SELF, sHenchLastHeardOrSeen))
{
InitializeItemSpells(nClass, iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT, HENCH_INIT_ALL_SPELLS);
// TODO have hench buff - need to check CR
if (iUseMagic && iAmMonster && HenchTalentHide(iEffectsOnSelf, nGlobalMeleeAttackers))
{
}
else if (iUseMagic && iAmMonster && TK_UseBestSpell(OBJECT_INVALID, oNotHeardOrSeen, oFriend,
FALSE, FALSE, FALSE, iSummonHelp, TRUE, TRUE, TRUE, FALSE, iAmMonster))
{
}
else
{
MoveToLastSeenOrHeard();
}
}
else
{
ClearAllActions(TRUE);
CleanCombatVars();
if (iAmMonster)
{
SetLocalObject(OBJECT_SELF, "NW_GENERIC_LAST_ATTACK_TARGET", OBJECT_INVALID);
WalkWayPoints();
}
ClearWeaponStates();
HenchEquipDefaultWeapons();
}
DeleteLocalObject(OBJECT_SELF, sHenchLastTarget);
DeleteLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_RUN_STATE);
// nothing more to do
return;
}
ClearEnemyLocation();
DeleteLocalInt(OBJECT_SELF, henchBuffCountStr);
DeleteLocalInt(OBJECT_SELF, henchHealCountStr);
// fail safe set of last target
SetLocalObject(OBJECT_SELF, sHenchLastTarget, oIntruder);
int combatRoundCount;
combatRoundCount = GetLocalInt(OBJECT_SELF, henchCombatRoundStr);
combatRoundCount ++;
SetLocalInt(OBJECT_SELF, henchCombatRoundStr, combatRoundCount);
//Shout that I was attacked
if (!(iAmHenchman || iAmFamiliar || iAmCompanion) &&
(HENCH_MONSTER_SHOUT_FREQUENCY > 0) &&
(combatRoundCount % HENCH_MONSTER_SHOUT_FREQUENCY == 1))
{
SpeakString("NW_I_WAS_ATTACKED", TALKVOLUME_SILENT_TALK);
SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);
}
// June 2/04: Fix for when henchmen is told to use stealth until next fight
if(GetLocalInt(OBJECT_SELF, sHenchStealthMode)==2)
{
SetLocalInt(OBJECT_SELF, sHenchStealthMode, 0);
}
// ----------------------------------------------------------------------------------------
// July 27/2003 - Georg Zoeller,
// Added to allow a replacement for determine combat round
// If a creature has a local string variable named X2_SPECIAL_COMBAT_AI_SCRIPT
// set, the script name specified in the variable gets run instead
// see x2_ai_behold for details:
// ----------------------------------------------------------------------------------------
string sSpecialAI = GetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT");
if (sSpecialAI == "")
{
if (GetHasSpell(SPELL_HENCH_Beholder_Special_Spell_AI) ||
GetHasSpell(SPELL_HENCH_Beholder_Anti_Magic_Cone))
{
sSpecialAI = "x2_ai_behold";
}
}
if (sSpecialAI != "")
{
SetLocalObject(OBJECT_SELF,"X2_NW_I0_GENERIC_INTRUDER", oIntruder);
ExecuteScript(sSpecialAI, OBJECT_SELF);
if (GetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK"))
{
DeleteLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK");
return;
}
}
// Jug_Debug(GetName(OBJECT_SELF) + " intruder " + GetName(oIntruder));
InitializeItemSpells(nClass, iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT, HENCH_INIT_ALL_SPELLS);
// try to go invis if have sneak attack at beginning of combat (helps with sneaking)
if (iUseMagic && (combatRoundCount < 2 || GetHasFeat(FEAT_SNEAK_ATTACK)))
{
HenchTalentStealth(nGlobalMeleeAttackers);
}
// Get distance closer than which henchman will swap to melee.
float fThresholdDistance = GetLocalFloat(OBJECT_SELF, sHenchHenchRange);
if (fThresholdDistance == 0.0)
fThresholdDistance = PAUSANIAS_DISTANCE_THRESHOLD;
// Get challenge below which no defensive spells are cast
float fThresholdChallenge = GetLocalFloat(OBJECT_SELF, sHenchSpellChallenge);
if (fThresholdChallenge == 0.0)
{
if (nClass == CLASS_TYPE_WIZARD || nClass == CLASS_TYPE_SORCERER)
{
fThresholdChallenge = -3.;
}
else
{
fThresholdChallenge = PAUSANIAS_CHALLENGE_THRESHOLD;
}
}
// Signal to try to get some distance between self and the enemy.
int bBackAway = FALSE;
if (fThresholdDistance > 50.0)
{
bBackAway = TRUE;
fThresholdDistance = PAUSANIAS_DISTANCE_THRESHOLD;
}
// Monsters and non-associates do not care about the challenge rating for now.
if (iAmMonster || !iHaveMaster)
fThresholdChallenge = -100.;
// Should I try to cast spells if monsters are nearby? Yes by default
int iCastMelee = TRUE;
if (GetLocalInt(OBJECT_SELF,sHenchDontCastMelee))
iCastMelee = FALSE;
int iHealMelee = TRUE;
if (GetLocalInt(OBJECT_SELF,"DoNotHealMelee"))
iHealMelee = FALSE;
// The FIRST PRIORITY: self-preservation
int nRacialType = GetRacialType(OBJECT_SELF);
if (iCheckHealing && iAmMonster)
{
int nMonsterDamageOpt = GetMonsterOptions(HENCH_MONAI_HASTE | HENCH_MONAI_HEALPT);
if (nMonsterDamageOpt)
{
// Pausanias: Monsters get tougher for the harder game settings.
if ((nMonsterDamageOpt & HENCH_MONAI_HASTE) &&
!(iEffectsOnSelf & HENCH_HAS_HASTE_EFFECT))
{
ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectHaste(), OBJECT_SELF, 60.);
}
if ((nMonsterDamageOpt & HENCH_MONAI_HEALPT) && !(iHP < 20 && iMeleeAttackers))
{
if (nRacialType != RACIAL_TYPE_UNDEAD && GetCreatureUseItems(OBJECT_SELF))
{
int nHeal = GetLocalInt(OBJECT_SELF,"GaveHealing");
if (nHeal < (GetHitDice(OBJECT_SELF)/2))
{
++nHeal;
SetLocalInt(OBJECT_SELF, "GaveHealing", nHeal);
CreateItemOnObject("NW_IT_MPOTION003", OBJECT_SELF, 1);
}
}
}
}
}
float fChallenge;
if (iAmMonster || !iHaveMaster)
{
// Monsters and non-associates do not care about the challenge rating for now.
fChallenge = 100.0;
}
else
{
// Pausanias's Combined Challenge Rating (CCR)
fChallenge = GetEnemyChallenge();
}
// I am a familiar or animal companion
if ((iAmFamiliar || iAmCompanion) && GetIsObjectValid(oClosestSeenOrHeard))
{
// Get challenge above which familiar or animal companion will run away
float fAssociateChallenge;
int bFightToTheDeath;
if (iAmFamiliar)
{
fAssociateChallenge = GetLocalFloat(oMaster, sHenchFamiliarChallenge);
if (fAssociateChallenge == 0.0)
fAssociateChallenge = PAUSANIAS_FAMILIAR_THRESHOLD;
bFightToTheDeath = GetLocalInt(oMaster, sHenchFamiliarToDeath);
}
else
{
// default to 0.0 challenge if not set
fAssociateChallenge = GetLocalFloat(oMaster, sHenchAniCompChallenge);
bFightToTheDeath = GetLocalInt(oMaster, sHenchAniCompToDeath);
}
// Run away from tough enemies
if (!bFightToTheDeath && (fChallenge >= fAssociateChallenge || (iHP < 40)))
{
if (iAmFamiliar)
{
switch (d10())
{
case 1: SpeakString(sHenchFamiliarFlee1); return;
case 2: SpeakString(sHenchFamiliarFlee2); break;
case 3: SpeakString(sHenchFamiliarFlee3); break;
case 4: SpeakString(sHenchFamiliarFlee4); break;
}
}
else
{
if (d3() == 1)
{
SpeakString(sHenchAniCompFlee);
}
}
ClearAllActions();
HenchTalentHide(iEffectsOnSelf, nGlobalMeleeAttackers);
ActionMoveAwayFromObject(oClosestSeenOrHeard,TRUE,40.);
ActionMoveAwayFromObject(oClosestSeenOrHeard,TRUE,40.);
ActionMoveAwayFromObject(oClosestSeenOrHeard,TRUE,40.);
ActionMoveAwayFromObject(oClosestSeenOrHeard,TRUE,40.);
SetLocalInt(OBJECT_SELF, sHenchRunningAway, TRUE);
return;
}
}
if (!iMeleeAttackers)
{
nGlobalMeleeAttackers = 0;
}
// Condition for immediate self-healing
if (iCheckHealing)
{
// if hidden, try to bring up to max HP
if (HenchTalentHeal(OBJECT_SELF, iEffectsOnSelf, (iHP <= 90) && InvisibleTrue() ?
HENCH_HEAL_FORCE : HENCH_HEAL_NO_MINOR)) return;
if ((iAmHenchman || iAmFamiliar) && (GetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE) == HENCH_HEAL_SELF_CANT))
{
if (iAmHenchman)
{
if (Random(100) > 80) VoiceHealMe();
}
else
{
SpeakString(sHenchHealMe);
}
}
}
// get in as many melee attacks as possible before spell ends
if (GetHasSpellEffect(SPELL_TRUE_STRIKE))
{
HenchTalentMeleeAttack(oIntruder, fThresholdDistance, iMeleeAttackers,
iAmMonster ? 0 : (iAmHenchman ? 1 : 2), iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT);
return;
}
// NEXT priority: Heal master if needed.
if (GetAssociateHealMaster())
{
if (fDistance > fThresholdDistance || iHealMelee)
{
if (HenchTalentHeal(oMaster, iEffectsOnSelf, HENCH_HEAL_FORCE | HENCH_HEAL_NO_MINOR)) return;
if (d10() > 6 &&
(nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_CLERIC ||
nClass == CLASS_TYPE_DRUID || nClass == CLASS_TYPE_PALADIN))
SpeakString(sHenchCantHealMaster);
}
else
if (d10() > 6)
SpeakString(sHenchAskHealMaster);
}
// NEXT priority: follow or return to master for up to three rounds.
int nFollow = GetLocalInt(OBJECT_SELF,"FollowCount");
++nFollow;
if(iHaveMaster && !GetLocalInt(OBJECT_SELF, sHenchRunningAway) &&
(!bBackAway || GetLocalInt(OBJECT_SELF, sHenchScoutingFlag)))
{
SetLocalInt(OBJECT_SELF, sHenchScoutingFlag, FALSE);
if(GetDistanceToObject(oRealMaster) > 15.0 && !GetObjectSeen(oRealMaster))
{
if(GetCurrentAction(oRealMaster) != ACTION_FOLLOW)
{
ActionForceFollowObject(oRealMaster, GetFollowDistance());
return;
}
else if (nFollow < 4)
{
return;
}
}
}
SetLocalInt(OBJECT_SELF, "FollowCount", 0);
// Pausanias: Combat has finally begun, so we are no longer scouting
DeleteLocalInt(OBJECT_SELF, sHenchScoutingFlag);
DeleteLocalInt(OBJECT_SELF, sHenchRunningAway);
int iPanicMode = (GetLocalInt(OBJECT_SELF, "INeedHealing") == 2 && iHP < 35) ||
(iHaveMaster && GetPercentageHPLoss(oRealMaster) < 35);
if (iPanicMode)
{
//iCastMelee = TRUE;
iHealMelee = TRUE;
fChallenge = 90.0;
}
// TODO HotU adjust tactics to be more in line with HoTU overrides?
// Should I try to cast spells if monsters are nearby? Yes by default
int useAttackSpells = FALSE;
int buffSelf = FALSE;
int buffOthers = FALSE;
int allowWeakOffSpells = FALSE;
int allowAttackAbilities = FALSE;
int allowUnlimitedAttackAbilities = iUseMagic;
int allowBuffAbilities = FALSE;
if (iUseMagic && fChallenge >= fThresholdChallenge)
{
buffSelf = TRUE;
allowAttackAbilities = TRUE;
allowBuffAbilities = TRUE;
int nMaxHitDieTest = GetHitDice(OBJECT_SELF);
if (nMaxHitDieTest > 9)
{
nMaxHitDieTest = 9;
}
if (!iMeleeAttackers)
{
switch (nClass)
{
case CLASS_TYPE_WIZARD:
case CLASS_TYPE_SORCERER:
case CLASS_TYPE_FEY:
case CLASS_TYPE_OUTSIDER:
useAttackSpells = TRUE;
allowWeakOffSpells = d10(2) >= nMaxHitDieTest;
buffOthers = d2() == 1;
break;
case CLASS_TYPE_BARD:
case CLASS_TYPE_DRUID:
useAttackSpells = TRUE;
allowWeakOffSpells = useAttackSpells && d3(3) >= nMaxHitDieTest;
buffOthers = d2() == 1;
break;
case CLASS_TYPE_CLERIC:
case CLASS_TYPE_MAGICAL_BEAST:
useAttackSpells = d3() != 1;
allowWeakOffSpells = useAttackSpells && d3(3) >= nMaxHitDieTest;
buffOthers = TRUE;
break;
default:
useAttackSpells = d3() == 1;
allowWeakOffSpells = useAttackSpells && d3(3) >= nMaxHitDieTest;
buffOthers = TRUE;
break;
}
}
else if (iCastMelee)
{
switch (nClass)
{
case CLASS_TYPE_WIZARD:
case CLASS_TYPE_SORCERER:
useAttackSpells = TRUE;
allowWeakOffSpells = d10(2) >= nMaxHitDieTest;
buffOthers = d6() == 1;
break;
case CLASS_TYPE_BARD:
case CLASS_TYPE_DRUID:
case CLASS_TYPE_FEY:
case CLASS_TYPE_OUTSIDER:
useAttackSpells = d2() == 1;
allowWeakOffSpells = useAttackSpells && d4() >= nMaxHitDieTest;
buffOthers = d6() == 1;
break;
case CLASS_TYPE_CLERIC:
case CLASS_TYPE_MAGICAL_BEAST:
useAttackSpells = d4() == 1;
allowWeakOffSpells = useAttackSpells && d4() >= nMaxHitDieTest;
buffOthers = d4() == 1;
break;
default:
useAttackSpells = d8() == 1;
allowWeakOffSpells = useAttackSpells && d4() >= nMaxHitDieTest;
buffOthers = d10() == 1;
break;
}
}
}
// if raging, then turn off most spell casting
if (GetHasSpellEffect(SPELLABILITY_RAGE_5) || GetHasSpellEffect(SPELLABILITY_RAGE_4) ||
GetHasSpellEffect(SPELLABILITY_RAGE_3) || GetHasSpellEffect(SPELLABILITY_FEROCITY_2) ||
GetHasSpellEffect(SPELLABILITY_FEROCITY_1) || GetHasSpellEffect(SPELL_BLOOD_FRENZY) ||
GetHasFeatEffect(FEAT_BARBARIAN_RAGE) || GetHasFeatEffect(SPELLABILITY_FEROCITY_3))
{
useAttackSpells = FALSE;
buffSelf = buffSelf && d6() == 1;
buffOthers = FALSE;
allowWeakOffSpells = FALSE;
iSummonHelp = FALSE;
}
// if have melee attack bonuses, then turn off most spell casting
else if (GetHasSpellEffect(SPELLABILITY_DIVINE_STRENGTH) || GetHasSpellEffect(SPELLABILITY_DIVINE_PROTECTION) ||
GetHasSpellEffect(SPELL_DIVINE_POWER) || GetHasSpellEffect(SPELLABILITY_BATTLE_MASTERY) ||
GetHasSpellEffect(SPELL_DIVINE_FAVOR) || GetHasFeatEffect(FEAT_DIVINE_MIGHT) ||
GetHasFeatEffect(FEAT_DIVINE_SHIELD))
{
useAttackSpells = FALSE;
buffSelf = buffSelf && d2() == 1;
buffOthers = FALSE;
allowWeakOffSpells = FALSE;
iSummonHelp = FALSE;
}
if (!GetIsObjectValid(oIntruderSeen))
{
useAttackSpells = FALSE;
allowAttackAbilities = FALSE;
allowUnlimitedAttackAbilities = FALSE;
allowWeakOffSpells = FALSE;
}
else
{
if (oIntruderSeen == oClosestNonActive)
{
// only wizards or sorcerers attack disabled creatures with spells
if (nClass != CLASS_TYPE_SORCERER && nClass != CLASS_TYPE_WIZARD)
{
useAttackSpells = FALSE;
allowAttackAbilities = FALSE;
allowWeakOffSpells = FALSE;
}
buffSelf = FALSE;
buffOthers = FALSE;
iSummonHelp = FALSE;
}
}
// I am a henchman
if (GetIsObjectValid(oClosestSeenOrHeard) && ((iAmHenchman && bBackAway) || GetCombatCondition(X0_COMBAT_FLAG_RANGED)))
{
if (fDistance < 8.0 && fDistance > 3.0)
{
// Try to get some distance for up to 3 rounds if told to do so.
int nBackAway = GetLocalInt(OBJECT_SELF,"BackAway");
if (nBackAway < 4)
{
ActionMoveAwayFromObject(oClosestSeenOrHeard, TRUE, 15.0);
++nBackAway;
SetLocalInt(OBJECT_SELF,"BackAway", nBackAway);
return;
}
}
SetLocalInt(OBJECT_SELF,"BackAway",0);
}
int iCloseBuff = 0;
if (iAmHenchman && GetIsObjectValid(oClosestSeenOrHeard))
{
// 5% chance per round of speaking the relative challenge of the encounter.
if (d20() == 1)
{
if (fChallenge < -3.) SpeakString(sHenchWeakAttacker);
else if (fChallenge < -1.) SpeakString(sHenchModAttacker);
else if (fChallenge < 2.) SpeakString(sHenchStrongAttacker);
else SpeakString(sHenchOverpoweringAttacker);
}
// Logic: if we are at close range, and the encounter is tough
// Then Buff the PC up once, and then fight. Otherwise, if
// we are at close range, fight; else 20% chance of using
// the missile weapon. Sorcerors or wizards try to cast spells
// rather than fight.
if ((nClass != CLASS_TYPE_WIZARD && nClass != CLASS_TYPE_SORCERER) ||
!iCastMelee)
{
if (iCastMelee && iMeleeAttackers && !iPanicMode)
{
if (fChallenge >= 0.0)
{
iCloseBuff = GetLocalInt(OBJECT_SELF,"CloseRangeEnhanced");
iCloseBuff++;
if (iCloseBuff == 1)
{
buffOthers = TRUE; buffSelf = FALSE;
}
else if (iCloseBuff == 2)
{
buffOthers = FALSE; buffSelf = TRUE;
}
else
{
buffOthers = FALSE; buffSelf = FALSE;
}
}
else
{
buffOthers = FALSE; buffSelf = FALSE;
}
}
// 30% chance of attacking weak opponents outright even if
// they are not at close range.
// There's a 20% chance of attacking anyway if the missile weapon is equipped.
if (iMeleeAttackers || (fChallenge < -3. && d100() < 30) ||
(GetAssociateState(NW_ASC_USE_RANGED_WEAPON) && (d100() < 20)))
{
useAttackSpells = FALSE;
buffSelf = FALSE;
buffOthers = FALSE;
allowWeakOffSpells = FALSE;
iSummonHelp = FALSE;
}
}
}
// check for area effect spells damaging self
if (CheckAOEForSelf()) {return;}
/* TODO leave out for now
if (GetCombatCondition(X0_COMBAT_FLAG_AMBUSHER)
&& SpecialTacticsAmbusher(oClosestSeenOrHeard))
{
return;
}*/
//Remove negative effects from allies
if(iUseMagic && HenchTalentCureCondition()) {return;}
//Check if allies or self are injured
if(iUseMagic && HenchTalentHealAllies(iEffectsOnSelf, HENCH_HEAL_NO_MINOR)) {return;}
if (allowBuffAbilities)
{
if (HenchTalentPersistentAbilities()) {return;}
if(HenchTalentBardSong()) {return;}
}
if (InvisibleTrue())
{
// while invisible do maximum amount of buffing & summoning
int result = TK_UseBestSpell(OBJECT_INVALID, OBJECT_INVALID, oFriend, FALSE, FALSE, FALSE,
iSummonHelp, buffSelf, buffOthers, allowBuffAbilities, FALSE, iAmMonster);
if ((iCloseBuff == 1 && result == TK_BUFFOTHER) || (iCloseBuff == 2 && result == TK_BUFFSELF))
{
SetLocalInt(OBJECT_SELF,"CloseRangeEnhanced", iCloseBuff);
}
if (result) { return; }
iSummonHelp = FALSE;
buffSelf = FALSE;
allowBuffAbilities = FALSE;
}
// transformation spells if got here for druid or shifter
if (iUseMagic && allowAttackAbilities && GetLevelByClass(CLASS_TYPE_DRUID))
{
if (HenchTalentAdvancedPolymorph(iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT)) { return; }
}
int result = TK_UseBestSpell(oIntruderSeen, GetIsObjectValid(oClosestHeard) ? oClosestHeard : oNotHeardOrSeen, oFriend,
useAttackSpells, allowAttackAbilities, allowUnlimitedAttackAbilities, iSummonHelp, buffSelf, buffOthers,
allowBuffAbilities, allowWeakOffSpells, iAmMonster);
if ((iCloseBuff == 1 && result == TK_BUFFOTHER) || (iCloseBuff == 2 && result == TK_BUFFSELF))
{
SetLocalInt(OBJECT_SELF,"CloseRangeEnhanced", iCloseBuff);
}
if (result) { return; }
// transformation spells if got here for wizard, sor, or druid
if (iUseMagic && buffSelf && !(iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT))
{
if (HenchTalentPolymorph()) { return; }
}
if (!iMeleeAttackers && GetIsObjectValid(oIntruderSeen) && (!iAmHenchman || GetAssociateState(NW_ASC_USE_RANGED_WEAPON)) && GetCreatureUseItems(OBJECT_SELF))
{
if (HenchUseGrenade(oIntruderSeen)) { return; }
}
//Attack if out of spells
HenchTalentMeleeAttack(oIntruder, fThresholdDistance, iMeleeAttackers,
iAmMonster ? 0 : (iAmHenchman ? 1 : 2), iEffectsOnSelf & HENCH_HAS_POLYMORPH_EFFECT);
}