[Bots] Cleanup Bot Spell Functions, reduce reliance on NPC Functions/Attributes (#2495)

* [Bots] Initial Cleanup of Functions, moved Bot Casting out of mob_ai.cpp

* Moved Bots off NPC AI_Spells Struct, and AI_Spells private attribute.

* Formatting Fixes, fixed LogAI entries, Added LogAIModerate Alias

* Add Constants.

* Added Bot DB Struct, fixed some potential casting issues

* Formatting

* Formatting
This commit is contained in:
Aeadoin 2022-10-29 16:38:15 -04:00 committed by GitHub
parent 5708164511
commit bf43bda1e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 610 additions and 122 deletions

View File

@ -217,6 +217,25 @@ namespace EQ
stanceBurnAE stanceBurnAE
}; };
enum BotSpellIDs : int {
Warrior = 3001,
Cleric,
Paladin,
Ranger,
Shadowknight,
Druid,
Monk,
Bard,
Rogue,
Shaman,
Necromancer,
Wizard,
Magician,
Enchanter,
Beastlord,
Berserker
};
enum GravityBehavior : int8 { enum GravityBehavior : int8 {
Ground, Ground,
Flying, Flying,

View File

@ -38,6 +38,11 @@
OutF(LogSys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } while (0)
#define LogAIModerate(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::AI))\
OutF(LogSys, Logs::Moderate, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogAIDetail(message, ...) do {\ #define LogAIDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::AI))\ if (LogSys.IsLogEnabled(Logs::Detail, Logs::AI))\
OutF(LogSys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -843,7 +843,7 @@ void Bot::GenerateBaseStats()
int32 CorruptionResist = _baseCorrup; int32 CorruptionResist = _baseCorrup;
// pulling fixed values from an auto-increment field is dangerous... // pulling fixed values from an auto-increment field is dangerous...
switch(GetClass()) { switch (GetClass()) {
case WARRIOR: case WARRIOR:
BotSpellID = 3001; BotSpellID = 3001;
Strength += 10; Strength += 10;
@ -2173,6 +2173,47 @@ bool Bot::Process()
return true; return true;
} }
void Bot::AI_Bot_Start(uint32 iMoveDelay) {
Mob::AI_Start(iMoveDelay);
if (!pAIControlled) {
return;
}
if (AIBot_spells.empty()) {
AIautocastspell_timer = std::make_unique<Timer>(1000);
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer = std::make_unique<Timer>(500);
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
if (NPCTypedata) {
AI_AddBotSpells(NPCTypedata->npc_spells_id);
ProcessSpecialAbilities(NPCTypedata->special_abilities);
AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id);
}
SendTo(GetX(), GetY(), GetZ());
SaveGuardSpot(GetPosition());
}
void Bot::AI_Bot_Init()
{
AIautocastspell_timer.reset(nullptr);
casting_spell_AIindex = static_cast<uint8>(AIBot_spells.size());
roambox_max_x = 0;
roambox_max_y = 0;
roambox_min_x = 0;
roambox_min_y = 0;
roambox_distance = 0;
roambox_destination_x = 0;
roambox_destination_y = 0;
roambox_destination_z = 0;
roambox_min_delay = 2500;
roambox_delay = 2500;
}
void Bot::SpellProcess() { void Bot::SpellProcess() {
if(spellend_timer.Check(false)) { if(spellend_timer.Check(false)) {
NPC::SpellProcess(); NPC::SpellProcess();
@ -2259,7 +2300,7 @@ void Bot::BotRangedAttack(Mob* other) {
if (spellbonuses.NegateIfCombat) if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat); BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){ if (hidden || improved_hidden) {
hidden = false; hidden = false;
improved_hidden = false; improved_hidden = false;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
@ -5161,7 +5202,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
#endif #endif
//Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon. //Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon.
if (Hand == EQ::invslot::slotSecondary) { if (Hand == EQ::invslot::slotSecondary) {
if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc) {
ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQ::ItemData*) nullptr); ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQ::ItemData*) nullptr);
my_hit.min_damage = ucDamageBonus; my_hit.min_damage = ucDamageBonus;
hate += ucDamageBonus; hate += ucDamageBonus;
@ -6430,9 +6471,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
return; return;
} }
if(ka_time){ if (ka_time) {
switch(GetClass()){ switch (GetClass()) {
case SHADOWKNIGHT: { case SHADOWKNIGHT: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID()); CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
knightattack_timer.Start(HarmTouchReuseTime * 1000); knightattack_timer.Start(HarmTouchReuseTime * 1000);
@ -6530,9 +6571,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
int level = GetLevel(); int level = GetLevel();
int reuse = (TauntReuseTime * 1000); int reuse = (TauntReuseTime * 1000);
bool did_attack = false; bool did_attack = false;
switch(GetClass()) { switch (GetClass()) {
case WARRIOR: case WARRIOR:
if(level >= RuleI(Combat, NPCBashKickLevel)){ if (level >= RuleI(Combat, NPCBashKickLevel)) {
bool canBash = false; bool canBash = false;
if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1)) if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1))
canBash = true; canBash = true;
@ -6552,7 +6593,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
case CLERIC: case CLERIC:
case SHADOWKNIGHT: case SHADOWKNIGHT:
case PALADIN: case PALADIN:
if(level >= RuleI(Combat, NPCBashKickLevel)){ if (level >= RuleI(Combat, NPCBashKickLevel)) {
if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1)) if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1))
skill_to_use = EQ::skills::SkillBash; skill_to_use = EQ::skills::SkillBash;
} }
@ -7006,7 +7047,7 @@ int64 Bot::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) {
} }
else if (GetClass() == WIZARD || (IsMerc() && GetClass() == CASTERDPS)) { else if (GetClass() == WIZARD || (IsMerc() && GetClass() == CASTERDPS)) {
if ((GetLevel() >= RuleI(Spells, WizCritLevel)) && zone->random.Roll(RuleI(Spells, WizCritChance))){ if ((GetLevel() >= RuleI(Spells, WizCritLevel)) && zone->random.Roll(RuleI(Spells, WizCritChance))) {
//Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed) //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed)
ratio += zone->random.Int(20,70); ratio += zone->random.Int(20,70);
Critical = true; Critical = true;
@ -7436,16 +7477,16 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot s
MessageString(Chat::White, MELEE_SILENCE); MessageString(Chat::White, MELEE_SILENCE);
if(casting_spell_id) if(casting_spell_id)
AI_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot)); AI_Bot_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
return false; return false;
} }
} }
if(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()){ if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) {
MessageString(Chat::White, SPELL_WOULDNT_HOLD); MessageString(Chat::White, SPELL_WOULDNT_HOLD);
if(casting_spell_id) if(casting_spell_id)
AI_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot)); AI_Bot_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
return false; return false;
} }
@ -7733,7 +7774,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe
if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) { if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) {
bool noGroupSpell = false; bool noGroupSpell = false;
uint16 thespell = spell_id; uint16 thespell = spell_id;
for(int i = 0; i < AIspells.size(); i++) { for (int i = 0; i < AIBot_spells.size(); i++) {
int j = BotGetSpells(i); int j = BotGetSpells(i);
int spelltype = BotGetSpellType(i); int spelltype = BotGetSpellType(i);
bool spellequal = (j == thespell); bool spellequal = (j == thespell);
@ -7833,7 +7874,7 @@ void Bot::CalcBonuses() {
end_regen = CalcEnduranceRegen(); end_regen = CalcEnduranceRegen();
} }
int64 Bot::CalcHPRegenCap(){ int64 Bot::CalcHPRegenCap() {
int level = GetLevel(); int level = GetLevel();
int64 hpregen_cap = 0; int64 hpregen_cap = 0;
hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25); hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25);
@ -7841,7 +7882,7 @@ int64 Bot::CalcHPRegenCap(){
return (hpregen_cap * RuleI(Character, HPRegenMultiplier) / 100); return (hpregen_cap * RuleI(Character, HPRegenMultiplier) / 100);
} }
int64 Bot::CalcManaRegenCap(){ int64 Bot::CalcManaRegenCap() {
int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap;
switch(GetCasterClass()) { switch(GetCasterClass()) {
case 'I': case 'I':
@ -8278,7 +8319,7 @@ int64 Bot::CalcManaRegen() {
uint64 Bot::GetClassHPFactor() { uint64 Bot::GetClassHPFactor() {
uint32 factor; uint32 factor;
switch(GetClass()) { switch (GetClass()) {
case BEASTLORD: case BEASTLORD:
case BERSERKER: case BERSERKER:
case MONK: case MONK:
@ -9101,7 +9142,7 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool
} }
} }
if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL){ if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) {
if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) || if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) ||
(item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue)) (item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue))
{ {
@ -9194,7 +9235,7 @@ void Bot::CalcBotStats(bool showtext) {
CalcBonuses(); CalcBonuses();
AI_AddNPCSpells(GetBotSpellID()); AI_AddBotSpells(GetBotSpellID());
if(showtext) { if(showtext) {
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName()); GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());

View File

@ -174,9 +174,9 @@ public:
virtual bool Save(); virtual bool Save();
virtual void Depop(); virtual void Depop();
void CalcBotStats(bool showtext = true); void CalcBotStats(bool showtext = true);
uint16 BotGetSpells(int spellslot) { return AIspells[spellslot].spellid; } uint16 BotGetSpells(int spellslot) { return AIBot_spells[spellslot].spellid; }
uint32 BotGetSpellType(int spellslot) { return AIspells[spellslot].type; } uint32 BotGetSpellType(int spellslot) { return AIBot_spells[spellslot].type; }
uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } uint16 BotGetSpellPriority(int spellslot) { return AIBot_spells[spellslot].priority; }
virtual float GetProcChances(float ProcBonus, uint16 hand); virtual float GetProcChances(float ProcBonus, uint16 hand);
virtual int GetHandToHandDamage(void); virtual int GetHandToHandDamage(void);
virtual bool TryFinishingBlow(Mob *defender, int64 &damage); virtual bool TryFinishingBlow(Mob *defender, int64 &damage);
@ -220,7 +220,6 @@ public:
virtual void AddToHateList(Mob* other, int64 hate = 0, int64 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, bool pet_command = false); virtual void AddToHateList(Mob* other, int64 hate = 0, int64 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, bool pet_command = false);
virtual void SetTarget(Mob* mob); virtual void SetTarget(Mob* mob);
virtual void Zone(); virtual void Zone();
std::vector<AISpells_Struct> GetBotSpells() { return AIspells; }
bool IsArcheryRange(Mob* target); bool IsArcheryRange(Mob* target);
void ChangeBotArcherWeapons(bool isArcher); void ChangeBotArcherWeapons(bool isArcher);
void Sit(); void Sit();
@ -308,6 +307,9 @@ public:
void DoEnduranceRegen(); //This Regenerates endurance void DoEnduranceRegen(); //This Regenerates endurance
void DoEnduranceUpkeep(); //does the endurance upkeep void DoEnduranceUpkeep(); //does the endurance upkeep
bool AI_AddBotSpells(uint32 iDBSpellsID);
void AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp);
void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot);
// AI Methods // AI Methods
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
virtual bool AI_EngagedCastCheck(); virtual bool AI_EngagedCastCheck();
@ -321,6 +323,10 @@ public:
void SetGuardMode(); void SetGuardMode();
void SetHoldMode(); void SetHoldMode();
// Bot AI Methods
void AI_Bot_Init();
void AI_Bot_Start(uint32 iMoveDelay = 0);
// Mob AI Virtual Override Methods // Mob AI Virtual Override Methods
virtual void AI_Process(); virtual void AI_Process();
virtual void AI_Stop(); virtual void AI_Stop();
@ -637,7 +643,6 @@ private:
// Class Members // Class Members
uint32 _botID; uint32 _botID;
uint32 _botOwnerCharacterID; uint32 _botOwnerCharacterID;
//uint32 _botSpellID;
bool _spawnStatus; bool _spawnStatus;
Mob* _botOwner; Mob* _botOwner;
bool _botOrderAttack; bool _botOrderAttack;
@ -744,6 +749,8 @@ private:
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND]; static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND];
}; };
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID);
#endif // BOTS #endif // BOTS
#endif // BOT_H #endif // BOT_H

