diff --git a/data/sql/custom/db_characters/2024_11_21_00_characters_npcbot.sql b/data/sql/custom/db_characters/2024_11_21_00_characters_npcbot.sql new file mode 100644 index 00000000000000..b60283d3c13176 --- /dev/null +++ b/data/sql/custom/db_characters/2024_11_21_00_characters_npcbot.sql @@ -0,0 +1,3 @@ +-- +ALTER TABLE `characters_npcbot` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +ALTER TABLE `characters_npcbot` ADD `miscvalues` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER `spells_disabled`; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 60c25882196886..f88593dc54d6b8 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -630,6 +630,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_NPCBOT_FACTION, "UPDATE characters_npcbot SET faction = ? WHERE entry = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_NPCBOT_SPEC, "UPDATE characters_npcbot SET spec = ? WHERE entry = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_NPCBOT_DISABLED_SPELLS, "UPDATE characters_npcbot SET spells_disabled = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_MISCVALUES, "UPDATE characters_npcbot SET miscvalues = ? WHERE entry = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_REP_NPCBOT_STATS, "REPLACE INTO characters_npcbot_stats " "(entry, maxhealth, maxpower, strength, agility, stamina, intellect, spirit, armor, defense, resHoly, resFire, resNature, resFrost, resShadow, resArcane, blockPct, dodgePct, parryPct, critPct, attackPower, spellPower, spellPen, hastePct, hitBonusPct, expertise, armorPenPct) VALUES " "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index bd39dba8893ec9..25a8bcd9322b20 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -538,6 +538,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_NPCBOT_FACTION, CHAR_UPD_NPCBOT_SPEC, CHAR_UPD_NPCBOT_DISABLED_SPELLS, + CHAR_UPD_NPCBOT_MISCVALUES, CHAR_REP_NPCBOT_STATS, CHAR_REP_NPCBOT_TRANSMOG, CHAR_DEL_NPCBOT_TRANSMOG, diff --git a/src/server/game/AI/NpcBots/bot_ai.cpp b/src/server/game/AI/NpcBots/bot_ai.cpp index cfc22bd3283531..bdc01a10b41f7d 100644 --- a/src/server/game/AI/NpcBots/bot_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_ai.cpp @@ -240,6 +240,8 @@ bot_ai::bot_ai(Creature* creature) : CreatureAI(creature) _saveDisabledSpellsTimer = 0; _saveDisabledSpells = false; + _saveMiscValuesTimer = 0; + _saveMiscValues = false; _deathsCount = 0; _killsCount = 0; @@ -615,6 +617,8 @@ void bot_ai::ResetBotAI(uint8 resetType) if ((resetType == BOTAI_RESET_DISMISS || resetType == BOTAI_RESET_LOGOUT) && !IsTempBot()) { + if (resetType == BOTAI_RESET_DISMISS) + ResetAllMiscValues(); EnableAllSpells(resetType == BOTAI_RESET_DISMISS); InitRoles(); } @@ -13137,13 +13141,13 @@ BotEquipResult bot_ai::_equip(uint8 slot, Item* newItem, ObjectGuid receiver, bo if (slot == BOT_SLOT_MAINHAND) { SetAIMiscValue(BOTAI_MISC_DAGGER_MAINHAND, proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER); - SetAIMiscValue(BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH, newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + SetAIMiscValue(BOTAI_MISC_ENCHANT_TIMER_MH, uint32(newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) ? 0 : 1)); SetAIMiscValue(BOTAI_MISC_WEAPON_SPEC, proto->SubClass); } if (slot == BOT_SLOT_OFFHAND) { SetAIMiscValue(BOTAI_MISC_DAGGER_OFFHAND, proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER); - SetAIMiscValue(BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH, newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + SetAIMiscValue(BOTAI_MISC_ENCHANT_TIMER_OH, uint32(newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) ? 0 : 1)); } } @@ -13547,7 +13551,7 @@ void bot_ai::RemoveItemClassEnchantment(uint8 slot) { uint8 eslot = TEMP_ENCHANTMENT_SLOT; - if (!GetAIMiscValue(slot == BOT_SLOT_MAINHAND ? BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH : BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH)) + if (!GetAIMiscValue(slot == BOT_SLOT_MAINHAND ? BOTAI_MISC_ENCHANT_TIMER_MH : BOTAI_MISC_ENCHANT_TIMER_OH)) return; Item* weap = _equips[slot]; @@ -14806,6 +14810,7 @@ void bot_ai::DefaultInit() InitFaction(); InitOwner(); InitEquips(); + InitMiscValues(); } firstspawn = false; @@ -17834,6 +17839,18 @@ bool bot_ai::GlobalUpdate(uint32 diff) BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_DISABLED_SPELLS, &npcBotData->disabled_spells); } } + // 2) miscavalues + if (_saveMiscValues && _saveMiscValuesTimer <= diff) + { + _saveMiscValues = false; + _saveMiscValuesTimer = 5000; + + if (!IsTempBot()) + { + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_MISCVALUES, &npcBotData->miscvalues); + } + } if (_updateTimerEx2 <= diff) { @@ -18628,6 +18645,7 @@ void bot_ai::CommonTimers(uint32 diff) if (_updateTimerEx2 > diff) _updateTimerEx2 -= diff; if (_saveDisabledSpellsTimer > diff) _saveDisabledSpellsTimer -= diff; + if (_saveMiscValuesTimer > diff) _saveMiscValuesTimer -= diff; } void bot_ai::UpdateReviveTimer(uint32 diff) @@ -20996,6 +21014,59 @@ uint8 bot_ai::GetBotComboPoints() const return me->GetVehicle() ? vehcomboPoints : uint8(GetAIMiscValue(BOTAI_MISC_COMBO_POINTS)); } +void bot_ai::SetAIMiscValue(uint32 data, uint32 value) +{ + if (data >= BOT_MISCVALUE_SAVED_FIRST && data <= BOT_MISCVALUE_SAVED_LAST) + { + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + if (auto it = npcBotData->miscvalues.find(data); it == npcBotData->miscvalues.end() || it->second != value) + { + npcBotData->miscvalues[data] = value; + _saveMiscValues = true; + } + } +} + +void bot_ai::ResetAllMiscValues() +{ + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + + for (uint32 miscval = BOT_MISCVALUE_SAVED_FIRST; miscval <= BOT_MISCVALUE_SAVED_LAST; ++miscval) + { + switch (miscval) + { + case BOTAI_MISC_ENCHANT_IS_AUTO_MH: + case BOTAI_MISC_ENCHANT_IS_AUTO_OH: + SetAIMiscValue(miscval, uint32(true)); + break; + case BOTAI_MISC_ENCHANT_TIMER_MH: + case BOTAI_MISC_ENCHANT_TIMER_OH: + SetAIMiscValue(miscval, uint32(1)); + break; + case BOTAI_MISC_ENCHANT_CURRENT_MH: + case BOTAI_MISC_ENCHANT_CURRENT_OH: + case BOTAI_MISC_PET_TYPE: + case BOTAI_MISC_AURA_TYPE: + SetAIMiscValue(miscval, uint32(0)); + break; + default: + BOT_LOG_ERROR("npcbots", "ResetMiscValues: unknown saved miscvalue {} reset for bot {} (current: {})!", miscval, me->GetEntry(), GetAIMiscValue(miscval)); + SetAIMiscValue(miscval, uint32(0)); + break; + } + } + + npcBotData->miscvalues.clear(); + _saveMiscValues = true; +} + +void bot_ai::InitMiscValues() +{ + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + for (auto const& p : npcBotData->miscvalues) + SetAIMiscValue(p.first, p.second); +} + float bot_ai::GetBotAmmoDPS() const { if (CanUseAmmo()) diff --git a/src/server/game/AI/NpcBots/bot_ai.h b/src/server/game/AI/NpcBots/bot_ai.h index f80cb7b59c4625..b60da7a232f852 100644 --- a/src/server/game/AI/NpcBots/bot_ai.h +++ b/src/server/game/AI/NpcBots/bot_ai.h @@ -263,8 +263,10 @@ class bot_ai : public CreatureAI bool CanChangeEquip(uint8 slot) const; virtual bool CanSeeEveryone() const { return false; } virtual float GetBotArmorPenetrationCoef() const { return armor_pen; } + void InitMiscValues(); + void ResetAllMiscValues(); virtual uint32 GetAIMiscValue(uint32 /*data*/) const { return 0; } - virtual void SetAIMiscValue(uint32 /*data*/, uint32 /*value*/) {} + virtual void SetAIMiscValue(uint32 data, uint32 value); uint8 GetBotComboPoints() const; float GetBotAmmoDPS() const; @@ -714,6 +716,7 @@ class bot_ai : public CreatureAI uint32 _groupUpdateTimer; //save timers uint32 _saveDisabledSpellsTimer; + uint32 _saveMiscValuesTimer; uint32 _lastZoneId, _lastAreaId, _lastWMOAreaId; uint32 _selfrez_spell_id; @@ -755,6 +758,7 @@ class bot_ai : public CreatureAI //save flags bool _saveDisabledSpells; + bool _saveMiscValues; TeleportHomeEvent* teleHomeEvent; TeleportFinishEvent* teleFinishEvent; diff --git a/src/server/game/AI/NpcBots/bot_hunter_ai.cpp b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp index 80d5acdecc13f9..0523920e14d981 100644 --- a/src/server/game/AI/NpcBots/bot_hunter_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp @@ -108,16 +108,6 @@ enum HunterPassives enum HunterSpecial { - ASPECT_NONE = 0, - ASPECT_MONKEY = 1, - ASPECT_HAWK = 2, - ASPECT_CHEETAH = 3, - ASPECT_VIPER = 4, - ASPECT_BEAST = 5, - ASPECT_PACK = 6, - ASPECT_WILD = 7, - ASPECT_DRAGONHAWK = 8, - SPECIFIC_ASPECT_MONKEY = 0x001, SPECIFIC_ASPECT_HAWK = 0x002, SPECIFIC_ASPECT_CHEETAH = 0x004, @@ -225,6 +215,8 @@ class hunter_bot : public CreatureScript { _botclass = BOT_CLASS_HUNTER; + myPetType = 0; + InitUnitFlags(); } @@ -247,7 +239,7 @@ class hunter_bot : public CreatureScript void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } - void JustDied(Unit* u) override { Aspect = 0; UnsummonAll(false); bot_ai::JustDied(u); } + void JustDied(Unit* u) override { _myaspect = 0; UnsummonAll(false); bot_ai::JustDied(u); } void DoNonCombatActions(uint32 /*diff*/) { } void CheckAspects(uint32 diff) @@ -257,7 +249,7 @@ class hunter_bot : public CreatureScript aspectTimer = urand(5000, 10000); - if (Aspect == ASPECT_VIPER && GetManaPCT(me) < 50) + if (_myaspect == ASPECT_OF_THE_VIPER_1 && GetManaPCT(me) < 50) return; uint32 ASPECT_OF_THE_MONKEY = GetSpell(ASPECT_OF_THE_MONKEY_1); @@ -272,7 +264,7 @@ class hunter_bot : public CreatureScript std::map idMap; uint32 mask = _getAspectsMask(idMap); - if (Aspect == ASPECT_WILD) //manual + if (_myaspect == ASPECT_OF_THE_WILD_1) //manual { if (idMap[ASPECT_OF_THE_WILD_1] != ASPECT_OF_THE_WILD) if (doCast(me, ASPECT_OF_THE_WILD)) @@ -289,10 +281,10 @@ class hunter_bot : public CreatureScript } return; } - else if (Aspect == ASPECT_VIPER && GetManaPCT(me) > 50) + else if (_myaspect == ASPECT_OF_THE_VIPER_1 && GetManaPCT(me) > 50) { me->RemoveAurasDueToSpell(ASPECT_OF_THE_VIPER_1, me->GetGUID()); - Aspect = ASPECT_NONE; + _myaspect = 0; } if (IAmFree()) @@ -304,7 +296,7 @@ class hunter_bot : public CreatureScript (me->IsInCombat() || !map_allows_mount || !IsOutdoors() || IsFlagCarrier(me)) : !me->IsWithinDist(me->GetVictim(), 8.0f + GetSpellAttackRange(true)))) { - if (ASPECT_OF_THE_CHEETAH && !(mask & (SPECIFIC_ASPECT_CHEETAH | SPECIFIC_ASPECT_PACK)) && Aspect != ASPECT_CHEETAH) + if (ASPECT_OF_THE_CHEETAH && !(mask & (SPECIFIC_ASPECT_CHEETAH | SPECIFIC_ASPECT_PACK)) && _myaspect != ASPECT_OF_THE_CHEETAH_1) { if (doCast(me, ASPECT_OF_THE_CHEETAH)) return; @@ -312,10 +304,10 @@ class hunter_bot : public CreatureScript return; } - else if (Aspect == ASPECT_CHEETAH) + else if (_myaspect == ASPECT_OF_THE_CHEETAH_1) { me->RemoveAurasDueToSpell(ASPECT_OF_THE_CHEETAH_1, me->GetGUID()); - Aspect = ASPECT_NONE; + _myaspect = 0; } } else @@ -335,7 +327,7 @@ class hunter_bot : public CreatureScript return; } } - if (ASPECT_OF_THE_CHEETAH && Aspect != ASPECT_CHEETAH) + if (ASPECT_OF_THE_CHEETAH && _myaspect != ASPECT_OF_THE_CHEETAH_1) { movFlags = me->m_movementInfo.GetMovementFlags(); if ((movFlags & MOVEMENTFLAG_FORWARD) && !(movFlags & (MOVEMENTFLAG_FALLING_FAR)) && @@ -349,33 +341,33 @@ class hunter_bot : public CreatureScript return; } - else if (Aspect == ASPECT_PACK) + else if (_myaspect == ASPECT_OF_THE_PACK_1) { me->RemoveAurasDueToSpell(ASPECT_OF_THE_PACK_1, me->GetGUID()); - Aspect = ASPECT_NONE; + _myaspect = 0; } } - if ((Aspect == ASPECT_DRAGONHAWK && idMap[ASPECT_OF_THE_DRAGONHAWK_1] == ASPECT_OF_THE_DRAGONHAWK) || - (!ASPECT_OF_THE_DRAGONHAWK && ((Aspect == ASPECT_HAWK && idMap[ASPECT_OF_THE_HAWK_1] == ASPECT_OF_THE_HAWK) || - Aspect == ASPECT_MONKEY))) + if ((_myaspect == ASPECT_OF_THE_DRAGONHAWK_1 && idMap[ASPECT_OF_THE_DRAGONHAWK_1] == ASPECT_OF_THE_DRAGONHAWK) || + (!ASPECT_OF_THE_DRAGONHAWK && ((_myaspect == ASPECT_OF_THE_HAWK_1 && idMap[ASPECT_OF_THE_HAWK_1] == ASPECT_OF_THE_HAWK) || + _myaspect == ASPECT_OF_THE_MONKEY_1))) return; if (ASPECT_OF_THE_DRAGONHAWK && - (Aspect != ASPECT_DRAGONHAWK || idMap[ASPECT_OF_THE_DRAGONHAWK_1] != ASPECT_OF_THE_DRAGONHAWK)) + (_myaspect != ASPECT_OF_THE_DRAGONHAWK_1 || idMap[ASPECT_OF_THE_DRAGONHAWK_1] != ASPECT_OF_THE_DRAGONHAWK)) { if (doCast(me, ASPECT_OF_THE_DRAGONHAWK)) return; return; } if (ASPECT_OF_THE_HAWK && (!IsTank() || (!ASPECT_OF_THE_MONKEY && !ASPECT_OF_THE_DRAGONHAWK)) && - (Aspect != ASPECT_HAWK || idMap[ASPECT_OF_THE_HAWK_1] != ASPECT_OF_THE_HAWK)) + (_myaspect != ASPECT_OF_THE_HAWK_1 || idMap[ASPECT_OF_THE_HAWK_1] != ASPECT_OF_THE_HAWK)) { if (doCast(me, ASPECT_OF_THE_HAWK)) return; return; } - if (ASPECT_OF_THE_MONKEY && Aspect != ASPECT_MONKEY) + if (ASPECT_OF_THE_MONKEY && _myaspect != ASPECT_OF_THE_MONKEY_1) { if (doCast(me, ASPECT_OF_THE_MONKEY)) return; @@ -1553,28 +1545,14 @@ class hunter_bot : public CreatureScript switch (baseId) { case ASPECT_OF_THE_MONKEY_1: - Aspect = ASPECT_MONKEY; - break; case ASPECT_OF_THE_HAWK_1: - Aspect = ASPECT_HAWK; - break; case ASPECT_OF_THE_CHEETAH_1: - Aspect = ASPECT_CHEETAH; - break; case ASPECT_OF_THE_VIPER_1: - Aspect = ASPECT_VIPER; - break; case ASPECT_OF_THE_BEAST_1: - Aspect = ASPECT_BEAST; - break; case ASPECT_OF_THE_PACK_1: - Aspect = ASPECT_PACK; - break; case ASPECT_OF_THE_WILD_1: - Aspect = ASPECT_WILD; - break; case ASPECT_OF_THE_DRAGONHAWK_1: - Aspect = ASPECT_DRAGONHAWK; + _myaspect = baseId; break; default: break; @@ -1970,19 +1948,22 @@ class hunter_bot : public CreatureScript case BOTAI_MISC_PET_AVAILABLE_4: return BOT_PET_TENACITY_START; case BOTAI_MISC_PET_AVAILABLE_5: - return me->GetLevel() >= 80 ? BOT_PET_SILITHID : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_SILITHID : 0; case BOTAI_MISC_PET_AVAILABLE_6: - return me->GetLevel() >= 80 ? BOT_PET_CHIMAERA : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_CHIMAERA : 0; case BOTAI_MISC_PET_AVAILABLE_7: - return me->GetLevel() >= 80 ? BOT_PET_SPIRITBEAST : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_SPIRITBEAST : 0; case BOTAI_MISC_PET_AVAILABLE_8: - return me->GetLevel() >= 80 ? BOT_PET_COREHOUND : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_COREHOUND : 0; case BOTAI_MISC_PET_AVAILABLE_9: - return me->GetLevel() >= 80 ? BOT_PET_DEVILSAUR : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_DEVILSAUR : 0; case BOTAI_MISC_PET_AVAILABLE_10: - return me->GetLevel() >= 80 ? BOT_PET_RHINO : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_RHINO : 0; case BOTAI_MISC_PET_AVAILABLE_11: - return me->GetLevel() >= 80 ? BOT_PET_WORM : 0; + return _spec == BOT_SPEC_HUNTER_BEASTMASTERY && me->GetLevel() >= 80 ? BOT_PET_WORM : 0; + case BOTAI_MISC_AURA_TYPE: + return _myaspect; + break; default: return 0; } @@ -1999,14 +1980,14 @@ class hunter_bot : public CreatureScript default: break; } + + bot_ai::SetAIMiscValue(data, value); } void Reset() override { UnsummonAll(false); - myPetType = 0; - trapTimer = 0; stingTimer = 0; aspectTimer = 0; @@ -2016,7 +1997,7 @@ class hunter_bot : public CreatureScript petSummonTimer = 5000; - Aspect = 0; + _myaspect = 0; DefaultInit(); } @@ -2169,17 +2150,17 @@ class hunter_bot : public CreatureScript void FillAbilitiesSpecifics(Player const* player, std::list &specList) override { uint32 textId; - switch (Aspect) - { - case ASPECT_MONKEY: textId = BOT_TEXT_MONKEY; break; - case ASPECT_HAWK: textId = BOT_TEXT_HAWK; break; - case ASPECT_CHEETAH: textId = BOT_TEXT_CHEETAH; break; - case ASPECT_VIPER: textId = BOT_TEXT_VIPER; break; - case ASPECT_BEAST: textId = BOT_TEXT_BEAST; break; - case ASPECT_PACK: textId = BOT_TEXT_PACK; break; - case ASPECT_WILD: textId = BOT_TEXT_WILD; break; - case ASPECT_DRAGONHAWK: textId = BOT_TEXT_DRAGONHAWK; break; - default: textId = BOT_TEXT_NOASPECT; break; + switch (_myaspect) + { + case ASPECT_OF_THE_MONKEY_1: textId = BOT_TEXT_MONKEY; break; + case ASPECT_OF_THE_HAWK_1: textId = BOT_TEXT_HAWK; break; + case ASPECT_OF_THE_CHEETAH_1: textId = BOT_TEXT_CHEETAH; break; + case ASPECT_OF_THE_VIPER_1: textId = BOT_TEXT_VIPER; break; + case ASPECT_OF_THE_BEAST_1: textId = BOT_TEXT_BEAST; break; + case ASPECT_OF_THE_PACK_1: textId = BOT_TEXT_PACK; break; + case ASPECT_OF_THE_WILD_1: textId = BOT_TEXT_WILD; break; + case ASPECT_OF_THE_DRAGONHAWK_1: textId = BOT_TEXT_DRAGONHAWK; break; + default: textId = BOT_TEXT_NOASPECT; break; } specList.push_back(LocalizedNpcText(player, BOT_TEXT_ASPECT) + ": " + LocalizedNpcText(player, textId)); } @@ -2203,7 +2184,7 @@ class hunter_bot : public CreatureScript private: uint32 trapTimer, stingTimer, aspectTimer, flareTimer, misdirectionTimer, checkMendTimer; - uint8 Aspect; + uint32 _myaspect; //Pet uint32 myPetType; uint32 petSummonTimer; diff --git a/src/server/game/AI/NpcBots/bot_paladin_ai.cpp b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp index ebf231a8f383cd..c3caf416a77978 100644 --- a/src/server/game/AI/NpcBots/bot_paladin_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp @@ -143,15 +143,6 @@ enum PaladinPassives enum PaladinSpecial { - NOAURA = 0, - DEVOTIONAURA = 1, - CONCENTRATIONAURA = 2, - FIRERESAURA = 3, - FROSTRESAURA = 4, - SHADOWRESAURA = 5, - RETRIBUTIONAURA = 6, - CRUSADERAURA = 7, - SPECIFIC_BLESSING_WISDOM = 0x01, SPECIFIC_BLESSING_KINGS = 0x02, SPECIFIC_BLESSING_SANCTUARY = 0x04, @@ -263,6 +254,8 @@ class paladin_bot : public CreatureScript { _botclass = BOT_CLASS_PALADIN; + _myaura = 0; + InitUnitFlags(); } @@ -712,7 +705,7 @@ class paladin_bot : public CreatureScript xphploss > _heals[HOLY_LIGHT_1]) { //Aura Mastery - if (hp < 60 && _aura == CONCENTRATIONAURA && IsSpellReady(AURA_MASTERY_1, diff, false) && Rand() < 90 && + if (hp < 60 && _myaura == CONCENTRATION_AURA_1 && IsSpellReady(AURA_MASTERY_1, diff, false) && Rand() < 90 && ((!me->getAttackers().empty() && (*me->getAttackers().begin())->GetTypeId() == TYPEID_PLAYER) || me->GetMap()->Instanceable() || tanking)) if (doCast(me, GetSpell(AURA_MASTERY_1))) @@ -931,6 +924,11 @@ class paladin_bot : public CreatureScript return; //TODO: priority? + if (_myaura && GetSpell(_myaura) && (!idMap.contains(_myaura) || idMap[_myaura] < GetSpell(_myaura))) + { + if (doCast(me, GetSpell(_myaura))) + return; + } if (DEVOTION_AURA && (!(mask & SPECIFIC_AURA_DEVOTION) || idMap[DEVOTION_AURA_1] < DEVOTION_AURA) && (!RETRIBUTION_AURA || IsTank(master) || isProt)) @@ -2076,20 +2074,20 @@ class paladin_bot : public CreatureScript //Aura Helper if (caster == me) { - if (baseId == DEVOTION_AURA_1) - _aura = DEVOTIONAURA; - if (baseId == CONCENTRATION_AURA_1) - _aura = CONCENTRATIONAURA; - if (baseId == FIRE_RESISTANCE_AURA_1) - _aura = FIRERESAURA; - if (baseId == FROST_RESISTANCE_AURA_1) - _aura = FROSTRESAURA; - if (baseId == SHADOW_RESISTANCE_AURA_1) - _aura = SHADOWRESAURA; - if (baseId == RETRIBUTION_AURA_1) - _aura = RETRIBUTIONAURA; - if (baseId == CRUSADER_AURA_1) - _aura = CRUSADERAURA; + switch (baseId) + { + case DEVOTION_AURA_1: + case CONCENTRATION_AURA_1: + case FIRE_RESISTANCE_AURA_1: + case FROST_RESISTANCE_AURA_1: + case SHADOW_RESISTANCE_AURA_1: + case RETRIBUTION_AURA_1: + case CRUSADER_AURA_1: + SetAIMiscValue(BOTAI_MISC_AURA_TYPE, baseId); + break; + default: + break; + } } //immunity markers @@ -2166,6 +2164,31 @@ class paladin_bot : public CreatureScript return longRange ? CalcSpellMaxRange(GetSpell(EXORCISM_1) ? EXORCISM_1 : JUDGEMENT_OF_LIGHT_1) : 10.f; } + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_AURA_TYPE: + return _myaura; + default: + return 0; + } + } + + void SetAIMiscValue(uint32 data, uint32 value) override + { + switch (data) + { + case BOTAI_MISC_AURA_TYPE: + _myaura = value; + break; + default: + break; + } + + bot_ai::SetAIMiscValue(data, value); + } + void Reset() override { checkAuraTimer = 0; @@ -2174,7 +2197,6 @@ class paladin_bot : public CreatureScript checkBeaconTimer = 0; avDelayTimer = 0; shieldDelayTimer = 0; - _aura = NOAURA; _sacDamage = 0; CLEANSE = 0; @@ -2365,19 +2387,13 @@ class paladin_bot : public CreatureScript case HOLY_SHOCK_1: return HasRole(BOT_ROLE_HEAL); case DEVOTION_AURA_1: - return _aura != DEVOTIONAURA; case CONCENTRATION_AURA_1: - return _aura != CONCENTRATIONAURA; case FIRE_RESISTANCE_AURA_1: - return _aura != FIRERESAURA; case FROST_RESISTANCE_AURA_1: - return _aura != FROSTRESAURA; case SHADOW_RESISTANCE_AURA_1: - return _aura != SHADOWRESAURA; case RETRIBUTION_AURA_1: - return _aura != RETRIBUTIONAURA; case CRUSADER_AURA_1: - return _aura != CRUSADERAURA; + return _myaura != basespell; case PURIFY_1: return !GetSpell(CLEANSE_1); default: @@ -2389,16 +2405,16 @@ class paladin_bot : public CreatureScript void FillAbilitiesSpecifics(Player const* player, std::list &specList) override { uint32 textId; - switch (_aura) + switch (_myaura) { - case DEVOTIONAURA: textId = BOT_TEXT_DEVOTION; break; - case CONCENTRATIONAURA: textId = BOT_TEXT_CONCENTRATION; break; - case FIRERESAURA: textId = BOT_TEXT_FIRERESISTANCE; break; - case FROSTRESAURA: textId = BOT_TEXT_FROSTRESISTANCE; break; - case SHADOWRESAURA: textId = BOT_TEXT_SHADOWRESISTANCE; break; - case RETRIBUTIONAURA: textId = BOT_TEXT_RETRIBUTION; break; - case CRUSADERAURA: textId = BOT_TEXT_CRUSADER; break; - case NOAURA: default: textId = BOT_TEXT_NOAURA; break; + case DEVOTION_AURA_1: textId = BOT_TEXT_DEVOTION; break; + case CONCENTRATION_AURA_1: textId = BOT_TEXT_CONCENTRATION; break; + case FIRE_RESISTANCE_AURA_1: textId = BOT_TEXT_FIRERESISTANCE; break; + case FROST_RESISTANCE_AURA_1: textId = BOT_TEXT_FROSTRESISTANCE; break; + case SHADOW_RESISTANCE_AURA_1: textId = BOT_TEXT_SHADOWRESISTANCE; break; + case RETRIBUTION_AURA_1: textId = BOT_TEXT_RETRIBUTION; break; + case CRUSADER_AURA_1: textId = BOT_TEXT_CRUSADER; break; + default: textId = BOT_TEXT_NOAURA; break; } specList.push_back(LocalizedNpcText(player, BOT_TEXT_AURA) + ": " + LocalizedNpcText(player, textId)); } @@ -2454,7 +2470,7 @@ class paladin_bot : public CreatureScript //Timers /*misc*/uint32 checkAuraTimer, checkSealTimer, checkShieldTimer, checkBeaconTimer, avDelayTimer, shieldDelayTimer; //Special -/*misc*/uint8 _aura; +/*misc*/uint32 _myaura; /*misc*/int32 _sacDamage; typedef std::unordered_map HealMap; diff --git a/src/server/game/AI/NpcBots/bot_rogue_ai.cpp b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp index ffde5d8e643291..3aa526cf2c1d5e 100644 --- a/src/server/game/AI/NpcBots/bot_rogue_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp @@ -219,6 +219,13 @@ class rogue_bot : public CreatureScript { _botclass = BOT_CLASS_ROGUE; + mhEnchantExpireTimer = 1; + ohEnchantExpireTimer = 1; + mhEnchant = 0; + ohEnchant = 0; + needChooseMHEnchant = true; + needChooseOHEnchant = true; + InitUnitFlags(); } @@ -1735,9 +1742,9 @@ class rogue_bot : public CreatureScript return needChooseMHEnchant; case BOTAI_MISC_ENCHANT_IS_AUTO_OH: return needChooseOHEnchant; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + case BOTAI_MISC_ENCHANT_TIMER_MH: return mhEnchantExpireTimer; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + case BOTAI_MISC_ENCHANT_TIMER_OH: return ohEnchantExpireTimer; case BOTAI_MISC_ENCHANT_CURRENT_MH: return mhEnchant; @@ -1770,25 +1777,33 @@ class rogue_bot : public CreatureScript case BOTAI_MISC_DAGGER_OFFHAND: isdaggerOH = bool(value); break; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: - if (value) - mhEnchantExpireTimer = 0; + case BOTAI_MISC_ENCHANT_IS_AUTO_MH: + needChooseMHEnchant = bool(value); + break; + case BOTAI_MISC_ENCHANT_IS_AUTO_OH: + needChooseOHEnchant = bool(value); break; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: - if (value) - ohEnchantExpireTimer = 0; + case BOTAI_MISC_ENCHANT_TIMER_MH: + if (value == 0) + mhEnchantExpireTimer = value; + break; + case BOTAI_MISC_ENCHANT_TIMER_OH: + if (value == 0) + ohEnchantExpireTimer = value; break; case BOTAI_MISC_ENCHANT_CURRENT_MH: mhEnchant = value; - needChooseMHEnchant = value ? false : true; + SetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_MH, value ? false : true); break; case BOTAI_MISC_ENCHANT_CURRENT_OH: ohEnchant = value; - needChooseOHEnchant = value ? false : true; + SetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_OH, value ? false : true); break; default: break; } + + bot_ai::SetAIMiscValue(data, value); } void Reset() override @@ -1803,11 +1818,6 @@ class rogue_bot : public CreatureScript DefaultInit(); - mhEnchant = 0; - ohEnchant = 0; - needChooseMHEnchant = true; - needChooseOHEnchant = true; - //after InitEquips Item const* mh = GetEquips(BOT_SLOT_MAINHAND); Item const* oh = GetEquips(BOT_SLOT_OFFHAND); diff --git a/src/server/game/AI/NpcBots/bot_shaman_ai.cpp b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp index 71a6bfee0fe4b8..2a16d26487f1ff 100644 --- a/src/server/game/AI/NpcBots/bot_shaman_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp @@ -315,6 +315,13 @@ class shaman_bot : public CreatureScript { _botclass = BOT_CLASS_SHAMAN; + mhEnchantExpireTimer = 1; + ohEnchantExpireTimer = 1; + mhEnchant = 0; + ohEnchant = 0; + needChooseMHEnchant = true; + needChooseOHEnchant = true; + InitUnitFlags(); } @@ -2389,9 +2396,9 @@ class shaman_bot : public CreatureScript return needChooseMHEnchant; case BOTAI_MISC_ENCHANT_IS_AUTO_OH: return needChooseOHEnchant; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + case BOTAI_MISC_ENCHANT_TIMER_MH: return mhEnchantExpireTimer; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + case BOTAI_MISC_ENCHANT_TIMER_OH: return ohEnchantExpireTimer; case BOTAI_MISC_ENCHANT_CURRENT_MH: return mhEnchant; @@ -2418,25 +2425,33 @@ class shaman_bot : public CreatureScript { switch (data) { - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: - if (value) - mhEnchantExpireTimer = 0; + case BOTAI_MISC_ENCHANT_IS_AUTO_MH: + needChooseMHEnchant = bool(value); + break; + case BOTAI_MISC_ENCHANT_IS_AUTO_OH: + needChooseOHEnchant = bool(value); break; - case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: - if (value) - ohEnchantExpireTimer = 0; + case BOTAI_MISC_ENCHANT_TIMER_MH: + if (value == 0) + mhEnchantExpireTimer = value; + break; + case BOTAI_MISC_ENCHANT_TIMER_OH: + if (value == 0) + ohEnchantExpireTimer = value; break; case BOTAI_MISC_ENCHANT_CURRENT_MH: mhEnchant = value; - needChooseMHEnchant = value ? false : true; + SetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_MH, value ? false : true); break; case BOTAI_MISC_ENCHANT_CURRENT_OH: ohEnchant = value; - needChooseOHEnchant = value ? false : true; + SetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_OH, value ? false : true); break; default: break; } + + bot_ai::SetAIMiscValue(data, value); } void Reset() override @@ -2463,15 +2478,10 @@ class shaman_bot : public CreatureScript Earthy = false; maelUseUp = false; - mhEnchantExpireTimer = 1; - ohEnchantExpireTimer = 1; + mhEnchantExpireTimer = std::min(mhEnchantExpireTimer, 1); + ohEnchantExpireTimer = std::min(ohEnchantExpireTimer, 1); DefaultInit(); - - mhEnchant = 0; - ohEnchant = 0; - needChooseMHEnchant = true; - needChooseOHEnchant = true; } void ReduceCD(uint32 diff) override diff --git a/src/server/game/AI/NpcBots/bot_warlock_ai.cpp b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp index 473287f9a4bea8..47405266353dd6 100644 --- a/src/server/game/AI/NpcBots/bot_warlock_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp @@ -226,6 +226,8 @@ class warlock_bot : public CreatureScript { _botclass = BOT_CLASS_WARLOCK; + myPetType = 0; + InitUnitFlags(); } @@ -1885,14 +1887,14 @@ class warlock_bot : public CreatureScript default: break; } + + bot_ai::SetAIMiscValue(data, value); } void Reset() override { UnsummonAll(false); - myPetType = 0; - fearTimer = 0; banishTimer = 0; unbanishTimer = 0; diff --git a/src/server/game/AI/NpcBots/bot_warrior_ai.cpp b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp index f0e5a3a6ab1679..b455dc325b28f4 100644 --- a/src/server/game/AI/NpcBots/bot_warrior_ai.cpp +++ b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp @@ -1907,7 +1907,7 @@ class warrior_bot : public CreatureScript OnOwnerDamagedBy(u); } - void SetAIMiscValue(uint32 data, uint32 /*value*/) override + void SetAIMiscValue(uint32 data, uint32 value) override { switch (data) { @@ -1920,6 +1920,8 @@ class warrior_bot : public CreatureScript default: break; } + + bot_ai::SetAIMiscValue(data, value); } void Reset() override diff --git a/src/server/game/AI/NpcBots/botcommon.h b/src/server/game/AI/NpcBots/botcommon.h index 4536321f9927c0..d7875b765c166a 100644 --- a/src/server/game/AI/NpcBots/botcommon.h +++ b/src/server/game/AI/NpcBots/botcommon.h @@ -133,23 +133,38 @@ enum BotCommonValues GAME_EVENT_WINTER_VEIL = 2, //COMMON FACTIONS FACTION_TEMPLATE_HATES_EVERYTHING_1 = 2150, //faction 966 - Monster spar buddy -//COMMON AI MISC VALUES - BOTAI_MISC_COMBO_POINTS = 1, - BOTAI_MISC_DAGGER_MAINHAND, - BOTAI_MISC_DAGGER_OFFHAND, - BOTAI_MISC_ENCHANT_IS_AUTO_MH, - BOTAI_MISC_ENCHANT_IS_AUTO_OH, - BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH, - BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH, - BOTAI_MISC_ENCHANT_CURRENT_MH, - BOTAI_MISC_ENCHANT_CURRENT_OH, + //SOUNDS + SOUND_FREEZE_IMPACT_WINDWALK = 29, + SOUND_AXE_2H_IMPACT_FLESH_CRIT = 158, + SOUND_ABSORB_GET_HIT = 3334, + SOUND_MISS_WHOOSH_2H = 7081, + +//UNUSED + + //MAX_LOOT_ITEMS = 18 // Client limitation 3.3.5 code confirmed +}; + +enum BotMiscValues : uint32 +{ +//SAVED + BOTAI_MISC_ENCHANT_IS_AUTO_MH = 1, + BOTAI_MISC_ENCHANT_IS_AUTO_OH = 2, + BOTAI_MISC_ENCHANT_TIMER_MH = 3, + BOTAI_MISC_ENCHANT_TIMER_OH = 4, + BOTAI_MISC_ENCHANT_CURRENT_MH = 5, + BOTAI_MISC_ENCHANT_CURRENT_OH = 6, + BOTAI_MISC_PET_TYPE = 7, + BOTAI_MISC_AURA_TYPE = 8, +//INTERNAL BOTAI_MISC_ENCHANT_AVAILABLE_1, BOTAI_MISC_ENCHANT_AVAILABLE_2, BOTAI_MISC_ENCHANT_AVAILABLE_3, BOTAI_MISC_ENCHANT_AVAILABLE_4, BOTAI_MISC_ENCHANT_AVAILABLE_5, BOTAI_MISC_ENCHANT_AVAILABLE_6, - BOTAI_MISC_PET_TYPE, + BOTAI_MISC_COMBO_POINTS, + BOTAI_MISC_DAGGER_MAINHAND, + BOTAI_MISC_DAGGER_OFFHAND, BOTAI_MISC_PET_AVAILABLE_1, BOTAI_MISC_PET_AVAILABLE_2, BOTAI_MISC_PET_AVAILABLE_3, @@ -169,15 +184,9 @@ enum BotCommonValues BOTPETAI_MISC_CARRY, BOTPETAI_MISC_CAPACITY, BOTPETAI_MISC_MAX_ATTACKERS, - //SOUNDS - SOUND_FREEZE_IMPACT_WINDWALK = 29, - SOUND_AXE_2H_IMPACT_FLESH_CRIT = 158, - SOUND_ABSORB_GET_HIT = 3334, - SOUND_MISS_WHOOSH_2H = 7081, -//UNUSED - - //MAX_LOOT_ITEMS = 18 // Client limitation 3.3.5 code confirmed + BOT_MISCVALUE_SAVED_FIRST = BOTAI_MISC_ENCHANT_IS_AUTO_MH, + BOT_MISCVALUE_SAVED_LAST = BOTAI_MISC_AURA_TYPE }; enum BotClasses : uint8 diff --git a/src/server/game/AI/NpcBots/botdatamgr.cpp b/src/server/game/AI/NpcBots/botdatamgr.cpp index c68cf20b6f5871..d03a16c00ef7cd 100644 --- a/src/server/game/AI/NpcBots/botdatamgr.cpp +++ b/src/server/game/AI/NpcBots/botdatamgr.cpp @@ -848,11 +848,13 @@ void BotDataMgr::LoadNpcBots(bool spawn) BOT_LOG_INFO("server.loading", ">> Bots transmog data is not loaded. Table `characters_npcbot_transmog` is empty!"); // 0 1 2 3 4 5 - result = CharacterDatabase.Query("SELECT entry, owner, roles, spec, faction, UNIX_TIMESTAMP(hire_time)," + result = CharacterDatabase.Query("SELECT entry, owner, roles, spec, faction, UNIX_TIMESTAMP(hire_time), " // 6 7 8 9 10 11 12 13 14 - "equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet," - // 15 16 17 18 19 20 21 22 23 24 - "equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck, spells_disabled FROM characters_npcbot"); + "equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet, " + // 15 16 17 18 19 20 21 22 23 + "equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck, " + // 24 25 + "spells_disabled, miscvalues FROM characters_npcbot"); std::vector entryList; if (result) @@ -896,6 +898,17 @@ void BotDataMgr::LoadNpcBots(bool spawn) botData->disabled_spells.insert(*(Bcore::StringTo(tok[i]))); } + std::string miscvalues_str = field[++index].Get(); + if (!miscvalues_str.empty()) + { + std::vector tok = Bcore::Tokenize(miscvalues_str, ' ', false); + for (std::vector::size_type i = 0; i != tok.size(); ++i) + { + std::vector tok2 = Bcore::Tokenize(tok[i], ':', false); + botData->miscvalues.emplace(*(Bcore::StringTo(tok2[0])), *(Bcore::StringTo(tok2[1]))); + } + } + entryList.push_back(entry); _botsData[entry] = botData; ++datacounter; @@ -2706,6 +2719,20 @@ void BotDataMgr::UpdateNpcBotData(uint32 entry, NpcBotDataUpdateType updateType, CharacterDatabase.Execute(bstmt); break; } + case NPCBOT_UPDATE_MISCVALUES: + { + NpcBotData::MiscValuesContainer const* miscvals = (NpcBotData::MiscValuesContainer const*)(data); + std::ostringstream ss; + for (NpcBotData::MiscValuesContainer::const_iterator citr = miscvals->cbegin(); citr != miscvals->cend(); ++citr) + ss << citr->first << ':' << citr->second << ' '; + + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_MISCVALUES); + //"UPDATE characters_npcbot SET miscvalues = ? WHERE entry = ?", CONNECTION_ASYNCH + bstmt->SetData(0, ss.str()); + bstmt->SetData(1, entry); + CharacterDatabase.Execute(bstmt); + break; + } case NPCBOT_UPDATE_EQUIPS: { Item** items = (Item**)(data); diff --git a/src/server/game/AI/NpcBots/botdatamgr.h b/src/server/game/AI/NpcBots/botdatamgr.h index ff0036fa97c3a7..67cac9e81ac60b 100644 --- a/src/server/game/AI/NpcBots/botdatamgr.h +++ b/src/server/game/AI/NpcBots/botdatamgr.h @@ -59,6 +59,7 @@ enum NpcBotDataUpdateType NPCBOT_UPDATE_ROLES, NPCBOT_UPDATE_SPEC, NPCBOT_UPDATE_DISABLED_SPELLS, + NPCBOT_UPDATE_MISCVALUES, NPCBOT_UPDATE_FACTION, NPCBOT_UPDATE_EQUIPS, NPCBOT_UPDATE_ERASE, @@ -69,6 +70,7 @@ enum NpcBotDataUpdateType struct NpcBotData { typedef std::set DisabledSpellsContainer; + typedef std::map MiscValuesContainer; friend class BotDataMgr; friend struct WanderingBotsGenerator; @@ -80,6 +82,7 @@ struct NpcBotData uint8 spec; uint32 equips[BOT_INVENTORY_SIZE]; DisabledSpellsContainer disabled_spells; + MiscValuesContainer miscvalues; private: explicit NpcBotData(uint32 iroles, uint32 ifaction, uint8 ispec = 1) : owner(0), hire_time(0), roles(iroles), faction(ifaction), spec(ispec)