mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
7894 lines
268 KiB
C++
7894 lines
268 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.
|
|
|
|
*/
|
|
|
|
#ifdef BOTS
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
#include <thread>
|
|
|
|
#ifdef _WINDOWS
|
|
#define strcasecmp _stricmp
|
|
#endif
|
|
|
|
#include "../common/global_define.h"
|
|
#include "../common/eq_packet.h"
|
|
#include "../common/features.h"
|
|
#include "../common/guilds.h"
|
|
#include "../common/patches/patches.h"
|
|
#include "../common/ptimer.h"
|
|
#include "../common/rulesys.h"
|
|
#include "../common/serverinfo.h"
|
|
#include "../common/string_util.h"
|
|
#include "../common/say_link.h"
|
|
#include "../common/eqemu_logsys.h"
|
|
|
|
|
|
#include "bot_command.h"
|
|
#include "bot_database.h"
|
|
#include "guild_mgr.h"
|
|
#include "map.h"
|
|
#include "doors.h"
|
|
#include "qglobals.h"
|
|
#include "queryserv.h"
|
|
#include "quest_parser_collection.h"
|
|
#include "string_ids.h"
|
|
#include "titles.h"
|
|
#include "water_map.h"
|
|
#include "worldserver.h"
|
|
|
|
extern QueryServ* QServ;
|
|
extern WorldServer worldserver;
|
|
extern TaskManager *taskmanager;
|
|
void CatchSignal(int sig_num);
|
|
|
|
|
|
/*
|
|
* file-scope helper objects
|
|
*/
|
|
namespace
|
|
{
|
|
//#define BCSTSPELLDUMP // only needed if you're adding/tailoring bot command spells and need a file dump
|
|
|
|
#define m_message CC_WhiteSmoke
|
|
#define m_action CC_Yellow
|
|
#define m_note CC_Gray
|
|
#define m_usage CC_Cyan
|
|
#define m_fail CC_Red
|
|
#define m_unknown CC_Magenta
|
|
|
|
#define HP_RATIO_DELTA 5.0f
|
|
|
|
enum { EffectIDFirst = 1, EffectIDLast = 12 };
|
|
|
|
#define CLASSIDTOINDEX(x) ((x >= WARRIOR && x <= 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
|
|
}
|
|
|
|
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 (spells[spell_id].player_1[0] == '\0')
|
|
continue;
|
|
if (spells[spell_id].targettype != ST_Target && spells[spell_id].CastRestriction != 0) // watch
|
|
continue;
|
|
|
|
auto target_type = BCEnum::TT_None;
|
|
switch (spells[spell_id].targettype) {
|
|
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].CastRestriction) {
|
|
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 = WARRIOR; class_type <= 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].effectid[EFFECTIDTOINDEX(1)]) {
|
|
case SE_BindAffinity:
|
|
entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity);
|
|
break;
|
|
case SE_Charm:
|
|
if (spells[spell_id].SpellAffectIndex != 12)
|
|
break;
|
|
entry_prototype = new STCharmEntry();
|
|
if (spells[spell_id].ResistDiff <= -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[EFFECTIDTOINDEX(1)] > 100) {
|
|
entry_prototype = new STSizeEntry;
|
|
entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge;
|
|
}
|
|
else if (spells[spell_id].base[EFFECTIDTOINDEX(1)] > 0 && spells[spell_id].base[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].SpellAffectIndex != 9)
|
|
break;
|
|
entry_prototype = new STInvisibilityEntry;
|
|
entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living;
|
|
break;
|
|
case SE_SeeInvis:
|
|
if (spells[spell_id].SpellAffectIndex != 5)
|
|
break;
|
|
entry_prototype = new STInvisibilityEntry;
|
|
entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See;
|
|
break;
|
|
case SE_InvisVsUndead:
|
|
if (spells[spell_id].SpellAffectIndex != 9)
|
|
break;
|
|
entry_prototype = new STInvisibilityEntry;
|
|
entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead;
|
|
break;
|
|
case SE_InvisVsAnimals:
|
|
if (spells[spell_id].SpellAffectIndex != 9)
|
|
break;
|
|
entry_prototype = new STInvisibilityEntry;
|
|
entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal;
|
|
break;
|
|
case SE_Mez:
|
|
if (spells[spell_id].SpellAffectIndex != 12)
|
|
break;
|
|
entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize);
|
|
break;
|
|
case SE_Revive:
|
|
if (spells[spell_id].SpellAffectIndex != 1)
|
|
break;
|
|
entry_prototype = new STResurrectEntry();
|
|
entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type);
|
|
break;
|
|
case SE_Rune:
|
|
if (spells[spell_id].SpellAffectIndex != 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].effectid[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].effectid[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].typedescnum == 27) {
|
|
if (!spells[spell_id].goodEffect)
|
|
break;
|
|
if (spells[spell_id].skill != EQEmu::skills::SkillOffense && spells[spell_id].skill != EQEmu::skills::SkillDefense)
|
|
break;
|
|
|
|
entry_prototype = new STStanceEntry();
|
|
if (spells[spell_id].skill == EQEmu::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].SpellAffectIndex) {
|
|
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].effectid[effect_index] != SE_Blind && spells[spell_id].base[effect_index] >= 0)
|
|
continue;
|
|
else if (spells[spell_id].effectid[effect_index] == SE_Blind && !spells[spell_id].goodEffect)
|
|
continue;
|
|
|
|
switch (spells[spell_id].effectid[effect_index]) {
|
|
case SE_Blind:
|
|
entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Blindness)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_DiseaseCounter:
|
|
entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Disease)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_PoisonCounter:
|
|
entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Poison)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_CurseCounter:
|
|
entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Curse)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_CorruptionCounter:
|
|
entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Corruption)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base[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].base[effect_index] <= 0)
|
|
continue;
|
|
|
|
switch (spells[spell_id].effectid[effect_index]) {
|
|
case SE_ResistFire:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Fire)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_ResistCold:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Cold)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_ResistPoison:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Poison)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_ResistDisease:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Disease)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_ResistMagic:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Magic)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
case SE_ResistCorruption:
|
|
entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Corruption)] += spells[spell_id].base[effect_index];
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base[effect_index];
|
|
valid_spell = true;
|
|
}
|
|
if (!valid_spell) {
|
|
safe_delete(entry_prototype);
|
|
entry_prototype = nullptr;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 7:
|
|
case 10:
|
|
if (spells[spell_id].effectdescnum != 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
|
|
Log(Logs::General, Logs::Error, "DELETING entry_prototype (primary clause) - name: %s, target_type: %s, BCST: %s",
|
|
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
|
|
Log(Logs::General, Logs::Error, "DELETING entry_prototype (secondary clause) - name: %s, BCST: %s, target_type: %s",
|
|
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 = WARRIOR; class_type <= 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].spellgroup < spells[r->spell_id].spellgroup)
|
|
return true;
|
|
if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class)
|
|
return true;
|
|
if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && 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].spellgroup == spells[r->spell_id].spellgroup && 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].spellgroup < spells[r->spell_id].spellgroup)
|
|
return true;
|
|
if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class)
|
|
return true;
|
|
if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && 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].spellgroup == spells[r->spell_id].spellgroup && 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, ResistDiff))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 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, zonetype))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && GT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && 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, ResistDiff))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 3))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 3) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 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, ResistDiff))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && GT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 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, 2))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 2) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 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, 1))
|
|
return true;
|
|
if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, target_type))
|
|
return true;
|
|
if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_SPELLS_EFFECT_ID(l, r, base, 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, 1))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 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, 1))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && GT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 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, 1))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 1) && GT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 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, 1))
|
|
return true;
|
|
if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, spell_level))
|
|
return true;
|
|
if (EQ_SPELLS_EFFECT_ID(l, r, base, 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 = database.QueryDatabase(query);
|
|
if (!results.Success()) {
|
|
Log(Logs::General, Logs::Error, "load_teleport_zone_names() - Error in zone names query: %s", 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() {
|
|
Log(Logs::General, Logs::Commands, "load_bot_command_spells(): - 'RuleI(Bots, CommandSpellRank)' set to %i.", RuleI(Bots, CommandSpellRank));
|
|
if (bot_command_spells.empty()) {
|
|
Log(Logs::General, Logs::Error, "load_bot_command_spells() - 'bot_command_spells' is empty.");
|
|
return;
|
|
}
|
|
|
|
for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i)
|
|
Log(Logs::General, Logs::Commands, "load_bot_command_spells(): - '%s' returned %u 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 = WARRIOR; i <= 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(Bot::ClassIdToString(levels_iter->first).c_str(), Bot::ClassIdToString(test_iter->first).c_str()) < 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(), Bot::ClassIdToString(test_iter->first).c_str(), test_iter->second));
|
|
required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", Bot::ClassIdToString(test_iter->first).c_str(), 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].ResistDiff,
|
|
spells[spell_id].zonetype,
|
|
spells[spell_id].descnum,
|
|
spells[spell_id].typedescnum,
|
|
spells[spell_id].effectdescnum,
|
|
spells[spell_id].SpellAffectIndex
|
|
);
|
|
|
|
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].effectid[effect_index], i, spells[spell_id].base[effect_index], i, spells[spell_id].max[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
|
|
};
|
|
|
|
|
|
//struct bcl_struct *bot_command_list; // the actual linked list of bot commands
|
|
int bot_command_count; // how many bot commands we have
|
|
|
|
// this is the pointer to the dispatch function, updated once
|
|
// init has been performed to point at the real function
|
|
int (*bot_command_dispatch)(Client *,char const *) = bot_command_not_avail;
|
|
|
|
// TODO: Find out what these are for...
|
|
void bot_command_bestz(Client *c, const Seperator *message);
|
|
void bot_command_pf(Client *c, const Seperator *message);
|
|
|
|
std::map<std::string, BotCommandRecord *> bot_command_list;
|
|
std::map<std::string, std::string> bot_command_aliases;
|
|
|
|
// All allocated BotCommandRecords get put in here so they get deleted on shutdown
|
|
LinkedList<BotCommandRecord *> cleanup_bot_command_list;
|
|
|
|
|
|
/*
|
|
* bot_command_not_avail
|
|
* This is the default dispatch function when commands aren't loaded.
|
|
*
|
|
* Parameters:
|
|
* not used
|
|
*
|
|
*/
|
|
int bot_command_not_avail(Client *c, const char *message)
|
|
{
|
|
c->Message(m_fail, "Bot commands not available.");
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
/* the rest below here could be in a dynamically loaded module eventually *
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
|
|
Access Levels:
|
|
|
|
0 Normal
|
|
10 * Steward *
|
|
20 * Apprentice Guide *
|
|
50 * Guide *
|
|
80 * QuestTroupe *
|
|
81 * Senior Guide *
|
|
85 * GM-Tester *
|
|
90 * EQ Support *
|
|
95 * GM-Staff *
|
|
100 * GM-Admin *
|
|
150 * GM-Lead Admin *
|
|
160 * QuestMaster *
|
|
170 * GM-Areas *
|
|
180 * GM-Coder *
|
|
200 * GM-Mgmt *
|
|
250 * GM-Impossible *
|
|
|
|
*/
|
|
|
|
/*
|
|
* bot_command_init
|
|
* initializes the bot command list, call at startup
|
|
*
|
|
* Parameters:
|
|
* none
|
|
*
|
|
* When adding a new bot command, only hard-code 'real' bot commands -
|
|
* all command aliases are added later through a database call
|
|
*
|
|
*/
|
|
int bot_command_init(void)
|
|
{
|
|
bot_command_aliases.clear();
|
|
|
|
if (
|
|
bot_command_add("actionable", "Lists actionable command arguments and use descriptions", 0, bot_command_actionable) ||
|
|
bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", 0, bot_command_aggressive) ||
|
|
bot_command_add("attack", "Orders bots to attack a designated target", 0, bot_command_attack) ||
|
|
bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", 0, bot_command_bind_affinity) ||
|
|
bot_command_add("bot", "Lists the available bot management [subcommands]", 0, bot_command_bot) ||
|
|
bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", 0, bot_subcommand_bot_appearance) ||
|
|
bot_command_add("botbeardcolor", "Changes the beard color of a bot", 0, bot_subcommand_bot_beard_color) ||
|
|
bot_command_add("botbeardstyle", "Changes the beard style of a bot", 0, bot_subcommand_bot_beard_style) ||
|
|
bot_command_add("botcamp", "Orders a bot(s) to camp", 0, bot_subcommand_bot_camp) ||
|
|
bot_command_add("botclone", "Creates a copy of a bot", 200, bot_subcommand_bot_clone) ||
|
|
bot_command_add("botcreate", "Creates a new bot", 0, bot_subcommand_bot_create) ||
|
|
bot_command_add("botdelete", "Deletes all record of a bot", 0, bot_subcommand_bot_delete) ||
|
|
bot_command_add("botdetails", "Changes the Drakkin details of a bot", 0, bot_subcommand_bot_details) ||
|
|
bot_command_add("botdyearmor", "Changes the color of a bot's (bots') armor", 0, bot_subcommand_bot_dye_armor) ||
|
|
bot_command_add("boteyes", "Changes the eye colors of a bot", 0, bot_subcommand_bot_eyes) ||
|
|
bot_command_add("botface", "Changes the facial appearance of your bot", 0, bot_subcommand_bot_face) ||
|
|
bot_command_add("botfollowdistance", "Changes the follow distance(s) of a bot(s)", 0, bot_subcommand_bot_follow_distance) ||
|
|
bot_command_add("botgroup", "Lists the available bot-group [subcommands]", 0, bot_command_botgroup) ||
|
|
bot_command_add("botgroupaddmember", "Adds a member to a bot-group", 0, bot_subcommand_botgroup_add_member) ||
|
|
bot_command_add("botgroupcreate", "Creates a bot-group and designates a leader", 0, bot_subcommand_botgroup_create) ||
|
|
bot_command_add("botgroupdelete", "Deletes a bot-group and releases its members", 0, bot_subcommand_botgroup_delete) ||
|
|
bot_command_add("botgrouplist", "Lists all of your existing bot-groups", 0, bot_subcommand_botgroup_list) ||
|
|
bot_command_add("botgroupload", "Loads all members of a bot-group", 0, bot_subcommand_botgroup_load) ||
|
|
bot_command_add("botgroupremovemember", "Removes a bot from its bot-group", 0, bot_subcommand_botgroup_remove_member) ||
|
|
bot_command_add("bothaircolor", "Changes the hair color of a bot", 0, bot_subcommand_bot_hair_color) ||
|
|
bot_command_add("bothairstyle", "Changes the hairstyle of a bot", 0, bot_subcommand_bot_hairstyle) ||
|
|
bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", 0, bot_subcommand_bot_heritage) ||
|
|
bot_command_add("botinspectmessage", "Changes the inspect message of a bot", 0, bot_subcommand_bot_inspect_message) ||
|
|
bot_command_add("botlist", "Lists the bots that you own", 0, bot_subcommand_bot_list) ||
|
|
bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", 0, bot_subcommand_bot_out_of_combat) ||
|
|
bot_command_add("botreport", "Orders a bot to report its readiness", 0, bot_subcommand_bot_report) ||
|
|
bot_command_add("botspawn", "Spawns a created bot", 0, bot_subcommand_bot_spawn) ||
|
|
bot_command_add("botstance", "Changes the stance of a bot", 0, bot_subcommand_bot_stance) ||
|
|
bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", 0, bot_subcommand_bot_stop_melee_level) ||
|
|
bot_command_add("botsummon", "Summons bot(s) to your location", 0, bot_subcommand_bot_summon) ||
|
|
bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", 0, bot_subcommand_bot_tattoo) ||
|
|
bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", 0, bot_subcommand_bot_toggle_archer) ||
|
|
bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", 0, bot_subcommand_bot_toggle_helm) ||
|
|
bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", 0, bot_subcommand_bot_update) ||
|
|
bot_command_add("botwoad", "Changes the Barbarian woad of a bot", 0, bot_subcommand_bot_woad) ||
|
|
bot_command_add("charm", "Attempts to have a bot charm your target", 0, bot_command_charm) ||
|
|
bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", 0, bot_subcommand_circle) ||
|
|
bot_command_add("cure", "Orders a bot to remove any ailments", 0, bot_command_cure) ||
|
|
bot_command_add("defensive", "Orders a bot to use a defensive discipline", 0, bot_command_defensive) ||
|
|
bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", 0, bot_command_depart) ||
|
|
bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", 0, bot_command_escape) ||
|
|
bot_command_add("findaliases", "Find available aliases for a bot command", 0, bot_command_find_aliases) ||
|
|
bot_command_add("follow", "Orders bots to follow a designated target", 0, bot_command_follow) ||
|
|
bot_command_add("guard", "Orders bots to guard their current positions", 0, bot_command_guard) ||
|
|
bot_command_add("healrotation", "Lists the available bot heal rotation [subcommands]", 0, bot_command_heal_rotation) ||
|
|
bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", 0, bot_subcommand_heal_rotation_adaptive_targeting) ||
|
|
bot_command_add("healrotationaddmember", "Adds a bot to a heal rotation instance", 0, bot_subcommand_heal_rotation_add_member) ||
|
|
bot_command_add("healrotationaddtarget", "Adds target to a heal rotation instance", 0, bot_subcommand_heal_rotation_add_target) ||
|
|
bot_command_add("healrotationadjustcritical", "Adjusts the critial HP limit of the heal rotation instance's Class Armor Type criteria", 0, bot_subcommand_heal_rotation_adjust_critical) ||
|
|
bot_command_add("healrotationadjustsafe", "Adjusts the safe HP limit of the heal rotation instance's Class Armor Type criteria", 0, bot_subcommand_heal_rotation_adjust_safe) ||
|
|
bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", 0, bot_subcommand_heal_rotation_casting_override) ||
|
|
bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", 0, bot_subcommand_heal_rotation_change_interval) ||
|
|
bot_command_add("healrotationclearhot", "Clears the HOT of a heal rotation instance", 0, bot_subcommand_heal_rotation_clear_hot) ||
|
|
bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", 0, bot_subcommand_heal_rotation_clear_targets) ||
|
|
bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", 0, bot_subcommand_heal_rotation_create) ||
|
|
bot_command_add("healrotationdelete", "Deletes a bot heal rotation entry by leader", 0, bot_subcommand_heal_rotation_delete) ||
|
|
bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", 0, bot_subcommand_heal_rotation_fast_heals) ||
|
|
bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", 0, bot_subcommand_heal_rotation_list) ||
|
|
bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", 0, bot_subcommand_heal_rotation_remove_member) ||
|
|
bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", 0, bot_subcommand_heal_rotation_remove_target) ||
|
|
bot_command_add("healrotationresetlimits", "Resets all Class Armor Type HP limit criteria in a heal rotation to its default value", 0, bot_subcommand_heal_rotation_reset_limits) ||
|
|
bot_command_add("healrotationsave", "Saves a bot heal rotation entry by leader", 0, bot_subcommand_heal_rotation_save) ||
|
|
bot_command_add("healrotationsethot", "Sets the HOT in a heal rotation instance", 0, bot_subcommand_heal_rotation_set_hot) ||
|
|
bot_command_add("healrotationstart", "Starts a heal rotation", 0, bot_subcommand_heal_rotation_start) ||
|
|
bot_command_add("healrotationstop", "Stops a heal rotation", 0, bot_subcommand_heal_rotation_stop) ||
|
|
bot_command_add("help", "List available commands and their description - specify partial command as argument to search", 0, bot_command_help) ||
|
|
bot_command_add("hold", "Suspends a bot's AI processing until released", 0, bot_command_hold) ||
|
|
bot_command_add("identify", "Orders a bot to cast an item identification spell", 0, bot_command_identify) ||
|
|
bot_command_add("inventory", "Lists the available bot inventory [subcommands]", 0, bot_command_inventory) ||
|
|
bot_command_add("inventorygive", "Gives the item on your cursor to a bot", 0, bot_subcommand_inventory_give) ||
|
|
bot_command_add("inventorylist", "Lists all items in a bot's inventory", 0, bot_subcommand_inventory_list) ||
|
|
bot_command_add("inventoryremove", "Removes an item from a bot's inventory", 0, bot_subcommand_inventory_remove) ||
|
|
bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", 0, bot_subcommand_inventory_window) ||
|
|
bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", 0, bot_command_invisibility) ||
|
|
bot_command_add("levitation", "Orders a bot to cast a levitation spell", 0, bot_command_levitation) ||
|
|
bot_command_add("lull", "Orders a bot to cast a pacification spell", 0, bot_command_lull) ||
|
|
bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", 0, bot_command_mesmerize) ||
|
|
bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", 0, bot_command_movement_speed) ||
|
|
bot_command_add("pet", "Lists the available bot pet [subcommands]", 0, bot_command_pet) ||
|
|
bot_command_add("petremove", "Orders a bot to remove its pet", 0, bot_subcommand_pet_remove) ||
|
|
bot_command_add("petsettype", "Orders a Magician bot to use a specified pet type", 0, bot_subcommand_pet_set_type) ||
|
|
bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", 0, bot_command_pick_lock) ||
|
|
bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", 0, bot_subcommand_portal) ||
|
|
bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", 0, bot_command_pull) ||
|
|
bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", 0, bot_command_release) ||
|
|
bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", 0, bot_command_resistance) ||
|
|
bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", 0, bot_command_resurrect) ||
|
|
bot_command_add("rune", "Orders a bot to cast a rune of protection", 0, bot_command_rune) ||
|
|
bot_command_add("sendhome", "Orders a bot to open a magical doorway home", 0, bot_command_send_home) ||
|
|
bot_command_add("size", "Orders a bot to change a player's size", 0, bot_command_size) ||
|
|
bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", 0, bot_command_summon_corpse) ||
|
|
bot_command_add("taunt", "Toggles taunt use by a bot", 0, bot_command_taunt) ||
|
|
bot_command_add("track", "Orders a capable bot to track enemies", 0, bot_command_track) ||
|
|
bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", 0, bot_command_water_breathing)
|
|
) {
|
|
bot_command_deinit();
|
|
return -1;
|
|
}
|
|
|
|
std::map<std::string, std::pair<uint8, std::vector<std::string>>> bot_command_settings;
|
|
botdb.LoadBotCommandSettings(bot_command_settings);
|
|
|
|
auto working_bcl = bot_command_list;
|
|
for (auto working_bcl_iter : working_bcl) {
|
|
auto bot_command_settings_iter = bot_command_settings.find(working_bcl_iter.first);
|
|
if (bot_command_settings_iter == bot_command_settings.end()) {
|
|
if (working_bcl_iter.second->access == 0)
|
|
Log(Logs::General, Logs::Commands, "bot_command_init(): Warning: Bot Command '%s' defaulting to access level 0!", working_bcl_iter.first.c_str());
|
|
continue;
|
|
}
|
|
|
|
working_bcl_iter.second->access = bot_command_settings_iter->second.first;
|
|
Log(Logs::General, Logs::Commands, "bot_command_init(): - Bot Command '%s' set to access level %d.", working_bcl_iter.first.c_str(), bot_command_settings_iter->second.first);
|
|
if (bot_command_settings_iter->second.second.empty())
|
|
continue;
|
|
|
|
for (auto alias_iter : bot_command_settings_iter->second.second) {
|
|
if (alias_iter.empty())
|
|
continue;
|
|
if (bot_command_list.find(alias_iter) != bot_command_list.end()) {
|
|
Log(Logs::General, Logs::Commands, "bot_command_init(): Warning: Alias '%s' 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;
|
|
|
|
Log(Logs::General, Logs::Commands, "bot_command_init(): - Alias '%s' added to bot command '%s'.", alias_iter.c_str(), bot_command_aliases[alias_iter].c_str());
|
|
}
|
|
}
|
|
|
|
bot_command_dispatch = bot_command_real_dispatch;
|
|
|
|
BCSpells::Load();
|
|
|
|
return bot_command_count;
|
|
}
|
|
|
|
|
|
/*
|
|
* bot_command_deinit
|
|
* clears the bot command list, freeing resources
|
|
*
|
|
* Parameters:
|
|
* none
|
|
*
|
|
*/
|
|
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();
|
|
}
|
|
|
|
|
|
/*
|
|
* bot_command_add
|
|
* adds a bot command to the bot command list; used by bot_command_init
|
|
*
|
|
* Parameters:
|
|
* bot_command_string - the command ex: "spawn"
|
|
* desc - text description of bot command for #help
|
|
* access - default access level required to use command
|
|
* function - pointer to function that handles command
|
|
*
|
|
*/
|
|
int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function)
|
|
{
|
|
if (bot_command_name.empty()) {
|
|
Log(Logs::General, Logs::Error, "bot_command_add() - Bot command added with empty name string - check bot_command.cpp.");
|
|
return -1;
|
|
}
|
|
if (function == nullptr) {
|
|
Log(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' 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) {
|
|
Log(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' 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;
|
|
Log(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' equates to an alias of '%s' - 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;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* bot_command_real_dispatch
|
|
* Calls the correct function to process the client's command string.
|
|
* Called from Client::ChannelMessageReceived if message starts with
|
|
* bot command character (^).
|
|
*
|
|
* Parameters:
|
|
* c - pointer to the calling client object
|
|
* message - what the client typed
|
|
*
|
|
*/
|
|
int bot_command_real_dispatch(Client *c, const char *message)
|
|
{
|
|
Seperator sep(message, ' ', 10, 100, true); // "three word argument" should be considered 1 arg
|
|
|
|
bot_command_log_command(c, message);
|
|
|
|
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(m_fail, "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) {
|
|
Log(Logs::General, Logs::Commands, "%s (%s) used bot command: %s (target=%s)", c->GetName(), c->AccountName(), message, c->GetTarget()?c->GetTarget()->GetName():"NONE");
|
|
}
|
|
|
|
if(cur->function == nullptr) {
|
|
Log(Logs::General, Logs::Error, "Bot command '%s' has a null function\n", cstr.c_str());
|
|
return(-1);
|
|
} else {
|
|
//dispatch C++ bot command
|
|
cur->function(c, &sep); // dispatch bot command
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
void bot_command_log_command(Client *c, const char *message)
|
|
{
|
|
int admin = c->Admin();
|
|
|
|
bool continueevents = false;
|
|
switch (zone->loglevelvar) { //catch failsafe
|
|
case 9: // log only LeadGM
|
|
if ((admin >= 150) && (admin <200))
|
|
continueevents = true;
|
|
break;
|
|
case 8: // log only GM
|
|
if ((admin >= 100) && (admin <150))
|
|
continueevents = true;
|
|
break;
|
|
case 1:
|
|
if ((admin >= 200))
|
|
continueevents = true;
|
|
break;
|
|
case 2:
|
|
if ((admin >= 150))
|
|
continueevents = true;
|
|
break;
|
|
case 3:
|
|
if ((admin >= 100))
|
|
continueevents = true;
|
|
break;
|
|
case 4:
|
|
if ((admin >= 80))
|
|
continueevents = true;
|
|
break;
|
|
case 5:
|
|
if ((admin >= 20))
|
|
continueevents = true;
|
|
break;
|
|
case 6:
|
|
if ((admin >= 10))
|
|
continueevents = true;
|
|
break;
|
|
case 7:
|
|
continueevents = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (continueevents)
|
|
database.logevents(c->AccountName(), c->AccountID(), admin,c->GetName(), c->GetTarget()?c->GetTarget()->GetName():"None", "BotCommand", message, 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* helper functions by use
|
|
*/
|
|
namespace MyBots
|
|
{
|
|
static bool IsMyBot(Client *bot_owner, Mob *my_bot) {
|
|
if (!bot_owner || !my_bot || !my_bot->IsBot())
|
|
return false;
|
|
|
|
auto test_bot = my_bot->CastToBot();
|
|
if (!test_bot->GetOwner() || !test_bot->GetOwner()->IsClient() || test_bot->GetOwner()->CastToClient() != bot_owner)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IsMyBotInTargetsGroup(Client *bot_owner, Mob *grouped_bot) {
|
|
if (!bot_owner || !grouped_bot || !grouped_bot->GetGroup() || !IsMyBot(bot_owner, grouped_bot))
|
|
return false;
|
|
|
|
auto target_mob = bot_owner->GetTarget();
|
|
if (!target_mob)
|
|
return false;
|
|
|
|
if (!target_mob->GetGroup() || (!target_mob->IsClient() && !target_mob->IsBot()))
|
|
return false;
|
|
|
|
return (grouped_bot->GetGroup() == target_mob->GetGroup());
|
|
}
|
|
|
|
static bool IsMyBotInPlayerGroup(Client *bot_owner, Mob *grouped_bot, Client *grouped_player) {
|
|
if (!bot_owner || !grouped_player || !grouped_player->GetGroup() || !grouped_bot || !grouped_bot->GetGroup() || !IsMyBot(bot_owner, grouped_bot))
|
|
return false;
|
|
|
|
return (grouped_player->GetGroup() == grouped_bot->GetGroup());
|
|
}
|
|
|
|
static void UniquifySBL(std::list<Bot*> &sbl) {
|
|
sbl.remove(nullptr);
|
|
sbl.sort();
|
|
sbl.unique();
|
|
}
|
|
|
|
static void PopulateSBL_ByTargetedBot(Client *bot_owner, std::list<Bot*> &sbl, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
|
|
if (IsMyBot(bot_owner, bot_owner->GetTarget()))
|
|
sbl.push_back(bot_owner->GetTarget()->CastToBot());
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByNamedBot(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || !name)
|
|
return;
|
|
|
|
auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID());
|
|
for (auto bot_iter : selectable_bot_list) {
|
|
if (!strcasecmp(bot_iter->GetCleanName(), name)) {
|
|
sbl.push_back(bot_iter);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByMyGroupedBots(Client *bot_owner, std::list<Bot*> &sbl, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner)
|
|
return;
|
|
|
|
if (!bot_owner->GetGroup())
|
|
return;
|
|
|
|
std::list<Bot*> group_list;
|
|
bot_owner->GetGroup()->GetBotList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (IsMyBot(bot_owner, member_iter))
|
|
sbl.push_back(member_iter);
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByTargetsGroupedBots(Client *bot_owner, std::list<Bot*> &sbl, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner)
|
|
return;
|
|
|
|
auto target_mob = bot_owner->GetTarget();
|
|
if (!target_mob || !target_mob->GetGroup() || (!target_mob->IsClient() && !target_mob->IsBot()))
|
|
return;
|
|
|
|
std::list<Bot*> group_list;
|
|
target_mob->GetGroup()->GetBotList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (IsMyBot(bot_owner, member_iter))
|
|
sbl.push_back(member_iter);
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByNamesGroupedBots(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || !name)
|
|
return;
|
|
|
|
Mob* named_mob = nullptr;
|
|
std::list<Mob*> l_mob_list;
|
|
entity_list.GetMobList(l_mob_list);
|
|
for (auto mob_iter : l_mob_list) {
|
|
if (!strcasecmp(mob_iter->GetCleanName(), name)) {
|
|
named_mob = mob_iter;
|
|
break;
|
|
}
|
|
}
|
|
if (!named_mob || !named_mob->GetGroup() || (!named_mob->IsClient() && !named_mob->IsBot()))
|
|
return;
|
|
|
|
std::list<Bot*> group_list;
|
|
named_mob->GetGroup()->GetBotList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (IsMyBot(bot_owner, member_iter))
|
|
sbl.push_back(member_iter);
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByHealRotation(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || (!name && !bot_owner->GetTarget()))
|
|
return;
|
|
|
|
std::list<Bot*> selectable_bot_list;
|
|
if (name)
|
|
PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name);
|
|
else
|
|
PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list);
|
|
|
|
if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember())
|
|
return;
|
|
|
|
auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->MemberList();
|
|
for (auto hrm_iter : *hrm) {
|
|
if (IsMyBot(bot_owner, hrm_iter))
|
|
sbl.push_back(hrm_iter);
|
|
}
|
|
|
|
auto hrt = (*selectable_bot_list.front()->MemberOfHealRotation())->TargetList();
|
|
for (auto hrt_iter : *hrt) {
|
|
if (IsMyBot(bot_owner, hrt_iter))
|
|
sbl.push_back(hrt_iter->CastToBot());
|
|
}
|
|
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByHealRotationMembers(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || (!name && !bot_owner->GetTarget()))
|
|
return;
|
|
|
|
std::list<Bot*> selectable_bot_list;
|
|
if (name)
|
|
PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name);
|
|
else
|
|
PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list);
|
|
|
|
if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember())
|
|
return;
|
|
|
|
auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->MemberList();
|
|
for (auto hrm_iter : *hrm) {
|
|
if (IsMyBot(bot_owner, hrm_iter))
|
|
sbl.push_back(hrm_iter);
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_ByHealRotationTargets(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || (!name && !bot_owner->GetTarget()))
|
|
return;
|
|
|
|
std::list<Bot*> selectable_bot_list;
|
|
if (name)
|
|
PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name);
|
|
else
|
|
PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list);
|
|
|
|
if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember())
|
|
return;
|
|
|
|
auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->TargetList();
|
|
for (auto hrm_iter : *hrm) {
|
|
if (IsMyBot(bot_owner, hrm_iter))
|
|
sbl.push_back(static_cast<Bot*>(hrm_iter));
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
|
|
static void PopulateSBL_BySpawnedBots(Client *bot_owner, std::list<Bot*> &sbl) { // should be used for most spell casting commands
|
|
sbl.clear();
|
|
if (!bot_owner)
|
|
return;
|
|
|
|
sbl = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID());
|
|
sbl.remove(nullptr);
|
|
}
|
|
|
|
static void PopulateSBL_ByBotGroup(Client *bot_owner, std::list<Bot*> &sbl, const char* name, bool clear_list = true) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner || !name)
|
|
return;
|
|
|
|
std::string group_name = name;
|
|
|
|
uint32 botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDForLoadBotGroup(bot_owner->CharacterID(), group_name, botgroup_id) || !botgroup_id)
|
|
return;
|
|
|
|
std::map<uint32, std::list<uint32>> botgroup_list;
|
|
if (!botdb.LoadBotGroup(group_name, botgroup_list) || botgroup_list.find(botgroup_id) == botgroup_list.end() || !botgroup_list[botgroup_id].size())
|
|
return;
|
|
|
|
std::list<Bot*> selectable_bot_list;
|
|
PopulateSBL_BySpawnedBots(bot_owner, selectable_bot_list);
|
|
if (selectable_bot_list.empty())
|
|
return;
|
|
|
|
selectable_bot_list.remove(nullptr);
|
|
for (auto group_iter : botgroup_list[botgroup_id]) {
|
|
for (auto bot_iter : selectable_bot_list) {
|
|
if (bot_iter->GetBotID() != group_iter)
|
|
continue;
|
|
|
|
if (IsMyBot(bot_owner, bot_iter)) {
|
|
sbl.push_back(bot_iter);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!clear_list)
|
|
UniquifySBL(sbl);
|
|
}
|
|
};
|
|
|
|
namespace ActionableTarget
|
|
{
|
|
static bool AmIInPlayerGroup(Client *bot_owner, Client *grouped_player) {
|
|
if (!bot_owner || !grouped_player || !bot_owner->GetGroup() || !grouped_player->GetGroup())
|
|
return false;
|
|
|
|
return (bot_owner->GetGroup() == grouped_player->GetGroup());
|
|
}
|
|
|
|
static bool IsAttackable(Client *bot_owner, Mob* target_mob) {
|
|
if (!bot_owner || !target_mob || bot_owner == target_mob)
|
|
return false;
|
|
|
|
return bot_owner->IsAttackAllowed(target_mob);
|
|
}
|
|
|
|
static Client* AsSingle_ByPlayer(Client *bot_owner, bool return_me_on_null_target = true) {
|
|
if (!bot_owner)
|
|
return nullptr;
|
|
|
|
if (!bot_owner->GetTarget()) {
|
|
if (return_me_on_null_target)
|
|
return bot_owner;
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
if (!bot_owner->GetTarget()->IsClient())
|
|
return nullptr;
|
|
|
|
return bot_owner->GetTarget()->CastToClient();
|
|
}
|
|
|
|
static Client* AsGroupMember_ByPlayer(Client *bot_owner, bool return_me_on_null_target = true) {
|
|
if (!bot_owner)
|
|
return nullptr;
|
|
|
|
if (!bot_owner->GetTarget()) {
|
|
if (return_me_on_null_target)
|
|
return bot_owner;
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
if (!bot_owner->GetTarget()->IsClient() || !AmIInPlayerGroup(bot_owner, bot_owner->GetTarget()->CastToClient()))
|
|
return nullptr;
|
|
|
|
return bot_owner->GetTarget()->CastToClient();
|
|
}
|
|
|
|
static Corpse* AsCorpse_ByPlayer(Client *bot_owner) {
|
|
if (!bot_owner || !bot_owner->GetTarget() || !bot_owner->GetTarget()->IsPlayerCorpse())
|
|
return nullptr;
|
|
|
|
return bot_owner->GetTarget()->CastToCorpse();
|
|
}
|
|
|
|
static Mob* AsSingle_ByAttackable(Client *bot_owner) {
|
|
if (!IsAttackable(bot_owner, bot_owner->GetTarget()))
|
|
return nullptr;
|
|
|
|
return bot_owner->GetTarget();
|
|
}
|
|
|
|
static bool IsFriendlyAllowed(Mob* target_mob) {
|
|
if (!target_mob || target_mob->IsClient() || target_mob->IsBot() || (target_mob->IsPet() && target_mob->GetOwner() && (target_mob->GetOwner()->IsClient() || target_mob->GetOwner()->IsBot())) || target_mob->IsPlayerCorpse())
|
|
return true;
|
|
|
|
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() == BT_Animal)
|
|
verified_friendly = target_mob;
|
|
break;
|
|
case BCEnum::TT_Undead:
|
|
if (target_mob && target_mob->GetBodyType() == BT_Undead)
|
|
verified_friendly = target_mob;
|
|
break;
|
|
case BCEnum::TT_Summoned:
|
|
if (target_mob && target_mob->GetBodyType() == BT_Summoned)
|
|
verified_friendly = target_mob;
|
|
break;
|
|
case BCEnum::TT_Plant:
|
|
if (target_mob && target_mob->GetBodyType() == BT_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() == BT_Animal)
|
|
verified_enemy = target_mob;
|
|
break;
|
|
case BCEnum::TT_Undead:
|
|
if (target_mob->GetBodyType() == BT_Undead)
|
|
verified_enemy = target_mob;
|
|
break;
|
|
case BCEnum::TT_Summoned:
|
|
if (target_mob->GetBodyType() == BT_Summoned)
|
|
verified_enemy = target_mob;
|
|
break;
|
|
case BCEnum::TT_Plant:
|
|
if (target_mob->GetBodyType() == BT_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
|
|
{
|
|
enum ABType {
|
|
ABT_None = 0,
|
|
ABT_Target,
|
|
ABT_ByName,
|
|
ABT_OwnerGroup,
|
|
ABT_BotGroup,
|
|
ABT_TargetGroup,
|
|
ABT_NamesGroup,
|
|
ABT_HealRotation,
|
|
ABT_HealRotationMembers,
|
|
ABT_HealRotationTargets,
|
|
ABT_Spawned,
|
|
ABT_All
|
|
};
|
|
|
|
enum ABMask {
|
|
ABM_None = 0,
|
|
ABM_Target = (1 << (ABT_Target - 1)),
|
|
ABM_ByName = (1 << (ABT_ByName - 1)),
|
|
ABM_OwnerGroup = (1 << (ABT_OwnerGroup - 1)),
|
|
ABM_BotGroup = (1 << (ABT_BotGroup - 1)),
|
|
ABM_TargetGroup = (1 << (ABT_TargetGroup - 1)),
|
|
ABM_NamesGroup = (1 << (ABT_NamesGroup - 1)),
|
|
ABM_HealRotation = (1 << (ABT_HealRotation - 1)),
|
|
ABM_HealRotationMembers = (1 << (ABT_HealRotationMembers - 1)),
|
|
ABM_HealRotationTargets = (1 << (ABT_HealRotationTargets - 1)),
|
|
ABM_Spawned = (1 << (ABT_Spawned - 1)),
|
|
ABM_All = (1 << (ABT_All - 1)),
|
|
ABM_Spawned_All = (3 << (ABT_Spawned - 1)),
|
|
ABM_NoFilter = ~0,
|
|
// grouped values
|
|
ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_BotGroup | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned),
|
|
ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_BotGroup | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned)
|
|
};
|
|
|
|
// Populates 'sbl'
|
|
static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::list<Bot*> &sbl, int ab_mask, const char* name = nullptr, bool clear_list = true, bool suppress_message = false) {
|
|
if (clear_list)
|
|
sbl.clear();
|
|
if (!bot_owner)
|
|
return ABT_None;
|
|
|
|
auto ab_type = ABT_None;
|
|
if (!ab_type_arg.compare("target") || ab_type_arg.empty())
|
|
ab_type = ABT_Target;
|
|
else if (!ab_type_arg.compare("byname"))
|
|
ab_type = ABT_ByName;
|
|
else if (!ab_type_arg.compare("ownergroup"))
|
|
ab_type = ABT_OwnerGroup;
|
|
else if (!ab_type_arg.compare("botgroup"))
|
|
ab_type = ABT_BotGroup;
|
|
else if (!ab_type_arg.compare("targetgroup"))
|
|
ab_type = ABT_TargetGroup;
|
|
else if (!ab_type_arg.compare("namesgroup"))
|
|
ab_type = ABT_NamesGroup;
|
|
else if (!ab_type_arg.compare("healrotation"))
|
|
ab_type = ABT_HealRotation;
|
|
else if (!ab_type_arg.compare("healrotationmembers"))
|
|
ab_type = ABT_HealRotationMembers;
|
|
else if (!ab_type_arg.compare("healrotationtargets"))
|
|
ab_type = ABT_HealRotationTargets;
|
|
else if (!ab_type_arg.compare("spawned"))
|
|
ab_type = ABT_Spawned;
|
|
else if (!ab_type_arg.compare("all"))
|
|
ab_type = ABT_All;
|
|
|
|
if (ab_type_arg.empty())
|
|
ab_type_arg = "target";
|
|
|
|
switch (ab_type) {
|
|
case ABT_Target:
|
|
if (ab_mask & ABM_Target)
|
|
MyBots::PopulateSBL_ByTargetedBot(bot_owner, sbl, clear_list);
|
|
break;
|
|
case ABT_ByName:
|
|
if (ab_mask & ABM_ByName)
|
|
MyBots::PopulateSBL_ByNamedBot(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_OwnerGroup:
|
|
if (ab_mask & ABM_OwnerGroup)
|
|
MyBots::PopulateSBL_ByMyGroupedBots(bot_owner, sbl, clear_list);
|
|
break;
|
|
case ABT_BotGroup:
|
|
if (ab_mask & ABM_BotGroup)
|
|
MyBots::PopulateSBL_ByBotGroup(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_TargetGroup:
|
|
if (ab_mask & ABM_TargetGroup)
|
|
MyBots::PopulateSBL_ByTargetsGroupedBots(bot_owner, sbl, clear_list);
|
|
break;
|
|
case ABT_NamesGroup:
|
|
if (ab_mask & ABM_NamesGroup)
|
|
MyBots::PopulateSBL_ByNamesGroupedBots(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_HealRotation:
|
|
if (ab_mask & ABM_HealRotation)
|
|
MyBots::PopulateSBL_ByHealRotation(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_HealRotationMembers:
|
|
if (ab_mask & ABM_HealRotationMembers)
|
|
MyBots::PopulateSBL_ByHealRotationMembers(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_HealRotationTargets:
|
|
if (ab_mask & ABM_HealRotationTargets)
|
|
MyBots::PopulateSBL_ByHealRotationTargets(bot_owner, sbl, name, clear_list);
|
|
break;
|
|
case ABT_Spawned:
|
|
case ABT_All:
|
|
if (ab_mask & ABM_Spawned_All)
|
|
MyBots::PopulateSBL_BySpawnedBots(bot_owner, sbl);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (sbl.empty() && ab_type != ABT_All) {
|
|
if (suppress_message)
|
|
return ABT_None;
|
|
|
|
if (!ab_mask)
|
|
bot_owner->Message(m_fail, "Command passed null 'ActionableBot' criteria");
|
|
else if (ab_mask & ab_type)
|
|
bot_owner->Message(m_fail, "You have no spawned bots meeting this criteria - type: '%s', name: '%s'", ab_type_arg.c_str(), ((name) ? (name) : ("")));
|
|
else
|
|
bot_owner->Message(m_fail, "This command does not allow 'ActionableBot' criteria '%s'", ab_type_arg.c_str());
|
|
return ABT_None;
|
|
}
|
|
|
|
return ab_type;
|
|
}
|
|
|
|
// Returns single, scoped bot
|
|
static Bot* AsGroupMember_ByClass(Client *bot_owner, Client *bot_grouped_player, uint8 cls, bool petless = false) {
|
|
if (!bot_owner || !bot_grouped_player)
|
|
return nullptr;
|
|
if (!bot_grouped_player->GetGroup())
|
|
return nullptr;
|
|
|
|
std::list<Mob*> group_list;
|
|
bot_grouped_player->GetGroup()->GetMemberList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (!MyBots::IsMyBot(bot_owner, member_iter))
|
|
continue;
|
|
if (member_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && member_iter->GetPet())
|
|
continue;
|
|
|
|
return static_cast<Bot*>(member_iter);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* AsGroupMember_ByMinLevelAndClass(Client *bot_owner, Client *bot_grouped_player, uint8 minlvl, uint8 cls, bool petless = false) {
|
|
// This function can be nixed if we can enforce bot level as owner level..and the level check can then be moved to the spell loop in the command function
|
|
if (!bot_owner || !bot_grouped_player)
|
|
return nullptr;
|
|
if (!bot_grouped_player->GetGroup())
|
|
return nullptr;
|
|
|
|
std::list<Mob*> group_list;
|
|
bot_grouped_player->GetGroup()->GetMemberList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (!MyBots::IsMyBot(bot_owner, member_iter))
|
|
continue;
|
|
if (member_iter->GetLevel() < minlvl || member_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && member_iter->GetPet())
|
|
continue;
|
|
|
|
return static_cast<Bot*>(member_iter);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* AsSpawned_ByClass(Client *bot_owner, std::list<Bot*> &sbl, uint8 cls, bool petless = false) {
|
|
if (!bot_owner)
|
|
return nullptr;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!MyBots::IsMyBot(bot_owner, bot_iter))
|
|
continue;
|
|
if (bot_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && bot_iter->GetPet())
|
|
continue;
|
|
|
|
return bot_iter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* AsSpawned_ByMinLevelAndClass(Client *bot_owner, std::list<Bot*> &sbl, uint8 minlvl, uint8 cls, bool petless = false) {
|
|
// This function can be nixed if we can enforce bot level as owner level..and the level check can then be moved to the spell loop in the command function
|
|
if (!bot_owner)
|
|
return nullptr;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!MyBots::IsMyBot(bot_owner, bot_iter))
|
|
continue;
|
|
if (bot_iter->GetLevel() < minlvl || bot_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && bot_iter->GetPet())
|
|
continue;
|
|
|
|
return bot_iter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* AsTarget_ByBot(Client *bot_owner) {
|
|
if (!bot_owner || !MyBots::IsMyBot(bot_owner, bot_owner->GetTarget()))
|
|
return nullptr;
|
|
|
|
return bot_owner->GetTarget()->CastToBot();
|
|
}
|
|
|
|
static Bot* AsNamed_ByBot(Client *bot_owner, std::string bot_name) {
|
|
if (!bot_owner || bot_name.empty())
|
|
return nullptr;
|
|
|
|
std::list<Bot*> selectable_bot_list;
|
|
MyBots::PopulateSBL_BySpawnedBots(bot_owner, selectable_bot_list);
|
|
for (auto bot_iter : selectable_bot_list) {
|
|
if (!bot_name.compare(bot_iter->GetCleanName()))
|
|
return bot_iter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* Select_ByClass(Client* bot_owner, BCEnum::TType target_type, std::list<Bot*>& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) {
|
|
if (!bot_owner || sbl.empty())
|
|
return nullptr;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!MyBots::IsMyBot(bot_owner, bot_iter))
|
|
continue;
|
|
if (bot_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && bot_iter->GetPet())
|
|
continue;
|
|
if (target_type == BCEnum::TT_GroupV1) {
|
|
if (!target_mob)
|
|
return nullptr;
|
|
else if (bot_iter->GetGroup() != target_mob->GetGroup())
|
|
continue;
|
|
}
|
|
|
|
return bot_iter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static Bot* Select_ByMinLevelAndClass(Client* bot_owner, BCEnum::TType target_type, std::list<Bot*>& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) {
|
|
if (!bot_owner || sbl.empty())
|
|
return nullptr;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!MyBots::IsMyBot(bot_owner, bot_iter))
|
|
continue;
|
|
if (bot_iter->GetLevel() < minlvl || bot_iter->GetClass() != cls)
|
|
continue;
|
|
if (petless && bot_iter->GetPet())
|
|
continue;
|
|
if (target_type == BCEnum::TT_GroupV1) {
|
|
if (!target_mob)
|
|
return nullptr;
|
|
else if (bot_iter->GetGroup() != target_mob->GetGroup())
|
|
continue;
|
|
}
|
|
|
|
return bot_iter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Filters actual 'sbl' list
|
|
static void Filter_ByClasses(Client* bot_owner, std::list<Bot*>& sbl, uint16 class_mask) {
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); });
|
|
sbl.remove_if([class_mask](const Bot* l) { return (GetPlayerClassBit(l->GetClass()) & (~class_mask)); });
|
|
}
|
|
|
|
static void Filter_ByMinLevel(Client* bot_owner, std::list<Bot*>& sbl, uint8 min_level) {
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); });
|
|
sbl.remove_if([min_level](const Bot* l) { return (l->GetLevel() < min_level); });
|
|
}
|
|
|
|
static void Filter_ByArcher(Client* bot_owner, std::list<Bot*>& sbl) {
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); });
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!l->IsBotArcher()); });
|
|
}
|
|
|
|
static void Filter_ByHighestSkill(Client* bot_owner, std::list<Bot*>& sbl, EQEmu::skills::SkillType skill_type, float& skill_value) {
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); });
|
|
skill_value = 0.0f;
|
|
|
|
float mod_skill_value = 0.0f;
|
|
const Bot* skilled_bot = nullptr;
|
|
for (auto bot_iter : sbl) {
|
|
float base_skill_value = bot_iter->GetSkill(skill_type);
|
|
if (base_skill_value == 0.0f)
|
|
continue;
|
|
|
|
mod_skill_value = base_skill_value;
|
|
for (int16 index = EQEmu::invslot::EQUIPMENT_BEGIN; index <= EQEmu::invslot::EQUIPMENT_END; ++index) {
|
|
const EQEmu::ItemInstance* indexed_item = bot_iter->GetBotItem(index);
|
|
if (indexed_item && indexed_item->GetItem()->SkillModType == skill_type)
|
|
mod_skill_value += (base_skill_value * (((float)indexed_item->GetItem()->SkillModValue) / 100.0f));
|
|
}
|
|
|
|
if (!skilled_bot) {
|
|
skill_value = mod_skill_value;
|
|
skilled_bot = bot_iter;
|
|
}
|
|
else if (mod_skill_value > skill_value) {
|
|
skill_value = mod_skill_value;
|
|
skilled_bot = bot_iter;
|
|
}
|
|
}
|
|
|
|
sbl.remove_if([skilled_bot](const Bot* l) { return (l != skilled_bot); });
|
|
}
|
|
|
|
static void Filter_ByHighestPickLock(Client* bot_owner, std::list<Bot*>& sbl, float& pick_lock_value) {
|
|
sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); });
|
|
sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() != ROGUE && l->GetClass() != BARD); });
|
|
sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == ROGUE && l->GetLevel() < 5); });
|
|
sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == BARD && l->GetLevel() < 40); });
|
|
|
|
ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, EQEmu::skills::SkillPickLock, pick_lock_value);
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
* bot commands go below here
|
|
*/
|
|
void bot_command_actionable(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_actionable", sep->arg[0], "actionable"))
|
|
return;
|
|
|
|
c->Message(m_message, "Actionable command arguments:");
|
|
|
|
c->Message(m_usage, "target - selects target as single bot .. use ^command [target] or imply by empty actionable argument");
|
|
c->Message(m_usage, "byname [name] - selects single bot by name");
|
|
c->Message(m_usage, "ownergroup - selects all bots in the owner's group");
|
|
c->Message(m_usage, "botgroup [name] - selects members of a bot-group by its name");
|
|
c->Message(m_usage, "targetgroup - selects all bots in target's group");
|
|
c->Message(m_usage, "namesgroup [name] - selects all bots in name's group");
|
|
c->Message(m_usage, "healrotation [name] - selects all member and target bots of a heal rotation where name is a member");
|
|
c->Message(m_usage, "healrotationmembers [name] - selects all member bots of a heal rotation where name is a member");
|
|
c->Message(m_usage, "healrotationtargets [name] - selects all target bots of a heal rotation where name is a member");
|
|
c->Message(m_usage, "spawned - selects all spawned bots");
|
|
c->Message(m_usage, "all - selects all spawned bots .. argument use indicates en masse database updating");
|
|
|
|
c->Message(m_message, "You may only select your bots as actionable");
|
|
}
|
|
|
|
void bot_command_aggressive(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_aggressive", sep->arg[0], "aggressive"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Stance);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_Type1;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
sbl.remove(nullptr);
|
|
|
|
int success_count = 0;
|
|
int candidate_count = sbl.size();
|
|
for (auto list_iter : *local_list) {
|
|
if (sbl.empty())
|
|
break;
|
|
|
|
auto local_entry = list_iter->SafeCastToStance();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->stance_type != BCEnum::StT_Aggressive)
|
|
continue;
|
|
|
|
for (auto bot_iter = sbl.begin(); bot_iter != sbl.end(); ) {
|
|
Bot* my_bot = *bot_iter;
|
|
if (local_entry->caster_class != my_bot->GetClass()) {
|
|
++bot_iter;
|
|
continue;
|
|
}
|
|
if (local_entry->spell_level > my_bot->GetLevel()) {
|
|
++bot_iter;
|
|
continue;
|
|
}
|
|
|
|
my_bot->InterruptSpell();
|
|
if (candidate_count == 1)
|
|
Bot::BotGroupSay(my_bot, "Using '%s'", spells[local_entry->spell_id].name);
|
|
my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID());
|
|
++success_count;
|
|
|
|
bot_iter = sbl.erase(bot_iter);
|
|
}
|
|
}
|
|
|
|
c->Message(m_action, "%i of %i bots have used aggressive disciplines", success_count, candidate_count);
|
|
}
|
|
|
|
void bot_command_attack(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <enemy_target> %s [actionable: byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_Type2;
|
|
|
|
Mob* target_mob = ActionableTarget::AsSingle_ByAttackable(c);
|
|
if (!target_mob) {
|
|
c->Message(m_fail, "You must <target> an enemy to use this command");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
sbl.remove(nullptr);
|
|
for (auto bot_iter : sbl) {
|
|
bot_iter->WipeHateList();
|
|
bot_iter->AddToHateList(target_mob, 1);
|
|
if (!bot_iter->GetPet())
|
|
continue;
|
|
|
|
bot_iter->GetPet()->WipeHateList();
|
|
bot_iter->GetPet()->AddToHateList(target_mob, 1);
|
|
}
|
|
if (sbl.size() == 1)
|
|
Bot::BotGroupSay(sbl.front(), "Attacking %s", target_mob->GetCleanName());
|
|
else
|
|
c->Message(m_action, "%i of your bots are attacking %s", sbl.size(), target_mob->GetCleanName());
|
|
}
|
|
|
|
void bot_command_bind_affinity(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_BindAffinity];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_BindAffinity) || helper_command_alias_fail(c, "bot_command_bind_affinity", sep->arg[0], "bindaffinity"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_BindAffinity);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
// Cast effect message is not being generated
|
|
if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id))
|
|
c->Message(m_action, "Successfully bound %s to this location", target_mob->GetCleanName());
|
|
else
|
|
c->Message(m_fail, "Failed to bind %s to this location", target_mob->GetCleanName());
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_bot(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("botappearance");
|
|
subcommand_list.push_back("botcamp");
|
|
subcommand_list.push_back("botclone");
|
|
subcommand_list.push_back("botcreate");
|
|
subcommand_list.push_back("botdelete");
|
|
subcommand_list.push_back("botdetails");
|
|
subcommand_list.push_back("botdyearmor");
|
|
subcommand_list.push_back("botinspectmessage");
|
|
subcommand_list.push_back("botfollowdistance");
|
|
subcommand_list.push_back("botlist");
|
|
subcommand_list.push_back("botoutofcombat");
|
|
subcommand_list.push_back("botreport");
|
|
subcommand_list.push_back("botspawn");
|
|
subcommand_list.push_back("botstance");
|
|
subcommand_list.push_back("botstopmeleelevel");
|
|
subcommand_list.push_back("botsummon");
|
|
subcommand_list.push_back("bottogglearcher");
|
|
subcommand_list.push_back("bottogglehelm");
|
|
subcommand_list.push_back("botupdate");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = {
|
|
"botappearance", "botcamp", "botclone", "botcreate", "botdelete", "botdetails", "botdyearmor", "botinspectmessage", "botfollowdistance",
|
|
"botlist", "botoutofcombat", "botreport", "botspawn", "botstance", "botsummon", "bottogglearcher", "bottogglehelm", "botupdate"
|
|
};
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_command_bot", sep->arg[0], "bot"))
|
|
return;
|
|
|
|
helper_send_available_subcommands(c, "bot", subcommand_list);
|
|
}
|
|
|
|
void bot_command_botgroup(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("botgroupaddmember");
|
|
subcommand_list.push_back("botgroupcreate");
|
|
subcommand_list.push_back("botgroupdelete");
|
|
subcommand_list.push_back("botgrouplist");
|
|
subcommand_list.push_back("botgroupload");
|
|
subcommand_list.push_back("botgroupremovemember");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = {
|
|
"botgroupaddmember", "botgroupcreate", "botgroupdelete", "botgrouplist", "botgroupload", "botgroupremovemember"
|
|
};
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_command_botgroup", sep->arg[0], "botgroup"))
|
|
return;
|
|
|
|
helper_send_available_subcommands(c, "bot-group", subcommand_list);
|
|
}
|
|
|
|
void bot_command_charm(Client *c, const Seperator *sep)
|
|
{
|
|
auto local_list = &bot_command_spells[BCEnum::SpT_Charm];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Charm) || helper_command_alias_fail(c, "bot_command_charm", sep->arg[0], "charm"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <enemy_target> %s ([option: dire])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Charm);
|
|
return;
|
|
}
|
|
|
|
bool dire = false;
|
|
std::string dire_arg = sep->arg[1];
|
|
if (!dire_arg.compare("dire"))
|
|
dire = true;
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToCharm();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->dire != dire)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY);
|
|
if (!target_mob)
|
|
continue;
|
|
if (target_mob->IsCharmed()) {
|
|
c->Message(m_fail, "Your <target> is already charmed");
|
|
return;
|
|
}
|
|
|
|
if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel())
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob, true);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
uint32 dont_root_before = 0;
|
|
if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before))
|
|
target_mob->SetDontRootMeBefore(dont_root_before);
|
|
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_cure(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Cure];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Cure) || helper_command_alias_fail(c, "bot_command_cure", sep->arg[0], "cure"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s [ailment: blindness | disease | poison | curse | corruption]", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Cure);
|
|
return;
|
|
}
|
|
|
|
std::string ailment_arg = sep->arg[1];
|
|
|
|
auto ailment_type = BCEnum::AT_None;
|
|
if (!ailment_arg.compare("blindness"))
|
|
ailment_type = BCEnum::AT_Blindness;
|
|
else if (!ailment_arg.compare("disease"))
|
|
ailment_type = BCEnum::AT_Disease;
|
|
else if (!ailment_arg.compare("poison"))
|
|
ailment_type = BCEnum::AT_Poison;
|
|
else if (!ailment_arg.compare("curse"))
|
|
ailment_type = BCEnum::AT_Curse;
|
|
else if (!ailment_arg.compare("corruption"))
|
|
ailment_type = BCEnum::AT_Corruption;
|
|
|
|
if (ailment_type == BCEnum::AT_None) {
|
|
c->Message(m_fail, "You must specify a cure [ailment] to use this command");
|
|
return;
|
|
}
|
|
|
|
local_list->sort([ailment_type](STBaseEntry* l, STBaseEntry* r) {
|
|
auto _l = l->SafeCastToCure(), _r = r->SafeCastToCure();
|
|
if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] < _r->cure_value[AILMENTIDTOINDEX(ailment_type)])
|
|
return true;
|
|
if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana)
|
|
return true;
|
|
if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->cure_total < _r->cure_total)
|
|
return true;
|
|
|
|
return false;
|
|
});
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToCure();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (!local_entry->cure_value[AILMENTIDTOINDEX(ailment_type)])
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_defensive(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_defensive", sep->arg[0], "defensive"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Stance);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_Type1;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
sbl.remove(nullptr);
|
|
|
|
int success_count = 0;
|
|
int candidate_count = sbl.size();
|
|
for (auto list_iter : *local_list) {
|
|
if (sbl.empty())
|
|
break;
|
|
|
|
auto local_entry = list_iter->SafeCastToStance();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->stance_type != BCEnum::StT_Aggressive)
|
|
continue;
|
|
|
|
for (auto bot_iter = sbl.begin(); bot_iter != sbl.end(); ) {
|
|
Bot* my_bot = *bot_iter;
|
|
if (local_entry->caster_class != my_bot->GetClass()) {
|
|
++bot_iter;
|
|
continue;
|
|
}
|
|
if (local_entry->spell_level > my_bot->GetLevel()) {
|
|
++bot_iter;
|
|
continue;
|
|
}
|
|
|
|
my_bot->InterruptSpell();
|
|
if (candidate_count == 1)
|
|
Bot::BotGroupSay(my_bot, "Using '%s'", spells[local_entry->spell_id].name);
|
|
my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID());
|
|
++success_count;
|
|
|
|
bot_iter = sbl.erase(bot_iter);
|
|
}
|
|
}
|
|
|
|
c->Message(m_action, "%i of %i bots have used defensive disciplines", success_count, candidate_count);
|
|
}
|
|
|
|
void bot_command_depart(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Depart);
|
|
return;
|
|
}
|
|
|
|
bool single = false;
|
|
std::string single_arg = sep->arg[2];
|
|
if (!single_arg.compare("single"))
|
|
single = true;
|
|
|
|
std::string destination = sep->arg[1];
|
|
if (!destination.compare("list")) {
|
|
Bot* my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, DRUID);
|
|
Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, WIZARD);
|
|
helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single);
|
|
return;
|
|
}
|
|
else if (destination.empty()) {
|
|
c->Message(m_fail, "A [destination] or [list] argument is required to use this command");
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToDepart();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->single != single)
|
|
continue;
|
|
if (destination.compare(spells[local_entry->spell_id].teleport_zone))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_escape(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Escape];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Escape) || helper_command_alias_fail(c, "bot_command_escape", sep->arg[0], "escape"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s ([option: lesser])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Escape);
|
|
return;
|
|
}
|
|
|
|
bool use_lesser = false;
|
|
if (!strcasecmp(sep->arg[1], "lesser"))
|
|
use_lesser = true;
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToEscape();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->lesser != use_lesser)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_find_aliases(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_find_aliases", sep->arg[0], "findaliases"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [alias | command]", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
auto find_iter = bot_command_aliases.find(sep->arg[1]);
|
|
if (find_iter == bot_command_aliases.end()) {
|
|
c->Message(m_fail, "No bot commands or aliases match '%s'", sep->arg[1]);
|
|
return;
|
|
}
|
|
|
|
auto command_iter = bot_command_list.find(find_iter->second);
|
|
if (find_iter->second.empty() || command_iter == bot_command_list.end()) {
|
|
c->Message(m_unknown, "An unknown condition has occurred...");
|
|
return;
|
|
}
|
|
|
|
c->Message(m_message, "Available bot command aliases for '%s':", command_iter->first.c_str());
|
|
|
|
int bot_command_aliases_shown = 0;
|
|
for (auto alias_iter : bot_command_aliases) {
|
|
if (strcasecmp(find_iter->second.c_str(), alias_iter.second.c_str()) || c->Admin() < command_iter->second->access)
|
|
continue;
|
|
|
|
c->Message(m_usage, "%c%s", BOT_COMMAND_CHAR, alias_iter.first.c_str());
|
|
++bot_command_aliases_shown;
|
|
}
|
|
c->Message(m_message, "%d bot command alias%s listed.", bot_command_aliases_shown, bot_command_aliases_shown != 1 ? "es" : "");
|
|
}
|
|
|
|
void bot_command_follow(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s ([option: reset]) [actionable: byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_Type2;
|
|
|
|
bool reset = false;
|
|
int ab_arg = 1;
|
|
int name_arg = 2;
|
|
Mob* target_mob = nullptr;
|
|
|
|
std::string reset_arg = sep->arg[1];
|
|
if (!reset_arg.compare("reset")) {
|
|
reset = true;
|
|
ab_arg = 2;
|
|
name_arg = 3;
|
|
}
|
|
else {
|
|
target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single);
|
|
if (!target_mob) {
|
|
c->Message(m_fail, "You must <target> a friendly mob to use this command");
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[name_arg]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
sbl.remove(nullptr);
|
|
for (auto bot_iter : sbl) {
|
|
bot_iter->WipeHateList();
|
|
auto my_group = bot_iter->GetGroup();
|
|
if (my_group) {
|
|
if (reset) {
|
|
if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter)
|
|
bot_iter->SetFollowID(c->GetID());
|
|
else
|
|
bot_iter->SetFollowID(my_group->GetLeader()->GetID());
|
|
}
|
|
else {
|
|
if (bot_iter == target_mob)
|
|
bot_iter->SetFollowID(c->GetID());
|
|
else
|
|
bot_iter->SetFollowID(target_mob->GetID());
|
|
}
|
|
}
|
|
else {
|
|
bot_iter->SetFollowID(0);
|
|
}
|
|
if (!bot_iter->GetPet())
|
|
continue;
|
|
|
|
bot_iter->GetPet()->WipeHateList();
|
|
bot_iter->GetPet()->SetFollowID(bot_iter->GetID());
|
|
}
|
|
if (sbl.size() == 1) {
|
|
Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID());
|
|
Bot::BotGroupSay(sbl.front(), "Following %s", ((follow_mob) ? (follow_mob->GetCleanName()) : ("'nullptr'")));
|
|
}
|
|
else {
|
|
if (reset)
|
|
c->Message(m_action, "%i of your bots are following their default assignments", sbl.size());
|
|
else
|
|
c->Message(m_action, "%i of your bots are following %s", sbl.size(), target_mob->GetCleanName());
|
|
}
|
|
}
|
|
|
|
void bot_command_guard(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_guard", sep->arg[0], "guard"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [actionable: target | byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2);
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
sbl.remove(nullptr);
|
|
for (auto bot_iter : sbl) {
|
|
bot_iter->SetGuardMode();
|
|
}
|
|
if (sbl.size() == 1)
|
|
Bot::BotGroupSay(sbl.front(), "Guarding this position");
|
|
else
|
|
c->Message(m_action, "%i of your bots are guarding their positions", sbl.size());
|
|
}
|
|
|
|
void bot_command_heal_rotation(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("healrotationadaptivetargeting");
|
|
subcommand_list.push_back("healrotationaddmember");
|
|
subcommand_list.push_back("healrotationaddtarget");
|
|
subcommand_list.push_back("healrotationadjustcritical");
|
|
subcommand_list.push_back("healrotationadjustsafe");
|
|
subcommand_list.push_back("healrotationcastoverride");
|
|
subcommand_list.push_back("healrotationchangeinterval");
|
|
subcommand_list.push_back("healrotationclearhot");
|
|
subcommand_list.push_back("healrotationcleartargets");
|
|
subcommand_list.push_back("healrotationcreate");
|
|
subcommand_list.push_back("healrotationdelete");
|
|
subcommand_list.push_back("healrotationfastheals");
|
|
subcommand_list.push_back("healrotationlist");
|
|
subcommand_list.push_back("healrotationremovemember");
|
|
subcommand_list.push_back("healrotationremovetarget");
|
|
subcommand_list.push_back("healrotationresetlimits");
|
|
subcommand_list.push_back("healrotationsave");
|
|
subcommand_list.push_back("healrotationsethot");
|
|
subcommand_list.push_back("healrotationstart");
|
|
subcommand_list.push_back("healrotationstop");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = {
|
|
"healrotationadaptivetargeting", "healrotationaddmember", "healrotationaddtarget", "healrotationadjustcritical", "healrotationadjustsafe",
|
|
"healrotationcastoverride", "healrotationchangeinterval", "healrotationclearhot", "healrotationcleartargets", "healrotationcreate",
|
|
"healrotationdelete", "healrotationfastheals", "healrotationlist", "healrotationremovemember", "healrotationremovetarget", "healrotationsave",
|
|
"healrotationresetlimits", "healrotationsethot", "healrotationstart", "healrotationstop"
|
|
};
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation"))
|
|
return;
|
|
|
|
#if (EQDEBUG >= 12)
|
|
while (c->Admin() >= 250) {
|
|
if (strcasecmp(sep->arg[1], "shone")) { break; }
|
|
Bot* my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot || !(my_bot->IsHealRotationMember())) { break; }
|
|
auto tlist = (*my_bot->MemberOfHealRotation())->TargetList();
|
|
if (tlist->empty()) { break; }
|
|
for (auto tlist_iter : *tlist) {
|
|
if (tlist_iter)
|
|
tlist_iter->SetHP((tlist_iter->GetMaxHP() / 100 + 1));
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
helper_send_available_subcommands(c, "bot heal rotation", subcommand_list);
|
|
}
|
|
|
|
void bot_command_help(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_help", sep->arg[0], "help"))
|
|
return;
|
|
|
|
c->Message(m_message, "Available EQEMu bot commands:");
|
|
|
|
int bot_commands_shown = 0;
|
|
for (auto command_iter : bot_command_list) {
|
|
if (sep->arg[1][0] && command_iter.first.find(sep->arg[1]) == std::string::npos)
|
|
continue;
|
|
if (c->Admin() < command_iter.second->access)
|
|
continue;
|
|
|
|
c->Message(m_usage, "%c%s - %s", BOT_COMMAND_CHAR, command_iter.first.c_str(), command_iter.second->desc == nullptr ? "[no description]" : command_iter.second->desc);
|
|
++bot_commands_shown;
|
|
}
|
|
c->Message(m_message, "%d bot command%s listed.", bot_commands_shown, bot_commands_shown != 1 ? "s" : "");
|
|
c->Message(m_note, "type %ccommand [help | usage] for more information", BOT_COMMAND_CHAR);
|
|
}
|
|
|
|
void bot_command_hold(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_hold", sep->arg[0], "hold"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: <any>] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
sbl.remove(nullptr);
|
|
for (auto bot_iter : sbl)
|
|
bot_iter->SetPauseAI(true);
|
|
|
|
c->Message(m_action, "%i of your bots %s suspended", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is")));
|
|
}
|
|
|
|
void bot_command_identify(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Identify];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Identify) || helper_command_alias_fail(c, "bot_command_identify", sep->arg[0], "identify"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Identify);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_inventory(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("inventorygive");
|
|
subcommand_list.push_back("inventorylist");
|
|
subcommand_list.push_back("inventoryremove");
|
|
subcommand_list.push_back("inventorywindow");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = { "inventorygive", "inventorylist", "inventoryremove", "inventorywindow" };
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_command_inventory", sep->arg[0], "inventory"))
|
|
return;
|
|
|
|
helper_send_available_subcommands(c, "bot inventory", subcommand_list);
|
|
}
|
|
|
|
void bot_command_invisibility(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Invisibility];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Invisibility) || helper_command_alias_fail(c, "bot_command_invisibility", sep->arg[0], "invisibility"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s [invisibility: living | undead | animal | see]", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Invisibility);
|
|
return;
|
|
}
|
|
|
|
std::string invisibility = sep->arg[1];
|
|
|
|
BCEnum::IType invisibility_type = BCEnum::IT_None;
|
|
if (!invisibility.compare("living"))
|
|
invisibility_type = BCEnum::IT_Living;
|
|
else if (!invisibility.compare("undead"))
|
|
invisibility_type = BCEnum::IT_Undead;
|
|
else if (!invisibility.compare("animal"))
|
|
invisibility_type = BCEnum::IT_Animal;
|
|
else if (!invisibility.compare("see"))
|
|
invisibility_type = BCEnum::IT_See;
|
|
|
|
if (invisibility_type == BCEnum::IT_None) {
|
|
c->Message(m_fail, "You must specify an [invisibility]");
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToInvisibility();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->invis_type != invisibility_type)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_levitation(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Levitation];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Levitation) || helper_command_alias_fail(c, "bot_command_levitation", sep->arg[0], "levitation"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Levitation);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_lull(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Lull];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Lull) || helper_command_alias_fail(c, "bot_command_lull", sep->arg[0], "lull"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <enemy_target> %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Lull);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
//if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] && spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] < target_mob->GetLevel())
|
|
// continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
uint32 dont_root_before = 0;
|
|
if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before))
|
|
target_mob->SetDontRootMeBefore(dont_root_before);
|
|
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_mesmerize(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Mesmerize];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Mesmerize) || helper_command_alias_fail(c, "bot_command_mesmerize", sep->arg[0], "mesmerize"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <enemy_target> %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Mesmerize);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel())
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
uint32 dont_root_before = 0;
|
|
if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before))
|
|
target_mob->SetDontRootMeBefore(dont_root_before);
|
|
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_movement_speed(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_MovementSpeed];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_MovementSpeed) || helper_command_alias_fail(c, "bot_command_movement_speed", sep->arg[0], "movementspeed"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s ([group | sow])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed);
|
|
return;
|
|
}
|
|
|
|
bool group = false;
|
|
bool sow = false;
|
|
std::string arg1 = sep->arg[1];
|
|
if (!arg1.compare("group"))
|
|
group = true;
|
|
else if (!arg1.compare("sow"))
|
|
sow = true;
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToMovementSpeed();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (!sow && (local_entry->group != group))
|
|
continue;
|
|
if (sow && (local_entry->spell_id != 278)) // '278' = single-target "Spirit of Wolf"
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_pet(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("petremove");
|
|
subcommand_list.push_back("petsettype");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = { "petremove", "petsettype" };
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_command_pet", sep->arg[0], "pet"))
|
|
return;
|
|
|
|
helper_send_available_subcommands(c, "bot pet", subcommand_list);
|
|
}
|
|
|
|
void bot_command_pick_lock(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_pick_lock", sep->arg[0], "picklock"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s", sep->arg[0]);
|
|
c->Message(m_note, "requires one of the following bot classes:");
|
|
c->Message(m_message, "Rogue(5) or Bard(40)");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
float pick_lock_value = 0.0f;
|
|
ActionableBots::Filter_ByHighestPickLock(c, sbl, pick_lock_value);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "No bots are capable of performing this action");
|
|
return;
|
|
}
|
|
|
|
Bot* my_bot = sbl.front();
|
|
|
|
my_bot->InterruptSpell();
|
|
Bot::BotGroupSay(my_bot, "Attempting to pick the lock..");
|
|
|
|
std::list<Doors*> door_list;
|
|
entity_list.GetDoorsList(door_list);
|
|
|
|
int door_count = 0, open_count = 0;
|
|
for (auto door_iter : door_list) {
|
|
if (!door_iter || door_iter->IsDoorOpen())
|
|
continue;
|
|
|
|
glm::tvec4<float, glm::highp> diff = (c->GetPosition() - door_iter->GetPosition());
|
|
|
|
float curdist = ((diff.x * diff.x) + (diff.y * diff.y));
|
|
float curelev = (diff.z * diff.z);
|
|
#if (EQDEBUG >= 11)
|
|
if (curdist <= 130 && curelev <= 65 && curelev >= 25) // 2D limit is '130' (x^2 + y^2), 1D theoretically should be '65' (z^2)
|
|
Log(Logs::Detail, Logs::Doors, "bot_command_pick_lock(): DoorID: %i - Elevation difference failure within theoretical limit (%f <= 65.0)", door_iter->GetDoorID(), curelev);
|
|
#endif
|
|
if (curelev >= 25 || curdist > 130) // changed curelev from '10' to '25' - requiring diff.z to be less than '5'
|
|
continue;
|
|
|
|
++door_count;
|
|
if (pick_lock_value >= door_iter->GetLockpick()) {
|
|
door_iter->ForceOpen(my_bot);
|
|
++open_count;
|
|
}
|
|
else {
|
|
Bot::BotGroupSay(my_bot, "I am not skilled enough for this lock...");
|
|
}
|
|
}
|
|
c->Message(m_action, "%i door%s attempted - %i door%s successful", door_count, ((door_count != 1) ? ("s") : ("")), open_count, ((open_count != 1) ? ("s") : ("")));
|
|
}
|
|
|
|
void bot_command_pull(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <enemy_target> %s", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = ActionableBots::ABM_OwnerGroup; // existing behavior - need to add c->IsGrouped() check and modify code if different behavior is desired
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, "ownergroup", sbl, ab_mask) == ActionableBots::ABT_None)
|
|
return;
|
|
sbl.remove(nullptr);
|
|
|
|
auto target_mob = ActionableTarget::VerifyEnemy(c, BCEnum::TT_Single);
|
|
if (!target_mob) {
|
|
c->Message(m_fail, "Your current target is not attackable");
|
|
return;
|
|
}
|
|
|
|
Bot* bot_puller = nullptr;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter->IsArcheryRange(target_mob))
|
|
continue;
|
|
|
|
Bot::BotGroupSay(bot_iter, "Attempting to pull %s..", target_mob->GetCleanName());
|
|
bot_iter->InterruptSpell();
|
|
bot_iter->BotRangedAttack(target_mob);
|
|
bot_puller = bot_iter;
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, bot_puller);
|
|
}
|
|
|
|
void bot_command_release(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_release", sep->arg[0], "release"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: <any>] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
sbl.remove(nullptr);
|
|
for (auto bot_iter : sbl) {
|
|
bot_iter->WipeHateList();
|
|
bot_iter->SetPauseAI(false);
|
|
}
|
|
|
|
c->Message(m_action, "%i of your bots %s unsuspended", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is")));
|
|
}
|
|
|
|
void bot_command_resistance(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resistance];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resistance) || helper_command_alias_fail(c, "bot_command_resistance", sep->arg[0], "resistance"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s [resistance: fire | cold | poison | disease | magic | corruption]", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Resistance);
|
|
return;
|
|
}
|
|
|
|
std::string resistance_arg = sep->arg[1];
|
|
|
|
auto resistance_type = BCEnum::RT_None;
|
|
if (!resistance_arg.compare("fire"))
|
|
resistance_type = BCEnum::RT_Fire;
|
|
else if (!resistance_arg.compare("cold"))
|
|
resistance_type = BCEnum::RT_Cold;
|
|
else if (!resistance_arg.compare("poison"))
|
|
resistance_type = BCEnum::RT_Poison;
|
|
else if (!resistance_arg.compare("disease"))
|
|
resistance_type = BCEnum::RT_Disease;
|
|
else if (!resistance_arg.compare("magic"))
|
|
resistance_type = BCEnum::RT_Magic;
|
|
else if (!resistance_arg.compare("corruption"))
|
|
resistance_type = BCEnum::RT_Corruption;
|
|
|
|
if (resistance_type == BCEnum::RT_None) {
|
|
c->Message(m_fail, "You must specify a [resistance]");
|
|
return;
|
|
}
|
|
|
|
local_list->sort([resistance_type](STBaseEntry* l, STBaseEntry* r) {
|
|
auto _l = l->SafeCastToResistance(), _r = r->SafeCastToResistance();
|
|
if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] > _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)])
|
|
return true;
|
|
if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana)
|
|
return true;
|
|
if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->resist_total > _r->resist_total)
|
|
return true;
|
|
|
|
return false;
|
|
});
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToResistance();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (!local_entry->resist_value[RESISTANCEIDTOINDEX(resistance_type)])
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_resurrect(Client *c, const Seperator *sep)
|
|
{
|
|
// Obscure bot spell code prohibits the aoe portion from working correctly...
|
|
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resurrect];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resurrect) || helper_command_alias_fail(c, "bot_command_resurrect", sep->arg[0], "resurrect"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
//c->Message(m_usage, "usage: <corpse_target> %s ([option: aoe])", sep->arg[0]);
|
|
c->Message(m_usage, "usage: <corpse_target> %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Resurrect);
|
|
return;
|
|
}
|
|
|
|
bool aoe = false;
|
|
//std::string aoe_arg = sep->arg[1];
|
|
//if (!aoe_arg.compare("aoe"))
|
|
// aoe = true;
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToResurrect();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
//if (local_entry->aoe != aoe)
|
|
// continue;
|
|
if (local_entry->aoe)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
//if (!target_mob && !local_entry->aoe)
|
|
// continue;
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
//if (local_entry->aoe)
|
|
// target_mob = my_bot;
|
|
|
|
uint32 dont_root_before = 0;
|
|
if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before))
|
|
target_mob->SetDontRootMeBefore(dont_root_before);
|
|
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_rune(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Rune];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Rune) || helper_command_alias_fail(c, "bot_command_rune", sep->arg[0], "rune"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Rune);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_send_home(Client *c, const Seperator *sep)
|
|
{
|
|
// Obscure bot spell code prohibits the aoe portion from working correctly...
|
|
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SendHome];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SendHome) || helper_command_alias_fail(c, "bot_command_send_home", sep->arg[0], "sendhome"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s ([option: group])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_SendHome);
|
|
return;
|
|
}
|
|
|
|
bool group = false;
|
|
std::string group_arg = sep->arg[1];
|
|
if (!group_arg.compare("group"))
|
|
group = true;
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToSendHome();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->group != group)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_size(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Size];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Size) || helper_command_alias_fail(c, "bot_command_size", sep->arg[0], "size"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s [grow | shrink]", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Size);
|
|
return;
|
|
}
|
|
|
|
std::string size_arg = sep->arg[1];
|
|
auto size_type = BCEnum::SzT_Reduce;
|
|
if (!size_arg.compare("grow")) {
|
|
size_type = BCEnum::SzT_Enlarge;
|
|
}
|
|
else if (size_arg.compare("shrink")) {
|
|
c->Message(m_fail, "This command requires a [grow | shrink] argument");
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToSize();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->size_type != size_type)
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_summon_corpse(Client *c, const Seperator *sep)
|
|
{
|
|
// Same methodology as old command..but, does not appear to work... (note: didn't work there, either...)
|
|
|
|
// temp
|
|
c->Message(m_fail, "This command is currently unavailable...");
|
|
return;
|
|
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SummonCorpse];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SummonCorpse) || helper_command_alias_fail(c, "bot_command_summon_corpse", sep->arg[0], "summoncorpse"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <friendly_target> %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_SummonCorpse);
|
|
return;
|
|
}
|
|
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = ActionableTarget::AsSingle_ByPlayer(c);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
if (spells[local_entry->spell_id].base[EFFECTIDTOINDEX(1)] < target_mob->GetLevel())
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_command_taunt(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_Type1;
|
|
|
|
std::string arg1 = sep->arg[1];
|
|
|
|
bool taunt_state = false;
|
|
bool toggle_taunt = true;
|
|
int ab_arg = 1;
|
|
if (!arg1.compare("on")) {
|
|
taunt_state = true;
|
|
toggle_taunt = false;
|
|
ab_arg = 2;
|
|
}
|
|
else if (!arg1.compare("off")) {
|
|
toggle_taunt = false;
|
|
ab_arg = 2;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None)
|
|
return;
|
|
sbl.remove(nullptr);
|
|
|
|
int taunting_count = 0;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter->GetSkill(EQEmu::skills::SkillTaunt))
|
|
continue;
|
|
|
|
if (toggle_taunt)
|
|
bot_iter->SetTaunting(!bot_iter->IsTaunting());
|
|
else
|
|
bot_iter->SetTaunting(taunt_state);
|
|
|
|
if (sbl.size() == 1)
|
|
Bot::BotGroupSay(bot_iter, "I am %s taunting", bot_iter->IsTaunting() ? "now" : "no longer");
|
|
|
|
++taunting_count;
|
|
}
|
|
|
|
if (taunting_count) {
|
|
if (toggle_taunt)
|
|
c->Message(m_action, "%i of your bots %s toggled their taunting state", taunting_count, ((taunting_count != 1) ? ("have") : ("has")));
|
|
else
|
|
c->Message(m_action, "%i of your bots %s %s taunting", taunting_count, ((taunting_count != 1) ? ("have") : ("has")), ((taunt_state) ? ("started") : ("stopped")));
|
|
}
|
|
else {
|
|
c->Message(m_fail, "None of your bots are capable of taunting");
|
|
}
|
|
}
|
|
|
|
void bot_command_track(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_command_track", sep->arg[0], "track"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s (Ranger: [option=all: all | rare | local])", sep->arg[0]);
|
|
c->Message(m_note, "requires one of the following bot classes:");
|
|
c->Message(m_message, "Ranger(1), Druid(20) or Bard(35)");
|
|
return;
|
|
}
|
|
|
|
std::string tracking_scope = sep->arg[1];
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
uint16 class_mask = (PLAYER_CLASS_RANGER_BIT | PLAYER_CLASS_DRUID_BIT | PLAYER_CLASS_BARD_BIT);
|
|
ActionableBots::Filter_ByClasses(c, sbl, class_mask);
|
|
|
|
Bot* my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 1, RANGER);
|
|
if (tracking_scope.empty()) {
|
|
if (!my_bot)
|
|
my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 20, DRUID);
|
|
if (!my_bot)
|
|
my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 35, BARD);
|
|
}
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "No bots are capable of performing this action");
|
|
return;
|
|
}
|
|
|
|
int base_distance = 0;
|
|
bool track_named = false;
|
|
std::string tracking_msg;
|
|
switch (my_bot->GetClass()) {
|
|
case RANGER:
|
|
if (!tracking_scope.compare("local")) {
|
|
base_distance = 30;
|
|
tracking_msg = "Local tracking...";
|
|
}
|
|
else if (!tracking_scope.compare("rare")) {
|
|
base_distance = 80;
|
|
bool track_named = true;
|
|
tracking_msg = "Master tracking...";
|
|
}
|
|
else { // default to 'all'
|
|
base_distance = 80;
|
|
tracking_msg = "Advanced tracking...";
|
|
}
|
|
break;
|
|
case DRUID:
|
|
base_distance = 30;
|
|
tracking_msg = "Local tracking...";
|
|
break;
|
|
case BARD:
|
|
base_distance = 20;
|
|
tracking_msg = "Near tracking...";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
if (!base_distance) {
|
|
c->Message(m_unknown, "An unknown codition has occurred");
|
|
return;
|
|
}
|
|
|
|
my_bot->InterruptSpell();
|
|
Bot::BotGroupSay(my_bot, tracking_msg.c_str());
|
|
entity_list.ShowSpawnWindow(c, (c->GetLevel() * base_distance), track_named);
|
|
}
|
|
|
|
void bot_command_water_breathing(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_WaterBreathing];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_WaterBreathing) || helper_command_alias_fail(c, "bot_command_water_breathing", sep->arg[0], "waterbreathing"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<friendly_target>) %s", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_WaterBreathing);
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter;
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
|
|
/*
|
|
* bot subcommands go below here
|
|
*/
|
|
void bot_subcommand_bot_appearance(Client *c, const Seperator *sep)
|
|
{
|
|
/* VS2012 code - begin */
|
|
std::list<const char*> subcommand_list;
|
|
subcommand_list.push_back("botbeardcolor");
|
|
subcommand_list.push_back("botbeardstyle");
|
|
subcommand_list.push_back("botdetails");
|
|
subcommand_list.push_back("boteyes");
|
|
subcommand_list.push_back("botface");
|
|
subcommand_list.push_back("bothaircolor");
|
|
subcommand_list.push_back("bothairstyle");
|
|
subcommand_list.push_back("botheritage");
|
|
subcommand_list.push_back("bottattoo");
|
|
subcommand_list.push_back("botwoad");
|
|
/* VS2012 code - end */
|
|
|
|
/* VS2013 code
|
|
const std::list<const char*> subcommand_list = {
|
|
"botbeardcolor", "botbeardstyle", "botdetails", "boteyes", "botface",
|
|
"bothaircolor", "bothairstyle", "botheritage", "bottattoo", "botwoad"
|
|
};
|
|
*/
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_appearance", sep->arg[0], "botappearance"))
|
|
return;
|
|
|
|
helper_send_available_subcommands(c, "bot appearance", subcommand_list);
|
|
}
|
|
|
|
void bot_subcommand_bot_beard_color(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_beard_color", sep->arg[0], "botbeardcolor"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetGender() != MALE && my_bot->GetRace() != DWARF)
|
|
fail_type = BCEnum::AFT_GenderRace;
|
|
else if (!PlayerAppearance::IsValidBeardColor(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetBeardColor(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "beard color"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_beard_style(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_beard_style", sep->arg[0], "botbeardstyle"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetGender() != MALE && my_bot->GetRace() != DWARF)
|
|
fail_type = BCEnum::AFT_GenderRace;
|
|
else if (!PlayerAppearance::IsValidBeard(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetBeard(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "beard style"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_camp(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_camp", sep->arg[0], "botcamp"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl)
|
|
bot_iter->Camp();
|
|
}
|
|
|
|
void bot_subcommand_bot_clone(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_clone", sep->arg[0], "botclone"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [clone_name]", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
if (!my_bot->GetBotID()) {
|
|
c->Message(m_unknown, "An unknown error has occured - BotName: %s, BotID: %u", my_bot->GetCleanName(), my_bot->GetBotID());
|
|
Log(Logs::General, Logs::Commands, "bot_command_clone(): - Error: Active bot reported invalid ID (BotName: %s, BotID: %u, OwnerName: %s, OwnerID: %u, AcctName: %s, AcctID: %u)",
|
|
my_bot->GetCleanName(), my_bot->GetBotID(), c->GetCleanName(), c->CharacterID(), c->AccountName(), c->AccountID());
|
|
return;
|
|
}
|
|
|
|
if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) {
|
|
c->Message(m_fail, "You must [name] your bot clone");
|
|
return;
|
|
}
|
|
std::string bot_name = sep->arg[1];
|
|
|
|
if (!Bot::IsValidName(bot_name)) {
|
|
c->Message(m_fail, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str());
|
|
return;
|
|
}
|
|
|
|
std::string error_message;
|
|
|
|
bool available_flag = false;
|
|
if (!botdb.QueryNameAvailablity(bot_name, available_flag)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::QueryNameAvailablity());
|
|
return;
|
|
}
|
|
if (!available_flag) {
|
|
c->Message(m_fail, "The name %s is already being used. Please choose a different name", bot_name.c_str());
|
|
return;
|
|
}
|
|
|
|
uint32 max_bot_count = RuleI(Bots, CreationLimit);
|
|
|
|
uint32 bot_count = 0;
|
|
if (!botdb.QueryBotCount(c->CharacterID(), bot_count)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::QueryBotCount());
|
|
return;
|
|
}
|
|
if (bot_count >= max_bot_count) {
|
|
c->Message(m_fail, "You have reached the maximum limit of %i bots", max_bot_count);
|
|
return;
|
|
}
|
|
|
|
uint32 clone_id = 0;
|
|
if (!botdb.CreateCloneBot(c->CharacterID(), my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
|
|
c->Message(m_fail, "%s '%s'", BotDatabase::fail::CreateCloneBot(), bot_name.c_str());
|
|
return;
|
|
}
|
|
|
|
int clone_stance = BotStancePassive;
|
|
if (!botdb.LoadStance(my_bot->GetBotID(), clone_stance))
|
|
c->Message(m_fail, "%s for bot '%s'", BotDatabase::fail::LoadStance(), my_bot->GetCleanName());
|
|
if (!botdb.SaveStance(clone_id, clone_stance))
|
|
c->Message(m_fail, "%s for clone '%s'", BotDatabase::fail::SaveStance(), bot_name.c_str());
|
|
|
|
if (!botdb.CreateCloneBotInventory(c->CharacterID(), my_bot->GetBotID(), clone_id))
|
|
c->Message(m_fail, "%s for clone '%s'", BotDatabase::fail::CreateCloneBotInventory(), bot_name.c_str());
|
|
|
|
c->Message(m_action, "Bot '%s' was successfully cloned to bot '%s'", my_bot->GetCleanName(), bot_name.c_str());
|
|
}
|
|
|
|
void bot_subcommand_bot_create(Client *c, const Seperator *sep)
|
|
{
|
|
const std::string class_substrs[17] = { "",
|
|
"%u(WAR)", "%u(CLR)", "%u(PAL)", "%u(RNG)",
|
|
"%u(SHD)", "%u(DRU)", "%u(MNK)", "%u(BRD)",
|
|
"%u(ROG)", "%u(SHM)", "%u(NEC)", "%u(WIZ)",
|
|
"%u(MAG)", "%u(ENC)", "%u(BST)", "%u(BER)"
|
|
};
|
|
|
|
const std::string race_substrs[17] = { "",
|
|
"%u(HUM)", "%u(BAR)", "%u(ERU)", "%u(ELF)",
|
|
"%u(HIE)", "%u(DEF)", "%u(HEF)", "%u(DWF)",
|
|
"%u(TRL)", "%u(OGR)", "%u(HFL)", "%u(GNM)",
|
|
"%u(IKS)", "%u(VAH)", "%u(FRG)", "%u(DRK)"
|
|
};
|
|
|
|
const uint16 race_values[17] = { 0,
|
|
HUMAN, BARBARIAN, ERUDITE, WOOD_ELF,
|
|
HIGH_ELF, DARK_ELF, HALF_ELF, DWARF,
|
|
TROLL, OGRE, HALFLING, GNOME,
|
|
IKSAR, VAHSHIR, FROGLOK, DRAKKIN
|
|
};
|
|
|
|
const std::string gender_substrs[2] = {
|
|
"%u(M)", "%u(F)",
|
|
};
|
|
|
|
std::string msg_class = "class:";
|
|
std::string msg_race = "race:";
|
|
std::string msg_gender = "gender:";
|
|
std::string msg_separator;
|
|
|
|
msg_separator = " ";
|
|
for (int i = 0; i <= 15; ++i) {
|
|
if (((1 << i) & RuleI(Bots, AllowedClasses)) == 0)
|
|
continue;
|
|
|
|
msg_class.append(const_cast<const std::string&>(msg_separator));
|
|
msg_class.append(StringFormat(class_substrs[i + 1].c_str(), (i + 1)));
|
|
|
|
msg_separator = ", ";
|
|
}
|
|
|
|
msg_separator = " ";
|
|
for (int i = 0; i <= 15; ++i) {
|
|
if (((1 << i) & RuleI(Bots, AllowedRaces)) == 0)
|
|
continue;
|
|
|
|
msg_race.append(const_cast<const std::string&>(msg_separator));
|
|
msg_race.append(StringFormat(race_substrs[i + 1].c_str(), race_values[i + 1]));
|
|
|
|
msg_separator = ", ";
|
|
}
|
|
|
|
msg_separator = " ";
|
|
for (int i = 0; i <= 1; ++i) {
|
|
if (((1 << i) & RuleI(Bots, AllowedGenders)) == 0)
|
|
continue;
|
|
|
|
msg_gender.append(const_cast<const std::string&>(msg_separator));
|
|
msg_gender.append(StringFormat(gender_substrs[i].c_str(), i));
|
|
|
|
msg_separator = ", ";
|
|
}
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [bot_name] [bot_class] [bot_race] [bot_gender]", sep->arg[0]);
|
|
c->Message(m_note, msg_class.c_str());
|
|
c->Message(m_note, msg_race.c_str());
|
|
c->Message(m_note, msg_gender.c_str());
|
|
return;
|
|
}
|
|
|
|
if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) {
|
|
c->Message(m_fail, "You must [name] your bot");
|
|
return;
|
|
}
|
|
std::string bot_name = sep->arg[1];
|
|
|
|
if (sep->arg[2][0] == '\0' || !sep->IsNumber(2)) {
|
|
c->Message(m_fail, msg_class.c_str());
|
|
return;
|
|
}
|
|
uint8 bot_class = atoi(sep->arg[2]);
|
|
|
|
if (sep->arg[3][0] == '\0' || !sep->IsNumber(3)) {
|
|
c->Message(m_fail, msg_race.c_str());
|
|
return;
|
|
}
|
|
uint16 bot_race = atoi(sep->arg[3]);
|
|
|
|
if (sep->arg[4][0] == '\0') {
|
|
c->Message(m_fail, msg_gender.c_str());
|
|
return;
|
|
}
|
|
uint8 bot_gender = atoi(sep->arg[4]);
|
|
|
|
helper_bot_create(c, bot_name, bot_class, bot_race, bot_gender);
|
|
}
|
|
|
|
void bot_subcommand_bot_delete(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_delete", sep->arg[0], "botdelete"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
std::string error_message;
|
|
|
|
if (!my_bot->DeleteBot()) {
|
|
c->Message(m_unknown, "Failed to delete '%s' due to database error", my_bot->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
auto bid = my_bot->GetBotID();
|
|
std::string bot_name = my_bot->GetCleanName();
|
|
|
|
my_bot->Camp(false);
|
|
|
|
c->Message(m_action, "Successfully deleted bot '%s' (id: %i)", bot_name.c_str(), bid);
|
|
}
|
|
|
|
void bot_subcommand_bot_details(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Trouble-shoot model update issue
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_details", sep->arg[0], "botdetails"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Drakkin bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint32 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetRace() != DRAKKIN)
|
|
fail_type = BCEnum::AFT_Race;
|
|
else if (!PlayerAppearance::IsValidDetail(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetDrakkinDetails(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "details"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_dye_armor(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Trouble-shoot model update issue
|
|
|
|
const std::string msg_matslot = StringFormat("mat_slot: %c(All), %i(Head), %i(Chest), %i(Arms), %i(Wrists), %i(Hands), %i(Legs), %i(Feet)",
|
|
'*', EQEmu::textures::armorHead, EQEmu::textures::armorChest, EQEmu::textures::armorArms, EQEmu::textures::armorWrist, EQEmu::textures::armorHands, EQEmu::textures::armorLegs, EQEmu::textures::armorFeet);
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_dye_armor", sep->arg[0], "botdyearmor"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [mat_slot] [red: 0-255] [green: 0-255] [blue: 0-255] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
c->Message(m_note, msg_matslot.c_str());
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
uint8 material_slot = EQEmu::textures::materialInvalid;
|
|
int16 slot_id = INVALID_INDEX;
|
|
|
|
bool dye_all = (sep->arg[1][0] == '*');
|
|
if (!dye_all) {
|
|
material_slot = atoi(sep->arg[1]);
|
|
slot_id = EQEmu::InventoryProfile::CalcSlotFromMaterial(material_slot);
|
|
|
|
if (!sep->IsNumber(1) || slot_id == INVALID_INDEX || material_slot > EQEmu::textures::LastTintableTexture) {
|
|
c->Message(m_fail, "Valid [mat_slot]s for this command are:");
|
|
c->Message(m_fail, msg_matslot.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32 red_value = atoi(sep->arg[2]);
|
|
if (!sep->IsNumber(2) || red_value > 255) {
|
|
c->Message(m_fail, "Valid [red] values for this command are [0-255]");
|
|
return;
|
|
}
|
|
|
|
uint32 green_value = atoi(sep->arg[3]);
|
|
if (!sep->IsNumber(3) || green_value > 255) {
|
|
c->Message(m_fail, "Valid [green] values for this command are [0-255]");
|
|
return;
|
|
}
|
|
|
|
uint32 blue_value = atoi(sep->arg[4]);
|
|
if (!sep->IsNumber(4) || blue_value > 255) {
|
|
c->Message(m_fail, "Valid [blue] values for this command are [0-255]");
|
|
return;
|
|
}
|
|
|
|
uint32 rgb_value = ((0xFF) | (red_value << 16) | (green_value << 8) | (blue_value)); // watch the leading '(0xFF) | '
|
|
|
|
std::list<Bot*> sbl;
|
|
auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[5], sbl, ab_mask);
|
|
if (ab_type == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
if (!bot_iter->DyeArmor(slot_id, rgb_value, dye_all, (ab_type != ActionableBots::ABT_All))) {
|
|
c->Message(m_fail, "Failed to change armor color for '%s' due to unknown cause", bot_iter->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
//if (dye_all)
|
|
// helper_bot_appearance_form_update(bot_iter);
|
|
}
|
|
|
|
if (ab_type == ActionableBots::ABT_All) {
|
|
if (dye_all) {
|
|
if (!botdb.SaveAllArmorColors(c->CharacterID(), rgb_value))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveAllArmorColors());
|
|
}
|
|
else {
|
|
if (!botdb.SaveAllArmorColorBySlot(c->CharacterID(), slot_id, rgb_value))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveAllArmorColorBySlot());
|
|
}
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_eyes(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Trouble-shoot model update issue
|
|
|
|
// not sure if left/right bias is allowed in pc-type entities (something is keeping them from being different)
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_eyes", sep->arg[0], "boteyes"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
//c->Message(m_usage, "usage: <target_bot> %s [value:0-n] ([option: left | right])", sep->arg[0]);
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n]", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
//uint8 eye_bias = 0;
|
|
//std::string arg2 = sep->arg[2];
|
|
//if (!arg2.compare("left"))
|
|
// eye_bias = 1;
|
|
//else if (!arg2.compare("right"))
|
|
// eye_bias = 2;
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (!PlayerAppearance::IsValidEyeColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) {
|
|
fail_type = BCEnum::AFT_Value;
|
|
}
|
|
else {
|
|
//if (eye_bias == 1) {
|
|
// my_bot->SetEyeColor1(uvalue);
|
|
//}
|
|
//else if (eye_bias == 2) {
|
|
// my_bot->SetEyeColor2(uvalue);
|
|
//}
|
|
//else {
|
|
my_bot->SetEyeColor1(uvalue);
|
|
my_bot->SetEyeColor2(uvalue);
|
|
//}
|
|
}
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "eyes"))
|
|
return;
|
|
|
|
c->Message(m_action, "This feature will update the next time your bot is spawned");
|
|
//helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_face(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_face", sep->arg[0], "botface"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n]", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (!PlayerAppearance::IsValidFace(my_bot->GetRace(), my_bot->GetGender(), uvalue)) {
|
|
fail_type = BCEnum::AFT_Value;
|
|
}
|
|
else {
|
|
uint8 old_woad = 0;
|
|
if (my_bot->GetRace() == BARBARIAN)
|
|
old_woad = ((my_bot->GetLuclinFace() / 10) * 10);
|
|
my_bot->SetLuclinFace((old_woad + uvalue));
|
|
}
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "face"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_follow_distance(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_follow_distance", sep->arg[0], "botfollowdistance"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
c->Message(m_usage, "usage: %s [clear] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT;
|
|
bool set_flag = false;
|
|
int ab_arg = 2;
|
|
|
|
if (!strcasecmp(sep->arg[1], "set")) {
|
|
if (!sep->IsNumber(2)) {
|
|
c->Message(m_fail, "A numeric [distance] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
bfd = atoi(sep->arg[2]);
|
|
if (bfd < 1)
|
|
bfd = 1;
|
|
if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX)
|
|
bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX;
|
|
set_flag = true;
|
|
ab_arg = 3;
|
|
}
|
|
else if (strcasecmp(sep->arg[1], "clear")) {
|
|
c->Message(m_fail, "This command requires a [set | clear] argument");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]);
|
|
if (ab_type == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
int bot_count = 0;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
bot_iter->SetFollowDistance(bfd);
|
|
if (ab_type != ActionableBots::ABT_All && !botdb.SaveFollowDistance(c->CharacterID(), bot_iter->GetBotID(), bfd)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::SaveFollowDistance(), bot_iter->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
++bot_count;
|
|
}
|
|
|
|
if (ab_type == ActionableBots::ABT_All) {
|
|
if (!botdb.SaveAllFollowDistances(c->CharacterID(), bfd)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveAllFollowDistances());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared");
|
|
}
|
|
else {
|
|
c->Message(m_action, "%s %i of your spawned bot follow distances", (set_flag ? "Set" : "Cleared"), bot_count);
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_hair_color(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_hair_color", sep->arg[0], "bothaircolor"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n]", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (!PlayerAppearance::IsValidHairColor(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetHairColor(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "hair color"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_hairstyle(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_hairstyle", sep->arg[0], "bothairstyle"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n]", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (!PlayerAppearance::IsValidHair(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetHairStyle(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "hair style"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_heritage(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Trouble-shoot model update issue
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_heritage", sep->arg[0], "botheritage"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Drakkin bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint32 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetRace() != DRAKKIN)
|
|
fail_type = BCEnum::AFT_Race;
|
|
else if (!PlayerAppearance::IsValidHeritage(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetDrakkinHeritage(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "heritage"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_inspect_message(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_inspect_message", sep->arg[0], "botinspectmessage"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
c->Message(m_note, "Notes:");
|
|
if (c->ClientVersion() >= EQEmu::versions::ClientVersion::SoF) {
|
|
c->Message(m_message, "- Self-inspect and type your bot's inspect message");
|
|
c->Message(m_message, "- Close the self-inspect window to update the server");
|
|
c->Message(m_message, "- Type '%s set' to set the bot's inspect message", sep->arg[0]);
|
|
}
|
|
else {
|
|
c->Message(m_message, "- Self-inspect and type your bot's inspect message");
|
|
c->Message(m_message, "- Close the self-inspect window");
|
|
c->Message(m_message, "- Self-inspect again to update the server");
|
|
c->Message(m_message, "- Type '%s set' to set the bot's inspect message", sep->arg[0]);
|
|
}
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
bool set_flag = false;
|
|
if (!strcasecmp(sep->arg[1], "set")) {
|
|
set_flag = true;
|
|
}
|
|
else if (strcasecmp(sep->arg[1], "clear")) {
|
|
c->Message(15, "This command requires a [set | clear] argument");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]);
|
|
if (ab_type == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
const auto client_message_struct = &c->GetInspectMessage();
|
|
|
|
int bot_count = 0;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
auto bot_message_struct = &bot_iter->GetInspectMessage();
|
|
memset(bot_message_struct, 0, sizeof(InspectMessage_Struct));
|
|
if (set_flag)
|
|
memcpy(bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct));
|
|
|
|
if (ab_type != ActionableBots::ABT_All && !botdb.SaveInspectMessage(bot_iter->GetBotID(), *bot_message_struct)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::SaveInspectMessage(), bot_iter->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
++bot_count;
|
|
}
|
|
|
|
if (ab_type == ActionableBots::ABT_All) {
|
|
InspectMessage_Struct bot_message_struct;
|
|
memset(&bot_message_struct, 0, sizeof(InspectMessage_Struct));
|
|
if (set_flag)
|
|
memcpy(&bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct));
|
|
|
|
if (!botdb.SaveAllInspectMessages(c->CharacterID(), bot_message_struct)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveAllInspectMessages());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "%s all of your bot inspect messages", set_flag ? "Set" : "Cleared");
|
|
}
|
|
else {
|
|
c->Message(m_action, "%s %i of your spawned bot inspect messages", set_flag ? "Set" : "Cleared", bot_count);
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_list(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Consider re-working to use player race values instead of actual race
|
|
|
|
enum { FilterClass, FilterRace, FilterName, FilterCount, MaskClass = (1 << FilterClass), MaskRace = (1 << FilterRace), MaskName = (1 << FilterName) };
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_list", sep->arg[0], "botlist"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([class] [value]) ([race] [value]) ([name] [partial-full])", sep->arg[0]);
|
|
c->Message(m_note, "note: filter criteria is orderless and optional");
|
|
return;
|
|
}
|
|
|
|
uint32 filter_value[FilterCount];
|
|
int name_criteria_arg = 0;
|
|
memset(&filter_value, 0, sizeof(uint32) * FilterCount);
|
|
|
|
int filter_mask = 0;
|
|
for (int i = 1; i < (FilterCount * 2); i += 2) {
|
|
if (sep->arg[i][0] == '\0')
|
|
break;
|
|
|
|
if (!strcasecmp(sep->arg[i], "class")) {
|
|
filter_mask |= MaskClass;
|
|
filter_value[FilterClass] = atoi(sep->arg[i + 1]);
|
|
continue;
|
|
}
|
|
if (!strcasecmp(sep->arg[i], "race")) {
|
|
filter_mask |= MaskRace;
|
|
filter_value[FilterRace] = atoi(sep->arg[i + 1]);
|
|
continue;
|
|
}
|
|
if (!strcasecmp(sep->arg[i], "name")) {
|
|
filter_mask |= MaskName;
|
|
name_criteria_arg = (i + 1);
|
|
continue;
|
|
}
|
|
|
|
c->Message(m_fail, "A numeric value or name is required to use the filter property of this command (f: '%s', v: '%s')", sep->arg[i], sep->arg[i + 1]);
|
|
return;
|
|
}
|
|
|
|
std::list<BotsAvailableList> bots_list;
|
|
if (!botdb.LoadBotsList(c->CharacterID(), bots_list)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadBotsList());
|
|
return;
|
|
}
|
|
if (bots_list.empty()) {
|
|
c->Message(m_fail, "You have no bots");
|
|
return;
|
|
}
|
|
|
|
int bot_count = 0;
|
|
for (auto bots_iter : bots_list) {
|
|
if (filter_mask) {
|
|
if ((filter_mask & MaskClass) && filter_value[FilterClass] != bots_iter.Class)
|
|
continue;
|
|
if ((filter_mask & MaskRace) && filter_value[FilterRace] != bots_iter.Race)
|
|
continue;
|
|
if (filter_mask & MaskName) {
|
|
std::string name_criteria = sep->arg[name_criteria_arg];
|
|
std::transform(name_criteria.begin(), name_criteria.end(), name_criteria.begin(), ::tolower);
|
|
std::string name_check = bots_iter.Name;
|
|
std::transform(name_check.begin(), name_check.end(), name_check.begin(), ::tolower);
|
|
if (name_check.find(name_criteria) == std::string::npos)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
c->Message(m_message, "%s is a level %u %s %s %s",
|
|
bots_iter.Name,
|
|
bots_iter.Level,
|
|
Bot::RaceIdToString(bots_iter.Race).c_str(),
|
|
((bots_iter.Gender == FEMALE) ? ("Female") : ((bots_iter.Gender == MALE) ? ("Male") : ("Neuter"))),
|
|
Bot::ClassIdToString(bots_iter.Class).c_str()
|
|
);
|
|
|
|
++bot_count;
|
|
}
|
|
if (!bot_count) {
|
|
c->Message(m_fail, "You have no bots meeting this criteria");
|
|
}
|
|
else {
|
|
c->Message(m_action, "%i of %i bot%s shown", bot_count, bots_list.size(), ((bot_count != 1) ? ("s") : ("")));
|
|
c->Message(m_message, "Your limit is %i bot%s", RuleI(Bots, CreationLimit), ((RuleI(Bots, CreationLimit) != 1) ? ("s") : ("")));
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_out_of_combat(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_out_of_combat", sep->arg[0], "botoutofcombat"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
std::string arg1 = sep->arg[1];
|
|
|
|
bool behavior_state = false;
|
|
bool toggle_behavior = true;
|
|
int ab_arg = 1;
|
|
if (!arg1.compare("on")) {
|
|
behavior_state = true;
|
|
toggle_behavior = false;
|
|
ab_arg = 2;
|
|
}
|
|
else if (!arg1.compare("off")) {
|
|
toggle_behavior = false;
|
|
ab_arg = 2;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
if (toggle_behavior)
|
|
bot_iter->SetAltOutOfCombatBehavior(!bot_iter->GetAltOutOfCombatBehavior());
|
|
else
|
|
bot_iter->SetAltOutOfCombatBehavior(behavior_state);
|
|
|
|
helper_bot_out_of_combat(c, bot_iter);
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_report(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_report", sep->arg[0], "botreport"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::string ab_type_arg = sep->arg[1];
|
|
if (ab_type_arg.empty()) {
|
|
if (c->GetTarget()) {
|
|
if (c->GetTarget()->IsClient() && c->GetTarget()->CastToClient() == c)
|
|
ab_type_arg = "ownergroup";
|
|
else if (c->GetTarget()->IsClient() && c->GetTarget()->CastToClient() != c)
|
|
ab_type_arg = "targetgroup";
|
|
}
|
|
else {
|
|
ab_type_arg = "spawned";
|
|
}
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, ab_type_arg.c_str(), sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
std::string report_msg = StringFormat("%s %s reports", Bot::ClassIdToString(bot_iter->GetClass()).c_str(), bot_iter->GetCleanName());
|
|
report_msg.append(StringFormat(": %3.1f%% health", bot_iter->GetHPRatio()));
|
|
if (!IsNonSpellFighterClass(bot_iter->GetClass()))
|
|
report_msg.append(StringFormat(": %3.1f%% mana", bot_iter->GetManaRatio()));
|
|
|
|
c->Message(m_message, "%s", report_msg.c_str());
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_spawn(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_spawn", sep->arg[0], "botspawn"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [bot_name]", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
int rule_level = RuleI(Bots, BotCharacterLevel);
|
|
if (c->GetLevel() < rule_level) {
|
|
c->Message(m_fail, "You must be level %i to use bots", rule_level);
|
|
return;
|
|
}
|
|
|
|
if (c->GetFeigned()) {
|
|
c->Message(m_fail, "You can not spawn a bot while feigned");
|
|
return;
|
|
}
|
|
|
|
int spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID());
|
|
|
|
int rule_limit = RuleI(Bots, SpawnLimit);
|
|
if (spawned_bot_count >= rule_limit && !c->GetGM()) {
|
|
c->Message(m_fail, "You can not have more than %i spawned bots", rule_limit);
|
|
return;
|
|
}
|
|
|
|
if (RuleB(Bots, QuestableSpawnLimit) && !c->GetGM()) {
|
|
int allowed_bot_count = 0;
|
|
if (!botdb.LoadQuestableSpawnCount(c->CharacterID(), allowed_bot_count)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadQuestableSpawnCount());
|
|
return;
|
|
}
|
|
if (!allowed_bot_count) {
|
|
c->Message(m_fail, "You are not currently allowed any spawned bots");
|
|
return;
|
|
}
|
|
if (spawned_bot_count >= allowed_bot_count) {
|
|
c->Message(m_fail, "You have reached your current limit of %i spawned bots", allowed_bot_count);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) {
|
|
c->Message(m_fail, "You must specify a [name] to use this command");
|
|
return;
|
|
}
|
|
std::string bot_name = sep->arg[1];
|
|
|
|
uint32 bot_id = 0;
|
|
if (!botdb.LoadBotID(c->CharacterID(), bot_name, bot_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotID(), bot_name.c_str());
|
|
return;
|
|
}
|
|
if (!bot_id) {
|
|
c->Message(m_fail, "You don't own a bot named '%s'", bot_name.c_str());
|
|
return;
|
|
}
|
|
|
|
if (entity_list.GetMobByBotID(bot_id)) {
|
|
c->Message(m_fail, "'%s' is already spawned in zone", bot_name.c_str());
|
|
return;
|
|
}
|
|
|
|
// this probably needs work...
|
|
if (c->GetGroup()) {
|
|
std::list<Mob*> group_list;
|
|
c->GetGroup()->GetMemberList(group_list);
|
|
for (auto member_iter : group_list) {
|
|
if (!member_iter)
|
|
continue;
|
|
if (member_iter->qglobal) // what is this?? really should have had a message to describe failure... (can't spawn bots if you are assigned to a task/instance?)
|
|
return;
|
|
if (!member_iter->qglobal && (member_iter->GetAppearance() != eaDead) && (member_iter->IsEngaged() || (member_iter->IsClient() && member_iter->CastToClient()->GetAggroCount()))) {
|
|
c->Message(m_fail, "You can't summon bots while you are engaged.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (c->GetAggroCount() > 0) {
|
|
c->Message(m_fail, "You can't spawn bots while you are engaged.");
|
|
return;
|
|
}
|
|
|
|
//if (c->IsEngaged()) {
|
|
// c->Message(m_fail, "You can't spawn bots while you are engaged.");
|
|
// return;
|
|
//}
|
|
|
|
auto my_bot = Bot::LoadBot(bot_id);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "No valid bot '%s' (id: %i) exists", bot_name.c_str(), bot_id);
|
|
return;
|
|
}
|
|
|
|
if (!my_bot->Spawn(c)) {
|
|
c->Message(m_fail, "Failed to spawn bot '%s' (id: %i)", bot_name.c_str(), bot_id);
|
|
safe_delete(my_bot);
|
|
return;
|
|
}
|
|
|
|
static const char* bot_spawn_message[16] = {
|
|
"A solid weapon is my ally!", // WARRIOR / 'generic'
|
|
"The pious shall never die!", // CLERIC
|
|
"I am the symbol of Light!", // PALADIN
|
|
"There are enemies near!", // RANGER
|
|
"Out of the shadows, I step!", // SHADOWKNIGHT
|
|
"Nature's fury shall be wrought!", // DRUID
|
|
"Your punishment will be my fist!", // MONK
|
|
"Music is the overture of battle! ", // BARD
|
|
"Daggers into the backs of my enemies!", // ROGUE
|
|
"More bones to grind!", // SHAMAN
|
|
"Death is only the beginning!", // NECROMANCER
|
|
"I am the harbinger of demise!", // WIZARD
|
|
"The elements are at my command!", // MAGICIAN
|
|
"No being can resist my charm!", // ENCHANTER
|
|
"Battles are won by hand and paw!", // BEASTLORD
|
|
"My bloodthirst shall not be quenched!" // BERSERKER
|
|
};
|
|
|
|
Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[CLASSIDTOINDEX(my_bot->GetClass())]);
|
|
}
|
|
|
|
void bot_subcommand_bot_stance(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_stance", sep->arg[0], "botstance"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [current | value: 0-6] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
c->Message(m_note, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)",
|
|
BotStancePassive, GetBotStanceName(BotStancePassive),
|
|
BotStanceBalanced, GetBotStanceName(BotStanceBalanced),
|
|
BotStanceEfficient, GetBotStanceName(BotStanceEfficient),
|
|
BotStanceReactive, GetBotStanceName(BotStanceReactive),
|
|
BotStanceAggressive, GetBotStanceName(BotStanceAggressive),
|
|
BotStanceBurn, GetBotStanceName(BotStanceBurn),
|
|
BotStanceBurnAE, GetBotStanceName(BotStanceBurnAE)
|
|
);
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
bool current_flag = false;
|
|
auto bst = BotStanceUnknown;
|
|
|
|
if (!strcasecmp(sep->arg[1], "current"))
|
|
current_flag = true;
|
|
else if (sep->IsNumber(1))
|
|
bst = VALIDBOTSTANCE(atoi(sep->arg[1]));
|
|
|
|
if (!current_flag && bst == BotStanceUnknown) {
|
|
c->Message(m_fail, "A [current] argument or valid numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
if (!current_flag) {
|
|
bot_iter->SetBotStance(bst);
|
|
bot_iter->Save();
|
|
}
|
|
|
|
Bot::BotGroupSay(bot_iter, "My current stance is '%s' (%u)", GetBotStanceName(bot_iter->GetBotStance()), bot_iter->GetBotStance());
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_stop_melee_level(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_stop_melee_level", sep->arg[0], "botstopmeleelevel"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [current | reset | sync | value: 0-255]", sep->arg[0]);
|
|
c->Message(m_note, "note: Only caster and spell-casting fighter class bots may be modified");
|
|
c->Message(m_note, "note: Use [reset] to set stop melee level to server rule");
|
|
c->Message(m_note, "note: Use [sync] to set stop melee level to current bot level");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
if (!IsCasterClass(my_bot->GetClass()) && !IsSpellFighterClass(my_bot->GetClass())) {
|
|
c->Message(m_fail, "You must <target> a caster or spell-casting fighter class bot to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 sml = RuleI(Bots, CasterStopMeleeLevel);
|
|
|
|
if (sep->IsNumber(1)) {
|
|
sml = atoi(sep->arg[1]);
|
|
}
|
|
else if (!strcasecmp(sep->arg[1], "sync")) {
|
|
sml = my_bot->GetLevel();
|
|
}
|
|
else if (!strcasecmp(sep->arg[1], "current")) {
|
|
c->Message(m_message, "My current melee stop level is %u", my_bot->GetStopMeleeLevel());
|
|
return;
|
|
}
|
|
else if (strcasecmp(sep->arg[1], "reset")) {
|
|
c->Message(m_fail, "A [current] or [reset] argument, or numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
// [reset] falls through with initialization value
|
|
|
|
my_bot->SetStopMeleeLevel(sml);
|
|
if (!botdb.SaveStopMeleeLevel(c->CharacterID(), my_bot->GetBotID(), sml))
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::SaveStopMeleeLevel(), my_bot->GetCleanName());
|
|
|
|
c->Message(m_action, "Successfully set stop melee level for %s to %u", my_bot->GetCleanName(), sml);
|
|
}
|
|
|
|
void bot_subcommand_bot_summon(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_summon", sep->arg[0], "botsummon"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
//Bot::BotGroupSay(bot_iter, "Whee!");
|
|
|
|
bot_iter->WipeHateList();
|
|
bot_iter->SetTarget(nullptr);
|
|
bot_iter->Warp(glm::vec3(c->GetPosition()));
|
|
bot_iter->DoAnim(0);
|
|
|
|
if (!bot_iter->HasPet())
|
|
continue;
|
|
|
|
bot_iter->GetPet()->WipeHateList();
|
|
bot_iter->GetPet()->SetTarget(nullptr);
|
|
bot_iter->GetPet()->Warp(glm::vec3(c->GetPosition()));
|
|
}
|
|
|
|
if (sbl.size() == 1)
|
|
c->Message(m_action, "Summoned %s to you", ((sbl.front()) ? (sbl.front()->GetCleanName()) : ("'nullptr'")));
|
|
else
|
|
c->Message(m_action, "Summoned %i bots to you", sbl.size());
|
|
}
|
|
|
|
void bot_subcommand_bot_tattoo(Client *c, const Seperator *sep)
|
|
{
|
|
// TODO: Trouble-shoot model update issue
|
|
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_tattoo", sep->arg[0], "bottattoo"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Drakkin bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint32 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetRace() != DRAKKIN)
|
|
fail_type = BCEnum::AFT_Race;
|
|
else if (!PlayerAppearance::IsValidTattoo(my_bot->GetRace(), my_bot->GetGender(), uvalue))
|
|
fail_type = BCEnum::AFT_Value;
|
|
else
|
|
my_bot->SetDrakkinTattoo(uvalue);
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "tattoo"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_bot_toggle_archer(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_toggle_archer", sep->arg[0], "bottogglearcher"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
std::string arg1 = sep->arg[1];
|
|
|
|
bool archer_state = false;
|
|
bool toggle_archer = true;
|
|
int ab_arg = 1;
|
|
if (!arg1.compare("on")) {
|
|
archer_state = true;
|
|
toggle_archer = false;
|
|
ab_arg = 2;
|
|
}
|
|
else if (!arg1.compare("off")) {
|
|
toggle_archer = false;
|
|
ab_arg = 2;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
if (toggle_archer)
|
|
bot_iter->SetBotArcher(!bot_iter->IsBotArcher());
|
|
else
|
|
bot_iter->SetBotArcher(archer_state);
|
|
bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher());
|
|
|
|
if (bot_iter->GetClass() == RANGER && bot_iter->GetLevel() >= 61)
|
|
bot_iter->SetRangerAutoWeaponSelect(bot_iter->IsBotArcher());
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_bot_toggle_helm(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_toggle_helm", sep->arg[0], "bottogglehelm"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
const int ab_mask = ActionableBots::ABM_NoFilter;
|
|
|
|
std::string arg1 = sep->arg[1];
|
|
|
|
bool helm_state = false;
|
|
bool toggle_helm = true;
|
|
int ab_arg = 1;
|
|
if (!arg1.compare("on")) {
|
|
helm_state = true;
|
|
toggle_helm = false;
|
|
ab_arg = 2;
|
|
}
|
|
else if (!arg1.compare("off")) {
|
|
toggle_helm = false;
|
|
ab_arg = 2;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]);
|
|
if (ab_type == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
int bot_count = 0;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
if (toggle_helm)
|
|
bot_iter->SetShowHelm(!bot_iter->GetShowHelm());
|
|
else
|
|
bot_iter->SetShowHelm(helm_state);
|
|
|
|
if (ab_type != ActionableBots::ABT_All) {
|
|
if (!botdb.SaveHelmAppearance(c->CharacterID(), bot_iter->GetBotID(), bot_iter->GetShowHelm())) {
|
|
c->Message(m_unknown, "%s for '%s'", bot_iter->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
|
|
SpawnAppearance_Struct* saptr = (SpawnAppearance_Struct*)outapp->pBuffer;
|
|
saptr->spawn_id = bot_iter->GetID();
|
|
saptr->type = AT_ShowHelm;
|
|
saptr->parameter = bot_iter->GetShowHelm();
|
|
|
|
entity_list.QueueClients(bot_iter, outapp);
|
|
safe_delete(outapp);
|
|
|
|
//helper_bot_appearance_form_update(bot_iter);
|
|
}
|
|
++bot_count;
|
|
}
|
|
|
|
if (ab_type == ActionableBots::ABT_All) {
|
|
std::string query;
|
|
if (toggle_helm) {
|
|
if (!botdb.ToggleAllHelmAppearances(c->CharacterID()))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::ToggleAllHelmAppearances());
|
|
}
|
|
else {
|
|
if (!botdb.SaveAllHelmAppearances(c->CharacterID(), helm_state))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveAllHelmAppearances());
|
|
}
|
|
|
|
c->Message(m_action, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared"));
|
|
}
|
|
else {
|
|
c->Message(m_action, "%s %i of your spawned bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared"), bot_count);
|
|
}
|
|
|
|
// Notes:
|
|
/*
|
|
[CLIENT OPCODE TEST]
|
|
[10-16-2015 :: 14:57:56] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A4 02 [2B 00] 01 00 00 00 - showhelm = true (client)
|
|
[10-16-2015 :: 14:57:56] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A4 02 [2B 00] 01 00 00 00 - showhelm = true (client)
|
|
|
|
[10-16-2015 :: 14:58:02] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A4 02 [2B 00] 00 00 00 00 - showhelm = false (client)
|
|
[10-16-2015 :: 14:58:02] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A4 02 [2B 00] 00 00 00 00 - showhelm = false (client)
|
|
|
|
[BOT OPCODE TEST]
|
|
[10-16-2015 :: 22:15:34] [Packet :: Client -> Server (Dump)] [OP_ChannelMessage - 0x0045] [Size: 167]
|
|
0: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot.......
|
|
16: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
32: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
48: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
64: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot.......
|
|
80: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
96: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
128: 00 00 00 00 08 00 00 00 - CD CD CD CD CD CD CD CD | ................
|
|
144: 64 00 00 00 23 62 6F 74 - 20 73 68 6F 77 68 65 6C | d...#bot showhel
|
|
160: 6D 20 6F 6E 00 | m on.
|
|
|
|
[10-16-2015 :: 22:15:34] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A2 02 2B 00 01 00 00 00 - showhelm = true
|
|
|
|
[10-16-2015 :: 22:15:40] [Packet :: Client -> Server (Dump)] [OP_ChannelMessage - 0x0045] [Size: 168]
|
|
0: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot.......
|
|
16: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
32: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
48: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
64: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot.......
|
|
80: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
96: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................
|
|
128: 00 00 00 00 08 00 00 00 - CD CD CD CD CD CD CD CD | ................
|
|
144: 64 00 00 00 23 62 6F 74 - 20 73 68 6F 77 68 65 6C | d...#bot showhel
|
|
160: 6D 20 6F 66 66 00 | m off.
|
|
|
|
[10-16-2015 :: 22:15:40] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10]
|
|
0: A2 02 2B 00 00 00 00 00 - showhelm = false
|
|
|
|
*** Bot did not update using the OP_SpawnAppearance packet with AT_ShowHelm appearance type ***
|
|
*/
|
|
}
|
|
|
|
void bot_subcommand_bot_update(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_update", sep->arg[0], "botupdate"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You currently have no spawned bots");
|
|
return;
|
|
}
|
|
|
|
int bot_count = 0;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter || bot_iter->IsEngaged() || bot_iter->GetLevel() == c->GetLevel())
|
|
continue;
|
|
|
|
bot_iter->SetPetChooser(false);
|
|
bot_iter->CalcBotStats((sbl.size() == 1));
|
|
bot_iter->SendAppearancePacket(AT_WhoLevel, bot_iter->GetLevel(), true, true);
|
|
++bot_count;
|
|
}
|
|
|
|
c->Message(m_action, "Updated %i of your %i spawned bots", bot_count, sbl.size());
|
|
}
|
|
|
|
void bot_subcommand_bot_woad(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_bot_woad", sep->arg[0], "botwoad"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: <target_bot> %s [value: 0-n] (Barbarian bots only)", sep->arg[0]);
|
|
c->Message(m_note, "note: Actual limit is filter-based");
|
|
return;
|
|
}
|
|
|
|
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
|
if (!my_bot) {
|
|
c->Message(m_fail, "You must <target> a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
if (!sep->IsNumber(1)) {
|
|
c->Message(m_fail, "A numeric [value] is required to use this command");
|
|
return;
|
|
}
|
|
|
|
uint8 uvalue = atoi(sep->arg[1]);
|
|
|
|
auto fail_type = BCEnum::AFT_None;
|
|
if (my_bot->GetRace() != BARBARIAN) {
|
|
fail_type = BCEnum::AFT_Race;
|
|
}
|
|
else if (!PlayerAppearance::IsValidWoad(my_bot->GetRace(), my_bot->GetGender(), uvalue)) {
|
|
fail_type = BCEnum::AFT_Value;
|
|
}
|
|
else {
|
|
uint8 old_face = (my_bot->GetLuclinFace() % 10);
|
|
my_bot->SetLuclinFace(((uvalue * 10) + old_face));
|
|
}
|
|
|
|
if (helper_bot_appearance_fail(c, my_bot, fail_type, "woad"))
|
|
return;
|
|
|
|
helper_bot_appearance_form_final(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_botgroup_add_member(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_add_member", sep->arg[0], "botgroupaddmember"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_leader>) %s [member_name] ([leader_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must [name] a new member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto new_member = sbl.front();
|
|
if (!new_member) {
|
|
c->Message(m_unknown, "Error: New member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (new_member->HasGroup()) {
|
|
c->Message(m_fail, "%s is already a current member of a group and can not join another one", new_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
uint32 botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDByMemberID(new_member->GetBotID(), botgroup_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDByMemberID(), new_member->GetCleanName());
|
|
return;
|
|
}
|
|
if (botgroup_id) {
|
|
c->Message(m_fail, "%s is already a current member of a bot-group", new_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a group leader as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto botgroup_leader = sbl.front();
|
|
if (!botgroup_leader) {
|
|
c->Message(m_unknown, "Error: Group leader bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
Group* group_inst = botgroup_leader->GetGroup();
|
|
if (!group_inst || group_inst->GetLeader() != botgroup_leader) {
|
|
c->Message(m_fail, "%s is not the current leader of a group", botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDByLeaderID(botgroup_leader->GetBotID(), botgroup_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDByLeaderID(), botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
if (!botgroup_id) {
|
|
c->Message(m_fail, "%s is not the current leader of a bot-group", botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!Bot::AddBotToGroup(new_member, group_inst)) {
|
|
c->Message(m_fail, "Could not add %s as a new member to %s's group", new_member->GetCleanName(), botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
database.SetGroupID(new_member->GetName(), group_inst->GetID(), new_member->GetBotID());
|
|
|
|
if (!botdb.AddMemberToBotGroup(botgroup_leader->GetBotID(), new_member->GetBotID())) {
|
|
c->Message(m_fail, "%s - %s->%s", BotDatabase::fail::AddMemberToBotGroup(), new_member->GetCleanName(), botgroup_leader->GetCleanName());
|
|
Bot::RemoveBotFromGroup(new_member, botgroup_leader->GetGroup());
|
|
return;
|
|
}
|
|
|
|
std::string botgroup_name;
|
|
if (!botdb.LoadBotGroupNameByLeaderID(botgroup_leader->GetBotID(), botgroup_name))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadBotGroupNameByLeaderID());
|
|
|
|
c->Message(m_action, "Successfully added %s to bot-group %s", new_member->GetCleanName(), botgroup_name.c_str());
|
|
}
|
|
|
|
void bot_subcommand_botgroup_create(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_create", sep->arg[0], "botgroupcreate"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_leader>) %s [group_name] ([leader_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string botgroup_name_arg = sep->arg[1];
|
|
if (botgroup_name_arg.empty()) {
|
|
c->Message(m_fail, "You must specify a [name] for this bot-group to use this command");
|
|
return;
|
|
}
|
|
|
|
bool extant_flag = false;
|
|
if (!botdb.QueryBotGroupExistence(botgroup_name_arg, extant_flag)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::QueryBotGroupExistence(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (extant_flag) {
|
|
c->Message(m_fail, "The [name] %s already exists for a bot-group. Please choose another", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a group leader as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto botgroup_leader = sbl.front();
|
|
if (!botgroup_leader) {
|
|
c->Message(m_unknown, "Error: Group leader bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (botgroup_leader->HasGroup()) {
|
|
c->Message(m_fail, "%s is already a current member of a group", botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
uint32 botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDByLeaderID(botgroup_leader->GetBotID(), botgroup_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDByLeaderID(), botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
if (botgroup_id) {
|
|
c->Message(m_fail, "%s is already the current leader of a bot-group", botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDByMemberID(botgroup_leader->GetBotID(), botgroup_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDByMemberID(), botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
if (botgroup_id) {
|
|
c->Message(m_fail, "%s is already a current member of a bot-group", botgroup_leader->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
Group* group_inst = new Group(botgroup_leader);
|
|
if (!group_inst) {
|
|
c->Message(m_unknown, "Could not create a new group instance");
|
|
return;
|
|
}
|
|
|
|
if (!botdb.CreateBotGroup(botgroup_name_arg, botgroup_leader->GetBotID())) {
|
|
c->Message(m_fail, "%s '%s'", BotDatabase::fail::CreateBotGroup(), botgroup_name_arg.c_str());
|
|
safe_delete(group_inst);
|
|
return;
|
|
}
|
|
|
|
entity_list.AddGroup(group_inst);
|
|
database.SetGroupID(botgroup_leader->GetCleanName(), group_inst->GetID(), botgroup_leader->GetBotID());
|
|
database.SetGroupLeaderName(group_inst->GetID(), botgroup_leader->GetCleanName());
|
|
botgroup_leader->SetFollowID(c->GetID());
|
|
|
|
c->Message(m_action, "Successfully created bot-group '%s' with '%s' as its leader", botgroup_name_arg.c_str(), botgroup_leader->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_botgroup_delete(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_delete", sep->arg[0], "botgroupdelete"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [botgroup_name]", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string botgroup_name_arg = sep->arg[1];
|
|
if (botgroup_name_arg.empty()) {
|
|
c->Message(m_fail, "You must specify a [name] for this bot-group to use this command");
|
|
return;
|
|
}
|
|
|
|
uint32 botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDForLoadBotGroup(c->CharacterID(), botgroup_name_arg, botgroup_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDForLoadBotGroup(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (!botgroup_id) {
|
|
c->Message(m_fail, "Could not locate group id for '%s'", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
uint32 leader_id = 0;
|
|
if (!botdb.LoadLeaderIDByBotGroupID(botgroup_id, leader_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadLeaderIDByBotGroupID(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (!leader_id) {
|
|
c->Message(m_fail, "Could not locate leader id for '%s'", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> gbl;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
std::map<uint32, std::list<uint32>> member_list;
|
|
if (!botdb.LoadBotGroup(botgroup_name_arg, member_list)) {
|
|
c->Message(m_fail, "%s '%s'", BotDatabase::fail::LoadBotGroup(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (member_list.find(botgroup_id) == member_list.end() || member_list[botgroup_id].empty()) {
|
|
c->Message(m_fail, "Could not locate member list for bot-group '%s'", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
for (auto bot_iter : sbl) {
|
|
for (auto group_iter : member_list[botgroup_id]) {
|
|
if (bot_iter->GetBotID() == group_iter) {
|
|
gbl.push_back(bot_iter);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
gbl.unique();
|
|
|
|
for (auto group_member : gbl) {
|
|
if (group_member->HasGroup())
|
|
Bot::RemoveBotFromGroup(group_member, group_member->GetGroup());
|
|
}
|
|
|
|
if (!botdb.DeleteBotGroup(leader_id)) {
|
|
c->Message(m_fail, "%s '%s'", BotDatabase::fail::DeleteBotGroup(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Successfully deleted bot-group %s", botgroup_name_arg.c_str());
|
|
}
|
|
|
|
void bot_subcommand_botgroup_list(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_list", sep->arg[0], "botgrouplist"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<std::pair<std::string, std::string>> botgroups_list;
|
|
if (!botdb.LoadBotGroupsListByOwnerID(c->CharacterID(), botgroups_list)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadBotGroupsListByOwnerID());
|
|
return;
|
|
}
|
|
if (botgroups_list.empty()) {
|
|
c->Message(m_fail, "You have no saved bot-groups");
|
|
return;
|
|
}
|
|
|
|
int botgroup_count = 0;
|
|
for (auto botgroups_iter : botgroups_list)
|
|
c->Message(m_message, "(%i) Bot-group name: %s | Leader: %s", (++botgroup_count), botgroups_iter.first.c_str(), botgroups_iter.second.c_str());
|
|
|
|
c->Message(m_action, "%i bot-groups listed", botgroup_count);
|
|
}
|
|
|
|
void bot_subcommand_botgroup_load(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_load", sep->arg[0], "botgroupload"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [botgroup_name]", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string botgroup_name_arg = sep->arg[1];
|
|
if (botgroup_name_arg.empty()) {
|
|
c->Message(m_fail, "You must specify the [name] of a bot-group to load to use this command");
|
|
return;
|
|
}
|
|
|
|
bool extant_flag = false;
|
|
if (!botdb.QueryBotGroupExistence(botgroup_name_arg, extant_flag)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::QueryBotGroupExistence(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (!extant_flag) {
|
|
c->Message(m_fail, "Bot-group %s does not exist", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
Group* owner_group = c->GetGroup();
|
|
if (owner_group) {
|
|
std::list<Client*> member_list;
|
|
owner_group->GetClientList(member_list);
|
|
member_list.remove(nullptr);
|
|
|
|
for (auto member_iter : member_list) {
|
|
if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) {
|
|
c->Message(m_fail, "You can't spawn bots while your group is engaged");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (c->GetAggroCount() > 0) {
|
|
c->Message(m_fail, "You can't spawn bots while you are engaged");
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32 botgroup_id = 0;
|
|
if (!botdb.LoadBotGroupIDForLoadBotGroup(c->CharacterID(), botgroup_name_arg, botgroup_id) || !botgroup_id) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroupIDForLoadBotGroup(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
std::map<uint32, std::list<uint32>> member_list;
|
|
if (!botdb.LoadBotGroup(botgroup_name_arg, member_list)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadBotGroup(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (member_list.find(botgroup_id) == member_list.end() || member_list[botgroup_id].empty()) {
|
|
c->Message(m_fail, "Database returned an empty list for bot-group '%s'", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
int spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID());
|
|
|
|
if (RuleB(Bots, QuestableSpawnLimit)) {
|
|
int allowed_bot_count = 0;
|
|
if (!botdb.LoadQuestableSpawnCount(c->CharacterID(), allowed_bot_count)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadQuestableSpawnCount());
|
|
return;
|
|
}
|
|
|
|
if (!allowed_bot_count) {
|
|
c->Message(m_fail, "You can not spawn any bots");
|
|
return;
|
|
}
|
|
|
|
if (spawned_bot_count >= allowed_bot_count || (spawned_bot_count + member_list.begin()->second.size()) > allowed_bot_count) {
|
|
c->Message(m_fail, "You can not spawn more than %i bot%s (quest-limit)", allowed_bot_count, ((allowed_bot_count == 1) ? ("") : ("s")));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const int allowed_bot_limit = RuleI(Bots, SpawnLimit);
|
|
if (spawned_bot_count >= allowed_bot_limit || (spawned_bot_count + member_list.begin()->second.size()) > allowed_bot_limit) {
|
|
c->Message(m_fail, "You can not spawn more than %i bot%s (hard-limit)", allowed_bot_limit, ((allowed_bot_limit == 1) ? ("") : ("s")));
|
|
return;
|
|
}
|
|
|
|
uint32 leader_id = 0;
|
|
if (!botdb.LoadLeaderIDByBotGroupName(botgroup_name_arg, leader_id)) {
|
|
c->Message(m_fail, "%s for '%s'", BotDatabase::fail::LoadLeaderIDByBotGroupName(), botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
if (!leader_id) {
|
|
c->Message(m_fail, "Cannot locate bot-group leader id for '%s'", botgroup_name_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
auto botgroup_leader = Bot::LoadBot(leader_id);
|
|
if (!botgroup_leader) {
|
|
c->Message(m_fail, "Could not load bot-group leader for '%s'", botgroup_name_arg.c_str());
|
|
safe_delete(botgroup_leader);
|
|
return;
|
|
}
|
|
|
|
if (!botgroup_leader->Spawn(c)) {
|
|
c->Message(m_fail, "Could not spawn bot-group leader %s for '%s'", botgroup_leader->GetName(), botgroup_name_arg.c_str());
|
|
safe_delete(botgroup_leader);
|
|
return;
|
|
}
|
|
|
|
Group* group_inst = new Group(botgroup_leader);
|
|
|
|
entity_list.AddGroup(group_inst);
|
|
database.SetGroupID(botgroup_leader->GetCleanName(), group_inst->GetID(), botgroup_leader->GetBotID());
|
|
database.SetGroupLeaderName(group_inst->GetID(), botgroup_leader->GetCleanName());
|
|
botgroup_leader->SetFollowID(c->GetID());
|
|
|
|
member_list[botgroup_id].remove(0);
|
|
member_list[botgroup_id].remove(botgroup_leader->GetBotID());
|
|
for (auto member_iter : member_list[botgroup_id]) {
|
|
auto botgroup_member = Bot::LoadBot(member_iter);
|
|
if (!botgroup_member) {
|
|
c->Message(m_fail, "Could not load bot id %i", member_iter);
|
|
safe_delete(botgroup_member);
|
|
return;
|
|
}
|
|
|
|
if (!botgroup_member->Spawn(c)) {
|
|
c->Message(m_fail, "Could not spawn bot '%s' (id: %i)", botgroup_member->GetName(), member_iter);
|
|
safe_delete(botgroup_member);
|
|
return;
|
|
}
|
|
|
|
Bot::AddBotToGroup(botgroup_member, group_inst);
|
|
}
|
|
|
|
c->Message(m_action, "Successfully loaded bot-group %s", botgroup_name_arg.c_str());
|
|
}
|
|
|
|
void bot_subcommand_botgroup_remove_member(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_botgroup_remove_member", sep->arg[0], "botgroupremovemember"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a group member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto group_member = sbl.front();
|
|
if (!group_member) {
|
|
c->Message(m_unknown, "Error: Group member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!group_member->HasGroup()) {
|
|
c->Message(m_fail, "%s is not a current member of a group", group_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!Bot::RemoveBotFromGroup(group_member, group_member->GetGroup())) {
|
|
c->Message(m_fail, "Could not remove %s from their group", group_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!botdb.RemoveMemberFromBotGroup(group_member->GetBotID())) {
|
|
c->Message(m_fail, "%s - '%s'", BotDatabase::fail::RemoveMemberFromBotGroup(), group_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Successfully removed %s from their bot-group", group_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_circle(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_subcommand_circle", sep->arg[0], "circle"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Depart, DRUID);
|
|
return;
|
|
}
|
|
|
|
bool single = false;
|
|
std::string single_arg = sep->arg[2];
|
|
if (!single_arg.compare("single"))
|
|
single = true;
|
|
|
|
std::string destination = sep->arg[1];
|
|
if (!destination.compare("list")) {
|
|
auto my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, DRUID);
|
|
helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single);
|
|
return;
|
|
}
|
|
else if (destination.empty()) {
|
|
c->Message(m_fail, "A [destination] or [list] argument is required to use this command");
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToDepart();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->caster_class != DRUID)
|
|
continue;
|
|
if (local_entry->single != single)
|
|
continue;
|
|
if (destination.compare(spells[local_entry->spell_id].teleport_zone))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adaptive_targeting", sep->arg[0], "healrotationadaptivetargeting"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name]) ([option: on | off])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string adaptive_targeting_arg;
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (!sbl.empty()) {
|
|
adaptive_targeting_arg = sep->arg[2];
|
|
}
|
|
else {
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
adaptive_targeting_arg = sep->arg[1];
|
|
}
|
|
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
bool hr_adaptive_targeting = false;
|
|
|
|
if (!adaptive_targeting_arg.compare("on")) {
|
|
hr_adaptive_targeting = true;
|
|
}
|
|
else if (adaptive_targeting_arg.compare("off")) {
|
|
c->Message(m_action, "Adaptive targeting is currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
(*current_member->MemberOfHealRotation())->SetAdaptiveTargeting(hr_adaptive_targeting);
|
|
|
|
c->Message(m_action, "Adaptive targeting is now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_add_member(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_add_member", sep->arg[0], "healrotationaddmember"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [new_member_name] ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must [name] a new member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto new_member = sbl.front();
|
|
if (!new_member) {
|
|
c->Message(m_unknown, "Error: New member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (new_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is already a current member of a Heal Rotation and can not join another one", new_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!new_member->JoinHealRotationMemberPool(current_member->MemberOfHealRotation())) {
|
|
c->Message(m_fail, "Failed to add %s as a current member of this Heal Rotation", new_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Successfully added %s as a current member of this Heal Rotation", new_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_add_target(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_add_target", sep->arg[0], "healrotationaddtarget"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [heal_target_name] ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
auto heal_target = entity_list.GetMob(sep->arg[1]);
|
|
if (!heal_target) {
|
|
c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]);
|
|
return;
|
|
}
|
|
|
|
if ((!heal_target->IsClient() && !heal_target->IsBot() && !heal_target->IsPet()) ||
|
|
(heal_target->IsPet() && (!heal_target->GetOwner() || (!heal_target->GetOwner()->IsClient() && !heal_target->GetOwner()->IsBot()))))
|
|
{
|
|
c->Message(m_fail, "%s's entity type is not an allowable heal target", heal_target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!heal_target->JoinHealRotationTargetPool(current_member->MemberOfHealRotation())) {
|
|
c->Message(m_fail, "Failed to add heal target with a name of '%s'", heal_target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Successfully added heal target %s to %s's Heal Rotation", heal_target->GetCleanName(), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_adjust_critical(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adjust_critical", sep->arg[0], "healrotationadjustcritical"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE);
|
|
c->Message(m_note, "armor_types: %u(Base), %u(Cloth), %u(Leather), %u(Chain), %u(Plate)",
|
|
ARMOR_TYPE_UNKNOWN, ARMOR_TYPE_CLOTH, ARMOR_TYPE_LEATHER, ARMOR_TYPE_CHAIN, ARMOR_TYPE_PLATE);
|
|
return;
|
|
}
|
|
|
|
std::string armor_type_arg = sep->arg[1];
|
|
std::string critical_arg = sep->arg[2];
|
|
|
|
uint8 armor_type_value = 255;
|
|
if (sep->IsNumber(1))
|
|
armor_type_value = atoi(armor_type_arg.c_str());
|
|
|
|
if (armor_type_value > ARMOR_TYPE_LAST) {
|
|
c->Message(m_fail, "You must specify a valid [armor_type: %u-%u] to use this command", ARMOR_TYPE_FIRST, ARMOR_TYPE_LAST);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
float critical_ratio = CRITICAL_HP_RATIO_BASE;
|
|
if (sep->IsNumber(2))
|
|
critical_ratio = atof(critical_arg.c_str());
|
|
else if (!critical_arg.compare("+"))
|
|
critical_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value) + HP_RATIO_DELTA;
|
|
else if (!critical_arg.compare("-"))
|
|
critical_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value) - HP_RATIO_DELTA;
|
|
|
|
if (critical_ratio > SAFE_HP_RATIO_ABS)
|
|
critical_ratio = SAFE_HP_RATIO_ABS;
|
|
if (critical_ratio < CRITICAL_HP_RATIO_ABS)
|
|
critical_ratio = CRITICAL_HP_RATIO_ABS;
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(armor_type_value, critical_ratio)) {
|
|
c->Message(m_fail, "Critical value %3.1f%%(%u) exceeds safe value %3.1f%%(%u) for %s's Heal Rotation",
|
|
critical_ratio, armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value), armor_type_value, current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Class Armor Type %u critical value %3.1f%% set for %s's Heal Rotation",
|
|
armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_adjust_safe(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adjust_safe", sep->arg[0], "healrotationadjustsafe"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE);
|
|
c->Message(m_note, "armor_types: %u(Base), %u(Cloth), %u(Leather), %u(Chain), %u(Plate)",
|
|
ARMOR_TYPE_UNKNOWN, ARMOR_TYPE_CLOTH, ARMOR_TYPE_LEATHER, ARMOR_TYPE_CHAIN, ARMOR_TYPE_PLATE);
|
|
return;
|
|
}
|
|
|
|
std::string armor_type_arg = sep->arg[1];
|
|
std::string safe_arg = sep->arg[2];
|
|
|
|
uint8 armor_type_value = 255;
|
|
if (sep->IsNumber(1))
|
|
armor_type_value = atoi(armor_type_arg.c_str());
|
|
|
|
if (armor_type_value > ARMOR_TYPE_LAST) {
|
|
c->Message(m_fail, "You must specify a valid [armor_type: %u-%u] to use this command", ARMOR_TYPE_FIRST, ARMOR_TYPE_LAST);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
float safe_ratio = SAFE_HP_RATIO_BASE;
|
|
if (sep->IsNumber(2))
|
|
safe_ratio = atof(safe_arg.c_str());
|
|
else if (!safe_arg.compare("+"))
|
|
safe_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value) + HP_RATIO_DELTA;
|
|
else if (!safe_arg.compare("-"))
|
|
safe_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value) - HP_RATIO_DELTA;
|
|
|
|
if (safe_ratio > SAFE_HP_RATIO_ABS)
|
|
safe_ratio = SAFE_HP_RATIO_ABS;
|
|
if (safe_ratio < CRITICAL_HP_RATIO_ABS)
|
|
safe_ratio = CRITICAL_HP_RATIO_ABS;
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(armor_type_value, safe_ratio)) {
|
|
c->Message(m_fail, "Safe value %3.1f%%(%u) does not exceed critical value %3.1f%%(%u) for %s's Heal Rotation",
|
|
safe_ratio, armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value), armor_type_value, current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Class Armor Type %u safe value %3.1f%% set for %s's Heal Rotation",
|
|
armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_casting_override(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_casting_override", sep->arg[0], "healrotationcastingoverride"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name]) ([option: on | off])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string casting_override_arg;
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (!sbl.empty()) {
|
|
casting_override_arg = sep->arg[2];
|
|
}
|
|
else {
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
casting_override_arg = sep->arg[1];
|
|
}
|
|
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
bool hr_casting_override = false;
|
|
|
|
if (!casting_override_arg.compare("on")) {
|
|
hr_casting_override = true;
|
|
}
|
|
else if (casting_override_arg.compare("off")) {
|
|
c->Message(m_action, "Casting override is currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
(*current_member->MemberOfHealRotation())->SetCastingOverride(hr_casting_override);
|
|
|
|
c->Message(m_action, "Casting override is now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_change_interval", sep->arg[0], "healrotationchangeinterval"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name]) ([interval=%u: %u-%u(seconds)])",
|
|
sep->arg[0], CASTING_CYCLE_DEFAULT_INTERVAL_S, CASTING_CYCLE_MINIMUM_INTERVAL_S, CASTING_CYCLE_MAXIMUM_INTERVAL_S);
|
|
return;
|
|
}
|
|
|
|
std::string change_interval_arg;
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (!sbl.empty()) {
|
|
change_interval_arg = sep->arg[2];
|
|
}
|
|
else {
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
change_interval_arg = sep->arg[1];
|
|
}
|
|
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
uint32 hr_change_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S;
|
|
|
|
if (!change_interval_arg.empty()) {
|
|
hr_change_interval_s = atoi(change_interval_arg.c_str());
|
|
}
|
|
else {
|
|
hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS();
|
|
c->Message(m_action, "Casting interval is currently '%i' second%s for %s's Heal Rotation", hr_change_interval_s, ((hr_change_interval_s == 1) ? ("") : ("s")), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (hr_change_interval_s < CASTING_CYCLE_MINIMUM_INTERVAL_S || hr_change_interval_s > CASTING_CYCLE_MAXIMUM_INTERVAL_S)
|
|
hr_change_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S;
|
|
|
|
(*current_member->MemberOfHealRotation())->SetIntervalS(hr_change_interval_s);
|
|
|
|
hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS();
|
|
c->Message(m_action, "Casting interval is now '%i' second%s for %s's Heal Rotation", hr_change_interval_s, ((hr_change_interval_s == 1) ? ("") : ("s")), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_clear_hot(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_hot", sep->arg[0], "healrotationclearhot"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->ClearHOTTarget()) {
|
|
c->Message(m_fail, "Failed to clear %s's Heal Rotation HOT", current_member->GetCleanName());
|
|
}
|
|
|
|
c->Message(m_action, "Succeeded in clearing %s's Heal Rotation HOT", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_clear_targets(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_targets", sep->arg[0], "healrotationcleartargets"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->ClearTargetPool()) {
|
|
c->Message(m_fail, "Failed to clear all targets from %s's Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "All targets have been cleared from %s's Heal Rotation", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_create(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_create", sep->arg[0], "healrotationcreate"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_creator>) %s ([creator_name]) ([interval=%u: %u-%u(seconds)] [fastheals=off: on | off] [adaptivetargeting=off: on | off] [castingoverride=off: on | off])",
|
|
sep->arg[0], CASTING_CYCLE_DEFAULT_INTERVAL_S, CASTING_CYCLE_MINIMUM_INTERVAL_S, CASTING_CYCLE_MAXIMUM_INTERVAL_S);
|
|
return;
|
|
}
|
|
|
|
std::string interval_arg;
|
|
std::string fast_heals_arg;
|
|
std::string adaptive_targeting_arg;
|
|
std::string casting_override_arg;
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (!sbl.empty()) {
|
|
interval_arg = sep->arg[2];
|
|
fast_heals_arg = sep->arg[3];
|
|
adaptive_targeting_arg = sep->arg[4];
|
|
casting_override_arg = sep->arg[5];
|
|
}
|
|
else {
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
interval_arg = sep->arg[1];
|
|
fast_heals_arg = sep->arg[2];
|
|
adaptive_targeting_arg = sep->arg[3];
|
|
casting_override_arg = sep->arg[4];
|
|
}
|
|
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a creator as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto creator_member = sbl.front();
|
|
if (!creator_member) {
|
|
c->Message(m_unknown, "Error: Creator bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (creator_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is already a current member of a Heal Rotation", creator_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
uint32 hr_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S;
|
|
bool hr_fast_heals = false;
|
|
bool hr_adaptive_targeting = false;
|
|
bool hr_casting_override = false;
|
|
|
|
if (!casting_override_arg.compare("on")) {
|
|
hr_casting_override = true;
|
|
if (!adaptive_targeting_arg.compare("on"))
|
|
hr_adaptive_targeting = true;
|
|
if (!fast_heals_arg.compare("on"))
|
|
hr_fast_heals = true;
|
|
hr_interval_s = atoi(interval_arg.c_str());
|
|
}
|
|
else if (!casting_override_arg.compare("off")) {
|
|
if (!adaptive_targeting_arg.compare("on"))
|
|
hr_adaptive_targeting = true;
|
|
if (!fast_heals_arg.compare("on"))
|
|
hr_fast_heals = true;
|
|
hr_interval_s = atoi(interval_arg.c_str());
|
|
}
|
|
|
|
if (hr_interval_s < CASTING_CYCLE_MINIMUM_INTERVAL_S || hr_interval_s > CASTING_CYCLE_MAXIMUM_INTERVAL_S)
|
|
hr_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S;
|
|
|
|
hr_interval_s *= 1000; // convert to milliseconds for Bot/HealRotation constructor
|
|
|
|
if (!creator_member->CreateHealRotation(hr_interval_s, hr_fast_heals, hr_adaptive_targeting, hr_casting_override)) {
|
|
c->Message(m_fail, "Failed to add %s as a current member to a new Heal Rotation", creator_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
std::list<uint32> member_list;
|
|
std::list<std::string> target_list;
|
|
bool load_flag = false;
|
|
bool member_fail = false;
|
|
bool target_fail = false;
|
|
|
|
if (!botdb.LoadHealRotation(creator_member, member_list, target_list, load_flag, member_fail, target_fail))
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotation());
|
|
|
|
if (!load_flag) {
|
|
c->Message(m_action, "Successfully added %s as a current member to a new Heal Rotation", creator_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!member_fail) {
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
for (auto member_iter : member_list) {
|
|
if (!member_iter || member_iter == creator_member->GetBotID())
|
|
continue;
|
|
|
|
bool member_found = false;
|
|
for (auto bot_iter : sbl) {
|
|
if (bot_iter->GetBotID() != member_iter)
|
|
continue;
|
|
|
|
if (!bot_iter->JoinHealRotationMemberPool(creator_member->MemberOfHealRotation()))
|
|
c->Message(m_fail, "Failed to add member '%s'", bot_iter->GetCleanName());
|
|
member_found = true;
|
|
|
|
break;
|
|
}
|
|
|
|
if (!member_found)
|
|
c->Message(m_fail, "Could not locate member with bot id '%u'", member_iter);
|
|
}
|
|
}
|
|
else {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotationMembers());
|
|
}
|
|
|
|
if (!target_fail) {
|
|
for (auto target_iter : target_list) {
|
|
if (target_iter.empty())
|
|
continue;
|
|
|
|
auto target_mob = entity_list.GetMob(target_iter.c_str());
|
|
if (!target_mob) {
|
|
c->Message(m_fail, "Could not locate target '%s'", target_iter.c_str());
|
|
continue;
|
|
}
|
|
|
|
if (!target_mob->JoinHealRotationTargetPool(creator_member->MemberOfHealRotation()))
|
|
c->Message(m_fail, "Failed to add target '%s'", target_mob->GetCleanName());
|
|
}
|
|
}
|
|
else {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotationTargets());
|
|
}
|
|
|
|
c->Message(m_action, "Successfully loaded %s's Heal Rotation", creator_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_delete(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_delete", sep->arg[0], "healrotationdelete"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([option: all]) ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
bool all_flag = false;
|
|
int name_arg = 1;
|
|
if (!strcasecmp(sep->arg[1], "all")) {
|
|
all_flag = true;
|
|
name_arg = 2;
|
|
}
|
|
|
|
if (all_flag) {
|
|
if (botdb.DeleteAllHealRotations(c->CharacterID()))
|
|
c->Message(m_action, "Succeeded in deleting all heal rotations");
|
|
else
|
|
c->Message(m_fail, "%s", BotDatabase::fail::DeleteAllHealRotations());
|
|
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[name_arg]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!botdb.DeleteHealRotation(current_member->GetBotID())) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::DeleteHealRotation());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Succeeded in deleting %s's heal rotation", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_fast_heals(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_fast_heals", sep->arg[0], "healrotationfastheals"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name]) ([option: on | off])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::string fast_heals_arg;
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (!sbl.empty()) {
|
|
fast_heals_arg = sep->arg[2];
|
|
}
|
|
else {
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
fast_heals_arg = sep->arg[1];
|
|
}
|
|
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
bool hr_fast_heals = false;
|
|
|
|
if (!fast_heals_arg.compare("on")) {
|
|
hr_fast_heals = true;
|
|
}
|
|
else if (fast_heals_arg.compare("off")) {
|
|
c->Message(m_action, "Fast heals are currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
(*current_member->MemberOfHealRotation())->SetFastHeals(hr_fast_heals);
|
|
|
|
c->Message(m_action, "Fast heals are now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_list(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_list", sep->arg[0], "healrotationlist"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_note, "Heal Rotation Settings:");
|
|
|
|
c->Message(m_message, "Current state: %s", (((*current_member->MemberOfHealRotation())->IsActive()) ? ("active") : ("inactive")));
|
|
c->Message(m_message, "Casting interval: %i seconds", (*current_member->MemberOfHealRotation())->IntervalS());
|
|
c->Message(m_message, "Fast heals: '%s'", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")));
|
|
c->Message(m_message, "Adaptive targeting: '%s'", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")));
|
|
c->Message(m_message, "Casting override: '%s'", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")));
|
|
c->Message(m_message, "HOT state: %s", (((*current_member->MemberOfHealRotation())->IsHOTActive()) ? ("active") : ("inactive")));
|
|
c->Message(m_message, "HOT target: %s", (((*current_member->MemberOfHealRotation())->HOTTarget()) ? ((*current_member->MemberOfHealRotation())->HOTTarget()->GetCleanName()) : ("null")));
|
|
|
|
c->Message(m_message, "Base hp limits - critical: %3.1f%%, safe: %3.1f%%",
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN),
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN));
|
|
c->Message(m_message, "Cloth hp limits - critical: %3.1f%%, safe: %3.1f%%",
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH),
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH));
|
|
c->Message(m_message, "Leather hp limits - critical: %3.1f%%, safe: %3.1f%%",
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER),
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER));
|
|
c->Message(m_message, "Chain hp limits - critical: %3.1f%%, safe: %3.1f%%",
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN),
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN));
|
|
c->Message(m_message, "Plate hp limits - critical: %3.1f%%, safe: %3.1f%%",
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE),
|
|
(*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE));
|
|
|
|
c->Message(m_note, "Heal Rotation Members:");
|
|
|
|
int member_index = 0;
|
|
auto member_pool = (*current_member->MemberOfHealRotation())->MemberList();
|
|
for (auto member_iter : *member_pool) {
|
|
if (!member_iter)
|
|
continue;
|
|
|
|
c->Message(m_message, "(%i) %s", (++member_index), member_iter->GetCleanName());
|
|
}
|
|
if (!member_index)
|
|
c->Message(m_fail, "(0) None");
|
|
|
|
c->Message(m_note, "Heal Rotation Targets:");
|
|
|
|
int target_index = 0;
|
|
auto target_pool = (*current_member->MemberOfHealRotation())->TargetList();
|
|
for (auto target_iter : *target_pool) {
|
|
if (!target_iter)
|
|
continue;
|
|
|
|
c->Message(m_message, "(%i) %s", (++target_index), target_iter->GetCleanName());
|
|
}
|
|
if (!target_index)
|
|
c->Message(m_message, "(0) None");
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_remove_member(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_remove_member", sep->arg[0], "healrotationremovemember"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!current_member->LeaveHealRotationMemberPool()) {
|
|
c->Message(m_fail, "Failed to remove %s from their Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "%s has been removed from their Heal Rotation", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_remove_target(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_remove_target", sep->arg[0], "healrotationremovetarget"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [heal_target_name] ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
auto heal_target = entity_list.GetMob(sep->arg[1]);
|
|
if (!heal_target) {
|
|
c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]);
|
|
return;
|
|
}
|
|
|
|
if (!current_member->MemberOfHealRotation()->get()->IsTargetInPool(heal_target) || !heal_target->LeaveHealRotationTargetPool()) {
|
|
c->Message(m_fail, "Failed to remove heal target with a name of '%s'", heal_target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Successfully removed heal target %s from %s's Heal Rotation", heal_target->GetCleanName(), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_reset_limits(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_reset_limits", sep->arg[0], "healrotationresetlimits"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
(*current_member->MemberOfHealRotation())->ResetArmorTypeHPLimits();
|
|
|
|
c->Message(m_action, "Class Armor Type HP limit criteria has been set to default values for %s's Heal Rotation", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_save(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_save", sep->arg[0], "healrotationsave"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
bool member_fail = false;
|
|
bool target_fail = false;
|
|
if (!botdb.SaveHealRotation(current_member, member_fail, target_fail)) {
|
|
c->Message(m_fail, "%s", BotDatabase::fail::SaveHealRotation());
|
|
return;
|
|
}
|
|
if (member_fail)
|
|
c->Message(m_fail, "Failed to save heal rotation members");
|
|
if (target_fail)
|
|
c->Message(m_fail, "Failed to save heal rotation targets");
|
|
|
|
c->Message(m_action, "Succeeded in saving %s's heal rotation", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_set_hot(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_set_hot", sep->arg[0], "healrotationsethot"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s [heal_override_target_name] ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
auto hot_target = entity_list.GetMob(sep->arg[1]);
|
|
if (!hot_target) {
|
|
c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]);
|
|
return;
|
|
}
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->IsTargetInPool(hot_target)) {
|
|
c->Message(m_fail, "%s is not a target in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->SetHOTTarget(hot_target)) {
|
|
c->Message(m_fail, "Failed to set %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "Succeeded in setting %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_start(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_start", sep->arg[0], "healrotationstart"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if ((*current_member->MemberOfHealRotation())->IsActive()) {
|
|
c->Message(m_fail, "%s's Heal Rotation is already active", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!current_member->MemberOfHealRotation()->get()->Start()) {
|
|
c->Message(m_fail, "Failed to start %s's Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "%s's Heal Rotation is now active", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_heal_rotation_stop(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_stop", sep->arg[0], "healrotationstop"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: (<target_member>) %s ([member_name])", sep->arg[0]);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]);
|
|
if (sbl.empty())
|
|
MyBots::PopulateSBL_ByTargetedBot(c, sbl);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You must <target> or [name] a current member as a bot that you own to use this command");
|
|
return;
|
|
}
|
|
|
|
auto current_member = sbl.front();
|
|
if (!current_member) {
|
|
c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr");
|
|
return;
|
|
}
|
|
|
|
if (!current_member->IsHealRotationMember()) {
|
|
c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!(*current_member->MemberOfHealRotation())->IsActive()) {
|
|
c->Message(m_fail, "%s's Heal Rotation is already inactive", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if (!current_member->MemberOfHealRotation()->get()->Stop()) {
|
|
c->Message(m_fail, "Failed to stop %s's Heal Rotation", current_member->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
c->Message(m_action, "%s's Heal Rotation is now inactive", current_member->GetCleanName());
|
|
}
|
|
|
|
void bot_subcommand_inventory_give(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_inventory_give", sep->arg[0], "inventorygive"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
auto my_bot = sbl.front();
|
|
if (!my_bot) {
|
|
c->Message(m_unknown, "ActionableBots returned 'nullptr'");
|
|
return;
|
|
}
|
|
|
|
my_bot->FinishTrade(c, Bot::BotTradeClientNoDropNoTrade);
|
|
}
|
|
|
|
void bot_subcommand_inventory_list(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_inventory_list", sep->arg[0], "inventorylist"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
auto my_bot = sbl.front();
|
|
if (!my_bot) {
|
|
c->Message(m_unknown, "ActionableBots returned 'nullptr'");
|
|
return;
|
|
}
|
|
|
|
const EQEmu::ItemInstance* inst = nullptr;
|
|
const EQEmu::ItemData* item = nullptr;
|
|
bool is2Hweapon = false;
|
|
|
|
EQEmu::SayLinkEngine linker;
|
|
linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
|
|
|
|
uint32 inventory_count = 0;
|
|
for (int i = EQEmu::invslot::EQUIPMENT_BEGIN; i <= (EQEmu::invslot::EQUIPMENT_END + 1); ++i) {
|
|
if ((i == EQEmu::invslot::slotSecondary) && is2Hweapon)
|
|
continue;
|
|
|
|
inst = my_bot->CastToBot()->GetBotItem(i == 22 ? EQEmu::invslot::SLOT_POWER_SOURCE : i);
|
|
if (!inst || !inst->GetItem()) {
|
|
c->Message(m_message, "I need something for my %s (slot %i)", GetBotEquipSlotName(i), (i == 22 ? EQEmu::invslot::SLOT_POWER_SOURCE : i));
|
|
continue;
|
|
}
|
|
|
|
item = inst->GetItem();
|
|
if ((i == EQEmu::invslot::slotPrimary) && item->IsType2HWeapon()) {
|
|
is2Hweapon = true;
|
|
}
|
|
|
|
linker.SetItemInst(inst);
|
|
c->Message(m_message, "Using %s in my %s (slot %i)", linker.GenerateLink().c_str(), GetBotEquipSlotName(i), (i == 22 ? EQEmu::invslot::SLOT_POWER_SOURCE : i));
|
|
|
|
++inventory_count;
|
|
}
|
|
|
|
uint32 database_count = 0;
|
|
if (!botdb.QueryInventoryCount(my_bot->GetBotID(), database_count))
|
|
c->Message(m_unknown, "%s", BotDatabase::fail::QueryInventoryCount());
|
|
|
|
if (inventory_count != database_count)
|
|
c->Message(m_unknown, "Inventory-database item count mismatch: inv = '%u', db = '%u'", inventory_count, database_count);
|
|
}
|
|
|
|
void bot_subcommand_inventory_remove(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_inventory_remove", sep->arg[0], "inventoryremove"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [slotid: 0-22] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
if (c->GetTradeskillObject() || (c->trade->state == Trading)) {
|
|
c->Message_StringID(MT_Tell, MERCHANT_BUSY);
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
auto my_bot = sbl.front();
|
|
if (!my_bot) {
|
|
c->Message(m_unknown, "ActionableBots returned 'nullptr'");
|
|
return;
|
|
}
|
|
|
|
int slotId = atoi(sep->arg[1]);
|
|
if (!sep->IsNumber(1) || ((slotId > EQEmu::invslot::EQUIPMENT_END || slotId < EQEmu::invslot::EQUIPMENT_BEGIN) && slotId != EQEmu::invslot::SLOT_POWER_SOURCE)) {
|
|
c->Message(m_fail, "Valid slots are 0-21 or 9999");
|
|
return;
|
|
}
|
|
|
|
const EQEmu::ItemData* itm = nullptr;
|
|
const EQEmu::ItemInstance* itminst = my_bot->GetBotItem(slotId);
|
|
if (itminst)
|
|
itm = itminst->GetItem();
|
|
|
|
if (itminst && itm && c->CheckLoreConflict(itm)) {
|
|
c->Message_StringID(0, PICK_LORE);
|
|
return;
|
|
}
|
|
|
|
for (int m = EQEmu::invaug::SOCKET_BEGIN; m <= EQEmu::invaug::SOCKET_END; ++m) {
|
|
if (!itminst)
|
|
break;
|
|
|
|
EQEmu::ItemInstance *itma = itminst->GetAugment(m);
|
|
if (!itma)
|
|
continue;
|
|
if (!c->CheckLoreConflict(itma->GetItem()))
|
|
continue;
|
|
|
|
c->Message_StringID(0, PICK_LORE);
|
|
return;
|
|
}
|
|
|
|
std::string error_message;
|
|
if (itm) {
|
|
c->PushItemOnCursor(*itminst, true);
|
|
if ((slotId == EQEmu::invslot::slotRange) || (slotId == EQEmu::invslot::slotAmmo) || (slotId == EQEmu::invslot::slotPrimary) || (slotId == EQEmu::invslot::slotSecondary))
|
|
my_bot->SetBotArcher(false);
|
|
|
|
my_bot->RemoveBotItemBySlot(slotId, &error_message);
|
|
if (!error_message.empty()) {
|
|
c->Message(m_unknown, "Database Error: %s", error_message.c_str());
|
|
return;
|
|
}
|
|
|
|
my_bot->BotRemoveEquipItem(slotId);
|
|
my_bot->CalcBotStats();
|
|
}
|
|
|
|
switch (slotId) {
|
|
case EQEmu::invslot::slotCharm:
|
|
case EQEmu::invslot::slotEar1:
|
|
case EQEmu::invslot::slotHead:
|
|
case EQEmu::invslot::slotFace:
|
|
case EQEmu::invslot::slotEar2:
|
|
case EQEmu::invslot::slotNeck:
|
|
case EQEmu::invslot::slotBack:
|
|
case EQEmu::invslot::slotWrist1:
|
|
case EQEmu::invslot::slotWrist2:
|
|
case EQEmu::invslot::slotRange:
|
|
case EQEmu::invslot::slotPrimary:
|
|
case EQEmu::invslot::slotSecondary:
|
|
case EQEmu::invslot::slotFinger1:
|
|
case EQEmu::invslot::slotFinger2:
|
|
case EQEmu::invslot::slotChest:
|
|
case EQEmu::invslot::slotWaist:
|
|
case EQEmu::invslot::SLOT_POWER_SOURCE:
|
|
case EQEmu::invslot::slotAmmo:
|
|
c->Message(m_message, "My %s is %s unequipped", GetBotEquipSlotName(slotId), ((itm) ? ("now") : ("already")));
|
|
break;
|
|
case EQEmu::invslot::slotShoulders:
|
|
case EQEmu::invslot::slotArms:
|
|
case EQEmu::invslot::slotHands:
|
|
case EQEmu::invslot::slotLegs:
|
|
case EQEmu::invslot::slotFeet:
|
|
c->Message(m_message, "My %s are %s unequipped", GetBotEquipSlotName(slotId), ((itm) ? ("now") : ("already")));
|
|
break;
|
|
default:
|
|
c->Message(m_fail, "I'm soo confused...");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_inventory_window(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_inventory_window", sep->arg[0], "inventorywindow"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [actionable: target]", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = ActionableBots::ABM_Target;
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
auto my_bot = sbl.front();
|
|
if (!my_bot) {
|
|
c->Message(m_unknown, "ActionableBots returned 'nullptr'");
|
|
return;
|
|
}
|
|
|
|
std::string window_title = my_bot->GetCleanName();
|
|
window_title.append("`s Inventory");
|
|
|
|
std::string window_text;
|
|
//std::string item_link;
|
|
//EQEmu::SayLinkEngine linker;
|
|
//linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
|
|
|
|
for (int i = EQEmu::invslot::EQUIPMENT_BEGIN; i <= (EQEmu::invslot::EQUIPMENT_END + 1); ++i) {
|
|
const EQEmu::ItemData* item = nullptr;
|
|
const EQEmu::ItemInstance* inst = my_bot->CastToBot()->GetBotItem(i == 22 ? EQEmu::invslot::SLOT_POWER_SOURCE : i);
|
|
if (inst)
|
|
item = inst->GetItem();
|
|
|
|
window_text.append("<c \"#FFFFFF\">");
|
|
window_text.append(GetBotEquipSlotName(i == 22 ? EQEmu::invslot::SLOT_POWER_SOURCE : i));
|
|
window_text.append(": ");
|
|
if (item) {
|
|
//window_text.append("</c>");
|
|
//linker.SetItemInst(inst);
|
|
//item_link = linker.GenerateLink();
|
|
//window_text.append(item_link.c_str());
|
|
|
|
window_text.append("<c \"#00FF00\">");
|
|
window_text.append(StringFormat("%s", item->Name));
|
|
}
|
|
else {
|
|
window_text.append("<c \"#FFFF00\">");
|
|
window_text.append("[empty]");
|
|
}
|
|
window_text.append("<br>");
|
|
}
|
|
window_text.append("</c>");
|
|
|
|
c->SendPopupToClient(window_title.c_str(), window_text.c_str());
|
|
}
|
|
|
|
void bot_subcommand_pet_remove(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_pet_remove", sep->arg[0], "petremove"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
uint16 class_mask = (PLAYER_CLASS_DRUID_BIT | PLAYER_CLASS_NECROMANCER_BIT | PLAYER_CLASS_ENCHANTER_BIT);
|
|
ActionableBots::Filter_ByClasses(c, sbl, class_mask);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You have no spawned bots capable of charming");
|
|
return;
|
|
}
|
|
sbl.remove(nullptr);
|
|
|
|
int charmed_pet = 0;
|
|
int summoned_pet = 0;
|
|
for (auto bot_iter : sbl) { // Probably needs some work to release charmed pets
|
|
if (bot_iter->IsBotCharmer()) {
|
|
bot_iter->SetBotCharmer(false);
|
|
if (sbl.size() == 1)
|
|
Bot::BotGroupSay(bot_iter, "Using a summoned pet");
|
|
++summoned_pet;
|
|
continue;
|
|
}
|
|
|
|
if (bot_iter->GetPet()) {
|
|
bot_iter->GetPet()->Say_StringID(PET_GETLOST_STRING);
|
|
bot_iter->GetPet()->Depop(false);
|
|
bot_iter->SetPetID(0);
|
|
}
|
|
bot_iter->SetBotCharmer(true);
|
|
if (sbl.size() == 1)
|
|
Bot::BotGroupSay(bot_iter, "Available for Charming");
|
|
++charmed_pet;
|
|
}
|
|
|
|
if (sbl.size() != 1)
|
|
c->Message(m_action, "%i of your bots set for charming, %i of your bots set for summoned pet use", charmed_pet, summoned_pet);
|
|
}
|
|
|
|
void bot_subcommand_pet_set_type(Client *c, const Seperator *sep)
|
|
{
|
|
if (helper_command_alias_fail(c, "bot_subcommand_pet_set_type", sep->arg[0], "petsettype"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
|
|
c->Message(m_note, "requires one of the following bot classes:");
|
|
c->Message(m_message, "Magician(1)");
|
|
return;
|
|
}
|
|
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); // this can be expanded without code modification
|
|
|
|
std::string pet_arg = sep->arg[1];
|
|
|
|
uint8 pet_type = 255;
|
|
uint8 level_req = 255;
|
|
if (!pet_arg.compare("water")) {
|
|
pet_type = 0;
|
|
level_req = 1;
|
|
}
|
|
else if (!pet_arg.compare("fire")) {
|
|
pet_type = 1;
|
|
level_req = 3;
|
|
}
|
|
else if (!pet_arg.compare("air")) {
|
|
pet_type = 2;
|
|
level_req = 4;
|
|
}
|
|
else if (!pet_arg.compare("earth")) {
|
|
pet_type = 3;
|
|
level_req = 5;
|
|
}
|
|
else if (!pet_arg.compare("monster")) {
|
|
pet_type = 4;
|
|
level_req = 30;
|
|
}
|
|
|
|
if (pet_type == 255) {
|
|
c->Message(m_fail, "You must specify a pet [type: water | fire | air | earth | monster]");
|
|
return;
|
|
}
|
|
|
|
std::list<Bot*> sbl;
|
|
if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None)
|
|
return;
|
|
|
|
uint16 class_mask = PLAYER_CLASS_MAGICIAN_BIT;
|
|
ActionableBots::Filter_ByClasses(c, sbl, class_mask);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You have no spawned Magician bots");
|
|
return;
|
|
}
|
|
|
|
ActionableBots::Filter_ByMinLevel(c, sbl, level_req);
|
|
if (sbl.empty()) {
|
|
c->Message(m_fail, "You have no spawned Magician bots capable of using this pet type: '%s'", pet_arg.c_str());
|
|
return;
|
|
}
|
|
|
|
uint16 reclaim_energy_id = 331;
|
|
for (auto bot_iter : sbl) {
|
|
if (!bot_iter)
|
|
continue;
|
|
|
|
bot_iter->SetPetChooser(true);
|
|
bot_iter->SetPetChooserID(pet_type);
|
|
if (bot_iter->GetPet()) {
|
|
auto pet_id = bot_iter->GetPetID();
|
|
bot_iter->SetPetID(0);
|
|
bot_iter->CastSpell(reclaim_energy_id, pet_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bot_subcommand_portal(Client *c, const Seperator *sep)
|
|
{
|
|
bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart];
|
|
if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_subcommand_portal", sep->arg[0], "portal"))
|
|
return;
|
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]);
|
|
helper_send_usage_required_bots(c, BCEnum::SpT_Depart, WIZARD);
|
|
return;
|
|
}
|
|
|
|
bool single = false;
|
|
std::string single_arg = sep->arg[2];
|
|
if (!single_arg.compare("single"))
|
|
single = true;
|
|
|
|
std::string destination = sep->arg[1];
|
|
if (!destination.compare("list")) {
|
|
auto my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, WIZARD);
|
|
helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single);
|
|
return;
|
|
}
|
|
else if (destination.empty()) {
|
|
c->Message(m_fail, "A [destination] or [list] argument is required to use this command");
|
|
return;
|
|
}
|
|
|
|
ActionableTarget::Types actionable_targets;
|
|
Bot* my_bot = nullptr;
|
|
std::list<Bot*> sbl;
|
|
MyBots::PopulateSBL_BySpawnedBots(c, sbl);
|
|
|
|
bool cast_success = false;
|
|
for (auto list_iter : *local_list) {
|
|
auto local_entry = list_iter->SafeCastToDepart();
|
|
if (helper_spell_check_fail(local_entry))
|
|
continue;
|
|
if (local_entry->caster_class != WIZARD)
|
|
continue;
|
|
if (local_entry->single != single)
|
|
continue;
|
|
if (destination.compare(spells[local_entry->spell_id].teleport_zone))
|
|
continue;
|
|
|
|
auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY);
|
|
if (!target_mob)
|
|
continue;
|
|
|
|
my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob);
|
|
if (!my_bot)
|
|
continue;
|
|
|
|
cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id);
|
|
break;
|
|
}
|
|
|
|
helper_no_available_bots(c, my_bot);
|
|
}
|
|
|
|
|
|
/*
|
|
* bot command helpers go below here
|
|
*/
|
|
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(m_fail, "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(m_fail, "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(m_fail, "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(m_unknown, "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(m_action, "Successfully changed appearance for %s!", my_bot->GetCleanName());
|
|
}
|
|
|
|
void helper_bot_appearance_form_update(Bot *my_bot)
|
|
{
|
|
if (!my_bot)
|
|
return;
|
|
|
|
my_bot->SendIllusionPacket(
|
|
my_bot->GetRace(),
|
|
my_bot->GetGender(),
|
|
0xFF, //my_bot->GetTexture(), // 0xFF - change back if issues arise
|
|
0xFF, //my_bot->GetHelmTexture(), // 0xFF - change back if issues arise
|
|
my_bot->GetHairColor(),
|
|
my_bot->GetBeardColor(),
|
|
my_bot->GetEyeColor1(),
|
|
my_bot->GetEyeColor2(),
|
|
my_bot->GetHairStyle(),
|
|
my_bot->GetLuclinFace(),
|
|
my_bot->GetBeard(),
|
|
0xFF, // aa_title (0xFF)
|
|
my_bot->GetDrakkinHeritage(),
|
|
my_bot->GetDrakkinTattoo(),
|
|
my_bot->GetDrakkinDetails(),
|
|
my_bot->GetSize()
|
|
);
|
|
}
|
|
|
|
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(m_fail, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str());
|
|
return bot_id;
|
|
}
|
|
|
|
bool available_flag = false;
|
|
if (!botdb.QueryNameAvailablity(bot_name, available_flag)) {
|
|
bot_owner->Message(m_fail, "%s for '%s'", BotDatabase::fail::QueryNameAvailablity(), bot_name.c_str());
|
|
return bot_id;
|
|
}
|
|
if (!available_flag) {
|
|
bot_owner->Message(m_fail, "The name %s is already being used. Please choose a different name", bot_name.c_str());
|
|
return bot_id;
|
|
}
|
|
|
|
auto class_bit = GetPlayerClassBit(bot_class);
|
|
if ((class_bit & RuleI(Bots, AllowedClasses)) == PLAYER_CLASS_UNKNOWN_BIT) {
|
|
bot_owner->Message(m_fail, "Class '%s' bots are not allowed on this server", GetPlayerClassName(bot_class));
|
|
return bot_id;
|
|
}
|
|
|
|
auto race_bit = GetPlayerRaceBit(bot_race);
|
|
if ((race_bit & RuleI(Bots, AllowedRaces)) == PLAYER_RACE_UNKNOWN_BIT) {
|
|
bot_owner->Message(m_fail, "Race '%s' bots are not allowed on this server", GetPlayerRaceName(bot_class));
|
|
return bot_id;
|
|
}
|
|
|
|
if (!Bot::IsValidRaceClassCombo(bot_race, bot_class)) {
|
|
bot_owner->Message(m_fail, "'%s'(%u):'%s'(%u) is an invalid race-class combination",
|
|
Bot::RaceIdToString(bot_race).c_str(), bot_race, Bot::ClassIdToString(bot_class).c_str(), bot_class);
|
|
return bot_id;
|
|
}
|
|
|
|
if (bot_gender > FEMALE || (((1 << bot_gender) & RuleI(Bots, AllowedGenders)) == 0)) {
|
|
if (RuleI(Bots, AllowedGenders) == 3)
|
|
bot_owner->Message(m_fail, "gender: %u(M), %u(F)", MALE, FEMALE);
|
|
else if (RuleI(Bots, AllowedGenders) == 2)
|
|
bot_owner->Message(m_fail, "gender: %u(F)", FEMALE);
|
|
else if (RuleI(Bots, AllowedGenders) == 1)
|
|
bot_owner->Message(m_fail, "gender: %u(M)", MALE);
|
|
else
|
|
bot_owner->Message(m_fail, "gender: ERROR - No valid genders exist");
|
|
return bot_id;
|
|
}
|
|
|
|
uint32 max_bot_count = RuleI(Bots, CreationLimit);
|
|
|
|
uint32 bot_count = 0;
|
|
if (!botdb.QueryBotCount(bot_owner->CharacterID(), bot_count)) {
|
|
bot_owner->Message(m_fail, "%s", BotDatabase::fail::QueryBotCount());
|
|
return bot_id;
|
|
}
|
|
if (bot_count >= max_bot_count) {
|
|
bot_owner->Message(m_fail, "You have reached the maximum limit of %i bots", max_bot_count);
|
|
return bot_id;
|
|
}
|
|
|
|
auto DefaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(
|
|
bot_name.c_str(),
|
|
"",
|
|
bot_owner->GetLevel(),
|
|
bot_race,
|
|
bot_class,
|
|
bot_gender
|
|
);
|
|
|
|
auto my_bot = new Bot(DefaultNPCTypeStruct, bot_owner);
|
|
|
|
if (!my_bot->Save()) {
|
|
bot_owner->Message(m_unknown, "Failed to create '%s' due to unknown cause", my_bot->GetCleanName());
|
|
return bot_id;
|
|
}
|
|
|
|
bot_owner->Message(m_action, "Successfully created '%s' (id: %u)", my_bot->GetCleanName(), my_bot->GetBotID());
|
|
|
|
bot_id = my_bot->GetBotID();
|
|
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 WARRIOR:
|
|
case CLERIC:
|
|
case PALADIN:
|
|
case RANGER:
|
|
case SHADOWKNIGHT:
|
|
case DRUID:
|
|
case MONK:
|
|
bot_owner->Message(m_unknown, "%s has no out-of-combat behavior defined", my_bot->GetCleanName());
|
|
break;
|
|
case BARD:
|
|
bot_owner->Message(m_action, "%s will %s use out-of-combat behavior for bard songs", my_bot->GetCleanName(), ((my_bot->GetAltOutOfCombatBehavior()) ? ("now") : ("no longer")));
|
|
break;
|
|
case ROGUE:
|
|
case SHAMAN:
|
|
case NECROMANCER:
|
|
case WIZARD:
|
|
case MAGICIAN:
|
|
case ENCHANTER:
|
|
case BEASTLORD:
|
|
case BERSERKER:
|
|
bot_owner->Message(m_unknown, "%s has no out-of-combat behavior defined", my_bot->GetCleanName());
|
|
break;
|
|
default:
|
|
break;
|
|
bot_owner->Message(m_fail, "Undefined bot class for %s", my_bot->GetCleanName());
|
|
}
|
|
}
|
|
|
|
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, "Attempting to cast '%s' on %s", spells[spell_id].name, target_mob->GetCleanName());
|
|
|
|
return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQEmu::CastingSlot::Gem2, -1, -1, dont_root_before);
|
|
}
|
|
|
|
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(m_fail, "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(m_fail, "No bots are capable of performing this action");
|
|
return;
|
|
}
|
|
|
|
bot_owner->Message(m_message, "The following destinations are available:");
|
|
if (!local_list) {
|
|
bot_owner->Message(m_fail, "None");
|
|
return;
|
|
}
|
|
|
|
std::string msg;
|
|
std::string text_link;
|
|
|
|
int destinations = 0;
|
|
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;
|
|
msg = StringFormat("%ccircle %s%s", BOT_COMMAND_CHAR, spells[local_entry->spell_id].teleport_zone, ((single_flag) ? (" single") : ("")));
|
|
text_link = druid_bot->CreateSayLink(bot_owner, msg.c_str(), local_entry->long_name.c_str());
|
|
Bot::BotGroupSay(druid_bot, "dest: '%s' click: %s", spells[local_entry->spell_id].teleport_zone, text_link.c_str());
|
|
++destinations;
|
|
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;
|
|
msg = StringFormat("%cportal %s%s", BOT_COMMAND_CHAR, spells[local_entry->spell_id].teleport_zone, ((single_flag) ? (" single") : ("")));
|
|
text_link = wizard_bot->CreateSayLink(bot_owner, msg.c_str(), local_entry->long_name.c_str());
|
|
Bot::BotGroupSay(wizard_bot, "dest: '%s' click: %s", spells[local_entry->spell_id].teleport_zone, text_link.c_str());
|
|
++destinations;
|
|
continue;
|
|
}
|
|
}
|
|
if (!destinations)
|
|
bot_owner->Message(m_fail, "None");
|
|
}
|
|
|
|
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(m_fail, "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(m_message, "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(m_usage, "%c%s - %s", BOT_COMMAND_CHAR, subcommand_iter, ((find_iter != bot_command_list.end()) ? (find_iter->second->desc) : ("[no description]")));
|
|
++bot_subcommands_shown;
|
|
}
|
|
|
|
bot_owner->Message(m_message, "%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(m_note, "requires one of the following bot classes:");
|
|
if (bot_class)
|
|
bot_owner->Message(m_message, "%s", required_bots_map_by_class[spell_type][bot_class].c_str());
|
|
else
|
|
bot_owner->Message(m_message, "%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].zonetype && zone->GetZoneType() && !(spells[local_entry->spell_id].zonetype & 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(m_fail, "%s", required_bots_map[spell_type].c_str());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endif // BOTS
|