#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); }