MMD_PRC8/_module/nss/0i_talents.nss

3099 lines
160 KiB
Plaintext
Raw Normal View History

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