TheHordeOrcs_PRC8/_module/nss/0i_combat.nss

3499 lines
188 KiB
Plaintext
Raw Normal View History

/*//////////////////////////////////////////////////////////////////////////////
// Script Name: 0i_combat
////////////////////////////////////////////////////////////////////////////////
Include scripts for combat scripts.
*///////////////////////////////////////////////////////////////////////////////
// Programmer: Philos
////////////////////////////////////////////////////////////////////////////////
#include "0i_messages"
#include "0i_items"
#include "0i_spells"
// This structure is used to represent the number and type of
// enemies that a creature is facing, divided into four main
// categories: FIGHTERS, CLERICS, MAGES, MONSTERS.
struct stClasses
{
int FIGHTERS;
int FIGHTER_LEVELS;
int CLERICS;
int CLERIC_LEVELS;
int MAGES;
int MAGE_LEVELS;
int MONSTERS;
int MONSTER_LEVELS;
int TOTAL;
int TOTAL_LEVELS;
};
struct stTarget
{
object oTarget;
int nValue;
int nBestValue;
int nBestSecondaryValue;
float fNearestRange;
float fNearestSecondaryRange;
int nIndex;
int nSecondaryIndex;
string sTargetType;
};
//******************************************************************************
//************ GET TARGETS USING THE OBJECT SEARCH FUNCTIONS *******************
//******************************************************************************
// Returns the nearest enemy that is not disabled from oCreature.
// You may pass in any of the CREATURE_TYPE_* constants
// used in GetNearestCreature as nCType1 & nCType2, with
// corresponding values for nCValue1 & nCValue2.
// NOTE: CREATURE_TYPE_PERCEPTION = 7, PERCEPTION_SEEN = 7.
// bDisabled = TRUE will also return any disabled targets that are not dead.
object ai_GetNearestEnemy(object oCreature, int nNth = 1, int nCType1 = -1, int nCValue1 = -1, int nCType2 = -1, int nCValue2 = -1, int bDisabled = FALSE);
// Returns the nearest ally from oCreature.
// You may pass in any of the CREATURE_TYPE_* constants
// used in GetNearestCreature as nCType1 & nCType2, with
// corresponding values for nCValue1 & nCValue2.
// NOTE: CREATURE_TYPE_PERCEPTION = 7, PERCEPTION_SEEN = 7.
object ai_GetNearestAlly(object oCreature, int nNth = 1, int nCType1 = -1, int nCValue1 = -1, int nCType2 = -1, int nCValue2 = -1);
// Returns the number of alive enemies grouped near oCreature within fDistance.
int ai_GetNumOfEnemiesInGroup(object oCreature, float fDistance = AI_RANGE_MELEE);
// Returns the number of alive allies grouped near oCreature within fDistance.
int ai_GetNumOfAlliesInGroup(object oCreature, float fDistance = AI_RANGE_MELEE);
// Returns the number of creatures of nRacial_Type within fDistance that can be seen by oCreature.
int ai_GetRacialTypeCount(object oCreature, int nRacial_Type, float fDistance = AI_RANGE_PERCEPTION);
// Returns the weakest attacker that is in melee or is attacking oCreature's master.
object ai_GetLowestCRAttackerOnMaster(object oCreature);
//******************************************************************************
//******************** SET/CLEAR COMBAT STATE FUNCTIONS ************************
//******************************************************************************
// Sets oCreatures's combat state by setting variables for AI_ALLIES and AI_ENEMIES.
// Returns the nearest visible enemy.
object ai_SetCombatState(object oCreature);
// Clears all variables that were define for the current round for oCreature.
void ai_ClearCombatState(object oCreature);
//******************************************************************************
//*************** GET TARGETS USING COMBAT STATE FUNCTIONS *********************
//******************************************************************************
// These functions will find a target or an index to a target based on the
// combat state variables created by the function ai_SetCombatState.
// Returns the Index of the nearest creature seen within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetNearestIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest creature seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetNearestTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest creature seen with the lowest combat rating
// within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetLowestCRIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest creature seen with the lowest combat rating within fMaxRange
// in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetLowestCRTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest creature seen with the highest combat rating
// within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetHighestCRIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest creature seen with the highest combat rating within fMaxRange
// in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetHighestCRTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the creature seen with the lowest enemies to oCreature that
// they are in melee with minus the number of allies to the caller they are in
// melee with within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetLowestMeleeIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY);
// Returns the index of the creature seen with the most enemies to the caller that
// they are in melee with minus the number of allies to oCreature they are in
// melee with within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetHighestMeleeIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY);
// Returns a creature of sTargetType where they have the least number of
// allies and the most number of enemies within fMaxRange in the combat state.
// Returns OBJECT_INVALID if there is not a good creature to select.
// sTargetType is either AI_ENEMY, or AI_ALLY.
object ai_GetGroupedTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY);
// Returns the index of the nearest creature with the least % of hitpoints within
// fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetMostWoundedIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the creature with the lowest health seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetMostWoundedTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest ally with the least % of hitpoints within
// fMaxRange in the combat state.
// This also filters for AI_MODE_PARTY_HEALING_OFF and AI_MODE_SELF_HEALING_OFF.
// If no ally is found then it will return an index of 0.
int ai_GetAllyToHealIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the ally with the lowest health seen within fMaxRange in the combat state.
// This also filters for AI_MODE_PARTY_HEALING_OFF and AI_MODE_SELF_HEALING_OFF.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetAllyToHealTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the creature with the lowest fortitude save seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetLowestFortitudeSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the creature with the lowest reflex save seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetLowestReflexSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the creature with the lowest will save seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetLowestWillSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the creature with the lowest save based on nSpell save type seen
// within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
object ai_GetSpellTargetBasedOnSaves(object oCreature, int nSpell, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the index of the nearest creature seen that is busy attacking an ally
// within fMaxRange in the combat state.
// If none is found then it will return 0.
int ai_GetSneakAttackIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE);
// Returns the index of the nearest creature seen that is busy attacking an ally
// within fMaxRange in the combat state.
// If none is found then it will return 0.
int ai_GetNearestIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest combat creature seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetNearestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest creature seen with the lowest combat rating
// that is not in a dangerous area of effect within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetLowestCRIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the lowest combat creature seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetLowestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest creature seen with the highest combat rating
// that is not in a dangerous area of effect within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetHighestCRIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the highest combat creature seen within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
object ai_GetHighestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the creature seen with the most enemies to oCreature that
// they are in melee with minus the number of allies to oCreature they are in
// melee with that is not in a dangerous area of effect within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_GetHighestMeleeIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY);
// Returns a creature of sTargetType where they have the least number of
// allies and the most number of enemies within fMaxRange that are not in a
// dangerous area of effect in the combat state.
// Returns OBJECT_INVALID if there is not a good creature to select.
// sTargetType is either AI_ENEMY, or AI_ALLY.
object ai_GetGroupedTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY);
// Returns the nearest creature seen of nClassType within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetNearestClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the creature with the lowest combat rating seen of nClassType within
// fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetLowestCRClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the creature with the highest combat rating seen of nClassType within
// fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetHighestCRClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest creature seen of nRacialType within fMaxRange in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetNearestRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the creature with the lowest combat rating seen of nRacialType within
// fMaxRange in the combat state. Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetLowestCRRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the creature with the highest combat rating seen of nRacialType within
// fMaxRange in the combat state. Returns OBJECT_INVALID if no creature is found.
// sTargetType is either AI_ENEMY or AI_ALLY.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetHighestCRRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the nearest enemy seen that is attacking an ally with the least
// number of enemies on them within fMaxRange in the combat state.
// If none is found then it will return 0.
object ai_GetFlankTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE);
// Returns the nearest enemy creature seen wihtin fMaxRange that is a favored enemy
// of the caller in the combat state.
// Returns OBJECT_INVALID if no creature is found.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetNearestFavoredEnemyTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE);
// Returns the best target for melee combat based if we are in melee or not.
// If not in melee it will get the nearest target that is not in a dangerous
// area of effect for us to attack in the combat state.
// If in melee it will get the weakest target.
// If it returns OBJECT_INVALID then we should stop the attack. The only way
// to not get a target is if we have been told not to attack strong opponents.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetBestCRTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
// Returns the nearest target for melee combat based if we are in melee or not.
// If not in melee it will get the nearest target that is not in a dangerous
// area of effect for us to attack in the combat state.
// If it returns OBJECT_INVALID then we should stop the attack. The only way
// to not get a target is if we have been told not to attack strong opponents.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetNearestTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
// Returns the target with the lowest combat rating for melee combat based if
// we are in melee or not. If not in melee it will get the nearest target that
// is not in a dangerous area of effect for us to attack in the combat state.
// If it returns OBJECT_INVALID then we should stop the attack. The only way
// to not get a target is if we have been told not to attack strong opponents.
// bAlwaysAtk TRUE we attack everything! FALSE we don't attack strong enemies.
object ai_GetLowestCRTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE);
// Returns the target with the highest combat rating for melee combat based if
// we are in melee or not. If not in melee it will get the nearest target that
// is not in a dangerous area of effect for us to attack in the combat state.
// If it returns OBJECT_INVALID then we should stop the attack.
object ai_GetHighestCRTargetForMeleeCombat(object oCreature, int nInMelee);
// Returns the Index of the nearest creature seen within fMaxRange in the combat state.
// If no creature is found then it will return an index of 0.
// sTargetType is either AI_ENEMY or AI_ALLY.
int ai_MonsterGetNearestIndex(object oMonster, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE);
// Returns the index of the nearest enemy creature that can see oCreature.
int ai_GetNearestIndexThatSeesUs(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION);
// Returns the nearest creature attacking the caller within fMaxRange in the combat state.
// Returns OBJECT_INVALID if oCreature is not being attacked.
object ai_GetEnemyAttackingMe(object oCreature, float fMaxRange = AI_RANGE_MELEE);
// Returns the nearest creature attacking oAlly from oCreature within fMaxRange
// in the combat state.
// Returns OBJECT_INVALID if oAlly is not being attacked.
object ai_GetEnemyAttackingMyAlly(object oCreature, object oAlly, float fMaxRange = AI_RANGE_MELEE);
// Returns the number of enemies within fMaxRange of the caller in the combat state.
int ai_GetNumOfEnemiesInRange(object oCreature, float fMaxRange = AI_RANGE_MELEE);
// Returns the best ally target withing fMaxRange for nSpell to be cast on.
// Uses the ai_spells.2da file to pick a target.
object ai_GetAllyBuffTarget(object oCreature, int nSpell, float fMaxRange = AI_RANGE_BATTLEFIELD);
//******************************************************************************
//******************** OTHER COMBAT FUNCTIONS ********************************
//******************************************************************************
// Returns the current round that oCreature is in for this combat.
int ai_GetCurrentRound(object oCreature);
// Returns the difficulty of the battle based on the combat state.
// nDifficulty is Enemy level - Ally level + 20 + Player adjustment.
// 20+ : Impossible - Cannot win.
// 17 to 19 : Overpowering - Use all of our powers.
// 15 to 16 : Very Difficult - Use all of our power (Highest level spells).
// 11 to 14 : Challenging - Use most of our power (Higher level powers).
// 8 to 10 : Moderate - Use half of our power (Mid level powers and less).
// 5 to 7 : Easy - Use our weaker powers (Lowest level powers).
// 2 to 4 : Effortless - Don't waste spells and powers on this.
// 1 or less: Pointless - We probably should ignore these dangers.
int ai_GetDifficulty(object oCreature);
// Returns oCreatures Combat rating.
//(BAB + AC - 10) / 2
int ai_GetMyCombatRating(object oCreature);
// Returns the last creature oCreature attacked.
// bPhysical checks for creatures attacked in melee or range with a weapon.
// bSpell will look for creatures attacked by a spell.
object ai_GetAttackedTarget(object oCreature, int bPhysical = TRUE, int bSpell = FALSE);
// Returns TRUE if oCreature is of nClassType;
// May also check for general Class types with
// AI_CLASS_TYPE_ARCANE, AI_CLASS_TYPE_DIVINE, AI_CLASS_TYPE_CASTER, AI_CLASS_TYPE_WARRIOR.
int ai_CheckClassType(object oCreature, int nClassType);
// Returns TRUE if oCreature is of nRacialType;
// May also check for general racial types with
// AI_RACIAL_TYPE_ANIMAL_BEAST
int ai_CheckRacialType(object oCreature, int nRacialType);
// Saves oCreatures Normal appearance if they are not polymorphed and it has
// not already been saved.
void ai_SetNormalAppearance(object oCreature);
// Returns the normal appearance of oCreature.
int ai_GetNormalAppearance(object oCreature);
// Return the number and levels of all creatures within fMaxRange.
// They are grouped into Fighters, Clerics, Mages, and Monsters.
struct stClasses ai_GetFactionsClasses(object oCreature, int bEnemy = TRUE, float fMaxRange = AI_RANGE_BATTLEFIELD);
// This will return the class with the most levels.
// Returns a string of "FIGHTER", "CLERIC", "MAGE", or "MONSTER".
// Execute with GetFactionsClasses.
string ai_GetMostDangerousClass(struct stClasses stCount);
// Equips the best weapon, ranged or melee.
// Returns TRUE if equiped, FALSE if not.
// oTarget is the creature the caller is targeting.
void ai_EquipBestWeapons(object oCreature, object oTarget = OBJECT_INVALID);
// Equips a melee weapon AND checks for shield, two weapons, two handed, etc.
// Returns TRUE if equiped, FALSE if not.
// oTarget is the creature the caller is targeting.
int ai_EquipBestMeleeWeapon(object oCreature, object oTarget = OBJECT_INVALID);
// Equips a ranged weapon AND checks for ammo.
// Returns TRUE if equiped, FALSE if not.
// oTarget is the creature the caller is targeting.
int ai_EquipBestRangedWeapon(object oCreature, object oTarget = OBJECT_INVALID);
// Equips the best weapon for a monk character.
// Returns TRUE if equiped, FALSE if not.
// oTarget is the creature the caller is targeting.
int ai_EquipBestMonkMeleeWeapon(object oCreature, object oTarget = OBJECT_INVALID);
// Returns TRUE if oCreature is in a Dangerous Area of Effect in fMaxRange.
// bMove will attempt to move oCreature out of the Dangerous AOE if needed.
int ai_IsInADangerousAOE(object oCreature, float fMaxRange = AI_RANGE_BATTLEFIELD, int bMove = FALSE);
// Returns 1 if oHidden has an Invisiblity effect, Can't be spotted but can be heard.
// Returns 2 if oHidden has a Darkness effect. Can't be spotted but can be heard.
// Returns 3 if oHidden has a Sanctuary effect, Can't be spotted or heard.
// Returns 4 if oHidden is in stealth mode, Can be spotted and heard.
int ai_GetIsHidden(object oHidden);
// Returns TRUE if if oCaster has a good chance of effecting oCreature with nSpell.
int ai_CastOffensiveSpellVsTarget(object oCaster, object oCreature, int nSpell);
// Gets the base DC for a dragon.
int ai_GetDragonDC(object oCreature);
// Set oCreature's ai scripts based on its first class or the variable "AI_DEFAULT_SCRIPT".
void ai_SetCreatureAIScript(object oCreature);
// Returns TRUE if oTarget is immune to sneak attacks.
int ai_IsImmuneToSneakAttacks(object oCreature, object oTarget);
// Returns TRUE if iIndex target has a higher combat rating than oCreature.
int ai_IsStrongerThanMe(object oCreature, int nIndex);
// Returns TRUE if oTarget's CR is within nAdj of oCreature's level, otherwise FALSE.
int ai_StrongOpponent(object oCreature, object oTarget, int nAdj = 2);
// Returns TRUE if attacking oTarget with Power attack is a good option.
int ai_PowerAttackGood(object oCreature, object oTarget, float fAdj);
// Returns TRUE if oTarget's AC - oCreature Atk - nAtkAdj can hit within 25% to 75%.
int ai_AttackPenaltyOk(object oCreature, object oTarget, float fAtkAdj);
// Returns TRUE if oCreature AC - oTarget's Atk is less than 20.
int ai_ACAdjustmentGood(object oCreature, object oTarget, float fACAdj);
// Checks oCreatures melee weapon to see if they can kill oTarget in one hit.
int ai_WillKillInOneHit(object oCreature, object oTarget);
// Returns TRUE if oCreature has Mobility, SpringAttack, or a high Tumble.
int ai_CanIMoveInCombat(object oCreature);
// Returns TRUE if oCreature can safely fire a ranged weapon.
int ai_CanIUseRangedWeapon(object oCreature, int nInMelee);
// Returns TRUE if oCreature moves before the action. FALSE if they do not move.
// and -1 if the action is canceled.
// Checks current combat state to see if oCreature needs to move before using an action.
int ai_CheckCombatPosition(object oCreature, object oTarget, int nInMelee, int nAction, int nBaseItemType = 0);
//******************************************************************************
//************ GET TARGETS USING THE OBJECT SEARCH FUNCTIONS *******************
//******************************************************************************
object ai_GetNearestEnemy(object oCreature, int nNth = 1, int nCType1 = -1, int nCValue1 = -1, int nCType2 = -1, int nCValue2 = -1, int bDisabled = FALSE)
{
object oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oCreature, nNth, nCType1, nCValue1, nCType2, nCValue2);
if(bDisabled)
{
while(oTarget != OBJECT_INVALID && GetIsDead(oTarget))
{
oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oCreature, ++nNth, nCType1, nCValue1, nCType2, nCValue2);
}
}
else
{
while(oTarget != OBJECT_INVALID && ai_Disabled(oTarget))
{
oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
oCreature, ++nNth, nCType1, nCValue1, nCType2, nCValue2);
}
}
return oTarget;
}
object ai_GetNearestAlly(object oCreature, int nNth = 1, int nCType1 = -1, int nCValue1 = -1, int nCType2 = -1, int nCValue2 = -1)
{
return GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND,
oCreature, ++nNth, nCType1, nCValue1, nCType2, nCValue2);
}
int ai_GetNumOfEnemiesInGroup(object oCreature, float fDistance = AI_RANGE_MELEE)
{
int nCnt;
location lLocation = GetLocation(oCreature);
object oEnemy = GetFirstObjectInShape(SHAPE_SPHERE, fDistance, lLocation);
while(oEnemy != OBJECT_INVALID)
{
if(GetIsEnemy(oEnemy, oCreature) && !GetIsDead(oEnemy)) nCnt++;
oEnemy = GetNextObjectInShape(SHAPE_SPHERE, fDistance, lLocation);
}
return nCnt;
}
int ai_GetNumOfAlliesInGroup(object oCreature, float fDistance = AI_RANGE_MELEE)
{
int nCnt;
location lLocation = GetLocation(oCreature);
object oAlly = GetFirstObjectInShape(SHAPE_SPHERE, fDistance, lLocation);
while(oAlly != OBJECT_INVALID)
{
if(GetReputation(oCreature, oAlly) > 89 && oAlly != oCreature && !GetIsDead(oAlly))
{
nCnt++;
}
oAlly = GetNextObjectInShape(SHAPE_SPHERE, fDistance, lLocation);
}
return nCnt;
}
int ai_GetRacialTypeCount(object oCreature, int nRacial_Type, float fDistance = AI_RANGE_PERCEPTION)
{
int nCnt = 1;
int nCount = 0;
object oEnemy = ai_GetNearestEnemy(oCreature, nCnt,
CREATURE_TYPE_PERCEPTION,
PERCEPTION_SEEN,
CREATURE_TYPE_RACIAL_TYPE,
nRacial_Type);
while(oEnemy != OBJECT_INVALID && GetDistanceBetween(oEnemy, oCreature) <= fDistance)
{
if(!ai_GetHasEffectType(oEnemy, EFFECT_TYPE_TURNED)) nCount++;
nCnt++;
oEnemy = ai_GetNearestEnemy(oCreature, nCnt,
CREATURE_TYPE_PERCEPTION,
PERCEPTION_SEEN,
CREATURE_TYPE_RACIAL_TYPE,
nRacial_Type);
}
return nCount;
}
object ai_GetLowestCRAttackerOnMaster(object oCreature)
{
object oTarget = OBJECT_INVALID, oMaster = GetMaster(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "419", "Checking for weakest attacker on " + GetName(oMaster));
int nEnemyCombatRating, nWeakestCombatRating, nCntr = 1;
float fNearest = AI_RANGE_MELEE + 1.0f;
// Get the weakest opponent in melee with our master.
object oEnemy = ai_GetNearestEnemy(oMaster, nCntr, 7, 7);
float fDistance = GetDistanceBetween(oMaster, oEnemy);
while (oEnemy != OBJECT_INVALID && fDistance <= AI_RANGE_MELEE)
{
nEnemyCombatRating = ai_GetMyCombatRating(oEnemy);
if(AI_DEBUG) ai_Debug("0i_combat", "428", GetName(oEnemy) + " nECR: " + IntToString(nEnemyCombatRating));
if (nEnemyCombatRating < nWeakestCombatRating ||
nEnemyCombatRating == nWeakestCombatRating && fDistance < fNearest)
{
fNearest = fDistance;
nWeakestCombatRating = nEnemyCombatRating;
oTarget = oEnemy;
}
oEnemy = ai_GetNearestEnemy(oMaster, ++nCntr, 7, 7);
}
// No targets in melee with our master, lets see if there is a ranged attacker.
if (oTarget == OBJECT_INVALID) oTarget = GetLastHostileActor(oMaster);
return oTarget;
}
//******************************************************************************
//******************** SET/CLEAR COMBAT STATE FUNCTIONS ************************
//******************************************************************************
object ai_SetCombatState(object oCreature)
{
if(AI_DEBUG) ai_Counter_Start();
object oMaster = GetMaster();
if(oMaster == OBJECT_INVALID) oMaster = oCreature;
int nEnemyNum, nEnemyPower, nAllyNum, nAllyPower, nInMelee, nMagic;
int nHealth, nNth, nAllies, nPower, nDisabled, bThreat,nObjects;
int nEnemyHighestPower, nAllyHighestPower;
float fNearest = AI_RANGE_BATTLEFIELD;
float fDistance;
float fMaxRange = GetLocalFloat(oCreature, AI_ASSOC_PERCEPTION_DISTANCE);
if(fMaxRange == 0.0) fMaxRange = AI_RANGE_PERCEPTION;
string sCnt, sDebugText;
location lLocation = GetLocation(oMaster);
object oMelee, oNearestEnemy = OBJECT_INVALID;
if(AI_DEBUG) ai_Debug("0i_combat", "491", "************************************************************");
if(AI_DEBUG) ai_Debug("0i_combat", "492", "******************* CREATING COMBAT DATA *******************");
if(AI_DEBUG) ai_Debug("0i_combat", "493", GetName(oCreature));
// We want to include ourselves in the combat state.
object oObject = GetFirstObjectInShape(SHAPE_SPHERE, AI_RANGE_BATTLEFIELD, lLocation);
// Get all creatures within 40 meters(5 meters beyond our perception of 35).
// Centered on either the creature or their master.
while(oObject != OBJECT_INVALID)
{
// Process all enemies.
if(GetIsEnemy(oObject, oCreature))
{
if(GetObjectSeen(oObject, oCreature) || GetObjectHeard(oObject, oCreature))
{
fDistance = GetDistanceBetween(oObject, oCreature);
if(fDistance <= fMaxRange)
{
// ********** Get the Total levels of the Enemy **********
nPower = ai_GetCharacterLevels(oObject);
if(nPower < 1) nPower = 1;
if(nEnemyHighestPower < nPower) nEnemyHighestPower = nPower;
nEnemyPower += nPower;
// ********** Check if the Enemy is disabled **********
bThreat = TRUE;
nDisabled = ai_Disabled(oObject);
if(nDisabled)
{
if(AI_DEBUG) sDebugText += "**** DISABLED(" + IntToString(nDisabled) + ") ****";
// Decide if they are still a threat: 1 - dead, 2 - Bleeding.
if(nDisabled == 1 || nDisabled == 2 ||
//nDisabled == EFFECT_TYPE_CONFUSED ||
//nDisabled == EFFECT_TYPE_FRIGHTENED ||
//nDisabled == EFFECT_TYPE_PARALYZE ||
nDisabled == EFFECT_TYPE_CHARMED ||
nDisabled == EFFECT_TYPE_PETRIFY)
{
bThreat = FALSE;
if(AI_DEBUG) ai_Debug("0i_combat", "527", "Enemy: " + GetName(oObject) + sDebugText);
}
}
// If they are using the coward ai then treat them as frightened.
// we place it here as an else so we don't overwrite another disabled effect.
else if(GetLocalString(oObject, AI_COMBAT_SCRIPT) == "ai_coward")
{
nDisabled = EFFECT_TYPE_FRIGHTENED;
// !!!! For /DEBUG CODE !!!!
if(AI_DEBUG) sDebugText += "**** DISABLED(" + IntToString(nDisabled) + ") ****";
}
if(bThreat)
{
sCnt = IntToString(++nEnemyNum);
// ********** Set if the Enemy is disabled **********
SetLocalInt(oCreature, AI_ENEMY_DISABLED + sCnt, nDisabled);
// ********** Set the Enemy Object **********
SetLocalObject(oCreature, AI_ENEMY + sCnt, oObject);
// ********** Set the Enemy Combat Rating **********
SetLocalInt(oCreature, AI_ENEMY_COMBAT + sCnt, ai_GetMyCombatRating(oObject));
// ********** Set the Enemy Health Percentage **********
nHealth = ai_GetPercHPLoss(oObject);
SetLocalInt(oCreature, AI_ENEMY_HEALTH + sCnt, nHealth);
// ********** Set the number of enemies near the enemy **********
nInMelee = 0;
nNth = 1;
oMelee = GetNearestObject(OBJECT_TYPE_CREATURE, oObject, nNth);
while(oMelee != OBJECT_INVALID && !GetIsDead(oMelee) &&
GetDistanceBetween(oMelee, oObject) < AI_RANGE_MELEE)
{
// We add an enemy to the group.
if(GetIsEnemy(oMelee, oCreature)) nInMelee++;
oMelee = GetNearestObject(OBJECT_TYPE_CREATURE, oObject, ++nNth);
}
SetLocalInt(oCreature, AI_ENEMY_MELEE + sCnt, nInMelee);
// ********** Set the Enemies distance **********
fDistance = GetDistanceBetween(oObject, oCreature);
SetLocalFloat(oCreature, AI_ENEMY_RANGE + sCnt, fDistance);
// ********** Set if the Enemy is perceived **********
if(GetObjectSeen(oObject, oCreature) ||
(GetObjectHeard(oObject, oCreature) && fDistance <= AI_RANGE_MELEE &&
ai_GetIsHidden(oObject)))
{
SetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCnt, TRUE);
if(AI_DEBUG) sDebugText += "**** PERCEIVED Seen: " +
IntToString(GetObjectSeen(oObject, oCreature)) +
" Heard: " + IntToString(GetObjectHeard(oObject, oCreature)) + " ****";
}
else SetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCnt, FALSE);
// ********** Set the Nearest Enemy seen **********
if(fDistance < fNearest)
{
fNearest = fDistance;
oNearestEnemy = oObject;
}
}
}
// !!! Debug code !!!
if(AI_DEBUG && fDistance < AI_RANGE_MELEE) sDebugText += "**** MELEE ****";
if(AI_DEBUG) ai_Debug("0i_combat", "587", "Enemy(" + IntToString(nEnemyNum) + "): " +
GetName(oObject) + sDebugText);
if(AI_DEBUG) ai_Debug("0i_combat", "589", "nHealth: " + IntToString(nHealth) +
" nInMelee: " + IntToString(nInMelee) +
" fDistance: " + FloatToString(fDistance, 0, 2) +
" nNum: " + IntToString(nEnemyNum) +
" nPower: " + IntToString(nEnemyPower / 2));
}
else
{
// ********** Also add the levels of Unknown Enemies ***********
nPower = FloatToInt(ai_GetCharacterLevels(oObject) / 1.5);
if(nPower < 1) nPower = 1;
nEnemyPower += nPower;
if(AI_DEBUG) ai_Debug("0i_combat", "601", "Enemy(NOT PERCEIVED): " +
GetName(oObject) + " fDistance: " +
FloatToString(GetDistanceBetween(oObject, oCreature), 0, 2) +
" nPower: " + IntToString(nEnemyPower));
}
}
// Process all Allies.
else if(GetFactionEqual(oObject, oCreature))
{
// ********** Set if the Ally is disabled **********
nDisabled = ai_Disabled(oObject);
if(nDisabled)
{
sDebugText += "**** DISABLED(" + IntToString(nDisabled) + ") ****";
SetLocalInt(oCreature, AI_ALLY_DISABLED + sCnt, nDisabled);
}
if(nDisabled != 1)
{
sCnt = IntToString(++nAllyNum);
// ********** Set the Ally Object **********
SetLocalObject(oCreature, AI_ALLY + sCnt, oObject);
// ********** Set the Ally Combat Rating **********
SetLocalInt(oCreature, AI_ALLY_COMBAT + sCnt, ai_GetMyCombatRating(oObject));
// ********** Set the Ally Health Percentage **********
nHealth = ai_GetPercHPLoss(oObject);
SetLocalInt(oCreature, AI_ALLY_HEALTH + sCnt, nHealth);
// ********** Set the number of enemies near the ally **********
nInMelee = 0;
nNth = 1;
oMelee = GetNearestObject(OBJECT_TYPE_CREATURE, oObject, nNth);
while(oMelee != OBJECT_INVALID && !GetIsDead(oMelee) &&
GetDistanceBetween(oMelee, oObject) < AI_RANGE_MELEE)
{
if(GetIsEnemy(oMelee, oCreature)) nInMelee++;
//else nInMelee--;
oMelee = GetNearestObject(OBJECT_TYPE_CREATURE, oObject, ++nNth);
}
SetLocalInt(oCreature, AI_ALLY_MELEE + sCnt, nInMelee);
// ********** Set the Allies distance **********
SetLocalFloat(oCreature, AI_ALLY_RANGE + sCnt, GetDistanceBetween(oObject, oCreature));
// ********** All allies are considered to be seen **********
SetLocalInt(oCreature, AI_ALLY_PERCEIVED + sCnt, TRUE);
// ********** Get the Total levels of the Allies **********
nPower = ai_GetCharacterLevels(oObject);
if(nAllyHighestPower < nPower) nAllyHighestPower = nPower;
nAllyPower +=(nPower * nHealth) / 100;
if(AI_DEBUG) ai_Debug("0i_combat", "647", "Ally(" + IntToString(nAllyNum) + "): " +
GetName(oObject) + sDebugText);
if(AI_DEBUG) ai_Debug("0i_combat", "649", "nHealth: " + IntToString(nHealth) +
" nInMelee: " + IntToString(nInMelee) +
" fDistance: " + FloatToString(GetDistanceToObject(oObject), 0, 2) +
" nNum: " + IntToString(nAllyNum) +
" nPower: " + IntToString(nAllyPower / 2));
}
}
if(AI_DEBUG) sDebugText = "";
oObject = GetNextObjectInShape(SHAPE_SPHERE, AI_RANGE_BATTLEFIELD, lLocation);
}
if(AI_DEBUG) ai_Debug("0i_combat", "659", "Nearest Enemy: " + GetName(oNearestEnemy));
if(AI_DEBUG) ai_Debug("0i_combat", "660", "****************** FINISHED COMBAT DATA *******************");
if(AI_DEBUG) ai_Debug("0i_combat", "661", "************************************************************");
// Lets save processing by only clearing previous enemy data we don't overwrite.
int nPreviousEnd = GetLocalInt(oCreature, AI_ENEMY_NUMBERS);
int nCnt = nEnemyNum + 1;
if(AI_DEBUG) ai_Debug("0i_combat", "665", "Clearing Enemy Combat Data: nPreviousEnd: " +
IntToString(nPreviousEnd) + " nCurrentEnd: " + IntToString(nCnt - 1));
while(nPreviousEnd >= nCnt)
{
sCnt = IntToString(nCnt);
if(AI_DEBUG) ai_Debug("0i_combat", "670", "Clearing Enemy Combat Data: " + sCnt + " " +
GetName(GetLocalObject(oCreature, AI_ENEMY + sCnt)));
DeleteLocalObject(oCreature, AI_ENEMY + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCnt);
DeleteLocalFloat(oCreature, AI_ENEMY_RANGE + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_COMBAT + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_MELEE + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_HEALTH + sCnt);
nCnt ++;
}
// Lets save processing by only clearing previous ally data we don't overwrite.
nPreviousEnd = GetLocalInt(oCreature, AI_ALLY_NUMBERS);
nCnt = nAllyNum + 1;
if(AI_DEBUG) ai_Debug("0i_combat", "683", "Clearing Ally Combat Data: nPreviousEnd: " +
IntToString(nPreviousEnd) + " nCurrentEnd: " + IntToString(nCnt - 1));
while(nPreviousEnd >= nCnt)
{
sCnt = IntToString(nCnt);
if(AI_DEBUG) ai_Debug("0i_combat", "688", "Clearing Ally Combat Data: " + sCnt + " " +
GetName(GetLocalObject(oCreature, AI_ENEMY + sCnt)));
DeleteLocalObject(oCreature, AI_ALLY + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_PERCEIVED + sCnt);
DeleteLocalFloat(oCreature, AI_ALLY_RANGE + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_COMBAT + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_MELEE + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_HEALTH + sCnt);
nCnt ++;
}
// Finally set all group states.
SetLocalInt(oCreature, AI_ENEMY_NUMBERS, nEnemyNum);
// Total enemy power is half the levels of all enemies + the total levels
// of the highest level enemy.
nEnemyPower = (nEnemyPower / 2) + nEnemyHighestPower;
SetLocalInt(oCreature, AI_ENEMY_POWER, nEnemyPower);
SetLocalObject(oCreature, AI_ENEMY_NEAREST, oNearestEnemy);
SetLocalInt(oCreature, AI_ALLY_NUMBERS, nAllyNum);
// Total ally power is half the levels of all allies + the total levels
// of the highest level ally, only used by associates.
nAllyPower = (nAllyPower / 2) + nAllyHighestPower;
SetLocalInt(oCreature, AI_ALLY_POWER, nAllyPower);
if(AI_DEBUG) ai_Debug("0i_combat", "710", "nEnemyPower: " + IntToString(nEnemyPower) +
" nEnemyHighestPower: " + IntToString(nEnemyHighestPower) +
" nAllyPower: " + IntToString(nAllyPower) +
" nAllyHighestPower: " + IntToString(nAllyHighestPower));
if(AI_DEBUG) ai_Counter_End(GetName(oCreature) + " has finished the Combat State");
return oNearestEnemy;
}
void ai_ClearCombatState(object oCreature)
{
int bEnemyDone, bAllyDone, nCnt = 1;
int nEnemyNum = GetLocalInt(oCreature, AI_ENEMY_NUMBERS);
int nAllyNum = GetLocalInt(oCreature, AI_ALLY_NUMBERS);
if(AI_DEBUG) ai_Debug("0i_combat", "722", "Clearing " + GetName(oCreature) + "'s combat state." +
" nEnemyNum: " + IntToString(nEnemyNum) + " nAllyNum: " + IntToString(nAllyNum));
string sCnt;
while(!bEnemyDone || !bAllyDone)
{
sCnt = IntToString(nCnt);
if(nCnt <= nEnemyNum)
{
if(AI_DEBUG) ai_Debug("0i_combat", "730", "Clearing " + GetName(GetLocalObject(oCreature, AI_ENEMY + sCnt)) + ".");
DeleteLocalObject(oCreature, AI_ENEMY + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_DISABLED + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCnt);
DeleteLocalFloat(oCreature, AI_ENEMY_RANGE + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_COMBAT + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_MELEE + sCnt);
DeleteLocalInt(oCreature, AI_ENEMY_HEALTH + sCnt);
}
else bEnemyDone = TRUE;
if(nCnt <= nAllyNum)
{
if(AI_DEBUG) ai_Debug("0i_combat", "742", "Clearing " + GetName(GetLocalObject(oCreature, AI_ALLY + sCnt)) + ".");
DeleteLocalObject(oCreature, AI_ALLY + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_DISABLED + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_PERCEIVED + sCnt);
DeleteLocalFloat(oCreature, AI_ALLY_RANGE + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_COMBAT + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_MELEE + sCnt);
DeleteLocalInt(oCreature, AI_ALLY_HEALTH + sCnt);
}
else bAllyDone = TRUE;
nCnt++;
}
DeleteLocalObject(oCreature, AI_ENEMY_NEAREST);
DeleteLocalInt(oCreature, AI_ENEMY_NUMBERS);
DeleteLocalInt(oCreature, AI_ENEMY_POWER);
DeleteLocalInt(oCreature, AI_ALLY_NUMBERS);
DeleteLocalObject(oCreature, AI_ALLY_POWER);
// Also clear these combat variables at the end of combat.
DeleteLocalObject(oCreature, AI_ATTACKED_PHYSICAL);
DeleteLocalObject(oCreature, AI_ATTACKED_SPELL);
// Remove Talent variables.
DeleteLocalJson(oCreature, AI_TALENT_CURE);
DeleteLocalJson(oCreature, AI_TALENT_HEALING);
DeleteLocalJson(oCreature, AI_TALENT_ENHANCEMENT);
DeleteLocalJson(oCreature, AI_TALENT_PROTECTION);
DeleteLocalJson(oCreature, AI_TALENT_SUMMON);
DeleteLocalJson(oCreature, AI_TALENT_DISCRIMINANT_AOE);
DeleteLocalJson(oCreature, AI_TALENT_INDISCRIMINANT_AOE);
DeleteLocalJson(oCreature, AI_TALENT_RANGED);
DeleteLocalJson(oCreature, AI_TALENT_TOUCH);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_CURE);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_HEALING);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_ENHANCEMENT);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_PROTECTION);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_SUMMON);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_DISCRIMINANT_AOE);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_INDISCRIMINANT_AOE);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_RANGED);
DeleteLocalInt(oCreature, AI_MAX_TALENT + AI_TALENT_TOUCH);
DeleteLocalInt(oCreature, AI_AM_I_SEARCHING);
DeleteLocalInt(oCreature, AI_TRIED_TO_HIDE);
DeleteLocalObject(oCreature, AI_IS_INVISIBLE);
DeleteLocalInt(oCreature, sLastActionVarname);
DeleteLocalInt(oCreature, AI_TALENTS_SET);
DeleteLocalInt(oCreature, AI_ROUND);
DeleteLocalInt(oCreature, sIPHasHasteVarname);
DeleteLocalInt(oCreature, sIPImmuneVarname);
DeleteLocalInt(oCreature, sIPResistVarname);
DeleteLocalInt(oCreature, sIPReducedVarname);
ai_EndCombatRound(oCreature);
}
//******************************************************************************
//*********************** GET TARGETS INTERNAL FUNCTIONS ***********************
//******************************************************************************
// These functions are used by the Get Index/ Get Target functions below.
int ai_TargetIsInRangeofCreature(object oCreature, string sTargetType, string sCounter, float fMaxRange)
{
if(AI_DEBUG) ai_Debug("0i_combat", "796", "fMaxRange: " + FloatToString(fMaxRange, 0, 2) +
" fTargetRange: " + FloatToString(GetLocalFloat(oCreature, sTargetType + "_RANGE" + sCounter), 0, 2));
return fMaxRange >= GetLocalFloat(oCreature, sTargetType + "_RANGE" + sCounter);
}
int ai_TargetIsInRangeofMaster(object oCreature, object oTarget)
{
object oMaster = GetMaster();
if(oMaster == OBJECT_INVALID) return TRUE;
float fMaxRange = GetLocalFloat(oCreature, AI_ASSOC_PERCEPTION_DISTANCE);
if(fMaxRange == 0.0) fMaxRange = 20.0;
float fTargetRangefromMaster = GetDistanceBetween(oTarget, oMaster);
if(AI_DEBUG) ai_Debug("0i_combat", "807", "fMaxRangefromMaster: " + FloatToString(fMaxRange, 0, 2) +
" fTargetRangefromMaster: " + FloatToString(fTargetRangefromMaster, 0, 2));
return fMaxRange >= fTargetRangefromMaster;
}
struct stTarget ai_CheckForNearestTarget(object oCreature, struct stTarget sTarget, int nIndex, string sIndex)
{
if(AI_DEBUG) ai_Debug("0i_combat", "817", "Getting nearest index: " + sIndex +
" fRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestRange: " + FloatToString(sTarget.fNearestRange, 0, 2) +
" fNearestSecondaryRange: " + FloatToString(sTarget.fNearestSecondaryRange, 0, 2));
// Lets put any disabled targets and associates if set in a secondary group.
if(GetLocalInt(oCreature, sTarget.sTargetType + "_DISABLED" + sIndex) ||
(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(sTarget.oTarget)))
{
if(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestSecondaryRange)
{
sTarget.fNearestSecondaryRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nSecondaryIndex = nIndex;
}
}
else if(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestRange)
{
sTarget.fNearestRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nIndex = nIndex;
}
return sTarget;
}
struct stTarget ai_CheckForLowestValueTarget(object oCreature, struct stTarget sTarget, int nIndex, string sIndex)
{
if(AI_DEBUG) ai_Debug("0i_combat", "835", "Getting lowest value index: " + sIndex +
" fRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestSecondaryRange: " + FloatToString(sTarget.fNearestSecondaryRange, 0, 2) +
" sTarget.nValue: " + IntToString(sTarget.nValue) +
" sTarget.nBestValue: " + IntToString(sTarget.nBestValue) +
" sTarget.nBestSecondaryValue: " + IntToString(sTarget.nBestSecondaryValue));
// Lets put any disabled targets and associates if set in a secondary group.
if(GetLocalInt(oCreature, sTarget.sTargetType + "_DISABLED" + sIndex) ||
(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(sTarget.oTarget)))
{
if(sTarget.nValue < sTarget.nBestSecondaryValue ||
(sTarget.nValue == sTarget.nBestSecondaryValue &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestSecondaryRange))
{
sTarget.fNearestSecondaryRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nBestSecondaryValue = sTarget.nValue;
sTarget.nSecondaryIndex = nIndex;
}
}
// Has less value or equal value and is closer.
else if(sTarget.nValue < sTarget.nBestValue ||
(sTarget.nBestValue == sTarget.nValue &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestRange))
{
sTarget.fNearestRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nBestValue = sTarget.nValue;
sTarget.nIndex = nIndex;
}
return sTarget;
}
struct stTarget ai_CheckForHighestValueTarget(object oCreature, struct stTarget sTarget, int nIndex, string sIndex)
{
if(AI_DEBUG) ai_Debug("0i_combat", "865", "Getting highest value index: " + sIndex +
" fRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestRange: " + FloatToString(sTarget.fNearestRange, 0, 2) +
" fNearestSecondaryRange: " + FloatToString(sTarget.fNearestSecondaryRange, 0, 2) +
" sTarget.nValue: " + IntToString(sTarget.nValue) +
" sTarget.nBestValue: " + IntToString(sTarget.nBestValue) +
" sTarget.nBestSecondaryValue: " + IntToString(sTarget.nBestSecondaryValue));
// Lets put any disabled targets and associates if set in a secondary group.
if(GetLocalInt(oCreature, sTarget.sTargetType + "_DISABLED" + sIndex) ||
(ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) && GetAssociateType(sTarget.oTarget)))
{
if(sTarget.nValue > sTarget.nBestSecondaryValue ||
(sTarget.nValue == sTarget.nBestSecondaryValue &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestSecondaryRange))
{
sTarget.fNearestSecondaryRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nBestSecondaryValue = sTarget.nValue;
sTarget.nSecondaryIndex = nIndex;
}
}
// Has less value or equal value and is closer.
else if(sTarget.nValue > sTarget.nBestValue ||
(sTarget.nBestValue == sTarget.nValue &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestRange))
{
sTarget.fNearestRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nBestValue = sTarget.nValue;
sTarget.nIndex = nIndex;
}
return sTarget;
}
struct stTarget ai_CheckForNearestAllTarget(object oCreature, struct stTarget sTarget, int nIndex, string sIndex)
{
if(AI_DEBUG) ai_Debug("0i_combat", "895", "Getting nearest (not disabled) index: " + sIndex +
" fRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestRange: " + FloatToString(sTarget.fNearestRange, 0, 2));
// If we are ignoring associates set then ignore them.
// Has lower value or equal value and is closer. Familiars/Companions/Summons/Dominated.
if(AI_DEBUG) ai_Debug("0i_combat", "911", "Don't Ignore Associate: " + IntToString(!ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES)) +
" Not an Associate? " + IntToString(GetAssociateType(sTarget.oTarget) < 2));
if((!ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) || GetAssociateType(sTarget.oTarget) < 2) &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestRange)
{
sTarget.fNearestRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nIndex = nIndex;
}
return sTarget;
}
struct stTarget ai_CheckForLowestValueAllTarget(object oCreature, struct stTarget sTarget, int nIndex, string sIndex)
{
if(AI_DEBUG) ai_Debug("0i_combat", "923", "Getting lowest value index: " + sIndex +
" fRange: " + FloatToString(GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex), 0, 2) +
" fNearestRange: " + FloatToString(sTarget.fNearestRange, 0, 2) +
" sTarget.nValue: " + IntToString(sTarget.nValue) +
" sTarget.nBestValue: " + IntToString(sTarget.nBestValue));
// Has less value or equal value and is closer. Ignoring only Familiars/Companions/Summons/Dominated.
if(AI_DEBUG) ai_Debug("0i_combat", "922", "Don't Ignore Associate: " + IntToString(!ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES)) +
" Not an Associate? " + IntToString(GetAssociateType(sTarget.oTarget) < 2));
if((!ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES) || GetAssociateType(sTarget.oTarget) < 2) &&
sTarget.nValue < sTarget.nBestValue ||
(sTarget.nBestValue == sTarget.nValue &&
GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex) < sTarget.fNearestRange))
{
sTarget.fNearestRange = GetLocalFloat(oCreature, sTarget.sTargetType + "_RANGE" + sIndex);
sTarget.nBestValue = sTarget.nValue;
sTarget.nIndex = nIndex;
}
return sTarget;
}
//******************************************************************************
//************ GET INDEX/TARGETs USING COMBAT STATE FUNCTIONS ******************
//******************************************************************************
// These functions will find a target based on the combat state variables created
// by the function ai_SetCombatState for associates.
int ai_GetNearestIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(GetLocalInt(oCreature, AI_RULE_AI_DIFFICULTY))
{
return ai_GetLowestCRIndex(oCreature, fMaxRange, sTargetType, bAlwaysAtk);
}
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "911", "Getting the nearest index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "918", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "931", "Found nearest [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetNearestTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "936", "Getting the nearest target.");
string sIndex = IntToString(ai_GetNearestIndex(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetLowestCRIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 100;
sTarget.nBestSecondaryValue = 100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "953", "Getting the lowest CR index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "960", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "974", "Found lowest CR [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetLowestCRTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "979", "Getting the lowest CR target.");
string sIndex = IntToString(ai_GetLowestCRIndex(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetHighestCRIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "995", "Getting the highest CR index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1002", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1016", "Found highest CR [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetHighestCRTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1021", "Getting the highest CR target.");
string sIndex = IntToString(ai_GetHighestCRIndex(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetLowestMeleeIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 100;
sTarget.nBestSecondaryValue = 100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1037", "Getting the lowest InMelee index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_MELEE" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1058", "Found lowest InMelee [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
int ai_GetHighestMeleeIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1073", "Getting the highest InMelee index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_MELEE" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1094", "Found highest InMelee [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_CheckForGroupedTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1124", "Getting the highest InMelee target.");
string sIndex = IntToString(ai_GetHighestMeleeIndex(oCreature, fMaxRange, sTargetType));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetMostWoundedIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 200;
sTarget.nBestSecondaryValue = 200;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1113", "Getting the most wounded index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1120", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_HEALTH" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1130", "Found most wounded [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetMostWoundedTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1139", "Getting the most wounded target.");
string sIndex = IntToString(ai_GetMostWoundedIndex(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetAllyToHealIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.nBestValue = 200;
sTarget.sTargetType = AI_ALLY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTarget.sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1154", "Getting the most wounded ally to heal index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ALLY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ALLY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ALLY, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, AI_ALLY_HEALTH + sCounter);
sTarget = ai_CheckForLowestValueAllTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ALLY + sCounter);
}
// If we do not have a normal target then we are done..
if(AI_DEBUG) ai_Debug("0i_combat", "1187", "Found most wounded ally to heal Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetAllyToHealTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1192", "Getting the most wounded ally to heal target.");
string sIndex = IntToString(ai_GetAllyToHealIndex(oCreature, fMaxRange));
return GetLocalObject(oCreature, AI_ALLY + sIndex);
}
object ai_GetLowestFortitudeSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 200;
sTarget.nBestSecondaryValue = 200;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1113", "Getting the lowest fortitude save index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetFortitudeSavingThrow(sTarget.oTarget);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1232", "Found lowest fortitude save Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, AI_ENEMY + IntToString(sTarget.nIndex));
}
object ai_GetLowestReflexSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 200;
sTarget.nBestSecondaryValue = 200;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1248", "Getting the lowest reflex save index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetReflexSavingThrow(sTarget.oTarget);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1269", "Found lowest reflex save Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, AI_ENEMY + IntToString(sTarget.nIndex));
}
object ai_GetLowestWillSaveTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 200;
sTarget.nBestSecondaryValue = 200;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1285", "Getting the lowest will save index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetWillSavingThrow(sTarget.oTarget);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1306", "Found lowest will save Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, AI_ENEMY + IntToString(sTarget.nIndex));
}
object ai_GetSpellTargetBasedOnSaves(object oCreature, int nSpell, float fMaxRange = AI_RANGE_PERCEPTION)
{
// Check the spells save type in "ai_spells.2da" and find the weakest
// creature based on that save.
string sSaveType = Get2DAString("ai_spells", "SaveType", nSpell);
if(sSaveType == "Reflex") return ai_GetLowestReflexSaveTarget(oCreature, fMaxRange);
if(sSaveType == "Fortitude") return ai_GetLowestFortitudeSaveTarget(oCreature, fMaxRange);
if(sSaveType == "Will") return ai_GetLowestWillSaveTarget(oCreature, fMaxRange);
// If there is no save then lets see if we can find an enemy with the lowest health.
return ai_GetMostWoundedTarget(oCreature, fMaxRange);
}
int ai_GetNearestIndexThatSeesUs(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1334", "Getting the nearest creature that sees us index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1373", GetName(sTarget.oTarget) + " can see us? " +
IntToString(GetObjectSeen(oCreature, sTarget.oTarget)));
if(GetObjectSeen(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestAllTarget(oCreature, sTarget, nCounter, sCounter);
}
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(AI_DEBUG) ai_Debug("0i_combat", "1354", "Found nearest creature that sees us Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
int ai_GetBestSneakAttackIndex(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
object oAttacking;
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1372", "Getting the best sneak attack index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget) &&
!ai_IsImmuneToSneakAttacks(oCreature, sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
oAttacking = ai_GetAttackedTarget(sTarget.oTarget);
if(AI_DEBUG) ai_Debug("0i_combat", "1383", "oTarget: " + GetName(sTarget.oTarget) +
" is attacking " + GetName(oAttacking));
// They are attacking someone besides us or we are hidden?
if((oAttacking != OBJECT_INVALID && oAttacking != oCreature) ||
GetActionMode(oCreature, ACTION_MODE_STEALTH))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1398", "Found best sneak attack Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
int ai_GetNearestIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(GetLocalInt(oCreature, AI_RULE_AI_DIFFICULTY))
{
ai_GetLowestCRIndexNotInAOE(oCreature, fMaxRange, sTargetType, bAlwaysAtk);
}
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1416", "Getting the nearest not in AOE index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget) && !ai_IsInADangerousAOE(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1434", "Found nearest not in AOE Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetNearestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1439", "Getting the nearest not in AOE target.");
string sIndex = IntToString(ai_GetNearestIndexNotInAOE(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetLowestCRIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 100;
sTarget.nBestSecondaryValue = 100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1456", "Getting the lowest CR not in AOE index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && !ai_IsInADangerousAOE(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1463", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1477", "Found lowest CR not in AOE [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetLowestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1482", "Getting the lowest cr not in AOE target.");
string sIndex = IntToString(ai_GetLowestCRIndexNotInAOE(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetHighestCRIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1499", "Getting the highest CR not in AOE index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && !ai_IsInADangerousAOE(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1506", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1520", "Found highest CR not in AOE [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_GetHighestTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1525", "Getting the highest cr not in AOE target.");
string sIndex = IntToString(ai_GetHighestCRIndexNotInAOE(oCreature, fMaxRange, sTargetType, bAlwaysAtk));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
int ai_GetHighestMeleeIndexNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1542", "Getting the highest InMelee not in AOE index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && !ai_IsInADangerousAOE(sTarget.oTarget))
{
if(ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_MELEE" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1563", "Found highest InMelee not in AOE [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return sTarget.nIndex;
}
object ai_CheckForGroupedTargetNotInAOE(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1574", "Getting the highest InMelee not in AOE target.");
string sIndex = IntToString(ai_GetHighestMeleeIndexNotInAOE(oCreature, fMaxRange, sTargetType));
return GetLocalObject(oCreature, sTargetType + sIndex);
}
object ai_GetNearestClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(GetLocalInt(oCreature, AI_RULE_AI_DIFFICULTY))
{
ai_GetLowestCRClassTarget(oCreature, nClassType, fMaxRange, sTargetType, bAlwaysAtk);
}
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1591", "Getting the nearest class index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckClassType(sTarget.oTarget, nClassType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1598", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1611", "Found nearest class Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetLowestCRClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 100;
sTarget.nBestSecondaryValue = 100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1626", "Getting the lowest CR class index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckClassType(sTarget.oTarget, nClassType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1634", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1648", "Found lowest CR class [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetHighestCRClassTarget(object oCreature, int nClassType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1664", "Getting the highest CR class index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckClassType(sTarget.oTarget, nClassType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1671", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1685", "Found highest CR class [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetNearestRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
if(GetLocalInt(oCreature, AI_RULE_AI_DIFFICULTY))
{
ai_GetLowestCRRacialTarget(oCreature, nRacialType, fMaxRange, sTargetType, bAlwaysAtk);
}
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1703", "Getting the nearest race index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckRacialType(sTarget.oTarget, nRacialType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1710", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1723", "Found nearest race Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetLowestCRRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = 100;
sTarget.nBestSecondaryValue = 100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1739", "Getting the lowest CR race index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckRacialType(sTarget.oTarget, nRacialType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1746", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForLowestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1760", "Found lowest CR race [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetHighestCRRacialTarget(object oCreature, int nRacialType, float fMaxRange = AI_RANGE_PERCEPTION, string sTargetType = AI_ENEMY, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = sTargetType;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1776", "Getting the highest CR race index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, sTargetType + "_PERCEIVED" + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckRacialType(sTarget.oTarget, nRacialType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1783", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, sTargetType, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget.nValue = GetLocalInt(oCreature, sTargetType + "_COMBAT" + sCounter);
sTarget = ai_CheckForHighestValueTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, sTargetType + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1797", "Found highest CR race [" + sTargetType + "] Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, sTargetType + IntToString(sTarget.nIndex));
}
object ai_GetNearestFavoredEnemyTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.nBestValue = -100;
sTarget.nBestSecondaryValue = -100;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
int nRace, nRacialType;
while(nRace < 24)
{
// Find which favored enemies we have.
if(nRace < 1 && GetHasFeat(FEAT_FAVORED_ENEMY_ABERRATION, oCreature))
{
nRace = 1;
nRacialType = RACIAL_TYPE_ABERRATION;
}
else if(nRace < 2 && GetHasFeat(FEAT_FAVORED_ENEMY_ANIMAL, oCreature))
{
nRace = 2;
nRacialType = RACIAL_TYPE_ANIMAL;
}
else if(nRace < 3 && GetHasFeat(FEAT_FAVORED_ENEMY_BEAST, oCreature))
{
nRace = 3;
nRacialType = RACIAL_TYPE_BEAST;
}
else if(nRace < 4 && GetHasFeat(FEAT_FAVORED_ENEMY_CONSTRUCT, oCreature))
{
nRace = 4;
nRacialType = RACIAL_TYPE_CONSTRUCT;
}
else if(nRace < 5 && GetHasFeat(FEAT_FAVORED_ENEMY_DRAGON, oCreature))
{
nRace = 5;
nRacialType = RACIAL_TYPE_DRAGON;
}
else if(nRace < 6 && GetHasFeat(FEAT_FAVORED_ENEMY_DWARF, oCreature))
{
nRace = 6;
nRacialType = RACIAL_TYPE_DWARF;
}
else if(nRace < 7 && GetHasFeat(FEAT_FAVORED_ENEMY_ELEMENTAL, oCreature))
{
nRace = 7;
nRacialType = RACIAL_TYPE_ELEMENTAL;
}
else if(nRace < 8 && GetHasFeat(FEAT_FAVORED_ENEMY_ELF, oCreature))
{
nRace = 8;
nRacialType = RACIAL_TYPE_ELF;
}
else if(nRace < 9 && GetHasFeat(FEAT_FAVORED_ENEMY_FEY, oCreature))
{
nRace = 9;
nRacialType = RACIAL_TYPE_FEY;
}
else if(nRace < 10 && GetHasFeat(FEAT_FAVORED_ENEMY_GIANT, oCreature))
{
nRace = 10;
nRacialType = RACIAL_TYPE_GIANT;
}
else if(nRace < 11 && GetHasFeat(FEAT_FAVORED_ENEMY_GNOME, oCreature))
{
nRace = 11;
nRacialType = RACIAL_TYPE_GNOME;
}
else if(nRace < 12 && GetHasFeat(FEAT_FAVORED_ENEMY_GOBLINOID, oCreature))
{
nRace = 12;
nRacialType = RACIAL_TYPE_HUMANOID_GOBLINOID;
}
else if(nRace < 13 && GetHasFeat(FEAT_FAVORED_ENEMY_HALFELF, oCreature))
{
nRace = 13;
nRacialType = RACIAL_TYPE_HALFELF;
}
else if(nRace < 14 && GetHasFeat(FEAT_FAVORED_ENEMY_HALFLING, oCreature))
{
nRace = 14;
nRacialType = RACIAL_TYPE_HALFLING;
}
else if(nRace < 15 && GetHasFeat(FEAT_FAVORED_ENEMY_HALFORC, oCreature))
{
nRace = 15;
nRacialType = RACIAL_TYPE_HALFORC;
}
else if(nRace < 16 && GetHasFeat(FEAT_FAVORED_ENEMY_HUMAN, oCreature))
{
nRace = 16;
nRacialType = RACIAL_TYPE_HUMAN;
}
else if(nRace < 17 && GetHasFeat(FEAT_FAVORED_ENEMY_MAGICAL_BEAST, oCreature))
{
nRace = 17;
nRacialType = RACIAL_TYPE_MAGICAL_BEAST;
}
else if(nRace < 18 && GetHasFeat(FEAT_FAVORED_ENEMY_MONSTROUS, oCreature))
{
nRace = 18;
nRacialType = RACIAL_TYPE_HUMANOID_MONSTROUS;
}
else if(nRace < 19 && GetHasFeat(FEAT_FAVORED_ENEMY_ORC, oCreature))
{
nRace = 19;
nRacialType = RACIAL_TYPE_HUMANOID_ORC;
}
else if(nRace < 20 && GetHasFeat(FEAT_FAVORED_ENEMY_OUTSIDER, oCreature))
{
nRace = 20;
nRacialType = RACIAL_TYPE_OUTSIDER;
}
else if(nRace < 21 && GetHasFeat(FEAT_FAVORED_ENEMY_REPTILIAN, oCreature))
{
nRace = 21;
nRacialType = RACIAL_TYPE_HUMANOID_REPTILIAN;
}
else if(nRace < 22 && GetHasFeat(FEAT_FAVORED_ENEMY_SHAPECHANGER, oCreature))
{
nRace = 22;
nRacialType = RACIAL_TYPE_SHAPECHANGER;
}
else if(nRace < 23 && GetHasFeat(FEAT_FAVORED_ENEMY_UNDEAD, oCreature))
{
nRace = 23;
nRacialType = RACIAL_TYPE_UNDEAD;
}
else if(nRace < 24 && GetHasFeat(FEAT_FAVORED_ENEMY_VERMIN, oCreature))
{
nRace = 24;
nRacialType = RACIAL_TYPE_VERMIN;
}
else nRace = 25;
if(nRace < 25)
{
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "1940", "Getting the nearest favored race index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget) && ai_CheckRacialType(sTarget.oTarget, nRacialType))
{
if(AI_DEBUG) ai_Debug("0i_combat", "1947", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) +
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
}
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "1962", "Found nearest favored race Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, AI_ENEMY + IntToString(sTarget.nIndex));
}
object ai_GetFlankTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE)
{
int nCnt = 1, nInMelee, nHighestMelee;
string sCnt = "1";
float fAllyRange;
object oTarget, oAlly = GetLocalObject(oCreature, AI_ALLY + sCnt);
while(oAlly != OBJECT_INVALID)
{
fAllyRange = GetLocalFloat(oCreature, AI_ALLY_RANGE + sCnt);
if(AI_DEBUG) ai_Debug("0i_combat", "1974", "Getting Ally being Flanked Index: " + sCnt + " " +
GetName(oAlly) + " fAllyRange: " + FloatToString(fAllyRange, 0, 2) +
" fMaxRange: " + FloatToString(fMaxRange, 0, 2));
if(fAllyRange <= fMaxRange)
{
nInMelee = GetLocalInt(oCreature, AI_ALLY_MELEE + sCnt);
if(AI_DEBUG) ai_Debug("0i_combat", "1980", "nInMelee: " + IntToString(nInMelee));
if(!GetIsDead(oAlly) && nInMelee > nHighestMelee)
{
oTarget = ai_GetEnemyAttackingMyAlly(oCreature, oAlly, fMaxRange);
if(oTarget != OBJECT_INVALID) nHighestMelee = nInMelee;
}
}
sCnt = IntToString(++nCnt);
oAlly = GetLocalObject(oCreature, AI_ALLY + sCnt);
}
// If we do not have a good target then lets see if there are more targets.
if(oTarget == OBJECT_INVALID)
{
// If we just checked within melee then lets check what we can see if
// we can move around in combat.
if (fMaxRange == AI_RANGE_MELEE && ai_CanIMoveInCombat(oCreature))
{
oTarget = ai_GetFlankTarget(oCreature, AI_RANGE_PERCEPTION, bAlwaysAtk);
}
}
if(AI_DEBUG) ai_Debug("0i_combat", "2000", "oTarget " + GetName(oTarget) +
" is attacking " + GetName(oAlly));
return oTarget;
}
object ai_GetRangedTarget(object oCreature, float fMaxRange = AI_RANGE_PERCEPTION, int bAlwaysAtk = TRUE)
{
struct stTarget sTarget;
sTarget.fNearestRange = fMaxRange + 1.0;
sTarget.fNearestSecondaryRange = sTarget.fNearestRange;
sTarget.sTargetType = AI_ENEMY;
int nCounter = 1;
string sCounter = "1";
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
while(sTarget.oTarget != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "2037", "Getting the nearest ranged index: " +
sCounter + " " + GetName(sTarget.oTarget) +
" Seen: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter)) +
" GetIsDead: " + IntToString(GetIsDead(sTarget.oTarget)));
if(GetLocalInt(oCreature, AI_ENEMY_PERCEIVED + sCounter) &&
!GetIsDead(sTarget.oTarget))
{
if(AI_DEBUG) ai_Debug("0i_combat", "2044", "bAlwaysAtk: " + IntToString(bAlwaysAtk));
if((bAlwaysAtk || !ai_IsStrongerThanMe(oCreature, nCounter)) &&
ai_TargetIsInRangeofCreature(oCreature, AI_ENEMY, sCounter, fMaxRange) &&
ai_TargetIsInRangeofMaster(oCreature, sTarget.oTarget))
{
if(ai_GetIsRangeWeapon(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, sTarget.oTarget)))
{
sTarget = ai_CheckForNearestTarget(oCreature, sTarget, nCounter, sCounter);
}
}
}
sCounter = IntToString(++nCounter);
sTarget.oTarget = GetLocalObject(oCreature, AI_ENEMY + sCounter);
}
// If we do not have a normal target then use our best secondary target.
if(sTarget.nIndex == 0 && sTarget.nSecondaryIndex != 0) sTarget.nIndex = sTarget.nSecondaryIndex;
if(AI_DEBUG) ai_Debug("0i_combat", "2060", "Found nearest ranged Index: " + IntToString(sTarget.nIndex));
return GetLocalObject(oCreature, AI_ENEMY + IntToString(sTarget.nIndex));
}
object ai_GetBestTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
{
object oPCTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET);
if(oPCTarget != OBJECT_INVALID) return oPCTarget;
string sIndex;
// Are we in melee? If so try to get the weakest enemy in melee.
if(nInMelee > 0)
{
if(ai_CanIMoveInCombat(oCreature))
{
sIndex = IntToString(ai_GetLowestCRIndex(oCreature, AI_RANGE_MELEE));
}
else sIndex = IntToString(ai_GetNearestIndex(oCreature, AI_RANGE_MELEE));
}
// If not then lets go find someone to attack!
else
{
// If we are not in melee then we should get the nearest enemy.
sIndex = IntToString(ai_GetNearestIndexNotInAOE(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
/* Lets stay out of bad AOE's.
// If we didn't get a target then get any target within range.
if(sIndex == "0")
{
sIndex = IntToString(ai_GetLowestCRIndex(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
} */
}
object oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex);
// We might not have a target this is fine as sometimes we don't want to attack!
if(AI_DEBUG) ai_Debug("0i_combat", "2048", GetName(oTarget) + " is the best target for melee combat!");
return oTarget;
}
object ai_GetNearestTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
{
object oPCTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET);
if(oPCTarget != OBJECT_INVALID) return oPCTarget;
string sIndex;
// Are we in melee? If so try to get the nearest enemy in melee.
if(nInMelee > 0) sIndex = IntToString(ai_GetNearestIndex(oCreature, AI_RANGE_MELEE));
// If not then lets go find someone to attack!
else
{
// Get the nearest enemy.
sIndex = IntToString(ai_GetNearestIndexNotInAOE(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
// If we didn't get a target then get any target within range.
if(sIndex == "0")
{
sIndex = IntToString(ai_GetNearestIndex(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
}
}
object oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex);
// We might not have a target this is fine as sometimes we don't want to attack!
if(AI_DEBUG) ai_Debug("0i_combat", "2024", GetName(oTarget) + " is the nearest target for melee combat!");
return oTarget;
}
object ai_GetLowestCRTargetForMeleeCombat(object oCreature, int nInMelee, int bAlwaysAtk = TRUE)
{
object oPCTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET);
if(oPCTarget != OBJECT_INVALID) return oPCTarget;
string sIndex;
// Are we in melee? If so try to get the weakest enemy in melee.
if(nInMelee > 0) sIndex = IntToString(ai_GetLowestCRIndex(oCreature, AI_RANGE_MELEE));
// If not then lets go find someone to attack!
else
{
// Get the weakest combat rated enemy.
sIndex = IntToString(ai_GetLowestCRIndexNotInAOE(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
/* Lets stay out of bad AOE's.
// If we didn't get a target then get any target within range.
if(sIndex == "0")
{
sIndex = IntToString(ai_GetLowestCRIndex(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, bAlwaysAtk));
} */
}
object oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex);
// We might not have a target this is fine as sometimes we don't want to attack!
if(AI_DEBUG) ai_Debug("0i_combat", "2048", GetName(oTarget) + " is the weakest target for melee combat!");
return oTarget;
}
object ai_GetHighestCRTargetForMeleeCombat(object oCreature, int nInMelee)
{
object oPCTarget = GetLocalObject(oCreature, AI_PC_LOCKED_TARGET);
if(oPCTarget != OBJECT_INVALID) return oPCTarget;
string sIndex;
// Are we in melee? If so try to get the weakest enemy in melee.
if(nInMelee > 0) sIndex = IntToString(ai_GetHighestCRIndex(oCreature, AI_RANGE_MELEE));
// If not then lets go find someone to attack!
else
{
// Get the weakest combat rated enemy.
sIndex = IntToString(ai_GetHighestCRIndexNotInAOE(oCreature, AI_RANGE_PERCEPTION));
/* Lets stay out of bad AOE's.
// If we didn't get a target then get any target within range.
if(sIndex == "0") sIndex = IntToString(ai_GetHighestCRIndex(oCreature));
*/
}
object oTarget = GetLocalObject(oCreature, AI_ENEMY + sIndex);
// We might not have a target this is fine as sometimes we don't want to attack!
if(AI_DEBUG) ai_Debug("0i_combat", "2070", GetName(oTarget) + " is the strongest target for melee combat!");
return oTarget;
}
object ai_GetEnemyAttackingMe(object oCreature, float fMaxRange = AI_RANGE_MELEE)
{
int nCtr = 1;
float fDistance;
string sCtr = "1";
object oAttacked;
object oEnemy = GetLocalObject(oCreature, AI_ENEMY + "1");
while(oEnemy != OBJECT_INVALID)
{
if(!ai_Disabled(oEnemy))
{
fDistance = GetLocalFloat(oCreature, AI_ENEMY_RANGE + sCtr);
if(AI_DEBUG) ai_Debug("0i_combat", "2084", "Getting Enemy Attacking Me: " + sCtr + " " +
GetName(oEnemy) + " fTargetRange: " + FloatToString(fDistance, 0, 2) +
" fMaxRange: " + FloatToString(fMaxRange, 0, 2) + " Attacking: " +
GetName(ai_GetAttackedTarget(oEnemy)));
if(fDistance <= fMaxRange)
{
oAttacked = ai_GetAttackedTarget(oEnemy);
// If an enemy isn't attacking someone we must assume we are next!
if(oAttacked == oCreature || oAttacked == OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "2095", "Enemy attacking me: " + GetName(oEnemy) + " has attacked: " + GetName(ai_GetAttackedTarget(oEnemy)));
return oEnemy;
}
}
}
sCtr = IntToString(++nCtr);
oEnemy = GetLocalObject(oCreature, AI_ENEMY + sCtr);
}
return OBJECT_INVALID;
}
object ai_GetEnemyAttackingMyAlly(object oCreature, object oAlly, float fMaxRange = AI_RANGE_MELEE)
{
int nCtr = 1, nIndex, nDIndex;
int bIngnoreAssociates = ai_GetAIMode(oCreature, AI_MODE_IGNORE_ASSOCIATES);
float fEnemyRange, fNearestEnemyRange = fMaxRange + 1.0;
float fNearestDEnemyRange = fMaxRange + 1.0;
string sCtr = "1";
object oAttacked;
object oEnemy = GetLocalObject(oCreature, AI_ENEMY + "1");
while(oEnemy != OBJECT_INVALID)
{
fEnemyRange = GetLocalFloat(oCreature, AI_ENEMY_RANGE + sCtr);
if(AI_DEBUG) ai_Debug("0i_combat", "2117", "Getting Enemy Attacking Ally:" +
GetName(oAlly) + ": " + sCtr + " InMelee:" +
GetName(oEnemy) + " fEnemyRange: " + FloatToString(fEnemyRange, 0, 2) +
" fMaxRange: " + FloatToString(fMaxRange, 0, 2) + " Attacking: " +
GetName(ai_GetAttackedTarget(oEnemy)));
if(fEnemyRange <= fMaxRange)
{
oAttacked = ai_GetAttackedTarget(oEnemy);
if(AI_DEBUG) ai_Debug("0i_combat", "2125", "Enemy attacking " +
GetName(oAlly) + ": " + GetName(oEnemy) +
" has attacked: " + GetName(ai_GetAttackedTarget(oEnemy)));
// If an enemy isn't attacking someone we must assume we are next!
if(oAttacked == oAlly)
{
// Lets put any disabled targets in its own group, if we
// ignore associates lets put them here as well.
if(GetLocalInt(oCreature, AI_ENEMY_DISABLED + sCtr) ||
(bIngnoreAssociates && GetAssociateType(oEnemy)))
{
if(fEnemyRange < fNearestDEnemyRange)
{
fNearestDEnemyRange = fEnemyRange;
nDIndex = nCtr;
}
}
else if(fEnemyRange < fNearestEnemyRange)
{
fNearestEnemyRange = fEnemyRange;
nIndex = nCtr;
}
}
}
sCtr = IntToString(++nCtr);
oEnemy = GetLocalObject(oCreature, AI_ENEMY + sCtr);
}
// If we do not have a good target then lets see if there are more targets.
if(nIndex == 0 && nDIndex != 0)
{
// If we just checked within melee then lets check what we can see.
if (fMaxRange == AI_RANGE_MELEE) return ai_GetEnemyAttackingMyAlly(oCreature, oAlly, AI_RANGE_PERCEPTION);
else nIndex = nDIndex;
}
return GetLocalObject(oCreature, AI_ENEMY + IntToString(nIndex));
}
int ai_GetNumOfEnemiesInRange(object oCreature, float fMaxRange = AI_RANGE_MELEE)
{
int nNumOfEnemies, nCnt = 1;
float fDistance = GetLocalFloat(oCreature, AI_ENEMY_RANGE + "1");
while(fDistance != 0.0)
{
if(fDistance < fMaxRange) nNumOfEnemies ++;
fDistance = GetLocalFloat(oCreature, AI_ENEMY_RANGE + IntToString(++nCnt));
}
if(AI_DEBUG) ai_Debug("0i_combat", "2459", IntToString (nNumOfEnemies) + " enemies within " + FloatToString(fMaxRange, 0, 2) + " meters.");
return nNumOfEnemies;
}
object ai_GetAllyBuffTarget(object oCreature, int nSpell, float fMaxRange = AI_RANGE_BATTLEFIELD)
{
// Make sure we don't over extend our movement running across the
// battlefield to cast a spell on someone does not look good.
float fNearestEnemy = GetDistanceBetween(oCreature, GetLocalObject(oCreature, AI_ENEMY_NEAREST)) - 3.0f;
// If we are in melee then extend to melee incase an ally is just past the enemy.
if(fNearestEnemy <= AI_RANGE_MELEE) fNearestEnemy = AI_RANGE_MELEE;
if(fMaxRange > fNearestEnemy) fMaxRange = fNearestEnemy;
// Now lets get the best target based on the spell data in ai_spells.2da
string sBuffTarget = Get2DAString("ai_spells", "Buff_Target", nSpell);
if(AI_DEBUG) ai_Debug("0i_combat", "2596", "sBuffTarget: " + sBuffTarget + " fMaxRange: " + FloatToString(fMaxRange, 0, 2));
if(sBuffTarget == "0") return oCreature;
if(sBuffTarget == "1")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_STRENGTH, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "2")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_DEXTERITY, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "3")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_CONSTITUTION, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "4")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_INTELLIGENCE, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "5")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_WISDOM, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "6")
return ai_BuffHighestAbilityScoreTarget(oCreature, nSpell, ABILITY_CHARISMA, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "7")
return ai_BuffLowestACTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "8")
return ai_BuffLowestACWithOutACBonus(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "9")
return ai_BuffHighestAttackTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "10")
return ai_BuffMostWoundedTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "11")
return ai_BuffLowestFortitudeSaveTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "12")
return ai_BuffLowestReflexSaveTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "13")
return ai_BuffLowestWillSaveTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
else if(sBuffTarget == "14")
return ai_BuffLowestSaveTarget(oCreature, nSpell, "", fMaxRange, AI_ALLY);
return OBJECT_INVALID;
}
//******************************************************************************
//******************** OTHER COMBAT FUNCTIONS ********************************
//******************************************************************************
int ai_GetCurrentRound(object oCreature)
{
int nRound = GetLocalInt(oCreature, AI_ROUND) + 1;
SetLocalInt(oCreature, AI_ROUND, nRound);
if(AI_DEBUG) ai_Debug("0i_combat", "2471", "nRound: " + IntToString(nRound));
return nRound;
}
int ai_GetDifficulty(object oCreature)
{
int nAdjustment = GetLocalInt(oCreature, AI_DIFFICULTY_ADJUSTMENT);
int nDifficulty = GetLocalInt(oCreature, AI_ENEMY_POWER) - GetLocalInt(oCreature, AI_ALLY_POWER) + 13 + nAdjustment;
if(nDifficulty < 1) nDifficulty = 1;
if(AI_DEBUG) ai_Debug("0i_combat", "2474", "(Difficulty: Enemy Power: " + IntToString(GetLocalInt(oCreature, AI_ENEMY_POWER)) +
" - Ally Power: " + IntToString(GetLocalInt(oCreature, AI_ALLY_POWER)) +
") + 13 + nAdj: " + IntToString(nAdjustment) +
" = " + IntToString(nDifficulty) + "(Min of 1)");
return nDifficulty;
}
int ai_GetMyCombatRating(object oCreature)
{
object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
int nAtkBonus = GetBaseAttackBonus(oCreature);
if(GetHasFeat(FEAT_WEAPON_FINESSE, oCreature) && ai_GetIsFinesseWeapon(oCreature, oWeapon))
{
nAtkBonus += GetAbilityModifier(ABILITY_DEXTERITY, oCreature);
}
else nAtkBonus += GetAbilityModifier(ABILITY_STRENGTH, oCreature);
if(ai_GetIsMeleeWeapon(oWeapon)) nAtkBonus += ai_GetWeaponAtkBonus(oWeapon);
if(AI_DEBUG) ai_Debug("0i_combat", "2496", "GetMyCombatRating (nAtkBonus: " + IntToString(nAtkBonus) +
" nAC: " + IntToString(GetAC(oCreature)) + " - 10) / 2 = " +
IntToString((nAtkBonus + GetAC(oCreature) - 10) / 2));
return(nAtkBonus + GetAC(oCreature) - 10) / 2;
}
object ai_GetAttackedTarget(object oCreature, int bPhysical = TRUE, int bSpell = FALSE)
{
object oTarget = GetAttackTarget(oCreature);
if(!GetIsObjectValid(oTarget) && bPhysical) oTarget = GetLocalObject(oCreature, AI_ATTACKED_PHYSICAL);
if(!GetIsObjectValid(oTarget) && bSpell) oTarget = GetLocalObject(oCreature, AI_ATTACKED_SPELL);
if(!GetIsObjectValid(oTarget) || GetIsDead(oTarget)) return OBJECT_INVALID;
return oTarget;
}
int ai_CheckClassType(object oTarget, int nClassType)
{
int nCnt = 1, nClass = GetClassByPosition(1, oTarget);
// We check for the group class types.
if(nClassType < 0)
{
while(nCnt <= AI_MAX_CLASSES_PER_CHARACTER)
{
int nCaster = StringToInt(Get2DAString("classes", "SpellCaster", nClass));
if(nClassType == AI_CLASS_TYPE_WARRIOR && !nCaster) return TRUE;
else if(nClassType == AI_CLASS_TYPE_CASTER && nCaster) return TRUE;
int nSpellType = StringToInt(Get2DAString("classes", "Arcane", nClass));
if(nClassType == AI_CLASS_TYPE_ARCANE && nSpellType) return TRUE;
else if(nClassType == AI_CLASS_TYPE_DIVINE && !nSpellType) return TRUE;
nClass = GetClassByPosition(++nCnt, oTarget);
}
}
// Checks for normal classes.
else
{
while(nCnt <= AI_MAX_CLASSES_PER_CHARACTER)
{
if(nClass == nClassType) return TRUE;
nClass = GetClassByPosition(++nCnt, oTarget);
}
}
return FALSE;
}
int ai_CheckRacialType(object oTarget, int nRacialType)
{
int nRace = GetRacialType(oTarget);
if(nRacialType == nRace) return TRUE;
else if(nRacialType == AI_RACIAL_TYPE_ANIMAL_BEAST)
{
if(nRace == RACIAL_TYPE_ANIMAL ||
nRace == RACIAL_TYPE_BEAST ||
nRace == RACIAL_TYPE_MAGICAL_BEAST) return TRUE;
}
else if(nRacialType == AI_RACIAL_TYPE_HUMANOID)
{
switch (nRace)
{
case RACIAL_TYPE_DWARF :
case RACIAL_TYPE_ELF :
case RACIAL_TYPE_GNOME :
case RACIAL_TYPE_HALFELF :
case RACIAL_TYPE_HALFLING :
case RACIAL_TYPE_HALFORC :
case RACIAL_TYPE_HUMAN :
case RACIAL_TYPE_HUMANOID_GOBLINOID :
case RACIAL_TYPE_HUMANOID_MONSTROUS :
case RACIAL_TYPE_HUMANOID_ORC :
case RACIAL_TYPE_HUMANOID_REPTILIAN :
return TRUE;
}
}
return FALSE;
}
void ai_SetNormalAppearance(object oCreature)
{
if(!ai_GetHasEffectType(oCreature, EFFECT_TYPE_POLYMORPH))
{
int nForm = GetAppearanceType(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "2729", GetName(oCreature) + " form: " + IntToString(nForm));
SetLocalInt(oCreature, AI_NORMAL_FORM, nForm + 1);
}
}
int ai_GetNormalAppearance(object oCreature)
{
int nForm = GetLocalInt(oCreature, AI_NORMAL_FORM) - 1;
if(nForm == -1)
{
ai_SetNormalAppearance(oCreature);
nForm = GetLocalInt(oCreature, AI_NORMAL_FORM) - 1;
}
return nForm;
}
struct stClasses ai_GetFactionsClasses(object oCreature, int bEnemy = TRUE, float fMaxRange = AI_RANGE_BATTLEFIELD)
{
struct stClasses sCount;
int nCnt = 1, nPosition, nClass, nLevels;
object oTarget;
if(bEnemy) oTarget = ai_GetNearestEnemy(oCreature, 1, 7, 7);
else oTarget = ai_GetNearestAlly(oCreature, 1, 7, 7);
while(oTarget != OBJECT_INVALID && GetDistanceBetween(oTarget, oCreature) <= fMaxRange)
{
for(nPosition = 1; nPosition <= AI_MAX_CLASSES_PER_CHARACTER; nPosition++)
{
nClass = GetClassByPosition(nPosition, oTarget);
nLevels = GetLevelByPosition(nPosition, oTarget);
if(nClass == CLASS_TYPE_ANIMAL ||
nClass == CLASS_TYPE_BARBARIAN ||
nClass == CLASS_TYPE_COMMONER ||
nClass == CLASS_TYPE_CONSTRUCT ||
nClass == CLASS_TYPE_ELEMENTAL ||
nClass == CLASS_TYPE_FIGHTER ||
nClass == CLASS_TYPE_GIANT ||
nClass == CLASS_TYPE_HUMANOID ||
nClass == CLASS_TYPE_MONSTROUS ||
nClass == CLASS_TYPE_PALADIN ||
nClass == CLASS_TYPE_RANGER ||
nClass == CLASS_TYPE_ROGUE ||
nClass == CLASS_TYPE_VERMIN ||
nClass == CLASS_TYPE_MONK ||
nClass == CLASS_TYPE_SHAPECHANGER)
{
sCount.FIGHTERS += 1;
sCount.FIGHTER_LEVELS += nLevels;
}
else if(nClass == CLASS_TYPE_CLERIC ||
nClass == CLASS_TYPE_DRUID)
{
sCount.CLERICS += 1;
sCount.CLERIC_LEVELS += nLevels;
}
else if(nClass == CLASS_TYPE_BARD ||
nClass == CLASS_TYPE_FEY ||
nClass == CLASS_TYPE_SORCERER ||
nClass == CLASS_TYPE_WIZARD)
{
sCount.MAGES += 1;
sCount.MAGE_LEVELS += nLevels;
}
else if(nClass == CLASS_TYPE_ABERRATION ||
nClass == CLASS_TYPE_DRAGON ||
nClass == 29 || //oozes
nClass == CLASS_TYPE_MAGICAL_BEAST ||
nClass == CLASS_TYPE_OUTSIDER)
{
sCount.MONSTERS += 1;
sCount.MONSTER_LEVELS += nLevels;
}
sCount.TOTAL_LEVELS += nLevels;
}
sCount.TOTAL += 1;
if(bEnemy) oTarget = ai_GetNearestEnemy(oCreature, ++nCnt, 7, 7);
else oTarget = ai_GetNearestAlly(oCreature, ++nCnt, 7, 7);
}
if(AI_DEBUG) ai_Debug("0i_combat", "2627", "Enemy: " + IntToString(bEnemy) + " fMaxRange: " + FloatToString(fMaxRange, 0, 2) +
" CLERICS: " + IntToString(sCount.CLERICS) + "(" + IntToString(sCount.CLERIC_LEVELS) +
") FIGHTERS: " +IntToString(sCount.FIGHTERS) + "(" + IntToString(sCount.FIGHTER_LEVELS) +
") MAGES: " +IntToString(sCount.MAGES) + "(" + IntToString(sCount.MAGE_LEVELS) +
") MONSTERS: " +IntToString(sCount.MONSTERS) + "(" + IntToString(sCount.MONSTER_LEVELS) +
") TOTALS: " +IntToString(sCount.TOTAL) + "(" + IntToString(sCount.TOTAL_LEVELS));
return sCount;
}
string ai_GetMostDangerousClass(struct stClasses stCount)
{
string sClass;
// Lets weight the fighter levels 30% higher.
int nFighter =((stCount.FIGHTER_LEVELS) * 13)/10;
if(nFighter >= stCount.CLERIC_LEVELS)
{
if(nFighter >= stCount.MAGE_LEVELS)
{
if(nFighter >= stCount.MONSTER_LEVELS) return "FIGHTER";
else return "MONSTER";
}
else if(stCount.MAGE_LEVELS >= stCount.MONSTER_LEVELS) return "MAGE";
else return "MONSTER";
}
else if(stCount.CLERIC_LEVELS >= stCount.MAGE_LEVELS)
{
if(stCount.CLERIC_LEVELS >= stCount.MONSTER_LEVELS) return "CLERIC";
else return "MONSTER";
}
else if(stCount.MAGE_LEVELS >= stCount.MONSTER_LEVELS) return "MAGE";
else return "MONSTER";
return "";
}
void ai_EquipBestWeapons(object oCreature, object oTarget = OBJECT_INVALID)
{
// Lets not check for weapons on creatures that can't use them!
int nRacialType = GetRacialType(oCreature);
if(nRacialType == RACIAL_TYPE_ANIMAL ||
nRacialType == RACIAL_TYPE_DRAGON ||
nRacialType == RACIAL_TYPE_MAGICAL_BEAST ||
nRacialType == RACIAL_TYPE_OOZE ||
nRacialType == RACIAL_TYPE_VERMIN) return;
//if(Polymorphed()) return;
if(AI_DEBUG) ai_Debug("0i_combat", "2669", GetName(OBJECT_SELF) + " is equiping best weapon!");
// Determine if I am wielding a ranged weapon, melee weapon, or none.
int bIsWieldingRanged = ai_HasRangedWeaponWithAmmo(oCreature);
int bIsWieldingMelee = ai_GetIsMeleeWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND));
if(AI_DEBUG) ai_Debug("0i_combat", "2673", "bIsWieldingRanged: " + IntToString(bIsWieldingRanged) +
" bIsWieldingMelee: " + IntToString(bIsWieldingMelee));
// If we are hidden then change to a melee weapon so we can move in to attack.
if(ai_GetIsHidden(oCreature))
{
// Equip a melee weapon unless we already have one.
if(!bIsWieldingMelee) ai_EquipBestMeleeWeapon(oCreature, oTarget);
return;
}
// Equip the appropriate weapon for the distance of the enemy.
int nEnemyGroup = ai_GetNumOfEnemiesInGroup(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "2684", GetName(oCreature) + " has " + IntToString(nEnemyGroup) + " enemies within 5.0f them! PointBlank: " +
IntToString(GetHasFeat(FEAT_POINT_BLANK_SHOT, oCreature)));
// We are in melee combat.
if(nEnemyGroup > 0)
{
if(bIsWieldingRanged)
{
// We have the point blank shot feat or there are more than one enemy on us.
// Note: Point Blank shot feat is bad once we have more than one enemy on us.
if(!GetHasFeat(FEAT_POINT_BLANK_SHOT, oCreature) || nEnemyGroup > 1)
{
// If I'm not using a melee weapon.
if(!bIsWieldingMelee)
{
ai_EquipBestMeleeWeapon(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "2699", GetName(oCreature) + " is equiping melee weapon due to close enemies!");
}
}
}
}
// We are not in melee range.
else
{
if(AI_DEBUG) ai_Debug("0i_combat", "2707", GetName(oCreature) + " is not in melee combat with an enemy!");
// If are at range with the enemy then equip a ranged weapon.
if(!bIsWieldingRanged)
{
ai_EquipBestRangedWeapon(oTarget);
// Make sure that they equiped a range weapon.
bIsWieldingRanged = ai_HasRangedWeaponWithAmmo(oCreature);
bIsWieldingMelee = ai_GetIsMeleeWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature));
if(AI_DEBUG) ai_Debug("0i_combat", "2719", GetName(oCreature) + " is attempting to equip a ranged weapon: " + IntToString(bIsWieldingRanged));
// If we equiped a ranged weapon then drop out.
}
}
// We don't have a weapon out so equip one! We are in combat!
if(!bIsWieldingRanged && !bIsWieldingMelee) ai_EquipBestMeleeWeapon(OBJECT_INVALID);
}
int ai_EquipBestMeleeWeapon(object oCreature, object oTarget = OBJECT_INVALID)
{
if(ai_GetAIMode(oCreature, AI_MODE_EQUIP_WEAPON_OFF)) return FALSE;
if(AI_DEBUG) ai_Debug("0i_combat", "3049", GetName(oCreature) + " is equiping best melee weapon!");
float fItemPower, fOffItemPower, fRightPower, fLeftPower, f2HandedPower;
int nItemPower, nShieldPower, nShieldValue, nItemValue, nRightValue;
int n2HandedValue, nLeftValue, bTwoWeaponUser;
int nMaxItemValue = ai_GetMaxItemValueThatCanBeEquiped(GetHitDice(oCreature));
if(AI_DEBUG) ai_Debug("0i_combat", "3054", "nMaxItemValue: " + IntToString(nMaxItemValue));
bTwoWeaponUser = GetHasFeat(374/*FEAT_DUAL_WIELD*/, oCreature) || GetHasFeat(FEAT_TWO_WEAPON_FIGHTING, oCreature);
object oShield = OBJECT_INVALID;
object oRight = OBJECT_INVALID;
object oLeft = OBJECT_INVALID;
object o2Handed = OBJECT_INVALID;
object o2HandedHand = OBJECT_INVALID;
object oRightHand = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND);
if(oRightHand != OBJECT_INVALID)
{
// Setup the item in our right hand's avg dmg and gold value as our base.
if(ai_GetIsTwoHandedWeapon(oRightHand, oCreature))
{
if(ai_GetIsDoubleWeapon(oRightHand))
{
f2HandedPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRightHand, TRUE, FALSE, oRightHand);
}
else f2HandedPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRightHand, TRUE);
n2HandedValue = GetGoldPieceValue(oRightHand);
if(AI_DEBUG) ai_Debug("0i_combat", "3073", " 2Handed oRightHand: " + GetName(oRightHand) +
" f2HandPower: " + FloatToString(f2HandedPower, 0, 2) +
" n2HandedValue: " + IntToString(n2HandedValue));
}
else if(ai_GetIsSingleHandedWeapon(oRightHand, oCreature))
{
fRightPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRightHand);
nRightValue = GetGoldPieceValue(oRightHand);
if(AI_DEBUG) ai_Debug("0i_combat", "3081", " 1Handed oRightHand: " + GetName(oRightHand) +
" fRightPower: " + FloatToString(fRightPower, 0, 2) +
" nRightValue: " + IntToString(nRightValue));
}
}
object oLeftHand = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature);
if(oLeftHand != OBJECT_INVALID)
{
// Setup the item in our left hand's Shield AC and gold value as our base.
if(ai_GetIsShield(oLeftHand))
{
nShieldPower = ai_SetShieldAC(oCreature, oLeftHand);
nShieldValue = GetGoldPieceValue(oLeftHand);
if(AI_DEBUG) ai_Debug("0i_combat", "3098", " Shield oLeftHand: " + GetName(oLeftHand) +
" fShieldPower: " + IntToString(nShieldPower) +
" nShieldValue: " + IntToString(nShieldValue));
}
// Setup the item in our left hand's avg dmg and gold value as our base.
else
{
fLeftPower = ai_GetMeleeWeaponAvgDmg(oCreature, oLeftHand, FALSE, TRUE);
nLeftValue = GetGoldPieceValue(oLeftHand);
if(AI_DEBUG) ai_Debug("0i_combat", "3103", " 1Handed oLeftHand: " + GetName(oLeftHand) +
" fLeftPower: " + FloatToString(fLeftPower, 0, 2) +
" nLeftValue: " + IntToString(nLeftValue));
}
}
int nWeaponSize, nType, nCreatureSize = GetCreatureSize(oCreature);
// Get the best weapons they have in their inventory.
object oItem = GetFirstItemInInventory(oCreature);
// If they don't have any items then lets stop, we can't equip a weapon/shield.
if(oItem == OBJECT_INVALID) return FALSE;
while(oItem != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3114", GetName(oItem) + " MeleeWeapon: " +
IntToString(ai_GetIsMeleeWeapon(oItem)) + " Proficient: " +
IntToString(ai_GetIsProficientWith(oCreature, oItem)) +
" Identified: " + IntToString(GetIdentified(oItem)));
if(ai_GetIsProficientWith(oCreature, oItem) &&
GetIdentified(oItem) && ai_CheckIfCanUseItem(oCreature, oItem))
{
nItemValue = GetGoldPieceValue(oItem);
if(AI_DEBUG) ai_Debug("0i_combat", "3122", " nItemValue: " + IntToString(nItemValue));
if(!GetLocalInt(GetModule(), AI_RULE_ILR) || nMaxItemValue >= nItemValue)
{
if(ai_GetIsShield(oItem))
{
nItemPower = ai_SetShieldAC(oCreature, oItem);
if(nItemPower > nShieldPower ||
(nItemPower == nShieldPower && nItemValue > nShieldValue))
{ oShield = oItem; nShieldPower = nItemPower; nShieldValue = nItemValue; }
}
else if(ai_GetIsMeleeWeapon(oItem))
{
// Make sure the creature and weapon are close enough in size.
// Can wield a weapon up to one size larger than their size.
// Can wield a weapon down to two sizes smaller than their size.
nType = GetBaseItemType(oItem);
nWeaponSize = StringToInt(Get2DAString("baseitems", "WeaponSize", nType));
if(nWeaponSize >= nCreatureSize - 2 && nWeaponSize <= nCreatureSize + 1)
{
// Get item avg damage based on if it is 2handed or 1handed.
if(ai_GetIsSingleHandedWeapon(oItem, oCreature))
{
fItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oItem);
fOffItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oItem, FALSE, TRUE);
// If the new weapon is better than the weapon in our right hand.
if(fItemPower > fRightPower ||
(fItemPower == fRightPower && nItemValue > nRightValue))
{
// We need to check if the weapon in the right hand is
// better than the weapon in the left hand since we are
// replacing our right hand weapon.
// Note: we must find out if we have selected a weapon for the
// right hand i.e. oRight or the best weapon is in our
// right hand i.e. oRightHand!
fOffItemPower = 0.0;
if(oRight != OBJECT_INVALID && ai_GetIsSingleHandedWeapon(oRight, oCreature))
{
fOffItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRight, FALSE, TRUE);
}
else if(oRightHand != OBJECT_INVALID && ai_GetIsSingleHandedWeapon(oRightHand, oCreature))
{
fOffItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRightHand, FALSE, TRUE);
}
// If the right hand weapon is better than the weapon in our left hand.
if(fOffItemPower > fLeftPower || (fOffItemPower > 0.0 &&
fOffItemPower == fLeftPower && nRightValue > nLeftValue))
{
if(oRight != OBJECT_INVALID) oLeft = oRight;
else oLeft = oRightHand;
fLeftPower = fOffItemPower;
nLeftValue = nRightValue;
}
oRight = oItem;
fRightPower = fItemPower;
nRightValue = nItemValue;
}
// If the new weapon is better than the weapon in our left hand.
else if(fOffItemPower > fLeftPower ||
(fOffItemPower == fLeftPower && nItemValue > nLeftValue))
{ oLeft = oItem; fLeftPower = fOffItemPower; nLeftValue = nItemValue; }
}
else if(ai_GetIsTwoHandedWeapon(oItem, oCreature))
{
if(ai_GetIsDoubleWeapon(oItem))
{
fItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oItem, TRUE, FALSE, oItem);
}
else fItemPower = ai_GetMeleeWeaponAvgDmg(oCreature, oItem, TRUE);
// If the new weapon is better than the selected weapon.
if(fItemPower > f2HandedPower ||
(fItemPower == f2HandedPower && nItemValue > n2HandedValue))
{
o2Handed = oItem;
f2HandedPower = fItemPower;
n2HandedValue = nItemValue;
}
}
}
}
}
}
oItem = GetNextItemInInventory();
}
if(AI_DEBUG) ai_Debug("0i_combat", "3197", "oRight: " + GetName(oRight) + " oLeft:" +
GetName(oLeft) + " oShield: " + GetName(oShield) +
"o2Handed: " + GetName(o2Handed));
// First check for two weapons first.
if(bTwoWeaponUser && oRight != OBJECT_INVALID && oLeft != OBJECT_INVALID)
{
fRightPower = ai_GetMeleeWeaponAvgDmg(oCreature, oRight, FALSE, FALSE, oLeft);
fRightPower += ai_GetMeleeWeaponAvgDmg(oCreature, oLeft, FALSE, TRUE);
if(AI_DEBUG) ai_Debug("0i_combat", "3205", " Right/Left Power: " +
FloatToString(fRightPower, 0, 2) + " 2HandedPower: " +
FloatToString(f2HandedPower, 0, 2));
if(fRightPower > f2HandedPower)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3210", GetName(oCreature) + " is equiping " +
GetName(oRight) + " in the right hand and " + GetName(oLeft) +
" in the left hand.");
ActionEquipItem(oRight, INVENTORY_SLOT_RIGHTHAND);
ActionEquipItem(oLeft, INVENTORY_SLOT_LEFTHAND);
return TRUE;
}
}
if(f2HandedPower > fRightPower && o2Handed != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3220", GetName(oCreature) + " is equiping " +
GetName(o2Handed) + " in both hands.");
ActionEquipItem(o2Handed, INVENTORY_SLOT_RIGHTHAND);
return TRUE;
}
// Now lets just equip the best weapon for the right hand.
if(oRight != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3228", GetName(oCreature) + " is equiping " +
GetName(oRight) + " in the right hand. ");
ActionEquipItem(oRight, INVENTORY_SLOT_RIGHTHAND);
}
// Make sure we are not equiping a 2handed weapon and
// If not can we equip a shield?
if((oRight == OBJECT_INVALID || ai_GetIsSingleHandedWeapon(oRight, oCreature) ||
!ai_GetIsTwoHandedWeapon(oRightHand, oCreature)) &&
oShield != OBJECT_INVALID && GetHasFeat(FEAT_SHIELD_PROFICIENCY, oCreature))
{
if(AI_DEBUG) ai_Debug("0i_combat", "3238", GetName(oCreature) + " is equiping " +
GetName(oShield) + " in the left hand.");
ActionEquipItem(oShield, INVENTORY_SLOT_LEFTHAND);
return TRUE;
}
// Finally if we don't have a weapon to equip so check to see if we are
// holding a bow.
else if(oRight == OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3247", GetName(oCreature) + " did not equip a melee weapon");
// We couldn't find a melee weapon but we are looking to go into melee
// I'm holding a ranged weapon! We better put it up.
if(GetWeaponRanged(oRightHand))
{
if(AI_DEBUG) ai_Debug("0i_combat", "3252", GetName(oCreature) + " is unequiping " + GetName(oRightHand));
ActionUnequipItem(oRightHand);
return TRUE;
}
}
if(AI_DEBUG) ai_Debug("0i_combat", "3257", GetName(oCreature) + " is not equiping a weapon!");
return FALSE;
}
int ai_EquipBestRangedWeapon(object oCreature, object oTarget = OBJECT_INVALID)
{
if(ai_GetAIMode(oCreature, AI_MODE_EQUIP_WEAPON_OFF)) return FALSE;
if(AI_DEBUG) ai_Debug("0i_combat", "3267", GetName(oCreature) + " is looking for best ranged weapon!");
int nAmmo, nAmmoSlot, nBestType1, nBestType2, nType, nFeat, nItemValue, nRangedValue;
int nMaxItemValue = ai_GetMaxItemValueThatCanBeEquiped(GetHitDice(oCreature));
string sAmmo;
object oRightHand = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
if(oRightHand != OBJECT_INVALID && ai_GetIsRangeWeapon(oRightHand))
{
// Setup the item in our right hand as our base gold value to check against.
if(ai_GetIsRangeWeapon(oRightHand)) nRangedValue = GetGoldPieceValue(oRightHand);
}
object oRanged = OBJECT_INVALID, oAmmo = OBJECT_INVALID;
// Find the best type of ranged weapon for this player.
if(GetHasFeat(FEAT_WEAPON_FOCUS_LONGBOW, oCreature))
{ nBestType1 = BASE_ITEM_LONGBOW; nAmmo = BASE_ITEM_ARROW; nAmmoSlot = INVENTORY_SLOT_ARROWS; sAmmo = "arrow";}
else if(GetHasFeat(FEAT_WEAPON_FOCUS_SHORTBOW, oCreature))
{ nBestType1 = BASE_ITEM_SHORTBOW; nAmmo = BASE_ITEM_ARROW; nAmmoSlot = INVENTORY_SLOT_ARROWS; sAmmo = "arrow";}
else if(GetHasFeat(FEAT_WEAPON_FOCUS_HEAVY_CROSSBOW, oCreature))
{ nBestType1 = BASE_ITEM_HEAVYCROSSBOW; nAmmo = BASE_ITEM_BOLT; nAmmoSlot = INVENTORY_SLOT_BOLTS; sAmmo = "bolt";}
else if(GetHasFeat(FEAT_WEAPON_FOCUS_LIGHT_CROSSBOW, oCreature))
{ nBestType1 = BASE_ITEM_LIGHTCROSSBOW; nAmmo = BASE_ITEM_BOLT; nAmmoSlot = INVENTORY_SLOT_BOLTS; sAmmo = "bolt";}
else if(GetHasFeat(FEAT_WEAPON_FOCUS_SLING, oCreature))
{ nBestType1 = BASE_ITEM_SLING; nAmmo = BASE_ITEM_BULLET; nAmmoSlot = INVENTORY_SLOT_BULLETS; sAmmo = "bullet";}
else if(GetHasFeat(FEAT_WEAPON_FOCUS_DART, oCreature))
{ nBestType1 = BASE_ITEM_DART; }
else if(GetHasFeat(FEAT_WEAPON_FOCUS_SHURIKEN, oCreature))
{ nBestType1 = BASE_ITEM_SHURIKEN; }
else if(GetHasFeat(FEAT_WEAPON_FOCUS_THROWING_AXE, oCreature))
{ nBestType1 = BASE_ITEM_THROWINGAXE; }
// These feats require a bow.
else if(GetHasFeat(FEAT_RAPID_SHOT, oCreature))
{ nBestType1 = BASE_ITEM_LONGBOW; nBestType2 = BASE_ITEM_SHORTBOW;
nAmmo = BASE_ITEM_ARROW; nAmmoSlot = INVENTORY_SLOT_ARROWS; sAmmo = "arrow"; }
// This feat requires a xbow.
else if(GetHasFeat(FEAT_RAPID_RELOAD, oCreature))
{ nBestType1 = BASE_ITEM_HEAVYCROSSBOW; nBestType2 = BASE_ITEM_LIGHTCROSSBOW;
nAmmo = BASE_ITEM_BOLT; nAmmoSlot = INVENTORY_SLOT_BOLTS; sAmmo = "bolt"; }
if(AI_DEBUG) ai_Debug("0i_combat", "3262", "nBestType1: " + IntToString(nBestType1) + " nBestType2: " + IntToString(nBestType2) +
" nAmmo: " + IntToString(nAmmo));
int nCreatureSize = GetCreatureSize(oCreature) + 1;
// Cycle through the inventory looking for a ranged weapon.
object oItem = GetFirstItemInInventory(oCreature);
while(oItem != OBJECT_INVALID)
{
nType = GetBaseItemType(oItem);
if(AI_DEBUG) ai_Debug("0i_combat", "3269", "oItem: " + GetName(oItem) +
" Identified: " + IntToString(GetIdentified(oItem)) +
" Ranged Weapon: " + Get2DAString("baseitems", "RangedWeapon", nType));
// Make sure it is identified and it is a ranged weapon.
if(GetIdentified(oItem) && Get2DAString("baseitems", "RangedWeapon", nType) != "")
{
if(AI_DEBUG) ai_Debug("0i_combat", "3278", " Proficient: " +
IntToString(ai_GetIsProficientWith(oCreature, oItem)) +
" nMaxItemValue: " + IntToString(nMaxItemValue));
if(ai_GetIsProficientWith(oCreature, oItem))
{
if(ai_CheckIfCanUseItem(oCreature, oItem))
{
nItemValue = GetGoldPieceValue(oItem);
if(AI_DEBUG) ai_Debug("0i_combat", "3284", "nItemValue: " + IntToString(nItemValue));
if(!GetLocalInt(GetModule(), AI_RULE_ILR) || nMaxItemValue >= nItemValue)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3287", " Creature Size: " + IntToString(nCreatureSize) +
" Weapon Size: " + Get2DAString("baseitems", "WeaponSize", nType));
// Make sure they are large enough to use it.
if(StringToInt(Get2DAString("baseitems", "WeaponSize", nType)) <= nCreatureSize)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3292", "nItemValue: " + IntToString(nItemValue) +
" nRangedValue: " + IntToString(nRangedValue) + " nType: " + IntToString(nType));
// Is it of the best range weapon type? 0 is any range weapon.
// Also grab any range weapon until we have a best type.
if(nType == nBestType1 || nType == nBestType2 ||
nBestType1 == 0 || oRanged == OBJECT_INVALID)
{
if(nItemValue > nRangedValue)
{
if(ai_GetHasItemProperty(oItem, ITEM_PROPERTY_UNLIMITED_AMMUNITION))
{
oRanged = oItem; nRangedValue = nItemValue;
if(AI_DEBUG) ai_Debug("0i_combat", "3304", "Selecting oRanged: " + GetName(oRanged) +
" nRangedValue: " + IntToString(nRangedValue) + " and doesn't need ammo!");
}
else
{
if(nBestType1 == 0)
{
if(nType == BASE_ITEM_LONGBOW || nType == BASE_ITEM_SHORTBOW)
{ nAmmo = BASE_ITEM_ARROW; sAmmo = "arrow"; nAmmoSlot = INVENTORY_SLOT_ARROWS; }
else if(nType == BASE_ITEM_HEAVYCROSSBOW || nType == BASE_ITEM_LIGHTCROSSBOW)
{ nAmmo = BASE_ITEM_BOLT; sAmmo = "bolt"; nAmmoSlot = INVENTORY_SLOT_BOLTS; }
else if(nType == BASE_ITEM_SLING)
{ nAmmo = BASE_ITEM_BULLET; sAmmo = "bullet"; nAmmoSlot = INVENTORY_SLOT_BULLETS; }
else nAmmo = 0;
}
// Now do we have ammo for it?
if(AI_DEBUG) ai_Debug("0i_combat", "3320", "nAmmo: " + IntToString(nAmmo));
if(nAmmo > 0)
{
if(nAmmo == BASE_ITEM_ARROW ||
nAmmo == BASE_ITEM_BOLT ||
nAmmo == BASE_ITEM_BULLET) oAmmo = GetItemInSlot(nAmmoSlot);
if(oAmmo == OBJECT_INVALID)
{
// We don't have ammo equiped so lets see if we have any in our inventory.
oAmmo = GetFirstItemInInventory();
while(oAmmo != OBJECT_INVALID)
{
if(GetBaseItemType(oAmmo) == nAmmo) break;
oAmmo = GetNextItemInInventory();
}
if(oAmmo != OBJECT_INVALID) ActionEquipItem(oAmmo, nAmmoSlot);
}
}
if(oAmmo != OBJECT_INVALID)
{
oRanged = oItem; nRangedValue = nItemValue;
if(AI_DEBUG) ai_Debug("0i_combat", "3307", "Selecting oRanged: " + GetName(oRanged) +
" nRangedValue: " + IntToString(nRangedValue));
}
}
}
}
}
}
}
}
}
oItem = GetNextItemInInventory(oCreature);
}
// They don't have a range weapon so lets break out.
if(oRanged == OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3357", GetName(oCreature) + " did not equip a ranged weapon!");
return FALSE;
}
ActionEquipItem(oRanged, INVENTORY_SLOT_RIGHTHAND);
return TRUE;
}
int ai_EquipBestMonkMeleeWeapon(object oCreature, object oTarget = OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "2949", GetName(OBJECT_SELF) + " is equiping best monk melee weapon!");
int nValue, nRightValue;
int nMaxItemValue = ai_GetMaxItemValueThatCanBeEquiped(GetHitDice(oCreature));
object oRight = OBJECT_INVALID;
object oRightHand = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature);
if(oRightHand != OBJECT_INVALID)
{
nRightValue = GetGoldPieceValue(oRightHand);
}
// Get the best kama they have in their inventory.
object oItem = GetFirstItemInInventory(oCreature);
// If they don't have any kamas then lets stop, we can't equip a weapon.
if(oItem == OBJECT_INVALID) return FALSE;
while(oItem != OBJECT_INVALID)
{
nValue = GetGoldPieceValue(oItem);
// Make sure they are high enough level to equip this item.
if(nMaxItemValue >= nValue && nValue > 1)
{
// Is it a single handed weapon?
if(GetBaseItemType(oItem) == BASE_ITEM_KAMA)
{
// Replace the lowest value right weapon.
if(nValue > nRightValue)
{
oRight = oItem; nRightValue = nValue;
}
}
}
oItem = GetNextItemInInventory(oCreature);
}
// Finally lets just equip the kama if we have one.
if(oRight == OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "2983", GetName(oCreature) + " did not equip a melee weapon!");
return FALSE;
}
if(AI_DEBUG) ai_Debug("0i_combat", "2986", GetName(oCreature) + " is equiping " + GetName(oRight) + " in the right hand.");
ActionEquipItem(oRight, INVENTORY_SLOT_RIGHTHAND);
return TRUE;
}
int ai_IsInADangerousAOE(object oCreature, float fMaxRange = AI_RANGE_BATTLEFIELD, int bMove = FALSE)
{
int bDangerous, nSpell, nCnt = 1;
string sAOEType;
object oAOE = GetNearestObject(OBJECT_TYPE_AREA_OF_EFFECT, oCreature, nCnt);
float fRadius, fDistance = GetDistanceBetween(oCreature, oAOE);
while(oAOE != OBJECT_INVALID && fDistance <= fMaxRange)
{
// AOE's have the tag set to the "LABEL" in vfx_persistent.2da
// I check vs those labels to see if the AOE is offensive.
// Below is the list of Offensive AOE effects.
sAOEType = GetTag(oAOE);
if(sAOEType == "VFX_PER_WEB") { fRadius = 6.7; nSpell = SPELL_WEB; }
else if(sAOEType == "VFX_PER_ENTANGLE") { fRadius = 5.0; nSpell = SPELL_ENTANGLE; }
else if(sAOEType == "VFX_PER_GREASE") { fRadius = 6.0; nSpell = SPELL_GREASE; }
else if(sAOEType == "VFX_PER_EVARDS_BLACK_TENTACLES")
{ fRadius = 5.0; nSpell = SPELL_EVARDS_BLACK_TENTACLES; }
//else if(sAOEType == "VFX_PER_DARKNESS") { fRadius = 6.7; nSpell = SPELL_DARKNESS; }
//else if(sAOEType == "VFX_MOB_SILENCE") { fRadius = 4.0; nSpell = SPELL_SILENCE; }
else if(sAOEType == "VFX_PER_FOGSTINK") { fRadius = 6.7; nSpell = SPELL_STINKING_CLOUD; }
else if(sAOEType == "VFX_PER_FOGFIRE") { fRadius = 5.0; nSpell = SPELL_INCENDIARY_CLOUD; }
else if(sAOEType == "VFX_PER_FOGKILL") { fRadius = 5.0; nSpell = SPELL_CLOUDKILL; }
else if(sAOEType == "VFX_PER_FOGMIND") { fRadius = 5.0; nSpell = SPELL_MIND_FOG; }
else if(sAOEType == "VFX_PER_CREEPING_DOOM") { fRadius = 6.7; nSpell = SPELL_CREEPING_DOOM; }
else if(sAOEType == "VFX_PER_FOGACID") { fRadius = 5.0; nSpell = SPELL_ACID_FOG; }
else if(sAOEType == "VFX_PER_FOGBEWILDERMENT") { fRadius = 5.0; nSpell = SPELL_CLOUD_OF_BEWILDERMENT; }
else if(sAOEType == "VFX_PER_WALLFIRE") { fRadius = 10.0; nSpell = SPELL_WALL_OF_FIRE; }
else if(sAOEType == "VFX_PER_WALLBLADE") { fRadius = 10.0; nSpell = SPELL_BLADE_BARRIER; }
else if(sAOEType == "VFX_PER_DELAY_BLAST_FIREBALL") { fRadius = 2.0; nSpell = SPELL_DELAYED_BLAST_FIREBALL; }
else if(sAOEType == "VFX_PER_GLYPH") { fRadius = 2.5; nSpell = SPELL_GLYPH_OF_WARDING; }
else fRadius = 0.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3088", GetName(oCreature) + " distance from AOE is " + FloatToString(fDistance, 0, 2) +
" AOE Radius: " + FloatToString(fRadius, 0, 2) +
" AOE Type: " + GetTag(oAOE));
// fRadius > 0.0 keeps them from tiggering that they are in a dangerous
// AOE due to having an AOE on them.
if(fRadius > 0.0 && fDistance <= fRadius &&
!ai_CreatureImmuneToEffect(GetAreaOfEffectCreator(oAOE), oCreature, nSpell))
{
bDangerous = TRUE;
if(nSpell == SPELL_WEB || nSpell == SPELL_ENTANGLE)
{
if(ai_HasRangedWeaponWithAmmo(oCreature)) bDangerous = FALSE;
if(GetReflexSavingThrow(oCreature) + GetAbilityModifier(ABILITY_DEXTERITY, oCreature) >= ai_GetCharacterLevels(oCreature))
bDangerous = FALSE;
}
break;
}
oAOE = GetNearestObject(OBJECT_TYPE_AREA_OF_EFFECT, oCreature, ++nCnt);
fDistance = GetDistanceBetween(oCreature, oAOE);
}
if(bDangerous && bMove)
{
location lLocation;
object oTarget;
if(ai_GetIsInCombat(oCreature))
{
object oMaster = GetMaster(oCreature);
// If we have a ranged weapon then backout and use that.
if(ai_HasRangedWeaponWithAmmo(oCreature))
{
lLocation = GetRandomLocation(GetArea(oCreature), oCreature, fRadius + 1.0);
}
else // we must find a target out of the AOE or fight in the AOE.
{
oTarget = ai_GetNearestTargetNotInAOE(oCreature, AI_RANGE_PERCEPTION, AI_ENEMY, TRUE);
if(oTarget != OBJECT_INVALID) lLocation = GetLocation(oTarget);
}
}
else lLocation = GetRandomLocation(GetArea(oCreature), oCreature, fRadius + 1.0);
ai_ClearCreatureActions();
if(AI_DEBUG) ai_Debug("0i_combat", "3035", GetName(oCreature) + " is moving out of area of effect!");
ActionMoveToLocation(lLocation, TRUE);
return TRUE;
}
else if(bDangerous) return TRUE;
return FALSE;
}
int ai_GetIsHidden(object oHidden)
{
int nEffectType;
effect eEffect = GetFirstEffect(oHidden);
while(GetIsEffectValid(eEffect))
{
nEffectType = GetEffectType(eEffect);
if(nEffectType == EFFECT_TYPE_INVISIBILITY) return 1;
else if(nEffectType == EFFECT_TYPE_IMPROVEDINVISIBILITY) return 1;
else if(nEffectType == EFFECT_TYPE_DARKNESS) return 2;
else if(nEffectType == EFFECT_TYPE_SANCTUARY) return 3;
else if(nEffectType == EFFECT_TYPE_ETHEREAL) return 3;
eEffect = GetNextEffect(oHidden);
}
if(GetActionMode(oHidden, ACTION_MODE_STEALTH)) return 4;
return FALSE;
}
int ai_CastOffensiveSpellVsTarget(object oCaster, object oCreature, int nSpell)
{
// Check saves.
string sSave = Get2DAString("ai_spells", "SaveType", nSpell);
// There is no save!
if(sSave == "") return TRUE;
// Get the level of the spell.
int nSpellLvl = StringToInt(Get2DAString("spells", "Innate", nSpell));
// Randomize our check.
nSpellLvl += Random(AI_SPELL_CHECK_DIE) + AI_SPELL_CHECK_BONUS;
// Check feats that might increase our DC.
string sSchool = Get2DAString("spells", "School", nSpell);
if(sSchool == "V")
{
if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_EVOCATION, oCaster)) nSpellLvl += 4;
else if(GetHasFeat(FEAT_SPELL_FOCUS_EVOCATION, oCaster)) nSpellLvl += 2;
}
else if(sSchool == "C")
{
if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_CONJURATION, oCaster)) nSpellLvl += 4;
else if(GetHasFeat(FEAT_SPELL_FOCUS_CONJURATION, oCaster)) nSpellLvl += 2;
}
else if(sSchool == "N")
{
if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_NECROMANCY, oCaster)) nSpellLvl += 4;
else if(GetHasFeat(FEAT_SPELL_FOCUS_NECROMANCY, oCaster)) nSpellLvl += 2;
}
else if(sSchool == "E")
{
if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ENCHANTMENT, oCaster)) nSpellLvl += 4;
else if(GetHasFeat(FEAT_SPELL_FOCUS_ENCHANTMENT, oCaster)) nSpellLvl += 2;
}
else if(sSchool == "I")
{
if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ILLUSION, oCaster)) nSpellLvl += 4;
else if(GetHasFeat(FEAT_SPELL_FOCUS_ILLUSION, oCaster)) nSpellLvl += 2;
}
else if(sSave == "Reflex")
{
string sImmunityType = Get2DAString("ai_spells", "ImmunityType", nSpell);
// Give a bonus to our check for half dmg spells unless they can dodge it!
if((sImmunityType == "Fire" || sImmunityType == "Electricity" || sImmunityType == "Acid" ||
sImmunityType == "Cold" || sImmunityType == "Sonic") &&
!GetHasFeat(FEAT_IMPROVED_EVASION, oCreature)) nSpellLvl += AI_SPELL_CHECK_NO_EVASION_BONUS;
if(AI_DEBUG) ai_Debug("0i_combat", "3050", " nSpellLvl: " + IntToString(nSpellLvl) +
" > nMagic: " + IntToString(GetReflexSavingThrow(oCreature)));
return (nSpellLvl > GetReflexSavingThrow(oCreature));
}
else if(sSave == "Fortitude") return (nSpellLvl > GetFortitudeSavingThrow(oCreature));
else if(sSave == "Will") return (nSpellLvl > GetWillSavingThrow(oCreature));
return TRUE;
}
int ai_GetDragonDC(object oCreature)
{
int nDC, nHitDice = GetHitDice(oCreature);
if(nHitDice < 4) { nDC = 12; }
else if(nHitDice < 7) { nDC = 13; }
else if(nHitDice < 10) { nDC = 14; }
else if(nHitDice < 13) { nDC = 16; }
else if(nHitDice < 16) { nDC = 18; }
else if(nHitDice < 19) { nDC = 20; }
else if(nHitDice < 22) { nDC = 22; }
else if(nHitDice < 25) { nDC = 24; }
else if(nHitDice < 28) { nDC = 26; }
else if(nHitDice < 31) { nDC = 28; }
else if(nHitDice < 34) { nDC = 30; }
else if(nHitDice < 37) { nDC = 32; }
else if(nHitDice < 39) { nDC = 34; }
else { nDC = 36; }
string sTag = GetTag(oCreature);
if(sTag == "gold_dragon") nDC += 5;
if(sTag == "red_dragon" || sTag == "silver_dragon") return nDC + 4;
else if(sTag == "black_dragon" || sTag == "brass_dragon") return nDC + 3;
else if(sTag == "green_dragon" || sTag == "copper_dragon") return nDC + 2;
else if(sTag == "blue_dragon" || sTag == "bronze_dragon") return nDC + 1;
//else if(sTag == "white_dragon") nDC += 0;
return nDC;
}
void ai_SetCreatureAIScript(object oCreature)
{
string sCombatAI = GetLocalString(oCreature, AI_DEFAULT_SCRIPT);
// Non-Hostile NPC's do not need to use special tactics by default.
if(sCombatAI == "" && GetLocalInt(GetModule(), AI_RULE_AMBUSH) && d100() < 34)
{
// They should have skill ranks equal to their level + 1 to use a special AI.
int nSkillNeeded = GetHitDice(oCreature) + 1;
/*/ Ambusher: requires either Improved Invisibility or Invisibility.
if(GetHasSpell(SPELL_IMPROVED_INVISIBILITY, oCreature) ||
GetHasSpell(SPELL_INVISIBILITY, oCreature))
{
int bCast = ai_TryToCastSpell(oCreature, SPELL_IMPROVED_INVISIBILITY, oCreature);
if(!bCast) bCast = ai_TryToCastSpell(oCreature, SPELL_INVISIBILITY, oCreature);
if(bCast) sCombatAI = "ai_ambusher";
} */
if(GetHasFeat(FEAT_SNEAK_ATTACK, oCreature, TRUE))
{
sCombatAI = "ai_flanker";
}
// Ambusher: Requires a Hide and Move silently skill equal to your level + 1.
else if(GetSkillRank(SKILL_HIDE, oCreature) >= nSkillNeeded &&
GetSkillRank(SKILL_MOVE_SILENTLY, oCreature) >= nSkillNeeded)
{
sCombatAI = "ai_ambusher";
}
// Defensive : requires Parry skill equal to your level or Expertise.
else if(GetSkillRank(SKILL_PARRY, oCreature) >= nSkillNeeded ||
GetHasFeat(FEAT_EXPERTISE, oCreature) ||
GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature))
{
sCombatAI = "ai_defensive";
}
else if(GetHasSpell(SPELL_LESSER_DISPEL, oCreature) ||
GetHasSpell(SPELL_DISPEL_MAGIC, oCreature) || GetHasSpell(SPELL_GREATER_DISPELLING, oCreature))
{
sCombatAI = "ai_cntrspell";
}
else if(ai_CheckClassType(oCreature, AI_CLASS_TYPE_ARCANE) &&
ai_GetCharacterLevels(oCreature) > 4) sCombatAI = "ai_ranged";
else if(ai_EquipBestRangedWeapon(oCreature)) sCombatAI = "ai_ranged";
else if(GetSkillRank(SKILL_TAUNT, oCreature) >= nSkillNeeded) sCombatAI = "ai_taunter";
}
if(sCombatAI == "")
{
int nAssociateType = GetAssociateType(oCreature);
if (nAssociateType == ASSOCIATE_TYPE_FAMILIAR)
{
sCombatAI = "ai_default";
}
else
{
// Select the best ai for this henchmen based on class.
int nClass = GetClassByPosition(1, oCreature);
// If they have more than one class use the default ai.
if(GetClassByPosition(2, oCreature) != CLASS_TYPE_INVALID) sCombatAI = "ai_default";
else if(nClass == CLASS_TYPE_BARBARIAN) sCombatAI = "ai_barbarian";
else if(nClass == CLASS_TYPE_BARD) sCombatAI = "ai_bard";
else if(nClass == CLASS_TYPE_CLERIC) sCombatAI = "ai_cleric";
else if(nClass == CLASS_TYPE_DRUID) sCombatAI = "ai_druid";
else if(nClass == CLASS_TYPE_FIGHTER) sCombatAI = "ai_fighter";
else if(nClass == CLASS_TYPE_MONK) sCombatAI = "ai_monk";
else if(nClass == CLASS_TYPE_PALADIN) sCombatAI = "ai_paladin";
else if(nClass == CLASS_TYPE_RANGER) sCombatAI = "ai_ranger";
else if(nClass == CLASS_TYPE_ROGUE) sCombatAI = "ai_rogue";
else if(nClass == CLASS_TYPE_SORCERER) sCombatAI = "ai_sorcerer";
else if(nClass == CLASS_TYPE_WIZARD) sCombatAI = "ai_wizard";
//else if(nClass == CLASS_TYPE_ABERRATION) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_ANIMAL) sCombatAI = "ai_animal";
//else if(nClass == CLASS_TYPE_CONSTRUCT) sCombatAI = "ai_animal";
else if(nClass == CLASS_TYPE_DRAGON) sCombatAI = "ai_dragon";
//else if(nClass == CLASS_TYPE_ELEMENTAL) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_FEY) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_GIANT) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_HUMANOID) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_MAGICAL_BEAST) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_MONSTROUS) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_OOZE) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_OUTSIDER) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_UNDEAD) sCombatAI = "ai_default";
//else if(nClass == CLASS_TYPE_VERMIN) sCombatAI = "ai_animal";
else sCombatAI = "ai_default";
}
}
if(AI_DEBUG) ai_Debug("0i_combat", "3740", GetName(oCreature) + " is setting AI to " + sCombatAI);
SetLocalString(oCreature, AI_DEFAULT_SCRIPT, sCombatAI);
SetLocalString(oCreature, AI_COMBAT_SCRIPT, sCombatAI);
}
int ai_IsImmuneToSneakAttacks(object oCreature, object oTarget)
{
if(GetHasFeat(FEAT_UNCANNY_DODGE_2, oTarget) &&
GetLevelByClass(CLASS_TYPE_ROGUE, oCreature) + 3 < GetLevelByClass(CLASS_TYPE_ROGUE, oTarget)) return TRUE;
if(GetIsImmune(oTarget, IMMUNITY_TYPE_SNEAK_ATTACK)) return TRUE;
object oSkin = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTarget);
if(ai_GetHasItemProperty(oSkin, ITEM_PROPERTY_IMMUNITY_MISCELLANEOUS, IP_CONST_IMMUNITYMISC_BACKSTAB)) return TRUE;
return FALSE;
}
int ai_IsStrongerThanMe(object oCreature, int nIndex)
{
int nEnemyCombat = GetLocalInt(oCreature, AI_ENEMY_COMBAT + IntToString(nIndex));
int nCreatureCombat = ai_GetMyCombatRating(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "3955", "IsStrongerThanMe: nCreatureCombat: " +
IntToString(nCreatureCombat) + " nEnemyCombat: " + IntToString(nEnemyCombat));
return (nEnemyCombat > nCreatureCombat);
}
int ai_StrongOpponent(object oCreature, object oTarget, int nAdj = 2)
{
int nLevel = GetHitDice(oCreature);
if(AI_DEBUG) ai_Debug("0i_combat", "3220", "ai_StrongOpponent");
nAdj = nAdj *((nAdj + nLevel) / 10);
if(AI_DEBUG) ai_Debug("0i_combat", "3222", "Is the opponent strong? Target CR >= Our level - nAdj(" +
FloatToString(GetChallengeRating(oTarget), 0, 2) + " >= " + IntToString(nLevel - nAdj) + ")");
return (FloatToInt(GetChallengeRating(oTarget)) >= nLevel - nAdj);
}
int ai_PowerAttackGood(object oCreature, object oTarget, float fAdj)
{
int nAvgDmg = ai_GetWeaponDamage(oCreature, 2);
if(AI_DEBUG) ai_Debug("0i_combat", "3412", "PowerAttack: (nAvgDmg: " + IntToString(nAvgDmg) +
" > Target HP: " + IntToString(GetCurrentHitPoints(oTarget)) +
") Skip: " + IntToString(nAvgDmg > GetCurrentHitPoints(oTarget)));
if(nAvgDmg > GetCurrentHitPoints(oTarget)) return FALSE;
float fAvgDmg = IntToFloat(nAvgDmg);
float fTargetAC = IntToFloat(GetAC(oTarget));
float fCreatureAtk = IntToFloat(ai_GetCreatureAttackBonus(oCreature));
float fNormalChance = (21.0 - (fTargetAC - fCreatureAtk)) / 20.0;
// Our chance to hit is already minimum of 5% so this doesn't hurt our chance!
if(fNormalChance <= 0.05) return TRUE;
float fAdjDamage = (fAvgDmg + fAdj) * ((21.0-(fTargetAC - fCreatureAtk + fAdj))/20);
if(AI_DEBUG) ai_Debug("0i_combat", "3420", "fNormalDamage: " + FloatToString(fNormalChance * fAvgDmg, 0, 2) +
" < fAdjDamage: " + FloatToString(fAdjDamage, 0, 2) + " = " + IntToString(fNormalChance * fAvgDmg < fAdjDamage));
return fNormalChance * fAvgDmg < fAdjDamage;
}
int ai_AttackPenaltyOk(object oCreature, object oTarget, float fAtkAdj)
{
float fTargetAC = IntToFloat(GetAC(oTarget));
float fCreatureAtk = IntToFloat(ai_GetCreatureAttackBonus(oCreature));
float fNormalChance = (21.0-(fTargetAC - fCreatureAtk))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3431", "Normal Avg Chance: " + FloatToString(fNormalChance, 0, 2) + " <= 0.05");
// We already need a 20 to hit so this doesn't hurt our chances!
if(fNormalChance <= 0.05) return TRUE;
float fAdjChance = (21.0-(fTargetAC - fCreatureAtk + fAtkAdj))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3435", "Adjusted Avg Chance: " + FloatToString(fAdjChance, 0, 2) + " > 0.55");
// if our chance is 55% or better to hit then use it.
return fAdjChance > 0.55;
}
int ai_AttackBonusGood(object oCreature, object oTarget, float fAtkAdj)
{
float fTargetAC = IntToFloat(GetAC(oTarget));
float fCreatureAtk = IntToFloat(ai_GetCreatureAttackBonus(oCreature));
float fNormalChance = (21.0-(fTargetAC - fCreatureAtk))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3450", "Normal Avg Chance: " + FloatToString(fNormalChance, 0, 2) + " > 0.99");
// We already hit them with any roll so this will not help.
if(fNormalChance > 0.99) return FALSE;
float fAdjChance = (21.0-(fTargetAC - fCreatureAtk - fAtkAdj))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3454", "Adjusted Avg Chance: " + FloatToString(fAdjChance, 0, 2) + " < 0.0");
// if our chance increases our to hit then this is good.
return fAdjChance > 0.0;
}
int ai_ACAdjustmentGood(object oCreature, object oTarget, float fACAdj)
{
float fCreatureAC = IntToFloat(GetAC(oCreature));
float fTargetAtk = IntToFloat(ai_GetCreatureAttackBonus(oTarget));
float fNormalChance = (21.0-(fCreatureAC - fTargetAtk))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3444", "Normal Chance To Hit: " + FloatToString(fNormalChance, 0, 2) + " <= 0.05");
// They already need a 20 to hit so adding more AC is worthless.
if(fNormalChance <= 0.05) return FALSE;
float fAdjChance = (21.0-(fCreatureAC - fTargetAtk + fACAdj))/20.0;
if(AI_DEBUG) ai_Debug("0i_combat", "3448", "Adjusted Chance To Hit: " + FloatToString(fAdjChance, 0, 2) + " < 1.00");
// Anything less than 1 helps are AC!
return fAdjChance < 1.00;
}
int ai_CanIMoveInCombat(object oCreature)
{
// DC 15 tumble check is required to not give attacks of opportunity.
return (GetHasFeat(FEAT_MOBILITY, oCreature) || GetHasFeat(FEAT_SPRING_ATTACK, oCreature) ||
GetSkillRank(SKILL_TUMBLE, oCreature) > 9);
}
int ai_CanIUseRangedWeapon(object oCreature, int nInMelee)
{
return (!nInMelee || ai_GetEnemyAttackingMe(oCreature) == OBJECT_INVALID);
}
int ai_CheckRangedCombatPosition(object oCreature, object oTarget, int nAction)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3559", "Ranged attack: See oTarget? " +
IntToString(GetObjectSeen(oTarget, oCreature)) + " Line of Sight? " +
IntToString(LineOfSightObject(oCreature, oTarget)));
if(nAction == AI_LAST_ACTION_RANGED_ATK)
{
// Watch the nearest enemy instead of our target as they could move toward us.
object oNearestEnemy = GetLocalObject(oCreature, AI_ENEMY_NEAREST);
float fDistance = GetDistanceBetween(oCreature, oNearestEnemy);
if(AI_DEBUG) ai_Debug("0i_combat", "3337", "oNearestEnemy: " + GetName(oNearestEnemy) +
" fDistance: " + FloatToString(fDistance, 0, 2));
// If we have sneak attack then we want to be within 30'.
if(GetHasFeat(FEAT_SNEAK_ATTACK, oCreature))
{
if(fDistance > AI_RANGE_CLOSE)
{
// We check this because if the enemy is moving or has not
// started acting then we don't want to move up on them as they
// might move towards us. Just attack! Only sneak attack if they are busy.
int nAction = GetCurrentAction(oNearestEnemy);
if(AI_DEBUG) ai_Debug("0i_combat", "3353", GetName(oNearestEnemy) + " current action: " + IntToString(nAction));
if(nAction == ACTION_MOVETOPOINT ||
nAction == ACTION_INVALID ||
nAction == ACTION_RANDOMWALK) return FALSE;
// If they are attacking make sure it is in melee?
// If not then don't move since they might be moving toward us.
if(nAction == ACTION_ATTACKOBJECT)
{
if(!ai_GetNumOfEnemiesInRange(oNearestEnemy)) return FALSE;
}
if(AI_DEBUG) ai_Debug("0i_combat", "3355", GetName(oCreature) + " is moving closer [8.0] to " +
GetName(oNearestEnemy) + " to sneak attack with a ranged weapon.");
ai_SetLastAction(oCreature, AI_LAST_ACTION_MOVE);
ActionMoveToObject(oNearestEnemy, TRUE, AI_RANGE_CLOSE);
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
return TRUE;
}
}
else if(fDistance < AI_RANGE_LONG)
{
// Lets move back a little, too far and we miss attacks!
if(AI_DEBUG) ai_Debug("0i_combat", "3374", GetName(oCreature) + " is moving away from " +
GetName(oNearestEnemy) + "[2.0] to use a ranged weapon.");
ai_SetLastAction(oCreature, AI_LAST_ACTION_MOVE);
ActionMoveAwayFromObject(oNearestEnemy, TRUE, 2.0);
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
return TRUE;
}
}
// If we are casting a hostile spell then check positioning.
else if(nAction > -1 && Get2DAString("ai_spells", "HostileSetting", nAction) == "1")
{
// We are out of melee and casting a spell on an ally so don't move.
if(GetReputation(oCreature, oTarget) > 89) return FALSE;
float fSpellRange = ai_GetSpellRange(nAction);
float fTargetRange = GetDistanceBetween(oCreature, oTarget);
if(AI_DEBUG) ai_Debug("0i_combat", "3389", "fSpellRange: " + FloatToString(fSpellRange, 0, 2) +
" fTargetRange: " + FloatToString(fTargetRange, 0, 2));
// Adjust the ranges to see if we are too close.
if(fSpellRange == 5.0) fSpellRange = 4.5f;
//else if(fSpellRange == 8.0) fSpellRange = 8.0f;
else if(fSpellRange > 10.0f) fSpellRange = 10.0f;
if(AI_DEBUG) ai_Debug("0i_combat", "3395", "Adjusted spell range is " +
FloatToString(fSpellRange, 0, 2) + " : " + GetName(oTarget) + " range is " +
FloatToString(fTargetRange, 0, 2) + ".");
// We are closer than we have to be to cast our spell.
if(fTargetRange < fSpellRange)
{
// Lets move back a little, too far and we miss attacks!
if(AI_DEBUG) ai_Debug("0i_combat", "3402", GetName(oCreature) + " is moving away from " +
GetName(oTarget) + "[2.0] to cast a spell.");
ai_SetLastAction(oCreature, AI_LAST_ACTION_MOVE);
ActionMoveAwayFromObject(oTarget, FALSE, 2.0);
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
return TRUE;
}
}
return FALSE;
}
int ai_CheckMeleeCombatPosition(object oCreature, object oTarget, int nAction, int nBaseItemType = 0)
{
// If we are not being attacked then we might want to back out of combat.
if(ai_GetEnemyAttackingMe(oCreature) != OBJECT_INVALID)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3417", "I am being attacked so stand my ground!");
return FALSE;
}
object oNearestEnemy = GetLocalObject(oCreature, AI_ENEMY_NEAREST);
float fDistance = GetDistanceBetween(oCreature, oNearestEnemy);
if(AI_DEBUG) ai_Debug("0i_combat", "3422", "oNearestEnemy: " + GetName(oNearestEnemy) + " fDistance " + FloatToString(fDistance, 0, 2));
if(nAction == AI_LAST_ACTION_RANGED_ATK)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3425", GetName(oCreature) + " is moving away from " + GetName(oNearestEnemy) +
"[" + FloatToString(AI_RANGE_MELEE - fDistance + 1.0, 0, 2) + "]" + " to use a ranged weapon.");
ai_SetLastAction(oCreature, AI_LAST_ACTION_MOVE);
// Lets move just out of melee range!
int bRun = ai_CanIMoveInCombat(oCreature);
ActionMoveAwayFromObject(oNearestEnemy, bRun, AI_RANGE_MELEE - fDistance + 1.0);
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
return TRUE;
}
// If we want to cast a spell this round then back away!
else if(nAction > -1)
{
// Some items we don't need to move on such as wands, staves, and rods.
if(nBaseItemType == BASE_ITEM_ENCHANTED_WAND ||
nBaseItemType == BASE_ITEM_MAGICWAND ||
nBaseItemType == BASE_ITEM_MAGICSTAFF ||
nBaseItemType == BASE_ITEM_MAGICROD) return FALSE;
float fSpellRange = ai_GetSpellRange(nAction);
// A Touch spell means we should not move if we are not the target.
if(fSpellRange <= 5.0 && oCreature != oTarget) return FALSE;
if(AI_DEBUG) ai_Debug("0i_combat", "3446", GetName(oCreature) + " is moving away from " +
GetName(oTarget) + "[" + FloatToString(AI_RANGE_MELEE - fDistance + 1.0, 0, 2) + "] to cast a spell.");
ai_SetLastAction(oCreature, AI_LAST_ACTION_MOVE);
SetActionMode(oCreature, ACTION_MODE_DEFENSIVE_CAST, FALSE);
// Lets move just out of melee range!
int bRun = ai_CanIMoveInCombat(oCreature);
ActionMoveAwayFromObject(oNearestEnemy, bRun, AI_RANGE_MELEE - fDistance + 1.0);
ActionDoCommand(ExecuteScript("0e_do_combat_rnd", oCreature));
return TRUE;
}
return FALSE;
}
int ai_CheckCombatPosition(object oCreature, object oTarget, int nInMelee, int nAction, int nBaseItemType = 0)
{
if(AI_DEBUG) ai_Debug("0i_combat", "3460", "|-----> Checking position in combat: " +
GetName(oCreature) + " nMelee: " + IntToString(nInMelee) +
" Action: " + IntToString(nAction) +
" Hold mode: " + IntToString(ai_GetAIMode(oCreature, AI_MODE_STAND_GROUND)) +
" Use Advanced Movement: " + IntToString(GetLocalInt(GetModule(), AI_RULE_ADVANCED_MOVEMENT)));
// We don't want to move around in combat if we were told to hold.
if(ai_GetAIMode(oCreature, AI_MODE_STAND_GROUND)) return FALSE;
if(!GetLocalInt(GetModule(), AI_RULE_ADVANCED_MOVEMENT)) return FALSE;
if(ai_CompareLastAction(oCreature, AI_LAST_ACTION_MOVE)) return FALSE;
// We are not in melee combat so lets see how close we should get.
if(!nInMelee) return ai_CheckRangedCombatPosition(oCreature, oTarget, nAction);
// If we are in melee we might need to move out of combat.
return ai_CheckMeleeCombatPosition(oCreature, oTarget, nAction, nBaseItemType);
}