3499 lines
188 KiB
Plaintext
3499 lines
188 KiB
Plaintext
/*//////////////////////////////////////////////////////////////////////////////
|
|
// 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);
|
|
}
|