View File

@ -39,7 +39,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
return false; return false;
if (iChance < 100) { if (iChance < 100) {
if (zone->random.Int(0, 100) > iChance){ if (zone->random.Int(0, 100) > iChance) {
return false; return false;
} }
} }
@ -87,13 +87,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); Mob* addMob = GetFirstIncomingMobToMez(this, botSpell);
if(!addMob){ if (!addMob) {
//Say("!addMob."); //Say("!addMob.");
break;}
if(!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
break; break;
}
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) {
break;
}
castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost);
if(castedSpell) if(castedSpell)
@ -135,58 +136,65 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
if(hpr < 35) { if(hpr < 35) {
botSpell = GetBestBotSpellForFastHeal(this); botSpell = GetBestBotSpellForFastHeal(this);
} }
else if(hpr >= 35 && hpr < 70){ else if (hpr >= 35 && hpr < 70) {
if(GetNumberNeedingHealedInGroup(60, false) >= 3) if (GetNumberNeedingHealedInGroup(60, false) >= 3) {
botSpell = GetBestBotSpellForGroupHeal(this); botSpell = GetBestBotSpellForGroupHeal(this);
}
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
}
} }
else if(hpr >= 70 && hpr < 95){ else if (hpr >= 70 && hpr < 95) {
if(GetNumberNeedingHealedInGroup(80, false) >= 3) if (GetNumberNeedingHealedInGroup(80, false) >= 3) {
botSpell = GetBestBotSpellForGroupHealOverTime(this); botSpell = GetBestBotSpellForGroupHealOverTime(this);
}
if(hasAggro) if (hasAggro) {
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
}
} }
else { else {
if(!tar->FindType(SE_HealOverTime)) if (!tar->FindType(SE_HealOverTime)) {
botSpell = GetBestBotSpellForHealOverTime(this); botSpell = GetBestBotSpellForHealOverTime(this);
}
} }
} }
else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) { else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) {
if(GetNumberNeedingHealedInGroup(40, true) >= 2){ if (GetNumberNeedingHealedInGroup(40, true) >= 2) {
botSpell = GetBestBotSpellForGroupCompleteHeal(this); botSpell = GetBestBotSpellForGroupCompleteHeal(this);
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHeal(this); botSpell = GetBestBotSpellForGroupHeal(this);
}
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHealOverTime(this); botSpell = GetBestBotSpellForGroupHealOverTime(this);
}
if(hpr < 40) { if (hpr < 40) {
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
}
} }
} }
else if(GetNumberNeedingHealedInGroup(60, true) >= 2){ else if (GetNumberNeedingHealedInGroup(60, true) >= 2) {
botSpell = GetBestBotSpellForGroupHeal(this); botSpell = GetBestBotSpellForGroupHeal(this);
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHealOverTime(this); botSpell = GetBestBotSpellForGroupHealOverTime(this);
}
if(hpr < 40) { if (hpr < 40) {
if(botSpell.SpellId == 0) if(botSpell.SpellId == 0)
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
} }
} }
else if(hpr < 40) else if (hpr < 40) {
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
else if(hpr >= 40 && hpr < 75) }
else if (hpr >= 40 && hpr < 75) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
else { else {
if(hpr < 90 && !tar->FindType(SE_HealOverTime)) if (hpr < 90 && !tar->FindType(SE_HealOverTime)) {
botSpell = GetBestBotSpellForHealOverTime(this); botSpell = GetBestBotSpellForHealOverTime(this);
}
} }
} }
else { else {
@ -213,17 +221,18 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
} }
//If we're at specified mana % or below, don't heal as hybrid //If we're at specified mana % or below, don't heal as hybrid
if(tar->GetHPRatio() <= hpRatioToCast) if (tar->GetHPRatio() <= hpRatioToCast) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
} }
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellForSingleTargetHeal(this); botSpell = GetFirstBotSpellForSingleTargetHeal(this);
}
if(botSpell.SpellId == 0 && botClass == BARD){ if (botSpell.SpellId == 0 && botClass == BARD) {
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
} }
@ -257,8 +266,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
} }
}*/ }*/
if(botClass != BARD) { if(botClass != BARD) {
if(IsGroupSpell(botSpell.SpellId)){ if (IsGroupSpell(botSpell.SpellId)) {
if(HasGroup()) { if (HasGroup()) {
Group *g = GetGroup(); Group *g = GetGroup();
if(g) { if(g) {
@ -345,14 +354,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
} }
// Put the zone levitate and movement check here since bots are able to bypass the client casting check // Put the zone levitate and movement check here since bots are able to bypass the client casting check
if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate()) if ((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
|| (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
if(botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())){ if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) {
continue; continue;
} }
} }
switch(tar->GetArchetype()) switch (tar->GetArchetype())
{ {
case ARCHETYPE_CASTER: case ARCHETYPE_CASTER:
//TODO: probably more caster specific spell effects in here //TODO: probably more caster specific spell effects in here
@ -491,8 +500,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned);
} }
if(botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) { if (botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) {
if(botSpell.SpellId == 0) { if (botSpell.SpellId == 0) {
uint8 stunChance = (tar->IsCasting() ? 30: 15); uint8 stunChance = (tar->IsCasting() ? 30: 15);
if(botClass == PALADIN) if(botClass == PALADIN)
@ -913,29 +922,29 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break; break;
} }
case SpellType_Cure: { case SpellType_Cure: {
if(GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2)) if (GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2))
{ {
botSpell = GetBestBotSpellForCure(this, tar); botSpell = GetBestBotSpellForCure(this, tar);
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0)
break; break;
uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore();
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime);
if(castedSpell) { if (castedSpell) {
if(botClass != BARD) { if (botClass != BARD) {
if(IsGroupSpell(botSpell.SpellId)){ if (IsGroupSpell(botSpell.SpellId)) {
Group *g; Group *g;
if(HasGroup()) { if (HasGroup()) {
Group *g = GetGroup(); Group *g = GetGroup();
if(g) { if (g) {
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) { for ( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
if(g->members[i] && !g->members[i]->qglobal) { if (g->members[i] && !g->members[i]->qglobal) {
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) if (TempDontCureMeBeforeTime != tar->DontCureMeBefore())
g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000);
} }
} }
@ -943,8 +952,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
} }
} }
else { else {
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) {
tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000);
}
} }
} }
} }
@ -1063,7 +1073,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
int32 manaCost = mana_cost; int32 manaCost = mana_cost;
if (manaCost == -1) if (manaCost == -1)
manaCost = spells[AIspells[i].spellid].mana; manaCost = spells[AIBot_spells[i].spellid].mana;
else if (manaCost == -2) else if (manaCost == -2)
manaCost = 0; manaCost = 0;
@ -1074,7 +1084,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
if(RuleB(Bots, FinishBuffing)) { if(RuleB(Bots, FinishBuffing)) {
if(manaCost > hasMana) { if(manaCost > hasMana) {
// Let's have the bots complete the buff time process // Let's have the bots complete the buff time process
if(AIspells[i].type & SpellType_Buff) { if(AIBot_spells[i].type & SpellType_Buff) {
extraMana = manaCost - hasMana; extraMana = manaCost - hasMana;
SetMana(manaCost); SetMana(manaCost);
} }
@ -1083,19 +1093,21 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
float dist2 = 0; float dist2 = 0;
if (AIspells[i].type & SpellType_Escape) { if (AIBot_spells[i].type & SpellType_Escape) {
dist2 = 0; dist2 = 0;
} else } else
dist2 = DistanceSquared(m_Position, tar->GetPosition()); dist2 = DistanceSquared(m_Position, tar->GetPosition());
if (((((spells[AIspells[i].spellid].target_type==ST_GroupTeleport && AIspells[i].type==2) if (((((spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type==2)
|| spells[AIspells[i].spellid].target_type==ST_AECaster || spells[AIBot_spells[i].spellid].target_type==ST_AECaster
|| spells[AIspells[i].spellid].target_type==ST_Group || spells[AIBot_spells[i].spellid].target_type==ST_Group
|| spells[AIspells[i].spellid].target_type==ST_AEBard) || spells[AIBot_spells[i].spellid].target_type==ST_AEBard)
&& dist2 <= spells[AIspells[i].spellid].aoe_range*spells[AIspells[i].spellid].aoe_range) && dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range)
|| dist2 <= GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)*GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana())) || dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana()))
{ {
result = NPC::AIDoSpellCast(i, tar, mana_cost, oDontDoAgainBefore); casting_spell_AIindex = i;
LogAIDetail("Bot::AIDoSpellCast: spellid = [{}], tar = [{}], mana = [{}], Name: [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name);
result = Mob::CastSpell(AIBot_spells[i].spellid, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[AIBot_spells[i].spellid].cast_time, AIBot_spells[i].manacost == -2 ? 0 : mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIBot_spells[i].resist_adjust));
if(IsCasting() && IsSitting()) if(IsCasting() && IsSitting())
Stand(); Stand();
@ -1107,20 +1119,20 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
extraMana = false; extraMana = false;
} }
else { //handle spell recast and recast timers else { //handle spell recast and recast timers
//if(GetClass() == BARD && IsGroupSpell(AIspells[i].spellid)) { //if(GetClass() == BARD && IsGroupSpell(AIBot_spells[i].spellid)) {
// // Bard buff songs have been moved to their own npc spell type.. // // Bard buff songs have been moved to their own npc spell type..
// // Buff stacking is now checked as opposed to manipulating the timer to avoid rapid casting // // Buff stacking is now checked as opposed to manipulating the timer to avoid rapid casting
// //AIspells[i].time_cancast = (spells[AIspells[i].spellid].recast_time > (spells[AIspells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIspells[i].spellid].buffduration * 6000; // //AIBot_spells[i].time_cancast = (spells[AIBot_spells[i].spellid].recast_time > (spells[AIBot_spells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].buffduration * 6000;
// //spellend_timer.Start(spells[AIspells[i].spellid].cast_time); // //spellend_timer.Start(spells[AIBot_spells[i].spellid].cast_time);
//} //}
//else //else
// AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time; // AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time;
AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time; AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time;
if(spells[AIspells[i].spellid].timer_id > 0) { if(spells[AIBot_spells[i].spellid].timer_id > 0) {
SetSpellRecastTimer(spells[AIspells[i].spellid].timer_id, spells[AIspells[i].spellid].recast_time); SetSpellRecastTimer(spells[AIBot_spells[i].spellid].timer_id, spells[AIBot_spells[i].spellid].recast_time);
} }
} }
@ -1551,13 +1563,13 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) {
else { else {
botSpell = GetBestBotSpellForPercentageHeal(this); botSpell = GetBestBotSpellForPercentageHeal(this);
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
if(botSpell.SpellId == 0) if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellForSingleTargetHeal(this); botSpell = GetFirstBotSpellForSingleTargetHeal(this);
}
if(botSpell.SpellId == 0){ if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
} }
} }
@ -1593,7 +1605,7 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff
std::list<BotSpell> result; std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1620,7 +1632,7 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster,
std::list<BotSpell> result; std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1649,7 +1661,7 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellTyp
std::list<BotSpell> result; std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1676,7 +1688,7 @@ std::list<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa
std::list<BotSpell_wPriority> result; std::list<BotSpell_wPriority> result;
if (botCaster && botCaster->AI_HasSpells()) { if (botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1711,7 +1723,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) {
result.ManaCost = 0; result.ManaCost = 0;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1767,7 +1779,7 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) {
if(botCaster) { if(botCaster) {
std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime);
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
@ -1803,7 +1815,7 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) {
result.ManaCost = 0; result.ManaCost = 0;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -1909,7 +1921,7 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) {
if(botCaster) { if(botCaster) {
std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime);
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
@ -2320,7 +2332,7 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) {
return result; return result;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -2367,7 +2379,7 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) {
bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100 ? true: false; bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100 ? true: false;
if(botCaster && botCaster->AI_HasSpells()) { if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells(); std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@ -2526,8 +2538,8 @@ int32 Bot::GetSpellRecastTimer(Bot *caster, int timer_index) {
bool Bot::CheckSpellRecastTimers(Bot *caster, int SpellIndex) { bool Bot::CheckSpellRecastTimers(Bot *caster, int SpellIndex) {
if(caster) { if(caster) {
if(caster->AIspells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast if(caster->AIBot_spells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast
if(GetSpellRecastTimer(caster, spells[caster->AIspells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer if(GetSpellRecastTimer(caster, spells[caster->AIBot_spells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer
return true; //can cast spell return true; //can cast spell
} }
} }
@ -2674,4 +2686,344 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index); return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
} }
bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
// ok, this function should load the list, and the parent list then shove them into the struct and sort
npc_spells_id = iDBSpellsID;
AIBot_spells.clear();
if (iDBSpellsID == 0) {
AIautocastspell_timer->Disable();
return false;
}
DBbotspells_Struct* spell_list = content_db.GetBotSpells(iDBSpellsID);
if (!spell_list) {
AIautocastspell_timer->Disable();
return false;
}
DBbotspells_Struct* parentlist = content_db.GetBotSpells(spell_list->parent_list);
std::string debug_msg = StringFormat("Loading NPCSpells onto %s: dbspellsid=%u, level=%u", GetName(), iDBSpellsID, GetLevel());
if (spell_list) {
debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->entries.size(), spell_list->parent_list));
if (spell_list->parent_list) {
if (parentlist)
debug_msg.append(StringFormat(" (found, %u)", parentlist->entries.size()));
else
debug_msg.append(" (not found)");
}
}
else {
debug_msg.append(" (not found)");
}
LogAIDetail("[{}]", debug_msg.c_str());
if (parentlist) {
for (const auto &iter : parentlist->entries) {
LogAI("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
}
}
LogAIModerate("fin (parent list)");
if (spell_list) {
for (const auto &iter : spell_list->entries) {
LogAIDetail("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
}
}
LogAIModerate("fin (spell list)");
uint16 attack_proc_spell = -1;
int8 proc_chance = 3;
uint16 range_proc_spell = -1;
int16 rproc_chance = 0;
uint16 defensive_proc_spell = -1;
int16 dproc_chance = 0;
uint32 _fail_recast = 0;
uint32 _engaged_no_sp_recast_min = 0;
uint32 _engaged_no_sp_recast_max = 0;
uint8 _engaged_beneficial_self_chance = 0;
uint8 _engaged_beneficial_other_chance = 0;
uint8 _engaged_detrimental_chance = 0;
uint32 _pursue_no_sp_recast_min = 0;
uint32 _pursue_no_sp_recast_max = 0;
uint8 _pursue_detrimental_chance = 0;
uint32 _idle_no_sp_recast_min = 0;
uint32 _idle_no_sp_recast_max = 0;
uint8 _idle_beneficial_chance = 0;
if (parentlist) {
attack_proc_spell = parentlist->attack_proc;
proc_chance = parentlist->proc_chance;
range_proc_spell = parentlist->range_proc;
rproc_chance = parentlist->rproc_chance;
defensive_proc_spell = parentlist->defensive_proc;
dproc_chance = parentlist->dproc_chance;
_fail_recast = parentlist->fail_recast;
_engaged_no_sp_recast_min = parentlist->engaged_no_sp_recast_min;
_engaged_no_sp_recast_max = parentlist->engaged_no_sp_recast_max;
_engaged_beneficial_self_chance = parentlist->engaged_beneficial_self_chance;
_engaged_beneficial_other_chance = parentlist->engaged_beneficial_other_chance;
_engaged_detrimental_chance = parentlist->engaged_detrimental_chance;
_pursue_no_sp_recast_min = parentlist->pursue_no_sp_recast_min;
_pursue_no_sp_recast_max = parentlist->pursue_no_sp_recast_max;
_pursue_detrimental_chance = parentlist->pursue_detrimental_chance;
_idle_no_sp_recast_min = parentlist->idle_no_sp_recast_min;
_idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max;
_idle_beneficial_chance = parentlist->idle_beneficial_chance;
for (auto &e : parentlist->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
if (!IsSpellInBotList(spell_list, e.spellid))
{
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
}
}
if (spell_list->attack_proc >= 0) {
attack_proc_spell = spell_list->attack_proc;
proc_chance = spell_list->proc_chance;
}
if (spell_list->range_proc >= 0) {
range_proc_spell = spell_list->range_proc;
rproc_chance = spell_list->rproc_chance;
}
if (spell_list->defensive_proc >= 0) {
defensive_proc_spell = spell_list->defensive_proc;
dproc_chance = spell_list->dproc_chance;
}
//If any casting variables are defined in the current list, ignore those in the parent list.
if (spell_list->fail_recast || spell_list->engaged_no_sp_recast_min || spell_list->engaged_no_sp_recast_max
|| spell_list->engaged_beneficial_self_chance || spell_list->engaged_beneficial_other_chance || spell_list->engaged_detrimental_chance
|| spell_list->pursue_no_sp_recast_min || spell_list->pursue_no_sp_recast_max || spell_list->pursue_detrimental_chance
|| spell_list->idle_no_sp_recast_min || spell_list->idle_no_sp_recast_max || spell_list->idle_beneficial_chance) {
_fail_recast = spell_list->fail_recast;
_engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min;
_engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max;
_engaged_beneficial_self_chance = spell_list->engaged_beneficial_self_chance;
_engaged_beneficial_other_chance = spell_list->engaged_beneficial_other_chance;
_engaged_detrimental_chance = spell_list->engaged_detrimental_chance;
_pursue_no_sp_recast_min = spell_list->pursue_no_sp_recast_min;
_pursue_no_sp_recast_max = spell_list->pursue_no_sp_recast_max;
_pursue_detrimental_chance = spell_list->pursue_detrimental_chance;
_idle_no_sp_recast_min = spell_list->idle_no_sp_recast_min;
_idle_no_sp_recast_max = spell_list->idle_no_sp_recast_max;
_idle_beneficial_chance = spell_list->idle_beneficial_chance;
}
for (auto &e : spell_list->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
std::sort(AIBot_spells.begin(), AIBot_spells.end(), [](const BotSpells_Struct& a, const BotSpells_Struct& b) {
return a.priority > b.priority;
});
if (IsValidSpell(attack_proc_spell)) {
AddProcToWeapon(attack_proc_spell, true, proc_chance);
if (RuleB(Spells, NPCInnateProcOverride)) {
innate_proc_spell_id = attack_proc_spell;
}
}
if (IsValidSpell(range_proc_spell)) {
AddRangedProc(range_proc_spell, (rproc_chance + 100));
}
if (IsValidSpell(defensive_proc_spell)) {
AddDefensiveProc(defensive_proc_spell, (dproc_chance + 100));
}
//Set AI casting variables
AISpellVar.fail_recast = (_fail_recast) ? _fail_recast : RuleI(Spells, AI_SpellCastFinishedFailRecast);
AISpellVar.engaged_no_sp_recast_min = (_engaged_no_sp_recast_min) ? _engaged_no_sp_recast_min : RuleI(Spells, AI_EngagedNoSpellMinRecast);
AISpellVar.engaged_no_sp_recast_max = (_engaged_no_sp_recast_max) ? _engaged_no_sp_recast_max : RuleI(Spells, AI_EngagedNoSpellMaxRecast);
AISpellVar.engaged_beneficial_self_chance = (_engaged_beneficial_self_chance) ? _engaged_beneficial_self_chance : RuleI(Spells, AI_EngagedBeneficialSelfChance);
AISpellVar.engaged_beneficial_other_chance = (_engaged_beneficial_other_chance) ? _engaged_beneficial_other_chance : RuleI(Spells, AI_EngagedBeneficialOtherChance);
AISpellVar.engaged_detrimental_chance = (_engaged_detrimental_chance) ? _engaged_detrimental_chance : RuleI(Spells, AI_EngagedDetrimentalChance);
AISpellVar.pursue_no_sp_recast_min = (_pursue_no_sp_recast_min) ? _pursue_no_sp_recast_min : RuleI(Spells, AI_PursueNoSpellMinRecast);
AISpellVar.pursue_no_sp_recast_max = (_pursue_no_sp_recast_max) ? _pursue_no_sp_recast_max : RuleI(Spells, AI_PursueNoSpellMaxRecast);
AISpellVar.pursue_detrimental_chance = (_pursue_detrimental_chance) ? _pursue_detrimental_chance : RuleI(Spells, AI_PursueDetrimentalChance);
AISpellVar.idle_no_sp_recast_min = (_idle_no_sp_recast_min) ? _idle_no_sp_recast_min : RuleI(Spells, AI_IdleNoSpellMinRecast);
AISpellVar.idle_no_sp_recast_max = (_idle_no_sp_recast_max) ? _idle_no_sp_recast_max : RuleI(Spells, AI_IdleNoSpellMaxRecast);
AISpellVar.idle_beneficial_chance = (_idle_beneficial_chance) ? _idle_beneficial_chance : RuleI(Spells, AI_IdleBeneficialChance);
if (AIBot_spells.empty()) {
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer->Trigger();
}
return true;
}
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) {
auto it = std::find_if(spell_list->entries.begin(), spell_list->entries.end(),
[iSpellID](const DBbotspells_entries_Struct &a) { return a.spellid == iSpellID; });
return it != spell_list->entries.end();
}
DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
{
if (iDBSpellsID == 0) {
return nullptr;
}
auto it = Bot_Spells_Cache.find(iDBSpellsID);
if (it != Bot_Spells_Cache.end()) { // it's in the cache, easy =)
return &it->second;
}
if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already
Bot_Spells_LoadTried.insert(iDBSpellsID);
std::string query = StringFormat("SELECT id, parent_list, attack_proc, proc_chance, "
"range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, "
"idle_b_chance FROM npc_spells WHERE id=%d",
iDBSpellsID);
auto results = QueryDatabase(query);
if (!results.Success()) {
return nullptr;
}
if (results.RowCount() != 1) {
return nullptr;
}
auto row = results.begin();
DBbotspells_Struct spell_set;
spell_set.parent_list = atoi(row[1]);
spell_set.attack_proc = atoi(row[2]);
spell_set.proc_chance = atoi(row[3]);
spell_set.range_proc = atoi(row[4]);
spell_set.rproc_chance = atoi(row[5]);
spell_set.defensive_proc = atoi(row[6]);
spell_set.dproc_chance = atoi(row[7]);
spell_set.fail_recast = atoi(row[8]);
spell_set.engaged_no_sp_recast_min = atoi(row[9]);
spell_set.engaged_no_sp_recast_max = atoi(row[10]);
spell_set.engaged_beneficial_self_chance = atoi(row[11]);
spell_set.engaged_beneficial_other_chance = atoi(row[12]);
spell_set.engaged_detrimental_chance = atoi(row[13]);
spell_set.pursue_no_sp_recast_min = atoi(row[14]);
spell_set.pursue_no_sp_recast_max = atoi(row[15]);
spell_set.pursue_detrimental_chance = atoi(row[16]);
spell_set.idle_no_sp_recast_min = atoi(row[17]);
spell_set.idle_no_sp_recast_max = atoi(row[18]);
spell_set.idle_beneficial_chance = atoi(row[19]);
// pulling fixed values from an auto-increment field is dangerous...
query = StringFormat(
"SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust "
"FROM bot_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel",
iDBSpellsID);
results = QueryDatabase(query);
if (!results.Success()) {
return nullptr;
}
int entryIndex = 0;
for (row = results.begin(); row != results.end(); ++row, ++entryIndex) {
DBbotspells_entries_Struct entry;
int spell_id = atoi(row[0]);
entry.spellid = spell_id;
entry.type = atoul(row[1]);
entry.minlevel = atoi(row[2]);
entry.maxlevel = atoi(row[3]);
entry.manacost = atoi(row[4]);
entry.recast_delay = atoi(row[5]);
entry.priority = atoi(row[6]);
entry.min_hp = atoi(row[7]);
entry.max_hp = atoi(row[8]);
// some spell types don't make much since to be priority 0, so fix that
if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0)
entry.priority = 1;
if (row[9]) {
entry.resist_adjust = atoi(row[9]);
}
else if (IsValidSpell(spell_id)) {
entry.resist_adjust = spells[spell_id].resist_difficulty;
}
spell_set.entries.push_back(entry);
}
Bot_Spells_Cache.insert(std::make_pair(iDBSpellsID, spell_set));
return &Bot_Spells_Cache[iDBSpellsID];
}
return nullptr;
}
// adds a spell to the list, taking into account priority and resorting list as needed.
void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType,
int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp)
{
if(!IsValidSpell(iSpellID)) {
return;
}
HasAISpell = true;
BotSpells_Struct t;
t.priority = iPriority;
t.spellid = iSpellID;
t.type = iType;
t.manacost = iManaCost;
t.recast_delay = iRecastDelay;
t.time_cancast = 0;
t.resist_adjust = iResistAdjust;
t.min_hp = min_hp;
t.max_hp = max_hp;
AIBot_spells.push_back(t);
// If we're going from an empty list, we need to start the timer
if (AIBot_spells.empty()) {
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
}
//this gets called from InterruptSpell() for failure or SpellFinished() for success
void Bot::AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot) {
if (slot == 1) {
uint32 recovery_time = 0;
if (iCastSucceeded) {
if (casting_spell_AIindex < AIBot_spells.size()) {
recovery_time += spells[AIBot_spells[casting_spell_AIindex].spellid].recovery_time;
if (AIBot_spells[casting_spell_AIindex].recast_delay >= 0) {
if (AIBot_spells[casting_spell_AIindex].recast_delay < 10000) {
AIBot_spells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIBot_spells[casting_spell_AIindex].recast_delay*1000);
}
}
else {
AIBot_spells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[casting_spell_AIindex].spellid].recast_time;
}
}
if (recovery_time < AIautocastspell_timer->GetSetAtTrigger()) {
recovery_time = AIautocastspell_timer->GetSetAtTrigger();
}
AIautocastspell_timer->Start(recovery_time, false);
}
else {
AIautocastspell_timer->Start(AISpellVar.fail_recast, false);
}
casting_spell_AIindex = AIBot_spells.size();
}
}
#endif #endif

View File

@ -2138,11 +2138,11 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) {
} }
else { else {
//check for heal over time. if not present, try it first //check for heal over time. if not present, try it first
if(!tar->FindType(SE_HealOverTime)) { if (!tar->FindType(SE_HealOverTime)) {
selectedMercSpell = GetBestMercSpellForHealOverTime(this); selectedMercSpell = GetBestMercSpellForHealOverTime(this);
//get regular heal //get regular heal
if(selectedMercSpell.spellid == 0) { if (selectedMercSpell.spellid == 0) {
selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this); selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this);
} }
} }

View File

@ -2988,16 +2988,9 @@ DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID)
query = StringFormat( query = StringFormat(
"SELECT spellid, type, minlevel, maxlevel, " "SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust " "manacost, recast_delay, priority, min_hp, max_hp, resist_adjust "
#ifdef BOTS
"FROM %s "
"WHERE npc_spells_id=%d ORDER BY minlevel",
(iDBSpellsID >= 3001 && iDBSpellsID <= 3016 ? "bot_spells_entries" : "npc_spells_entries"),
iDBSpellsID);
#else
"FROM npc_spells_entries " "FROM npc_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel", "WHERE npc_spells_id=%d ORDER BY minlevel",
iDBSpellsID); iDBSpellsID);
#endif
results = QueryDatabase(query); results = QueryDatabase(query);
if (!results.Success()) { if (!results.Success()) {

View File

@ -31,6 +31,7 @@
#include "../common/linked_list.h" #include "../common/linked_list.h"
#include "../common/servertalk.h" #include "../common/servertalk.h"
#include "../common/say_link.h" #include "../common/say_link.h"
#include "../common/data_verification.h"
#include "client.h" #include "client.h"
#include "entity.h" #include "entity.h"
@ -42,6 +43,10 @@
#include "water_map.h" #include "water_map.h"
#include "npc_scale_manager.h" #include "npc_scale_manager.h"
#ifdef BOTS
#include "bot.h"
#endif
#include <cctype> #include <cctype>
#include <stdio.h> #include <stdio.h>
#include <string> #include <string>
@ -309,8 +314,16 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
AISpellVar.idle_no_sp_recast_max = static_cast<uint32>(RuleI(Spells, AI_IdleNoSpellMaxRecast)); AISpellVar.idle_no_sp_recast_max = static_cast<uint32>(RuleI(Spells, AI_IdleNoSpellMaxRecast));
AISpellVar.idle_beneficial_chance = static_cast<uint8> (RuleI(Spells, AI_IdleBeneficialChance)); AISpellVar.idle_beneficial_chance = static_cast<uint8> (RuleI(Spells, AI_IdleBeneficialChance));
AI_Init(); // It's possible for IsBot() to not be set yet during Bot loading, so have to use an alternative to catch Bots
AI_Start(); if (!EQ::ValueWithin(npc_type_data->npc_spells_id, EQ::constants::BotSpellIDs::Warrior, EQ::constants::BotSpellIDs::Berserker)) {
AI_Init();
AI_Start();
#ifdef BOTS
} else {
CastToBot()->AI_Bot_Init();
CastToBot()->AI_Bot_Start();
#endif
}
d_melee_texture1 = npc_type_data->d_melee_texture1; d_melee_texture1 = npc_type_data->d_melee_texture1;
d_melee_texture2 = npc_type_data->d_melee_texture2; d_melee_texture2 = npc_type_data->d_melee_texture2;

View File

@ -68,6 +68,18 @@ struct AISpells_Struct {
int8 max_hp; // >0 won't cast if HP is above int8 max_hp; // >0 won't cast if HP is above
}; };
struct BotSpells_Struct {
uint32 type; // 0 = never, must be one (and only one) of the defined values
int16 spellid; // <= 0 = no spell
int16 manacost; // -1 = use spdat, -2 = no cast time
uint32 time_cancast; // when we can cast this spell next
int32 recast_delay;
int16 priority;
int16 resist_adjust;
int16 min_hp; // >0 won't cast if HP is below
int16 max_hp; // >0 won't cast if HP is above
};
struct AISpellsEffects_Struct { struct AISpellsEffects_Struct {
uint16 spelleffectid; uint16 spelleffectid;
int32 base_value; int32 base_value;
@ -585,6 +597,7 @@ protected:
uint32* pDontCastBefore_casting_spell; uint32* pDontCastBefore_casting_spell;
std::vector<AISpells_Struct> AIspells; std::vector<AISpells_Struct> AIspells;
std::vector<BotSpells_Struct> AIBot_spells; //Will eventually be moved to Bot Class once Bots are no longer reliant on NPC constructor
bool HasAISpell; bool HasAISpell;
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false); virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);

View File

@ -1874,6 +1874,7 @@ bool Zone::Depop(bool StartSpawnTimer) {
// clear spell cache // clear spell cache
database.ClearNPCSpells(); database.ClearNPCSpells();
database.ClearBotSpells();
zone->spawn_group_list.ReloadSpawnGroups(); zone->spawn_group_list.ReloadSpawnGroups();

View File

@ -107,6 +107,44 @@ struct DBnpcspellseffects_Struct {
DBnpcspellseffects_entries_Struct entries[0]; DBnpcspellseffects_entries_Struct entries[0];
}; };
#pragma pack(1)
struct DBbotspells_entries_Struct {
uint16 spellid;
uint8 minlevel;
uint8 maxlevel;
uint32 type;
int16 manacost;
int16 priority;
int32 recast_delay;
int16 resist_adjust;
int8 min_hp;
int8 max_hp;
};
#pragma pack()
struct DBbotspells_Struct {
uint32 parent_list;
uint16 attack_proc;
uint8 proc_chance;
uint16 range_proc;
int16 rproc_chance;
uint16 defensive_proc;
int16 dproc_chance;
uint32 fail_recast;
uint32 engaged_no_sp_recast_min;
uint32 engaged_no_sp_recast_max;
uint8 engaged_beneficial_self_chance;
uint8 engaged_beneficial_other_chance;
uint8 engaged_detrimental_chance;
uint32 pursue_no_sp_recast_min;
uint32 pursue_no_sp_recast_max;
uint8 pursue_detrimental_chance;
uint32 idle_no_sp_recast_min;
uint32 idle_no_sp_recast_max;
uint8 idle_beneficial_chance;
std::vector<DBbotspells_entries_Struct> entries;
};
struct DBTradeskillRecipe_Struct { struct DBTradeskillRecipe_Struct {
EQ::skills::SkillType tradeskill; EQ::skills::SkillType tradeskill;
int16 skill_needed; int16 skill_needed;
@ -491,6 +529,10 @@ public:
void ClearNPCSpells() { npc_spells_cache.clear(); npc_spells_loadtried.clear(); } void ClearNPCSpells() { npc_spells_cache.clear(); npc_spells_loadtried.clear(); }
const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false); const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false);
/*Bots */
DBbotspells_Struct* GetBotSpells(uint32 iDBSpellsID);
void ClearBotSpells() { Bot_Spells_Cache.clear(); Bot_Spells_LoadTried.clear(); }
/* Mercs */ /* Mercs */
const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel); const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel);
void LoadMercEquipment(Merc *merc); void LoadMercEquipment(Merc *merc);
@ -595,6 +637,8 @@ protected:
std::unordered_set<uint32> npc_spells_loadtried; std::unordered_set<uint32> npc_spells_loadtried;
DBnpcspellseffects_Struct** npc_spellseffects_cache; DBnpcspellseffects_Struct** npc_spellseffects_cache;
bool* npc_spellseffects_loadtried; bool* npc_spellseffects_loadtried;
std::unordered_map<uint32, DBbotspells_Struct> Bot_Spells_Cache;
std::unordered_set<uint32> Bot_Spells_LoadTried;
}; };
extern ZoneDatabase database; extern ZoneDatabase database;