/*////////////////////////////////////////////////////////////////////////////// Script: 0i_talents Programmer: Philos //////////////////////////////////////////////////////////////////////////////// Fuctions to use a category of skills, feats, spells, or items. */////////////////////////////////////////////////////////////////////////////// #include "0i_combat" // ***************************************************************************** // ************************* Try * Defensive Talents *************************** // ***************************************************************************** // These functions try to find and use a specific set of talents intelligently. // Returns TRUE if oCreature uses a healing talent on oTarge. // nInMelee is the number of enemies the caller is in melee with. // If oTarget is set then they will heal that target if they need it. // Otherwise checks all allies to see who we should heal based on the talent. int ai_TryHealingTalent(object oCreature, int nInMelee, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses a cure condition talent on an ally or self. int ai_TryCureConditionTalent(object oCreature, int nInMelee, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses a defensive talent. // Checks for a Defensive talent(Protection, Enhancement, or Summons). // Randomizes the order to mix up spells in combat. // if oTarget is set then the defensive talent will be cast on them or OBJECT_SELF. int ai_TryDefensiveTalents(object oCreature, int nInMelee, int nMaxLevel, int nRound = 0, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses a defensive talent. // Checks the enemy faction for most powerful class and picks a buff based on it. //int ai_TryAdvancedBuffOnSelf(object oCreature, int nInMelee); // Set any auras this oCreature has instantly. // This can be done in the OnSpawn script, heart beat, or Perception. void ai_SetAura(object oCreature); // ***************************************************************************** // ************************ Try Physical Attack Talents ************************ // ***************************************************************************** // These functions try to find and use melee attack talents intelligently. // Wrapper for ActionAttack, oCreature uses nAction (attack) on oTarget. // nInMelee is only used in AI_LAST_ACTION_RANGED_ATK actions. // bPassive TRUE oCreature will not move while attacking. // nActionMode, pass the action mode if one is being used. void ai_ActionAttack(object oCreature, int nAction, object oTarget, int nInMelee = 0, int bPassive = FALSE, int nActionMode = 0); // Returns TRUE if oCreature uses a dragons breath talent // Check for dragon's attacks under TALENT_CATEGORY_DRAGONS_BREATH(19). // nRound must be supplied so we can keep track of the breath uses. int ai_TryDragonBreathAttack(object oCreature, int nRound, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses a dragons wing attacks. // Checks to see if a dragon can use its wings on a nearby enemy. // Checks the right side and then the left side to see if it can attack. int ai_TryWingAttacks(object oCreature); // Returns TRUE if oCreature uses a dragons tail slap. // Looks behind the dragon to see if it can use it's tail slap on an enemy. int ai_TryTailSlap(object oCreature); // Returns TRUE if oCreature uses a dragons crush attack. // Dragon can fly up and crash down on opponents to do bludgeoning damage. // If 3 times smaller than the dragon they will take extra damage and be // Knocked Down for 1 round if Reflex save is not made. int ai_TryCrushAttack(object oCreature, object oTarget); // Returns TRUE if oCreature uses a dragons tail sweep attack. // If the enemy is 4 sizes smaller than it the dragon to use its tail to sweep // behind it doing damage and knocking the opponents down. int ai_TryTailSweepAttack(object oCreature); // Returns TRUE if oCreature finds a good target and uses Sneak Attack. int ai_TrySneakAttack(object oCreature, int nInMelee, int bAlwaysAtk = TRUE); // Returns TRUE if oCreature finds a good ranged target and uses Sneak Attack. int ai_TryRangedSneakAttack(object oCreature, int nInMelee); // Returns TRUE if oCreature uses a harmful melee talent. int ai_TryMeleeTalents(object oCreature, object oTarget); // ***************************************************************************** // ******************************* Try * Skills ******************************** // ***************************************************************************** // These functions try to find and use a specific set of skills intelligently. // Wrapper to have oCreature use nSkill on oTarget. void ai_UseSkill(object oCreature, int nSkill, object oTarget); // Returns TRUE if oCreature uses the parry skill on someone attacking them. // Checks if doing a parry might be successful. int ai_TryParry(object oCreature); // Returns TRUE if oCreature uses the Taunt skill on oTarget. // Checks if doing a taunt might be successful against oTarget. int ai_TryTaunt(object oCreature, object oTarget); // Returns TRUE if oCreature uses the Animial emapthy skill on oTarget. // For it to work oTarget must be an Animal, Beast, or Magical Beast. // Checks if doing Animal Empathy might be successful against oTarget. int ai_TryAnimalEmpathy(object oCreature, object oTarget = OBJECT_INVALID); // ***************************************************************************** // ******************************** Try * Feats ******************************** // ***************************************************************************** // These functions try to find and use a specific set of feats intelligently. // Wrapper to have oCreature use nFeat on oTarget. void ai_UseFeat(object oCreature, int nFeat, object oTarget, int nSubFeat = 0); // Wrapper to have oCreature use nActionMode on oTarget. // nInMelee is only used in AI_LAST_ACTION_RANGED_ATK actions. // bPassive TRUE oCreature will not move while attacking. void ai_UseFeatAttackMode(object oCreature, int nActionMode, int nAction, object oTarget, int nInMelee = 0, int bPassive = FALSE); // Returns TRUE if oCreature uses Rage. // This checks if they are already in a rage and if they have the Rage feat. int ai_TryBarbarianRageFeat(object oCreature); // Returns TRUE if oCreature uses Bard song. // This checks if they have any uses left, have the feat and if its viable. int ai_TryBardSongFeat(object oCreature); // Returns TRUE if oCreature uses Called shot. // This checks if they have the feat and if its viable. int ai_TryCalledShotFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Disarm. // This checks if they have the feat and if its viable. int ai_TryDisarmFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Divine Might. // This only checks if they can use the feat and have turn undead uses left. int ai_TryDivineMightFeat(object oCreature, int nInMelee); // Returns TRUE if oCreature uses Divine Shield. // This only checks if they can use the feat and have turn undead uses left. int ai_TryDivineShieldFeat(object oCreature, int nInMelee); // Returns TRUE if oCreature uses Expertise. // This checks if they have the feat and if its viable. // Also checks to see if the Improved Expertise feat would be better. int ai_TryExpertiseFeat(object oCreature); // Returns TRUE if oCreature uses Flurry of Blows. // This checks if they have the feat and if its viable. int ai_TryFlurryOfBlowsFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Improved Expertise. // This checks if they have the feat and if its viable. // Also checks to see if the Expertise feat would be better. int ai_TryImprovedExpertiseFeat(object oCreature); // Returns TRUE if oCreature uses Improved Power Attack. // This checks if they have the feat and if its viable. // Also checks to see if the Power Attack feat would be better. int ai_TryImprovedPowerAttackFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Ki Damage. // This checks if they have any uses left, have the feat and if its viable. int ai_TryKiDamageFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Knockdown. // This checks if they have the feat and if its viable. int ai_TryKnockdownFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses a polymorph self feat. // This checks if they have the feat and will use the best one. int ai_TryPolymorphSelfFeat(object oCreature); // Returns TRUE if oCreature uses Power Attack. // This checks if they have the feat and if its viable. // Also checks to see if the Improved Power Attack would be better. int ai_TryPowerAttackFeat(object oCreature, object oEnemy); // Returns TRUE if oCreature uses Quivering palm. // This checks if they have any uses left, have the feat and if its viable. int ai_TryQuiveringPalmFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Power Attack. // This checks if they have the feat and if its viable. // Using a bow and having arrows should be checked before calling this. int ai_TryRapidShotFeat(object oCreature, object oTarget, int nInMelee); // Returns TRUE if oCreature uses Sap. // This checks if they have the feat and if its viable. int ai_TrySapFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Smite evil. // This checks if they have any uses left, have the feat and if its viable. int ai_TrySmiteEvilFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Smite good. // This checks if they have any uses left, have the feat and if its viable. int ai_TrySmiteGoodFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses Stunning fists. // This checks if they have any uses left, have the feat and if its viable. int ai_TryStunningFistFeat(object oCreature, object oTarget); // Returns TRUE if oCreature uses a summon animal companion talent. int ai_TrySummonAnimalCompanionTalent(object oCreature); // Returns TRUE if oCreature uses a summon familiar talent. int ai_TrySummonFamiliarTalent(object oCreature); // Returns TRUE if oCreature uses the Lay on Hands feat talent. int ai_TryLayOnHands(object oCreature); // Returns TRUE if oCreature uses a turning talent. int ai_TryTurningTalent(object oCreature); // Returns TRUE if oCreature uses Whirlwind. // This checks if they have the feat and if its viable. int ai_TryWhirlwindFeat(object oCreature); // Returns TRUE if oCreature uses Wholeness of Body. // This checks if they have any uses left, have the feat and if its viable. int ai_TryWholenessOfBodyFeat(object oCreature); // ***************************************************************************** // ***************************** TALENT SCRIPTS ****************************** // ***************************************************************************** // These functions do not fall into another section. // Returns the MaxLevel used in GetCreatureTalent for oCreature. // This checks the intelligence and the level of oCreature. // Returns either -1 (random) or 10 for all talents. int ai_GetMonsterTalentMaxLevel(object oCreature); // Returns the nMaxLevel used in GetCreatureTalent for oCreature. // This checks the difficulty of the combat and the level of oCreature. // Return a number equal to 1 and half the level of oCreature upto 10. // The max spell level used is equal to nMaxLevel or less. int ai_GetAssociateTalentMaxLevel(object oCreature, int nDifficulty); // Returns TRUE if oCreature has nTalent. // nTalent will be a spell in the spells.2da. int ai_GetHasTalent(object oCreature, int nTalent); // Saves a talent in JsonArray. // Array: 0-Type (1-spell, 2-sp ability, 4-feat, 3-item) // Type 1)spell 0-type, 1-spell, 2-class, 3-level, 4-slot. // Type 2)sp Ability 0-type, 1-spell, 2-class, 3-level, 4-slot. // Type 3)feat 0-type, 1-spell, 2- class, 3- level. // Type 4)item 0-type, 1-spell, 2-item object, 3-level, 4-slot. // jJsonLevel is the level to place the talent in the json array // maybe different then the talents actual level which is passed in nLevel. void ai_SaveTalent(object oCreature, int nClass, int nJsonLevel, int nLevel, int nSlot, int nSpell, int nType, int bBuff, object oItem = OBJECT_INVALID); // Removes a talent nSlotIndex from jLevel in jCategory. void ai_RemoveTalent(object oCreature, json jCategory, json jLevel, string sCategory, int nLevel, int nSlotIndex); // Saves a creatures talents to variables upon them for combat use. // bMonster will check to see if they should be buffed when we set the talents. void ai_SetCreatureTalents(object oCreature, int bMonster); // Return TRUE if oCreature spontaneously casts a cure spell from a talent in sCategory. int ai_UseSpontaneousCureTalentFromCategory(object oCreature, string sCategory, int nInMelee, int nDamage, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses jTalent on oTarget. // also Returns -1 if oCreature uses jTalent on oTarget with a memorized spell. // This allows the user to remove jTalent from jLevel in jCategory. int ai_UseCreatureSpellTalent(object oCreature, json jLevel, json jTalent, string sCategory, int nInMelee, object oTarget = OBJECT_INVALID); // Return TRUE if oCreature uses a jTalent from oItem on oTarget. int ai_UseCreatureItemTalent(object oCreature, json jLevel, json jTalent, string sCategory, int nInMelee, object oTarget = OBJECT_INVALID); // Returns TRUE if oCreature uses a talent from sCategory of nLevel or less. int ai_UseCreatureTalent(object oCreature, string sCategory, int nInMelee, int nLevel = 10, object oTarget = OBJECT_INVALID); // Return TRUE if oCreature uses nTalent on oTarget. int ai_UseTalent(object oCreature, int nTalent, object oTarget); // Returns TRUE if jTalent is used on oTarget by oCaster. // Checks the talent type and casts the correct spell. For items it checks uses. int ai_UseTalentOnObject(object oCaster, json jTalent, object oTarget, int nInMelee); // Returns TRUE if jTalent is used at lTarget location by oCaster. // Checks the talent type and cast the correct spell. For items it checks uses. int ai_UseTalentAtLocation(object oCaster, json jTalent, object oTarget, int nInMelee); // Return TRUE if oCreature uses jTalent on oTarget after checking special cases. int ai_CheckSpecialTalentsandUse(object oCreature, json jTalent, string sCategory, int nInMelee, object oTarget); int ai_TryHealingTalent(object oCreature, int nInMelee, object oTarget = OBJECT_INVALID) { // First lets evaluate oTarget and see how strong of a spell we will need. if(oTarget != OBJECT_INVALID) { if(oTarget == oCreature) { if(ai_GetAIMode(oCreature, AI_MODE_SELF_HEALING_OFF)) return FALSE; } else if(ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) return FALSE; } // We don't have a target so lets go check for one. else { if(!ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) { // Lets not run past an enemy to heal unless we have the feats, bad tactics! float fRange; if(ai_CanIMoveInCombat(oCreature)) fRange = AI_RANGE_PERCEPTION; else { fRange = GetDistanceBetween(oCreature, GetLocalObject(oCreature, AI_ENEMY_NEAREST)) - 3.0f; // Looks bad when your right next to an ally, but technically the enemy is closer. if(fRange < AI_RANGE_MELEE) fRange = AI_RANGE_MELEE; } oTarget = ai_GetAllyToHealTarget(oCreature, fRange); } else oTarget = oCreature; if(oTarget == OBJECT_INVALID) return FALSE; } // Should we ignore associates? if(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(oTarget) > 1) return FALSE; int nHp = ai_GetPercHPLoss(oTarget); int nHpLimit = ai_GetHealersHpLimit(oCreature); if(AI_DEBUG) ai_Debug("0i_talents", "256", "nHp: " + IntToString(nHp) + "< nHpLimit: " + IntToString(nHpLimit)); if(nHp > nHpLimit) return FALSE; int nDamage = GetMaxHitPoints(oTarget) - GetCurrentHitPoints(oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "260", GetName(oTarget) + " has lost " + IntToString(nDamage) + " hitpoints!"); // Do they have Lay on Hands? int bUseMagic = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC); if(bUseMagic && GetHasFeat(FEAT_LAY_ON_HANDS, oCreature)) { int nCanHeal = GetAbilityModifier(ABILITY_CHARISMA, oCreature) * ai_GetCharacterLevels(oCreature); if(nCanHeal <= nDamage) { ai_UseFeat(oCreature, FEAT_LAY_ON_HANDS, oTarget); return TRUE; } } int nMaxLevel = 9; // If they are about to die then throw caution to the wind and HEAL! if(nHp <= AI_HEALTH_BLOODY || nHp < 11) nInMelee = 0; if(ai_UseCreatureTalent(oCreature, AI_TALENT_HEALING, nInMelee, nMaxLevel, oTarget)) return TRUE; if(AI_DEBUG) ai_Debug("0i_talents", "275", GetName(oCreature) + " has no healing spells!" + " Cleric lvls: " + IntToString(GetLevelByClass(CLASS_TYPE_CLERIC, oCreature)) + " Sontaneous casting: " + IntToString(ai_GetMagicMode(oCreature, AI_MAGIC_NO_SPONTANEOUS_CURE))); if(bUseMagic && GetLevelByClass(CLASS_TYPE_CLERIC, oCreature) && !ai_GetMagicMode(oCreature, AI_MAGIC_NO_SPONTANEOUS_CURE)) { // We need to check our talents and see what spells we can convert. if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_ENHANCEMENT, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_PROTECTION, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_DISCRIMINANT_AOE, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_INDISCRIMINANT_AOE, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_TOUCH, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_RANGED, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_SUMMON, nInMelee, nDamage, oTarget)) return TRUE; if(ai_UseSpontaneousCureTalentFromCategory(oCreature, AI_TALENT_CURE, nInMelee, nDamage, oTarget)) return TRUE; } return FALSE; } int ai_CheckTargetVsConditions(object oTarget, json jTalent, int nConditions) { // Check nCondition for any negative effects based on the talent we have. switch(JsonGetInt(JsonArrayGet(jTalent, 1))) { case SPELL_NEUTRALIZE_POISON : if(ai_GetHasNegativeCondition(AI_CONDITION_POISON, nConditions)) return TRUE; break; case SPELL_REMOVE_DISEASE : if(ai_GetHasNegativeCondition(AI_CONDITION_DISEASE, nConditions)) return TRUE; break; case SPELL_REMOVE_BLINDNESS_AND_DEAFNESS : if(ai_GetHasNegativeCondition(AI_CONDITION_BLINDDEAF, nConditions)) return TRUE; break; case SPELL_REMOVE_FEAR : if(ai_GetHasNegativeCondition(AI_CONDITION_FRIGHTENED, nConditions)) return TRUE; break; case SPELL_REMOVE_CURSE : if(ai_GetHasNegativeCondition(AI_CONDITION_CURSE, nConditions)) return TRUE; break; case SPELL_REMOVE_PARALYSIS : if(ai_GetHasNegativeCondition(AI_CONDITION_PARALYZE, nConditions)) return TRUE; break; case SPELL_CLARITY : if(ai_GetHasNegativeCondition(AI_CONDITION_DAZED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_CHARMED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_CONFUSED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_STUNNED, nConditions)) return TRUE; break; case SPELL_GREATER_RESTORATION : if(ai_GetHasNegativeCondition(AI_CONDITION_DAZED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_CONFUSED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_DOMINATED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_SLOW, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_FRIGHTENED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_STUNNED, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_CHARMED, nConditions)) return TRUE; case SPELL_RESTORATION : if(ai_GetHasNegativeCondition(AI_CONDITION_LEVEL_DRAIN, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_BLINDDEAF, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_PARALYZE, nConditions)) return TRUE; case SPELL_LESSER_RESTORATION : if(ai_GetHasNegativeCondition(AI_CONDITION_ABILITY_DRAIN, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_SAVE_DECREASE, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_SR_DECREASE, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_SKILL_DECREASE, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_AC_DECREASE , nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_ATK_DECREASE, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_DMG_DECREASE, nConditions)) return TRUE; if(ai_GetHasNegativeCondition(AI_CONDITION_DMG_I_DECREASE, nConditions)) return TRUE; } return FALSE; } int ai_CheckTalentsVsConditions(object oCreature, int nConditions, int nInMelee, int nLevel, object oTarget) { // Get the saved category from oCreature. json jCategory = GetLocalJson(oCreature, AI_TALENT_CURE); if(AI_DEBUG) ai_Debug("0i_talents", "357", "jCategory: " + AI_TALENT_CURE + " " + JsonDump(jCategory, 2)); if(JsonGetType(jCategory) == JSON_TYPE_NULL) { SetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE, -1); return FALSE; } // Get the max talent level so we can skip the higher ones and save time. int nMaxTalentLevel = GetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE); if(AI_DEBUG) ai_Debug("0i_talents", "365", AI_MAX_TALENT + AI_TALENT_CURE + ": " + IntToString(nMaxTalentLevel) + " nLevel: " + IntToString(nLevel)); if(nMaxTalentLevel < nLevel) nLevel = nMaxTalentLevel; if(nLevel < 0 || nLevel > 10) nLevel = 9; json jLevel, jTalent; int nClass, nSlot, nType, nSlotIndex, nMaxSlotIndex, nTalentUsed; int bUseMagic = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC); int bUseMagicItems = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC_ITEMS); if(AI_DEBUG) ai_Debug("0i_talents", "374", "bUseMagic: " + IntToString(bUseMagic) + " bUseMagicItems: " + IntToString(bUseMagicItems)); // Loop through nLevels down to 0 looking for the first talent (i.e. the highest). while(nLevel >= 0) { // Get the array of nLevel cycling down to 0. jLevel = JsonArrayGet(jCategory, nLevel); nMaxSlotIndex = JsonGetLength(jLevel); if(AI_DEBUG) ai_Debug("0i_talents", "382", "nLevel: " + IntToString(nLevel) + " nMaxSlotIndex: " + IntToString(nMaxSlotIndex)); if(nMaxSlotIndex > 0) { // Get the talent within nLevel cycling from the first to the last. nSlotIndex = 0; while (nSlotIndex <= nMaxSlotIndex) { jTalent= JsonArrayGet(jLevel, nSlotIndex); if(AI_DEBUG) ai_Debug("0i_talents", "391", "nSlotIndex: " + IntToString(nSlotIndex) + " jTalent Type: " + IntToString(JsonGetType(jTalent))); // Check to see if the talent matches oTargets nConditionss. if(ai_CheckTargetVsConditions(oTarget, jTalent, nConditions)) { nType = JsonGetInt(JsonArrayGet(jTalent, 0)); if(bUseMagic) { if(nType == AI_TALENT_TYPE_SPELL) { if(ai_CastInMelee(oCreature, JsonGetInt(JsonArrayGet(jTalent, 1)), nInMelee)) { nTalentUsed = ai_UseCreatureSpellTalent(oCreature, jLevel, jTalent, AI_TALENT_CURE, nInMelee, oTarget); // -1 means it was a memorized spell and we need to remove it. if(nTalentUsed == -1) { ai_RemoveTalent(oCreature, jCategory, jLevel, AI_TALENT_CURE, nLevel, nSlotIndex); return TRUE; } else if(nTalentUsed) return TRUE; } } else if(nType == AI_TALENT_TYPE_SP_ABILITY) { // Special ability spells do not need to concentrate?! if(ai_CheckSpecialTalentsandUse(oCreature, jTalent, AI_TALENT_CURE, nInMelee, oTarget)) { // When the ability is used that slot is now not readied. // Multiple uses of the same spell are stored in different slots. ai_RemoveTalent(oCreature, jCategory, jLevel, AI_TALENT_CURE, nLevel, nSlotIndex); return TRUE; } } } if(bUseMagicItems && nType == AI_TALENT_TYPE_ITEM) { // Items do not need to concentrate. if(ai_UseCreatureItemTalent(oCreature, jLevel, jTalent, AI_TALENT_CURE, nInMelee, oTarget)) { if(AI_DEBUG) ai_Debug("0i_talents", "430", "Checking if Item is used up: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 4)))); if(JsonGetInt(JsonArrayGet(jTalent, 4)) == -1) { ai_RemoveTalent(oCreature, jCategory, jLevel, AI_TALENT_CURE, nLevel, nSlotIndex); } return TRUE; } } } nSlotIndex++; } } else SetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE, nLevel - 1); nLevel--; } return FALSE; } int ai_TryCureConditionTalent(object oCreature, int nInMelee, object oTarget = OBJECT_INVALID) { // Is Casting Cure spells off? if(ai_GetMagicMode(oCreature, AI_MAGIC_CURE_SPELLS_OFF)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "450", AI_MAX_TALENT + AI_TALENT_CURE + ": " + IntToString(GetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE))); // If the creature doesn't have cure talents then we set it to -1. if(GetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE) == -1) return FALSE; // We check targets to see if they need to be cured. int nNegativeConditions, nTargetNegConds, nIndex, nCnt = 1; object oTarget; if(oTarget == OBJECT_INVALID) { oTarget = GetLocalObject(oCreature, AI_ALLY + "1"); while(oTarget != OBJECT_INVALID) { nTargetNegConds = ai_GetNegativeConditions(oTarget); // Should we ignore associates? if(!ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) || GetAssociateType(oTarget) < 2) { if(nNegativeConditions < nTargetNegConds) { nNegativeConditions = nTargetNegConds; nIndex = nCnt; } } oTarget = GetLocalObject(oCreature, AI_ALLY + IntToString(++nCnt)); } // No one has a negative condition then get out. if(!nNegativeConditions) return FALSE; oTarget = GetLocalObject(oCreature, AI_ALLY + IntToString(nIndex)); } else { nNegativeConditions = ai_GetNegativeConditions(oTarget); if(!nNegativeConditions) return FALSE; } if(oTarget == oCreature) { if(ai_GetAIMode(oCreature, AI_MODE_SELF_HEALING_OFF)) return FALSE; } else if(ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "489", "nNegativeConditions: " + IntToString(nNegativeConditions) + " on " + GetName(oTarget)); if(ai_CheckTalentsVsConditions(oCreature, nNegativeConditions, nInMelee, 9, oTarget)) return TRUE; return FALSE; } // ***************************************************************************** // ************************* Try * Defensive Talents *************************** // ***************************************************************************** // These functions try to find and use a specific set of talents intelligently. int ai_TryDefensiveTalents(object oCreature, int nInMelee, int nMaxLevel, int nRound = 0, object oTarget = OBJECT_INVALID) { // Summons are powerfull and should be used as much as possible. if(ai_UseCreatureTalent(oCreature, AI_TALENT_SUMMON, nInMelee, nMaxLevel, oTarget)) return TRUE; // Added to reduce casting defensive talents later in combat and constantly. if(nRound >= d8()) return FALSE; // Try to mix them up so we don't always cast spells in the same order. int nRoll = d2(); if(AI_DEBUG) ai_Debug("0i_talents", "507", "Lets help someone(Check Talents: " +IntToString(nRoll) + " nMaxLevel: " + IntToString(nMaxLevel) + ")!"); if(nRoll == 1) { if(ai_UseCreatureTalent(oCreature, AI_TALENT_ENHANCEMENT, nInMelee, nMaxLevel, oTarget)) return TRUE; if(ai_UseCreatureTalent(oCreature, AI_TALENT_PROTECTION, nInMelee, nMaxLevel, oTarget)) return TRUE; } else if(nRoll == 2) { if(ai_UseCreatureTalent(oCreature, AI_TALENT_PROTECTION, nInMelee, nMaxLevel, oTarget)) return TRUE; if(ai_UseCreatureTalent(oCreature, AI_TALENT_ENHANCEMENT, nInMelee, nMaxLevel, oTarget)) return TRUE; } return FALSE; } void ai_SetAura(object oCreature) { // Cycle through a creatures special abilities and use any auras. int bCanUse, nIndex = 0, nMaxSpAbility = GetSpellAbilityCount(oCreature); int nSpell = GetSpellAbilitySpell(oCreature, nIndex); while(nIndex < nMaxSpAbility) { bCanUse = FALSE; if(GetSpellAbilityReady(oCreature, nIndex)) { if(nSpell == SPELLABILITY_AURA_BLINDING) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_COLD) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_ELECTRICITY) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_FEAR) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_FIRE) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_HORRIFICAPPEARANCE) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_MENACE) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_HORRIFICAPPEARANCE) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_PROTECTION) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_STUN) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_UNEARTHLY_VISAGE) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_UNNATURAL) bCanUse = TRUE; else if(nSpell == SPELLABILITY_AURA_HORRIFICAPPEARANCE) bCanUse = TRUE; else if(nSpell == 306 /*SPELLABILITY_AURA_TYRANT_FOG_MIST*/) bCanUse = TRUE; else if(nSpell == 412 /*SPELLABILITY_AURA_DRAGON_FEAR*/) bCanUse = TRUE; else if(nSpell == 761 /*SPELLABILITY_AURA_HELLFIRE*/) bCanUse = TRUE; else if(nSpell == 805/*SPELLABILITY_AURA_TROGLODYTE_STENCH*/) bCanUse = TRUE; } if(bCanUse) ActionCastSpellAtObject(nSpell, oCreature, 255, FALSE, 0, 0, TRUE); nSpell = GetSpellAbilitySpell(oCreature, ++nIndex); } } // ***************************************************************************** // ************************* Try * Skills ************************************** // ***************************************************************************** // These functions try to find and use a specific set of skills intelligently. void ai_UseSkill(object oCreature, int nSkill, object oTarget) { ai_SetLastAction(oCreature, AI_LAST_ACTION_USED_SKILL); if(GetIsEnemy(oTarget, oCreature)) SetLocalObject(oCreature, AI_ATTACKED_PHYSICAL, oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "498", GetName(oCreature) + " is using skill: " + GetStringByStrRef(StringToInt(Get2DAString("skills", "Name", nSkill))) + " on " + GetName(oTarget)); ActionUseSkill(nSkill, oTarget); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); } int ai_TryParry(object oCreature) { // Only use parry on an active melee attacker object oTarget = GetLastHostileActor(oCreature); // If we are already in parry mode then lets keep it up. if(GetActionMode(oCreature, ACTION_MODE_PARRY) && GetCurrentAction(oCreature) == ACTION_ATTACKOBJECT) return TRUE; if(oTarget == OBJECT_INVALID || ai_GetAttackedTarget(oTarget) != oCreature || !ai_GetIsMeleeWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget))) return FALSE; // Only if our parry skill > their attack bonus + 5 + d10 // Parry has a -4 atk adjustment. Our chance to hit should be 75% + d10. // EnemyAtk(20) - OurParrySkill(10) = 0 + d10(75% to 25% chance to hit). int nParrySkill = GetSkillRank(SKILL_PARRY, oCreature); int nAtk = ai_GetCreatureAttackBonus(oTarget); if(nAtk - nParrySkill >= 0 + d10()) return FALSE; ai_EquipBestMeleeWeapon(oCreature, oTarget); SetActionMode(oCreature, ACTION_MODE_PARRY, TRUE); ai_SetLastAction(oCreature, AI_LAST_ACTION_USED_SKILL); ActionAttack(oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "524", "Using parry against " + GetName(oTarget) + "!"); return TRUE; } int ai_TryTaunt(object oCreature, object oTarget) { int nCoolDown = GetLocalInt(oCreature, "AI_TAUNT_COOLDOWN"); if(AI_DEBUG) ai_Debug("0i_talents", "530", "Has Taunt Effect? " + IntToString(ai_GetHasEffectType(oTarget, EFFECT_TYPE_TAUNT)) + " Cooldown: " + IntToString(nCoolDown)); if(nCoolDown > 0) { SetLocalInt(oCreature, "AI_TAUNT_COOLDOWN", --nCoolDown); return FALSE; } if(!ai_GetHasEffectType(oTarget, EFFECT_TYPE_TAUNT)) return FALSE; // Check to see if we have a good chance for it to work. int nTauntRnk = GetSkillRank(SKILL_TAUNT, oCreature); if(AI_DEBUG) ai_Debug("0i_talents", "542", "Check Taunt: TauntRnk: " + IntToString(nTauntRnk) + " HitDice + 1: " + IntToString(GetHitDice(oCreature) + 1) + " Concentration: " + IntToString(GetSkillRank(SKILL_CONCENTRATION, oTarget)) + "."); int nConcentration = GetSkillRank(SKILL_CONCENTRATION, oTarget); // Our chance is greater than 50%. if(nTauntRnk <= nConcentration) return FALSE; ai_UseSkill(oCreature, SKILL_TAUNT, oTarget); SetLocalInt(oCreature, "AI_TAUNT_COOLDOWN", AI_TAUNT_COOLDOWN); return TRUE; } int ai_TryAnimalEmpathy(object oCreature, object oTarget = OBJECT_INVALID) { if(!GetSkillRank(SKILL_ANIMAL_EMPATHY, oCreature)) return FALSE; int nCoolDown = GetLocalInt(oCreature, "AI_EMPATHY_COOLDOWN"); if(AI_DEBUG) ai_Debug("0i_talents", "556", "Has Dominate Effect? " + IntToString(ai_GetHasEffectType(oTarget, EFFECT_TYPE_DOMINATED)) + " Cooldown: " + IntToString(nCoolDown)); if(nCoolDown > 0) { SetLocalInt(oCreature, "AI_EMPATHY_COOLDOWN", --nCoolDown); return FALSE; } if(oTarget == OBJECT_INVALID) { oTarget = ai_GetNearestRacialTarget(oCreature, AI_RACIAL_TYPE_ANIMAL_BEAST); if(oTarget == OBJECT_INVALID) return FALSE; } if(!GetObjectSeen(oCreature, oTarget)) return FALSE; if(ai_GetHasEffectType(oTarget, EFFECT_TYPE_DOMINATED) || GetIsImmune(oTarget, IMMUNITY_TYPE_MIND_SPELLS) || GetIsImmune(oTarget, IMMUNITY_TYPE_DOMINATE) || GetAssociateType(oTarget) != ASSOCIATE_TYPE_NONE) return FALSE; // Get the race of the target, it only works on Animals, Beasts, and Magical Beasts. int nRace = GetRacialType(oTarget); int nDC; if(nRace == RACIAL_TYPE_ANIMAL) nDC = 5; else if(nRace == RACIAL_TYPE_BEAST || nRace == RACIAL_TYPE_MAGICAL_BEAST) nDC = 9; else return FALSE; // Check to see if we have a good chance for it to work. int nEmpathyRnk = GetSkillRank(SKILL_ANIMAL_EMPATHY, oCreature); nDC += GetHitDice(oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "632", "Check Animal Empathy: Rnk: " + IntToString(nEmpathyRnk) + " nDC: " + IntToString(nDC) + "."); // Our chance is greater than 50%. if(nEmpathyRnk <= nDC) return FALSE; ai_UseSkill(oCreature, SKILL_ANIMAL_EMPATHY, oTarget); SetLocalInt(oCreature, "AI_EMPATHY_COOLDOWN", AI_EMPATHY_COOLDOWN); return TRUE; } // ***************************************************************************** // ************************* Try * Feats *************************************** // ***************************************************************************** // These functions try to find and use a specific set of feats intelligently. void ai_UseFeat(object oCreature, int nFeat, object oTarget, int nSubFeat = 0) { ai_SetLastAction(oCreature, AI_LAST_ACTION_USED_FEAT); if(GetIsEnemy(oTarget, oCreature)) SetLocalObject(oCreature, AI_ATTACKED_PHYSICAL, oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "600", GetName(oCreature) + " is using feat: " + GetStringByStrRef(StringToInt(Get2DAString("feat", "FEAT", nFeat))) + " on " + GetName(oTarget)); ActionUseFeat(nFeat, oTarget, nSubFeat); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); } void ai_UseFeatAttackMode(object oCreature, int nActionMode, int nAction, object oTarget, int nInMelee = 0, int bPassive = FALSE) { if(AI_DEBUG) ai_Debug("0i_talents", "608", "Action mode (" + IntToString(nActionMode) + ") Is it set?: " + IntToString(GetActionMode(oCreature, nActionMode))); if(!GetActionMode(oCreature, nActionMode)) { if(AI_DEBUG) ai_Debug("0i_talents", "612", "Setting action mode: " + IntToString(nActionMode)); SetActionMode(oCreature, nActionMode, TRUE); SetLocalInt(oCreature, AI_CURRENT_ACTION_MODE, nActionMode); } ai_ActionAttack(oCreature, nAction, oTarget, nInMelee, bPassive, nActionMode); } int ai_TryBarbarianRageFeat(object oCreature) { // Must not have rage already, must have the feat, and enemy must be strong enough. if(GetHasFeatEffect(FEAT_BARBARIAN_RAGE, oCreature) || !GetHasFeat(FEAT_BARBARIAN_RAGE, oCreature)) return FALSE; ai_UseFeat(oCreature, FEAT_BARBARIAN_RAGE, oCreature); return TRUE; } int ai_TryBardSongFeat(object oCreature) { if(AI_DEBUG) ai_Debug("0i_talents", "629", "BardSong Effect: " + IntToString(GetHasSpellEffect(411/*SPELL_BARD_SONG*/)) + " Level: " + IntToString(GetLevelByClass(CLASS_TYPE_BARD)) + " HasFeat: " + IntToString(GetHasFeat(FEAT_BARD_SONGS))); if(GetHasSpellEffect(411/*SPELL_BARD_SONG*/, oCreature) || !GetHasFeat(FEAT_BARD_SONGS, oCreature)) return FALSE; ai_UseFeat(oCreature, FEAT_BARD_SONGS, oCreature); return TRUE; } int ai_TryCalledShotFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_CALLED_SHOT, oCreature)) return FALSE; // Called shot has a -4 to hit adjustment. if(!ai_AttackPenaltyOk(oCreature, oTarget, -4.0)) return FALSE; ai_UseFeat(oCreature, FEAT_CALLED_SHOT, oTarget); return TRUE; } int ai_TryDisarmFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_DISARM, oCreature)) return FALSE; // If we can't disarm them then get out! if(!GetIsCreatureDisarmable(oTarget)) return FALSE; int nEAC = GetAC(oTarget); int nOAtk = ai_GetCreatureAttackBonus(oCreature); // The combatant with the larger weapon gains +4 per size category. // Weapon Size in the baseitems.2da is 1 = Tiny, 2 = Small, 3 = Medium, 4 = Large. int nOWeaponType = GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND)); int nOWeaponSize = StringToInt(Get2DAString("baseitems", "WeaponSize", nOWeaponType)); int nEWeaponType = GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget)); int nEWeaponSize = StringToInt(Get2DAString("baseitems", "WeaponSize", nEWeaponType)); nOAtk +=(nOWeaponSize - nEWeaponSize) * 4; // Do they have Improved Disarm? if(GetHasFeat(FEAT_IMPROVED_DISARM, oCreature)) nOAtk += 2; // Disarm has a -6 atk adjustment. if(!ai_AttackPenaltyOk(oCreature, oTarget, -6.0)) return FALSE; ai_UseFeat(oCreature, FEAT_DISARM, oTarget); return TRUE; } int ai_TryDivineMightFeat(object oCreature, int nInMelee) { if(!GetHasFeat(FEAT_TURN_UNDEAD)) return FALSE; if(!GetHasFeat(FEAT_DIVINE_MIGHT)) return FALSE; if(GetHasFeatEffect(FEAT_DIVINE_MIGHT, oCreature)) return FALSE; if(!nInMelee) return FALSE; object oTarget = ai_GetEnemyAttackingMe(oCreature); if(oTarget == OBJECT_INVALID) return FALSE; float fAtkAdj = IntToFloat(GetAbilityModifier(ABILITY_CHARISMA, oCreature)); if(!ai_AttackBonusGood(oCreature, oTarget, fAtkAdj)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "722", "USING DIVINE MIGHT on " + GetName(oCreature) + "."); ai_UseFeat(oCreature, FEAT_DIVINE_MIGHT, oCreature); return TRUE; } int ai_TryDivineShieldFeat(object oCreature, int nInMelee) { if(!GetHasFeat(FEAT_TURN_UNDEAD)) return FALSE; if(!GetHasFeat(FEAT_DIVINE_SHIELD)) return FALSE; if(GetHasFeatEffect(FEAT_DIVINE_SHIELD, oCreature)) return FALSE; if(!nInMelee) return FALSE; object oTarget = ai_GetEnemyAttackingMe(oCreature); if(oTarget == OBJECT_INVALID) return FALSE; float fACAdj = IntToFloat(GetAbilityModifier(ABILITY_CHARISMA, oCreature)); if(!ai_ACAdjustmentGood(oCreature, oTarget, fACAdj)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "736", "USING DIVINE SHIELD on " + GetName(oCreature) + "."); ai_UseFeat(oCreature, FEAT_DIVINE_SHIELD, oCreature); return TRUE; } int ai_TryExpertiseFeat(object oCreature) { if(!GetHasFeat(FEAT_EXPERTISE, oCreature)) return FALSE; object oTarget = ai_GetEnemyAttackingMe(oCreature); // Expertise has a -5 atk and a +5 AC adjustment. if(oTarget == OBJECT_INVALID || !ai_AttackPenaltyOk(oCreature, oTarget, -5.0) || !ai_ACAdjustmentGood(oCreature, oTarget, 5.0)) { SetActionMode(oCreature, ACTION_MODE_EXPERTISE, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "704", "USING EXPERTISE on " + GetName(oTarget) + "."); ai_UseFeatAttackMode(oCreature, ACTION_MODE_EXPERTISE, AI_LAST_ACTION_MELEE_ATK, oTarget); return TRUE; } int ai_TryFlurryOfBlowsFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_FLURRY_OF_BLOWS, oCreature)) return FALSE; // Flurry of Blows has a -2 atk adjustment. if(!ai_AttackPenaltyOk(oCreature, oTarget, -2.0)) { SetActionMode(oCreature, ACTION_MODE_FLURRY_OF_BLOWS, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "718", "USING FLURRY OF BLOWS on " + GetName(oTarget) + "."); ai_UseFeatAttackMode(oCreature, ACTION_MODE_FLURRY_OF_BLOWS, AI_LAST_ACTION_MELEE_ATK, oTarget, TRUE); return TRUE; } int ai_TryImprovedExpertiseFeat(object oCreature) { if(!GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature)) return FALSE; object oTarget = ai_GetEnemyAttackingMe(oCreature); // Improved expertise has a -10 atk +10 AC adjustment. if(oTarget == OBJECT_INVALID || !ai_AttackPenaltyOk(oCreature, oTarget, -10.0) || !ai_ACAdjustmentGood(oCreature, oTarget, 10.0)) { SetActionMode(oCreature, ACTION_MODE_IMPROVED_EXPERTISE, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "735", "USING IMPROVED EXPERTISE on " + GetName(oTarget) + "."); ai_UseFeatAttackMode(oCreature, ACTION_MODE_IMPROVED_EXPERTISE, AI_LAST_ACTION_MELEE_ATK, oTarget); return TRUE; } int ai_TryImprovedPowerAttackFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_IMPROVED_POWER_ATTACK, oCreature)) return FALSE; // Improved Power Attack has a -10 atk adjustment. // If we cannot hit or will kill in one hit then maybe we should use Power Attack instead. if(ai_PowerAttackGood(oCreature, oTarget, 10.0)) { ai_UseFeatAttackMode(oCreature, ACTION_MODE_IMPROVED_POWER_ATTACK, AI_LAST_ACTION_MELEE_ATK, oTarget); return TRUE; } SetActionMode(oCreature, ACTION_MODE_IMPROVED_POWER_ATTACK, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return ai_TryPowerAttackFeat(oCreature, oTarget); } int ai_TryKiDamageFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_KI_DAMAGE, oCreature)) return FALSE; // Must have > 40 hitpoints AND // Damage reduction OR damage resistance // or just have over 200 hitpoints int bHasDamageReduction = FALSE; int bHasDamageResistance = FALSE; int bHasHitpoints = FALSE; int bHasMassiveHitpoints = FALSE; int bOutNumbered; int nCurrentHP = GetCurrentHitPoints(oTarget); if(nCurrentHP > 40) bHasHitpoints = TRUE; if(nCurrentHP > 200) bHasMassiveHitpoints = TRUE; if(ai_GetHasEffectType(oTarget, EFFECT_TYPE_DAMAGE_REDUCTION)) bHasDamageReduction = TRUE; if(ai_GetHasEffectType(oTarget, EFFECT_TYPE_DAMAGE_RESISTANCE)) bHasDamageResistance = TRUE; if(ai_GetNearestEnemy(oCreature, 3, 7, 7) != OBJECT_INVALID) bOutNumbered = TRUE; if((!bHasHitpoints || (!bHasDamageReduction && !bHasDamageResistance)) && (!bHasMassiveHitpoints) && (!bHasHitpoints || !bOutNumbered)) return FALSE; ai_UseFeat(oCreature, FEAT_KI_DAMAGE, oTarget); return TRUE; } int ai_TryKnockdownFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_KNOCKDOWN, oCreature)) return FALSE; int nMySize = GetCreatureSize(oCreature); if(GetHasFeat(FEAT_IMPROVED_KNOCKDOWN, oCreature)) nMySize++; // Prevent silly use of knockdown on immune or too-large targets. // Knockdown has a -4 atk adjustment. if(GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN) || GetCreatureSize(oTarget) > nMySize + 1 || !ai_AttackPenaltyOk(oCreature, oTarget, -4.0)) return FALSE; ai_UseFeat(oCreature, FEAT_KNOCKDOWN, oTarget); return TRUE; } int ai_TryPolymorphSelfFeat(object oCreature) { if(GetHasFeat(FEAT_EPIC_OUTSIDER_SHAPE)) { int nSubFeat = Random(3) + 733; // 733 azer, 734 rakshasa, 735 Slaad. if(ai_UseFeat(oCreature, FEAT_EPIC_OUTSIDER_SHAPE, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_EPIC_CONSTRUCT_SHAPE)) { int nSubFeat = Random(3) + 738; // 738 Stone, 739 Flesh, 740 Iron. if(ai_UseFeat(oCreature, FEAT_EPIC_CONSTRUCT_SHAPE, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_EPIC_WILD_SHAPE_DRAGON)) { int nSubFeat = Random(3) + 707; // 707 Red, 708 Blue, 709 Green. if(ai_UseFeat(oCreature, FEAT_EPIC_WILD_SHAPE_DRAGON, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_EPIC_WILD_SHAPE_UNDEAD)) { int nSubFeat = Random(3) + 704; // 704 Risen Lord, 705 Vampire, 706 Spectre. if(ai_UseFeat(oCreature, FEAT_EPIC_WILD_SHAPE_UNDEAD, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_4)) { int nSubFeat; int nRoll = d3(); if(nRoll == 1) nSubFeat = 679; // Medusa else if(nRoll == 2) nSubFeat = 691; // Mindflayer else nSubFeat = 694; // DireTiger if(ai_UseFeat(oCreature, FEAT_GREATER_WILDSHAPE_4, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_3)) { int nSubFeat; int nRoll = d3(); if(nRoll == 1) nSubFeat = 670; // Basilisk else if(nRoll == 2) nSubFeat = 673; // Drider else nSubFeat = 674; // Manticore if(ai_UseFeat(oCreature, FEAT_GREATER_WILDSHAPE_3, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_2)) { int nSubFeat; int nRoll = d3(); if(nRoll == 1) nSubFeat = 672; // Harpy else if(nRoll == 2) nSubFeat = 678; // Gargoyle else nSubFeat = 680; // Minotaur if(ai_UseFeat(oCreature, FEAT_GREATER_WILDSHAPE_2, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_1)) { int nSubFeat = Random(5) + 658; // Wyrmling if(ai_UseFeat(oCreature, FEAT_GREATER_WILDSHAPE_1, oCreature, nSubFeat)) return TRUE; } if(GetHasFeat(FEAT_HUMANOID_SHAPE)) { int nSubFeat = Random(3) + 682; // 682 Drow, 683 Lizard, 684 Kobold. if(ai_UseFeat(oCreature, FEAT_HUMANOID_SHAPE, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_ELEMENTAL_SHAPE)) { int nSubFeat = Random(4) + SUBFEAT_ELEMENTAL_SHAPE_EARTH; if(ai_UseFeat(oCreature, FEAT_ELEMENTAL_SHAPE, oCreature, nSubFeat)) return TRUE; } else if(GetHasFeat(FEAT_WILD_SHAPE)) { int nSubFeat; int nCompanionType = GetAnimalCompanionCreatureType(oCreature); if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_NONE) nSubFeat = Random(5) + SUBFEAT_WILD_SHAPE_BROWN_BEAR; else { if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_BADGER) nSubFeat = SUBFEAT_WILD_SHAPE_BADGER; else if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_BOAR) nSubFeat = SUBFEAT_WILD_SHAPE_BOAR; else if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_BEAR) nSubFeat = SUBFEAT_WILD_SHAPE_BROWN_BEAR; else if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_PANTHER) nSubFeat = SUBFEAT_WILD_SHAPE_PANTHER; else if(nCompanionType == ANIMAL_COMPANION_CREATURE_TYPE_WOLF) nSubFeat = SUBFEAT_WILD_SHAPE_WOLF; else nSubFeat = Random(5) + SUBFEAT_WILD_SHAPE_BROWN_BEAR; } if(AI_DEBUG) ai_Debug("0i_talents", "885", " Using wild shape feat: " + IntToString(nSubFeat)); ai_UseFeat(oCreature, FEAT_WILD_SHAPE, oCreature, nSubFeat); return TRUE; } return FALSE; } int ai_TryPowerAttackFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_POWER_ATTACK, oCreature)) return FALSE; // Power Attack has a -5 atk adjustment. if(ai_PowerAttackGood(oCreature, oTarget, 5.0)) { ai_UseFeatAttackMode(oCreature, ACTION_MODE_POWER_ATTACK, AI_LAST_ACTION_MELEE_ATK, oTarget); return TRUE; } SetActionMode(oCreature, ACTION_MODE_POWER_ATTACK, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return FALSE; } int ai_TryQuiveringPalmFeat(object oCreature, object oTarget) { // Must have the feat, and enemy must be lower level, and not immune to crits. if(!GetHasFeat(FEAT_QUIVERING_PALM, oCreature) || GetHitDice(oTarget) >= GetHitDice(oCreature) || GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT)) return FALSE; ai_UseFeat(oCreature, FEAT_QUIVERING_PALM, oTarget); return TRUE; } int ai_TryRapidShotFeat(object oCreature, object oTarget, int nInMelee) { if(!GetHasFeat(FEAT_RAPID_SHOT, oCreature)) return FALSE; // Rapidshot has a -4 atk adjustment. if(!ai_AttackPenaltyOk(oCreature, oTarget, -4.0)) { SetActionMode(oCreature, ACTION_MODE_RAPID_SHOT, FALSE); DeleteLocalInt(oCreature, AI_CURRENT_ACTION_MODE); return FALSE; } ai_UseFeatAttackMode(oCreature, ACTION_MODE_RAPID_SHOT, AI_LAST_ACTION_RANGED_ATK, oTarget, nInMelee, TRUE); return TRUE; } int ai_TrySapFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_SAP, oCreature)) return FALSE; // Does not work on creatures that cannot be hit by criticals or stunned. // Sap has a -4 atk adjustment. if(GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT) || GetIsImmune(oTarget, IMMUNITY_TYPE_STUN) || !ai_AttackPenaltyOk(oCreature, oTarget, -4.0)) return FALSE; ai_UseFeat(oCreature, FEAT_SAP, oTarget); return TRUE; } int ai_TrySmiteEvilFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_SMITE_EVIL, oCreature) || GetAlignmentGoodEvil(oTarget) != ALIGNMENT_EVIL || !ai_StrongOpponent(oCreature, oTarget)) return FALSE; ai_UseFeat(oCreature, FEAT_SMITE_EVIL, oTarget); return TRUE; } int ai_TrySmiteGoodFeat(object oCreature, object oTarget) { if(!GetHasFeat(FEAT_SMITE_GOOD, oCreature) || GetAlignmentGoodEvil(oTarget) != ALIGNMENT_GOOD || !ai_StrongOpponent(oCreature, oTarget)) return FALSE; ai_UseFeat(oCreature, FEAT_SMITE_GOOD, oTarget); return TRUE; } int ai_TryStunningFistFeat(object oCreature, object oTarget) { // Cannot use if we have a weapon equiped. if(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature) != OBJECT_INVALID) return FALSE; // Does not work on creatures that cannot be hit by criticals or stunned. // Stunning Fists has a -4 atk adjustment. if(!GetHasFeat(FEAT_STUNNING_FIST, oCreature) || GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT) || GetIsImmune(oTarget, IMMUNITY_TYPE_STUN) || !ai_StrongOpponent(oCreature, oTarget) || !ai_AttackPenaltyOk(oCreature, oTarget, -4.0)) return FALSE; ai_UseFeat(oCreature, FEAT_STUNNING_FIST, oTarget); return TRUE; } void ai_NameAssociate(object oCreature, int nAssociateType, string sName) { object oAssociate = GetAssociate(nAssociateType, oCreature); if(GetName(oCreature) != "") return; SetName(oAssociate, sName); ChangeFaction(oAssociate, oCreature); } int ai_TrySummonAnimalCompanionTalent(object oCreature) { if(!GetHasFeat(FEAT_ANIMAL_COMPANION, oCreature)) return FALSE; if(GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, oCreature) != OBJECT_INVALID) return FALSE; ai_UseFeat(oCreature, FEAT_ANIMAL_COMPANION, oCreature); DelayCommand(0.0, ai_NameAssociate(oCreature, ASSOCIATE_TYPE_FAMILIAR, "Animal Companion")); return TRUE; } int ai_TrySummonFamiliarTalent(object oCreature) { if(!GetHasFeat(FEAT_SUMMON_FAMILIAR, oCreature)) return FALSE; if(GetAssociate(ASSOCIATE_TYPE_FAMILIAR, oCreature) != OBJECT_INVALID) return FALSE; ai_UseFeat(oCreature, FEAT_SUMMON_FAMILIAR, oCreature); DelayCommand(0.0, ai_NameAssociate(oCreature, ASSOCIATE_TYPE_FAMILIAR, "Familiar")); return TRUE; } int ai_TryLayOnHands(object oCreature) { if(!GetHasFeat(FEAT_LAY_ON_HANDS, oCreature)) return FALSE; // Lets not run past an enemy to use touch atk unless we have the feats, bad tactics! float fRange; if(ai_CanIMoveInCombat(oCreature)) fRange = AI_RANGE_PERCEPTION; else { fRange = GetDistanceBetween(oCreature, GetLocalObject(oCreature, AI_ENEMY_NEAREST)) - 3.0f; // Looks bad when your right next to an ally, but technically the enemy is closer. if(fRange < AI_RANGE_MELEE) fRange = AI_RANGE_MELEE; } object oTarget = ai_GetLowestCRRacialTarget(oCreature, RACIAL_TYPE_UNDEAD, fRange); if(oTarget == OBJECT_INVALID) return FALSE; ai_UseFeat(oCreature, FEAT_LAY_ON_HANDS, oTarget); return TRUE; } int ai_TryTurningTalent(object oCreature) { if(!GetHasFeat(FEAT_TURN_UNDEAD, oCreature)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "1043", "Checking for Turning Targets."); int nHDCount, nHDCount2, nRacial, nHD; // Get characters levels. int nClericLevel = GetLevelByClass(CLASS_TYPE_CLERIC, oCreature); int nPaladinLevel = GetLevelByClass(CLASS_TYPE_PALADIN, oCreature); int nBlackguardlevel = GetLevelByClass(CLASS_TYPE_BLACKGUARD, oCreature); int nTotalLevel = GetHitDice(oCreature); int nTurnLevel = nClericLevel; int nClassLevel = nClericLevel; // GZ: Since paladin levels stack when turning, blackguard levels should stack as well // GZ: but not with the paladin levels (thus else if). if(nBlackguardlevel - 2 > 0 && nBlackguardlevel > nPaladinLevel) { nClassLevel += (nBlackguardlevel - 2); nTurnLevel += (nBlackguardlevel - 2); } else if(nPaladinLevel - 2 > 0) { nClassLevel += (nPaladinLevel - 2); nTurnLevel += (nPaladinLevel - 2); } //Flags for bonus turning types int nElemental = GetHasFeat(FEAT_AIR_DOMAIN_POWER, oCreature) + GetHasFeat(FEAT_EARTH_DOMAIN_POWER, oCreature) + GetHasFeat(FEAT_FIRE_DOMAIN_POWER, oCreature) + GetHasFeat(FEAT_WATER_DOMAIN_POWER, oCreature); int nVermin = GetHasFeat(FEAT_PLANT_DOMAIN_POWER, oCreature); int nConstructs = GetHasFeat(FEAT_DESTRUCTION_DOMAIN_POWER, oCreature); int nGoodOrEvilDomain = GetHasFeat(FEAT_GOOD_DOMAIN_POWER, oCreature) + GetHasFeat(FEAT_EVIL_DOMAIN_POWER, oCreature); int nPlanar = GetHasFeat(854, oCreature); // Get turning check average, modify if have the Sun Domain int nChrMod = GetAbilityModifier(ABILITY_CHARISMA, oCreature); int nTurnCheck = 15 + nChrMod; //The roll to apply to the max HD of undead that can be turned --> nTurnLevel int nTurnHD = 12 + nChrMod + nClassLevel; //The number of HD of undead that can be turned. if(GetHasFeat(FEAT_SUN_DOMAIN_POWER, oCreature)) { nTurnCheck += 2; nTurnHD += 3; } //Determine the maximum HD of the undead that can be turned using a roll of 15 + ChrMod. if(nTurnCheck == 15) nTurnLevel += 1; else if(nTurnCheck >= 16 && nTurnCheck <= 18) nTurnLevel += 2; else if(nTurnCheck >= 19 && nTurnCheck <= 21) nTurnLevel += 3; else if(nTurnCheck >= 22) nTurnLevel += 4; // Collect the number of HitDice we will affect. int nCnt = 1; object oEnemy = GetNearestCreature(7, 7, oCreature, nCnt); while(oEnemy != OBJECT_INVALID && nHDCount < nTurnHD && GetDistanceBetween(oEnemy, oCreature) <= 20.0) { if(GetIsEnemy(oEnemy, oCreature) && !ai_Disabled(oEnemy)) { nRacial = GetRacialType(oEnemy); nHD = 0; if(nRacial == RACIAL_TYPE_UNDEAD) nHD = GetHitDice(oEnemy) + GetTurnResistanceHD(oEnemy); else if(nRacial == RACIAL_TYPE_OUTSIDER && nGoodOrEvilDomain + nPlanar > 0) { //Planar turning decreases spell resistance against turning by 1/2 if(nPlanar) nHD = GetHitDice(oEnemy) + (GetSpellResistance(oEnemy) / 2); else nHD = GetHitDice(oEnemy) + GetSpellResistance(oEnemy); } else if(nRacial == RACIAL_TYPE_VERMIN && nVermin > 0) nHD = GetHitDice(oEnemy); else if(nRacial == RACIAL_TYPE_ELEMENTAL && nElemental > 0) nHD = GetHitDice(oEnemy); else if (nRacial == RACIAL_TYPE_CONSTRUCT && nConstructs > 0) nHD = GetHitDice(oEnemy); // Only count undead we can defeat! if(AI_DEBUG) ai_Debug("0i_talents", "1110", " nHD: " + IntToString(nHD) + " nTurnLevel: " + IntToString(nTurnLevel) + " nTurnHD: " + IntToString(nTurnHD) + " nHDCount: " + IntToString(nHDCount)); if(nHD > 0 && nHD <= nTurnLevel && nHD <= (nTurnHD - nHDCount)) nHDCount += nHD; } oEnemy = GetNearestCreature(7, 7, oCreature, ++nCnt); } if(AI_DEBUG) ai_Debug("0i_talents", "1089", "Found " + IntToString(nHDCount) + " hitdice to turn from my location."); // Lets do one more check to see if we can get a better position to use TurnUndead. nCnt = 1; object oNearestEnemy = GetLocalObject(oCreature, AI_ENEMY_NEAREST); if(GetDistanceBetween(oCreature, oNearestEnemy) > AI_RANGE_MELEE) { oEnemy = oNearestEnemy; if(AI_DEBUG) ai_Debug("0i_talents", "1126", GetName(oEnemy)); while(oEnemy != OBJECT_INVALID && nHDCount2 < nTurnHD && GetDistanceBetween(oEnemy, oNearestEnemy) <= 20.0) { if(AI_DEBUG) ai_Debug("0i_talents", "1129", GetName(oEnemy)); if(GetIsEnemy(oEnemy, oCreature) && !ai_Disabled(oEnemy)) { nRacial = GetRacialType(oEnemy); nHD = 0; if(nRacial == RACIAL_TYPE_UNDEAD) nHD = GetHitDice(oEnemy) + GetTurnResistanceHD(oEnemy); else if(nRacial == RACIAL_TYPE_OUTSIDER && nGoodOrEvilDomain + nPlanar > 0) { //Planar turning decreases spell resistance against turning by 1/2 if(nPlanar) nHD = GetHitDice(oEnemy) + (GetSpellResistance(oEnemy) / 2); else nHD = GetHitDice(oEnemy) + GetSpellResistance(oEnemy); } else if(nRacial == RACIAL_TYPE_VERMIN && nVermin > 0) nHD = GetHitDice(oEnemy); else if(nRacial == RACIAL_TYPE_ELEMENTAL && nElemental > 0) nHD = GetHitDice(oEnemy); else if (nRacial == RACIAL_TYPE_CONSTRUCT && nConstructs > 0) nHD = GetHitDice(oEnemy); // Only count undead we can defeat! if(AI_DEBUG) ai_Debug("0i_talents", "1140", " nHD: " + IntToString(nHD) + " nTurnLevel: " + IntToString(nTurnLevel) + " nTurnHD: " + IntToString(nTurnHD) + " nHDCount2: " + IntToString(nHDCount2)); if(nHD > 0 && nHD <= nTurnLevel && nHD <= (nTurnHD - nHDCount2)) nHDCount2 += nHD; } oEnemy = GetNearestCreature(7, 7, oNearestEnemy, ++nCnt); } } if(AI_DEBUG) ai_Debug("0i_talents", "1148", "Found " + IntToString(nHDCount2) + " hitdice to turn from enemy location."); if(nHDCount > nHDCount2) { if(AI_DEBUG) ai_Debug("0i_talents", "1176", " My Location - nHDCount: " + IntToString(nHDCount) + " >= nTurnHD / 2: " + IntToString(nTurnHD / 2)); if(nHDCount < nTurnHD / 2) return FALSE; ai_UseFeat(oCreature, FEAT_TURN_UNDEAD, oCreature); return TRUE; } else { if(AI_DEBUG) ai_Debug("0i_talents", "1184", " Better location - nHDCount2: " + IntToString(nHDCount2) + " >= nTurnHD / 2: " + IntToString(nTurnHD / 2)); if(nHDCount2 < nTurnHD / 2) return FALSE; ActionMoveToObject(oNearestEnemy, TRUE, 1.0f); ai_UseFeat(oCreature, FEAT_TURN_UNDEAD, oCreature); return TRUE; } return FALSE; } int ai_TryWhirlwindFeat(object oCreature) { if(!GetHasFeat(FEAT_WHIRLWIND_ATTACK, oCreature)) return FALSE; // Only worth using if there are 3+ targets. if(AI_DEBUG) ai_Debug("0i_talents", "860", "WHIRLWIND : NumOfEnemies: " + IntToString(ai_GetNumOfEnemiesInGroup(oCreature, 3.0)) + "."); // Shortened distance so its more effective(went from 5.0 to 2.0 and up to 3.0) if(ai_GetNumOfEnemiesInGroup(oCreature, 3.0) < d3() + 1) return FALSE; // * DO NOT WHIRLWIND if any of the targets are "large" or bigger // * it seldom works against such large opponents. // * Though its okay to use Improved Whirlwind against these targets if((!GetHasFeat(FEAT_IMPROVED_WHIRLWIND, oCreature)) || (GetCreatureSize(ai_GetNearestEnemy(oCreature, 1, 7, 7)) >= CREATURE_SIZE_LARGE && GetCreatureSize(ai_GetNearestEnemy(oCreature, 2, 7, 7)) >= CREATURE_SIZE_LARGE)) ai_UseFeat(oCreature, FEAT_WHIRLWIND_ATTACK, oCreature); return TRUE; } int ai_TryWholenessOfBodyFeat(object oCreature) { if(!GetHasFeat(FEAT_WHOLENESS_OF_BODY, oCreature)) return FALSE; // Get when we are suppose to heal base off conversation with PC or // on spawn generation. int nHp = ai_GetPercHPLoss(oCreature); if(nHp >= AI_HEALTH_WOUNDED) return FALSE; ai_UseFeat(oCreature, FEAT_WHOLENESS_OF_BODY, oCreature); return TRUE; } // ***************************************************************************** // ******************** Try Physical Attack Talents **************************** // ***************************************************************************** // These functions try to find and use physical attack talents intelligently. void ai_ActionAttack(object oCreature, int nAction, object oTarget, int nInMelee = 0, int bPassive = FALSE, int nActionMode = 0) { // If we are doing a ranged attack then check our position on the battlefield. if(nAction == AI_LAST_ACTION_RANGED_ATK && ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nAction)) return; ai_SetLastAction(oCreature, nAction); SetLocalObject(oCreature, AI_ATTACKED_PHYSICAL, oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "894", GetName(oCreature) + " is attacking(" + IntToString(nAction) + ") " + GetName(oTarget) + " Current Action: " + IntToString(GetCurrentAction(oCreature)) + " Lastround Attacked Target: " + GetName(ai_GetAttackedTarget(oCreature)) + " bPassive: " + IntToString(bPassive) + " nActionMode: " + IntToString(nActionMode)); ActionAttack(oTarget, bPassive); if(nActionMode == 0) ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); } void ai_FlyToAttacks(object oCreature, object oTarget) { ai_TryWingAttacks(oCreature); // If we don't do a Tail sweep attack then see if we can do a Tail slap! if(!ai_TryTailSweepAttack(oCreature)) ai_TryTailSlap(oCreature); ai_ActionAttack(oCreature, AI_LAST_ACTION_MELEE_ATK, oTarget); } void ai_FlyToTarget(object oCreature, object oTarget) { if(AI_DEBUG) ai_Debug("0i_talents", "908", GetName(OBJECT_SELF) + " is flying to " + GetName(oTarget) + "!"); effect eFly = EffectDisappearAppear(GetLocation(oTarget)); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFly, oCreature, 3.0f); DelayCommand(4.0f, ai_FlyToAttacks(oCreature, oTarget)); // Used to make creature wait before starting its next round. SetLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS, 5); } int ai_TryDragonBreathAttack(object oCreature, int nRound, object oTarget = OBJECT_INVALID) { int nCnt = GetLocalInt(oCreature, "AI_DRAGONS_BREATH"); if(AI_DEBUG) ai_Debug("0i_talents", "918", "Try Dragon Breath Attack: nRound(" + IntToString(nRound) + ")" + " <= nCnt(" + IntToString(nCnt) + ")!"); if(nRound <= nCnt) return FALSE; talent tUse = GetCreatureTalentBest(TALENT_CATEGORY_DRAGONS_BREATH, 20, oCreature); if(!GetIsTalentValid(tUse)) return FALSE; if(oTarget == OBJECT_INVALID) { string sIndex = IntToString(ai_GetHighestMeleeIndexNotInAOE(oCreature)); oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex); if(oTarget == OBJECT_INVALID) return FALSE; } SetLocalInt(oCreature, "AI_DRAGONS_BREATH", d4() + nRound); ActionCastSpellAtObject(GetIdFromTalent(tUse), oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "1019", GetName(oCreature) + " breaths on " + GetName(oTarget) + "!"); return TRUE; } void ai_DragonMeleeAttack(object oCreature, object oTarget, string sDmgDice, string sText) { if(AI_DEBUG) ai_Debug("0i_talents", "941", "oAttacker: " + GetName(oCreature) + " oTarget: " + GetName(oTarget)); int nDmg, nCheck, nAB = ai_GetCreatureAttackBonus(oCreature) - 5; int nAC = GetAC(oTarget); int nRoll = d20(); string sHit; // nCheck is a hit if nCheck > -1 and a miss if < 0; if(nRoll == 20) nCheck = 20; // We add one to the check so a equal result is still a hit. else if(nRoll > 1) nCheck = nRoll + nAB - nAC + 1; else nCheck == 0; if(nCheck > 0) { nDmg = ai_RollDiceString(sDmgDice); if(nCheck == 20) nDmg = nDmg * 2; } if(nCheck > 0) sHit = "*hit*"; else sHit = "*miss*"; string sMessage = ai_AddColorToText(GetName(oCreature) + "'s", AI_COLOR_LIGHT_MAGENTA) + ai_AddColorToText(sText + "attacks " + GetName(oTarget) + " : " + sHit + " :(" + IntToString(nRoll) + " + " + IntToString(nAB) + " = " + IntToString(nRoll + nAB) + ")", AI_COLOR_DARK_ORANGE); if(ai_GetIsCharacter(oTarget)) SendMessageToPC(oCreature, sMessage); if(ai_GetIsCharacter(oTarget)) SendMessageToPC(oTarget, sMessage); if(AI_DEBUG) ai_Debug("0i_talents", "965", "nAB: " + IntToString(nAB) + " nAC: " + IntToString(nAC) + " nRoll: " + IntToString(nRoll) + " nCheck: " + IntToString(nCheck) + " nDmg: " + IntToString(nDmg)); if(nCheck <= 0) return; // Apply any damage to the target! effect eDmg = EffectDamage(nDmg, DAMAGE_TYPE_BLUDGEONING); ApplyEffectToObject(DURATION_TYPE_INSTANT, eDmg, oTarget); } // Checks to see if a dragon can use its wings on a nearby enemy. // Checks the right side and then the left side to see if it can attack. int ai_TryWingAttacks(object oCreature) { if(AI_DEBUG) ai_Debug("0i_talents", "977", GetName(oCreature) + " is checking for wing Attacks!"); // Only Medium size dragons can use thier wings in combat. // We use HitDice to base size S:1-5, M:6-11, L:12-17, H:18-29, G:30-39, C:40+. int nHitDice = GetHitDice(oCreature); if(nHitDice <= 5) return FALSE; int nDragonSize; string sDmgDice, sMessage; float fSize; // Get the stats based on the size of the dragon. if(nHitDice < 12) { fSize = 5.0f; nDragonSize = 3; sDmgDice = "1d4"; } // Medium else if(nHitDice < 18) { fSize = 10.0f; nDragonSize = 4; sDmgDice = "1d6"; } // Large else if(nHitDice < 30) { fSize = 10.0f; nDragonSize = 5; sDmgDice = "1d8"; } // Huge else if(nHitDice < 40) { fSize = 15.0f; nDragonSize = 6; sDmgDice = "2d6"; } // Gargantuan else { fSize = 15.0f; nDragonSize = 7; sDmgDice = "2d8"; } // Colossal // Add half the dragons strength modifier. int nDmg = GetAbilityModifier(ABILITY_STRENGTH, oCreature); if(nDmg > 0) sDmgDice = sDmgDice + "+" + IntToString(nDmg / 2); if(AI_DEBUG) ai_Debug("0i_talents", "994", "nHitDice: " + IntToString(nHitDice) + " nDragonSize: " + IntToString(nDragonSize) + " sDmgDice: " + sDmgDice + " nDmg: " + IntToString(nDmg)); // Get the closest enemy to our right wing. location lWing = GetFlankingRightLocation(oCreature); object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fSize, lWing); while(oTarget != OBJECT_INVALID) { if(AI_DEBUG) ai_Debug("0i_talents", "1002", "oTarget: " + GetName(oTarget)); if(GetIsEnemy(oTarget, oCreature) && !GetIsDead(oTarget)) break; oTarget = GetNextObjectInShape(SHAPE_SPHERE, fSize, lWing); } if(oTarget != OBJECT_INVALID) ai_DragonMeleeAttack(oCreature, oTarget, sDmgDice, " right wing "); // Get the closest enemy to our left wing. lWing = GetFlankingLeftLocation(oCreature); oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fSize, lWing); while(oTarget != OBJECT_INVALID) { if(AI_DEBUG) ai_Debug("0i_talents", "1012", "oTarget: " + GetName(oTarget)); if(GetIsEnemy(oTarget, oCreature) && !GetIsDead(oTarget)) break; oTarget = GetNextObjectInShape(SHAPE_SPHERE, fSize, lWing); } if(oTarget != OBJECT_INVALID) ai_DragonMeleeAttack(oCreature, oTarget, sDmgDice, " left wing "); return TRUE; } // Looks behind the dragon to see if it can use it's tail slap on an enemy. int ai_TryTailSlap(object oCreature) { if(AI_DEBUG) ai_Debug("0i_talents", "1022", GetName(OBJECT_SELF) + " is checking for tail slap Attack!"); // Only Large size dragons can use thier tail in combat. // We use HitDice to base size S:1-5, M:6-11, L:12-17, H:18-29, G:30-39, C:40+. int nHitDice = GetHitDice(oCreature); if(nHitDice <= 11) return FALSE; int nDragonSize; string sDmgDice, sMessage; float fSize; // Get the stats based on the size of the dragon. if(nHitDice < 12) { fSize = 5.0f; nDragonSize = 3; sDmgDice = "1d4"; } // Medium else if(nHitDice < 18) { fSize = 10.0f; nDragonSize = 4; sDmgDice = "1d6"; } // Large else if(nHitDice < 30) { fSize = 10.0f; nDragonSize = 5; sDmgDice = "1d8"; } // Huge else if(nHitDice < 40) { fSize = 15.0f; nDragonSize = 6; sDmgDice = "2d6"; } // Gargantuan else { fSize = 15.0f; nDragonSize = 7; sDmgDice = "2d8"; } // Colossal // Add one and a half the dragons strength modifier. int nDmg = GetAbilityModifier(ABILITY_STRENGTH, oCreature); if(nDmg > 0) sDmgDice = sDmgDice + "+" + IntToString(nDmg + nDmg / 2); if(AI_DEBUG) ai_Debug("0i_talents", "1039", "nHitDice: " + IntToString(nHitDice) + " nDragonSize: " + IntToString(nDragonSize) + " sDmgDice: " + sDmgDice + " nDmg: " + IntToString(nDmg)); // Get the closest enemy to our tail. location lTail = GetBehindLocation(oCreature); object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fSize, lTail); while(oTarget != OBJECT_INVALID) { if(GetIsEnemy(oTarget, oCreature) && !GetIsDead(oTarget)) break; oTarget = GetNextObjectInShape(SHAPE_SPHERE, fSize, lTail); } if(oTarget != OBJECT_INVALID) ai_DragonMeleeAttack(oCreature, oTarget, sDmgDice, " tail ");\ return TRUE; } void ai_CrushEffect(object oCreature, object oBaseTarget, int nHitDice) { int nDragonSize, nAtkValue, nDC = ai_GetDragonDC(oCreature); string sDmgDice, sMessage; location lImpact = GetLocation(oBaseTarget); float fSize; // Get the stats based on the size of the dragon. if(nHitDice < 30) { fSize = 15.0f; nDragonSize = 5; sDmgDice = "2d8"; } // Huge else if(nHitDice < 40) { fSize = 25.0f; nDragonSize = 6; sDmgDice = "4d6"; } // Gargantuan else { fSize = 45.0f; nDragonSize = 7; sDmgDice = "4d8"; } // Colossal // Add the dragons strength modifier 1.5 times. int nDmgBonus = GetAbilityModifier(ABILITY_STRENGTH, oCreature); if(nDmgBonus > 0) sDmgDice = sDmgDice + "+" + IntToString(nDmgBonus + nDmgBonus / 2); // Dragon flies up and then crushes the area below it. effect eDmg, eKnockDown = EffectKnockdown(); effect eImpact = EffectVisualEffect(VFX_FNF_SCREEN_SHAKE); object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fSize, lImpact); while(oTarget != OBJECT_INVALID) { if(ai_GetIsCharacter(oTarget)) DelayCommand(1.0, ApplyEffectToObject(DURATION_TYPE_INSTANT, eImpact, oTarget)); // If they have evasion they automatically dodge the crush attack. if(!GetHasFeat(FEAT_EVASION, oTarget) && oTarget != oCreature) { if(!ReflexSave(oTarget, nDC, SAVING_THROW_TYPE_NONE, oCreature)) { eDmg =EffectDamage(ai_RollDiceString(sDmgDice), DAMAGE_TYPE_BLUDGEONING); ApplyEffectToObject(DURATION_TYPE_INSTANT, eDmg, oTarget); sMessage = ai_AddColorToText(GetName(oCreature), AI_COLOR_LIGHT_MAGENTA) + ai_AddColorToText(" crushes " + GetName(oTarget) + ".", AI_COLOR_DARK_ORANGE); if(ai_GetIsCharacter(oTarget)) SendMessageToPC(oTarget, sMessage); // Must be 3 sizes smaller to be affected by extra damage and knockdown. if(nDragonSize - 2 < GetCreatureSize(oTarget)) { if(!GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN)) { ApplyEffectToObject(DURATION_TYPE_INSTANT, eDmg, oTarget); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eKnockDown, oTarget, 6.0f); } } } } else { if(ai_GetIsCharacter(oTarget)) { sMessage = ai_AddColorToText(GetName(oTarget), AI_COLOR_LIGHT_MAGENTA) + ai_AddColorToText(" dodges the crush attack from " + GetName(oTarget) + ".", AI_COLOR_DARK_ORANGE); if(ai_GetIsCharacter(oTarget)) SendMessageToPC(oTarget, sMessage); } } oTarget = GetNextObjectInShape(SHAPE_SPHERE, fSize, lImpact); } // Now do normal attacks! ai_FlyToAttacks(oCreature, oBaseTarget); } int ai_TryCrushAttack(object oCreature, object oTarget) { if(AI_DEBUG) ai_Debug("0i_talents", "1110", GetName(OBJECT_SELF) + " is checking for crush Attack!"); // Only Huge size dragons can use crush attack. // We use HitDice to base size S:1-5, M:6-11, L:12-17, H:18-29, G:30-39, C:40+. int nHitDice = GetHitDice(oCreature); if(nHitDice <= 17) return FALSE; int nCrush = GetLocalInt(oCreature, "0_DRAGON_CRUSH") - 1; if(nCrush > 0) { SetLocalInt(oCreature, "0_DRAGON_CRUSH", nCrush); return FALSE; } effect eFly = EffectDisappearAppear(GetLocation(oTarget)); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFly, oCreature, 3.0f); DelayCommand(4.0f, ai_CrushEffect(oCreature, oTarget, nHitDice)); // Used to make creature wait before starting its next round. SetLocalInt(oCreature, AI_COMBAT_WAIT_IN_SECONDS, 5); // We only crush every 3 rounds if we can. SetLocalInt(oCreature, "0_DRAGON_CRUSH", 3); return TRUE; } int ai_TryTailSweepAttack(object oCreature) { if(AI_DEBUG) ai_Debug("0i_talents", "1132", GetName(oCreature) + " is checking for tail sweep Attack!"); // Only Gargantuan size dragons can use tail sweep attack. // We use HitDice to base size S:1-5, M:6-11, L:12-17, H:18-29, G:30-40, C:40+. int nHitDice = GetHitDice(oCreature); if(nHitDice <= 29) return FALSE; int nSweep = GetLocalInt(oCreature, "0_DRAGON_SWEEP") - 1; if(nSweep > 0) { SetLocalInt(oCreature, "0_DRAGON_SWEEP", nSweep); return FALSE; } int nDragonSize, nAtkValue, nDC = ai_GetDragonDC(oCreature); string sDmgDice, sMessage; float fSize; // Get the stats based on the size of the dragon. if(nHitDice < 33) { fSize = 15.0f; nDragonSize = 6; sDmgDice = "2d6"; } // Gargantuan else { fSize = 40.0f; nDragonSize = 7; sDmgDice = "2d8"; } // Colossal location lImpact = GetBehindLocation(oCreature); // We always sweep if we have the opportunity. // Add the dragons strength modifier 1.5 times. int nDmgBonus = GetAbilityModifier(ABILITY_STRENGTH, oCreature); if(nDmgBonus > 0) sDmgDice = sDmgDice + "+" + IntToString(nDmgBonus + nDmgBonus / 2); // Sweeps any creatures behind them. effect eDmg; effect eKnockDown = EffectKnockdown(); object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, fSize, lImpact); while(oTarget != OBJECT_INVALID) { sMessage = ai_AddColorToText(GetName(oCreature), AI_COLOR_LIGHT_MAGENTA) + ai_AddColorToText(" sweeps " + GetName(oTarget) + ".", AI_COLOR_ORANGE); if(ai_GetIsCharacter(oTarget)) SendMessageToPC(oTarget, sMessage); // If they have evasion they automatically dodge the sweep attack. if(!GetHasFeat(FEAT_EVASION, oTarget) && oTarget != oCreature) { if(!ReflexSave(oTarget, nDC, SAVING_THROW_TYPE_NONE, oCreature)) { eDmg = EffectDamage(ai_RollDiceString(sDmgDice), DAMAGE_TYPE_BLUDGEONING); ApplyEffectToObject(DURATION_TYPE_INSTANT, eDmg, oTarget); // Must be 4 sizes smaller to be affected by extra damage and knockdown. if(nDragonSize - 3 < GetCreatureSize(oTarget)) { if(!GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN)) { ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eKnockDown, oTarget, 12.0f); } } } } oTarget = GetNextObjectInShape(SHAPE_SPHERE, fSize, lImpact); } // We only sweep every 3 rounds if we can. SetLocalInt(oCreature, "0_DRAGON_SWEEP", 3); return TRUE; } int ai_TrySneakAttack(object oCreature, int nInMelee, int bAlwaysAtk = TRUE) { if(AI_DEBUG) ai_Debug("0i_talents", "1188", GetName(OBJECT_SELF) + " is checking for melee Sneak Attack!"); if(!GetHasFeat(FEAT_SNEAK_ATTACK, oCreature)) return FALSE; // Lets get the nearest target that is attacking someone besides me. object oTarget = OBJECT_INVALID; oTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET); if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature); if(oTarget == OBJECT_INVALID) { string sIndex; // Check if we have Mobility, Spring Attack or a good tumble. // if we do then look for other targets besides who we are in melee with. if(!nInMelee) sIndex = IntToString(ai_GetBestSneakAttackIndex(oCreature, AI_RANGE_PERCEPTION, bAlwaysAtk)); // If there are few enemies then we can safely move around. else if(nInMelee < 3 || ai_CanIMoveInCombat(oCreature)) { sIndex = IntToString(ai_GetBestSneakAttackIndex(oCreature, AI_RANGE_MELEE)); } // Ok we are in a serious fight so lets not give attack of opportunities. else sIndex = IntToString(ai_GetNearestIndex(oCreature, AI_RANGE_MELEE)); oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex); } if(oTarget == OBJECT_INVALID) return FALSE; int nRacialType = GetRacialType(oTarget); if(nRacialType == RACIAL_TYPE_CONSTRUCT || nRacialType == RACIAL_TYPE_UNDEAD) return FALSE; if(ai_GetHasEffectType(oTarget, IMMUNITY_TYPE_CRITICAL_HIT)) return FALSE; ai_ActionAttack(oCreature, AI_LAST_ACTION_MELEE_ATK, oTarget); return TRUE; } int ai_TryRangedSneakAttack(object oCreature, int nInMelee) { if(AI_DEBUG) ai_Debug("0i_talents", "1209", GetName(oCreature) + " is checking for a Ranged Sneak Attack!"); // If we have Sneak Attack then we should be attacking targets that // are busy fighting so we can get extra damage. if(!GetHasFeat(FEAT_SNEAK_ATTACK, oCreature)) return FALSE; object oTarget = OBJECT_INVALID; oTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET); if(ai_GetAIMode(oCreature, AI_MODE_DEFEND_MASTER)) oTarget = ai_GetLowestCRAttackerOnMaster(oCreature); if(oTarget == OBJECT_INVALID) oTarget = GetLocalObject(oCreature, AI_ENEMY + IntToString(ai_GetBestSneakAttackIndex(oCreature))); if(oTarget == OBJECT_INVALID) return FALSE; int nRacialType = GetRacialType(oTarget); if(nRacialType == RACIAL_TYPE_CONSTRUCT || nRacialType == RACIAL_TYPE_UNDEAD) return FALSE; if(ai_GetHasEffectType(oTarget, IMMUNITY_TYPE_CRITICAL_HIT)) return FALSE; // If we have a target and are not within 30' then move within 30'. if(GetDistanceToObject(oTarget) > AI_RANGE_CLOSE) ActionMoveToObject(oTarget, TRUE, AI_RANGE_CLOSE); ai_ActionAttack(oCreature, AI_LAST_ACTION_RANGED_ATK, oTarget, nInMelee, TRUE); return TRUE; } int ai_TryMeleeTalents(object oCreature, object oTarget) { if(AI_DEBUG) ai_Debug("0i_talents", "1224", "Check category melee talents!"); talent tUse = GetCreatureTalentBest(TALENT_CATEGORY_HARMFUL_MELEE, 20, oCreature); if(!GetIsTalentValid(tUse)) return FALSE; int nId = GetIdFromTalent(tUse); if(AI_DEBUG) ai_Debug("0i_talents", "1228", "TALENT_CATEGORY_MELEE_TALENTS nId: " + IntToString(nId)); if(nId == FEAT_POWER_ATTACK) { if(ai_TryPowerAttackFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_EXPERTISE) { if(ai_TryExpertiseFeat(oCreature)) return TRUE; } else if(nId == FEAT_KNOCKDOWN) { if(ai_TryKnockdownFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_SMITE_EVIL) { if(ai_TrySmiteEvilFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_SMITE_GOOD) { if(ai_TrySmiteGoodFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_IMPROVED_POWER_ATTACK) { if(ai_TryImprovedPowerAttackFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_IMPROVED_EXPERTISE) { if(ai_TryImprovedExpertiseFeat(oCreature)) return TRUE; } else if(nId == FEAT_FLURRY_OF_BLOWS) { if(ai_TryFlurryOfBlowsFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_STUNNING_FIST) { if(ai_TryStunningFistFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_SAP) { if(ai_TrySapFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_DISARM) { if(ai_TryDisarmFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_KI_DAMAGE) { if(ai_TryKiDamageFeat(oCreature, oTarget)) return TRUE; } else if(nId == FEAT_CALLED_SHOT) { if(ai_TryCalledShotFeat(oCreature, oTarget)) return TRUE; } return FALSE; } // ***************************************************************************** // ***************************** TALENT SCRIPTS ****************************** // ***************************************************************************** // These functions do not fall into another section. int ai_GetMonsterTalentMaxLevel(object oCreature) { // Monsters should use either the best spell they have or a random spell so // they all don't look robotic. Mix it up based on an Intelligence check. int nMaxLevel = (ai_GetCharacterLevels(oCreature) + 1) / 2; if(nMaxLevel > 9) nMaxLevel = 9; if(AI_DEBUG) ai_Debug("0i_talents", "1258", "nMaxLevel: " + IntToString(nMaxLevel)); return nMaxLevel; } int ai_GetAssociateTalentMaxLevel(object oCreature, int nDifficulty) { int nLevel = (ai_GetCharacterLevels(oCreature) + 1) / 2; if(nLevel > 20) nLevel = 20; int nMaxLevel = (nLevel * nDifficulty) / 20; if(nMaxLevel < 1) nMaxLevel = 1; if(AI_DEBUG) ai_Debug("0i_talents", "1267", "nLevel: " + IntToString(nLevel) + " nMaxLevel: " + IntToString(nMaxLevel)); return nMaxLevel; } int ai_GetHasTalent(object oCreature, int nTalent) { string sCategory = Get2DAString("ai_spells", "Category", nTalent); json jCategory = GetLocalJson(oCreature, sCategory); if(JsonGetType(jCategory) == JSON_TYPE_NULL) return FALSE; int nLevel, nSlot, nSlotIndex, nMaxSlotIndex, nSpell; json jLevel, jTalent; // Loop through nLevels looking for nTalent while(nLevel <= 9) { // Get the array of nLevel. jLevel = JsonArrayGet(jCategory, nLevel); nMaxSlotIndex = JsonGetLength(jLevel); if(nMaxSlotIndex > 0) { // Get the talent within nLevel cycling from the first to the last. nSlotIndex = 0; while (nSlotIndex < nMaxSlotIndex) { jTalent= JsonArrayGet(jLevel, nSlotIndex); nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); if(nSpell == nTalent) return TRUE; nSlotIndex++; } } nLevel++; } return FALSE; } object ai_CheckTalentForBuffing(object oCreature, string sCategory, int nSpell) { // Should we buff this monster caster? Added legacy code just in case. if((sCategory == "P" || sCategory == "E" || sCategory == "S") && (GetLocalInt(GetModule(), AI_RULE_BUFF_MONSTERS) || GetLocalInt(oCreature, "NW_GENERIC_MASTER") & 0x04000000)) return ai_GetBuffTarget(oCreature, nSpell); //if(sCategory == "S" && GetLocalInt(GetModule(), AI_RULE_PRESUMMON)) return oCreature; return OBJECT_INVALID; } int ai_UseBuffTalent(object oCreature, int nClass, int nLevel, int nSlot, int nSpell, int nType, object oTarget, object oItem) { if(nType == AI_TALENT_TYPE_SPELL) { if(Get2DAString("classes", "MemorizesSpells", nClass) == "1") { if(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlot)) { ai_CastMemorizedSpell(oCreature, nClass, nLevel, nSlot, oTarget, TRUE); return TRUE; } } else if(GetSpellUsesLeft(oCreature, nClass, nSpell)) { ai_CastKnownSpell(oCreature, nClass, nSpell, oTarget, TRUE); return TRUE; } } else if(nType == AI_TALENT_TYPE_SP_ABILITY) { ActionCastSpellAtObject(nSpell, oTarget, 255, FALSE, 0, 0, TRUE, 255); } /* This will not work as there is no cheat option for using an item. else if(nType == AI_TALENT_TYPE_ITEM) { int nBaseItem = GetBaseItemType(oItem); if(!AI_BUFF_MONSTER_POTIONS && (nBaseItem == BASE_ITEM_POTIONS || nBaseItem == BASE_ITEM_ENCHANTED_POTION)) return FALSE; itemproperty ipProp = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ipProp)) { if(nIndex++ == nSlot) break; ipProp = GetNextItemProperty(oItem); } // Cast items have the following: // 1)Single_Use. // 2-6) Charges/Use [Note: 7 is 0 charges per use]. // 8-12) Uses/Day [Note: 13 is unlimited uses per day]. // We set the slot to -1 to let the other function know we need this talent removed. int nUses = GetItemPropertyCostTableValue(ipProp); if(nUses == 1) jTalent = JsonArrayInsert(jTalent, JsonInt(-1), 4); else if(nUses > 1 && nUses < 7) { if(AI_DEBUG) ai_Debug("0i_talents", "1319", "Item charges: " + IntToString(GetItemCharges(oItem))); int nCharges = GetItemCharges(oItem); if(nUses == 6 && nCharges == 1 || nUses == 5 && nCharges < 4 || nUses == 4 && nCharges < 6 || nUses == 3 && nCharges < 8 || nUses == 2 && nCharges < 10) return FALSE; } else if(nUses > 7 && nUses < 13) { if(AI_DEBUG) ai_Debug("0i_talents", "1327", "Item uses: " + IntToString(GetItemPropertyUsesPerDayRemaining(oItem, ipProp))); int nPerDay = GetItemPropertyUsesPerDayRemaining(oItem, ipProp); if(nUses == 8 && nPerDay == 1 || nUses == 9 && nPerDay < 4 || nUses == 10 && nPerDay < 6 || nUses == 11 && nPerDay < 8 || nUses == 12 && nPerDay < 10) return FASLE; } ActionUseItemOnObject(oItem, ipProp, oTarget, nSubIndex); return TRUE; } */ return FALSE; } int ai_SpellRestricted(int nSpell) { json jRSpells = GetLocalJson(GetModule(), AI_RULE_RESTRICTED_SPELLS); int nIndex, nMaxIndex = JsonGetLength(jRSpells); while(nIndex < nMaxIndex) { if(JsonGetInt(JsonArrayGet(jRSpells, nIndex)) == nSpell) { if(AI_DEBUG) ai_Debug("0i_talents", "1703", IntToString(nSpell) + " is has been restricted and will be ignored!"); return TRUE; } nIndex++; } return FALSE; } void ai_SaveTalent(object oCreature, int nClass, int nJsonLevel, int nLevel, int nSlot, int nSpell, int nType, int bMonster, object oItem = OBJECT_INVALID) { // Players/Admins can restrict some spells. if(ai_SpellRestricted(nSpell)) return; // Get the talent category, we organize all talents by categories. string sCategory = Get2DAString("ai_spells", "Category", nSpell); // If it is a blank talent or it is an Area of Effect talent we skip. if(sCategory == "" || sCategory == "A") return; // Check to see if we should be prebuffing. if(bMonster) { int nSpellBuffDuration = StringToInt(Get2DAString("ai_spells", "Buff_Duration", nSpell)); if(nSpellBuffDuration == 3) { if(AI_DEBUG) ai_Debug("0i_talents", "1600", GetName(oCreature) + " is buffing with spell " + IntToString(nSpell)); object oTarget = ai_CheckTalentForBuffing(oCreature, sCategory, nSpell); if(oTarget != OBJECT_INVALID && ai_UseBuffTalent(oCreature, nClass, nLevel, nSlot, nSpell, nType, oTarget, oItem)) return; } } json jCategory = GetLocalJson(oCreature, sCategory); // With no jCategory then we make one with all 0-9 levels. if(JsonGetType(jCategory) == JSON_TYPE_NULL) { jCategory = JsonArray(); jCategory = JsonArrayInsert(jCategory, JsonArray(), 0); int nNewLevel = 9; while(nNewLevel > 0) { jCategory = JsonArrayInsert(jCategory, JsonArray()); nNewLevel--; } } // Get the current Level so we can save to it. json jLevel = JsonArrayGet(jCategory, nJsonLevel); json jTalent = JsonArray(); if(nType == AI_TALENT_TYPE_SPELL || nType == AI_TALENT_TYPE_SP_ABILITY) { jTalent = JsonArrayInsert(jTalent, JsonInt(nType), 0); jTalent = JsonArrayInsert(jTalent, JsonInt(nSpell)); jTalent = JsonArrayInsert(jTalent, JsonInt(nClass)); jTalent = JsonArrayInsert(jTalent, JsonInt(nLevel)); jTalent = JsonArrayInsert(jTalent, JsonInt(nSlot)); } else if(nType == AI_TALENT_TYPE_ITEM) { jTalent = JsonArrayInsert(jTalent, JsonInt(nType), 0); jTalent = JsonArrayInsert(jTalent, JsonInt(nSpell)); jTalent = JsonArrayInsert(jTalent, JsonString(ObjectToString(oItem))); jTalent = JsonArrayInsert(jTalent, JsonInt(nLevel)); jTalent = JsonArrayInsert(jTalent, JsonInt(nSlot)); } jLevel = JsonArrayInsert(jLevel, jTalent); jCategory = JsonArraySet(jCategory, nJsonLevel, jLevel); SetLocalJson(oCreature, sCategory, jCategory); if(AI_DEBUG) ai_Debug("0i_talents", "1777", sCategory + ": " + JsonDump(jCategory, 1)); if(AI_DEBUG) ai_Debug("0i_talents", "1778", "AI_MAX_TALENT: " + IntToString(GetLocalInt(oCreature, AI_MAX_TALENT + sCategory)) + " nJsonLevel: " + IntToString(nJsonLevel)); // Set AI_MAX_TALENT if this talent is higher than the maximum. if(nJsonLevel > GetLocalInt(oCreature, AI_MAX_TALENT + sCategory)) { SetLocalInt(oCreature, AI_MAX_TALENT + sCategory, nJsonLevel); } } // For removing used up spell slots. void ai_RemoveTalent(object oCreature, json jCategory, json jLevel, string sCategory, int nLevel, int nSlotIndex) { if(AI_DEBUG) ai_Debug("0i_talents", "1400", "removing Talent from slot: " + IntToString(nSlotIndex)); jLevel = JsonArrayDel(jLevel, nSlotIndex); if(AI_DEBUG) ai_Debug("0i_talents", "1402", "jLevel: " + JsonDump(jLevel, 2)); jCategory = JsonArraySet(jCategory, nLevel, jLevel); if(AI_DEBUG) ai_Debug("0i_talents", "1404", "jCategory: " + JsonDump(jCategory, 2)); SetLocalJson(oCreature, sCategory, jCategory); } // For removing Sorcerer/Bard spell levels once used up. void ai_RemoveTalentLevel(object oCreature, json jCategory, json jLevel, string sCategory, int nLevel) { if(AI_DEBUG) ai_Debug("0i_talents", "1410", "removing Talent level: " + IntToString(nLevel)); jCategory = JsonArrayDel(jCategory, nLevel); if(AI_DEBUG) ai_Debug("0i_talents", "1412", "jCategory: " + JsonDump(jCategory, 2)); SetLocalJson(oCreature, sCategory, jCategory); } void ai_SetCreatureSpellTalents(object oCreature, int bMonster) { if(AI_DEBUG) ai_Debug("0i_talents", "1417", GetName(oCreature) + ": Setting Spell Talents for combat [Buff: " + IntToString(bMonster) + "]."); // Cycle through all classes and spells. int nClassPosition = 1, nMaxSlot, nLevel, nSlot, nSpell, nIndex, nMetaMagic; int nClass = GetClassByPosition(nClassPosition, oCreature); while(nClassPosition <= AI_MAX_CLASSES_PER_CHARACTER && nClass != CLASS_TYPE_INVALID) { if(AI_DEBUG) ai_Debug("0i_talents", "1824", "nClass: " + IntToString(nClass) + " nClassPosition: " + IntToString(nClassPosition) + " SpellCaster: " + Get2DAString("classes", "SpellCaster", nClass) + " Memorized: " + Get2DAString("classes", "MemorizesSpells", nClass)); if(Get2DAString("classes", "SpellCaster", nClass) == "1") { // Search all memorized spells for the spell. if(Get2DAString("classes", "MemorizesSpells", nClass) == "1") { // Check each level organizing from highest to lowest. nLevel = (GetLevelByPosition(nClassPosition, oCreature) + 1) / 2; if(nLevel > 9) nLevel = 9; while(nLevel > -1) { // Check each slot within each level. nMaxSlot = GetMemorizedSpellCountByLevel(oCreature, nClass, nLevel); if(AI_DEBUG) ai_Debug("0i_talents", "1434", "nClass: " + IntToString(nClass) + " nLevel: " + IntToString(nLevel) + " nMaxSlot: " + IntToString(nMaxSlot)); nSlot = 0; while(nSlot < nMaxSlot) { if(AI_DEBUG) ai_Debug("0i_talents", "1440", "nSlot: " + IntToString(nSlot) + " nSpell: " + IntToString(GetMemorizedSpellId(oCreature, nClass, nLevel, nSlot)) + " spell memorized: " + IntToString(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlot))); if(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlot) == 1) { nSpell = GetMemorizedSpellId(oCreature, nClass, nLevel, nSlot); /* Spells are already at the higher level when saved as a talent. // Move a spell up to a different JsonLevel as higher Jsonlevel // spells usually get cast first. nMetaMagic = GetMemorizedSpellMetaMagic(oCreature, nClass, nLevel, nSlot); if(nMetaMagic > 0) { if(nMetaMagic == METAMAGIC_STILL) nMetaMagic = 1; else if(nMetaMagic == METAMAGIC_EXTEND) nMetaMagic = 1; else if(nMetaMagic == METAMAGIC_SILENT) nMetaMagic = 1; else if(nMetaMagic == METAMAGIC_EMPOWER) nMetaMagic = 2; else if(nMetaMagic == METAMAGIC_MAXIMIZE) nMetaMagic = 3; else if(nMetaMagic == METAMAGIC_QUICKEN) nMetaMagic = 4; nAdjLevel = nLevel + nMetaMagic; if(nAdjLevel > 9) nAdjLevel = 9; } else nAdjLevel = nLevel; */ ai_SaveTalent(oCreature, nClass, nLevel, nLevel, nSlot, nSpell, AI_TALENT_TYPE_SPELL, bMonster); } nSlot++; } nLevel--; } } // Check non-memorized known lists for the spell. else { // Check each level starting with the highest to lowest. nLevel = (GetLevelByPosition(nClassPosition, oCreature) + 1) / 2; if(nLevel > 9) nLevel = 9; while(nLevel > -1) { // Check each slot within each level. nMaxSlot = GetKnownSpellCount(oCreature, nClass, nLevel); if(AI_DEBUG) ai_Debug("0i_talents", "1462", "nClass: " + IntToString(nClass) + " nLevel: " + IntToString(nLevel) + " nMaxSlot: " + IntToString(nMaxSlot)); nSlot = 0; while(nSlot < nMaxSlot) { nSpell = GetKnownSpellId(oCreature, nClass, nLevel, nSlot); if(AI_DEBUG) ai_Debug("0i_talents", "1469", "nSlot: " + IntToString(nSlot) + " nSpell: " + IntToString(nSpell) + " nUsesLeft: " + IntToString(GetSpellUsesLeft(oCreature, nClass, nSpell))); if(GetSpellUsesLeft(oCreature, nClass, nSpell) > 0) { ai_SaveTalent(oCreature, nClass, nLevel, nLevel, nSlot, nSpell, AI_TALENT_TYPE_SPELL, bMonster); } nSlot++; } nLevel--; } } } nClassPosition++; nClass = GetClassByPosition(nClassPosition, oCreature); } } void ai_SetCreatureSpecialAbilityTalents(object oCreature, int bMonster) { if(AI_DEBUG) ai_Debug("0i_talents", "1488", GetName(oCreature) + ": Setting Special Ability Talents for combat."); // Cycle through all the creatures special abilities. int nMaxSpecialAbilities = GetSpellAbilityCount(oCreature); if(AI_DEBUG) ai_Debug("0i_talents", "1491", IntToString(GetSpellAbilityCount(oCreature)) + " Spell abilities."); if(nMaxSpecialAbilities) { int nIndex, nSpell, nLevel; while(nIndex < nMaxSpecialAbilities) { nSpell = GetSpellAbilitySpell(oCreature, nIndex); if(GetSpellAbilityReady(oCreature, nSpell)) { nLevel = StringToInt(Get2DAString("spells", "Innate", nSpell)); ai_SaveTalent(oCreature, 255, nLevel, nLevel, nIndex, nSpell, AI_TALENT_TYPE_SP_ABILITY, bMonster); } nIndex++; } } } void ai_CheckItemProperties(object oCreature, object oItem, int bMonster, int bEquiped = FALSE) { if(AI_DEBUG) ai_Debug("0i_talents", "1509", "Checking Item properties on " + GetName(oItem)); // We have established that we can use the item if it is equiped. if(!bEquiped && !ai_CheckIfCanUseItem(oCreature, oItem)) return; // Get or create an Immunity in json so we can check item immunities quickly. int nSpellImmunity, bHasItemImmunity, nPerDay, nCharges, nUses, bSaveTalent; json jImmunity = GetLocalJson(oCreature, AI_TALENT_IMMUNITY); if(JsonGetType(jImmunity) == JSON_TYPE_NULL) jImmunity = JsonArray(); int nIprpSubType, nSpell, nLevel, nIPType, nIndex; itemproperty ipProp = GetFirstItemProperty(oItem); // Lets skip this if there are no properties. if(!GetIsItemPropertyValid(ipProp)) return; // Check for cast spell property and add them to the talent list. while(GetIsItemPropertyValid(ipProp)) { nIPType = GetItemPropertyType(ipProp); if(AI_DEBUG) ai_Debug("0i_talents", "1895", "ItempropertyType(15/80/53): " + IntToString(nIPType)); if(nIPType == ITEM_PROPERTY_CAST_SPELL) { bSaveTalent = TRUE; // Get how they use the item (charges or uses per day). nUses = GetItemPropertyCostTableValue(ipProp); if(nUses > 1 && nUses < 7) { nCharges = GetItemCharges(oItem); if(AI_DEBUG) ai_Debug("0i_talents", "1530", "Charges per use: " + IntToString(nUses) + " Item charges: " + IntToString(nCharges)); if((nUses == IP_CONST_CASTSPELL_NUMUSES_1_CHARGE_PER_USE && nCharges < 1) || (nUses == IP_CONST_CASTSPELL_NUMUSES_2_CHARGES_PER_USE && nCharges < 2) || (nUses == IP_CONST_CASTSPELL_NUMUSES_3_CHARGES_PER_USE && nCharges < 3) || (nUses == IP_CONST_CASTSPELL_NUMUSES_4_CHARGES_PER_USE && nCharges < 4) || (nUses == IP_CONST_CASTSPELL_NUMUSES_5_CHARGES_PER_USE && nCharges < 5)) bSaveTalent = FALSE; } else if(nUses > 7 && nUses < 13) { nPerDay = GetItemPropertyUsesPerDayRemaining(oItem, ipProp); if(AI_DEBUG) ai_Debug("0i_talents", "1676", "Item uses: " + IntToString(nPerDay)); if(nPerDay == 0) bSaveTalent = FALSE; } if(bSaveTalent) { // SubType is the ip spell index for iprp_spells.2da nIprpSubType = GetItemPropertySubType(ipProp); nSpell = StringToInt(Get2DAString("iprp_spells", "SpellIndex", nIprpSubType)); nLevel = StringToInt(Get2DAString("iprp_spells", "InnateLvl", nIprpSubType)); ai_SaveTalent(oCreature, 255, nLevel, nLevel, nIndex, nSpell, AI_TALENT_TYPE_ITEM, bMonster, oItem); } } else if(nIPType == ITEM_PROPERTY_HEALERS_KIT) { // Lets set Healing kits as Cure Light Wounds since they heal 1d20 in combat. nSpell = SPELL_CURE_MINOR_WOUNDS; // Save the healer kit as level 9 so we can use them first. // Must also have ranks in healing kits. if(GetSkillRank(SKILL_HEAL, oCreature) > 0) { ai_SaveTalent(oCreature, 255, 7, 0, nIndex, nSpell, AI_TALENT_TYPE_ITEM, bMonster, oItem); } } if(bEquiped) { if(nIPType == ITEM_PROPERTY_IMMUNITY_SPECIFIC_SPELL) { bHasItemImmunity = TRUE; nSpellImmunity = GetItemPropertyCostTableValue(ipProp); nSpellImmunity = StringToInt(Get2DAString("iprp_spellcost", "SpellIndex", nSpellImmunity)); //if(AI_DEBUG) ai_Debug("0i_talents", "1950", "SpellImmunity to " + Get2DAString("spells", "Label", nSpellImmunity)); jImmunity = JsonArrayInsert(jImmunity, JsonInt(nSpellImmunity)); } else if(nIPType == ITEM_PROPERTY_HASTE) { SetLocalInt(oCreature, sIPHasHasteVarname, TRUE); } else if(nIPType == ITEM_PROPERTY_IMMUNITY_DAMAGE_TYPE) { int nBit, nIpSubType = GetItemPropertySubType(ipProp); if(AI_DEBUG) ai_Debug("0i_talents", "1957", "nIPSubType: " + IntToString(nIpSubType)); if(nIpSubType == 0) nBit = DAMAGE_TYPE_BLUDGEONING; else if(nIpSubType == 1) nBit = DAMAGE_TYPE_PIERCING; else if(nIpSubType == 2) nBit = DAMAGE_TYPE_SLASHING; else if(nIpSubType == 5) nBit = DAMAGE_TYPE_MAGICAL; else if(nIpSubType == 6) nBit = DAMAGE_TYPE_ACID; else if(nIpSubType == 7) nBit = DAMAGE_TYPE_COLD; else if(nIpSubType == 8) nBit = DAMAGE_TYPE_DIVINE; else if(nIpSubType == 9) nBit = DAMAGE_TYPE_ELECTRICAL; else if(nIpSubType == 10) nBit = DAMAGE_TYPE_FIRE; else if(nIpSubType == 11) nBit = DAMAGE_TYPE_NEGATIVE; else if(nIpSubType == 12) nBit = DAMAGE_TYPE_POSITIVE; else if(nIpSubType == 13) nBit = DAMAGE_TYPE_SONIC; if(nBit > 0) ai_SetItemProperty(oCreature, sIPImmuneVarname, nBit, TRUE); } else if(nIPType == ITEM_PROPERTY_DAMAGE_RESISTANCE) { int nBit, nIpSubType = GetItemPropertySubType(ipProp); if(nIpSubType == 0) nBit = DAMAGE_TYPE_BLUDGEONING; else if(nIpSubType == 1) nBit = DAMAGE_TYPE_PIERCING; else if(nIpSubType == 2) nBit = DAMAGE_TYPE_SLASHING; else if(nIpSubType == 5) nBit = DAMAGE_TYPE_MAGICAL; else if(nIpSubType == 6) nBit = DAMAGE_TYPE_ACID; else if(nIpSubType == 7) nBit = DAMAGE_TYPE_COLD; else if(nIpSubType == 8) nBit = DAMAGE_TYPE_DIVINE; else if(nIpSubType == 9) nBit = DAMAGE_TYPE_ELECTRICAL; else if(nIpSubType == 10) nBit = DAMAGE_TYPE_FIRE; else if(nIpSubType == 11) nBit = DAMAGE_TYPE_NEGATIVE; else if(nIpSubType == 12) nBit = DAMAGE_TYPE_POSITIVE; else if(nIpSubType == 13) nBit = DAMAGE_TYPE_SONIC; if(nBit > 0) ai_SetItemProperty(oCreature, sIPResistVarname, nBit, TRUE); } else if(nIPType == ITEM_PROPERTY_DAMAGE_REDUCTION) { int nIpSubType = GetItemPropertySubType(ipProp); SetLocalInt(oCreature, sIPReducedVarname, nIpSubType); } } nIndex++; ipProp = GetNextItemProperty(oItem); } // If nSpellImmunity has been set then we need to save our Immunity json. if(bHasItemImmunity) SetLocalJson(oCreature, AI_TALENT_IMMUNITY, jImmunity); } void ai_SetCreatureItemTalents(object oCreature, int bMonster) { if(AI_DEBUG) ai_Debug("0i_talents", "1561", GetName(oCreature) + ": Setting Item Talents for combat."); int bEquiped; string sSlots; // Cycle through all the creatures inventory items. object oItem = GetFirstItemInInventory(oCreature); while(oItem != OBJECT_INVALID) { if(GetIdentified(oItem)) { // Does the item need to be equiped to use its powers? sSlots = Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem)); if(AI_DEBUG) ai_Debug("0i_talents", "1572", GetName(oItem) + " requires " + Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem)) + " slots."); if(sSlots == "0x00000") ai_CheckItemProperties(oCreature, oItem, bMonster); } oItem = GetNextItemInInventory(oCreature); } int nSlot; // Cycle through all the creatures equiped items. oItem = GetItemInSlot(nSlot, oCreature); while(nSlot < 11) { if(oItem != OBJECT_INVALID) ai_CheckItemProperties(oCreature, oItem, bMonster, TRUE); oItem = GetItemInSlot(++nSlot, oCreature); } oItem = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oCreature); if(oItem != OBJECT_INVALID) ai_CheckItemProperties(oCreature, oItem, bMonster, TRUE); } void ai_SetCreatureTalents(object oCreature, int bMonster) { json jCreature = ObjectToJson(oCreature); //if(AI_DEBUG) ai_Debug("0i_talents", "2072", GetName(oCreature) + " jCreature: " + JsonDump(jCreature, 4)); if(GetLocalInt(oCreature, AI_TALENTS_SET)) return; SetLocalInt(oCreature, AI_TALENTS_SET, TRUE); object oModule = GetModule(); ai_Counter_Start(); ai_SetCreatureSpellTalents(oCreature, bMonster); ai_Counter_End(GetName(oCreature) + ": Spell Talents"); ai_SetCreatureSpecialAbilityTalents(oCreature, bMonster); ai_Counter_End(GetName(oCreature) + ": Special Ability Talents"); DeleteLocalJson(oCreature, AI_TALENT_IMMUNITY); ai_SetCreatureItemTalents(oCreature, bMonster); ai_Counter_End(GetName(oCreature) + ": Item Talents"); if(GetLocalInt(oModule, AI_RULE_SUMMON_COMPANIONS) && GetLocalInt(oModule, AI_RULE_PRESUMMON) && bMonster) { ai_TrySummonFamiliarTalent(oCreature); ai_TrySummonAnimalCompanionTalent(oCreature); } // AI_CAT_CURE is setup differently we save the level as the highest. //if(JsonGetType(GetLocalJson(oCreature, AI_TALENT_CURE)) != JSON_TYPE_NULL) SetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE, 9); // With spontaneous cure spells we need to clear this as the number of spells don't count. //if(GetLevelByClass(CLASS_TYPE_CLERIC, oCreature)) SetLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_HEALING, 0); } int ai_UseSpontaneousCureTalentFromCategory(object oCreature, string sCategory, int nInMelee, int nDamage, object oTarget = OBJECT_INVALID) { // Get the saved category from oCreature. json jCategory = GetLocalJson(oCreature, sCategory); if(AI_DEBUG) ai_Debug("0i_talents", "2095", "jCategory: " + sCategory + " " + JsonDump(jCategory, 2)); if(JsonGetType(jCategory) == JSON_TYPE_NULL) return FALSE; int nLevel = 4; // If there are no talents at lower levels then start at the lower level. int nMaxTalentLevel = GetLocalInt(oCreature, AI_MAX_TALENT + sCategory); if(AI_DEBUG) ai_Debug("0i_talents", "2100", AI_MAX_TALENT + sCategory + ": " + IntToString(nMaxTalentLevel) + " nLevel: " + IntToString(nLevel)); if(nMaxTalentLevel < nLevel) nLevel = nMaxTalentLevel; if(nLevel < 0 || nLevel > 5) nLevel = 4; json jLevel, jTalent, jLevelSave; int nTalentType, nTalentClass, nTalentSlot, nSpell; int nSlotIndex, nMaxSlotIndex, nMaxNoTalentLevel, nSpellSave, nLevelSave, nSlotSave; string sSpellName; // Loop through nLevels down to nMinNoTalentLevel looking for the first talent // (i.e. the highest or best?). while(nLevel > -1) { // Get the array of nLevel cycling down to 0. jLevel = JsonArrayGet(jCategory, nLevel); nMaxSlotIndex = JsonGetLength(jLevel); if(AI_DEBUG) ai_Debug("0i_talents", "2116", "nLevel: " + IntToString(nLevel) + " nMaxSlotIndex: " + IntToString(nMaxSlotIndex)); if(nMaxSlotIndex > 0) { // Get the talent within nLevel cycling from the first to the last. nSlotIndex = 0; while (nSlotIndex < nMaxSlotIndex) { jTalent= JsonArrayGet(jLevel, nSlotIndex); if(AI_DEBUG) ai_Debug("0i_talents", "2125", "nSlotIndex: " + IntToString(nSlotIndex) + " jTalent Type: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 0)))); nTalentType = JsonGetInt(JsonArrayGet(jTalent, 0)); nTalentClass = JsonGetInt(JsonArrayGet(jTalent, 2)); // We can only convert spells from the cleric class. if(nTalentType == AI_TALENT_TYPE_SPELL && nTalentClass == CLASS_TYPE_CLERIC) { if(nLevel == 4) nSpell = SPELL_CURE_CRITICAL_WOUNDS; else if(nLevel == 3) nSpell = SPELL_CURE_SERIOUS_WOUNDS; else if(nLevel == 2) nSpell = SPELL_CURE_MODERATE_WOUNDS; else if(nLevel == 1) nSpell = SPELL_CURE_LIGHT_WOUNDS; else nSpell = 0; if(AI_DEBUG) ai_Debug("0i_talents", "2137", "nSpell: " + IntToString(nSpell)); if(nSpell) { if(ai_ShouldWeCastThisCureSpell(nSpell, nDamage)) { nTalentSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); SetMemorizedSpellReady(oCreature, nTalentClass, nLevel, nTalentSlot, FALSE); ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); sSpellName = GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpell))); if(ai_GetIsCharacter(oCreature)) ai_SendMessages(GetName(oCreature) + " has spontaneously cast " + sSpellName + " on " + GetName(oTarget) + ".", AI_COLOR_MAGENTA, oCreature); if(AI_DEBUG) ai_Debug("0i_talents", "2148", GetName(oCreature) + " has spontaneously cast " + sSpellName + " on " + GetName(oTarget) + "."); ActionCastSpellAtObject(nSpell, oTarget, 255, TRUE); return TRUE; } // Save the lowest level cure spell as we might need to cast it. else if(nLevel < nLevelSave) { jLevelSave = jLevel; nLevelSave = nLevel; nSlotSave = nTalentSlot; nSpellSave = nSpell; } } } nSlotIndex++; } } else SetLocalInt(oCreature, AI_MAX_TALENT + sCategory, nLevel - 1); nLevel--; } // Did we find a spell? If we did then use it. if(nSpellSave) { if(AI_DEBUG) ai_Debug("0i_talents", "2171", GetName(oCreature) + " has cast the lowest level cure spell on " + GetName(oTarget) + "."); SetMemorizedSpellReady(oCreature, CLASS_TYPE_CLERIC, nLevelSave, nSlotSave, FALSE); ai_RemoveTalent(oCreature, jCategory, jLevelSave, sCategory, nLevelSave, nSlotSave); sSpellName = GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpellSave))); if(ai_GetIsCharacter(oCreature)) ai_SendMessages(GetName(oCreature) + " has spontaneously cast " + sSpellName + " on " + GetName(oTarget) + ".", AI_COLOR_MAGENTA, oCreature); ActionCastSpellAtObject(nSpellSave, oTarget, 255, TRUE); return TRUE; } return FALSE; } int ai_UseCreatureSpellTalent(object oCreature, json jLevel, json jTalent, string sCategory, int nInMelee, object oTarget = OBJECT_INVALID) { // Check for polymorph, spells cannot be used while polymorphed. if(GetAppearanceType(oCreature) != ai_GetNormalAppearance(oCreature)) return FALSE; // Get the spells information so we can check if they still have it. int nClass = JsonGetInt(JsonArrayGet(jTalent, 2)); int nLevel = JsonGetInt(JsonArrayGet(jTalent, 3)); int nSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); if(ai_IsSilenced(oCreature, JsonGetInt(JsonArrayGet(jTalent, 2)))) { if(GetMemorizedSpellMetaMagic(oCreature, nClass, nLevel, nSlot) != METAMAGIC_SILENT) { object oAOE = GetNearestObjectByTag("VFX_MOB_SILENCE", oCreature); float fDistance = GetDistanceBetween(oAOE, oCreature); if(fDistance != 0.0 && fDistance <= 4.0) { location lLocation = GetRandomLocation(GetArea(oCreature), oCreature, 5.0); ai_ClearCreatureActions(); if(AI_DEBUG) ai_Debug("0i_talents", "2225", GetName(oCreature) + " is moving out of a silence effect!"); ActionMoveToLocation(lLocation, TRUE); return TRUE; } else return FALSE; } } if(ai_ArcaneSpellFailureTooHigh(oCreature, nClass, nLevel, nSlot)) return FALSE; if(Get2DAString("classes", "MemorizesSpells", nClass) == "1") { // Shouldn't need this anymore, we need to do a debug looking at this. if(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlot) < 1) return FALSE; if(ai_CheckSpecialTalentsandUse(oCreature, jTalent, sCategory, nInMelee, oTarget)) { if(ai_CompareLastAction(oCreature, AI_LAST_ACTION_CAST_SPELL)) return -1; return TRUE; } return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "1629", "Known caster Level: " + IntToString(nLevel) + " Uses : " + IntToString(GetSpellUsesLeft(oCreature, nClass, JsonGetInt(JsonArrayGet(jTalent, 1))))); if(!GetSpellUsesLeft(oCreature, nClass, JsonGetInt(JsonArrayGet(jTalent, 1)))) return -2; return ai_CheckSpecialTalentsandUse(oCreature, jTalent, sCategory, nInMelee, oTarget); } int ai_UseCreatureItemTalent(object oCreature, json jLevel, json jTalent, string sCategory, int nInMelee, object oTarget = OBJECT_INVALID) { object oItem = StringToObject(JsonGetString(JsonArrayGet(jTalent, 2))); int nItemType = GetBaseItemType(oItem); // Check if the item is a potion since there are some special cases. if(nItemType == BASE_ITEM_POTIONS || nItemType == BASE_ITEM_ENCHANTED_POTION) { // Potions cause attack of opportunities and this could be deadly! // Removed for healing potions as that is one time you would use potions in melee. if(sCategory != AI_TALENT_HEALING) { if(AI_DEBUG) ai_Debug("0i_talents", "1925", "Using a non-healing potion nInMelee: " + IntToString(nInMelee)); if(nInMelee > 1) return FALSE; // Don't use potions on allies that are not within 3 meters. if(GetDistanceBetween(oCreature, oTarget) > 3.1) return FALSE; } // For now we are allowing creatures to use "give" potions to others // unless the player is using a healing potion and has party healing turned off. else if(oCreature != oTarget && ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) return FALSE; } // Check for polymorph, only potions can be used while polymorphed. else if(GetAppearanceType(oCreature) != ai_GetNormalAppearance(oCreature)) return FALSE; else if(nItemType == BASE_ITEM_HEALERSKIT) { if(!GetLocalInt(GetModule(), AI_RULE_HEALERSKITS)) return FALSE; if(oCreature != oTarget && ai_GetAIMode(oCreature, AI_MODE_PARTY_HEALING_OFF)) return FALSE; if(AI_DEBUG) ai_Debug("0i_talents", "1724", "Using " + GetName(oItem) + " nInMelee: " + IntToString(nInMelee) + " targeting: " + GetName(oTarget)); ai_SetLastAction(oCreature, AI_LAST_ACTION_USED_ITEM); ActionUseItemOnObject(oItem, GetFirstItemProperty(oItem), oTarget); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); // We also must check for stack size. if(GetItemStackSize(oItem) == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); return TRUE; } if(ai_CheckSpecialTalentsandUse(oCreature, jTalent, sCategory, nInMelee, oTarget)) return TRUE; return FALSE; } int ai_UseCreatureTalent(object oCreature, string sCategory, int nInMelee, int nLevel = 10, object oTarget = OBJECT_INVALID) { // Get the saved category from oCreature. json jCategory = GetLocalJson(oCreature, sCategory); if(AI_DEBUG) ai_Debug("0i_talents", "2292", "jCategory: " + sCategory + " " + JsonDump(jCategory, 2)); if(JsonGetType(jCategory) == JSON_TYPE_NULL) return FALSE; // If there are no talents at lower levels then start at the lower level. int nMaxTalentLevel = GetLocalInt(oCreature, AI_MAX_TALENT + sCategory); if(AI_DEBUG) ai_Debug("0i_talents", "2297", AI_MAX_TALENT + sCategory + ": " + IntToString(nMaxTalentLevel) + " nLevel: " + IntToString(nLevel)); if(nMaxTalentLevel < nLevel) nLevel = nMaxTalentLevel; if(nLevel < 0 || nLevel > 10) nLevel = 9; json jLevel, jTalent; int nClass, nSlot, nType, nSlotIndex, nMaxSlotIndex, nTalentUsed, nSpell; int bUseMagic = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC); int bUseMagicItems = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC_ITEMS); if(AI_DEBUG) ai_Debug("0i_talents", "2305", "bUseMagic: " + IntToString(bUseMagic) + " bUseMagicItems: " + IntToString(bUseMagicItems) + " nLevel: " + IntToString(nLevel)); // Loop through nLevels down to nMinNoTalentLevel looking for the first talent // (i.e. the highest or best?). while(nLevel > -1) { // Get the array of nLevel cycling down to 0. jLevel = JsonArrayGet(jCategory, nLevel); nMaxSlotIndex = JsonGetLength(jLevel); if(AI_DEBUG) ai_Debug("0i_talents", "2288", "nLevel: " + IntToString(nLevel) + " nMaxSlotIndex: " + IntToString(nMaxSlotIndex)); if(nMaxSlotIndex > 0) { // Get the talent within nLevel cycling from the first to the last. nSlotIndex = 0; while (nSlotIndex < nMaxSlotIndex) { jTalent= JsonArrayGet(jLevel, nSlotIndex); if(AI_DEBUG) ai_Debug("0i_talents", "2300", "nSlotIndex: " + IntToString(nSlotIndex) + " jTalent Type: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 0)))); nType = JsonGetInt(JsonArrayGet(jTalent, 0)); if(bUseMagic) { if(nType == AI_TALENT_TYPE_SPELL) { nTalentUsed = ai_UseCreatureSpellTalent(oCreature, jLevel, jTalent, sCategory, nInMelee, oTarget); // -1 means it was a memorized spell and we need to remove it. if(nTalentUsed == -1) { ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); return TRUE; } else if(nTalentUsed == -2) { ai_RemoveTalentLevel(oCreature, jCategory, jLevel, sCategory, nLevel); } else if(nTalentUsed) return TRUE; } else if(nType == AI_TALENT_TYPE_SP_ABILITY) { // Special ability spells do not need to concentrate?! if(ai_CheckSpecialTalentsandUse(oCreature, jTalent, sCategory, nInMelee, oTarget)) { // When the ability is used that slot is now not readied. // Multiple uses of the same spell are stored in different slots. ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); return TRUE; } } } if(bUseMagicItems && nType == AI_TALENT_TYPE_ITEM) { // Items do not need to concentrate. if(ai_UseCreatureItemTalent(oCreature, jLevel, jTalent, sCategory, nInMelee, oTarget)) { if(AI_DEBUG) ai_Debug("0i_talents", "2337", "Checking if Item is used up: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 4)))); if(JsonGetInt(JsonArrayGet(jTalent, 4)) == -1) { ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); } return TRUE; } } //else if(nType == AI_TALENT_TYPE_FEAT) {} nSlotIndex++; } } else SetLocalInt(oCreature, AI_MAX_TALENT + sCategory, nLevel - 1); nLevel--; } return FALSE; } int ai_UseTalent(object oCreature, int nTalent, object oTarget) { if(AI_DEBUG) ai_Debug("0i_talents", "1912", GetName(oCreature) + " is trying to use " + GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nTalent))) + " on " + GetName(oTarget)); // Get the saved category from oCreature. string sCategory = Get2DAString("ai_spells", "Category", nTalent); json jCategory = GetLocalJson(oCreature, sCategory); if(AI_DEBUG) ai_Debug("0i_talents", "1917", "jCategory: " + sCategory + " " + JsonDump(jCategory, 2)); if(JsonGetType(jCategory) == JSON_TYPE_NULL) return FALSE; json jLevel, jTalent; int nLevel, nClass, nSlot, nType, nSlotIndex, nMaxSlotIndex, nTalentUsed, nSpell; // Loop through nLevels down to nMinNoTalentLevel looking for the first talent // (i.e. the highest or best?). while(nLevel <= 9) { // Get the array of nLevel. jLevel = JsonArrayGet(jCategory, nLevel); nMaxSlotIndex = JsonGetLength(jLevel); if(AI_DEBUG) ai_Debug("0i_talents", "1925", "nLevel: " + IntToString(nLevel) + " nMaxSlotIndex: " + IntToString(nMaxSlotIndex)); if(nMaxSlotIndex > 0) { // Get the talent within nLevel cycling from the first to the last. nSlotIndex = 0; while (nSlotIndex < nMaxSlotIndex) { jTalent= JsonArrayGet(jLevel, nSlotIndex); if(AI_DEBUG) ai_Debug("0i_talents", "1936", "nSlotIndex: " + IntToString(nSlotIndex) + " jTalent Type: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 0)))); nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); if(nSpell == nTalent) { nType = JsonGetInt(JsonArrayGet(jTalent, 0)); if(nType == AI_TALENT_TYPE_SPELL || nType == AI_TALENT_TYPE_SP_ABILITY) { if(ai_UseTalentOnObject(oCreature, jTalent, oTarget, 0)) { ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); return TRUE; } } else if(nType == AI_TALENT_TYPE_ITEM) { // Items do not need to concentrate. if(ai_UseCreatureItemTalent(oCreature, jLevel, jTalent, sCategory, 0, oTarget)) { if(AI_DEBUG) ai_Debug("0i_talents", "1955", "Checking if Item is used up: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 4)))); if(JsonGetInt(JsonArrayGet(jTalent, 4)) == -1) { ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex); } return TRUE; } } } nSlotIndex++; } } nLevel++; } return FALSE; } int ai_UseTalentOnObject(object oCreature, json jTalent, object oTarget, int nInMelee) { int nClass, nLevel, nSlot, nMetaMagic, nDomain; int nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); int nType = JsonGetInt(JsonArrayGet(jTalent, 0)); if(nType == AI_TALENT_TYPE_SPELL) { if(!ai_CastInMelee(oCreature, nSpell, nInMelee)) return FALSE; nClass = JsonGetInt(JsonArrayGet(jTalent, 2)); if(Get2DAString("classes", "MemorizesSpells", nClass) == "1") { nLevel = JsonGetInt(JsonArrayGet(jTalent, 3)); nSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); if(GetMemorizedSpellIsDomainSpell(oCreature, nClass, nLevel, nSlot) == 1) nDomain = nLevel; else nDomain = 0; nMetaMagic = GetMemorizedSpellMetaMagic(oCreature, nClass, nLevel, nSlot); } else { nMetaMagic = METAMAGIC_NONE; nDomain = 0; } if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell)) return TRUE; } else if(nType == AI_TALENT_TYPE_SP_ABILITY) { if(AI_DEBUG) ai_Debug("0i_talents", "1790", GetName(oCreature) + " is using a special ability!"); nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); nClass = 255; if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell)) return TRUE; } else if(nType == AI_TALENT_TYPE_ITEM) { object oItem = StringToObject(JsonGetString(JsonArrayGet(jTalent, 2))); int nBaseItemType = GetBaseItemType(oItem); if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell, nBaseItemType)) return TRUE; int nIndex, nSubIndex = 0; nSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); itemproperty ipProp = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ipProp)) { if(nIndex++ == nSlot) break; ipProp = GetNextItemProperty(oItem); } // Cast items have the following: // 1)Single_Use. // 2-6) Charges/Use [Note: 7 is 0 charges per use]. // 8-12) Uses/Day [Note: 13 is unlimited uses per day]. // We set the slot to -1 to let the other function know we need this talent removed. int nUses = GetItemPropertyCostTableValue(ipProp); if(nUses == 1) { if(AI_DEBUG) ai_Debug("0i_talents", "1816", "Single Use item."); if(AI_DEBUG) ai_Debug("0i_talents", "1817", "Stack size: " + IntToString(GetItemStackSize(oItem))); // We also must check for stack size. if(GetItemStackSize(oItem) == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); } else if(nUses > 1 && nUses < 7) { int nCharges = GetItemCharges(oItem); // If the item is equipable then do not use the last charge! if(Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem)) != "0x00000") { if(nCharges <= 7 - nUses) return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "1824", "Item charges: " + IntToString(nCharges)); if(nCharges < (7 - nUses) * 2) { if(AI_DEBUG) ai_Debug("0i_talents", "1829", "Stack size: " + IntToString(GetItemStackSize(oItem))); // We also must check for stack size. if(GetItemStackSize(oItem) == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); } } else if(nUses > 7 && nUses < 13) { int nPerDay = GetItemPropertyUsesPerDayRemaining(oItem, ipProp); if(AI_DEBUG) ai_Debug("0i_talents", "1837", "Item uses: " + IntToString(nPerDay)); if(nPerDay == 1) { if(AI_DEBUG) ai_Debug("0i_talents", "1842", "Stack size: " + IntToString(GetItemStackSize(oItem))); // We also must check for stack size. if(GetItemStackSize(oItem) == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); } } // Lets not always use unlimited items! else if(nUses == 7 || nUses == 13) { if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; } ai_SetLastAction(oCreature, nSpell); ActionUseItemOnObject(oItem, ipProp, oTarget, nSubIndex); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); if(AI_DEBUG) ai_Debug("0i_talents", "1850", GetName(oCreature) + " is using " + GetName(oItem) + " on " + GetName(oTarget)); return TRUE; } if(AI_DEBUG) ai_Debug("0i_talents", "1853", "nMetaMagic: " + IntToString(nMetaMagic) + " nDomain: " + IntToString(nDomain) + " nClass: " + IntToString(nClass)); ai_SetLastAction(oCreature, nSpell); ActionCastSpellAtObject(nSpell, oTarget, nMetaMagic, FALSE, nDomain, 0, FALSE, nClass, FALSE); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); if(AI_DEBUG) { string sSpellName = GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpell))); ai_Debug("0i_talents", "1859", GetName(oCreature) + " is casting " + sSpellName + " on " + GetName(oTarget)); } return TRUE; } int ai_UseTalentAtLocation(object oCreature, json jTalent, object oTarget, int nInMelee) { int nSpell, nClass, nLevel, nSlot, nMetaMagic, nDomain; int nType = JsonGetInt(JsonArrayGet(jTalent, 0)); if(nType == AI_TALENT_TYPE_SPELL) { if(!ai_CastInMelee(oCreature, nSpell, nInMelee)) return FALSE; nClass = JsonGetInt(JsonArrayGet(jTalent, 2)); if(Get2DAString("classes", "MemorizesSpells", nClass) == "1") { nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); nLevel = JsonGetInt(JsonArrayGet(jTalent, 3)); nSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); if(GetMemorizedSpellIsDomainSpell(oCreature, nClass, nLevel, nSlot) == 1) nDomain = nLevel; else nDomain = 0; nMetaMagic = GetMemorizedSpellMetaMagic(oCreature, nClass, nLevel, nSlot); } else { nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); nMetaMagic = METAMAGIC_NONE; nDomain = 0; } } else if(nType == AI_TALENT_TYPE_SP_ABILITY) { if(AI_DEBUG) ai_Debug("0i_talents", "1888", GetName(oCreature) + " is using a special ability!"); nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); nClass = 255; } else if(nType == AI_TALENT_TYPE_ITEM) { object oItem = StringToObject(JsonGetString(JsonArrayGet(jTalent, 2))); int nBaseItemType = GetBaseItemType(oItem); if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell, nBaseItemType)) return TRUE; int nIndex; int nSubIndex = JsonGetInt(JsonArrayGet(jTalent, 3));; nSlot = JsonGetInt(JsonArrayGet(jTalent, 4)); itemproperty ipProp = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ipProp)) { if(nIndex++ == nSlot) break; ipProp = GetNextItemProperty(oItem); } // Cast items have the following: // 1)Single_Use. // 2-6) Charges/Use [Note: 7 is 0 charges per use]. // 8-12) Uses/Day [Note: 13 is unlimited uses per day]. // We set the slot to -1 to let the other function know we need this talent removed. int nUses = GetItemPropertyCostTableValue(ipProp); if(nUses == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); else if(nUses > 1 && nUses < 7) { if(AI_DEBUG) ai_Debug("0i_talents", "1915", "Item charges: " + IntToString(GetItemCharges(oItem))); int nCharges = GetItemCharges(oItem); // If the item is equipable then do not use the last charge! if(Get2DAString("baseitems", "EquipableSlots", GetBaseItemType(oItem)) != "0x00000") { if(nCharges <= 7 - nUses) return FALSE; } if(AI_DEBUG) ai_Debug("0i_talents", "1824", "Item charges: " + IntToString(nCharges)); if(nCharges < (7 - nUses) * 2) { if(AI_DEBUG) ai_Debug("0i_talents", "1829", "Stack size: " + IntToString(GetItemStackSize(oItem))); // We also must check for stack size. if(GetItemStackSize(oItem) == 1) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); } } else if(nUses > 7 && nUses < 13) { if(AI_DEBUG) ai_Debug("0i_talents", "1923", "Item uses: " + IntToString(GetItemPropertyUsesPerDayRemaining(oItem, ipProp))); int nPerDay = GetItemPropertyUsesPerDayRemaining(oItem, ipProp); if(nUses == 8 && nPerDay == 1 || nUses == 9 && nPerDay < 4 || nUses == 10 && nPerDay < 6 || nUses == 11 && nPerDay < 8 || nUses == 12 && nPerDay < 10) JsonArrayInsertInplace(jTalent, JsonInt(-1), 4); } // Lets not always use unlimited items! else if(nUses == 7 || nUses == 13) { if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; } if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell)) return TRUE; ai_SetLastAction(oCreature, nSpell); ActionUseItemAtLocation(oItem, ipProp, GetLocation(oTarget), nSubIndex); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); if(AI_DEBUG) ai_Debug("0i_talents", "1934", GetName(oCreature) + " is using " + GetName(oItem) + " at a location."); return TRUE; } if(ai_CheckCombatPosition(oCreature, oTarget, nInMelee, nSpell)) return TRUE; ai_SetLastAction(oCreature, nSpell); ActionCastSpellAtLocation(nSpell, GetLocation(oTarget), nMetaMagic, FALSE, 0, FALSE, nClass, FALSE, nDomain); ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature)); string sSpellName = GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpell))); if(AI_DEBUG) ai_Debug("0i_talents", "1943", GetName(oCreature) + " is casting " + sSpellName + " at a location!"); return TRUE; } int ai_CheckSpecialTalentsandUse(object oCreature, json jTalent, string sCategory, int nInMelee, object oTarget) { int nSpell = JsonGetInt(JsonArrayGet(jTalent, 1)); if(AI_DEBUG) ai_Debug("0i_talents", "1949", "nSpell: " + GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpell))) + " sCategory: " + sCategory); if(sCategory == AI_TALENT_DISCRIMINANT_AOE) { //ai_Debug("0i_talents", "1953", "CompareLastAction: " + // IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. //if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; // Check to see if Disjunction should *not* be cast. if(nSpell == SPELL_MORDENKAINENS_DISJUNCTION) { // Our master does not want us using any type of dispel! if(ai_GetMagicMode(oCreature, AI_MAGIC_STOP_DISPEL)) return FALSE; float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); // Get the biggest group we can. string sIndex = IntToString(ai_GetHighestMeleeIndexNotInAOE(oCreature)); oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex); if(!ai_CreatureHasDispelableEffect(oCreature, oTarget)) return FALSE; // Maybe we should do an area of effect instead? int nEnemies = ai_GetNumOfEnemiesInRange(oTarget, 5.0); if(nEnemies > 2) { if(ai_UseTalentAtLocation(oCreature, jTalent, oTarget, nInMelee)) return TRUE; } } // These spells have a Range of Personal i.e. cast on themselves, and // an Area of Effect of Colossal (10.0). else if(nSpell == SPELL_FIRE_STORM || nSpell == SPELL_STORM_OF_VENGEANCE) { // Make sure we have enough enemies to use this on. int nEnemies = ai_GetNumOfEnemiesInRange(oCreature, 10.0); if(nEnemies < 2) return FALSE; // Get the nearest target to check defenses on. oTarget = ai_GetNearestTarget(oCreature, 10.0); if(!ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; if(ai_UseTalentAtLocation(oCreature, jTalent, oTarget, nInMelee)) return TRUE; } else if(nSpell == SPELL_UNDEATH_TO_DEATH) { float fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); int nUndead = ai_GetRacialTypeCount(oCreature, RACIAL_TYPE_UNDEAD, fRange); if(nUndead < 3) return FALSE; oTarget = ai_GetLowestCRRacialTarget(oCreature, RACIAL_TYPE_UNDEAD, fRange); } // Get a target for discriminant spells if one is not already set. if(oTarget == OBJECT_INVALID) { float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_CheckForGroupedTargetNotInAOE(oCreature, fRange); } if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget) || !ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; } else if(sCategory == AI_TALENT_INDISCRIMINANT_AOE) { //ai_Debug("0i_talents", "1991", "CompareLastAction: " + // IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. //if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; // These spells have a Range of Personal i.e. cast on themselves, and // an Area of Effect of Colossal (10.0). if(nSpell == SPELL_METEOR_SWARM) { // Make sure we have enough enemies and few allies to hit. int nAllies = ai_GetNumOfAlliesInGroup(oCreature, 10.0); int nEnemies = ai_GetNumOfEnemiesInRange(oCreature, 10.0); if(nAllies > 1 || nEnemies < 2) return FALSE; // Get the nearest target to check defenses on. oTarget = ai_GetNearestTarget(oCreature, 10.0); if(!ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; if(ai_UseTalentAtLocation(oCreature, jTalent, oCreature, nInMelee)) return TRUE; } // Get a target for indiscriminant spells if one is not already set. if(oTarget == OBJECT_INVALID) { float fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_CheckForGroupedTargetNotInAOE(oCreature, fRange); // Check for the number of allies, if there are too many then skip. if(oTarget == OBJECT_INVALID) return FALSE; int nRoll = d6() + 1; if(GetAssociateType(oCreature)) nRoll = d3(); int nAllies = ai_GetNumOfAlliesInGroup(oTarget, AI_RANGE_CLOSE); if(AI_DEBUG) ai_Debug("0i_talents", "2084", "Num of Allies in range: " + IntToString(nAllies)+ " < nRoll: " + IntToString(nRoll)); if(nAllies >= nRoll) return FALSE; } if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget) || !ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; //********************************************************************** //********** These spells are checked after picking a target *********** //********************************************************************** // Check if the Sleep spells are being used appropriately. if(nSpell == SPELL_SLEEP) { if(GetHitDice(oTarget) > 4) return FALSE; } // Lets only use silence on casters. else if(nSpell == SPELL_SILENCE) { if(!ai_CheckClassType(oTarget, AI_CLASS_TYPE_CASTER)) { oTarget = ai_GetNearestClassTarget(oCreature, AI_CLASS_TYPE_CASTER); if(oTarget == OBJECT_INVALID) return FALSE; } } } else if(sCategory == AI_TALENT_RANGED) { //ai_Debug("0i_talents", "2045", "CompareLastAction: " + // IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. //if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; // Check to see if Dispel Magic and similar spells should *not* be cast if(nSpell == SPELL_DISPEL_MAGIC || nSpell == SPELL_LESSER_DISPEL || nSpell == SPELL_GREATER_DISPELLING) { // Our master does not want us using any type of dispel! if(ai_GetMagicMode(oCreature, AI_MAGIC_STOP_DISPEL)) return FALSE; float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); // Lets get a caster as they should have more buffs. oTarget = ai_GetNearestClassTarget(oCreature, AI_CLASS_TYPE_CASTER, fRange); // No caster then get the most powerful enemy! if(oTarget == OBJECT_INVALID) oTarget = ai_GetHighestCRTarget(oCreature, fRange); if(oTarget != OBJECT_INVALID) { if(!ai_CreatureHasDispelableEffect(oCreature, oTarget)) return FALSE; // Maybe we should do an area of effect instead? int nEnemies = ai_GetNumOfEnemiesInRange(oTarget, 5.0); if(nEnemies > 2) { if(ai_UseTalentAtLocation(oCreature, jTalent, oTarget, nInMelee)) return TRUE; } } if(oTarget == OBJECT_INVALID) return FALSE; } // Make sure the spell will work on the target. else if(nSpell == SPELL_HOLD_PERSON || nSpell == SPELL_DOMINATE_PERSON || nSpell == SPELL_CHARM_PERSON) { if(oTarget != OBJECT_INVALID) { int nRaceType = GetRacialType(oTarget); if(AI_DEBUG) ai_Debug("0i_talents", "2075", " Person Spell race: " + IntToString(nRaceType)); if((nRaceType > 6 && nRaceType < 12) || nRaceType > 15) oTarget = OBJECT_INVALID; } if(oTarget == OBJECT_INVALID) { float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_GetNearestRacialTarget(oCreature, AI_RACIAL_TYPE_HUMANOID, fRange); if(oTarget == OBJECT_INVALID) return FALSE; } } else if(nSpell == SPELL_HOLD_ANIMAL || nSpell == SPELL_DOMINATE_ANIMAL) { if(oTarget != OBJECT_INVALID) { if(GetRacialType(oTarget) != RACIAL_TYPE_ANIMAL) oTarget = OBJECT_INVALID; } if(oTarget == OBJECT_INVALID) { float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_GetNearestRacialTarget(oCreature, AI_RACIAL_TYPE_ANIMAL_BEAST, fRange); if(oTarget == OBJECT_INVALID) return FALSE; } } // Get a target for ranged spells if one is not already set. if(oTarget == OBJECT_INVALID) { float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_GetSpellTargetBasedOnSaves(oCreature, nSpell, fRange); } if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget) || !ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; //********************************************************************** //********** These spells are checked after picking a target *********** //********************************************************************** // Don't use Domination spells on players! They don't work. if((nSpell == SPELL_DOMINATE_MONSTER || nSpell == SPELL_DOMINATE_PERSON)) { if(ai_GetIsCharacter(oTarget)) return FALSE; } // Check to see if they have the shield spell up. else if(nSpell == SPELL_MAGIC_MISSILE) { if(GetHasSpellEffect(SPELL_SHIELD, oTarget)) return FALSE; } // Scare only works on 5 hitdice or less. else if(nSpell == SPELL_SCARE) { if(GetHitDice(oTarget) > 5) return FALSE; } // Don't use drown against nonliving opponents. else if(nSpell == SPELL_DROWN) { if(ai_IsNonliving(GetRacialType(oTarget))) return FALSE; } // Don't use Power Word Kill on Targets with more than 100hp else if(nSpell == SPELL_POWER_WORD_KILL) { if(GetCurrentHitPoints(oTarget) <= 100) return FALSE; } } else if(sCategory == AI_TALENT_TOUCH) { //ai_Debug("0i_talents", "2139", "CompareLastAction: " + // IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. //if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; // Get a target for touch spells if one is not already set. if(oTarget == OBJECT_INVALID) { oTarget = ai_GetSpellTargetBasedOnSaves(oCreature, nSpell, AI_RANGE_MELEE); } if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget) || !ai_CastOffensiveSpellVsTarget(oCreature, oTarget, nSpell) || ai_CreatureImmuneToEffect(oCreature, oTarget, nSpell)) return FALSE; } else if(sCategory == AI_TALENT_HEALING) { int nHpLost = ai_GetPercHPLoss(oTarget); // If the target is bloody then just use the best we have! if(nHpLost > AI_HEALTH_BLOODY) { // Make sure we should use a mass heal on us or an ally! // Two allies need healing or one is almost dead to use mass heal! if(nSpell == SPELL_MASS_HEAL) { int bWoundedAlly; object oAlly = ai_GetNearestAlly(oTarget); if(oAlly != OBJECT_INVALID) { // If we don't have a nearby ally that needs healed then skip. if(ai_GetPercHPLoss(oAlly) > AI_HEALTH_WOUNDED || GetDistanceBetween(oCreature, oAlly) > 9.0f) return FALSE; } } // Make sure they have taken enough damage. int nHpDmg = GetMaxHitPoints(oTarget) - GetCurrentHitPoints(oTarget); if(!ai_ShouldWeCastThisCureSpell(nSpell, nHpDmg)) return FALSE; } } else if(sCategory == AI_TALENT_ENHANCEMENT) { if(AI_DEBUG) ai_Debug("0i_talents", "2713", "CompareLastAction: " + IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; if(nSpell == SPELL_INVISIBILITY || nSpell == SPELL_SANCTUARY) { // Lets not run past an enemy to cast an enhancement unless we have // the ability to move in combat, bad tactics! float fRange; if(ai_CanIMoveInCombat(oCreature)) fRange = AI_RANGE_PERCEPTION; else { fRange = GetDistanceBetween(oCreature, GetLocalObject(oCreature, AI_ENEMY_NEAREST)) - 3.0f; // Looks bad when your right next to an ally, but technically the enemy is closer. if(fRange < AI_RANGE_MELEE) fRange = AI_RANGE_MELEE; } oTarget = ai_GetAllyToHealTarget(oCreature, fRange); if(oTarget != OBJECT_INVALID) { int nHp = ai_GetPercHPLoss(oTarget); int nHpLimit = ai_GetHealersHpLimit(oCreature); if(nHp > nHpLimit) return FALSE; } } if(nSpell == SPELL_PRAYER) { int nEnemies = ai_GetNumOfEnemiesInRange(oCreature, 10.0); int nAllies = ai_GetNumOfAlliesInGroup(oCreature, 10.0); if(nEnemies + nAllies < 5) return FALSE; oTarget = oCreature; } // Since haste does not have an effect when it comes from items when we // check for item properties we set this variable so we know they have it. else if(nSpell == SPELL_HASTE && GetLocalInt(oCreature, sIPHasHasteVarname)) return FALSE; // Only reason to cast Ultravision(Darkvision) in combat is if a Darkness // spell is nearby. else if(nSpell == SPELL_DARKVISION) { int nCnt = 1, bCastSpell; string sAOEType; object oAOE = GetNearestObject(OBJECT_TYPE_AREA_OF_EFFECT, oCreature, nCnt); while(oAOE != OBJECT_INVALID && GetDistanceBetween(oCreature, oAOE) <= AI_RANGE_PERCEPTION) { // AOE's have the tag set to the "LABEL" in vfx_persistent.2da sAOEType = GetTag(oAOE); if(AI_DEBUG) ai_Debug("0i_talents", "2759", "Ultravision check; AOE tag: " + sAOEType); if(sAOEType == "VFX_PER_DARKNESS") { if(!GetHasFeat(FEAT_DARKVISION)) bCastSpell = TRUE; break; } oAOE = GetNearestObject(OBJECT_TYPE_AREA_OF_EFFECT, oCreature, ++nCnt); } if(!bCastSpell) return FALSE; } // Get a target for enhancement spells if one is not already set. if(oTarget == OBJECT_INVALID) { // Get talents range and target. float fRange = ai_GetSpellRange(nSpell); // Personal spell if(fRange == 0.1f) oTarget = oCreature; // Range/Touch spell else oTarget = ai_GetAllyBuffTarget(oCreature, nSpell, fRange); } if(AI_DEBUG) ai_Debug("0i_talents", "2260", " oTarget: " + GetName(oTarget) + " HasSpellEffect: " + IntToString(GetHasSpellEffect(nSpell, oTarget))); if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget)) return FALSE; //********************************************************************** //********** These spells are checked after picking a target *********** //********************************************************************** // Weapon enhancing spells only work on melee weapons! if(nSpell == SPELL_MAGIC_WEAPON || nSpell == SPELL_GREATER_MAGIC_WEAPON || nSpell == SPELL_BLESS_WEAPON || nSpell == SPELL_FLAME_WEAPON || nSpell == SPELL_DARKFIRE) { object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget); if(!ai_GetIsMeleeWeapon(oWeapon)) return FALSE; } // Should we ignore associates? if(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(oTarget) > 1) return FALSE; } else if(sCategory == AI_TALENT_PROTECTION) { if(AI_DEBUG) ai_Debug("0i_talents", "2281", "CompareLastAction: " + IntToString(ai_CompareLastAction(oCreature, nSpell))); // If we used this spell talent last round then don't use it this round. if(ai_CompareLastAction(oCreature, nSpell)) return FALSE; // Stone bones only effects the undead. if(nSpell == SPELL_STONE_BONES) { if(oTarget != OBJECT_INVALID) { if(GetRacialType(oTarget) != RACIAL_TYPE_UNDEAD) oTarget = OBJECT_INVALID; } if(oTarget == OBJECT_INVALID) { float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); oTarget = ai_GetNearestRacialTarget(oCreature, RACIAL_TYPE_UNDEAD, fRange); if(oTarget == OBJECT_INVALID) return FALSE; } } else if(nSpell == SPELL_MAGIC_FANG) { oTarget = GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, oCreature); if(oTarget == OBJECT_INVALID) return FALSE; } // Lets see if we should cast resistances in our current situation, // lets check for enemy casters that may have energy damaging spells, or energy weapons. else if(nSpell == SPELL_ENDURE_ELEMENTS || nSpell == SPELL_PROTECTION_FROM_ELEMENTS || nSpell == SPELL_RESIST_ELEMENTS || nSpell == SPELL_ENERGY_BUFFER) { int bCastSpell; object oEnemy = ai_GetEnemyAttackingMe(oCreature); if(oEnemy != OBJECT_INVALID) { object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oEnemy); if(oWeapon == OBJECT_INVALID) oWeapon = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oEnemy); if(oWeapon == OBJECT_INVALID) oWeapon = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oEnemy); if(AI_DEBUG) ai_Debug("0i_talents", "2812", GetName(oEnemy) + " is using weapon: " + GetName(oWeapon)); if(oWeapon != OBJECT_INVALID) { itemproperty nProperty = GetFirstItemProperty(oWeapon); while(GetIsItemPropertyValid(nProperty)) { if(GetItemPropertyType(nProperty) == ITEM_PROPERTY_DAMAGE_BONUS) { int nSubType = GetItemPropertySubType(nProperty); if(AI_DEBUG) ai_Debug("0i_talents", "2821", GetName(oWeapon) + " has PropertySubType: " + IntToString(nSubType) + " If equals [6,7,9,10,13] don't cast!"); if(nSubType == 6 || nSubType == 7 || nSubType == 9 || nSubType == 10 || nSubType == 13) { bCastSpell = TRUE; break; } } nProperty = GetNextItemProperty(oWeapon); } } } if(ai_GetNearestClassTarget(oCreature, AI_CLASS_TYPE_CASTER) != OBJECT_INVALID) bCastSpell = TRUE; if(!bCastSpell) return FALSE; } // Get a target for protection spells if one is not already set. if(oTarget == OBJECT_INVALID) { // Get talents range and target. float fRange = ai_GetSpellRange(nSpell); // Personal spell if(fRange == 0.1f) oTarget = oCreature; // Range/Touch spell else oTarget = ai_GetAllyBuffTarget(oCreature, nSpell, fRange); } if(oTarget == OBJECT_INVALID || GetHasSpellEffect(nSpell, oTarget)) return FALSE; //********************************************************************** //********** These spells are checked after picking a target *********** //********************************************************************** // Don't double up Stoneskin, Ghostly visage, or Ethereal visage. if(nSpell == SPELL_GHOSTLY_VISAGE || nSpell == SPELL_ETHEREAL_VISAGE || nSpell == SPELL_STONESKIN) { if(GetHasSpellEffect(SPELL_ETHEREAL_VISAGE, oTarget) || GetHasSpellEffect(SPELL_STONESKIN, oTarget) || GetHasSpellEffect(SPELL_GHOSTLY_VISAGE, oTarget)) return FALSE; } // Don't use displacement if we are invisible! else if(nSpell == SPELL_DISPLACEMENT) { if(GetHasSpellEffect(SPELL_INVISIBILITY, oTarget) || GetHasSpellEffect(SPELL_IMPROVED_INVISIBILITY, oTarget) || GetHasSpellEffect(SPELL_INVISIBILITY_SPHERE, oTarget) || GetHasSpellEffect(SPELL_DISPLACEMENT, oTarget)) return FALSE; } // Should we ignore associates? if(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(oTarget) > 1) return FALSE; } else if(sCategory == AI_TALENT_SUMMON) { if(GetAssociate(ASSOCIATE_TYPE_SUMMONED, oCreature) != OBJECT_INVALID) return FALSE; if(oTarget == OBJECT_INVALID) { /* Removed for now, summons creature in location that enemy was... looks bad. float fRange; if(nInMelee) fRange = AI_RANGE_MELEE; else fRange = ai_GetOffensiveSpellSearchRange(oCreature, nSpell); // Select lowest enemy combat target for summons. oTarget = ai_GetLowestCRTarget(oCreature, fRange); if(oTarget == OBJECT_INVALID) oTarget = oCreature; */ oTarget = oCreature; if(ai_UseTalentAtLocation(oCreature, jTalent, oTarget, nInMelee)) { DelayCommand(4.0, ai_NameAssociate(oCreature, ASSOCIATE_TYPE_SUMMONED, "")); return TRUE; } } } else if(sCategory == AI_TALENT_CURE) { } if(ai_UseTalentOnObject(oCreature, jTalent, oTarget, nInMelee)) return TRUE; return FALSE; }