mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
[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:
parent
5708164511
commit
bf43bda1e2
@ -217,6 +217,25 @@ namespace EQ
|
||||
stanceBurnAE
|
||||
};
|
||||
|
||||
enum BotSpellIDs : int {
|
||||
Warrior = 3001,
|
||||
Cleric,
|
||||
Paladin,
|
||||
Ranger,
|
||||
Shadowknight,
|
||||
Druid,
|
||||
Monk,
|
||||
Bard,
|
||||
Rogue,
|
||||
Shaman,
|
||||
Necromancer,
|
||||
Wizard,
|
||||
Magician,
|
||||
Enchanter,
|
||||
Beastlord,
|
||||
Berserker
|
||||
};
|
||||
|
||||
enum GravityBehavior : int8 {
|
||||
Ground,
|
||||
Flying,
|
||||
|
||||
@ -38,6 +38,11 @@
|
||||
OutF(LogSys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} 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 {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::AI))\
|
||||
OutF(LogSys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
77
zone/bot.cpp
77
zone/bot.cpp
@ -843,7 +843,7 @@ void Bot::GenerateBaseStats()
|
||||
int32 CorruptionResist = _baseCorrup;
|
||||
|
||||
// pulling fixed values from an auto-increment field is dangerous...
|
||||
switch(GetClass()) {
|
||||
switch (GetClass()) {
|
||||
case WARRIOR:
|
||||
BotSpellID = 3001;
|
||||
Strength += 10;
|
||||
@ -2173,6 +2173,47 @@ bool Bot::Process()
|
||||
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() {
|
||||
if(spellend_timer.Check(false)) {
|
||||
NPC::SpellProcess();
|
||||
@ -2259,7 +2300,7 @@ void Bot::BotRangedAttack(Mob* other) {
|
||||
if (spellbonuses.NegateIfCombat)
|
||||
BuffFadeByEffect(SE_NegateIfCombat);
|
||||
|
||||
if(hidden || improved_hidden){
|
||||
if (hidden || improved_hidden) {
|
||||
hidden = false;
|
||||
improved_hidden = false;
|
||||
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
|
||||
//Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon.
|
||||
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);
|
||||
my_hit.min_damage = ucDamageBonus;
|
||||
hate += ucDamageBonus;
|
||||
@ -6430,9 +6471,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(ka_time){
|
||||
if (ka_time) {
|
||||
|
||||
switch(GetClass()){
|
||||
switch (GetClass()) {
|
||||
case SHADOWKNIGHT: {
|
||||
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
|
||||
knightattack_timer.Start(HarmTouchReuseTime * 1000);
|
||||
@ -6530,9 +6571,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
|
||||
int level = GetLevel();
|
||||
int reuse = (TauntReuseTime * 1000);
|
||||
bool did_attack = false;
|
||||
switch(GetClass()) {
|
||||
switch (GetClass()) {
|
||||
case WARRIOR:
|
||||
if(level >= RuleI(Combat, NPCBashKickLevel)){
|
||||
if (level >= RuleI(Combat, NPCBashKickLevel)) {
|
||||
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))
|
||||
canBash = true;
|
||||
@ -6552,7 +6593,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
|
||||
case CLERIC:
|
||||
case SHADOWKNIGHT:
|
||||
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))
|
||||
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)) {
|
||||
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)
|
||||
ratio += zone->random.Int(20,70);
|
||||
Critical = true;
|
||||
@ -7436,16 +7477,16 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot s
|
||||
MessageString(Chat::White, MELEE_SILENCE);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()){
|
||||
if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) {
|
||||
MessageString(Chat::White, SPELL_WOULDNT_HOLD);
|
||||
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;
|
||||
}
|
||||
@ -7733,7 +7774,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe
|
||||
if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) {
|
||||
bool noGroupSpell = false;
|
||||
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 spelltype = BotGetSpellType(i);
|
||||
bool spellequal = (j == thespell);
|
||||
@ -7833,7 +7874,7 @@ void Bot::CalcBonuses() {
|
||||
end_regen = CalcEnduranceRegen();
|
||||
}
|
||||
|
||||
int64 Bot::CalcHPRegenCap(){
|
||||
int64 Bot::CalcHPRegenCap() {
|
||||
int level = GetLevel();
|
||||
int64 hpregen_cap = 0;
|
||||
hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25);
|
||||
@ -7841,7 +7882,7 @@ int64 Bot::CalcHPRegenCap(){
|
||||
return (hpregen_cap * RuleI(Character, HPRegenMultiplier) / 100);
|
||||
}
|
||||
|
||||
int64 Bot::CalcManaRegenCap(){
|
||||
int64 Bot::CalcManaRegenCap() {
|
||||
int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap;
|
||||
switch(GetCasterClass()) {
|
||||
case 'I':
|
||||
@ -8278,7 +8319,7 @@ int64 Bot::CalcManaRegen() {
|
||||
|
||||
uint64 Bot::GetClassHPFactor() {
|
||||
uint32 factor;
|
||||
switch(GetClass()) {
|
||||
switch (GetClass()) {
|
||||
case BEASTLORD:
|
||||
case BERSERKER:
|
||||
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) ||
|
||||
(item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue))
|
||||
{
|
||||
@ -9194,7 +9235,7 @@ void Bot::CalcBotStats(bool showtext) {
|
||||
|
||||
CalcBonuses();
|
||||
|
||||
AI_AddNPCSpells(GetBotSpellID());
|
||||
AI_AddBotSpells(GetBotSpellID());
|
||||
|
||||
if(showtext) {
|
||||
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());
|
||||
|
||||
17
zone/bot.h
17
zone/bot.h
@ -174,9 +174,9 @@ public:
|
||||
virtual bool Save();
|
||||
virtual void Depop();
|
||||
void CalcBotStats(bool showtext = true);
|
||||
uint16 BotGetSpells(int spellslot) { return AIspells[spellslot].spellid; }
|
||||
uint32 BotGetSpellType(int spellslot) { return AIspells[spellslot].type; }
|
||||
uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; }
|
||||
uint16 BotGetSpells(int spellslot) { return AIBot_spells[spellslot].spellid; }
|
||||
uint32 BotGetSpellType(int spellslot) { return AIBot_spells[spellslot].type; }
|
||||
uint16 BotGetSpellPriority(int spellslot) { return AIBot_spells[spellslot].priority; }
|
||||
virtual float GetProcChances(float ProcBonus, uint16 hand);
|
||||
virtual int GetHandToHandDamage(void);
|
||||
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 SetTarget(Mob* mob);
|
||||
virtual void Zone();
|
||||
std::vector<AISpells_Struct> GetBotSpells() { return AIspells; }
|
||||
bool IsArcheryRange(Mob* target);
|
||||
void ChangeBotArcherWeapons(bool isArcher);
|
||||
void Sit();
|
||||
@ -308,6 +307,9 @@ public:
|
||||
void DoEnduranceRegen(); //This Regenerates endurance
|
||||
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
|
||||
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
|
||||
virtual bool AI_EngagedCastCheck();
|
||||
@ -321,6 +323,10 @@ public:
|
||||
void SetGuardMode();
|
||||
void SetHoldMode();
|
||||
|
||||
// Bot AI Methods
|
||||
void AI_Bot_Init();
|
||||
void AI_Bot_Start(uint32 iMoveDelay = 0);
|
||||
|
||||
// Mob AI Virtual Override Methods
|
||||
virtual void AI_Process();
|
||||
virtual void AI_Stop();
|
||||
@ -637,7 +643,6 @@ private:
|
||||
// Class Members
|
||||
uint32 _botID;
|
||||
uint32 _botOwnerCharacterID;
|
||||
//uint32 _botSpellID;
|
||||
bool _spawnStatus;
|
||||
Mob* _botOwner;
|
||||
bool _botOrderAttack;
|
||||
@ -744,6 +749,8 @@ private:
|
||||
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 // BOT_H
|
||||
|
||||
@ -39,7 +39,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
return false;
|
||||
|
||||
if (iChance < 100) {
|
||||
if (zone->random.Int(0, 100) > iChance){
|
||||
if (zone->random.Int(0, 100) > iChance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -87,13 +87,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
Mob* addMob = GetFirstIncomingMobToMez(this, botSpell);
|
||||
|
||||
if(!addMob){
|
||||
if (!addMob) {
|
||||
//Say("!addMob.");
|
||||
break;}
|
||||
|
||||
if(!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) {
|
||||
break;
|
||||
}
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost);
|
||||
|
||||
if(castedSpell)
|
||||
@ -135,58 +136,65 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if(hpr < 35) {
|
||||
botSpell = GetBestBotSpellForFastHeal(this);
|
||||
}
|
||||
else if(hpr >= 35 && hpr < 70){
|
||||
if(GetNumberNeedingHealedInGroup(60, false) >= 3)
|
||||
else if (hpr >= 35 && hpr < 70) {
|
||||
if (GetNumberNeedingHealedInGroup(60, false) >= 3) {
|
||||
botSpell = GetBestBotSpellForGroupHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
}
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
}
|
||||
}
|
||||
else if(hpr >= 70 && hpr < 95){
|
||||
if(GetNumberNeedingHealedInGroup(80, false) >= 3)
|
||||
else if (hpr >= 70 && hpr < 95) {
|
||||
if (GetNumberNeedingHealedInGroup(80, false) >= 3) {
|
||||
botSpell = GetBestBotSpellForGroupHealOverTime(this);
|
||||
|
||||
if(hasAggro)
|
||||
}
|
||||
if (hasAggro) {
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!tar->FindType(SE_HealOverTime))
|
||||
if (!tar->FindType(SE_HealOverTime)) {
|
||||
botSpell = GetBestBotSpellForHealOverTime(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) {
|
||||
if(GetNumberNeedingHealedInGroup(40, true) >= 2){
|
||||
if (GetNumberNeedingHealedInGroup(40, true) >= 2) {
|
||||
botSpell = GetBestBotSpellForGroupCompleteHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForGroupHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
}
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForGroupHealOverTime(this);
|
||||
|
||||
if(hpr < 40) {
|
||||
if(botSpell.SpellId == 0)
|
||||
}
|
||||
if (hpr < 40) {
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(GetNumberNeedingHealedInGroup(60, true) >= 2){
|
||||
else if (GetNumberNeedingHealedInGroup(60, true) >= 2) {
|
||||
botSpell = GetBestBotSpellForGroupHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForGroupHealOverTime(this);
|
||||
|
||||
if(hpr < 40) {
|
||||
}
|
||||
if (hpr < 40) {
|
||||
if(botSpell.SpellId == 0)
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
}
|
||||
}
|
||||
else if(hpr < 40)
|
||||
else if (hpr < 40) {
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
else if(hpr >= 40 && hpr < 75)
|
||||
}
|
||||
else if (hpr >= 40 && hpr < 75) {
|
||||
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
|
||||
}
|
||||
else {
|
||||
if(hpr < 90 && !tar->FindType(SE_HealOverTime))
|
||||
if (hpr < 90 && !tar->FindType(SE_HealOverTime)) {
|
||||
botSpell = GetBestBotSpellForHealOverTime(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
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(tar->GetHPRatio() <= hpRatioToCast)
|
||||
if (tar->GetHPRatio() <= hpRatioToCast) {
|
||||
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
|
||||
}
|
||||
}
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
}
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetFirstBotSpellForSingleTargetHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0 && botClass == BARD){
|
||||
}
|
||||
if (botSpell.SpellId == 0 && botClass == BARD) {
|
||||
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
|
||||
}
|
||||
|
||||
@ -257,8 +266,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
}
|
||||
}*/
|
||||
if(botClass != BARD) {
|
||||
if(IsGroupSpell(botSpell.SpellId)){
|
||||
if(HasGroup()) {
|
||||
if (IsGroupSpell(botSpell.SpellId)) {
|
||||
if (HasGroup()) {
|
||||
Group *g = GetGroup();
|
||||
|
||||
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
|
||||
if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
|
||||
if ((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
|
||||
|| (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
|
||||
if(botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())){
|
||||
if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch(tar->GetArchetype())
|
||||
switch (tar->GetArchetype())
|
||||
{
|
||||
case ARCHETYPE_CASTER:
|
||||
//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);
|
||||
}
|
||||
|
||||
if(botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) {
|
||||
if(botSpell.SpellId == 0) {
|
||||
if (botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) {
|
||||
if (botSpell.SpellId == 0) {
|
||||
uint8 stunChance = (tar->IsCasting() ? 30: 15);
|
||||
|
||||
if(botClass == PALADIN)
|
||||
@ -913,29 +922,29 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
if (botSpell.SpellId == 0)
|
||||
break;
|
||||
|
||||
uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime);
|
||||
|
||||
if(castedSpell) {
|
||||
if(botClass != BARD) {
|
||||
if(IsGroupSpell(botSpell.SpellId)){
|
||||
if (castedSpell) {
|
||||
if (botClass != BARD) {
|
||||
if (IsGroupSpell(botSpell.SpellId)) {
|
||||
Group *g;
|
||||
|
||||
if(HasGroup()) {
|
||||
if (HasGroup()) {
|
||||
Group *g = GetGroup();
|
||||
|
||||
if(g) {
|
||||
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
|
||||
if(g->members[i] && !g->members[i]->qglobal) {
|
||||
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore())
|
||||
if (g) {
|
||||
for ( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
|
||||
if (g->members[i] && !g->members[i]->qglobal) {
|
||||
if (TempDontCureMeBeforeTime != tar->DontCureMeBefore())
|
||||
g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000);
|
||||
}
|
||||
}
|
||||
@ -943,8 +952,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore())
|
||||
if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) {
|
||||
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;
|
||||
|
||||
if (manaCost == -1)
|
||||
manaCost = spells[AIspells[i].spellid].mana;
|
||||
manaCost = spells[AIBot_spells[i].spellid].mana;
|
||||
else if (manaCost == -2)
|
||||
manaCost = 0;
|
||||
|
||||
@ -1074,7 +1084,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
||||
if(RuleB(Bots, FinishBuffing)) {
|
||||
if(manaCost > hasMana) {
|
||||
// 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;
|
||||
SetMana(manaCost);
|
||||
}
|
||||
@ -1083,20 +1093,22 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
||||
|
||||
float dist2 = 0;
|
||||
|
||||
if (AIspells[i].type & SpellType_Escape) {
|
||||
if (AIBot_spells[i].type & SpellType_Escape) {
|
||||
dist2 = 0;
|
||||
} else
|
||||
dist2 = DistanceSquared(m_Position, tar->GetPosition());
|
||||
|
||||
if (((((spells[AIspells[i].spellid].target_type==ST_GroupTeleport && AIspells[i].type==2)
|
||||
|| spells[AIspells[i].spellid].target_type==ST_AECaster
|
||||
|| spells[AIspells[i].spellid].target_type==ST_Group
|
||||
|| spells[AIspells[i].spellid].target_type==ST_AEBard)
|
||||
&& dist2 <= spells[AIspells[i].spellid].aoe_range*spells[AIspells[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()))
|
||||
if (((((spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type==2)
|
||||
|| spells[AIBot_spells[i].spellid].target_type==ST_AECaster
|
||||
|| spells[AIBot_spells[i].spellid].target_type==ST_Group
|
||||
|| spells[AIBot_spells[i].spellid].target_type==ST_AEBard)
|
||||
&& dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range)
|
||||
|| 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())
|
||||
Stand();
|
||||
}
|
||||
@ -1107,20 +1119,20 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
||||
extraMana = false;
|
||||
}
|
||||
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..
|
||||
// // 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;
|
||||
// //spellend_timer.Start(spells[AIspells[i].spellid].cast_time);
|
||||
// //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[AIBot_spells[i].spellid].cast_time);
|
||||
//}
|
||||
//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) {
|
||||
SetSpellRecastTimer(spells[AIspells[i].spellid].timer_id, spells[AIspells[i].spellid].recast_time);
|
||||
if(spells[AIBot_spells[i].spellid].timer_id > 0) {
|
||||
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 {
|
||||
botSpell = GetBestBotSpellForPercentageHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0)
|
||||
}
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetFirstBotSpellForSingleTargetHeal(this);
|
||||
|
||||
if(botSpell.SpellId == 0){
|
||||
}
|
||||
if (botSpell.SpellId == 0) {
|
||||
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
|
||||
}
|
||||
}
|
||||
@ -1593,7 +1605,7 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff
|
||||
std::list<BotSpell> result;
|
||||
|
||||
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--) {
|
||||
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;
|
||||
|
||||
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--) {
|
||||
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;
|
||||
|
||||
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--) {
|
||||
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;
|
||||
|
||||
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--) {
|
||||
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
|
||||
@ -1711,7 +1723,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) {
|
||||
result.ManaCost = 0;
|
||||
|
||||
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--) {
|
||||
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
|
||||
@ -1767,7 +1779,7 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) {
|
||||
|
||||
if(botCaster) {
|
||||
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) {
|
||||
// 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;
|
||||
|
||||
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--) {
|
||||
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
|
||||
@ -1909,7 +1921,7 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) {
|
||||
|
||||
if(botCaster) {
|
||||
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) {
|
||||
// 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;
|
||||
|
||||
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--) {
|
||||
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;
|
||||
|
||||
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--) {
|
||||
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) {
|
||||
if(caster) {
|
||||
if(caster->AIspells[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(caster->AIBot_spells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -2674,4 +2686,344 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
|
||||
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
|
||||
|
||||
@ -2138,11 +2138,11 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) {
|
||||
}
|
||||
else {
|
||||
//check for heal over time. if not present, try it first
|
||||
if(!tar->FindType(SE_HealOverTime)) {
|
||||
if (!tar->FindType(SE_HealOverTime)) {
|
||||
selectedMercSpell = GetBestMercSpellForHealOverTime(this);
|
||||
|
||||
//get regular heal
|
||||
if(selectedMercSpell.spellid == 0) {
|
||||
if (selectedMercSpell.spellid == 0) {
|
||||
selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2988,16 +2988,9 @@ DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID)
|
||||
query = StringFormat(
|
||||
"SELECT spellid, type, minlevel, maxlevel, "
|
||||
"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 "
|
||||
"WHERE npc_spells_id=%d ORDER BY minlevel",
|
||||
iDBSpellsID);
|
||||
#endif
|
||||
results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success()) {
|
||||
|
||||
17
zone/npc.cpp
17
zone/npc.cpp
@ -31,6 +31,7 @@
|
||||
#include "../common/linked_list.h"
|
||||
#include "../common/servertalk.h"
|
||||
#include "../common/say_link.h"
|
||||
#include "../common/data_verification.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "entity.h"
|
||||
@ -42,6 +43,10 @@
|
||||
#include "water_map.h"
|
||||
#include "npc_scale_manager.h"
|
||||
|
||||
#ifdef BOTS
|
||||
#include "bot.h"
|
||||
#endif
|
||||
|
||||
#include <cctype>
|
||||
#include <stdio.h>
|
||||
#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_beneficial_chance = static_cast<uint8> (RuleI(Spells, AI_IdleBeneficialChance));
|
||||
|
||||
AI_Init();
|
||||
AI_Start();
|
||||
// It's possible for IsBot() to not be set yet during Bot loading, so have to use an alternative to catch Bots
|
||||
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_texture2 = npc_type_data->d_melee_texture2;
|
||||
|
||||
13
zone/npc.h
13
zone/npc.h
@ -68,6 +68,18 @@ struct AISpells_Struct {
|
||||
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 {
|
||||
uint16 spelleffectid;
|
||||
int32 base_value;
|
||||
@ -585,6 +597,7 @@ protected:
|
||||
|
||||
uint32* pDontCastBefore_casting_spell;
|
||||
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;
|
||||
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);
|
||||
|
||||
@ -1874,7 +1874,8 @@ bool Zone::Depop(bool StartSpawnTimer) {
|
||||
|
||||
// clear spell cache
|
||||
database.ClearNPCSpells();
|
||||
|
||||
database.ClearBotSpells();
|
||||
|
||||
zone->spawn_group_list.ReloadSpawnGroups();
|
||||
|
||||
return true;
|
||||
|
||||
@ -107,6 +107,44 @@ struct DBnpcspellseffects_Struct {
|
||||
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 {
|
||||
EQ::skills::SkillType tradeskill;
|
||||
int16 skill_needed;
|
||||
@ -491,6 +529,10 @@ public:
|
||||
void ClearNPCSpells() { npc_spells_cache.clear(); npc_spells_loadtried.clear(); }
|
||||
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 */
|
||||
const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel);
|
||||
void LoadMercEquipment(Merc *merc);
|
||||
@ -595,6 +637,8 @@ protected:
|
||||
std::unordered_set<uint32> npc_spells_loadtried;
|
||||
DBnpcspellseffects_Struct** npc_spellseffects_cache;
|
||||
bool* npc_spellseffects_loadtried;
|
||||
std::unordered_map<uint32, DBbotspells_Struct> Bot_Spells_Cache;
|
||||
std::unordered_set<uint32> Bot_Spells_LoadTried;
|
||||
};
|
||||
|
||||
extern ZoneDatabase database;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user