/*//////////////////////////////////////////////////////////////////////////////
 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 ranged talent.
int ai_TryRangedTalents(object oCreature, object oTarget, int nInMelee);
// Returns TRUE if oCreature uses a harmful melee talent.
int ai_TryMeleeTalents(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Charge
int ai_TryCharging(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Grapple
int ai_TryGrappling(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Trip
int ai_TryTripping(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 the spell level value to be used to compare if they should cast a
// spell vs use Polymorph.
int ai_GetHasPolymorphSelfFeat(object oCreature);
// 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);
//:: [PRC8] Tests & runs Awesome Blow
int ai_TryAwesomeBlowFeat(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Bullrush
int ai_TryBullrushFeat(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Overrun
int ai_TryOverrunFeat(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Ranged Disarm
int ai_TryRangedDisarmFeat(object oCreature, object oTarget);
//:: [PRC8] Tests & runs Swarm of Arrows
int ai_TrySwarmofArrowsFeat(object oCreature);
//:: [PRC8] Tests & runs Inlindl School Strike
int ai_TryInlindlSchoolFeat(object oCreature, object oTarget);
// *****************************************************************************
// *****************************  TALENT SCRIPTS  ******************************
// *****************************************************************************
// These functions do not fall into another section.

// Returns the MaxLevel used in GetCreatureTalent for oCreature.
// This checks the level of oCreature.
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, int bPreBuff, int bFullBuff, 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.
// bForceTalentSetup - Tells the talent system to force a talent setup but no Buffing.
void ai_SetCreatureTalents(object oCreature, int bMonster, int bForceTalentSetup = FALSE);
// 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.
// bCheckPosition will see if we should move back due to spell failure in combat.
int ai_UseTalentOnObject(object oCaster, json jTalent, object oTarget, int nInMelee, int bCheckPosition = TRUE);
// 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", "268", "nHp: " + IntToString(nHp) +
             "< nHpLimit: " + IntToString(nHpLimit));
    if(nHp > nHpLimit) return FALSE;
    int nDamage = GetMaxHitPoints(oTarget) - GetCurrentHitPoints(oTarget);
    if(AI_DEBUG) ai_Debug("0i_talents", "272", GetName(oTarget) + " has lost " + IntToString(nDamage) + " hitpoints!");
    int bUseMagic = !ai_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC);
    // If they are about to die then throw caution to the wind and HEAL!
    if(nHp <= AI_HEALTH_BLOODY || nHp < 11) nInMelee = 0;
    int nRace = GetRacialType(oTarget);
    if(nRace == RACIAL_TYPE_UNDEAD)
    {
        json jCategory = GetLocalJson(oCreature, AI_TALENT_TOUCH);
        if(JsonGetType(jCategory) == JSON_TYPE_NULL) return FALSE;
        int nLevel = 4, bFound, nSlotIndex, nMaxSlotIndex, nSpell;
        json jLevel, jTalent;
        // Loop through nLevels looking for nTalent
        while(nLevel > 0)
        {
            // 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);
                    if(AI_DEBUG) ai_Debug("0i_talents", "296", "jTalent: " + JsonDump(jTalent, 4));
                    nSpell = JsonGetInt(JsonArrayGet(jTalent, 1));
                    if(nSpell == SPELL_INFLICT_CRITICAL_WOUNDS)
                    {
                        bFound = TRUE;
                        break;
                    }
                    else if(nSpell == SPELL_INFLICT_SERIOUS_WOUNDS)
                    {
                        bFound = TRUE;
                        break;
                    }
                    else if(nSpell == SPELL_INFLICT_MODERATE_WOUNDS)
                    {
                        bFound = TRUE;
                        break;
                    }
                    else if(nSpell == SPELL_INFLICT_LIGHT_WOUNDS)
                    {
                        bFound = TRUE;
                        break;
                    }
                    nSlotIndex++;
                }
            }
            if(bFound) break;
            nLevel--;
        }
        if(bFound)
        {
            if(ai_UseTalentOnObject(oCreature, jTalent, oTarget, nInMelee))
            {
                ai_RemoveTalent(oCreature, jCategory, jLevel, AI_TALENT_TOUCH, nLevel, nSlotIndex);
                return TRUE;
            }
        }
    }
    else
    {
        // Do they have Lay on Hands?
        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(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;
        }
    }
    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;
        case SPELL_RAISE_DEAD :
        case SPELL_RESURRECTION :
            if(ai_GetHasNegativeCondition(AI_CONDITION_DEAD, 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", "362", "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", "370", 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", "379", "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", "387", "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", "396", "nSlotIndex: " + IntToString(nSlotIndex) +
                         " jTalent Type: " + IntToString(JsonGetType(jTalent)));
                // Check to see if the talent matches oTargets nConditions.
                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", "460", 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;
    // Check for dead allies.
    object oTarget;
    object oDead = ai_GetNearestAlly(oCreature, 1, CREATURE_TYPE_IS_ALIVE, FALSE);
    if(oDead == 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_CONDITION_DEAD;
        oTarget = oDead;
    }
    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", "501", "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 powerful 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_GetHasPolymorphSelfFeat(object oCreature)
{
    // Lets check to see if we should actually Polymorph?
    if(GetHasFeat(FEAT_EPIC_OUTSIDER_SHAPE)) return 3;
    else if(GetHasFeat(FEAT_EPIC_CONSTRUCT_SHAPE)) return 3;
    else if(GetHasFeat(FEAT_EPIC_WILD_SHAPE_DRAGON)) return 3;
    else if(GetHasFeat(FEAT_EPIC_WILD_SHAPE_UNDEAD)) return 3;
    else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_4)) return 2;
    else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_3)) return 2;
    else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_2)) return 1;
    else if(GetHasFeat(FEAT_GREATER_WILDSHAPE_1)) return 1;
    if(GetHasFeat(FEAT_HUMANOID_SHAPE)) return 0;
    else if(GetHasFeat(FEAT_ELEMENTAL_SHAPE)) return 0;
    else if(GetHasFeat(FEAT_WILD_SHAPE)) return 0;
    return -1;
}
int ai_TryPolymorphSelfFeat(object oCreature)
{
    // Lets check to see if we should actually Polymorph?
    if(GetHasFeat(FEAT_EPIC_OUTSIDER_SHAPE))
    {
        int nSubFeat = Random(3) + 733; // 733 azer, 734 rakshasa,  735 Slaad.
        if(AI_DEBUG) ai_Debug("0i_talents", "863", " Using outsider shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "868", " Using construct shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "873", " Using dragon shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "878", " Using undead shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "887", " Using wild shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "897", " Using wild shape 3 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "910", " Using greater wild shape 2 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "910", " Using greater wild shape 1 feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "915", " Using humanoid shape feat: " + IntToString(nSubFeat));
        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_DEBUG) ai_Debug("0i_talents", "958", " Using Elemental shape feat: " + IntToString(nSubFeat));
        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 ||
           !GetLocalInt(GetModule(), AI_RULE_SUMMON_COMPANIONS))
        {
            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", "985", " 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_GetMagicMode(oCreature, AI_MAGIC_NO_MAGIC)) 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;
}
//:: [PRC8] Tests & runs Awesome Blow
int ai_TryAwesomeBlowFeat(object oCreature, object oTarget)
{
    //:: FEAT_AWESOME_BLOW (5370)
    if(!GetHasFeat(5370, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1250", "ai_TryAwesomeBlowFeat: Sanity Check PASSED!");
    //:: Can't Awesome Blow incorporeal creatures
    if(GetLocalInt(oTarget, "X2_L_IS_INCORPOREAL") || ai_GetHasEffectType(oTarget, EFFECT_TYPE_ETHEREAL)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1255", "ai_TryAwesomeBlowFeat: Incorporealty check PASSED!");
    //:: Get sizes
    int nSizeCreature   = GetCreatureSize(oCreature);
    int nSizeTarget     = GetCreatureSize(oTarget);
    //:: Must be larger than target
    if(nSizeCreature <= nSizeTarget)
    return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1265", "ai_TryAwesomeBlowFeat: Size check PASSED!");
    //:: Should we use Awesome Blow?
    if(!ai_CanIAwesomeBlow(oCreature, oTarget)) return FALSE;
    ai_UseFeat(oCreature, 5370, oTarget);
    return TRUE;
}
//:: [PRC8] Tests & runs Overrun
int ai_TryOverrunFeat(object oCreature, object oTarget)
{
    //:: FEAT_OVERRUN (2811)
    if(!GetHasFeat(2811, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1281", "ai_TryOverrunFeat: Sanity Check PASSED!");
    //:: Get sizes
    int nSizeCreature   = GetCreatureSize(oCreature);
    int nSizeTarget     = GetCreatureSize(oTarget);
    //:: One size smaller than target at best.
    if(nSizeCreature + 1 <= nSizeTarget) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1291", "ai_TryOverrunFeat: Size Check PASSED!");
    // Should we use Overrun?
    if(!ai_CanIOverrun(oCreature, oTarget)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1296", "ai_TryOverrunFeat: ai_CanIOverrun Check PASSED!");
       ai_UseFeat(oCreature, 2811, oTarget);
    return TRUE;
}
//:: [PRC8] Tests & runs Bullrush
int ai_TryBullrushFeat(object oCreature, object oTarget)
{
    //:: FEAT_BULLRUSH (2810)
    //:: FEAT_IMPROVED_BULLRUSH (2806)
    //:: FEAT_BULLRUSH_CHARGE (3413)
    if(!GetHasFeat(2810 /* FEAT_BULLRUSH */, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1312", "ai_TryBullrushFeat: Sanity Check PASSED!");
    //:: Get sizes
    int nSizeCreature = GetCreatureSize(oCreature);
    int nSizeTarget   = GetCreatureSize(oTarget);
    //:: One size smaller than target at best
    if(nSizeCreature + 1 <= nSizeTarget) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1322", "ai_TryBullrushFeat: Size Check PASSED!");
    if(d10() > 5) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1327", "ai_TryBullrushFeat: Randomizer PASSED!");
    //:: Verify if attempt is reasonable
    if(!ai_CanIBullrush(oCreature, oTarget)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1333", "ai_TryBullrushFeat: ai_CanIBullrush Check PASSED!");
    //:: Decide whether to charge or not
    float fDistance = GetDistanceBetween(oCreature, oTarget);
    if (fDistance > 3.0 && GetHasFeat(3413 /* FEAT_BULLRUSH_CHARGE */, oCreature))
    {
        if(AI_DEBUG) ai_Debug("0i_talents", "1335", "ai_TryBullrushFeat: Using Bullrush Charge.");
              ai_UseFeat(oCreature, 3413 /* FEAT_BULLRUSH_CHARGE */, oTarget);
    }
    else
    {
        if(AI_DEBUG) ai_Debug("0i_talents", "1340", "ai_TryBullrushFeat: Using Bullrush.");
              ai_UseFeat(oCreature, 2810 /* FEAT_BULLRUSH */, oTarget); //::
    }
    return TRUE;
}
//:: [PRC8] Tests & runs Ranged Disarm
int ai_TryRangedDisarmFeat(object oCreature, object oTarget)
{
    //:: Sanity checks
    if(!GetIsObjectValid(oCreature) || !GetIsObjectValid(oTarget)) return FALSE;
    if(!GetHasFeat(5192 /*FEAT_RANGED_DISARM*/, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1339", "ai_TryRangedDisarmFeat: Sanity Check PASSED!");
    //:: Must have a ranged weapon equipped
       object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
       if(!GetIsObjectValid(oWeapon) || !GetWeaponRanged(oWeapon)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1345", "ai_TryRangedDisarmFeat: Equipped w/ Bow Check PASSED!");
    //:: Target must be disarmable
       if(!GetLocalInt(GetModule(), "PRC_PNP_DISARM") && !GetIsCreatureDisarmable(oTarget)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1350", "ai_TryRangedDisarmFeat: Target Disarmable Check PASSED!");
       //:: Target must be holding a weapon
       object oTargetWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget);
       if(!GetIsObjectValid(oTargetWeapon)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1350", "ai_TryRangedDisarmFeat: Target Armed Check PASSED!");
    //:: Check range to target - max 30 feet for this feat
       if(GetDistanceBetween(oCreature, oTarget) > 30.0) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1350", "ai_TryRangedDisarmFeat: Target Distance Check PASSED!");
    int nOWeaponType = GetBaseItemType(oWeapon);
       int nOWeaponSize = StringToInt(Get2DAString("baseitems", "WeaponSize", nOWeaponType));
    int nEWeaponType = GetBaseItemType(oTargetWeapon);
       int nEWeaponSize = StringToInt(Get2DAString("baseitems", "WeaponSize", nEWeaponType));
    int nSizeBonus = (nOWeaponSize - nEWeaponSize) * 4;
    //:: Attack penalty check
       if (!ai_AttackPenaltyOk(oCreature, oTarget, IntToFloat(nSizeBonus))) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1350", "ai_TryRangedDisarmFeat: Attack Check PASSED!");
    //:: Execute the feat use
       ai_UseFeat(oCreature, 5192 /*FEAT_RANGED_DISARM*/, oTarget);
    return TRUE;
}
//:: [PRC8] Tests & runs Swarm of Arrows
int ai_TrySwarmofArrowsFeat(object oCreature)
{
    //:: Sanity checks
    if(!GetIsObjectValid(oCreature)) return FALSE;
       if(!GetHasFeat(25995 /*FEAT_EPIC_SWARM_OF_ARROWS*/, oCreature)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1369", "ai_TrySwarmofArrowsFeat: Sanity Check PASSED!");
    //:: Must have a longbow or shortbow equipped
       object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
       int nBaseItem = GetBaseItemType(oWeapon);
       if(!GetIsObjectValid(oWeapon)) return FALSE;
       if(nBaseItem != BASE_ITEM_LONGBOW && nBaseItem != BASE_ITEM_SHORTBOW) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1377", "ai_TrySwarmofArrowsFeat: Weapon Check PASSED!");
    //:: Are we in melee combat?
       //int nInMelee = ai_GetNumOfEnemiesInRange(oCreature);
       //if( !ai_CanIUseRangedWeapon(oCreature, nInMelee)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1382", "ai_TrySwarmofArrowsFeat: Safe Distance DISABLED!");
    //:: Are there 3+ enemies to attack within 30'?
       int nNumOfEnemies = ai_GetNumOfEnemiesInGroup(oCreature, FeetToMeters(30.0));
       if(nNumOfEnemies < 3) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1387", "ai_TrySwarmofArrowsFeat: "+IntToString(nNumOfEnemies)+" enemies in range, PASSED!");
    //:: Execute the feat use
       ai_UseFeat(oCreature, 25995 /*FEAT_EPIC_SWARM_OF_ARROWS*/, oCreature);
    return TRUE;
}
//:: [PRC8] Tests & runs Inlindl School Strike
int ai_TryInlindlSchoolFeat(object oCreature, object oTarget)
{
    if (!GetHasFeat(5284/* FEAT_INLINDL_SCHOOL */, oCreature)) return FALSE;
    object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
    int nWeaponType     = GetBaseItemType(oWeapon);
       int nWeaponSize  = StringToInt(Get2DAString("baseitems", "WeaponSize", nWeaponType));
       int nCreatureSize    = GetCreatureSize(oCreature);
    if(!GetIsObjectValid(oWeapon)) return FALSE;
    if(nWeaponType != BASE_ITEM_RAPIER && nWeaponType != 203 /* BASE_ITEM_ELVEN_THINBLADE */ &&
       nWeaponType != 204 /* BASE_ITEM_ELVEN_COURTBLADE */ &&
      (nWeaponSize > 3 || nWeaponSize >= nCreatureSize)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1404", "ai_TryInlindlSchoolFeat: Weapon Check PASSED!");
    //:: Are we in melee combat?
       if(ai_GetNumOfEnemiesInRange(oCreature) < 1) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1408", "ai_TryInlindlSchoolFeat: Melee Combat DETECTED!");
    int nShieldAC = ai_GetTotalShieldACBonus(oCreature);
       if(nShieldAC < 2) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1412", "Inlindl School: Shield AC Check PASSED!");
    float fACPenalty = -1 * IntToFloat(nShieldAC);
    if(AI_DEBUG) ai_Debug("0i_talents", "1416", "ai_TryInlindlSchoolFeat >> Att Bonus: " + IntToString(nShieldAC/2) + " AC Malus: "+FloatToString(fACPenalty)+".");
    if(ai_AttackBonusGood(oCreature, oTarget, IntToFloat(nShieldAC/2))) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1420", "ai_TryInlindlSchoolFeat: Att Bonus PASSED!");
    if(ai_ACAdjustmentGood(oCreature, oTarget, fACPenalty)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1424", "ai_TryInlindlSchoolFeat: AC Malus PASSED!");
       //:: Execute the feat use
       ai_UseFeat(oCreature, 5284 /*FEAT_INLINDL_SCHOOL*/, oTarget);
       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_GetHighestGroupIndexNotInAOE(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_GetNearestPhysicalIndex(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_TryRangedTalents(object oCreature, object oTarget, int nInMelee)
{
    if(AI_DEBUG) ai_Debug("0i_talents", "1593", "Check category melee talents!");
    if(ai_TryRapidShotFeat(oCreature, oTarget, nInMelee)) return TRUE;
    return FALSE;
}
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; }
    else if(GetLocalInt(GetModule(), AI_USING_PRC))
    {
        if(nId == 2822) { if(ai_TryTripping(oCreature, oTarget)) return TRUE; }         //:: FEAT_CM_TRIP (2822)
        else if(nId == 2823) { if(ai_TryCharging(oCreature, oTarget)) return TRUE; }            //:: FEAT_CM_CHARGE (2823)
        else if(nId == 5370) { if(ai_TryAwesomeBlowFeat(oCreature, oTarget)) return TRUE; }     //:: FEAT_AWESOME_BLOW (5370)
        else if(nId == 2810) { if(ai_TryBullrushFeat(oCreature, oTarget)) return TRUE; }        //:: FEAT_BULLRUSH (2810)
        else if(nId == 3414) { if(ai_TryGrappling(oCreature, oTarget)) return TRUE; }           //:: FEAT_CM_GRAPPLE (3414)
        else if(nId == 2811) { if(ai_TryOverrunFeat(oCreature, oTarget)) return TRUE; }         //:: FEAT_OVERRUN (2811)
        else if(nId == 5284) { if(ai_TryInlindlSchoolFeat(oCreature, oTarget)) return TRUE; }   //:: FEAT_INLINDL_SCHOOL (5284)
    }
    return FALSE;
}
//:: [PRC8] Tests & runs Trip
int ai_TryTripping(object oCreature, object oTarget)
{
    //:: FEAT_TRIP (2822)
    //:: FEAT_IMPROVED_TRIP (2807)
    //:: Sanity checks
    if(!GetIsObjectValid(oCreature) || !GetIsObjectValid(oTarget)) return FALSE;
    if(!GetHasFeat(2822, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1862", "ai_TryTripping: Sanity Checks PASSED!");
       /*   int nCoolDown = GetLocalInt(oCreature, "AI_TRIP_COOLDOWN");
            if(nCoolDown > 0)
        {
            SetLocalInt(oCreature, "AI_TRIP_COOLDOWN", --nCoolDown);
            return FALSE;
        } */
       int nMySize = GetCreatureSize(oCreature);
       int nTheirSize = GetCreatureSize(oTarget);
    //:: Prevent silly use of knockdown on immune or too-large targets.
    if(GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN) || nTheirSize > nMySize + 1) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1879", "ai_TryTripping: Immunity Checks PASSED!");
    if(!ai_CanITrip(oCreature, oTarget)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1883", "ai_TryTripping: ai_CanITrip Check PASSED!");
       ai_UseFeat(oCreature, 2822 /* FEAT_TRIP */, oTarget);
       //SetLocalInt(oCreature, "AI_TRIP_COOLDOWN", AI_TRIP_COOLDOWN);
    return TRUE;
}
//:: [PRC8] Tests & runs Charge
int ai_TryCharging(object oCreature, object oTarget)
{
    //:: FEAT_CM_CHARGE (2823)
    //:: FEAT_BULLRUSH_CHARGE (3413)
       //:: FEAT_MINOTAUR_CHARGE (4668)
       //:: FEAT_ACROBATIC_CHARGE (2808)
       //:: FEAT_SHIELD_CHARGE (3256)
       //:: FEAT_POWERFUL_CHARGE (3506)
       //:: FEAT_GREATER_POWERFUL_CHARGE (3507)
       //:: FEAT_RHINO_TRIBE_CHARGE (3508)
       //:: FEAT_FURIOUS_CHARGE (3509)
       //:: FEAT_RECKLESS_CHARGE (3510)
       //:: FEAT_COBALT_CHARGE (8878)
    if (!GetHasFeat(2823, oCreature)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1901", "ai_TryCharging: Sanity Check PASSED!");
       int nChargeScore = GetHasFeat(2823, oCreature) +  // FEAT_CM_CHARGE
                GetHasFeat(3413, oCreature) +  // FEAT_BULLRUSH_CHARGE
                GetHasFeat(4668, oCreature) +  // Minotaur
                GetHasFeat(2808, oCreature) +  // Acrobatic
                GetHasFeat(3256, oCreature) +  // Shield
                GetHasFeat(3506, oCreature) +  // Powerful
                GetHasFeat(3507, oCreature) +  // Greater Powerful
                GetHasFeat(3508, oCreature) +  // Rhino Tribe
                GetHasFeat(3509, oCreature) +  // Furious
                GetHasFeat(3510, oCreature) +  // Reckless
                GetHasFeat(8878, oCreature);   // Cobalt
    if(AI_DEBUG) ai_Debug("0i_talents", "1915", "ai_TryCharging: Charge Score is: "+IntToString(nChargeScore)+".");
    //:: Charge inclination threshold check
    if(d20() > 7 + nChargeScore) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1921", "ai_TryCharging: Threshold Check PASSED!");
    //:: Movement check
    float f2DASpeed         = StringToFloat(Get2DAString("racialtypes", "Endurance", GetRacialType(oCreature)));
       if(AI_DEBUG) ai_Debug("0i_talents", "1925", "ai_TryCharging >> 2DA speed is: "+FloatToString(f2DASpeed)+".");
       float fCreatureSpeed     =  IntToFloat(10 * GetMovementRate(oCreature));
       if(AI_DEBUG) ai_Debug("0i_talents", "1929", "ai_TryCharging >> Creature speed is: "+FloatToString(fCreatureSpeed)+".");
       float fChargeSpeed = (f2DASpeed > fCreatureSpeed) ? f2DASpeed : fCreatureSpeed;
       if(AI_DEBUG) ai_Debug("0i_talents", "1929", "ai_TryCharging >> Charge speed is: "+FloatToString(fChargeSpeed)+".");
       //:: If target is too far away to reach in one round, return
    if (GetDistanceBetween(oCreature, oTarget) > 2.0 * fChargeSpeed) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1935", "ai_TryCharging: Distance to Target (far) Check PASSED!");
    //:: If target is too close, return
    if (GetDistanceBetween(oCreature, oTarget) < FeetToMeters(11.0)) return FALSE;
    if(AI_DEBUG) ai_Debug("0i_talents", "1941", "ai_TryCharging: Distance to Target (near) Check PASSED!");
    //:: Check AB and AC gain/loss
    if (!ai_AttackPenaltyOk(oCreature, oTarget, 2.0)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1945", "ai_TryCharging: Attack Mod Check PASSED!");
    if (!ai_ACAdjustmentGood(oCreature, oTarget, -2.0)) return FALSE;
       if(AI_DEBUG) ai_Debug("0i_talents", "1948", "ai_TryCharging: AC Penalty Check PASSED!");
    //:: Use Charge
    ai_UseFeat(oCreature, 2823 /* FEAT_CM_CHARGE */, oTarget);
    return TRUE;
}
//:: [PRC8] Tests & runs Grapple
int ai_TryGrappling(object oCreature, object oTarget)
{
    //:: FEAT_CM_GRAPPLE (3414)
       //:: FEAT_IMPROVED_GRAPPLE (2804)
       //:: FEAT_DEVASTATING_GRAPPLE (4246)
    if(!GetHasFeat(3414, oCreature)) return FALSE;
    //:: Can't grapple incorporeal or ethereal creatures
    if(GetLocalInt(oTarget, "X2_L_IS_INCORPOREAL") || ai_GetHasEffectType(oTarget, EFFECT_TYPE_ETHEREAL)) return FALSE;
    //:: Get sizes
    int nSizeCreature = GetCreatureSize(oCreature);
    int nSizeTarget   = GetCreatureSize(oTarget);
    //:: Must not be grappling up 2+ size categories
    if (nSizeTarget - nSizeCreature >= 2) return FALSE;
    //:: Should we attempt a grapple?
    if (!ai_CanIGrapple(oCreature, oTarget)) return FALSE;
    //:: Use the grapple feat
    ai_UseFeat(oCreature, 3414, oTarget);
    return TRUE;
}
// *****************************************************************************
// *****************************  TALENT SCRIPTS  ******************************
// *****************************************************************************
// These functions do not fall into another section.

int ai_GetMonsterTalentMaxLevel(object oCreature)
{
    return 9;
    //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_PRESUMMON))) return ai_GetBuffTarget(oCreature, nSpell);
    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, int bPreBuff, int bFullBuff, 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 && bPreBuff)
    {
        int nSpellBuffDuration = StringToInt(Get2DAString("ai_spells", "Buff_Duration", nSpell));
        if(nSpellBuffDuration == 3 || (nSpellBuffDuration == 2 && bFullBuff))
        {
            object oTarget = ai_CheckTalentForBuffing(oCreature, sCategory, nSpell);
            if(oTarget != OBJECT_INVALID)
            {
                if(AI_DEBUG) ai_Debug("0i_talents", "1794", GetName(oCreature) + " is prebuffing with spell " + IntToString(nSpell));
                if(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", "1835", sCategory + ": " + JsonDump(jCategory, 1));
    // 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);
    }
    if(AI_DEBUG) ai_Debug("0i_talents", "1841", "AI_MAX_TALENT: " +
             IntToString(GetLocalInt(oCreature, AI_MAX_TALENT + sCategory)) +
             " nJsonLevel: " + IntToString(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", "1848", "removing Talent from slot: " + IntToString(nSlotIndex));
    jLevel = JsonArrayDel(jLevel, nSlotIndex);
    if(AI_DEBUG) ai_Debug("0i_talents", "1850", "jLevel: " + JsonDump(jLevel, 2));
    jCategory = JsonArraySet(jCategory, nLevel, jLevel);
    if(AI_DEBUG) ai_Debug("0i_talents", "1852", "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", "1858", "removing Talent level: " + IntToString(nLevel));
    jCategory = JsonArrayDel(jCategory, nLevel);
    jCategory = JsonArraySet(jCategory, nLevel, JsonArray());
    if(AI_DEBUG) ai_Debug("0i_talents", "1861", "jCategory: " + JsonDump(jCategory, 2));
    SetLocalJson(oCreature, sCategory, jCategory);
}
/*******************************************************************************
                    New Set Talents by Level then category.
*******************************************************************************/
/*void ai_SaveTalentByLevel(object oCreature, int nClass, int nJsonLevel, int nLevel, int nSlot, int nSpell, int nType, int bMonster, int bPreBuff, int bFullBuff, 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 && bPreBuff)
    {
        int nSpellBuffDuration = StringToInt(Get2DAString("ai_spells", "Buff_Duration", nSpell));
        if(nSpellBuffDuration == 3 || (nSpellBuffDuration == 2 && bFullBuff))
        {
            object oTarget = ai_CheckTalentForBuffing(oCreature, sCategory, nSpell);
            if(oTarget != OBJECT_INVALID)
            {
                if(AI_DEBUG) ai_Debug("0i_talents", "1794", GetName(oCreature) + " is prebuffing with spell " + IntToString(nSpell));
                if(ai_UseBuffTalent(oCreature, nClass, nLevel, nSlot, nSpell, nType, oTarget, oItem)) return;
            }
        }
    }
    string sLevel = IntToString(nLevel);
    // Get the Level saved to Json.
    json jLevel = GetLocalJson(oCreature, "SPELL_LEVEL_" + sLevel);
    // If jLevel is not created we set it up.
    if(JsonGetType(jLevel) == JSON_TYPE_NULL) jLevel = JsonArray();
    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));
        jTalent = JsonArrayInsert(jTalent, JsonString(sCategory));
    }
    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));
        jTalent = JsonArrayInsert(jTalent, JsonString(sCategory));
    }
    jLevel = JsonArrayInsert(jLevel, jTalent);
    SetLocalJson(oCreature, "SPELL_LEVEL_" + sLevel, jLevel);
    if(AI_DEBUG) ai_Debug("0i_talents", "2265", sLevel + ": " + JsonDump(jLevel, 1));
    // Set AI_MAX_TALENT if this talent is higher than the maximum.
    if(nJsonLevel > GetLocalInt(oCreature, AI_MAX_LEVEL + sLevel))
    {
        SetLocalInt(oCreature, AI_MAX_LEVEL + sLevel, nJsonLevel);
    }
    if(AI_DEBUG) ai_Debug("0i_talents", "2271", "AI_MAX_LEVEL: " +
             IntToString(GetLocalInt(oCreature, AI_MAX_LEVEL + sLevel)) +
             " nJsonLevel: " + IntToString(nJsonLevel));
}
// For removing used up spell slots.
void ai_RemoveTalentByLevel(object oCreature, json jLevel, int nLevel, int nSlotIndex)
{
    if(AI_DEBUG) ai_Debug("0i_talents", "2278", "removing Talent from slot: " + IntToString(nSlotIndex));
    jLevel = JsonArrayDel(jLevel, nSlotIndex);
    if(AI_DEBUG) ai_Debug("0i_talents", "2280", "jLevel: " + JsonDump(jLevel, 2));
    SetLocalJson(oCreature, "SPELL_LEVEL_" + IntToString(nLevel), jLevel);
}
// For removing Sorcerer/Bard spell levels once used up.
void ai_RemoveTalentLevelByLevel(object oCreature, int nLevel)
{
    DeleteLocalJson(oCreature, "SPELL_LEVEL_" + IntToString(nLevel));
}
void ai_SetCreatureSpellTalentsByLevel(object oCreature, int bMonster, int bPreBuff, int bFullBuff)
{
    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_SaveTalentByLevel(oCreature, nClass, nLevel, nLevel, nSlot, nSpell, AI_TALENT_TYPE_SPELL, bMonster, bPreBuff, bFullBuff);
                        }
                        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_SaveTalentByLevel(oCreature, nClass, nLevel, nLevel, nSlot, nSpell, AI_TALENT_TYPE_SPELL, bMonster, bPreBuff, bFullBuff);
                        }
                        nSlot++;
                    }
                    nLevel--;
                }
            }
        }
        nClassPosition++;
        nClass = GetClassByPosition(nClassPosition, oCreature);
    }
}
void ai_SetCreatureSpecialAbilityTalentsByLevel(object oCreature, int bMonster, int bPreBuff, int bFullBuff)
{
    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(nMaxSpecialAbilities) + " 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_SaveTalentByLevel(oCreature, 255, nLevel, nLevel, nIndex, nSpell, AI_TALENT_TYPE_SP_ABILITY, bMonster, bPreBuff, bFullBuff);
            }
            nIndex++;
        }
    }
}
void ai_CheckItemPropertiesByLevel(object oCreature, object oItem, int bMonster, int bPreBuff, int bFullBuff, 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_SaveTalentByLevel(oCreature, 255, nLevel, nLevel, nIndex, nSpell, AI_TALENT_TYPE_ITEM, bMonster, bPreBuff, bFullBuff, 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, bPreBuff, bFullBuff, 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 any Immunity has been set then we need to save our Immunity json.
    if(bHasItemImmunity) SetLocalJson(oCreature, AI_TALENT_IMMUNITY, jImmunity);
}
void ai_SetCreatureItemTalentsByLevel(object oCreature, int bMonster, int bPreBuff, int bFullBuff)
{
    if(AI_DEBUG) ai_Debug("0i_talents", "1561", GetName(oCreature) + ": Setting Item Talents for combat.");
    int bEquiped;
    // Set the Immunities to -1 so we know they were set incase there are no immunities.
    SetLocalInt(oCreature, sIPReducedVarname, -1);
    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_CheckItemPropertiesByLevel(oCreature, oItem, bMonster, bPreBuff, bFullBuff);
        }
        oItem = GetNextItemInInventory(oCreature);
    }
    int nSlot;
    // Cycle through all the creatures equiped items.
    oItem = GetItemInSlot(nSlot, oCreature);
    while(nSlot < 11)
    {
        if(oItem != OBJECT_INVALID) ai_CheckItemPropertiesByLevel(oCreature, oItem, bMonster, bPreBuff, bFullBuff, TRUE);
        oItem = GetItemInSlot(++nSlot, oCreature);
    }
    oItem = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oCreature);
    if(oItem != OBJECT_INVALID) ai_CheckItemPropertiesByLevel(oCreature, oItem, bMonster, bPreBuff, bFullBuff, TRUE);
}
void ai_SetCreatureTalentsByLevel(object oCreature, int bMonster, int bForceTalentSetup = FALSE)
{
    //json jCreature = ObjectToJson(oCreature);
    //if(AI_DEBUG) ai_Debug("0i_talents", "2072", GetName(oCreature) + " jCreature: " + JsonDump(jCreature, 4));
    if(GetLocalInt(oCreature, AI_TALENTS_SET) && !bForceTalentSetup) return;
    SetLocalInt(oCreature, AI_TALENTS_SET, TRUE);
    object oModule = GetModule();
    int bPreBuff = GetLocalInt(GetModule(), AI_RULE_BUFF_MONSTERS) || (GetLocalInt(oCreature, "NW_GENERIC_MASTER") & 0x04000000);
    int bFullBuff = GetLocalInt(GetModule(), AI_RULE_FULL_BUFF_MONSTERS);
    if(bForceTalentSetup) bPreBuff = FALSE;
    ai_Counter_Start();
    ai_SetCreatureSpellTalentsByLevel(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Spell Talents");
    ai_SetCreatureSpecialAbilityTalentsByLevel(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Special Ability Talents");
    DeleteLocalJson(oCreature, AI_TALENT_IMMUNITY);
    ai_SetCreatureItemTalentsByLevel(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Item Talents");
    if(GetLocalInt(oModule, AI_RULE_SUMMON_COMPANIONS) &&
       GetLocalInt(oModule, AI_RULE_PRESUMMON) && bMonster && bPreBuff)
    {
        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);
} */
/*******************************************************************************
                        Old Set talents by category then level.
*******************************************************************************/
void ai_SetCreatureSpellTalents(object oCreature, int bMonster, int bPreBuff, int bFullBuff)
{
    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, bPreBuff, bFullBuff);
                        }
                        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, bPreBuff, bFullBuff);
                        }
                        nSlot++;
                    }
                    nLevel--;
                }
            }
        }
        nClassPosition++;
        nClass = GetClassByPosition(nClassPosition, oCreature);
    }
}
void ai_SetCreatureSpecialAbilityTalents(object oCreature, int bMonster, int bPreBuff, int bFullBuff)
{
    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(nMaxSpecialAbilities) + " 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, bPreBuff, bFullBuff);
            }
            nIndex++;
        }
    }
}
void ai_CheckItemProperties(object oCreature, object oItem, int bMonster, int bPreBuff, int bFullBuff, 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, bPreBuff, bFullBuff, 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, bPreBuff, bFullBuff, 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 any Immunity 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, int bPreBuff, int bFullBuff)
{
    if(AI_DEBUG) ai_Debug("0i_talents", "1561", GetName(oCreature) + ": Setting Item Talents for combat.");
    int bEquiped;
    // Set the Immunities to -1 so we know they were set incase there are no immunities.
    SetLocalInt(oCreature, sIPReducedVarname, -1);
    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, bPreBuff, bFullBuff);
        }
        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, bPreBuff, bFullBuff, TRUE);
        oItem = GetItemInSlot(++nSlot, oCreature);
    }
    oItem = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oCreature);
    if(oItem != OBJECT_INVALID) ai_CheckItemProperties(oCreature, oItem, bMonster, bPreBuff, bFullBuff, TRUE);
}
void ai_SetCreatureTalents(object oCreature, int bMonster, int bForceTalentSetup = FALSE)
{
    //json jCreature = ObjectToJson(oCreature);
    //if(AI_DEBUG) ai_Debug("0i_talents", "2072", GetName(oCreature) + " jCreature: " + JsonDump(jCreature, 4));
    if(GetLocalInt(oCreature, AI_TALENTS_SET) && !bForceTalentSetup) return;
    SetLocalInt(oCreature, AI_TALENTS_SET, TRUE);
    object oModule = GetModule();
    int bPreBuff = GetLocalInt(GetModule(), AI_RULE_BUFF_MONSTERS) || (GetLocalInt(oCreature, "NW_GENERIC_MASTER") & 0x04000000);
    int bFullBuff = GetLocalInt(GetModule(), AI_RULE_FULL_BUFF_MONSTERS);
    if(bForceTalentSetup) bPreBuff = FALSE;
    ai_Counter_Start();
    ai_SetCreatureSpellTalents(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Spell Talents");
    ai_SetCreatureSpecialAbilityTalents(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Special Ability Talents");
    DeleteLocalJson(oCreature, AI_TALENT_IMMUNITY);
    ai_SetCreatureItemTalents(oCreature, bMonster, bPreBuff, bFullBuff);
    ai_Counter_End(GetName(oCreature) + ": Item Talents");
    if(GetLocalInt(oModule, AI_RULE_SUMMON_COMPANIONS) &&
       GetLocalInt(oModule, AI_RULE_PRESUMMON) && bMonster && bPreBuff)
    {
        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;
    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;
}
void ai_UpdateMaxTalentLevel(object oCreature, json jCategory, string sCategory, int nMaxTalentLevel, int nLevel)
{
    // We only reduce the MaxTalentLevel if this is the top most talent level.
    if(nMaxTalentLevel == nLevel)
    {
        json jLevel = JsonArrayGet(jCategory, --nLevel);
        if(AI_DEBUG) ai_Debug("0i_talents", "2331", "jLevel length: " + IntToString(JsonGetLength(jLevel)));
        while(JsonGetLength(jLevel) == 0 && nLevel > -1)
        {
            jLevel = JsonArrayGet(jCategory, --nLevel);
        }
        if(AI_DEBUG) ai_Debug("0i_talents", "2335", "Setting AI_MAX_TALENT_" + sCategory + " to " + IntToString(nLevel));
        SetLocalInt(oCreature, AI_MAX_TALENT + sCategory, nLevel);
    }
}
/*******************************************************************************
            New talent system that checks by level then category.
*******************************************************************************/
/*int ai_UseCreatureTalentByLevel(object oCreature, int nInMelee, int nLevel = 10, object oTarget = OBJECT_INVALID)
{
    string sLevel = IntToString(nLevel);
    // Get the Max Talent Level to see if we even need to pull this talent.
    int nMaxTalentLevel = GetLocalInt(oCreature, AI_MAX_LEVEL + sLevel);
    if(AI_DEBUG) ai_Debug("0i_talents", "2394", AI_MAX_LEVEL + sLevel + ": " +
             IntToString(nMaxTalentLevel) +
             "  nLevel: " + IntToString(nLevel));
    if(nMaxTalentLevel == -1) return FALSE;
    // If there are no talents at higher levels then start at the lowest talent level.
    if(nMaxTalentLevel < nLevel) nLevel = nMaxTalentLevel;
    if(nLevel < 0 || nLevel > 10) nLevel = 9;
    json jTalent;
    int bHasTalent, 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", "2413", "bUseMagic: " + IntToString(bUseMagic) +
                          " bUseMagicItems: " + IntToString(bUseMagicItems));
    // Loop through nLevels starting at the highest then going to the lowest.
    // (i.e. the highest or best is our assumption).
    // Get the saved level from oCreature.
    json jLevel = GetLocalJson(oCreature, "SPELL_LEVEL_" + IntToString(nLevel));
    if(AI_DEBUG) ai_Debug("0i_talents", "3091", "jLevel: " + IntToString(nLevel) + " " + JsonDump(jLevel, 2));
    if(JsonGetType(jCategory) == JSON_TYPE_NULL)
    {
        SetLocalInt(oCreature, AI_MAX_LEVEL + sCategory, -1);
        return FALSE;
    }
    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", "2422", "Level: " + IntToString(nLevel) +
                 " nMaxSlotIndex: " + IntToString(nMaxSlotIndex));
        if(nMaxSlotIndex > 0)
        {
            bHasTalent = TRUE;
            // 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", "2432", "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);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                            return TRUE;
                        }
                        // There are no more spell slots left for non-memorizing caster so remove the level.
                        else if(nTalentUsed == -2)
                        {
                            ai_RemoveTalentLevel(oCreature, jCategory, jLevel, sCategory, nLevel);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, 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);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                            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", "2473", "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);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                        }
                        return TRUE;
                    }
                }
                //else if(nType == AI_TALENT_TYPE_FEAT) {}
                nSlotIndex++;
            }
        }
        else if(!bHasTalent) SetLocalInt(oCreature, AI_MAX_LEVEL + sCategory, nLevel - 1);
        nLevel--;
    }
    return FALSE;
}
int ai_UseTalentByLevel(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;
} */
/*******************************************************************************
               Old talent system that checks by category then level.
*******************************************************************************/
int ai_UseCreatureTalent(object oCreature, string sCategory, int nInMelee, int nLevel = 10, object oTarget = OBJECT_INVALID)
{
    // Get the Max Talent Level to see if we even need to pull this talent.
    int nMaxTalentLevel = GetLocalInt(oCreature, AI_MAX_TALENT + sCategory);
    if(AI_DEBUG) ai_Debug("0i_talents", "2394", AI_MAX_TALENT + sCategory + ": " +
             IntToString(nMaxTalentLevel) +
             "  nLevel: " + IntToString(nLevel));
    if(nMaxTalentLevel == -1) return FALSE;
    // Get the saved category from oCreature.
    json jCategory = GetLocalJson(oCreature, sCategory);
    if(AI_DEBUG) ai_Debug("0i_talents", "2326", "jCategory: " + sCategory + " " + JsonDump(jCategory, 2));
    if(JsonGetType(jCategory) == JSON_TYPE_NULL)
    {
        SetLocalInt(oCreature, AI_MAX_TALENT + sCategory, -1);
        return FALSE;
    }
    // If there are no talents at higher levels then start at the lowest talent level.
    if(nMaxTalentLevel < nLevel) nLevel = nMaxTalentLevel;
    if(nLevel < 0 || nLevel > 10) nLevel = 9;
    json jLevel, jTalent;
    int bHasTalent, 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", "2413", "bUseMagic: " + IntToString(bUseMagic) +
                          " bUseMagicItems: " + IntToString(bUseMagicItems));
    // Loop through nLevels starting at the highest then going to the lowest.
    // (i.e. the highest or best is our assumption).
    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", "2422", "nLevel: " + IntToString(nLevel) +
                 " nMaxSlotIndex: " + IntToString(nMaxSlotIndex));
        if(nMaxSlotIndex > 0)
        {
            bHasTalent = TRUE;
            // 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", "2432", "nSlotIndex: " + IntToString(nSlotIndex) +
                         " jTalent Type: " + IntToString(JsonGetInt(JsonArrayGet(jTalent, 0))));
                nType = JsonGetInt(JsonArrayGet(jTalent, 0));
                if(bUseMagic)
                {
                    if(nType == AI_TALENT_TYPE_SPELL)
                    {
                        // Check to make sure they still have this spell.
                        nClass = JsonGetInt(JsonArrayGet(jTalent, 2));
                        if(AI_DEBUG) ai_Debug("0i_talents", "3290", GetName(oCreature) + " Memorizes Spells? " +
                           Get2DAString("classes", "MemorizesSpells", nClass));
                        if(Get2DAString("classes", "MemorizesSpells", nClass) == "1")
                        {
                            if(AI_DEBUG) ai_Debug("0i_talents", "3294", " Spell Memorized? " +
                               IntToString(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlotIndex)));
                            if(GetMemorizedSpellReady(oCreature, nClass, nLevel, nSlotIndex) < 1)
                            {
                                ai_RemoveTalent(oCreature, jCategory, jLevel, sCategory, nLevel, nSlotIndex);
                                if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                            }
                            else if(ai_UseCreatureSpellTalent(oCreature, jLevel, jTalent, sCategory, nInMelee, oTarget)) return TRUE;
                        }
                        else if(!GetSpellUsesLeft(oCreature, nClass, JsonGetInt(JsonArrayGet(jTalent, 1))))
                        {
                            ai_RemoveTalentLevel(oCreature, jCategory, jLevel, sCategory, nLevel);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                            nTalentUsed = FALSE;
                        }
                        else if(ai_UseCreatureSpellTalent(oCreature, jLevel, jTalent, sCategory, nInMelee, oTarget)) 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);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                            return TRUE;
                        }
                    }
                }
                else 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", "2473", "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);
                            if(nMaxSlotIndex == 1) ai_UpdateMaxTalentLevel(oCreature, jCategory, sCategory, nMaxTalentLevel, nLevel);
                        }
                        return TRUE;
                    }
                }
                //else if(nType == AI_TALENT_TYPE_FEAT) {}
                nSlotIndex++;
            }
        }
        else if(!bHasTalent) 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;
}
// Lets do a check right before we cast the spell to see if the target is still good.
void ai_CheckSpellTarget(object oCreature, object oTarget)
{
    if(GetIsDead(oTarget))
    {
        //SendMessageToPC(GetFirstPC(), "0i_talents, 2864, " + GetName(oCreature) +
        //" is stopping our spell casting because " + GetName(oTarget) + " is dead!");
        //WriteTimestampedLogEntry("0i_talents, 2864, " + GetName(oCreature) +
        //" is stopping our spell casting because " + GetName(oTarget) + " is dead!");
        ai_ClearCreatureActions();
        ExecuteScript("0e_do_combat_rnd", oCreature);
    }
}
int ai_UseTalentOnObject(object oCreature, json jTalent, object oTarget, int nInMelee, int bCheckPosition = TRUE)
{
    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(bCheckPosition && 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(bCheckPosition && 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);
    // This was added to do a second check right before they cast to make sure
    // the target was not killed between us deciding to cast and then casting!
    DelayCommand(2.5, ai_CheckSpellTarget(oCreature, oTarget));
    ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
    // Temp Debug!!!!!
    //string sSpellName = GetStringByStrRef(StringToInt(Get2DAString("spells", "Name", nSpell)));
    //SendMessageToPC(GetFirstPC(), "0i_talents, 1859 " + GetName(oCreature) + " is casting " + sSpellName + " on " + GetName(oTarget));
    // Temp Debug!!!!!
    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));
    // This checks to see if they tried to cast this spell last round and if we
    // are trying to cast it again before at least 3 seconds is up then the spell
    // is being canceled... remove this spell and look for another!
    if(ai_CompareLastAction(oCreature, nSpell) &&
       ai_GetCurrentTimeStamp() - GetLocalInt(oCreature, sLastActionTimeVarname) < 2)
    {
        if(AI_DEBUG)
        {
            int nLastTime = GetLocalInt(oCreature, sLastActionTimeVarname);
            int nCurrentTime = ai_GetCurrentTimeStamp();
            ai_Debug("0i_talents", "3634", "Same spell cast within less than 2 seconds!" +
               " nSpell: " + IntToString(nSpell) + " nLastActionTime: " + IntToString(nLastTime) +
               " nCurrentTime: " + IntToString(nCurrentTime) + " Difference: " + IntToString(nCurrentTime - nLastTime));
        }
        return FALSE;
    }
    if(AI_DEBUG) ai_Debug("0i_talents", "3637", "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.
            int nIndex = ai_GetHighestGroupIndexNotInAOE(oCreature);
            if(nIndex == 0)
            {
                nIndex = ai_GetHighestGroupIndex(oCreature);
                if(nIndex == 0) return FALSE;
            }
            oTarget = GetLocalObject(oCreature, AI_ENEMY + IntToString(nIndex));
            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);
        }
        else if(nSpell == SPELLABILITY_PULSE_DROWN)
        {
            if(nInMelee > 1)
            {
                oTarget = GetLocalObject(oCreature, AI_ENEMY_NEAREST);
                int nRace = GetRacialType(oTarget);
                if(AI_DEBUG) ai_Debug("0i_talents", "2778", "nRace(10/24/16): " + IntToString(nRace));
                if(nRace != RACIAL_TYPE_CONSTRUCT &&
                   nRace != RACIAL_TYPE_UNDEAD &&
                   nRace != RACIAL_TYPE_ELEMENTAL)
                {
                    if(AI_DEBUG) ai_Debug("0i_talents", "2783", "Using Pulse Drown talent!");
                    if(ai_UseTalentOnObject(oCreature, jTalent, oCreature, nInMelee, FALSE)) return TRUE;
                }
            }
            return FALSE;
        }
        else if(nSpell == SPELLABILITY_PULSE_WHIRLWIND)
        {
            if(nInMelee > 1)
            {
                if(AI_DEBUG) ai_Debug("0i_talents", "2793", "Using Pulse Whirlwind talent!");
                if(ai_UseTalentOnObject(oCreature, jTalent, oCreature, nInMelee, FALSE)) return TRUE;
            }
            return FALSE;
        }
        // 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_GetGroupTargetNotInAOE(oCreature, fRange);
            if(oTarget == OBJECT_INVALID)
            {
                oTarget = ai_GetGroupTarget(oCreature, fRange);
                if(oTarget == OBJECT_INVALID) return FALSE;
            }
        }
        if(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_GetGroupTargetNotInAOE(oCreature, fRange);
            if(oTarget == OBJECT_INVALID)
            {
                oTarget = ai_GetGroupTarget(oCreature, fRange);
                if(oTarget == OBJECT_INVALID) return FALSE;
            }
            // Check for the number of allies, if there are too many then skip.
            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) return FALSE;
            }
            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;
            }
        }
        // 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) return FALSE;
        }
        if(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) return FALSE;
        }
        if(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) return FALSE;
        }
        if(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;
}
