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