diff --git a/zone/bot.cpp b/zone/bot.cpp index 124cb6bb1..dd7d7e9bc 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3607,6 +3607,8 @@ bool Bot::Spawn(Client* botCharacterOwner) { } } + MapSpellTypeLevels(); + if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) { OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline."); CheckBotSpells(); //This runs through a serious of checks and outputs any spells that are set to the wrong spell type in the database diff --git a/zone/bot.h b/zone/bot.h index d558b1a75..6cd4079b7 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -609,6 +609,7 @@ public: bool HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob* tar); void CheckBotSpells(); + void MapSpellTypeLevels(); [[nodiscard]] int GetMaxBuffSlots() const final { return EQ::spells::LONG_BUFFS; } [[nodiscard]] int GetMaxSongSlots() const final { return EQ::spells::SHORT_BUFFS; } @@ -951,6 +952,7 @@ public: void SetBotTimers(std::vector timers) { bot_timers = timers; } std::vector GetBotBlockedBuffs() { return bot_blocked_buffs; } void SetBotBlockedBuffs(std::vector blocked_buffs) { bot_blocked_buffs = blocked_buffs; } + const CommandedSpellTypesMinLevelMap& GetCommandedSpellTypesMinLevels() { return commanded_spells_min_level; } uint32 GetLastZoneID() const { return _lastZoneId; } int32 GetBaseAC() const { return _baseAC; } int32 GetBaseATK() const { return _baseATK; } @@ -1079,6 +1081,9 @@ protected: std::vector AIBot_spells; std::vector AIBot_spells_enforced; std::unordered_map> AIBot_spells_by_type; + + CommandedSpellTypesMinLevelMap commanded_spells_min_level; + std::vector bot_timers; std::vector bot_blocked_buffs; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 2ceb520bd..2c05b748f 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -64,1168 +64,6 @@ extern QueryServ* QServ; extern WorldServer worldserver; extern TaskManager *task_manager; -bcst_map bot_command_spells; -bcst_required_bot_classes_map required_bots_map; -bcst_required_bot_classes_map_by_class required_bots_map_by_class; - -class BCSpells -{ -public: - static void Load() { - bot_command_spells.clear(); - bcst_levels_map bot_levels_map; - - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { - bot_command_spells[static_cast(i)]; - bot_levels_map[static_cast(i)]; - } - - for (int spell_id = 2; spell_id < SPDAT_RECORDS; ++spell_id) { - if (!IsValidSpell(spell_id)) { - continue; - } - - if (spells[spell_id].player_1[0] == '\0') { - continue; - } - - if ( - spells[spell_id].target_type != ST_Target && - spells[spell_id].cast_restriction != 0 - ) { - continue; - } - - auto target_type = BCEnum::TT_None; - switch (spells[spell_id].target_type) { - case ST_GroupTeleport: - target_type = BCEnum::TT_GroupV1; - break; - case ST_AECaster: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AECaster; - break; - case ST_AEBard: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AEBard; - break; - case ST_Target: - switch (spells[spell_id].cast_restriction) { - case 0: - target_type = BCEnum::TT_Single; - break; - case 104: - target_type = BCEnum::TT_Animal; - break; - case 105: - target_type = BCEnum::TT_Plant; - break; - case 118: - target_type = BCEnum::TT_Summoned; - break; - case 120: - target_type = BCEnum::TT_Undead; - break; - default: - break; - } - break; - case ST_Self: - target_type = BCEnum::TT_Self; - break; - case ST_AETarget: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AETarget; - break; - case ST_Animal: - target_type = BCEnum::TT_Animal; - break; - case ST_Undead: - target_type = BCEnum::TT_Undead; - break; - case ST_Summoned: - target_type = BCEnum::TT_Summoned; - break; - case ST_Corpse: - target_type = BCEnum::TT_Corpse; - break; - case ST_Plant: - target_type = BCEnum::TT_Plant; - break; - case ST_Group: - target_type = BCEnum::TT_GroupV2; - break; - default: - break; - } - if (target_type == BCEnum::TT_None) - continue; - - uint8 class_levels[16] = {0}; - bool player_spell = false; - for (int class_type = Class::Warrior; class_type <= Class::Berserker; ++class_type) { - int class_index = CLASSIDTOINDEX(class_type); - if (spells[spell_id].classes[class_index] == 0 || - spells[spell_id].classes[class_index] > HARD_LEVEL_CAP) { - continue; - } - - class_levels[class_index] = spells[spell_id].classes[class_index]; - player_spell = true; - } - if (!player_spell) - continue; - - STBaseEntry* entry_prototype = nullptr; - while (true) { - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)]) { - case SE_BindAffinity: - entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); - break; - case SE_Charm: - if (spells[spell_id].spell_affect_index != 12) - break; - entry_prototype = new STCharmEntry(); - if (spells[spell_id].resist_difficulty <= -1000) - entry_prototype->SafeCastToCharm()->dire = true; - break; - case SE_Teleport: - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - break; - case SE_Succor: - if (!strcmp(spells[spell_id].teleport_zone, "same")) { - entry_prototype = new STEscapeEntry; - } else { - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_Translocate: - if (spells[spell_id].teleport_zone[0] == '\0') { - entry_prototype = new STSendHomeEntry(); - entry_prototype->SafeCastToSendHome()->group = BCSpells::IsGroupType(target_type); - } else { - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_ModelSize: - if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; - } else if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 0 && - spells[spell_id].base_value[EFFECTIDTOINDEX(1)] < 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; - } - break; - case SE_Identify: - entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); - break; - case SE_Invisibility: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; - break; - case SE_SeeInvis: - if (spells[spell_id].spell_affect_index != 5) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; - break; - case SE_InvisVsUndead: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; - break; - case SE_InvisVsAnimals: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; - break; - case SE_Mez: - if (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)] != 31) - break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); - break; - case SE_Revive: - if (spells[spell_id].spell_affect_index != 1) - break; - entry_prototype = new STResurrectEntry(); - entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); - break; - case SE_Rune: - if (spells[spell_id].spell_affect_index != 2) - break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); - break; - case SE_SummonCorpse: - entry_prototype = new STBaseEntry(BCEnum::SpT_SummonCorpse); - break; - case SE_WaterBreathing: - entry_prototype = new STBaseEntry(BCEnum::SpT_WaterBreathing); - break; - default: - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(2)]) { - case SE_Succor: - entry_prototype = new STEscapeEntry; - std::string is_lesser = spells[spell_id].name; - if (is_lesser.find("Lesser") != std::string::npos) - entry_prototype->SafeCastToEscape()->lesser = true; - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(3)]) { - case SE_Lull: - entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); - break; - case SE_Levitate: // needs more criteria - entry_prototype = new STBaseEntry(BCEnum::SpT_Levitation); - break; - default: - break; - } - if (entry_prototype) - break; - - while (spells[spell_id].type_description_id == 27) { - if (!spells[spell_id].good_effect) - break; - if (spells[spell_id].skill != EQ::skills::SkillOffense && - spells[spell_id].skill != EQ::skills::SkillDefense) - break; - - entry_prototype = new STStanceEntry(); - if (spells[spell_id].skill == EQ::skills::SkillOffense) - entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Aggressive; - else - entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Defensive; - - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].spell_affect_index) { - case 1: { - bool valid_spell = false; - entry_prototype = new STCureEntry; - - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].effect_id[effect_index] != SE_Blind && - spells[spell_id].base_value[effect_index] >= 0) - continue; - else if (spells[spell_id].effect_id[effect_index] == SE_Blind && - !spells[spell_id].good_effect) - continue; - - switch (spells[spell_id].effect_id[effect_index]) { - case SE_Blind: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Blindness)] += spells[spell_id].base_value[effect_index]; - break; - case SE_DiseaseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Disease)] += spells[spell_id].base_value[effect_index]; - break; - case SE_PoisonCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Poison)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CurseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Curse)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CorruptionCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Corruption)] += spells[spell_id].base_value[effect_index]; - break; - default: - continue; - } - entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - - break; - } - case 2: { - bool valid_spell = false; - entry_prototype = new STResistanceEntry; - - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].max_value[effect_index] <= 0) - continue; - - switch (spells[spell_id].effect_id[effect_index]) { - case SE_ResistFire: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Fire)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistCold: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Cold)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistPoison: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Poison)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistDisease: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Disease)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistMagic: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Magic)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistCorruption: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Corruption)] += spells[spell_id].max_value[effect_index]; - break; - default: - continue; - } - entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].max_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - - break; - } - case 7: - case 10: - if (spells[spell_id].effect_description_id != 65) - break; - if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) - break; - entry_prototype = new STMovementSpeedEntry(); - entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); - break; - default: - break; - } - if (entry_prototype) - break; - - break; - } - if (!entry_prototype) - continue; - - if (target_type == BCEnum::TT_Self && (entry_prototype->BCST() != BCEnum::SpT_Stance && - entry_prototype->BCST() != BCEnum::SpT_SummonCorpse)) { -#ifdef BCSTSPELLDUMP - LogError("DELETING entry_prototype (primary clause) - name: [{}], target_type: [{}], BCST: [{}]", - spells[spell_id].name, BCEnum::TargetTypeEnumToString(target_type).c_str(), BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str()); -#endif - safe_delete(entry_prototype); - continue; - } - if (entry_prototype->BCST() == BCEnum::SpT_Stance && target_type != BCEnum::TT_Self) { -#ifdef BCSTSPELLDUMP - LogError("DELETING entry_prototype (secondary clause) - name: [{}], BCST: [{}], target_type: [{}]", - spells[spell_id].name, BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str(), BCEnum::TargetTypeEnumToString(target_type).c_str()); -#endif - safe_delete(entry_prototype); - continue; - } - - assert(entry_prototype->BCST() != BCEnum::SpT_None); - - entry_prototype->spell_id = spell_id; - entry_prototype->target_type = target_type; - - bcst_levels& bot_levels = bot_levels_map[entry_prototype->BCST()]; - for (int class_type = Class::Warrior; class_type <= Class::Berserker; ++class_type) { - int class_index = CLASSIDTOINDEX(class_type); - if (!class_levels[class_index]) - continue; - - STBaseEntry* spell_entry = nullptr; - switch (entry_prototype->BCST()) { - case BCEnum::SpT_Charm: - if (entry_prototype->IsCharm()) - spell_entry = new STCharmEntry(entry_prototype->SafeCastToCharm()); - break; - case BCEnum::SpT_Cure: - if (entry_prototype->IsCure()) - spell_entry = new STCureEntry(entry_prototype->SafeCastToCure()); - break; - case BCEnum::SpT_Depart: - if (entry_prototype->IsDepart()) - spell_entry = new STDepartEntry(entry_prototype->SafeCastToDepart()); - break; - case BCEnum::SpT_Escape: - if (entry_prototype->IsEscape()) - spell_entry = new STEscapeEntry(entry_prototype->SafeCastToEscape()); - break; - case BCEnum::SpT_Invisibility: - if (entry_prototype->IsInvisibility()) - spell_entry = new STInvisibilityEntry(entry_prototype->SafeCastToInvisibility()); - break; - case BCEnum::SpT_MovementSpeed: - if (entry_prototype->IsMovementSpeed()) - spell_entry = new STMovementSpeedEntry(entry_prototype->SafeCastToMovementSpeed()); - break; - case BCEnum::SpT_Resistance: - if (entry_prototype->IsResistance()) - spell_entry = new STResistanceEntry(entry_prototype->SafeCastToResistance()); - break; - case BCEnum::SpT_Resurrect: - if (entry_prototype->IsResurrect()) - spell_entry = new STResurrectEntry(entry_prototype->SafeCastToResurrect()); - break; - case BCEnum::SpT_SendHome: - if (entry_prototype->IsSendHome()) - spell_entry = new STSendHomeEntry(entry_prototype->SafeCastToSendHome()); - break; - case BCEnum::SpT_Size: - if (entry_prototype->IsSize()) - spell_entry = new STSizeEntry(entry_prototype->SafeCastToSize()); - break; - case BCEnum::SpT_Stance: - if (entry_prototype->IsStance()) - spell_entry = new STStanceEntry(entry_prototype->SafeCastToStance()); - break; - default: - spell_entry = new STBaseEntry(entry_prototype); - break; - } - - assert(spell_entry); - - spell_entry->caster_class = class_type; - spell_entry->spell_level = class_levels[class_index]; - - bot_command_spells[spell_entry->BCST()].push_back(spell_entry); - - if (bot_levels.find(class_type) == bot_levels.end() || - bot_levels[class_type] > class_levels[class_index]) - bot_levels[class_type] = class_levels[class_index]; - } - - delete(entry_prototype); - } - - remove_inactive(); - order_all(); - load_teleport_zone_names(); - build_strings(bot_levels_map); - status_report(); - -#ifdef BCSTSPELLDUMP - spell_dump(); -#endif - } - - static void Unload() { - for (auto map_iter : bot_command_spells) { - if (map_iter.second.empty()) - continue; - for (auto list_iter: map_iter.second) { - safe_delete(list_iter); - } - map_iter.second.clear(); - } - bot_command_spells.clear(); - required_bots_map.clear(); - required_bots_map_by_class.clear(); - } - - static bool IsCasterCentered(BCEnum::TType target_type) { - switch (target_type) { - case BCEnum::TT_AECaster: - case BCEnum::TT_AEBard: - return true; - default: - return false; - } - } - - static bool IsGroupType(BCEnum::TType target_type) { - switch (target_type) { - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - return true; - default: - return false; - } - } -private: - static void remove_inactive() { - if (bot_command_spells.empty()) - return; - - for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { - if (map_iter->second.empty()) - continue; - - bcst_list* spells_list = &map_iter->second; - bcst_list* removed_spells_list = new bcst_list; - - spells_list->remove(nullptr); - spells_list->remove_if([removed_spells_list](STBaseEntry* l) { - if (l->spell_id < 2 || l->spell_id >= SPDAT_RECORDS || strlen(spells[l->spell_id].name) < 3) { - removed_spells_list->push_back(l); - return true; - } - else { - return false; - } - }); - - for (auto del_iter: *removed_spells_list) - { - safe_delete(del_iter); - } - removed_spells_list->clear(); - - if (RuleI(Bots, CommandSpellRank) == 1) { - spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) - return true; - - return false; - }); - spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { - std::string r_name = spells[r->spell_id].name; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) { - removed_spells_list->push_back(r); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - if (RuleI(Bots, CommandSpellRank) == 2) { - spells_list->remove_if([removed_spells_list](STBaseEntry* l) { - std::string l_name = spells[l->spell_id].name; - if (spells[l->spell_id].rank == 10) { - removed_spells_list->push_back(l); - return true; - } - if (l_name.find("III") == (l_name.size() - 3)) { - removed_spells_list->push_back(l); - return true; - } - if (l_name.find("III ") == (l_name.size() - 4)) { - removed_spells_list->push_back(l); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - // needs rework - if (RuleI(Bots, CommandSpellRank) == 2 || RuleI(Bots, CommandSpellRank) == 3) { - spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) - return true; - - return false; - }); - spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { - std::string l_name = spells[l->spell_id].name; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) { - removed_spells_list->push_back(r); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - safe_delete(removed_spells_list); - } - } - - static void order_all() { - // Example of a macro'd lambda using anonymous property dereference: - // #define XXX(p) ([](const <_Ty>* l, const <_Ty>* r) { return (l->p < r->p); }) - - -#define LT_STBASE(l, r, p) (l->p < r->p) -#define LT_STCHARM(l, r, p) (l->SafeCastToCharm()->p < r->SafeCastToCharm()->p) -#define LT_STCURE(l, r, p) (l->SafeCastToCure()->p < r->SafeCastToCure()->p) -#define LT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] < r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define LT_STDEPART(l, r, p) (l->SafeCastToDepart()->p < r->SafeCastToDepart()->p) -#define LT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p < r->SafeCastToEscape()->p) -#define LT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p < r->SafeCastToInvisibility()->p) -#define LT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p < r->SafeCastToResistance()->p) -#define LT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] < r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define LT_STSTANCE(l, r, p) (l->SafeCastToStance()->p < r->SafeCastToStance()->p) -#define LT_SPELLS(l, r, p) (spells[l->spell_id].p < spells[r->spell_id].p) -#define LT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] < spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define LT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) < 0) - -#define EQ_STBASE(l, r, p) (l->p == r->p) -#define EQ_STCHARM(l, r, p) (l->SafeCastToCharm()->p == r->SafeCastToCharm()->p) -#define EQ_STCURE(l, r, p) (l->SafeCastToCure()->p == r->SafeCastToCure()->p) -#define EQ_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] == r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define EQ_STDEPART(l, r, p) (l->SafeCastToDepart()->p == r->SafeCastToDepart()->p) -#define EQ_STESCAPE(l, r, p) (l->SafeCastToEscape()->p == r->SafeCastToEscape()->p) -#define EQ_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p == r->SafeCastToInvisibility()->p) -#define EQ_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p == r->SafeCastToResistance()->p) -#define EQ_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] == r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define EQ_STSTANCE(l, r, p) (l->SafeCastToStance()->p == r->SafeCastToStance()->p) -#define EQ_SPELLS(l, r, p) (spells[l->spell_id].p == spells[r->spell_id].p) -#define EQ_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] == spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define EQ_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) == 0) - -#define GT_STBASE(l, r, p) (l->p > r->p) -#define GT_STCHARM(l, r, p) (l->SafeCastToCharm()->p > r->SafeCastToCharm()->p) -#define GT_STCURE(l, r, p) (l->SafeCastToCure()->p > r->SafeCastToCure()->p) -#define GT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] > r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define GT_STDEPART(l, r, p) (l->SafeCastToDepart()->p > r->SafeCastToDepart()->p) -#define GT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p > r->SafeCastToEscape()->p) -#define GT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p > r->SafeCastToInvisibility()->p) -#define GT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p > r->SafeCastToResistance()->p) -#define GT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] > r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define GT_STSTANCE(l, r, p) (l->SafeCastToStance()->p > r->SafeCastToStance()->p) -#define GT_SPELLS(l, r, p) (spells[l->spell_id].p > spells[r->spell_id].p) -#define GT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] > spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define GT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) > 0) - - - for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { - if (map_iter->second.size() < 2) - continue; - - auto spell_type = map_iter->first; - bcst_list* spell_list = &map_iter->second; - switch (spell_type) { - case BCEnum::SpT_BindAffinity: - if (RuleB(Bots, PreferNoManaCommandSpells)) { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, mana)) - return true; - if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - else { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - continue; - case BCEnum::SpT_Charm: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Cure: // per-use sorting in command handler - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (l->spell_id < r->spell_id) - return true; - if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Depart: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, caster_class)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && EQ_STBASE(l, r, spell_level) && LT_SPELLS_STR(l, r, name)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Escape: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STESCAPE(l, r, lesser)) - return true; - if (EQ_STESCAPE(l, r, lesser) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Identify: - if (RuleB(Bots, PreferNoManaCommandSpells)) { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, mana)) - return true; - if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - else { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - continue; - case BCEnum::SpT_Invisibility: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STINVISIBILITY(l, r, invis_type)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - return false; - }); - continue; - case BCEnum::SpT_Levitation: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, zone_type)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Lull: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 3)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Mesmerize: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_MovementSpeed: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 2)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Resistance: // per-use sorting in command handler - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (l->spell_id < r->spell_id) - return true; - if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Resurrect: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Rune: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_SendHome: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Size: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - - auto l_size_type = l->SafeCastToSize()->size_type; - auto r_size_type = r->SafeCastToSize()->size_type; - if (l_size_type < r_size_type) - return true; - if (l_size_type == BCEnum::SzT_Enlarge && r_size_type == BCEnum::SzT_Enlarge) { - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - } - if (l_size_type == BCEnum::SzT_Reduce && r_size_type == BCEnum::SzT_Reduce) { - if (EQ_STBASE(l, r, target_type) && LT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - } - - return false; - }); - continue; - case BCEnum::SpT_Stance: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STSTANCE(l, r, stance_type)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_SummonCorpse: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && EQ_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_WaterBreathing: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - default: - continue; - } - } - } - - static void load_teleport_zone_names() { - auto depart_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (depart_list->empty()) - return; - - std::string query = "SELECT `short_name`, `long_name` FROM `zone` WHERE '' NOT IN (`short_name`, `long_name`)"; - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - LogError("load_teleport_zone_names() - Error in zone names query: [{}]", results.ErrorMessage().c_str()); - return; - } - - std::map zone_names; - for (auto row = results.begin(); row != results.end(); ++row) - zone_names[row[0]] = row[1]; - - for (auto list_iter = depart_list->begin(); list_iter != depart_list->end();) { - auto test_iter = zone_names.find(spells[(*list_iter)->spell_id].teleport_zone); - if (test_iter == zone_names.end()) { - list_iter = depart_list->erase(list_iter); - continue; - } - - (*list_iter)->SafeCastToDepart()->long_name = test_iter->second; - ++list_iter; - } - - } - - static void build_strings(bcst_levels_map& bot_levels_map) { - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) - helper_bots_string(static_cast(i), bot_levels_map[static_cast(i)]); - } - - static void status_report() { - LogCommands("load_bot_command_spells(): - 'RuleI(Bots, CommandSpellRank)' set to [{}]", RuleI(Bots, CommandSpellRank)); - if (bot_command_spells.empty()) { - LogError("load_bot_command_spells() - 'bot_command_spells' is empty"); - return; - } - - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) - LogCommands("load_bot_command_spells(): - [{}] returned [{}] spell entries", - BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), bot_command_spells[static_cast(i)].size()); - } - - static void helper_bots_string(BCEnum::SpType type_index, bcst_levels& bot_levels) { - for (int i = Class::Warrior; i <= Class::Berserker; ++i) - required_bots_map_by_class[type_index][i] = "Unavailable..."; - - if (bot_levels.empty()) { - required_bots_map[type_index] = "This command is currently unavailable..."; - return; - } - - required_bots_map[type_index] = ""; - - auto map_size = bot_levels.size(); - while (bot_levels.size()) { - bcst_levels::iterator test_iter = bot_levels.begin(); - for (bcst_levels::iterator levels_iter = bot_levels.begin(); levels_iter != bot_levels.end(); ++levels_iter) { - if (levels_iter->second < test_iter->second) - test_iter = levels_iter; - if (strcasecmp(GetClassIDName(levels_iter->first), GetClassIDName(test_iter->first)) < 0 && levels_iter->second <= test_iter->second) - test_iter = levels_iter; - } - - std::string bot_segment; - if (bot_levels.size() == map_size) - bot_segment = "%s(%u)"; - else if (bot_levels.size() > 1) - bot_segment = ", %s(%u)"; - else - bot_segment = " or %s(%u)"; - - required_bots_map[type_index].append(StringFormat(bot_segment.c_str(), GetClassIDName(test_iter->first), test_iter->second)); - required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", GetClassIDName(test_iter->first), test_iter->second); - bot_levels.erase(test_iter); - } - } - -#ifdef BCSTSPELLDUMP - static void spell_dump() { - std::ofstream spell_dump; - spell_dump.open(StringFormat("bcs_dump/spell_dump_%i.txt", getpid()), std::ios_base::app | std::ios_base::out); - - if (bot_command_spells.empty()) { - spell_dump << "BCSpells::spell_dump() - 'bot_command_spells' map is empty.\n"; - spell_dump.close(); - return; - } - - int entry_count = 0; - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { - auto bcst_id = static_cast(i); - spell_dump << StringFormat("BCSpells::spell_dump(): - '%s' returned %u spells:\n", - BCEnum::SpellTypeEnumToString(bcst_id).c_str(), bot_command_spells[bcst_id].size()); - - bcst_list& map_entry = bot_command_spells[bcst_id]; - for (auto list_iter = map_entry.begin(); list_iter != map_entry.end(); ++list_iter) { - STBaseEntry* list_entry = *list_iter; - int spell_id = list_entry->spell_id; - spell_dump << StringFormat("\"%20s\" tt:%02u/cc:%02u/cl:%03u", - ((strlen(spells[spell_id].name) > 20) ? (std::string(spells[spell_id].name).substr(0, 20).c_str()) : (spells[spell_id].name)), - list_entry->target_type, - list_entry->caster_class, - list_entry->spell_level - ); - - spell_dump << StringFormat(" /mn:%05u/RD:%06i/zt:%02i/d#:%06i/td#:%05i/ed#:%05i/SAI:%03u", - spells[spell_id].mana, - spells[spell_id].resist_difficulty, - spells[spell_id].zone_type, - spells[spell_id].description_id, - spells[spell_id].type_description_id, - spells[spell_id].effect_description_id, - spells[spell_id].spell_affect_index - ); - - for (int i = EffectIDFirst; i <= 3/*EffectIDLast*/; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - spell_dump << StringFormat(" /e%02i:%04i/b%02i:%06i/m%02i:%06i", - i, spells[spell_id].effect_id[effect_index], i, spells[spell_id].base_value[effect_index], i, spells[spell_id].max_value[effect_index]); - } - - switch (list_entry->BCST()) { - case BCEnum::SpT_Charm: - spell_dump << StringFormat(" /d:%c", ((list_entry->SafeCastToCharm()->dire) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Cure: - spell_dump << ' '; - for (int i = 0; i < BCEnum::AilmentTypeCount; ++i) { - spell_dump << StringFormat("/cv%02i:%03i", i, list_entry->SafeCastToCure()->cure_value[i]); - } - break; - case BCEnum::SpT_Depart: { - std::string long_name = list_entry->SafeCastToDepart()->long_name.c_str(); - spell_dump << StringFormat(" /ln:%20s", ((long_name.size() > 20) ? (long_name.substr(0, 20).c_str()) : (long_name.c_str()))); - break; - } - case BCEnum::SpT_Escape: - spell_dump << StringFormat(" /l:%c", ((list_entry->SafeCastToEscape()->lesser) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Invisibility: - spell_dump << StringFormat(" /it:%02i", list_entry->SafeCastToInvisibility()->invis_type); - break; - case BCEnum::SpT_MovementSpeed: - spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToMovementSpeed()->group) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Resistance: - spell_dump << ' '; - for (int i = 0; i < BCEnum::ResistanceTypeCount; ++i) { - spell_dump << StringFormat("/rv%02i:%03i", i, list_entry->SafeCastToResistance()->resist_value[i]); - } - break; - case BCEnum::SpT_Resurrect: - spell_dump << StringFormat(" /aoe:%c", ((list_entry->SafeCastToResurrect()->aoe) ? ('T') : ('F'))); - break; - case BCEnum::SpT_SendHome: - spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToSendHome()->group) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Size: - spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToSize()->size_type); - break; - case BCEnum::SpT_Stance: - spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToStance()->stance_type); - break; - default: - break; - } - - spell_dump << "\n"; - ++entry_count; - } - - spell_dump << StringFormat("required_bots_map[%s] = \"%s\"\n", - BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), required_bots_map[static_cast(i)].c_str()); - - spell_dump << "\n"; - } - - spell_dump << StringFormat("Total bcs entry count: %i\n", entry_count); - spell_dump.close(); - } -#endif -}; - int bot_command_count; int (*bot_command_dispatch)(Client *,char const *) = bot_command_not_avail; @@ -1465,8 +303,6 @@ int bot_command_init(void) bot_command_dispatch = bot_command_real_dispatch; - BCSpells::Load(); - return bot_command_count; } @@ -1477,8 +313,6 @@ void bot_command_deinit(void) bot_command_dispatch = bot_command_not_avail; bot_command_count = 0; - - BCSpells::Unload(); } int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function) @@ -1552,18 +386,18 @@ int bot_command_real_dispatch(Client *c, const char *message) } -bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc) +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, uint8 fail_type, const char* type_desc) { switch (fail_type) { - case BCEnum::AFT_Value: - bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); - return true; - case BCEnum::AFT_GenderRace: - bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); - return true; - case BCEnum::AFT_Race: - bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); - return true; + case AFT_Value: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); + return true; + case AFT_GenderRace: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); + return true; + case AFT_Race: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); + return true; default: return false; } @@ -1876,26 +710,6 @@ int helper_bot_follow_option_chain(Client* bot_owner) return chain_follow_count; } -bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast, uint32* dont_root_before) -{ - if (!casting_bot || !target_mob) - return false; - - casting_bot->InterruptSpell(); - if (annouce_cast) { - Bot::BotGroupSay( - casting_bot, - fmt::format( - "Attempting to cast {} on {}.", - spells[spell_id].name, - target_mob->GetCleanName() - ).c_str() - ); - } - - return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1, dont_root_before); -} - bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command) { if (!rule_value) { @@ -1917,104 +731,6 @@ bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, c return false; } -void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag) -{ - if (!bot_owner) { - return; - } - - if (!MyBots::IsMyBot(bot_owner, druid_bot)) { - druid_bot = nullptr; - } - - if (!MyBots::IsMyBot(bot_owner, wizard_bot)) { - wizard_bot = nullptr; - } - - if (!druid_bot && !wizard_bot) { - bot_owner->Message(Chat::Yellow, "No bots are capable of performing this action"); - return; - } - - if (!local_list) { - bot_owner->Message(Chat::Yellow, "There are no destinations you can be taken to."); - return; - } - - auto destination_count = 0; - auto destination_number = 1; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (!local_entry) { - continue; - } - - if ( - druid_bot && - druid_bot->GetClass() == local_entry->caster_class && - druid_bot->GetLevel() >= local_entry->spell_level - ) { - if (local_entry->single != single_flag) { - continue; - } - - druid_bot->OwnerMessage( - fmt::format( - "Destination {} | {} | {}", - destination_number, - local_entry->long_name, - Saylink::Silent( - fmt::format( - "^circle {}{}", - spells[local_entry->spell_id].teleport_zone, - single_flag ? " single" : "" - ), - "Goto" - ) - ) - ); - - destination_count++; - destination_number++; - continue; - } - - if ( - wizard_bot && - wizard_bot->GetClass() == local_entry->caster_class && - wizard_bot->GetLevel() >= local_entry->spell_level - ) { - if (local_entry->single != single_flag) { - continue; - } - - wizard_bot->OwnerMessage( - fmt::format( - "Destination {} | {} | {}", - destination_number, - local_entry->long_name, - Saylink::Silent( - fmt::format( - "^portal {}{}", - spells[local_entry->spell_id].teleport_zone, - single_flag ? " single" : "" - ), - "Goto" - ) - ) - ); - - destination_count++; - destination_number++; - continue; - } - } - - if (!destination_count) { - bot_owner->Message(Chat::Yellow, "There are no destinations you can be taken to."); - } -} - bool helper_is_help_or_usage(const char* arg) { if (!arg) @@ -2067,33 +783,59 @@ void helper_send_available_subcommands(Client* bot_owner, const char* command_si bot_owner->Message(Chat::White, "%d bot subcommand%s listed.", bot_subcommands_shown, bot_subcommands_shown != 1 ? "s" : ""); } -void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class) +void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type) { - bot_owner->Message(Chat::White, "requires one of the following bot classes:"); - if (bot_class) - bot_owner->Message(Chat::White, "%s", required_bots_map_by_class[spell_type][bot_class].c_str()); - else - bot_owner->Message(Chat::White, "%s", required_bots_map[spell_type].c_str()); -} - -bool helper_spell_check_fail(STBaseEntry* local_entry) -{ - if (!local_entry) - return true; - if (spells[local_entry->spell_id].zone_type && zone->GetZoneType() && !(spells[local_entry->spell_id].zone_type & zone->GetZoneType())) - return true; - - return false; -} - -bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type) -{ - if (!spell_list || spell_list->empty()) { - bot_owner->Message(Chat::Yellow, "%s", required_bots_map[spell_type].c_str()); - return true; + if (!bot_owner) { + return; } - return false; + auto sbl = entity_list.GetBotListByCharacterID(bot_owner->CharacterID()); + Bot* bot = nullptr; + + for (const auto& b : sbl) { + if (b) { + bot = b; + + break; + } + } + + auto& spell_map = bot->GetCommandedSpellTypesMinLevels(); + + if (spell_map.empty()) { + bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type"); //deleteme + return; + } + + bool found = false; + std::string description; + + for (int i = Class::Warrior; i <= Class::Berserker; ++i) { + auto spell_type_itr = spell_map.find(spell_type); + auto class_itr = spell_type_itr->second.find(i); + const auto& spell_info = class_itr->second; + + if (spell_info.min_level < UINT8_MAX) { + found = true; + + if (!description.empty()) { + description.append(", "); + } + else { + bot_owner->Message(Chat::Yellow, "Required bots to cast: Class [Class ID]: [Level]"); + } + + description.append(spell_info.description); + } + } + + if (!found || description.empty()) { + bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type"); + + return; + } + + bot_owner->Message(Chat::Green, "%s", description.c_str()); } void SendSpellTypeWindow(Client* c, const Seperator* sep) { diff --git a/zone/bot_command.h b/zone/bot_command.h index 1482bcc61..553f098c4 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -27,194 +27,6 @@ class Seperator; #include "bot.h" #include "dialogue_window.h" -class BCEnum -{ -public: - typedef enum SpellType { - SpT_None = 0, - SpT_BindAffinity, - SpT_Charm, - SpT_Cure, - SpT_Depart, - SpT_Escape, - SpT_Identify, - SpT_Invisibility, - SpT_Levitation, - SpT_Lull, - SpT_Mesmerize, - SpT_MovementSpeed, - SpT_Resistance, - SpT_Resurrect, - SpT_Rune, - SpT_SendHome, - SpT_Size, - SpT_Stance, - SpT_SummonCorpse, - SpT_WaterBreathing - } SpType; - static const int SpellTypeFirst = SpT_BindAffinity; - static const int SpellTypeLast = SpT_WaterBreathing; - - typedef enum TargetType { - TT_None = 0, - TT_Corpse, - TT_Self, - TT_Animal, - TT_Undead, - TT_Summoned, - TT_Plant, - TT_Single, - TT_GroupV1, - TT_GroupV2, - TT_AECaster, - TT_AEBard, - TT_AETarget - } TType; - static const int TargetTypeFirst = TT_Corpse; - static const int TargetTypeLast = TT_AETarget; - static const int TargetTypeCount = 13; - - typedef enum TargetMask { - TM_None = 0, - TM_Corpse = 1, - TM_Self = 2, - TM_Animal = 4, - TM_Undead = 8, - TM_Summoned = 16, - TM_Plant = 32, - TM_Single = 124, // currently, 2^6 + 2^{2..5}) -or- (64+32+16+8+4) - TM_GroupV1 = 128, - TM_GroupV2 = 256, - TM_AECaster = 512, - TM_AEBard = 1024, - TM_AETarget = 2048 - } TMask; - - typedef enum AppearanceFailType { - AFT_None = 0, - AFT_Value, - AFT_GenderRace, - AFT_Race - } AFType; - - typedef enum AilmentType { - AT_None = 0, - AT_Blindness, // SE: 20 - AT_Disease, // SE: 35 - AT_Poison, // SE: 36 - AT_Curse, // SE: 116 - AT_Corruption // SE: 369 - } AType; - static const int AilmentTypeCount = 5; - - typedef enum InvisType { - IT_None = 0, - IT_Animal, - IT_Undead, - IT_Living, - IT_See - } IType; - - typedef enum ResistanceType { - RT_None = 0, - RT_Fire, // SE: 46 - RT_Cold, // SE: 47 - RT_Poison, // SE: 48 - RT_Disease, // SE: 49 - RT_Magic, // SE: 50 - RT_Corruption // SE: 370 - } RType; - static const int ResistanceTypeCount = 6; - - typedef enum SizeType { - SzT_None = 0, - SzT_Enlarge, - SzT_Reduce - } SzType; - - typedef enum StanceType { - StT_None = 0, - StT_Aggressive, - StT_Defensive - } StType; - - static std::string SpellTypeEnumToString(BCEnum::SpType spell_type) { - switch (spell_type) { - case SpT_BindAffinity: - return "SpT_BindAffinity"; - case SpT_Charm: - return "SpT_Charm"; - case SpT_Cure: - return "SpT_Cure"; - case SpT_Depart: - return "SpT_Depart"; - case SpT_Escape: - return "SpT_Escape"; - case SpT_Identify: - return "SpT_Identify"; - case SpT_Invisibility: - return "SpT_Invisibility"; - case SpT_Levitation: - return "SpT_Levitation"; - case SpT_Lull: - return "SpT_Lull"; - case SpT_Mesmerize: - return "SpT_Mesmerize"; - case SpT_MovementSpeed: - return "SpT_MovementSpeed"; - case SpT_Resistance: - return "SpT_Resistance"; - case SpT_Resurrect: - return "SpT_Resurrect"; - case SpT_Rune: - return "SpT_Rune"; - case SpT_SendHome: - return "SpT_SendHome"; - case SpT_Size: - return "SpT_Size"; - case SpT_Stance: - return "SpT_Stance"; - case SpT_SummonCorpse: - return "SpT_SummonCorpse"; - case SpT_WaterBreathing: - return "SpT_WaterBreathing"; - default: - return "SpT_None"; - } - } - - static std::string TargetTypeEnumToString(BCEnum::TType target_type) { - switch (target_type) { - case TT_Self: - return "TT_Self"; - case TT_Animal: - return "TT_Animal"; - case TT_Undead: - return "TT_Undead"; - case TT_Summoned: - return "TT_Summoned"; - case TT_Plant: - return "TT_Plant"; - case TT_Single: - return "TT_Single"; - case TT_GroupV1: - return "TT_GroupV1"; - case TT_GroupV2: - return "TT_GroupV2"; - case TT_AECaster: - return "TT_AECaster"; - case TT_AEBard: - return "TT_AEBard"; - case TT_AETarget: - return "TT_AETarget"; - case TT_Corpse: - return "TT_Corpse"; - default: - return "TT_None"; - } - } -}; - namespace { #define HP_RATIO_DELTA 5.0f @@ -222,14 +34,17 @@ namespace enum { EffectIDFirst = 1, EffectIDLast = 12 }; #define VALIDATECLASSID(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x) : (0)) -#define CLASSIDTOINDEX(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x - 1) : (0)) -#define EFFECTIDTOINDEX(x) ((x >= EffectIDFirst && x <= EffectIDLast) ? (x - 1) : (0)) -#define AILMENTIDTOINDEX(x) ((x >= BCEnum::AT_Blindness && x <= BCEnum::AT_Corruption) ? (x - 1) : (0)) -#define RESISTANCEIDTOINDEX(x) ((x >= BCEnum::RT_Fire && x <= BCEnum::RT_Corruption) ? (x - 1) : (0)) // ActionableTarget action_type #define FRIENDLY true #define ENEMY false + + enum { + AFT_None = 0, + AFT_Value, + AFT_GenderRace, + AFT_Race + }; } namespace MyBots @@ -759,128 +574,6 @@ namespace ActionableTarget return false; } - - static Mob* VerifyFriendly(Client* bot_owner, BCEnum::TType target_type, bool return_me_on_null_target = true) { - if (IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - Mob* verified_friendly = nullptr; - switch (target_type) { - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_friendly = target_mob; - break; - case BCEnum::TT_Animal: - if (target_mob && target_mob->GetBodyType() == BodyType::Animal) - verified_friendly = target_mob; - break; - case BCEnum::TT_Undead: - if (target_mob && target_mob->GetBodyType() == BodyType::Undead) - verified_friendly = target_mob; - break; - case BCEnum::TT_Summoned: - if (target_mob && target_mob->GetBodyType() == BodyType::Summoned) - verified_friendly = target_mob; - break; - case BCEnum::TT_Plant: - if (target_mob && target_mob->GetBodyType() == BodyType::Plant) - verified_friendly = target_mob; - break; - case BCEnum::TT_Corpse: - if (target_mob && target_mob->IsCorpse()) - verified_friendly = target_mob; - break; - default: - return nullptr; - } - - if (return_me_on_null_target && !target_mob && !verified_friendly) { - switch (target_type) { - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_friendly = bot_owner; - break; - default: - break; - } - } - - return verified_friendly; - } - - static Mob* VerifyEnemy(Client* bot_owner, BCEnum::TType target_type) { - if (!IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - Mob* verified_enemy = nullptr; - switch (target_type) { - case BCEnum::TT_Animal: - if (target_mob->GetBodyType() == BodyType::Animal) - verified_enemy = target_mob; - break; - case BCEnum::TT_Undead: - if (target_mob->GetBodyType() == BodyType::Undead) - verified_enemy = target_mob; - break; - case BCEnum::TT_Summoned: - if (target_mob->GetBodyType() == BodyType::Summoned) - verified_enemy = target_mob; - break; - case BCEnum::TT_Plant: - if (target_mob->GetBodyType() == BodyType::Plant) - verified_enemy = target_mob; - break; - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_enemy = target_mob; - break; - case BCEnum::TT_Corpse: - if (target_mob->IsCorpse()) - verified_enemy = target_mob; - break; - default: - return nullptr; - } - - return verified_enemy; - } - - class Types { - Mob* target[BCEnum::TargetTypeCount]; - bool target_set[BCEnum::TargetTypeCount]; - - public: - Types() { Clear(); } - - void Clear() { - for (int i = BCEnum::TT_None; i <= BCEnum::TargetTypeLast; ++i) { - target[i] = nullptr; - target_set[i] = false; - } - target_set[BCEnum::TT_None] = true; - } - - Mob* Select(Client* bot_owner, BCEnum::TType target_type, bool action_type, bool return_me_on_null_target = true) { - if (target_set[target_type]) - return target[target_type]; - - if (action_type == FRIENDLY) - target[target_type] = VerifyFriendly(bot_owner, target_type, return_me_on_null_target); - else - target[target_type] = VerifyEnemy(bot_owner, target_type); - target_set[target_type] = true; - - return target[target_type]; - } - }; } namespace ActionableBots @@ -1228,7 +921,7 @@ namespace ActionableBots return nullptr; } - static Bot* Select_ByClass(Client* bot_owner, BCEnum::TType target_type, std::vector& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + static Bot* Select_ByClass(Client* bot_owner, int target_type, std::vector& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { if (!bot_owner || sbl.empty()) return nullptr; @@ -1239,7 +932,7 @@ namespace ActionableBots continue; if (petless && bot_iter->GetPet()) continue; - if (target_type == BCEnum::TT_GroupV1) { + if (target_type == ST_GroupTeleport) { if (!target_mob) return nullptr; else if (bot_iter->GetGroup() != target_mob->GetGroup()) @@ -1252,7 +945,7 @@ namespace ActionableBots return nullptr; } - static Bot* Select_ByMinLevelAndClass(Client* bot_owner, BCEnum::TType target_type, std::vector& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + static Bot* Select_ByMinLevelAndClass(Client* bot_owner, int target_type, std::vector& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { if (!bot_owner || sbl.empty()) return nullptr; @@ -1263,7 +956,7 @@ namespace ActionableBots continue; if (petless && bot_iter->GetPet()) continue; - if (target_type == BCEnum::TT_GroupV1) { + if (target_type == ST_GroupTeleport) { if (!target_mob) return nullptr; else if (bot_iter->GetGroup() != target_mob->GetGroup()) @@ -1332,315 +1025,6 @@ namespace ActionableBots } } - -class STBaseEntry; -class STCharmEntry; -class STCureEntry; -class STDepartEntry; -class STEscapeEntry; -class STInvisibilityEntry; -class STMovementSpeedEntry; -class STResistanceEntry; -class STResurrectEntry; -class STSendHomeEntry; -class STSizeEntry; -class STStanceEntry; - -class STBaseEntry -{ -protected: - BCEnum::SpType m_bcst; - -public: - int spell_id; - uint8 spell_level; - uint8 caster_class; - BCEnum::TType target_type; - - // A non-polymorphic constructor requires an appropriate, non-'ST_None' BCEnum::SType - STBaseEntry(BCEnum::SpType init_bcst = BCEnum::SpT_None) { - spell_id = 0; - spell_level = 255; - caster_class = 255; - target_type = BCEnum::TT_None; - m_bcst = init_bcst; - } - STBaseEntry(STBaseEntry* prototype) { - spell_id = prototype->spell_id; - spell_level = 255; - caster_class = 255; - target_type = prototype->target_type; - m_bcst = prototype->BCST(); - } - virtual ~STBaseEntry() { return; }; - - BCEnum::SpType BCST() { return m_bcst; } - - virtual bool IsDerived() { return false; } - - bool IsCharm() const { return (m_bcst == BCEnum::SpT_Charm); } - bool IsCure() const { return (m_bcst == BCEnum::SpT_Cure); } - bool IsDepart() const { return (m_bcst == BCEnum::SpT_Depart); } - bool IsEscape() const { return (m_bcst == BCEnum::SpT_Escape); } - bool IsInvisibility() const { return (m_bcst == BCEnum::SpT_Invisibility); } - bool IsMovementSpeed() const { return (m_bcst == BCEnum::SpT_MovementSpeed); } - bool IsResistance() const { return (m_bcst == BCEnum::SpT_Resistance); } - bool IsResurrect() const { return (m_bcst == BCEnum::SpT_Resurrect); } - bool IsSendHome() const { return (m_bcst == BCEnum::SpT_SendHome); } - bool IsSize() const { return (m_bcst == BCEnum::SpT_Size); } - bool IsStance() const { return (m_bcst == BCEnum::SpT_Stance); } - - virtual STCharmEntry* SafeCastToCharm() { return nullptr; } - virtual STCureEntry* SafeCastToCure() { return nullptr; } - virtual STDepartEntry* SafeCastToDepart() { return nullptr; } - virtual STEscapeEntry* SafeCastToEscape() { return nullptr; } - virtual STInvisibilityEntry* SafeCastToInvisibility() { return nullptr; } - virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return nullptr; } - virtual STResistanceEntry* SafeCastToResistance() { return nullptr; } - virtual STResurrectEntry* SafeCastToResurrect() { return nullptr; } - virtual STSendHomeEntry* SafeCastToSendHome() { return nullptr; } - virtual STSizeEntry* SafeCastToSize() { return nullptr; } - virtual STStanceEntry* SafeCastToStance() { return nullptr; } -}; - -class STCharmEntry : public STBaseEntry -{ -public: - bool dire; - - STCharmEntry() { - m_bcst = BCEnum::SpT_Charm; - dire = false; - } - STCharmEntry(STCharmEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Charm; - dire = prototype->dire; - } - virtual ~STCharmEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STCharmEntry* SafeCastToCharm() { return ((m_bcst == BCEnum::SpT_Charm) ? (static_cast(this)) : (nullptr)); } -}; - -class STCureEntry : public STBaseEntry -{ -public: - int cure_value[BCEnum::AilmentTypeCount]; - int cure_total; - - STCureEntry() { - m_bcst = BCEnum::SpT_Cure; - memset(&cure_value, 0, (sizeof(int) * BCEnum::AilmentTypeCount)); - cure_total = 0; - } - STCureEntry(STCureEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Cure; - memcpy(&cure_value, prototype->cure_value, (sizeof(int) * BCEnum::AilmentTypeCount)); - cure_total = prototype->cure_total; - } - virtual ~STCureEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STCureEntry* SafeCastToCure() { return ((m_bcst == BCEnum::SpT_Cure) ? (static_cast(this)) : (nullptr)); } -}; - -class STDepartEntry : public STBaseEntry -{ -public: - bool single; - std::string long_name; - - STDepartEntry() { - m_bcst = BCEnum::SpT_Depart; - single = false; - long_name.clear(); - } - STDepartEntry(STDepartEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Depart; - single = prototype->single; - long_name = prototype->long_name; - } - virtual ~STDepartEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STDepartEntry* SafeCastToDepart() { return ((m_bcst == BCEnum::SpT_Depart) ? (static_cast(this)) : (nullptr)); } -}; - -class STEscapeEntry : public STBaseEntry -{ -public: - bool lesser; - - STEscapeEntry() { - m_bcst = BCEnum::SpT_Escape; - lesser = false; - } - STEscapeEntry(STEscapeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Escape; - lesser = prototype->lesser; - } - virtual ~STEscapeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STEscapeEntry* SafeCastToEscape() { return ((m_bcst == BCEnum::SpT_Escape) ? (static_cast(this)) : (nullptr)); } -}; - -class STInvisibilityEntry : public STBaseEntry -{ -public: - BCEnum::IType invis_type; - - STInvisibilityEntry() { - m_bcst = BCEnum::SpT_Invisibility; - invis_type = BCEnum::IT_None; - } - STInvisibilityEntry(STInvisibilityEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Invisibility; - invis_type = prototype->invis_type; - } - virtual ~STInvisibilityEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STInvisibilityEntry* SafeCastToInvisibility() { return ((m_bcst == BCEnum::SpT_Invisibility) ? (static_cast(this)) : (nullptr)); } -}; - -class STMovementSpeedEntry : public STBaseEntry -{ -public: - bool group; - - STMovementSpeedEntry() { - m_bcst = BCEnum::SpT_MovementSpeed; - group = false; - } - STMovementSpeedEntry(STMovementSpeedEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_MovementSpeed; - group = prototype->group; - } - virtual ~STMovementSpeedEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return ((m_bcst == BCEnum::SpT_MovementSpeed) ? (static_cast(this)) : (nullptr)); } -}; - -class STResistanceEntry : public STBaseEntry -{ -public: - int resist_value[BCEnum::ResistanceTypeCount]; - int resist_total; - - STResistanceEntry() { - m_bcst = BCEnum::SpT_Resistance; - memset(&resist_value, 0, (sizeof(int) * BCEnum::ResistanceTypeCount)); - resist_total = 0; - } - STResistanceEntry(STResistanceEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Resistance; - memcpy(&resist_value, prototype->resist_value, (sizeof(int) * BCEnum::ResistanceTypeCount)); - resist_total = prototype->resist_total; - } - virtual ~STResistanceEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STResistanceEntry* SafeCastToResistance() { return ((m_bcst == BCEnum::SpT_Resistance) ? (static_cast(this)) : (nullptr)); } -}; - -class STResurrectEntry : public STBaseEntry -{ -public: - bool aoe; - - STResurrectEntry() { - m_bcst = BCEnum::SpT_Resurrect; - aoe = false; - } - STResurrectEntry(STResurrectEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Resurrect; - aoe = prototype->aoe; - } - virtual ~STResurrectEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STResurrectEntry* SafeCastToResurrect() { return ((m_bcst == BCEnum::SpT_Resurrect) ? (static_cast(this)) : (nullptr)); } -}; - -class STSendHomeEntry : public STBaseEntry -{ -public: - bool group; - - STSendHomeEntry() { - m_bcst = BCEnum::SpT_SendHome; - group = false; - } - STSendHomeEntry(STSendHomeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_SendHome; - group = prototype->group; - } - virtual ~STSendHomeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STSendHomeEntry* SafeCastToSendHome() { return ((m_bcst == BCEnum::SpT_SendHome) ? (static_cast(this)) : (nullptr)); } -}; - -class STSizeEntry : public STBaseEntry -{ -public: - BCEnum::SzType size_type; - - STSizeEntry() { - m_bcst = BCEnum::SpT_Size; - size_type = BCEnum::SzT_None; - } - STSizeEntry(STSizeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Size; - size_type = prototype->size_type; - } - virtual ~STSizeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STSizeEntry* SafeCastToSize() { return ((m_bcst == BCEnum::SpT_Size) ? (static_cast(this)) : (nullptr)); } -}; - -class STStanceEntry : public STBaseEntry { -public: - BCEnum::StType stance_type; - - STStanceEntry() { - m_bcst = BCEnum::SpT_Stance; - stance_type = BCEnum::StT_None; - } - STStanceEntry(STStanceEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Stance; - stance_type = prototype->stance_type; - } - virtual ~STStanceEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STStanceEntry* SafeCastToStance() { return ((m_bcst == BCEnum::SpT_Stance) ? (static_cast(this)) : (nullptr)); } -}; - - -typedef std::list bcst_list; -typedef std::map bcst_map; - -typedef std::map bcst_required_bot_classes_map; -typedef std::map> bcst_required_bot_classes_map_by_class; - -typedef std::map bcst_levels; -typedef std::map bcst_levels_map; - #define BOT_COMMAND_CHAR '^' typedef void (*BotCmdFuncPtr)(Client *,const Seperator *); @@ -1791,21 +1175,17 @@ void bot_command_pet_set_type(Client *c, const Seperator *sep); // bot command helpers -bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc); +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, uint8 fail_type, const char* type_desc); void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot); void helper_bot_appearance_form_update(Bot *my_bot); uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender); int helper_bot_follow_option_chain(Client *bot_owner); -bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast = true, uint32* dont_root_before = nullptr); bool helper_command_disabled(Client *bot_owner, bool rule_value, const char *command); bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command); -void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag = false); bool helper_is_help_or_usage(const char* arg); bool helper_no_available_bots(Client *bot_owner, Bot *my_bot = nullptr); void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, std::vector subcommand_list); -void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class = Class::None); -bool helper_spell_check_fail(STBaseEntry* local_entry); -bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type); +void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type); void SendSpellTypeWindow(Client* c, const Seperator* sep); #endif diff --git a/zone/bot_commands/appearance.cpp b/zone/bot_commands/appearance.cpp index 5b8d883cc..5758846c9 100644 --- a/zone/bot_commands/appearance.cpp +++ b/zone/bot_commands/appearance.cpp @@ -45,11 +45,11 @@ void bot_command_beard_color(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != DWARF) - fail_type = BCEnum::AFT_GenderRace; + fail_type = AFT_GenderRace; else if (!PlayerAppearance::IsValidBeardColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetBeardColor(uvalue); @@ -82,11 +82,11 @@ void bot_command_beard_style(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != DWARF) - fail_type = BCEnum::AFT_GenderRace; + fail_type = AFT_GenderRace; else if (!PlayerAppearance::IsValidBeard(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetBeard(uvalue); @@ -121,11 +121,11 @@ void bot_command_details(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; + fail_type = AFT_Race; else if (!PlayerAppearance::IsValidDetail(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetDrakkinDetails(uvalue); @@ -280,9 +280,9 @@ void bot_command_eyes(Client *c, const Seperator *sep) //else if (!arg2.compare("right")) // eye_bias = 2; - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (!PlayerAppearance::IsValidEyeColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; } else { //if (eye_bias == 1) { @@ -327,9 +327,9 @@ void bot_command_face(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (!PlayerAppearance::IsValidFace(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; } else { uint8 old_woad = 0; @@ -367,9 +367,9 @@ void bot_command_hair_color(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (!PlayerAppearance::IsValidHairColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetHairColor(uvalue); @@ -402,9 +402,9 @@ void bot_command_hairstyle(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (!PlayerAppearance::IsValidHair(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetHairStyle(uvalue); @@ -439,11 +439,11 @@ void bot_command_heritage(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; + fail_type = AFT_Race; else if (!PlayerAppearance::IsValidHeritage(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetDrakkinHeritage(uvalue); @@ -478,11 +478,11 @@ void bot_command_tattoo(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; + fail_type = AFT_Race; else if (!PlayerAppearance::IsValidTattoo(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; else my_bot->SetDrakkinTattoo(uvalue); @@ -515,12 +515,12 @@ void bot_command_woad(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; + auto fail_type = AFT_None; if (my_bot->GetRace() != BARBARIAN) { - fail_type = BCEnum::AFT_Race; + fail_type = AFT_Race; } else if (!PlayerAppearance::IsValidWoad(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + fail_type = AFT_Value; } else { uint8 old_face = (my_bot->GetLuclinFace() % 10); diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 2d453c6f3..5d62102aa 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -229,7 +229,7 @@ void bot_command_cast(Client* c, const Seperator* sep) if (sep->IsNumber(1)) { spell_type = atoi(sep->arg[1]); - if (spell_type < BotSpellTypes::START || (spell_type > BotSpellTypes::END && spell_type < BotSpellTypes::COMMANDED_START) || spell_type > BotSpellTypes::COMMANDED_END) { + if (!c->IsValidSpellType(spell_type)) { c->Message( Chat::Yellow, fmt::format( @@ -621,6 +621,10 @@ void bot_command_cast(Client* c, const Seperator* sep) tar ? tar->GetCleanName() : "your target" ).c_str() ); + + if (!aa_type && !by_spell_id) { + helper_send_usage_required_bots(c, spell_type); + } } else { c->Message( diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp index eeceb6ce9..e082e05f0 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -36,10 +36,15 @@ void bot_command_pull(Client *c, const Seperator *sep) sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); - auto target_mob = ActionableTarget::VerifyEnemy(c, BCEnum::TT_Single); - if (!target_mob) { + auto target_mob = c->GetTarget(); + if ( + !target_mob || + target_mob == c || + !c->IsAttackAllowed(target_mob) + ) { c->Message(Chat::White, "Your current target is not attackable!"); + return; } diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 22d33957e..69e1ce672 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -122,4 +122,11 @@ struct BotBlockedBuffs_Struct { uint8_t blocked_pet; }; +struct BotSpellTypesByClass_Struct { + uint8_t min_level = 255; + std::string description; +}; + +using CommandedSpellTypesMinLevelMap = std::map>; + #endif // BOT_STRUCTS diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 78d6ed135..9be4ad282 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2821,6 +2821,63 @@ BotSpell Bot::GetBestBotSpellForNukeByBodyType(Bot* caster, uint8 body_type, uin return result; } +BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if ( + IsResurrectSpell(bot_spell_list_itr->SpellId) && + caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if ( + IsCharmSpell(bot_spell_list_itr->SpellId) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + + void Bot::CheckBotSpells() { auto spell_list = BotSpellsEntriesRepository::All(content_db); uint16 spell_id; @@ -2902,13 +2959,13 @@ void Bot::CheckBotSpells() { correct_type = GetCorrectSpellType(s.type, spell_id); parent_type = GetParentSpellType(correct_type); - + if (RuleB(Bots, UseParentSpellTypeForChecks)) { if (s.type == parent_type || s.type == correct_type) { continue; } } - else { + else { if (IsPetBotSpellType(s.type)) { correct_type = GetPetSpellType(correct_type); } @@ -2917,7 +2974,7 @@ void Bot::CheckBotSpells() { if (correct_type == s.type) { continue; } - + if (correct_type == UINT16_MAX) { LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown." , GetSpellName(spell_id) @@ -2949,58 +3006,51 @@ void Bot::CheckBotSpells() { } } -BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type) { - BotSpell result; +void Bot::MapSpellTypeLevels() { + commanded_spells_min_level.clear(); - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; + auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START }); + auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END }); - if (caster) { - std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive); + for (int i = start; i <= end; ++i) { + if (!Bot::IsValidSpellType(i)) { + continue; + } - for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsResurrectSpell(bot_spell_list_itr->SpellId) && - caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) - ) { - result.SpellId = bot_spell_list_itr->SpellId; - result.SpellIndex = bot_spell_list_itr->SpellIndex; - result.ManaCost = bot_spell_list_itr->ManaCost; - - break; - } + for (int x = Class::Warrior; x <= Class::Berserker; ++x) { + commanded_spells_min_level[i][x] = { UINT8_MAX, "" }; } } - return result; -} + auto spell_list = BotSpellsEntriesRepository::All(content_db); -BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_type) { - BotSpell result; + for (const auto& s : spell_list) { + if (!IsValidSpell(s.spell_id)) { + LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id); + continue; + } - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; + uint16_t spell_type = s.type; + int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX; + uint8_t min_level = s.minlevel; - if (caster) { - std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm); + if ( + !EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) || + !Bot::IsValidSpellType(spell_type) + ) { + continue; + } + + auto& spell_info = commanded_spells_min_level[spell_type][bot_class]; - for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsCharmSpell(bot_spell_list_itr->SpellId) && - caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) - ) { - result.SpellId = bot_spell_list_itr->SpellId; - result.SpellIndex = bot_spell_list_itr->SpellIndex; - result.ManaCost = bot_spell_list_itr->ManaCost; - - break; - } + if (min_level < spell_info.min_level) { + spell_info.min_level = min_level; + spell_info.description = StringFormat( + "%s [#%u]: Level %u", + GetClassIDName(bot_class), + bot_class, + min_level + ); } } - - return result; }