eqemu-server/zone/bot_command.cpp
Alex King 3f46210639
[Bots] Cleanup empty bot_commands files (#4095)
* [Bots] Cleanup empty `bot_commands` files

# Notes
- These files were part of the conversion of bot commands to individual files, these commands are part of overarching commands and therefore do not have their own files.
- These were not removed when initially committed.

* Update bot_command.cpp

* Update bot_command.cpp
2024-02-19 02:19:56 -06:00

2181 lines
82 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
To add a new bot command 3 things must be done:
1. At the bottom of bot_command.h you must add a prototype for it.
2. Add the function in this file.
3. In the bot_command_init function you must add a call to bot_command_add
for your function.
Notes: If you want an alias for your bot command, add an entry to the
`bot_command_settings` table in your database. The access level you
set with bot_command_add is the default setting if the bot command isn't
listed in the `bot_command_settings` db table.
*/
#include <string.h>
#ifdef _WINDOWS
#define strcasecmp _stricmp
#endif
#include "../common/data_verification.h"
#include "../common/global_define.h"
#include "../common/eq_packet.h"
#include "../common/features.h"
#include "../common/ptimer.h"
#include "../common/rulesys.h"
#include "../common/serverinfo.h"
#include "../common/strings.h"
#include "../common/say_link.h"
#include "bot_command.h"
#include "zonedb.h"
#include "qglobals.h"
#include "queryserv.h"
#include "quest_parser_collection.h"
#include "titles.h"
#include "water_map.h"
#include "worldserver.h"
#include "mob.h"
#include <fmt/format.h>
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<BCEnum::SpType>(i)];
bot_levels_map[static_cast<BCEnum::SpType>(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<std::string, std::string> 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<BCEnum::SpType>(i), bot_levels_map[static_cast<BCEnum::SpType>(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<BCEnum::SpType>(i)).c_str(), bot_command_spells[static_cast<BCEnum::SpType>(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<BCEnum::SpType>(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<BCEnum::SpType>(i)).c_str(), required_bots_map[static_cast<BCEnum::SpType>(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;
std::map<std::string, BotCommandRecord *> bot_command_list;
std::map<std::string, std::string> bot_command_aliases;
LinkedList<BotCommandRecord *> cleanup_bot_command_list;
int bot_command_not_avail(Client *c, const char *message)
{
c->Message(Chat::White, "Bot commands not available.");
return -1;
}
int bot_command_init(void)
{
bot_command_aliases.clear();
if (
bot_command_add("actionable", "Lists actionable command arguments and use descriptions", AccountStatus::Player, bot_command_actionable) ||
bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", AccountStatus::Player, bot_command_aggressive) ||
bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", AccountStatus::Player, bot_command_apply_poison) ||
bot_command_add("applypotion", "Applies cursor-held potion to a bot's effects", AccountStatus::Player, bot_command_apply_potion) ||
bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) ||
bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", AccountStatus::Player, bot_command_bind_affinity) ||
bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) ||
bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) ||
bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_command_beard_color) ||
bot_command_add("botbeardstyle", "Changes the beard style of a bot", AccountStatus::Player, bot_command_beard_style) ||
bot_command_add("botcamp", "Orders a bot(s) to camp", AccountStatus::Player, bot_command_camp) ||
bot_command_add("botclone", "Creates a copy of a bot", AccountStatus::GMMgmt, bot_command_clone) ||
bot_command_add("botcreate", "Creates a new bot", AccountStatus::Player, bot_command_create) ||
bot_command_add("botdelete", "Deletes all record of a bot", AccountStatus::Player, bot_command_delete) ||
bot_command_add("botdetails", "Changes the Drakkin details of a bot", AccountStatus::Player, bot_command_details) ||
bot_command_add("botdyearmor", "Changes the color of a bot's (bots') armor", AccountStatus::Player, bot_command_dye_armor) ||
bot_command_add("boteyes", "Changes the eye colors of a bot", AccountStatus::Player, bot_command_eyes) ||
bot_command_add("botface", "Changes the facial appearance of your bot", AccountStatus::Player, bot_command_face) ||
bot_command_add("botfollowdistance", "Changes the follow distance(s) of a bot(s)", AccountStatus::Player, bot_command_follow_distance) ||
bot_command_add("bothaircolor", "Changes the hair color of a bot", AccountStatus::Player, bot_command_hair_color) ||
bot_command_add("bothairstyle", "Changes the hairstyle of a bot", AccountStatus::Player, bot_command_hairstyle) ||
bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", AccountStatus::Player, bot_command_heritage) ||
bot_command_add("botinspectmessage", "Changes the inspect message of a bot", AccountStatus::Player, bot_command_inspect_message) ||
bot_command_add("botlist", "Lists the bots that you own", AccountStatus::Player, bot_command_list_bots) ||
bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", AccountStatus::Player, bot_command_out_of_combat) ||
bot_command_add("botreport", "Orders a bot to report its readiness", AccountStatus::Player, bot_command_report) ||
bot_command_add("botspawn", "Spawns a created bot", AccountStatus::Player, bot_command_spawn) ||
bot_command_add("botstance", "Changes the stance of a bot", AccountStatus::Player, bot_command_stance) ||
bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", AccountStatus::Player, bot_command_stop_melee_level) ||
bot_command_add("botsuffix", "Sets a bots suffix", AccountStatus::Player, bot_command_suffix) ||
bot_command_add("botsummon", "Summons bot(s) to your location", AccountStatus::Player, bot_command_summon) ||
bot_command_add("botsurname", "Sets a bots surname (last name)", AccountStatus::Player, bot_command_surname) ||
bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", AccountStatus::Player, bot_command_tattoo) ||
bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_archer) ||
bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", AccountStatus::Player, bot_command_toggle_helm) ||
bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_command_title) ||
bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_command_update) ||
bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) ||
bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) ||
bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) ||
bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) ||
bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) ||
bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) ||
bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) ||
bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) ||
bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) ||
bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", AccountStatus::Player, bot_command_escape) ||
bot_command_add("findaliases", "Find available aliases for a bot command", AccountStatus::Player, bot_command_find_aliases) ||
bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) ||
bot_command_add("guard", "Orders bots to guard their current positions", AccountStatus::Player, bot_command_guard) ||
bot_command_add("healrotation", "Lists the available bot heal rotation [subcommands]", AccountStatus::Player, bot_command_heal_rotation) ||
bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_adaptive_targeting) ||
bot_command_add("healrotationaddmember", "Adds a bot to a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_add_member) ||
bot_command_add("healrotationaddtarget", "Adds target to a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_add_target) ||
bot_command_add("healrotationadjustcritical", "Adjusts the critial HP limit of the heal rotation instance's Class Armor Type criteria", AccountStatus::Player, bot_command_heal_rotation_adjust_critical) ||
bot_command_add("healrotationadjustsafe", "Adjusts the safe HP limit of the heal rotation instance's Class Armor Type criteria", AccountStatus::Player, bot_command_heal_rotation_adjust_safe) ||
bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_casting_override) ||
bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_change_interval) ||
bot_command_add("healrotationclearhot", "Clears the HOT of a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_clear_hot) ||
bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_clear_targets) ||
bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", AccountStatus::Player, bot_command_heal_rotation_create) ||
bot_command_add("healrotationdelete", "Deletes a bot heal rotation entry by leader", AccountStatus::Player, bot_command_heal_rotation_delete) ||
bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_fast_heals) ||
bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", AccountStatus::Player, bot_command_heal_rotation_list) ||
bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_remove_member) ||
bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", AccountStatus::Player, bot_command_heal_rotation_remove_target) ||
bot_command_add("healrotationresetlimits", "Resets all Class Armor Type HP limit criteria in a heal rotation to its default value", AccountStatus::Player, bot_command_heal_rotation_reset_limits) ||
bot_command_add("healrotationsave", "Saves a bot heal rotation entry by leader", AccountStatus::Player, bot_command_heal_rotation_save) ||
bot_command_add("healrotationsethot", "Sets the HOT in a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_set_hot) ||
bot_command_add("healrotationstart", "Starts a heal rotation", AccountStatus::Player, bot_command_heal_rotation_start) ||
bot_command_add("healrotationstop", "Stops a heal rotation", AccountStatus::Player, bot_command_heal_rotation_stop) ||
bot_command_add("help", "List available commands and their description - specify partial command as argument to search", AccountStatus::Player, bot_command_help) ||
bot_command_add("hold", "Prevents a bot from attacking until released", AccountStatus::Player, bot_command_hold) ||
bot_command_add("identify", "Orders a bot to cast an item identification spell", AccountStatus::Player, bot_command_identify) ||
bot_command_add("inventory", "Lists the available bot inventory [subcommands]", AccountStatus::Player, bot_command_inventory) ||
bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_command_inventory_give) ||
bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_command_inventory_list) ||
bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_command_inventory_remove) ||
bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_command_inventory_window) ||
bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", AccountStatus::Player, bot_command_invisibility) ||
bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) ||
bot_command_add("levitation", "Orders a bot to cast a levitation spell", AccountStatus::Player, bot_command_levitation) ||
bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) ||
bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", AccountStatus::Player, bot_command_mesmerize) ||
bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", AccountStatus::Player, bot_command_movement_speed) ||
bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) ||
bot_command_add("pet", "Lists the available bot pet [subcommands]", AccountStatus::Player, bot_command_pet) ||
bot_command_add("petgetlost", "Orders a bot to remove its summoned pet", AccountStatus::Player, bot_command_pet_get_lost) ||
bot_command_add("petremove", "Orders a bot to remove its charmed pet", AccountStatus::Player, bot_command_pet_remove) ||
bot_command_add("petsettype", "Orders a Magician bot to use a specified pet type", AccountStatus::Player, bot_command_pet_set_type) ||
bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", AccountStatus::Player, bot_command_pick_lock) ||
bot_command_add("pickpocket", "Orders a capable bot to pickpocket a NPC", AccountStatus::Player, bot_command_pickpocket) ||
bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) ||
bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_portal) ||
bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) ||
bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) ||
bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", AccountStatus::Player, bot_command_resistance) ||
bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", AccountStatus::Player, bot_command_resurrect) ||
bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) ||
bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) ||
bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) ||
bot_command_add("spellinfo", "Opens a dialogue window with spell info", AccountStatus::Player, bot_spell_info_dialogue_window) ||
bot_command_add("spells", "Lists all Spells learned by the Bot.", AccountStatus::Player, bot_command_spell_list) ||
bot_command_add("spellsettings", "Lists a bot's spell setting entries", AccountStatus::Player, bot_command_spell_settings_list) ||
bot_command_add("spellsettingsadd", "Add a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_add) ||
bot_command_add("spellsettingsdelete", "Delete a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_delete) ||
bot_command_add("spellsettingstoggle", "Toggle a bot spell use", AccountStatus::Player, bot_command_spell_settings_toggle) ||
bot_command_add("spellsettingsupdate", "Update a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_update) ||
bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) ||
bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) ||
bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) ||
bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) ||
bot_command_add("track", "Orders a capable bot to track enemies", AccountStatus::Player, bot_command_track) ||
bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) ||
bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing)
) {
bot_command_deinit();
return -1;
}
std::map<std::string, std::pair<uint8, std::vector<std::string>>> bot_command_settings;
database.botdb.LoadBotCommandSettings(bot_command_settings);
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
std::vector<std::string> orphaned_bot_command_settings;
for (auto bcs_iter : bot_command_settings) {
auto bcl_iter = bot_command_list.find(bcs_iter.first);
if (bcl_iter == bot_command_list.end()) {
orphaned_bot_command_settings.push_back(bcs_iter.first);
LogInfo(
"Bot Command [{}] no longer exists... Deleting orphaned entry from `bot_command_settings` table",
bcs_iter.first.c_str()
);
}
}
if (orphaned_bot_command_settings.size()) {
if (!database.botdb.UpdateOrphanedBotCommandSettings(orphaned_bot_command_settings)) {
LogInfo("Failed to process 'Orphaned Bot Commands' update operation.");
}
}
auto working_bcl = bot_command_list;
for (auto working_bcl_iter : working_bcl) {
auto bcs_iter = bot_command_settings.find(working_bcl_iter.first);
if (bcs_iter == bot_command_settings.end()) {
injected_bot_command_settings.emplace_back(std::pair<std::string, uint8>(working_bcl_iter.first, working_bcl_iter.second->access));
LogInfo(
"New Bot Command [{}] found... Adding to `bot_command_settings` table with access [{}]",
working_bcl_iter.first.c_str(),
working_bcl_iter.second->access
);
if (working_bcl_iter.second->access == 0) {
LogCommands(
"bot_command_init(): Warning: Bot Command [{}] defaulting to access level 0!",
working_bcl_iter.first.c_str()
);
}
continue;
}
working_bcl_iter.second->access = bcs_iter->second.first;
LogCommands(
"bot_command_init(): - Bot Command [{}] set to access level [{}]",
working_bcl_iter.first.c_str(),
bcs_iter->second.first
);
if (bcs_iter->second.second.empty()) {
continue;
}
for (auto alias_iter : bcs_iter->second.second) {
if (alias_iter.empty()) {
continue;
}
if (bot_command_list.find(alias_iter) != bot_command_list.end()) {
LogCommands(
"bot_command_init(): Warning: Alias [{}] already exists as a bot command - skipping!",
alias_iter.c_str()
);
continue;
}
bot_command_list[alias_iter] = working_bcl_iter.second;
bot_command_aliases[alias_iter] = working_bcl_iter.first;
LogCommands(
"bot_command_init(): - Alias [{}] added to bot command [{}]",
alias_iter.c_str(),
bot_command_aliases[alias_iter].c_str()
);
}
}
if (injected_bot_command_settings.size()) {
if (!database.botdb.UpdateInjectedBotCommandSettings(injected_bot_command_settings)) {
LogInfo("Failed to process 'Injected Bot Commands' update operation.");
}
}
bot_command_dispatch = bot_command_real_dispatch;
BCSpells::Load();
return bot_command_count;
}
void bot_command_deinit(void)
{
bot_command_list.clear();
bot_command_aliases.clear();
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)
{
if (bot_command_name.empty()) {
LogError("bot_command_add() - Bot command added with empty name string - check bot_command.cpp");
return -1;
}
if (function == nullptr) {
LogError("bot_command_add() - Bot command [{}] added without a valid function pointer - check bot_command.cpp", bot_command_name.c_str());
return -1;
}
if (bot_command_list.count(bot_command_name) != 0) {
LogError("bot_command_add() - Bot command [{}] is a duplicate bot command name - check bot_command.cpp", bot_command_name.c_str());
return -1;
}
for (auto iter : bot_command_list) {
if (iter.second->function != function)
continue;
LogError("bot_command_add() - Bot command [{}] equates to an alias of [{}] - check bot_command.cpp", bot_command_name.c_str(), iter.first.c_str());
return -1;
}
BotCommandRecord *bcr = new BotCommandRecord;
bcr->access = access;
bcr->desc = desc;
bcr->function = function;
bot_command_list[bot_command_name] = bcr;
bot_command_aliases[bot_command_name] = bot_command_name;
cleanup_bot_command_list.Append(bcr);
bot_command_count++;
return 0;
}
int bot_command_real_dispatch(Client *c, const char *message)
{
Seperator sep(message, ' ', 10, 100, true); // "three word argument" should be considered 1 arg
std::string cstr(sep.arg[0]+1);
if(bot_command_list.count(cstr) != 1) {
return(-2);
}
BotCommandRecord *cur = bot_command_list[cstr];
if(c->Admin() < cur->access){
c->Message(Chat::White, "Your access level is not high enough to use this bot command.");
return(-1);
}
/* QS: Player_Log_Issued_Commands */
if (RuleB(QueryServ, PlayerLogIssuedCommandes)){
std::string event_desc = StringFormat("Issued bot command :: '%s' in zoneid:%i instid:%i", message, c->GetZoneID(), c->GetInstanceID());
QServ->PlayerLogEvent(Player_Log_Issued_Commands, c->CharacterID(), event_desc);
}
if(cur->access >= COMMANDS_LOGGING_MIN_STATUS) {
LogCommands("[{}] ([{}]) used bot command: [{}] (target=[{}])", c->GetName(), c->AccountName(), message, c->GetTarget()?c->GetTarget()->GetName():"NONE");
}
if(cur->function == nullptr) {
LogError("Bot command [{}] has a null function\n", cstr.c_str());
return(-1);
} else {
//dispatch C++ bot command
cur->function(c, &sep); // dispatch bot command
}
return 0;
}
bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc)
{
switch (fail_type) {
case BCEnum::AFT_Value:
bot_owner->Message(Chat::White, "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::White, "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::White, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName());
return true;
default:
return false;
}
}
void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot)
{
if (!MyBots::IsMyBot(bot_owner, my_bot))
return;
if (!my_bot->Save()) {
bot_owner->Message(Chat::White, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName());
return;
}
helper_bot_appearance_form_update(my_bot);
bot_owner->Message(Chat::White, "Successfully changed appearance for %s!", my_bot->GetCleanName());
}
void helper_bot_appearance_form_update(Bot *my_bot)
{
if (!my_bot) {
return;
}
my_bot->SendIllusionPacket(
AppearanceStruct{
.beard = my_bot->GetBeard(),
.beard_color = my_bot->GetBeardColor(),
.drakkin_details = my_bot->GetDrakkinDetails(),
.drakkin_heritage = my_bot->GetDrakkinHeritage(),
.drakkin_tattoo = my_bot->GetDrakkinTattoo(),
.eye_color_one = my_bot->GetEyeColor1(),
.eye_color_two = my_bot->GetEyeColor2(),
.face = my_bot->GetLuclinFace(),
.gender_id = my_bot->GetGender(),
.hair = my_bot->GetHairStyle(),
.hair_color = my_bot->GetHairColor(),
.helmet_texture = my_bot->GetHelmTexture(),
.race_id = my_bot->GetRace(),
.size = my_bot->GetSize(),
.texture = my_bot->GetTexture(),
}
);
}
uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender)
{
uint32 bot_id = 0;
if (!bot_owner) {
return bot_id;
}
if (!Bot::IsValidName(bot_name)) {
bot_owner->Message(
Chat::White,
fmt::format(
"'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z'. Mixed case {} allowed.",
bot_name, RuleB(Bots, AllowCamelCaseNames) ? "is" : "is not"
).c_str()
);
return bot_id;
}
bool available_flag = false;
if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) {
bot_owner->Message(
Chat::White,
fmt::format(
"Failed to query name availability for '{}'.",
bot_name
).c_str()
);
return bot_id;
}
if (!available_flag) {
bot_owner->Message(
Chat::White,
fmt::format(
"The name '{}' is already being used. Please choose a different name",
bot_name
).c_str()
);
return bot_id;
}
if (!Bot::IsValidRaceClassCombo(bot_race, bot_class)) {
const std::string bot_race_name = GetRaceIDName(bot_race);
const std::string bot_class_name = GetClassIDName(bot_class);
bot_owner->Message(
Chat::White,
fmt::format(
"{} {} is an invalid race-class combination, would you like to {} proper combinations for {}?",
bot_race_name,
bot_class_name,
Saylink::Silent(
fmt::format("^viewcombos {}", bot_race),
"view"
),
bot_race_name
).c_str()
);
return bot_id;
}
if (!EQ::ValueWithin(bot_gender, Gender::Male, Gender::Female)) {
bot_owner->Message(
Chat::White,
fmt::format(
"Gender: {} ({}) or {} ({})",
GetGenderName(Gender::Male),
Gender::Male,
GetGenderName(Gender::Female),
Gender::Female
).c_str()
);
return bot_id;
}
auto bot_creation_limit = bot_owner->GetBotCreationLimit();
auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
uint32 bot_count = 0;
uint32 bot_class_count = 0;
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
bot_owner->Message(Chat::White, "Failed to query bot count.");
return bot_id;
}
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
std::string message;
if (bot_creation_limit) {
message = fmt::format(
"You cannot create anymore than {} bot{}.",
bot_creation_limit,
bot_creation_limit != 1 ? "s" : ""
);
} else {
message = "You cannot create any bots.";
}
bot_owner->Message(Chat::White, message.c_str());
return bot_id;
}
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
std::string message;
if (bot_creation_limit_class) {
message = fmt::format(
"You cannot create anymore than {} {} bot{}.",
bot_creation_limit_class,
GetClassIDName(bot_class),
bot_creation_limit_class != 1 ? "s" : ""
);
} else {
message = fmt::format(
"You cannot create any {} bots.",
GetClassIDName(bot_class)
);
}
bot_owner->Message(Chat::White, message.c_str());
return bot_id;
}
auto bot_character_level = bot_owner->GetBotRequiredLevel();
if (
bot_character_level >= 0 &&
bot_owner->GetLevel() < bot_character_level
) {
bot_owner->Message(
Chat::White,
fmt::format(
"You must be level {} to use bots.",
bot_character_level
).c_str()
);
return bot_id;
}
auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class);
if (
bot_character_level_class >= 0 &&
bot_owner->GetLevel() < bot_character_level_class
) {
bot_owner->Message(
Chat::White,
fmt::format(
"You must be level {} to use {} bots.",
bot_character_level_class,
GetClassIDName(bot_class)
).c_str()
);
return bot_id;
}
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
if (!my_bot->Save()) {
bot_owner->Message(
Chat::White,
fmt::format(
"Failed to create '{}' due to unknown cause.",
my_bot->GetCleanName()
).c_str()
);
safe_delete(my_bot);
return bot_id;
}
bot_owner->Message(
Chat::White,
fmt::format(
"Bot Created | Name: {} ID: {} Race: {} Class: {}",
my_bot->GetCleanName(),
my_bot->GetBotID(),
GetRaceIDName(my_bot->GetRace()),
GetClassIDName(my_bot->GetClass())
).c_str()
);
bot_id = my_bot->GetBotID();
if (parse->PlayerHasQuestSub(EVENT_BOT_CREATE)) {
const auto& export_string = fmt::format(
"{} {} {} {} {}",
bot_name,
bot_id,
bot_race,
bot_class,
bot_gender
);
parse->EventPlayer(EVENT_BOT_CREATE, bot_owner, export_string, 0);
}
my_bot->AddBotStartingItems(bot_race, bot_class);
safe_delete(my_bot);
return bot_id;
}
void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot)
{
if (!bot_owner || !my_bot)
return;
switch (my_bot->GetClass()) {
case Class::Warrior:
case Class::Cleric:
case Class::Paladin:
case Class::Ranger:
case Class::ShadowKnight:
case Class::Druid:
case Class::Monk:
bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName());
break;
case Class::Bard:
bot_owner->Message(Chat::White, "%s will %s use out-of-combat behavior for bard songs", my_bot->GetCleanName(), ((my_bot->GetAltOutOfCombatBehavior()) ? ("now") : ("no longer")));
break;
case Class::Rogue:
case Class::Shaman:
case Class::Necromancer:
case Class::Wizard:
case Class::Magician:
case Class::Enchanter:
case Class::Beastlord:
case Class::Berserker:
bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName());
break;
default:
break;
bot_owner->Message(Chat::White, "Undefined bot class for %s", my_bot->GetCleanName());
}
}
int helper_bot_follow_option_chain(Client* bot_owner)
{
if (!bot_owner) {
return 0;
}
std::list<Bot*> sbl;
MyBots::PopulateSBL_BySpawnedBots(bot_owner, sbl);
if (sbl.empty()) {
return 0;
}
int chain_follow_count = 0;
Mob* followee = bot_owner;
// only add groups that do not belong to bot_owner
std::map<uint32, Group*> bot_group_map;
for (auto bot_iter : sbl) {
if (!bot_iter || bot_iter->GetManualFollow() || bot_iter->GetGroup() == bot_owner->GetGroup()) {
continue;
}
Group* bot_group = bot_iter->GetGroup();
if (!bot_iter->GetGroup()) {
continue;
}
bot_group_map[bot_group->GetID()] = bot_group;
}
std::list<Bot*> bot_member_list;
if (bot_owner->GetGroup()) {
bot_owner->GetGroup()->GetBotList(bot_member_list);
for (auto bot_member_iter : bot_member_list) {
if (!bot_member_iter || bot_member_iter->GetBotOwnerCharacterID() != bot_owner->CharacterID() || bot_member_iter == followee || bot_member_iter->GetManualFollow()) {
continue;
}
bot_member_iter->SetFollowID(followee->GetID());
followee = bot_member_iter;
++chain_follow_count;
}
}
for (auto bot_group_iter : bot_group_map) {
if (!bot_group_iter.second) {
continue;
}
bot_group_iter.second->GetBotList(bot_member_list);
for (auto bot_member_iter : bot_member_list) {
if (!bot_member_iter || bot_member_iter->GetBotOwnerCharacterID() != bot_owner->CharacterID() || bot_member_iter == followee || bot_member_iter->GetManualFollow()) {
continue;
}
bot_member_iter->SetFollowID(followee->GetID());
followee = bot_member_iter;
++chain_follow_count;
}
}
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) {
bot_owner->Message(Chat::White, "Bot command %s is not enabled on this server.", command);
return true;
}
return false;
}
bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command)
{
auto alias_iter = bot_command_aliases.find(&alias[1]);
if (alias_iter == bot_command_aliases.end() || alias_iter->second.compare(command)) {
bot_owner->Message(Chat::White, "Undefined linker usage in %s (%s)", command_handler, &alias[1]);
return true;
}
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::White, "No bots are capable of performing this action");
return;
}
if (!local_list) {
bot_owner->Message(Chat::White, "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::White, "There are no destinations you can be taken to.");
}
}
bool helper_is_help_or_usage(const char* arg)
{
if (!arg)
return false;
if (strcasecmp(arg, "help") && strcasecmp(arg, "usage"))
return false;
return true;
}
bool helper_no_available_bots(Client *bot_owner, Bot *my_bot)
{
if (!bot_owner)
return true;
if (!my_bot) {
bot_owner->Message(Chat::White, "No bots are capable of performing this action");
return true;
}
return false;
}
void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, const std::list<const char*>& subcommand_list)
{
bot_owner->Message(Chat::White, "Available %s management subcommands:", command_simile);
int bot_subcommands_shown = 0;
for (const auto subcommand_iter : subcommand_list) {
auto find_iter = bot_command_list.find(subcommand_iter);
if (find_iter == bot_command_list.end())
continue;
if (bot_owner->Admin() < find_iter->second->access)
continue;
bot_owner->Message(
Chat::White,
fmt::format(
"^{} - {}",
subcommand_iter,
find_iter != bot_command_list.end() ? find_iter->second->desc : "No Description"
).c_str()
);
++bot_subcommands_shown;
}
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)
{
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::White, "%s", required_bots_map[spell_type].c_str());
return true;
}
return false;
}
#include "bot_commands/actionable.cpp"
#include "bot_commands/aggressive.cpp"
#include "bot_commands/appearance.cpp"
#include "bot_commands/apply_poison.cpp"
#include "bot_commands/apply_potion.cpp"
#include "bot_commands/attack.cpp"
#include "bot_commands/bind_affinity.cpp"
#include "bot_commands/bot.cpp"
#include "bot_commands/caster_range.cpp"
#include "bot_commands/charm.cpp"
#include "bot_commands/click_item.cpp"
#include "bot_commands/cure.cpp"
#include "bot_commands/defensive.cpp"
#include "bot_commands/depart.cpp"
#include "bot_commands/escape.cpp"
#include "bot_commands/find_aliases.cpp"
#include "bot_commands/follow.cpp"
#include "bot_commands/guard.cpp"
#include "bot_commands/heal_rotation.cpp"
#include "bot_commands/help.cpp"
#include "bot_commands/hold.cpp"
#include "bot_commands/identify.cpp"
#include "bot_commands/inventory.cpp"
#include "bot_commands/invisibility.cpp"
#include "bot_commands/item_use.cpp"
#include "bot_commands/levitation.cpp"
#include "bot_commands/lull.cpp"
#include "bot_commands/mesmerize.cpp"
#include "bot_commands/movement_speed.cpp"
#include "bot_commands/name.cpp"
#include "bot_commands/owner_option.cpp"
#include "bot_commands/pet.cpp"
#include "bot_commands/pick_lock.cpp"
#include "bot_commands/pickpocket.cpp"
#include "bot_commands/precombat.cpp"
#include "bot_commands/pull.cpp"
#include "bot_commands/release.cpp"
#include "bot_commands/resistance.cpp"
#include "bot_commands/resurrect.cpp"
#include "bot_commands/rune.cpp"
#include "bot_commands/send_home.cpp"
#include "bot_commands/size.cpp"
#include "bot_commands/spell.cpp"
#include "bot_commands/summon.cpp"
#include "bot_commands/summon_corpse.cpp"
#include "bot_commands/suspend.cpp"
#include "bot_commands/taunt.cpp"
#include "bot_commands/teleport.cpp"
#include "bot_commands/timer.cpp"
#include "bot_commands/track.cpp"
#include "bot_commands/view_combos.cpp"
#include "bot_commands/water_breathing.cpp"