From bc79e28d49a62dfd954b1e26e604cb398b95f18a Mon Sep 17 00:00:00 2001 From: Uleat Date: Tue, 29 Jan 2019 20:25:35 -0500 Subject: [PATCH 01/18] Fix for bots disappearing while idle (update) --- zone/bot.cpp | 28 +++++++++++----------------- zone/bot.h | 4 ++-- zone/mob.h | 4 ++-- zone/waypoints.cpp | 6 ------ 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index f7575c030..4b493d9bd 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2013,12 +2013,20 @@ bool Bot::Process() { if(GetAppearance() == eaDead && GetHP() > 0) SetAppearance(eaStanding); + if (IsMoving()) { + ping_timer.Disable(); + } + else { + if (!ping_timer.Enabled()) + ping_timer.Start(BOT_KEEP_ALIVE_INTERVAL); + + if (ping_timer.Check()) + SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); + } + if (IsStunned() || IsMezzed()) return true; - if (!IsMoving() && ping_timer.Check()) - SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); - // Bot AI AI_Process(); return true; @@ -9082,18 +9090,4 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) return saylink; } -void Bot::StopMoving() { - if (!ping_timer.Enabled()) - ping_timer.Start(8000); - - Mob::StopMoving(); -} - -void Bot::StopMoving(float new_heading) { - if (!ping_timer.Enabled()) - ping_timer.Start(8000); - - Mob::StopMoving(new_heading); -} - #endif diff --git a/zone/bot.h b/zone/bot.h index ed3cf8d92..55a8cba97 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -44,6 +44,8 @@ #define BOT_LEASH_DISTANCE 250000 // as DSq value (500 units) +#define BOT_KEEP_ALIVE_INTERVAL 5000 // 5 seconds + extern WorldServer worldserver; const int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this @@ -341,8 +343,6 @@ public: virtual int GetRunspeed() const { return (int)((float)_GetRunSpeed() * 1.785714f); } virtual void WalkTo(float x, float y, float z); virtual void RunTo(float x, float y, float z); - virtual void StopMoving(); - virtual void StopMoving(float new_heading); bool UseDiscipline(uint32 spell_id, uint32 target); uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets); bool GetNeedsCured(Mob *tar); diff --git a/zone/mob.h b/zone/mob.h index 809e7841c..423dbb63e 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -589,8 +589,8 @@ public: void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu); void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); void SentPositionPacket(float dx, float dy, float dz, float dh, int anim, bool send_to_self = false); - virtual void StopMoving(); - virtual void StopMoving(float new_heading); + void StopMoving(); + void StopMoving(float new_heading); void SetSpawned() { spawned = true; }; bool Spawned() { return spawned; }; virtual bool ShouldISpawnFor(Client *c) { return true; } diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 968300e56..cf79aae08 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -1075,9 +1075,6 @@ void Bot::WalkTo(float x, float y, float z) if (IsSitting()) Stand(); - if (ping_timer.Enabled()) - ping_timer.Disable(); - Mob::WalkTo(x, y, z); } @@ -1086,9 +1083,6 @@ void Bot::RunTo(float x, float y, float z) if (IsSitting()) Stand(); - if (ping_timer.Enabled()) - ping_timer.Disable(); - Mob::RunTo(x, y, z); } #endif From 1526a167bbc5c85079f499ab7e98b24ad67570f0 Mon Sep 17 00:00:00 2001 From: Uleat Date: Wed, 30 Jan 2019 19:11:20 -0500 Subject: [PATCH 02/18] Update '/who' handler to behave like '/who all' in regards to gm flags --- zone/entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/entity.cpp b/zone/entity.cpp index 7b8e0880c..ad8d1fb1b 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4178,7 +4178,7 @@ void EntityList::ZoneWho(Client *c, Who_All_Struct *Who) WAPP2->RankMSGID = 12315; else if (ClientEntry->IsBuyer()) WAPP2->RankMSGID = 6056; - else if (ClientEntry->Admin() >= 10) + else if (ClientEntry->Admin() >= 10 && ClientEntry->GetGM()) WAPP2->RankMSGID = 12312; else WAPP2->RankMSGID = 0xFFFFFFFF; From cbe811cf94ecbc07de147027b86db4bfda18c1f6 Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 31 Jan 2019 21:59:11 -0500 Subject: [PATCH 03/18] Oops!! I did it again! --- common/eq_limits.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/eq_limits.cpp b/common/eq_limits.cpp index b6e47ea36..2f8674de8 100644 --- a/common/eq_limits.cpp +++ b/common/eq_limits.cpp @@ -1167,7 +1167,7 @@ static const EQEmu::spells::LookupEntry spells_static_lookup_entries[EQEmu::vers /*[ClientVersion::UF] =*/ EQEmu::spells::LookupEntry( UF::spells::SPELL_ID_MAX, - SoD::spells::SPELLBOOK_SIZE, + UF::spells::SPELLBOOK_SIZE, UF::spells::SPELL_GEM_COUNT, UF::spells::LONG_BUFFS, UF::spells::SHORT_BUFFS, @@ -1180,7 +1180,7 @@ static const EQEmu::spells::LookupEntry spells_static_lookup_entries[EQEmu::vers /*[ClientVersion::RoF] =*/ EQEmu::spells::LookupEntry( RoF::spells::SPELL_ID_MAX, - SoD::spells::SPELLBOOK_SIZE, + RoF::spells::SPELLBOOK_SIZE, UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case RoF::spells::LONG_BUFFS, RoF::spells::SHORT_BUFFS, @@ -1193,7 +1193,7 @@ static const EQEmu::spells::LookupEntry spells_static_lookup_entries[EQEmu::vers /*[ClientVersion::RoF2] =*/ EQEmu::spells::LookupEntry( RoF2::spells::SPELL_ID_MAX, - SoD::spells::SPELLBOOK_SIZE, + RoF2::spells::SPELLBOOK_SIZE, UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case RoF2::spells::LONG_BUFFS, RoF2::spells::SHORT_BUFFS, From 36b0a60451e07604f5c6dd7a44903780464d79de Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 2 Feb 2019 21:51:57 -0500 Subject: [PATCH 04/18] Fix for Titanium returning wrong value in ConvertClientVersionToExpansion## --- common/emu_versions.cpp | 53 +++++------------------------------------ common/emu_versions.h | 4 ++-- common/eq_limits.cpp | 12 +++++----- common/eq_limits.h | 14 +++++++++++ world/client.cpp | 5 ++-- zone/client_packet.cpp | 5 ++-- 6 files changed, 32 insertions(+), 61 deletions(-) diff --git a/common/emu_versions.cpp b/common/emu_versions.cpp index d74835671..e5b3c5e70 100644 --- a/common/emu_versions.cpp +++ b/common/emu_versions.cpp @@ -18,6 +18,7 @@ */ #include "emu_versions.h" +#include "emu_constants.h" bool EQEmu::versions::IsValidClientVersion(ClientVersion client_version) @@ -493,7 +494,7 @@ EQEmu::expansions::Expansion EQEmu::expansions::ConvertExpansionBitToExpansion(u } } -uint32 EQEmu::expansions::ConvertExpansionToExpansionMask(Expansion expansion) +uint32 EQEmu::expansions::ConvertExpansionToExpansionsMask(Expansion expansion) { switch (expansion) { case Expansion::RoK: @@ -543,57 +544,15 @@ uint32 EQEmu::expansions::ConvertExpansionToExpansionMask(Expansion expansion) EQEmu::expansions::Expansion EQEmu::expansions::ConvertClientVersionToExpansion(versions::ClientVersion client_version) { - switch (client_version) { - case versions::ClientVersion::Titanium: - return expansions::Expansion::PoR; - case versions::ClientVersion::SoF: - return expansions::Expansion::SoF; - case versions::ClientVersion::SoD: - return expansions::Expansion::SoD; - case versions::ClientVersion::UF: - return expansions::Expansion::UF; - case versions::ClientVersion::RoF: - case versions::ClientVersion::RoF2: - return expansions::Expansion::RoF; - default: - return expansions::Expansion::EverQuest; - } + return EQEmu::constants::StaticLookup(client_version)->Expansion; } uint32 EQEmu::expansions::ConvertClientVersionToExpansionBit(versions::ClientVersion client_version) { - switch (client_version) { - case versions::ClientVersion::Titanium: - return expansions::bitPoR; - case versions::ClientVersion::SoF: - return expansions::bitSoF; - case versions::ClientVersion::SoD: - return expansions::bitSoD; - case versions::ClientVersion::UF: - return expansions::bitUF; - case versions::ClientVersion::RoF: - case versions::ClientVersion::RoF2: - return expansions::bitRoF; - default: - return expansions::bitEverQuest; - } + return EQEmu::constants::StaticLookup(client_version)->ExpansionBit; } -uint32 EQEmu::expansions::ConvertClientVersionToExpansionMask(versions::ClientVersion client_version) +uint32 EQEmu::expansions::ConvertClientVersionToExpansionsMask(versions::ClientVersion client_version) { - switch (client_version) { - case versions::ClientVersion::Titanium: - return expansions::maskPoR; - case versions::ClientVersion::SoF: - return expansions::maskSoF; - case versions::ClientVersion::SoD: - return expansions::maskSoD; - case versions::ClientVersion::UF: - return expansions::maskUF; - case versions::ClientVersion::RoF: - case versions::ClientVersion::RoF2: - return expansions::maskRoF; - default: - return expansions::maskEverQuest; - } + return EQEmu::constants::StaticLookup(client_version)->ExpansionsMask; } diff --git a/common/emu_versions.h b/common/emu_versions.h index 17ea82b9f..b55c32267 100644 --- a/common/emu_versions.h +++ b/common/emu_versions.h @@ -210,10 +210,10 @@ namespace EQEmu const char* ExpansionName(uint32 expansion_bit); uint32 ConvertExpansionToExpansionBit(Expansion expansion); Expansion ConvertExpansionBitToExpansion(uint32 expansion_bit); - uint32 ConvertExpansionToExpansionMask(Expansion expansion); + uint32 ConvertExpansionToExpansionsMask(Expansion expansion); Expansion ConvertClientVersionToExpansion(versions::ClientVersion client_version); uint32 ConvertClientVersionToExpansionBit(versions::ClientVersion client_version); - uint32 ConvertClientVersionToExpansionMask(versions::ClientVersion client_version); + uint32 ConvertClientVersionToExpansionsMask(versions::ClientVersion client_version); } /*expansions*/ diff --git a/common/eq_limits.cpp b/common/eq_limits.cpp index 2f8674de8..9f52de30d 100644 --- a/common/eq_limits.cpp +++ b/common/eq_limits.cpp @@ -43,17 +43,17 @@ static const EQEmu::constants::LookupEntry constants_static_lookup_entries[EQEmu { /*[ClientVersion::Unknown] =*/ EQEmu::constants::LookupEntry( - EQEmu::expansions::Expansion::EverQuest, - ClientUnknown::INULL, - ClientUnknown::INULL, + ClientUnknown::constants::EXPANSION, + ClientUnknown::constants::EXPANSION_BIT, + ClientUnknown::constants::EXPANSIONS_MASK, ClientUnknown::INULL, ClientUnknown::INULL ), /*[ClientVersion::Client62] =*/ EQEmu::constants::LookupEntry( - EQEmu::expansions::Expansion::EverQuest, - Client62::INULL, - Client62::INULL, + Client62::constants::EXPANSION, + Client62::constants::EXPANSION_BIT, + Client62::constants::EXPANSIONS_MASK, Client62::INULL, Client62::INULL ), diff --git a/common/eq_limits.h b/common/eq_limits.h index 1f83c4606..5d62ced66 100644 --- a/common/eq_limits.h +++ b/common/eq_limits.h @@ -243,6 +243,13 @@ namespace ClientUnknown const int16 IINVALID = -1; const int16 INULL = 0; + namespace constants { + const EQEmu::expansions::Expansion EXPANSION = EQEmu::expansions::Expansion::EverQuest; + const uint32 EXPANSION_BIT = EQEmu::expansions::bitEverQuest; + const uint32 EXPANSIONS_MASK = EQEmu::expansions::maskEverQuest; + + } // namespace constants + } /*ClientUnknown*/ namespace Client62 @@ -250,6 +257,13 @@ namespace Client62 const int16 IINVALID = -1; const int16 INULL = 0; + namespace constants { + const EQEmu::expansions::Expansion EXPANSION = EQEmu::expansions::Expansion::EverQuest; + const uint32 EXPANSION_BIT = EQEmu::expansions::bitEverQuest; + const uint32 EXPANSIONS_MASK = EQEmu::expansions::maskEverQuest; + + } // namespace constants + } /*Client62*/ #endif /*COMMON_EQ_LIMITS_H*/ diff --git a/world/client.cpp b/world/client.cpp index 06bceb52d..bc044dae5 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -172,12 +172,11 @@ void Client::SendExpansionInfo() { auto outapp = new EQApplicationPacket(OP_ExpansionInfo, sizeof(ExpansionInfo_Struct)); ExpansionInfo_Struct *eis = (ExpansionInfo_Struct*)outapp->pBuffer; - // need to rework .. not until full scope of change is accounted for, though if (RuleB(World, UseClientBasedExpansionSettings)) { - eis->Expansions = EQEmu::expansions::ConvertClientVersionToExpansionMask(eqs->ClientVersion()); + eis->Expansions = EQEmu::expansions::ConvertClientVersionToExpansionsMask(eqs->ClientVersion()); } else { - eis->Expansions = RuleI(World, ExpansionSettings); + eis->Expansions = (RuleI(World, ExpansionSettings) & EQEmu::expansions::ConvertClientVersionToExpansionsMask(eqs->ClientVersion())); } QueuePacket(outapp); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 41b38b0c9..2fb7448f6 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1414,12 +1414,11 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if (m_pp.ldon_points_tak < 0 || m_pp.ldon_points_tak > 2000000000) { m_pp.ldon_points_tak = 0; } if (m_pp.ldon_points_available < 0 || m_pp.ldon_points_available > 2000000000) { m_pp.ldon_points_available = 0; } - // need to rework .. not until full scope of change is accounted for, though if (RuleB(World, UseClientBasedExpansionSettings)) { - m_pp.expansions = EQEmu::expansions::ConvertClientVersionToExpansionMask(ClientVersion()); + m_pp.expansions = EQEmu::expansions::ConvertClientVersionToExpansionsMask(ClientVersion()); } else { - m_pp.expansions = RuleI(World, ExpansionSettings); + m_pp.expansions = (RuleI(World, ExpansionSettings) & EQEmu::expansions::ConvertClientVersionToExpansionsMask(ClientVersion())); } if (!database.LoadAlternateAdvancement(this)) { From 93a0ad2cebcfe065e9b11de7070126f9851e3717 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Feb 2019 07:02:27 -0500 Subject: [PATCH 05/18] Added command 'profanity' --- changelog.txt | 20 ++ common/CMakeLists.txt | 2 + common/profanity_manager.cpp | 249 ++++++++++++++++++ common/profanity_manager.h | 62 +++++ common/servertalk.h | 1 + common/version.h | 2 +- utils/sql/db_update_manifest.txt | 1 + .../required/2019_02_04_profanity_command.sql | 10 + world/zoneserver.cpp | 4 + zone/client.cpp | 8 + zone/command.cpp | 64 +++++ zone/command.h | 1 + zone/net.cpp | 5 + zone/worldserver.cpp | 6 + 14 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 common/profanity_manager.cpp create mode 100644 common/profanity_manager.h create mode 100644 utils/sql/git/required/2019_02_04_profanity_command.sql diff --git a/changelog.txt b/changelog.txt index 236fe430f..c73fca6db 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,26 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 2/4/2019 == +Uleat: Added command 'profanity' (aliased 'prof') + - This is a server-based tool for redacting any language that an admin deems as profanity (socially unacceptable within their community) + - Five options are available under this command.. + -- 'list' - shows the current list of banned words + -- 'clear' - clears the current list of banned words + -- 'add ' - adds to the banned word list + -- 'del ' - deletes from the banned word list + -- 'reload' - forces a reload of the banned word list + - All actions are immediate and a world broadcast refreshes other active zones + - The system is in stand-by when the list is empty..just add a word to the list to begin censorship + - Redaction only occurs on genuine occurences of any banned word + -- Banned words are replaced with a series of '*' characters + -- Compounded words are ignored to avoid issues with allowed words containing a banned sub-string + -- If 'test' is banned, 'testing' will not be banned .. it must be added separately + - Extreme care should be exercised when adding words to the banned list.. + -- Quest failures and limited social interactions may alienate players if they become inhibiting + -- System commands are allowed to be processed before redaction occurs in the 'say' channel + - A longer list requires more clock cycles to process - so, try to keep them to the most offensible occurrences + == 1/26/2019 == Uleat: Fix for class Bot not honoring NPCType data reference - Fixes bots not moving on spawn/grouping issue diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b953815e6..5e31e6794 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -55,6 +55,7 @@ SET(common_sources perl_eqdb.cpp perl_eqdb_res.cpp proc_launcher.cpp + profanity_manager.cpp ptimer.cpp races.cpp rdtsc.cpp @@ -181,6 +182,7 @@ SET(common_headers packet_functions.h platform.h proc_launcher.h + profanity_manager.h profiler.h ptimer.h queue.h diff --git a/common/profanity_manager.cpp b/common/profanity_manager.cpp new file mode 100644 index 000000000..1cf453ba5 --- /dev/null +++ b/common/profanity_manager.cpp @@ -0,0 +1,249 @@ +/* EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2019 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +#include "profanity_manager.h" +#include "dbcore.h" + +#include +#include + + +static std::list profanity_list; +static bool update_originator_flag = false; + +bool EQEmu::ProfanityManager::LoadProfanityList(DBcore *db) { + if (update_originator_flag == true) { + update_originator_flag = false; + return true; + } + + if (!load_database_entries(db)) + return false; + + return true; +} + +bool EQEmu::ProfanityManager::UpdateProfanityList(DBcore *db) { + if (!load_database_entries(db)) + return false; + + update_originator_flag = true; + + return true; +} + +bool EQEmu::ProfanityManager::DeleteProfanityList(DBcore *db) { + if (!clear_database_entries(db)) + return false; + + update_originator_flag = true; + + return true; +} + +bool EQEmu::ProfanityManager::AddProfanity(DBcore *db, const char *profanity) { + if (!db || !profanity) + return false; + + std::string entry(profanity); + + std::transform(entry.begin(), entry.end(), entry.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + + if (check_for_existing_entry(entry.c_str())) + return true; + + if (entry.length() < REDACTION_LENGTH_MIN) + return false; + + profanity_list.push_back(entry); + + std::string query = "REPLACE INTO `profanity_list` (`word`) VALUES ('"; + query.append(entry); + query.append("')"); + auto results = db->QueryDatabase(query); + if (!results.Success()) + return false; + + update_originator_flag = true; + + return true; +} + +bool EQEmu::ProfanityManager::RemoveProfanity(DBcore *db, const char *profanity) { + if (!db || !profanity) + return false; + + std::string entry(profanity); + + std::transform(entry.begin(), entry.end(), entry.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + + if (!check_for_existing_entry(entry.c_str())) + return true; + + profanity_list.remove(entry); + + std::string query = "DELETE FROM `profanity_list` WHERE `word` LIKE '"; + query.append(entry); + query.append("'"); + auto results = db->QueryDatabase(query); + if (!results.Success()) + return false; + + update_originator_flag = true; + + return true; +} + +void EQEmu::ProfanityManager::RedactMessage(char *message) { + if (!message) + return; + + std::string test_message(message); + // hard-coded max length based on channel message buffer size (4096 bytes).. + // ..will need to change or remove if other sources are used for redaction + if (test_message.length() < REDACTION_LENGTH_MIN || test_message.length() >= 4096) + return; + + std::transform(test_message.begin(), test_message.end(), test_message.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + + for (const auto &iter : profanity_list) { // consider adding textlink checks if it becomes an issue + size_t pos = 0; + size_t start_pos = 0; + + while (pos != std::string::npos) { + pos = test_message.find(iter, start_pos); + if (pos == std::string::npos) + continue; + + if ((pos + iter.length()) == test_message.length() || !isalpha(test_message.at(pos + iter.length()))) { + if (pos == 0 || !isalpha(test_message.at(pos - 1))) + memset((message + pos), REDACTION_CHARACTER, iter.length()); + } + + start_pos = (pos + iter.length()); + } + } +} + +void EQEmu::ProfanityManager::RedactMessage(std::string &message) { + if (message.length() < REDACTION_LENGTH_MIN || message.length() >= 4096) + return; + + std::string test_message(const_cast(message)); + + std::transform(test_message.begin(), test_message.end(), test_message.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + + for (const auto &iter : profanity_list) { // consider adding textlink checks if it becomes an issue + size_t pos = 0; + size_t start_pos = 0; + + while (pos != std::string::npos) { + pos = test_message.find(iter, start_pos); + if (pos == std::string::npos) + continue; + + if ((pos + iter.length()) == test_message.length() || !isalpha(test_message.at(pos + iter.length()))) { + if (pos == 0 || !isalpha(test_message.at(pos - 1))) + message.replace(pos, iter.length(), iter.length(), REDACTION_CHARACTER); + } + + start_pos = (pos + iter.length()); + } + } +} + +bool EQEmu::ProfanityManager::ContainsCensoredLanguage(const char *message) { + if (!message) + return false; + + return ContainsCensoredLanguage(std::string(message)); +} + +bool EQEmu::ProfanityManager::ContainsCensoredLanguage(const std::string &message) { + if (message.length() < REDACTION_LENGTH_MIN || message.length() >= 4096) + return false; + + std::string test_message(message); + + std::transform(test_message.begin(), test_message.end(), test_message.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + + for (const auto &iter : profanity_list) { + if (test_message.find(iter) != std::string::npos) + return true; + } + + return false; +} + +const std::list &EQEmu::ProfanityManager::GetProfanityList() { + return profanity_list; +} + +bool EQEmu::ProfanityManager::IsCensorshipActive() { + return (profanity_list.size() != 0); +} + +bool EQEmu::ProfanityManager::load_database_entries(DBcore *db) { + if (!db) + return false; + + profanity_list.clear(); + + std::string query = "SELECT `word` FROM `profanity_list`"; + auto results = db->QueryDatabase(query); + if (!results.Success()) + return false; + + for (auto row = results.begin(); row != results.end(); ++row) { + if (std::strlen(row[0]) >= REDACTION_LENGTH_MIN) { + std::string entry(row[0]); + std::transform(entry.begin(), entry.end(), entry.begin(), [](unsigned char c) -> unsigned char { return tolower(c); }); + if (!check_for_existing_entry(entry.c_str())) + profanity_list.push_back((std::string)entry); + } + } + + return true; +} + +bool EQEmu::ProfanityManager::clear_database_entries(DBcore *db) { + if (!db) + return false; + + profanity_list.clear(); + + std::string query = "DELETE FROM `profanity_list`"; + auto results = db->QueryDatabase(query); + if (!results.Success()) + return false; + + return true; +} + +bool EQEmu::ProfanityManager::check_for_existing_entry(const char *profanity) { + if (!profanity) + return false; + + for (const auto &iter : profanity_list) { + if (iter.compare(profanity) == 0) + return true; + } + + return false; +} diff --git a/common/profanity_manager.h b/common/profanity_manager.h new file mode 100644 index 000000000..fa601f0f0 --- /dev/null +++ b/common/profanity_manager.h @@ -0,0 +1,62 @@ +/* EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2019 EQEMu Development Team (http://eqemulator.net) + + 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 +*/ + +#ifndef COMMON_PROFANITY_MANAGER_H +#define COMMON_PROFANITY_MANAGER_H + +#include +#include + + +class DBcore; + +namespace EQEmu +{ + class ProfanityManager { + public: + static bool LoadProfanityList(DBcore *db); + static bool UpdateProfanityList(DBcore *db); + static bool DeleteProfanityList(DBcore *db); + + static bool AddProfanity(DBcore *db, const char *profanity); + static bool RemoveProfanity(DBcore *db, const char *profanity); + + static void RedactMessage(char *message); + static void RedactMessage(std::string &message); + + static bool ContainsCensoredLanguage(const char *message); + static bool ContainsCensoredLanguage(const std::string &message); + + static const std::list &GetProfanityList(); + + static bool IsCensorshipActive(); + + static const char REDACTION_CHARACTER = '*'; + static const int REDACTION_LENGTH_MIN = 3; + + private: + static bool load_database_entries(DBcore *db); + static bool clear_database_entries(DBcore *db); + static bool check_for_existing_entry(const char *profanity); + + }; + +} /*EQEmu*/ + +#endif /*COMMON_PROFANITY_MANAGER_H*/ diff --git a/common/servertalk.h b/common/servertalk.h index c5bee9ef8..a6b866fbe 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -159,6 +159,7 @@ #define ServerOP_SetWorldTime 0x200B #define ServerOP_GetWorldTime 0x200C #define ServerOP_SyncWorldTime 0x200E +#define ServerOP_RefreshCensorship 0x200F #define ServerOP_LSZoneInfo 0x3001 #define ServerOP_LSZoneStart 0x3002 diff --git a/common/version.h b/common/version.h index c45ef76d8..5f78cdb3a 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9135 +#define CURRENT_BINARY_DATABASE_VERSION 9136 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9021 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 2a6f35e89..cbf6d9419 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -389,6 +389,7 @@ 9133|2018_11_25_StuckBehavior.sql|SHOW COLUMNS FROM `npc_types` LIKE 'stuck_behavior'|empty| 9134|2019_01_04_update_global_base_scaling.sql|SELECT * FROM db_version WHERE version >= 9134|empty| 9135|2019_01_10_multi_version_spawns.sql|SHOW COLUMNS FROM `spawn2` LIKE 'version'|contains|unsigned| +9136|2019_02_14_profanity_command.sql|SHOW TABLES LIKE 'profanity_list'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2019_02_04_profanity_command.sql b/utils/sql/git/required/2019_02_04_profanity_command.sql new file mode 100644 index 000000000..33562fe75 --- /dev/null +++ b/utils/sql/git/required/2019_02_04_profanity_command.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `profanity_list`; + +CREATE TABLE `profanity_list` ( + `word` VARCHAR(16) NOT NULL +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +; + +REPLACE INTO `command_settings` VALUES ('profanity', 150, 'prof'); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 7eaa7e3c1..f3c6aebfc 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -981,6 +981,10 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { safe_delete(pack); break; } + case ServerOP_RefreshCensorship: { + zoneserver_list.SendPacket(pack); + break; + } case ServerOP_SetWorldTime: { Log(Logs::Detail, Logs::World_Server, "Received SetWorldTime"); eqTimeOfDay* newtime = (eqTimeOfDay*)pack->pBuffer; diff --git a/zone/client.cpp b/zone/client.cpp index beb21ae3d..ff96b885b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -38,6 +38,7 @@ extern volatile bool RunLoops; #include "../common/rulesys.h" #include "../common/string_util.h" #include "../common/data_verification.h" +#include "../common/profanity_manager.h" #include "data_bucket.h" #include "position.h" #include "net.h" @@ -895,6 +896,10 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s language = 0; // No need for language when drunk } + // Censor the message + if (EQEmu::ProfanityManager::IsCensorshipActive() && (chan_num != 8)) + EQEmu::ProfanityManager::RedactMessage(message); + switch(chan_num) { case 0: { /* Guild Chat */ @@ -1092,6 +1097,9 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s break; } + if (EQEmu::ProfanityManager::IsCensorshipActive()) + EQEmu::ProfanityManager::RedactMessage(message); + #ifdef BOTS if (message[0] == BOT_COMMAND_CHAR) { if (bot_command_dispatch(this, message) == -2) { diff --git a/zone/command.cpp b/zone/command.cpp index 0590b16ea..d73c760ed 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -54,6 +54,7 @@ #include "../common/string_util.h" #include "../say_link.h" #include "../common/eqemu_logsys.h" +#include "../common/profanity_manager.h" #include "data_bucket.h" #include "command.h" @@ -307,6 +308,7 @@ int command_init(void) command_add("petitioninfo", "[petition number] - Get info about a petition", 20, command_petitioninfo) || command_add("pf", "- Display additional mob coordinate and wandering data", 0, command_pf) || command_add("picklock", "Analog for ldon pick lock for the newer clients since we still don't have it working.", 0, command_picklock) || + command_add("profanity", "Manage censored language.", 150, command_profanity) || #ifdef EQPROFILE command_add("profiledump", "- Dump profiling info to logs", 250, command_profiledump) || @@ -11043,6 +11045,68 @@ void command_picklock(Client *c, const Seperator *sep) } } +void command_profanity(Client *c, const Seperator *sep) +{ + std::string arg1(sep->arg[1]); + + while (true) { + if (arg1.compare("list") == 0) { + // do nothing + } + else if (arg1.compare("clear") == 0) { + EQEmu::ProfanityManager::DeleteProfanityList(&database); + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("add") == 0) { + if (!EQEmu::ProfanityManager::AddProfanity(&database, sep->arg[2])) + c->Message(CC_Red, "Could not add '%s' to the profanity list.", sep->arg[2]); + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("del") == 0) { + if (!EQEmu::ProfanityManager::RemoveProfanity(&database, sep->arg[2])) + c->Message(CC_Red, "Could not delete '%s' from the profanity list.", sep->arg[2]); + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else if (arg1.compare("reload") == 0) { + if (!EQEmu::ProfanityManager::UpdateProfanityList(&database)) + c->Message(CC_Red, "Could not reload the profanity list."); + auto pack = new ServerPacket(ServerOP_RefreshCensorship); + worldserver.SendPacket(pack); + safe_delete(pack); + } + else { + break; + } + + std::string popup; + const auto &list = EQEmu::ProfanityManager::GetProfanityList(); + for (const auto &iter : list) { + popup.append(iter); + popup.append("
"); + } + if (list.empty()) + popup.append("** Censorship Inactive **
"); + else + popup.append("** End of List **
"); + + c->SendPopupToClient("Profanity List", popup.c_str()); + + return; + } + + c->Message(0, "Usage: #profanity [list] - shows profanity list"); + c->Message(0, "Usage: #profanity [clear] - deletes all entries"); + c->Message(0, "Usage: #profanity [add] [] - adds entry"); + c->Message(0, "Usage: #profanity [del] [] - deletes entry"); + c->Message(0, "Usage: #profanity [reload] - reloads profanity list"); +} + void command_mysql(Client *c, const Seperator *sep) { if(!sep->arg[1][0] || !sep->arg[2][0]) { diff --git a/zone/command.h b/zone/command.h index 21ea58919..9efeddcc7 100644 --- a/zone/command.h +++ b/zone/command.h @@ -210,6 +210,7 @@ void command_permagender(Client *c, const Seperator *sep); void command_permarace(Client *c, const Seperator *sep); void command_petitioninfo(Client *c, const Seperator *sep); void command_picklock(Client *c, const Seperator *sep); +void command_profanity(Client *c, const Seperator *sep); #ifdef EQPROFILE void command_profiledump(Client *c, const Seperator *sep); diff --git a/zone/net.cpp b/zone/net.cpp index 5dec87474..4fe935be5 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -32,6 +32,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/eq_stream_ident.h" #include "../common/patches/patches.h" #include "../common/rulesys.h" +#include "../common/profanity_manager.h" #include "../common/misc_functions.h" #include "../common/string_util.h" #include "../common/platform.h" @@ -350,6 +351,10 @@ int main(int argc, char** argv) { Log(Logs::General, Logs::Zone_Server, "Loading corpse timers"); database.GetDecayTimes(npcCorpseDecayTimes); + Log(Logs::General, Logs::Zone_Server, "Loading profanity list"); + if (!EQEmu::ProfanityManager::LoadProfanityList(&database)) + Log(Logs::General, Logs::Error, "Loading profanity list FAILED!"); + Log(Logs::General, Logs::Zone_Server, "Loading commands"); int retval = command_init(); if (retval<0) diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index e187a3144..c2e094ee9 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -36,6 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/misc_functions.h" #include "../common/rulesys.h" #include "../common/servertalk.h" +#include "../common/profanity_manager.h" #include "client.h" #include "corpse.h" @@ -793,6 +794,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } break; } + case ServerOP_RefreshCensorship: { + if (!EQEmu::ProfanityManager::LoadProfanityList(&database)) + Log(Logs::General, Logs::Error, "Received request to refresh the profanity list..but, the action failed"); + break; + } case ServerOP_ChangeWID: { if (pack->size != sizeof(ServerChangeWID_Struct)) { std::cout << "Wrong size on ServerChangeWID_Struct. Got: " << pack->size << ", Expected: " << sizeof(ServerChangeWID_Struct) << std::endl; From cd95f5862513f7a17a3cd6c852443772efa4b7c4 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Feb 2019 12:03:25 -0500 Subject: [PATCH 06/18] Fix for linux build --- common/profanity_manager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/common/profanity_manager.cpp b/common/profanity_manager.cpp index 1cf453ba5..70817576f 100644 --- a/common/profanity_manager.cpp +++ b/common/profanity_manager.cpp @@ -21,6 +21,7 @@ #include "dbcore.h" #include +#include #include From 594ec4faee4ce0b945c012fd43ee887ef3a7cdaa Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Feb 2019 14:00:08 -0500 Subject: [PATCH 07/18] Fix for possible server crash when applying poison --- zone/client_packet.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 2fb7448f6..9e03a6e4e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -2812,24 +2812,19 @@ void Client::Handle_OP_ApplyPoison(const EQApplicationPacket *app) return; } - uint32 ApplyPoisonSuccessResult = 0; ApplyPoison_Struct* ApplyPoisonData = (ApplyPoison_Struct*)app->pBuffer; + + uint32 ApplyPoisonSuccessResult = 0; + const EQEmu::ItemInstance* PrimaryWeapon = GetInv().GetItem(EQEmu::invslot::slotPrimary); const EQEmu::ItemInstance* SecondaryWeapon = GetInv().GetItem(EQEmu::invslot::slotSecondary); - const EQEmu::ItemInstance* PoisonItemInstance = GetInv()[ApplyPoisonData->inventorySlot]; - const EQEmu::ItemData* poison=PoisonItemInstance->GetItem(); - const EQEmu::ItemData* primary=nullptr; - const EQEmu::ItemData* secondary=nullptr; - bool IsPoison = PoisonItemInstance && - (poison->ItemType == EQEmu::item::ItemTypePoison); + const EQEmu::ItemInstance* PoisonItemInstance = GetInv().GetItem(ApplyPoisonData->inventorySlot); - if (PrimaryWeapon) { - primary=PrimaryWeapon->GetItem(); - } + const EQEmu::ItemData* primary = (PrimaryWeapon ? PrimaryWeapon->GetItem() : nullptr); + const EQEmu::ItemData* secondary = (SecondaryWeapon ? SecondaryWeapon->GetItem() : nullptr); + const EQEmu::ItemData* poison = (PoisonItemInstance ? PoisonItemInstance->GetItem() : nullptr); - if (SecondaryWeapon) { - secondary=SecondaryWeapon->GetItem(); - } + bool IsPoison = (poison && poison->ItemType == EQEmu::item::ItemTypePoison); if (IsPoison && GetClass() == ROGUE) { From ee970acc2e873e31da5fa4eff0d72e6e9cec7638 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Feb 2019 14:56:00 -0500 Subject: [PATCH 08/18] Fix for bots ceasing combat when their 'follow me' mob dies --- changelog.txt | 4 ++++ zone/bot.cpp | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index c73fca6db..8deecf26b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,6 +20,10 @@ Uleat: Added command 'profanity' (aliased 'prof') -- Quest failures and limited social interactions may alienate players if they become inhibiting -- System commands are allowed to be processed before redaction occurs in the 'say' channel - A longer list requires more clock cycles to process - so, try to keep them to the most offensible occurrences +Uleat: Fix for bots ceasing combat when their 'follow me' mob dies + - Bots will revert to their client leash owner (bot owner or client group leader) when their FollowID() mob is no longer valid + - Combat will no longer be interrupted in these cases + - Does not apply to bot owner death... == 1/26/2019 == Uleat: Fix for class Bot not honoring NPCType data reference diff --git a/zone/bot.cpp b/zone/bot.cpp index 4b493d9bd..d7e330f6e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2248,10 +2248,9 @@ void Bot::AI_Process() { Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); Group* bot_group = GetGroup(); - Mob* follow_mob = entity_list.GetMob(GetFollowID()); - + // Primary reasons for not processing AI - if (!bot_owner || !bot_group || !follow_mob || !IsAIControlled()) + if (!bot_owner || !bot_group || !IsAIControlled()) return; if (bot_owner->IsDead()) { @@ -2261,11 +2260,18 @@ void Bot::AI_Process() { return; } - // We also need a leash owner (subset of primary AI criteria) + // We also need a leash owner and follow mob (subset of primary AI criteria) Client* leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); if (!leash_owner) return; + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob) { + follow_mob = leash_owner; + SetFollowID(leash_owner->GetID()); + } + // Berserk updates should occur if primary AI criteria are met if (GetClass() == WARRIOR || GetClass() == BERSERKER) { if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { From b810e3aa710f0a6994f618a9c540377aab4de9ea Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Feb 2019 21:39:16 -0500 Subject: [PATCH 09/18] Fix for profanity command script file name in manifest [ci skip] --- utils/sql/db_update_manifest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index cbf6d9419..ff4ccbe05 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -389,7 +389,7 @@ 9133|2018_11_25_StuckBehavior.sql|SHOW COLUMNS FROM `npc_types` LIKE 'stuck_behavior'|empty| 9134|2019_01_04_update_global_base_scaling.sql|SELECT * FROM db_version WHERE version >= 9134|empty| 9135|2019_01_10_multi_version_spawns.sql|SHOW COLUMNS FROM `spawn2` LIKE 'version'|contains|unsigned| -9136|2019_02_14_profanity_command.sql|SHOW TABLES LIKE 'profanity_list'|empty| +9136|2019_02_04_profanity_command.sql|SHOW TABLES LIKE 'profanity_list'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not From 93394e0edc0d5787a00f18439a5a2982e05cb099 Mon Sep 17 00:00:00 2001 From: Brian Kinney Date: Wed, 6 Feb 2019 21:52:59 -0500 Subject: [PATCH 10/18] Reference the defender melee mitigation, not attacker --- zone/attack.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 483cce368..d789639f0 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4514,7 +4514,9 @@ void Mob::ApplyMeleeDamageMods(uint16 skill, int &damage, Mob *defender, ExtraAt if (defender->IsClient() && defender->GetClass() == WARRIOR) dmgbonusmod -= 5; // 168 defensive - dmgbonusmod += (defender->spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect); + dmgbonusmod -= (defender->spellbonuses.MeleeMitigationEffect + + defender->itembonuses.MeleeMitigationEffect + + defender->aabonuses.MeleeMitigationEffect); } damage += damage * dmgbonusmod / 100; From 1d0b00caf788e1cff8e30e43ba02c47b8b6e1d3d Mon Sep 17 00:00:00 2001 From: Brian Kinney Date: Wed, 6 Feb 2019 23:54:38 -0500 Subject: [PATCH 11/18] Raw numbers are negative so adding is correct --- zone/attack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index d789639f0..a51cd3163 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4514,7 +4514,7 @@ void Mob::ApplyMeleeDamageMods(uint16 skill, int &damage, Mob *defender, ExtraAt if (defender->IsClient() && defender->GetClass() == WARRIOR) dmgbonusmod -= 5; // 168 defensive - dmgbonusmod -= (defender->spellbonuses.MeleeMitigationEffect + + dmgbonusmod += (defender->spellbonuses.MeleeMitigationEffect + defender->itembonuses.MeleeMitigationEffect + defender->aabonuses.MeleeMitigationEffect); } From 3cffe5f7ef0f3a48a793792518d70a0a25f775d9 Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 7 Feb 2019 22:09:31 -0500 Subject: [PATCH 12/18] Put merc and bot classes on the same stance standard (mercs) --- changelog.txt | 5 + common/emu_constants.cpp | 34 ++++++ common/emu_constants.h | 18 +++ common/version.h | 2 +- .../sql/git/bots/bots_db_update_manifest.txt | 1 + .../2019_02_07_bots_stance_type_update.sql | 11 ++ zone/bot.cpp | 46 +++---- zone/bot.h | 44 ++----- zone/bot_command.cpp | 38 +++--- zone/bot_database.cpp | 14 +-- zone/botspellsai.cpp | 115 +++++++++--------- zone/client_packet.cpp | 2 +- zone/merc.cpp | 28 ++--- zone/merc.h | 18 +-- 14 files changed, 210 insertions(+), 166 deletions(-) create mode 100644 utils/sql/git/bots/required/2019_02_07_bots_stance_type_update.sql diff --git a/changelog.txt b/changelog.txt index 8deecf26b..d6ddcbf75 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 2/7/2019 == +Uleat: Put merc and bot classes on the same stance standard (mercs) + - Both classes will now use the same stance standard + - Pushed stance types up to EQEmu::constants + == 2/4/2019 == Uleat: Added command 'profanity' (aliased 'prof') - This is a server-based tool for redacting any language that an admin deems as profanity (socially unacceptable within their community) diff --git a/common/emu_constants.cpp b/common/emu_constants.cpp index e229b7d6d..950bc5ebb 100644 --- a/common/emu_constants.cpp +++ b/common/emu_constants.cpp @@ -118,3 +118,37 @@ EQEmu::bug::CategoryID EQEmu::bug::CategoryNameToCategoryID(const char* category return catOther; } + +const char *EQEmu::constants::GetStanceName(StanceType stance_type) { + switch (stance_type) { + case stanceUnknown: + return "Unknown"; + case stancePassive: + return "Passive"; + case stanceBalanced: + return "Balanced"; + case stanceEfficient: + return "Efficient"; + case stanceReactive: + return "Reactive"; + case stanceAggressive: + return "Aggressive"; + case stanceAssist: + return "Assist"; + case stanceBurn: + return "Burn"; + case stanceEfficient2: + return "Efficient2"; + case stanceBurnAE: + return "BurnAE"; + default: + return "Invalid"; + } +} + +int EQEmu::constants::ConvertStanceTypeToIndex(StanceType stance_type) { + if (stance_type >= EQEmu::constants::stancePassive && stance_type <= EQEmu::constants::stanceBurnAE) + return (stance_type - EQEmu::constants::stancePassive); + + return 0; +} diff --git a/common/emu_constants.h b/common/emu_constants.h index e3869570e..970b2861d 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -203,6 +203,24 @@ namespace EQEmu const size_t SAY_LINK_CLOSER_SIZE = 1; const size_t SAY_LINK_MAXIMUM_SIZE = (SAY_LINK_OPENER_SIZE + SAY_LINK_BODY_SIZE + SAY_LINK_TEXT_SIZE + SAY_LINK_CLOSER_SIZE); + enum StanceType : int { + stanceUnknown = 0, + stancePassive, + stanceBalanced, + stanceEfficient, + stanceReactive, + stanceAggressive, + stanceAssist, + stanceBurn, + stanceEfficient2, + stanceBurnAE + }; + + const char *GetStanceName(StanceType stance_type); + int ConvertStanceTypeToIndex(StanceType stance_type); + + const size_t STANCE_TYPE_MAX = stanceBurnAE; + } /*constants*/ namespace profile { diff --git a/common/version.h b/common/version.h index 5f78cdb3a..ff1c9ee46 100644 --- a/common/version.h +++ b/common/version.h @@ -32,7 +32,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9136 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9021 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9022 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index acdb20532..48e9fa026 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -20,6 +20,7 @@ 9019|2018_04_12_bots_stop_melee_level.sql|SHOW COLUMNS FROM `bot_data` LIKE 'stop_melee_level'|empty| 9020|2018_08_13_bots_inventory_update.sql|SELECT * FROM `inventory_versions` WHERE `version` = 2 and `bot_step` = 0|not_empty| 9021|2018_10_09_bots_owner_options.sql|SHOW TABLES LIKE 'bot_owner_options'|empty| +9022|2019_02_07_bots_stance_type_update.sql|SELECT * FROM `bot_spell_casting_chances` WHERE `spell_type_index` = '255' AND `class_id` = '255' AND `stance_index` = '0'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2019_02_07_bots_stance_type_update.sql b/utils/sql/git/bots/required/2019_02_07_bots_stance_type_update.sql new file mode 100644 index 000000000..d2e3169fa --- /dev/null +++ b/utils/sql/git/bots/required/2019_02_07_bots_stance_type_update.sql @@ -0,0 +1,11 @@ +-- Update `bot_stances`.`stance_id` to new values +UPDATE `bot_stances` SET `stance_id` = '9' WHERE `stance_id` = '6'; +UPDATE `bot_stances` SET `stance_id` = '7' WHERE `stance_id` = '5'; +UPDATE `bot_stances` SET `stance_id` = (`stance_id` + 1) WHERE `stance_id` in (0,1,2,3,4); + +-- Update `bot_spell_casting_chances`.`stance_index` to new values +UPDATE `bot_spell_casting_chances` SET `stance_index` = '8' WHERE `stance_index` = '6'; +UPDATE `bot_spell_casting_chances` SET `stance_index` = '6' WHERE `stance_index` = '5'; + +-- Update `bot_spell_casting_chances` implicit versioning +UPDATE `bot_spell_casting_chances` SET `stance_index` = '1' WHERE `spell_type_index` = '255' AND `class_id` = '255'; diff --git a/zone/bot.cpp b/zone/bot.cpp index d7e330f6e..381701a51 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -163,7 +163,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to if (!stance_flag && bot_owner) bot_owner->Message(13, "Could not locate stance for '%s'", GetCleanName()); - SetTaunting((GetClass() == WARRIOR || GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) && (GetBotStance() == BotStanceAggressive)); + SetTaunting((GetClass() == WARRIOR || GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) && (GetBotStance() == EQEmu::constants::stanceAggressive)); SetPauseAI(false); rest_timer.Disable(); @@ -2766,7 +2766,7 @@ void Bot::AI_Process() { // we can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged TEST_TARGET(); - if (GetBotStance() == BotStancePassive || (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY()))) + if (GetBotStance() == EQEmu::constants::stancePassive || (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY()))) return; // First, special attack per class (kick, backstab etc..) @@ -2893,7 +2893,7 @@ void Bot::AI_Process() { FaceTarget(GetTarget()); // This is a mob that is fleeing either because it has been feared or is low on hitpoints - if (GetBotStance() != BotStancePassive) { + if (GetBotStance() != EQEmu::constants::stancePassive) { AI_PursueCastCheck(); // This appears to always return true..can't trust for success/fail return; } @@ -2901,7 +2901,7 @@ void Bot::AI_Process() { } // end not in combat range if (!IsMoving() && !spellend_timer.Enabled()) { // This may actually need work... - if (GetBotStance() == BotStancePassive) + if (GetBotStance() == EQEmu::constants::stancePassive) return; if (GetTarget() && AI_EngagedCastCheck()) @@ -2959,7 +2959,7 @@ void Bot::AI_Process() { // Ok to idle if (fm_dist <= GetFollowDistance()) { if (!IsMoving() && AI_think_timer->Check() && !spellend_timer.Enabled()) { - if (GetBotStance() != BotStancePassive) { + if (GetBotStance() != EQEmu::constants::stancePassive) { if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) BotMeditate(true); } @@ -3004,7 +3004,7 @@ void Bot::AI_Process() { // Basically, bard bots get a chance to cast idle spells while moving if (IsMoving()) { - if (GetBotStance() != BotStancePassive) { + if (GetBotStance() != EQEmu::constants::stancePassive) { if (GetClass() == BARD && !spellend_timer.Enabled() && AI_think_timer->Check()) { AI_IdleCastCheck(); return; @@ -8268,19 +8268,19 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl Group *g = caster->GetGroup(); float hpRatioToHeal = 25.0f; switch(caster->GetBotStance()) { - case BotStanceReactive: - case BotStanceBalanced: - hpRatioToHeal = 50.0f; - break; - case BotStanceBurn: - case BotStanceBurnAE: - hpRatioToHeal = 20.0f; - break; - case BotStanceAggressive: - case BotStanceEfficient: - default: - hpRatioToHeal = 25.0f; - break; + case EQEmu::constants::stanceReactive: + case EQEmu::constants::stanceBalanced: + hpRatioToHeal = 50.0f; + break; + case EQEmu::constants::stanceBurn: + case EQEmu::constants::stanceBurnAE: + hpRatioToHeal = 20.0f; + break; + case EQEmu::constants::stanceAggressive: + case EQEmu::constants::stanceEfficient: + default: + hpRatioToHeal = 25.0f; + break; } if(g) { @@ -8819,11 +8819,11 @@ bool Bot::HasOrMayGetAggro() { } void Bot::SetDefaultBotStance() { - BotStanceType defaultStance = BotStanceBalanced; + EQEmu::constants::StanceType defaultStance = EQEmu::constants::stanceBalanced; if (GetClass() == WARRIOR) - defaultStance = BotStanceAggressive; + defaultStance = EQEmu::constants::stanceAggressive; - _baseBotStance = BotStancePassive; + _baseBotStance = EQEmu::constants::stancePassive; _botStance = defaultStance; } @@ -9096,4 +9096,6 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) return saylink; } +uint8 Bot::spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_MAX][cntHSND] = { 0 }; + #endif diff --git a/zone/bot.h b/zone/bot.h index 55a8cba97..08b62b7b0 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -54,34 +54,6 @@ const int MaxDisciplineTimer = 10; const int DisciplineReuseStart = MaxSpellTimer + 1; const int MaxTimer = MaxSpellTimer + MaxDisciplineTimer; -enum BotStanceType { - BotStancePassive, - BotStanceBalanced, - BotStanceEfficient, - BotStanceReactive, - BotStanceAggressive, - BotStanceBurn, - BotStanceBurnAE, - BotStanceUnknown, - MaxStances = BotStanceUnknown -}; - -#define BOT_STANCE_COUNT 8 -#define VALIDBOTSTANCE(x) ((x >= (int)BotStancePassive && x <= (int)BotStanceBurnAE) ? ((BotStanceType)x) : (BotStanceUnknown)) - -static const std::string bot_stance_name[BOT_STANCE_COUNT] = { - "Passive", // 0 - "Balanced", // 1 - "Efficient", // 2 - "Reactive", // 3 - "Aggressive", // 4 - "Burn", // 5 - "BurnAE", // 6 - "Unknown" // 7 -}; - -static const char* GetBotStanceName(int stance_id) { return bot_stance_name[VALIDBOTSTANCE(stance_id)].c_str(); } - #define VALIDBOTEQUIPSLOT(x) ((x >= EQEmu::invslot::EQUIPMENT_BEGIN && x <= EQEmu::invslot::EQUIPMENT_END) ? (x) : (EQEmu::invslot::EQUIPMENT_COUNT)) static const std::string bot_equip_slot_name[EQEmu::invslot::EQUIPMENT_COUNT + 1] = @@ -519,7 +491,7 @@ public: virtual bool IsBot() const { return true; } bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; } BotRoleType GetBotRole() { return _botRole; } - BotStanceType GetBotStance() { return _botStance; } + EQEmu::constants::StanceType GetBotStance() { return _botStance; } uint8 GetChanceToCastBySpellType(uint32 spellType); bool IsGroupHealer() { return m_CastingRoles.GroupHealer; } @@ -633,7 +605,12 @@ public: // void SetBotOwnerCharacterID(uint32 botOwnerCharacterID) { _botOwnerCharacterID = botOwnerCharacterID; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == RANGER ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetBotRole(BotRoleType botRole) { _botRole = botRole; } - void SetBotStance(BotStanceType botStance) { _botStance = ((botStance != BotStanceUnknown) ? (botStance) : (BotStancePassive)); } + void SetBotStance(EQEmu::constants::StanceType botStance) { + if (botStance >= EQEmu::constants::stancePassive && botStance <= EQEmu::constants::stanceBurnAE) + _botStance = botStance; + else + _botStance = EQEmu::constants::stancePassive; + } void SetSpellRecastTimer(int timer_index, int32 recast_delay); void SetDisciplineRecastTimer(int timer_index, int32 recast_delay); void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} @@ -727,8 +704,8 @@ private: uint32 _lastZoneId; bool _rangerAutoWeaponSelect; BotRoleType _botRole; - BotStanceType _botStance; - BotStanceType _baseBotStance; + EQEmu::constants::StanceType _botStance; + EQEmu::constants::StanceType _baseBotStance; unsigned int RestRegenHP; unsigned int RestRegenMana; unsigned int RestRegenEndurance; @@ -792,6 +769,9 @@ private: bool LoadPet(); // Load and spawn bot pet if there is one bool SavePet(); // Save and depop bot pet if there is one bool DeletePet(); + + public: + static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_MAX][cntHSND]; }; #endif // BOTS diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 94b25b3ad..b12aa2d46 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -4249,7 +4249,7 @@ void bot_subcommand_bot_clone(Client *c, const Seperator *sep) return; } - int clone_stance = BotStancePassive; + int clone_stance = EQEmu::constants::stancePassive; 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)) @@ -5160,29 +5160,34 @@ 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_usage, "usage: %s [current | value: 1-9] ([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) + EQEmu::constants::stancePassive, EQEmu::constants::GetStanceName(EQEmu::constants::stancePassive), + EQEmu::constants::stanceBalanced, EQEmu::constants::GetStanceName(EQEmu::constants::stanceBalanced), + EQEmu::constants::stanceEfficient, EQEmu::constants::GetStanceName(EQEmu::constants::stanceEfficient), + EQEmu::constants::stanceReactive, EQEmu::constants::GetStanceName(EQEmu::constants::stanceReactive), + EQEmu::constants::stanceAggressive, EQEmu::constants::GetStanceName(EQEmu::constants::stanceAggressive), + EQEmu::constants::stanceAssist, EQEmu::constants::GetStanceName(EQEmu::constants::stanceAssist), + EQEmu::constants::stanceBurn, EQEmu::constants::GetStanceName(EQEmu::constants::stanceBurn), + EQEmu::constants::stanceEfficient2, EQEmu::constants::GetStanceName(EQEmu::constants::stanceEfficient2), + EQEmu::constants::stanceBurnAE, EQEmu::constants::GetStanceName(EQEmu::constants::stanceBurnAE) ); return; } int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); bool current_flag = false; - auto bst = BotStanceUnknown; + auto bst = EQEmu::constants::stanceUnknown; if (!strcasecmp(sep->arg[1], "current")) current_flag = true; - else if (sep->IsNumber(1)) - bst = VALIDBOTSTANCE(atoi(sep->arg[1])); + else if (sep->IsNumber(1)) { + bst = (EQEmu::constants::StanceType)atoi(sep->arg[1]); + if (bst < EQEmu::constants::stanceUnknown || bst > EQEmu::constants::stanceBurnAE) + bst = EQEmu::constants::stanceUnknown; + } - if (!current_flag && bst == BotStanceUnknown) { + if (!current_flag && bst == EQEmu::constants::stanceUnknown) { c->Message(m_fail, "A [current] argument or valid numeric [value] is required to use this command"); return; } @@ -5200,7 +5205,12 @@ void bot_subcommand_bot_stance(Client *c, const Seperator *sep) bot_iter->Save(); } - Bot::BotGroupSay(bot_iter, "My current stance is '%s' (%u)", GetBotStanceName(bot_iter->GetBotStance()), bot_iter->GetBotStance()); + Bot::BotGroupSay( + bot_iter, + "My current stance is '%s' (%i)", + EQEmu::constants::GetStanceName(bot_iter->GetBotStance()), + bot_iter->GetBotStance() + ); } } diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index f32ecafc4..651729223 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -83,12 +83,8 @@ bool BotDatabase::LoadBotCommandSettings(std::map= MaxStances) + if (stance_index >= EQEmu::constants::STANCE_TYPE_MAX) continue; for (uint8 conditional_index = nHSND; conditional_index < cntHSND; ++conditional_index) { @@ -136,7 +132,7 @@ bool BotDatabase::LoadBotSpellCastingChances() if (value > 100) value = 100; - spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index] = value; + Bot::spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index] = value; } } @@ -877,7 +873,7 @@ bool BotDatabase::LoadStance(Bot* bot_inst, bool& stance_flag) return true; auto row = results.begin(); - bot_inst->SetBotStance((BotStanceType)atoi(row[0])); + bot_inst->SetBotStance((EQEmu::constants::StanceType)atoi(row[0])); stance_flag = true; return true; @@ -2857,12 +2853,12 @@ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_ind return 0; if (class_index >= PLAYER_CLASS_COUNT) return 0; - if (stance_index >= MaxStances) + if (stance_index >= EQEmu::constants::STANCE_TYPE_MAX) return 0; if (conditional_index >= cntHSND) return 0; - return spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index]; + return Bot::spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index]; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 154574453..d660f2420 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -192,25 +192,24 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { else { float hpRatioToCast = 0.0f; - switch(this->GetBotStance()) - { - case BotStanceEfficient: - case BotStanceAggressive: - hpRatioToCast = isPrimaryHealer?90.0f:50.0f; - break; - case BotStanceBalanced: - hpRatioToCast = isPrimaryHealer?95.0f:75.0f; - break; - case BotStanceReactive: - hpRatioToCast = isPrimaryHealer?100.0f:90.0f; - break; - case BotStanceBurn: - case BotStanceBurnAE: - hpRatioToCast = isPrimaryHealer?75.0f:25.0f; - break; - default: - hpRatioToCast = isPrimaryHealer?100.0f:0.0f; - break; + switch(this->GetBotStance()) { + case EQEmu::constants::stanceEfficient: + case EQEmu::constants::stanceAggressive: + hpRatioToCast = isPrimaryHealer?90.0f:50.0f; + break; + case EQEmu::constants::stanceBalanced: + hpRatioToCast = isPrimaryHealer?95.0f:75.0f; + break; + case EQEmu::constants::stanceReactive: + hpRatioToCast = isPrimaryHealer?100.0f:90.0f; + break; + case EQEmu::constants::stanceBurn: + case EQEmu::constants::stanceBurnAE: + hpRatioToCast = isPrimaryHealer?75.0f:25.0f; + break; + default: + hpRatioToCast = isPrimaryHealer?100.0f:0.0f; + break; } //If we're at specified mana % or below, don't heal as hybrid @@ -381,23 +380,22 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { { float manaRatioToCast = 75.0f; - switch(this->GetBotStance()) - { - case BotStanceEfficient: - manaRatioToCast = 90.0f; - break; - case BotStanceBalanced: - case BotStanceAggressive: - manaRatioToCast = 75.0f; - break; - case BotStanceReactive: - case BotStanceBurn: - case BotStanceBurnAE: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; + switch(this->GetBotStance()) { + case EQEmu::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQEmu::constants::stanceBalanced: + case EQEmu::constants::stanceAggressive: + manaRatioToCast = 75.0f; + break; + case EQEmu::constants::stanceReactive: + case EQEmu::constants::stanceBurn: + case EQEmu::constants::stanceBurnAE: + manaRatioToCast = 50.0f; + break; + default: + manaRatioToCast = 75.0f; + break; } //If we're at specified mana % or below, don't rune as enchanter @@ -461,25 +459,24 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { { float manaRatioToCast = 75.0f; - switch(this->GetBotStance()) - { - case BotStanceEfficient: - manaRatioToCast = 90.0f; - break; - case BotStanceBalanced: - manaRatioToCast = 75.0f; - break; - case BotStanceReactive: - case BotStanceAggressive: - manaRatioToCast = 50.0f; - break; - case BotStanceBurn: - case BotStanceBurnAE: - manaRatioToCast = 25.0f; - break; - default: - manaRatioToCast = 50.0f; - break; + switch(this->GetBotStance()) { + case EQEmu::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQEmu::constants::stanceBalanced: + manaRatioToCast = 75.0f; + break; + case EQEmu::constants::stanceReactive: + case EQEmu::constants::stanceAggressive: + manaRatioToCast = 50.0f; + break; + case EQEmu::constants::stanceBurn: + case EQEmu::constants::stanceBurnAE: + manaRatioToCast = 25.0f; + break; + default: + manaRatioToCast = 50.0f; + break; } //If we're at specified mana % or below, don't nuke as cleric or enchanter @@ -1310,7 +1307,7 @@ bool Bot::AI_EngagedCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. uint8 botClass = GetClass(); - BotStanceType botStance = GetBotStance(); + EQEmu::constants::StanceType botStance = GetBotStance(); bool mayGetAggro = HasOrMayGetAggro(); Log(Logs::Detail, Logs::AI, "Engaged autocast check triggered (BOTS). Trying to cast healing spells then maybe offensive spells."); @@ -2653,11 +2650,13 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) return 0; --class_index; - uint8 stance_index = (uint8)GetBotStance(); - if (stance_index >= MaxStances) + EQEmu::constants::StanceType stance_type = GetBotStance(); + if (stance_type < EQEmu::constants::stancePassive || stance_type > EQEmu::constants::stanceBurnAE) return 0; + uint8 stance_index = EQEmu::constants::ConvertStanceTypeToIndex(stance_type); uint8 type_index = nHSND; + if (HasGroup()) { if (IsGroupHealer()/* || IsRaidHealer()*/) type_index |= pH; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 9e03a6e4e..10cd81d89 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -9394,7 +9394,7 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app) //check to see if selected option is a valid stance slot (option is the slot the stance is in, not the actual stance) if (option >= 0 && option < numStances) { - merc->SetStance(mercTemplate->Stances[option]); + merc->SetStance((EQEmu::constants::StanceType)mercTemplate->Stances[option]); GetMercInfo().Stance = mercTemplate->Stances[option]; Log(Logs::General, Logs::Mercenaries, "Set Stance: %u for %s (%s)", merc->GetStance(), merc->GetName(), GetName()); diff --git a/zone/merc.cpp b/zone/merc.cpp index 1825a23eb..e71667320 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -66,7 +66,7 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) memset(equipment, 0, sizeof(equipment)); SetMercID(0); - SetStance(MercStanceBalanced); + SetStance(EQEmu::constants::stanceBalanced); rest_timer.Disable(); if (GetClass() == ROGUE) @@ -3669,13 +3669,13 @@ MercSpell Merc::GetBestMercSpellForAENuke(Merc* caster, Mob* tar) { switch(caster->GetStance()) { - case MercStanceBurnAE: + case EQEmu::constants::stanceBurnAE: initialCastChance = 50; break; - case MercStanceBalanced: + case EQEmu::constants::stanceBalanced: initialCastChance = 25; break; - case MercStanceBurn: + case EQEmu::constants::stanceBurn: initialCastChance = 0; break; } @@ -3717,11 +3717,11 @@ MercSpell Merc::GetBestMercSpellForTargetedAENuke(Merc* caster, Mob* tar) { switch(caster->GetStance()) { - case MercStanceBurnAE: + case EQEmu::constants::stanceBurnAE: numTargetsCheck = 1; break; - case MercStanceBalanced: - case MercStanceBurn: + case EQEmu::constants::stanceBalanced: + case EQEmu::constants::stanceBurn: numTargetsCheck = 2; break; } @@ -3769,11 +3769,11 @@ MercSpell Merc::GetBestMercSpellForPBAENuke(Merc* caster, Mob* tar) { switch(caster->GetStance()) { - case MercStanceBurnAE: + case EQEmu::constants::stanceBurnAE: numTargetsCheck = 2; break; - case MercStanceBalanced: - case MercStanceBurn: + case EQEmu::constants::stanceBalanced: + case EQEmu::constants::stanceBurn: numTargetsCheck = 3; break; } @@ -3820,11 +3820,11 @@ MercSpell Merc::GetBestMercSpellForAERainNuke(Merc* caster, Mob* tar) { switch(caster->GetStance()) { - case MercStanceBurnAE: + case EQEmu::constants::stanceBurnAE: numTargetsCheck = 1; break; - case MercStanceBalanced: - case MercStanceBurn: + case EQEmu::constants::stanceBalanced: + case EQEmu::constants::stanceBurn: numTargetsCheck = 2; break; } @@ -5649,7 +5649,7 @@ void Client::SpawnMerc(Merc* merc, bool setMaxStats) { merc->SetSuspended(false); SetMerc(merc); merc->Unsuspend(setMaxStats); - merc->SetStance(GetMercInfo().Stance); + merc->SetStance((EQEmu::constants::StanceType)GetMercInfo().Stance); Log(Logs::General, Logs::Mercenaries, "SpawnMerc Success for %s.", GetName()); diff --git a/zone/merc.h b/zone/merc.h index cf6a14009..0fdc05f7a 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -30,18 +30,6 @@ namespace EQEmu const int MercAISpellRange = 100; // TODO: Write a method that calcs what the merc's spell range is based on spell, equipment, AA, whatever and replace this -enum MercStanceType { - MercStancePassive = 1, - MercStanceBalanced, - MercStanceEfficient, - MercStanceReactive, - MercStanceAggressive, - MercStanceAssist, - MercStanceBurn, - MercStanceEfficient2, - MercStanceBurnAE -}; - struct MercSpell { uint16 spellid; // <= 0 = no spell uint32 type; // 0 = never, must be one (and only one) of the defined values @@ -175,7 +163,7 @@ public: uint8 GetTierID() { return _TierID; } uint32 GetCostFormula() { return _CostFormula; } uint32 GetMercNameType() { return _NameType; } - uint32 GetStance() { return _currentStance; } + EQEmu::constants::StanceType GetStance() { return _currentStance; } int GetHatedCount() { return _hatedCount; } inline const uint8 GetClientVersion() const { return _OwnerClientVersion; } @@ -265,7 +253,7 @@ public: void SetMercNameType( uint8 nametype ) { _NameType = nametype; } void SetClientVersion(uint8 clientVersion) { _OwnerClientVersion = clientVersion; } void SetSuspended(bool suspended) { _suspended = suspended; } - void SetStance( uint32 stance ) { _currentStance = stance; } + void SetStance( EQEmu::constants::StanceType stance ) { _currentStance = stance; } void SetHatedCount( int count ) { _hatedCount = count; } void Sit(); @@ -385,7 +373,7 @@ private: uint8 _CostFormula; uint8 _NameType; uint8 _OwnerClientVersion; - uint32 _currentStance; + EQEmu::constants::StanceType _currentStance; EQEmu::InventoryProfile m_inv; int32 max_end; From 146e28f7087a7b5ee710add15da76cafd3be24a9 Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 7 Feb 2019 22:13:58 -0500 Subject: [PATCH 13/18] Updater criteria fix [ci skip] --- utils/sql/git/bots/bots_db_update_manifest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 48e9fa026..bb1cd70d9 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -20,7 +20,7 @@ 9019|2018_04_12_bots_stop_melee_level.sql|SHOW COLUMNS FROM `bot_data` LIKE 'stop_melee_level'|empty| 9020|2018_08_13_bots_inventory_update.sql|SELECT * FROM `inventory_versions` WHERE `version` = 2 and `bot_step` = 0|not_empty| 9021|2018_10_09_bots_owner_options.sql|SHOW TABLES LIKE 'bot_owner_options'|empty| -9022|2019_02_07_bots_stance_type_update.sql|SELECT * FROM `bot_spell_casting_chances` WHERE `spell_type_index` = '255' AND `class_id` = '255' AND `stance_index` = '0'|empty| +9022|2019_02_07_bots_stance_type_update.sql|SELECT * FROM `bot_spell_casting_chances` WHERE `spell_type_index` = '255' AND `class_id` = '255' AND `stance_index` = '0'|not_empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not From bef849b5c1ad91af75e969df385e489995df78c5 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 9 Feb 2019 05:58:49 -0500 Subject: [PATCH 14/18] Definition clean-up --- common/emu_constants.h | 4 +++- common/spdat.h | 16 ++++++++-------- zone/bot.cpp | 4 ++-- zone/bot.h | 2 +- zone/bot_database.cpp | 4 ++-- zone/merc.cpp | 2 +- zone/mob_ai.cpp | 4 ++-- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/common/emu_constants.h b/common/emu_constants.h index 970b2861d..ee8fdfcee 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -219,7 +219,9 @@ namespace EQEmu const char *GetStanceName(StanceType stance_type); int ConvertStanceTypeToIndex(StanceType stance_type); - const size_t STANCE_TYPE_MAX = stanceBurnAE; + const int STANCE_TYPE_FIRST = stancePassive; + const int STANCE_TYPE_LAST = stanceBurnAE; + const int STANCE_TYPE_COUNT = stanceBurnAE; } /*constants*/ diff --git a/common/spdat.h b/common/spdat.h index cb9a301d8..877d37823 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -68,16 +68,16 @@ enum SpellTypes : uint32 SpellType_InCombatBuffSong = (1 << 18), // bard in-combat group/ae buffs SpellType_OutOfCombatBuffSong = (1 << 19), // bard out-of-combat group/ae buffs SpellType_PreCombatBuff = (1 << 20), - SpellType_PreCombatBuffSong = (1 << 21), - - SpellTypes_Detrimental = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow), - SpellTypes_Beneficial = (SpellType_Heal | SpellType_Buff | SpellType_Escape | SpellType_Pet | SpellType_InCombatBuff | SpellType_Cure | SpellType_HateRedux | SpellType_InCombatBuffSong | SpellType_OutOfCombatBuffSong | SpellType_PreCombatBuff | SpellType_PreCombatBuffSong), - - SpellTypes_Innate = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root), - - SpellType_Any = 0xFFFFFFFF + SpellType_PreCombatBuffSong = (1 << 21) }; +const uint32 SPELL_TYPE_MIN = (SpellType_Nuke << 1) - 1; +const uint32 SPELL_TYPE_MAX = (SpellType_PreCombatBuffSong << 1) - 1; +const uint32 SPELL_TYPE_ANY = 0xFFFFFFFF; + +const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); +const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellType_Escape | SpellType_Pet | SpellType_InCombatBuff | SpellType_Cure | SpellType_HateRedux | SpellType_InCombatBuffSong | SpellType_OutOfCombatBuffSong | SpellType_PreCombatBuff | SpellType_PreCombatBuffSong); +const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only diff --git a/zone/bot.cpp b/zone/bot.cpp index 381701a51..e6dd36011 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -8213,7 +8213,7 @@ bool Bot::CheckLoreConflict(const EQEmu::ItemData* item) { } bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes&SpellTypes_Detrimental) != 0) { + if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { Log(Logs::General, Logs::Error, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); return false; } @@ -9096,6 +9096,6 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) return saylink; } -uint8 Bot::spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_MAX][cntHSND] = { 0 }; +uint8 Bot::spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; #endif diff --git a/zone/bot.h b/zone/bot.h index 08b62b7b0..8d99654a6 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -771,7 +771,7 @@ private: bool DeletePet(); public: - static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_MAX][cntHSND]; + static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND]; }; #endif // BOTS diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 651729223..15194270f 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -122,7 +122,7 @@ bool BotDatabase::LoadBotSpellCastingChances() continue; --class_index; uint8 stance_index = atoi(row[2]); - if (stance_index >= EQEmu::constants::STANCE_TYPE_MAX) + if (stance_index >= EQEmu::constants::STANCE_TYPE_COUNT) continue; for (uint8 conditional_index = nHSND; conditional_index < cntHSND; ++conditional_index) { @@ -2853,7 +2853,7 @@ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_ind return 0; if (class_index >= PLAYER_CLASS_COUNT) return 0; - if (stance_index >= EQEmu::constants::STANCE_TYPE_MAX) + if (stance_index >= EQEmu::constants::STANCE_TYPE_COUNT) return 0; if (conditional_index >= cntHSND) return 0; diff --git a/zone/merc.cpp b/zone/merc.cpp index e71667320..7c15fbd65 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1908,7 +1908,7 @@ bool Merc::AI_IdleCastCheck() { bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes&SpellTypes_Detrimental) != 0) { + if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { //according to live, you can buff and heal through walls... //now with PCs, this only applies if you can TARGET the target, but // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 37fbb7814..5a31cc12b 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -379,7 +379,7 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SpellTypes_Detrimental) != 0) { + if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { //according to live, you can buff and heal through walls... //now with PCs, this only applies if you can TARGET the target, but // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. @@ -2813,7 +2813,7 @@ DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) entry.max_hp = atoi(row[8]); // some spell types don't make much since to be priority 0, so fix that - if (!(entry.type & SpellTypes_Innate) && entry.priority == 0) + if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) entry.priority = 1; if (row[9]) From 3bdd6c20a511317dd64699a550c313fecf38a851 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 9 Feb 2019 06:38:26 -0500 Subject: [PATCH 15/18] Bot definition clean-up --- zone/bot.cpp | 2 +- zone/bot.h | 106 +++++++++++------------------------------- zone/bot_command.cpp | 10 ++-- zone/bot_database.cpp | 4 +- zone/botspellsai.cpp | 50 ++++++++++---------- 5 files changed, 61 insertions(+), 111 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index e6dd36011..b221620e2 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9096,6 +9096,6 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) return saylink; } -uint8 Bot::spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; +uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; #endif diff --git a/zone/bot.h b/zone/bot.h index 8d99654a6..602b2912d 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -54,63 +54,7 @@ const int MaxDisciplineTimer = 10; const int DisciplineReuseStart = MaxSpellTimer + 1; const int MaxTimer = MaxSpellTimer + MaxDisciplineTimer; -#define VALIDBOTEQUIPSLOT(x) ((x >= EQEmu::invslot::EQUIPMENT_BEGIN && x <= EQEmu::invslot::EQUIPMENT_END) ? (x) : (EQEmu::invslot::EQUIPMENT_COUNT)) -static const std::string bot_equip_slot_name[EQEmu::invslot::EQUIPMENT_COUNT + 1] = -{ - "Charm", // slotCharm - "Ear 1", // slotEar1 - "Head", // slotHead - "Face", // slotFace - "Ear 2", // slotEar2 - "Neck", // slotNeck - "Shoulders", // slotShoulders - "Arms", // slotArms - "Back", // slotBack - "Wrist 1", // slotWrist1 - "Wrist 2", // slotWrist2 - "Range", // slotRange - "Hands", // slotHands - "Primary", // slotPrimary - "Secondary", // slotSecondary - "Finger 1", // slotFinger1 - "Finger 2", // slotFinger2 - "Chest", // slotChest - "Legs", // slotLegs - "Feet", // slotFeet - "Waist", // slotWaist - "Power Source", // slotPowerSource - "Ammo", // slotAmmo - "Unknown" -}; - -static const char* GetBotEquipSlotName(int slot_id) { return bot_equip_slot_name[VALIDBOTEQUIPSLOT(slot_id)].c_str(); } - -enum SpellTypeIndex { - SpellType_NukeIndex, - SpellType_HealIndex, - SpellType_RootIndex, - SpellType_BuffIndex, - SpellType_EscapeIndex, - SpellType_PetIndex, - SpellType_LifetapIndex, - SpellType_SnareIndex, - SpellType_DOTIndex, - SpellType_DispelIndex, - SpellType_InCombatBuffIndex, - SpellType_MezIndex, - SpellType_CharmIndex, - SpellType_SlowIndex, - SpellType_DebuffIndex, - SpellType_CureIndex, - SpellType_ResurrectIndex, - SpellType_HateReduxIndex, - SpellType_InCombatBuffSongIndex, - SpellType_OutOfCombatBuffSongIndex, - SpellType_PreCombatBuffIndex, - SpellType_PreCombatBuffSongIndex, - MaxSpellTypes -}; // nHSND negative Healer/Slower/Nuker/Doter // pH positive Healer @@ -200,29 +144,35 @@ public: BotRoleRaidHealer }; - enum EqExpansions { // expansions are off..EQ should be '0' - ExpansionNone, - ExpansionEQ, - ExpansionRoK, - ExpansionSoV, - ExpansionSoL, - ExpansionPoP, - ExpansionLoY, - ExpansionLDoN, - ExpansionGoD, - ExpansionOoW, - ExpansionDoN, - ExpansionDoDH, - ExpansionPoR, - ExpansionTSS, - ExpansionSoF, - ExpansionSoD, - ExpansionUF, - ExpansionHoT, - ExpansionVoA, - ExpansionRoF + enum SpellTypeIndex : uint32 { + spellTypeIndexNuke, + spellTypeIndexHeal, + spellTypeIndexRoot, + spellTypeIndexBuff, + spellTypeIndexEscape, + spellTypeIndexPet, + spellTypeIndexLifetap, + spellTypeIndexSnare, + spellTypeIndexDot, + spellTypeIndexDispel, + spellTypeIndexInCombatBuff, + spellTypeIndexMez, + spellTypeIndexCharm, + spellTypeIndexSlow, + spellTypeIndexDebuff, + spellTypeIndexCure, + spellTypeIndexResurrect, + spellTypeIndexHateRedux, + spellTypeIndexInCombatBuffSong, + spellTypeIndexOutOfCombatBuffSong, + spellTypeIndexPreCombatBuff, + spellTypeIndexPreCombatBuffSong }; + static const uint32 SPELL_TYPE_FIRST = spellTypeIndexNuke; + static const uint32 SPELL_TYPE_LAST = spellTypeIndexPreCombatBuffSong; + static const uint32 SPELL_TYPE_COUNT = SPELL_TYPE_LAST + 1; + // Class Constructors Bot(NPCType *npcTypeData, Client* botOwner); Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData); @@ -771,7 +721,7 @@ private: bool DeletePet(); public: - static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND]; + static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND]; }; #endif // BOTS diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index b12aa2d46..085a730ea 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -7230,7 +7230,7 @@ void bot_subcommand_inventory_list(Client *c, const Seperator *sep) inst = my_bot->CastToBot()->GetBotItem(i); if (!inst || !inst->GetItem()) { - c->Message(m_message, "I need something for my %s (slot %i)", GetBotEquipSlotName(i), i); + c->Message(m_message, "I need something for my %s (slot %i)", EQEmu::invslot::GetInvPossessionsSlotName(i), i); continue; } @@ -7240,7 +7240,7 @@ void bot_subcommand_inventory_list(Client *c, const Seperator *sep) } linker.SetItemInst(inst); - c->Message(m_message, "Using %s in my %s (slot %i)", linker.GenerateLink().c_str(), GetBotEquipSlotName(i), i); + c->Message(m_message, "Using %s in my %s (slot %i)", linker.GenerateLink().c_str(), EQEmu::invslot::GetInvPossessionsSlotName(i), i); ++inventory_count; } @@ -7343,14 +7343,14 @@ void bot_subcommand_inventory_remove(Client *c, const Seperator *sep) case EQEmu::invslot::slotWaist: case EQEmu::invslot::slotPowerSource: case EQEmu::invslot::slotAmmo: - c->Message(m_message, "My %s is %s unequipped", GetBotEquipSlotName(slotId), ((itm) ? ("now") : ("already"))); + c->Message(m_message, "My %s is %s unequipped", EQEmu::invslot::GetInvPossessionsSlotName(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"))); + c->Message(m_message, "My %s are %s unequipped", EQEmu::invslot::GetInvPossessionsSlotName(slotId), ((itm) ? ("now") : ("already"))); break; default: c->Message(m_fail, "I'm soo confused..."); @@ -7393,7 +7393,7 @@ void bot_subcommand_inventory_window(Client *c, const Seperator *sep) item = inst->GetItem(); window_text.append(""); - window_text.append(GetBotEquipSlotName(i)); + window_text.append(EQEmu::invslot::GetInvPossessionsSlotName(i)); window_text.append(": "); if (item) { //window_text.append(""); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 15194270f..ea5154598 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -115,7 +115,7 @@ bool BotDatabase::LoadBotSpellCastingChances() for (auto row = results.begin(); row != results.end(); ++row) { uint8 spell_type_index = atoi(row[0]); - if (spell_type_index >= MaxSpellTypes) + if (spell_type_index >= Bot::SPELL_TYPE_COUNT) continue; uint8 class_index = atoi(row[1]); if (class_index < WARRIOR || class_index > BERSERKER) @@ -2849,7 +2849,7 @@ bool BotDatabase::DeleteAllHealRotations(const uint32 owner_id) /* Bot miscellaneous functions */ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_index, uint8 stance_index, uint8 conditional_index) // class_index is 0-based { - if (spell_type_index >= MaxSpellTypes) + if (spell_type_index >= Bot::SPELL_TYPE_COUNT) return 0; if (class_index >= PLAYER_CLASS_COUNT) return 0; diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index d660f2420..5049f934d 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2570,79 +2570,79 @@ bool Bot::CheckDisciplineRecastTimers(Bot *caster, int timer_index) { uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) { - uint8 spell_type_index = MaxSpellTypes; + uint8 spell_type_index = SPELL_TYPE_COUNT; switch (spellType) { case SpellType_Nuke: - spell_type_index = SpellType_NukeIndex; + spell_type_index = spellTypeIndexNuke; break; case SpellType_Heal: - spell_type_index = SpellType_HealIndex; + spell_type_index = spellTypeIndexHeal; break; case SpellType_Root: - spell_type_index = SpellType_RootIndex; + spell_type_index = spellTypeIndexRoot; break; case SpellType_Buff: - spell_type_index = SpellType_BuffIndex; + spell_type_index = spellTypeIndexBuff; break; case SpellType_Escape: - spell_type_index = SpellType_EscapeIndex; + spell_type_index = spellTypeIndexEscape; break; case SpellType_Pet: - spell_type_index = SpellType_PetIndex; + spell_type_index = spellTypeIndexPet; break; case SpellType_Lifetap: - spell_type_index = SpellType_LifetapIndex; + spell_type_index = spellTypeIndexLifetap; break; case SpellType_Snare: - spell_type_index = SpellType_SnareIndex; + spell_type_index = spellTypeIndexSnare; break; case SpellType_DOT: - spell_type_index = SpellType_DOTIndex; + spell_type_index = spellTypeIndexDot; break; case SpellType_Dispel: - spell_type_index = SpellType_DispelIndex; + spell_type_index = spellTypeIndexDispel; break; case SpellType_InCombatBuff: - spell_type_index = SpellType_InCombatBuffIndex; + spell_type_index = spellTypeIndexInCombatBuff; break; case SpellType_Mez: - spell_type_index = SpellType_MezIndex; + spell_type_index = spellTypeIndexMez; break; case SpellType_Charm: - spell_type_index = SpellType_CharmIndex; + spell_type_index = spellTypeIndexCharm; break; case SpellType_Slow: - spell_type_index = SpellType_SlowIndex; + spell_type_index = spellTypeIndexSlow; break; case SpellType_Debuff: - spell_type_index = SpellType_DebuffIndex; + spell_type_index = spellTypeIndexDebuff; break; case SpellType_Cure: - spell_type_index = SpellType_CureIndex; + spell_type_index = spellTypeIndexCure; break; case SpellType_Resurrect: - spell_type_index = SpellType_ResurrectIndex; + spell_type_index = spellTypeIndexResurrect; break; case SpellType_HateRedux: - spell_type_index = SpellType_HateReduxIndex; + spell_type_index = spellTypeIndexHateRedux; break; case SpellType_InCombatBuffSong: - spell_type_index = SpellType_InCombatBuffSongIndex; + spell_type_index = spellTypeIndexInCombatBuffSong; break; case SpellType_OutOfCombatBuffSong: - spell_type_index = SpellType_OutOfCombatBuffSongIndex; + spell_type_index = spellTypeIndexOutOfCombatBuffSong; break; case SpellType_PreCombatBuff: - spell_type_index = SpellType_PreCombatBuffIndex; + spell_type_index = spellTypeIndexPreCombatBuff; break; case SpellType_PreCombatBuffSong: - spell_type_index = SpellType_PreCombatBuffSongIndex; + spell_type_index = spellTypeIndexPreCombatBuffSong; break; default: - spell_type_index = MaxSpellTypes; + spell_type_index = SPELL_TYPE_COUNT; break; } - if (spell_type_index >= MaxSpellTypes) + if (spell_type_index >= SPELL_TYPE_COUNT) return 0; uint8 class_index = GetClass(); From 0b4dcb4271090331afecb315c0f6d2809f067879 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 11 Feb 2019 20:03:02 -0500 Subject: [PATCH 16/18] Reworked command and quest api 'scribespells' methods --- zone/command.cpp | 118 ++++++++++++++++++++++++++++------------------ zone/questmgr.cpp | 76 ++++++++++++++++++----------- 2 files changed, 122 insertions(+), 72 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index d73c760ed..5d1d9eb5e 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6407,26 +6407,25 @@ void command_beardcolor(Client *c, const Seperator *sep) void command_scribespells(Client *c, const Seperator *sep) { - uint8 max_level, min_level; - uint16 book_slot, curspell, count; - Client *t=c; + // rewrote this command to test for possible type conversion issues + // most of the redundant checks can be removed if proven successful - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); + Client *t = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) + t = c->GetTarget()->CastToClient(); - if(!sep->arg[1][0]) - { + if(sep->argnum < 1 || !sep->IsNumber(1)) { c->Message(0, "FORMAT: #scribespells "); return; } - max_level = (uint8)atoi(sep->arg[1]); - if (!c->GetGM() && max_level > RuleI(Character, MaxLevel)) - max_level = RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level - min_level = sep->arg[2][0] ? (uint8)atoi(sep->arg[2]) : 1; //default to 1 if there isn't a 2nd argument - if (!c->GetGM() && min_level > RuleI(Character, MaxLevel)) - min_level = RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level + uint8 max_level = (uint8)atol(sep->arg[1]); + if (!c->GetGM() && max_level > (uint8)RuleI(Character, MaxLevel)) + max_level = (uint8)RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level + uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); //default to 1 if there isn't a 2nd argument + if (!c->GetGM() && min_level > (uint8)RuleI(Character, MaxLevel)) + min_level = (uint8)RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level if(max_level < 1 || min_level < 1) { @@ -6434,7 +6433,7 @@ void command_scribespells(Client *c, const Seperator *sep) return; } if (min_level > max_level) { - c->Message(0, "Error: Min Level must be less than or equal to Max Level."); + c->Message(0, "ERROR: Min Level must be less than or equal to Max Level."); return; } @@ -6443,42 +6442,71 @@ void command_scribespells(Client *c, const Seperator *sep) c->Message(0, "Scribing spells for %s.", t->GetName()); Log(Logs::General, Logs::Normal, "Scribe spells request for %s from %s, levels: %u -> %u", t->GetName(), c->GetName(), min_level, max_level); - for ( - curspell = 0, - book_slot = t->GetNextAvailableSpellBookSlot(), - count = 0; // ; - curspell < SPDAT_RECORDS && - book_slot < EQEmu::spells::SPELLBOOK_SIZE; // ; - curspell++, - book_slot = t->GetNextAvailableSpellBookSlot(book_slot) - ) - { - if - ( - spells[curspell].classes[WARRIOR] != 0 && // check if spell exists - spells[curspell].classes[t->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[t->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 - ) - { - if (book_slot == -1) { //no more book slots - t->Message(13, "Unable to scribe spell %s (%u) to spellbook: no more spell book slots available.", spells[curspell].name, curspell); - if (t != c) - c->Message(13, "Error scribing spells: %s ran out of spell book slots on spell %s (%u)", t->GetName(), spells[curspell].name, curspell); - break; - } - if(!IsDiscipline(curspell) && !t->HasSpellScribed(curspell)) { //isn't a discipline & we don't already have it scribed - t->ScribeSpell(curspell, book_slot); - count++; - } + int book_slot = t->GetNextAvailableSpellBookSlot(); + int spell_id = 0; + int count = 0; + + for ( ; spell_id < SPDAT_RECORDS && book_slot < EQEmu::spells::SPELLBOOK_SIZE; ++spell_id) { + if (book_slot == -1) { + t->Message( + 13, + "Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.", + ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), + spell_id + ); + if (t != c) + c->Message( + 13, + "Error scribing spells: %s ran out of spell book slots on spell %s (%i)", + t->GetName(), + ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), + spell_id + ); + + break; } + if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { + c->Message(13, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); + return; + } + if (book_slot < 0 || book_slot >= EQEmu::spells::SPELLBOOK_SIZE) { + c->Message(13, "FATAL ERROR: Book slot out-of-range (slot: %i, min: 0, max: %i)", book_slot, EQEmu::spells::SPELLBOOK_SIZE); + return; + } + + while (true) { + if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists + break; + if (spells[spell_id].classes[t->GetPP().class_ - 1] > max_level) // maximum level + break; + if (spells[spell_id].classes[t->GetPP().class_ - 1] < min_level) // minimum level + break; + if (spells[spell_id].skill == 52) + break; + + uint16 spell_id_ = (uint16)spell_id; + if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { + c->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%u != %i)", spell_id, spell_id_); + return; + } + + if (!IsDiscipline(spell_id_) && !t->HasSpellScribed(spell_id)) { // isn't a discipline & we don't already have it scribed + t->ScribeSpell(spell_id_, book_slot); + ++count; + } + + break; + } + + book_slot = t->GetNextAvailableSpellBookSlot(book_slot); } if (count > 0) { - t->Message(0, "Successfully scribed %u spells.", count); + t->Message(0, "Successfully scribed %i spells.", count); if (t != c) - c->Message(0, "Successfully scribed %u spells for %s.", count, t->GetName()); - } else { + c->Message(0, "Successfully scribed %i spells for %s.", count, t->GetName()); + } + else { t->Message(0, "No spells scribed."); if (t != c) c->Message(0, "No spells scribed for %s.", t->GetName()); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index cd6c048cd..ffb3bdb34 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -974,38 +974,53 @@ void QuestManager::permagender(int gender_id) { } uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { + // rewrote this handler to test for possible type conversion issues + // most of the redundant checks can be removed if proven successful + QuestManagerCurrentQuestVars(); - uint16 book_slot, count; - uint16 spell_id; + int book_slot = initiator->GetNextAvailableSpellBookSlot(); + int spell_id = 0; + int count = 0; uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); - bool SpellGlobalCheckResult = 0; - bool SpellBucketCheckResult = 0; + bool SpellGlobalCheckResult = false; + bool SpellBucketCheckResult = false; - for ( - spell_id = 0, - book_slot = initiator->GetNextAvailableSpellBookSlot(), - count = 0; // ; - spell_id < SPDAT_RECORDS && - book_slot < EQEmu::spells::SPELLBOOK_SIZE; // ; - spell_id++, - book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot) - ) - { - if - ( - spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists - spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[spell_id].skill != 52 && - spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 - ) - { - if (book_slot == -1) //no more book slots + for ( ; spell_id < SPDAT_RECORDS && book_slot < EQEmu::spells::SPELLBOOK_SIZE; ++spell_id) { + if (book_slot == -1) { + initiator->Message( + 13, + "Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.", + ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), + spell_id + ); + + break; + } + if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { + initiator->Message(13, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); + return count; + } + if (book_slot < 0 || book_slot >= EQEmu::spells::SPELLBOOK_SIZE) { + initiator->Message(13, "FATAL ERROR: Book slot out-of-range (slot: %i, min: 0, max: %i)", book_slot, EQEmu::spells::SPELLBOOK_SIZE); + return count; + } + + while (true) { + if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists break; - if(!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed + if (spells[spell_id].classes[initiator->GetPP().class_ - 1] > max_level) // maximum level + break; + if (spells[spell_id].classes[initiator->GetPP().class_ - 1] < min_level) // minimum level + break; + if (spells[spell_id].skill == 52) + break; + if (spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + break; + + if (!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); @@ -1013,19 +1028,26 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { initiator->ScribeSpell(spell_id, book_slot); count++; } - } else if (SpellBucketRule) { + } + else if (SpellBucketRule) { SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); if (SpellBucketCheckResult) { initiator->ScribeSpell(spell_id, book_slot); count++; } - } else { + } + else { initiator->ScribeSpell(spell_id, book_slot); count++; } } + + break; } + + book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot); } + return count; //how many spells were scribed successfully } From 43a488d5b5927ef5076d109acb8dddfbe18033c7 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 11 Feb 2019 21:46:20 -0500 Subject: [PATCH 17/18] Added type conversion to questmgr 'scribespells' --- zone/questmgr.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index ffb3bdb34..c024c00bd 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1020,24 +1020,30 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { if (spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) break; - if (!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed + uint16 spell_id_ = (uint16)spell_id; + if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { + initiator->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%u != %i)", spell_id, spell_id_); + return count; + } + + if (!IsDiscipline(spell_id_) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id_, char_id); if (SpellGlobalCheckResult) { - initiator->ScribeSpell(spell_id, book_slot); + initiator->ScribeSpell(spell_id_, book_slot); count++; } } else if (SpellBucketRule) { - SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id_, char_id); if (SpellBucketCheckResult) { - initiator->ScribeSpell(spell_id, book_slot); + initiator->ScribeSpell(spell_id_, book_slot); count++; } } else { - initiator->ScribeSpell(spell_id, book_slot); + initiator->ScribeSpell(spell_id_, book_slot); count++; } } From 2af4d3d67ddbd57106e5073260ab1c361749638e Mon Sep 17 00:00:00 2001 From: Uleat Date: Wed, 13 Feb 2019 07:55:04 -0500 Subject: [PATCH 18/18] Reworked command and quest api 'traindisc' methods --- zone/command.cpp | 115 +++++++++++++++++++---------------- zone/questmgr.cpp | 150 ++++++++++++++++++++++++++-------------------- 2 files changed, 150 insertions(+), 115 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 5d1d9eb5e..149c970d2 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6407,9 +6407,6 @@ void command_beardcolor(Client *c, const Seperator *sep) void command_scribespells(Client *c, const Seperator *sep) { - // rewrote this command to test for possible type conversion issues - // most of the redundant checks can be removed if proven successful - Client *t = c; if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) t = c->GetTarget()->CastToClient(); @@ -6421,14 +6418,13 @@ void command_scribespells(Client *c, const Seperator *sep) uint8 max_level = (uint8)atol(sep->arg[1]); if (!c->GetGM() && max_level > (uint8)RuleI(Character, MaxLevel)) - max_level = (uint8)RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level + max_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); //default to 1 if there isn't a 2nd argument + uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); // default to 1 if there isn't a 2nd argument if (!c->GetGM() && min_level > (uint8)RuleI(Character, MaxLevel)) - min_level = (uint8)RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level + min_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - if(max_level < 1 || min_level < 1) - { + if(max_level < 1 || min_level < 1) { c->Message(0, "ERROR: Level must be greater than 1."); return; } @@ -6486,7 +6482,7 @@ void command_scribespells(Client *c, const Seperator *sep) uint16 spell_id_ = (uint16)spell_id; if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - c->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%u != %i)", spell_id, spell_id_); + c->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); return; } @@ -8762,28 +8758,24 @@ void command_reloadtitles(Client *c, const Seperator *sep) void command_traindisc(Client *c, const Seperator *sep) { - uint8 max_level, min_level; - uint16 curspell, count; - Client *t=c; + Client *t = c; + if (c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) + t = c->GetTarget()->CastToClient(); - if(c->GetTarget() && c->GetTarget()->IsClient() && c->GetGM()) - t=c->GetTarget()->CastToClient(); - - if(!sep->arg[1][0]) - { + if (sep->argnum < 1 || !sep->IsNumber(1)) { c->Message(0, "FORMAT: #traindisc "); return; } - max_level = (uint8)atoi(sep->arg[1]); - if (!c->GetGM() && max_level > RuleI(Character, MaxLevel)) - max_level = RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level - min_level = sep->arg[2][0] ? (uint8)atoi(sep->arg[2]) : 1; //default to 1 if there isn't a 2nd argument - if (!c->GetGM() && min_level > RuleI(Character, MaxLevel)) - min_level = RuleI(Character, MaxLevel); //default to Character:MaxLevel if we're not a GM & it's higher than the max level + uint8 max_level = (uint8)atol(sep->arg[1]); + if (!c->GetGM() && max_level >(uint8)RuleI(Character, MaxLevel)) + max_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level - if(max_level < 1 || min_level < 1) - { + uint8 min_level = (sep->IsNumber(2) ? (uint8)atol(sep->arg[2]) : 1); // default to 1 if there isn't a 2nd argument + if (!c->GetGM() && min_level > (uint8)RuleI(Character, MaxLevel)) + min_level = (uint8)RuleI(Character, MaxLevel); // default to Character:MaxLevel if we're not a GM & it's higher than the max level + + if(max_level < 1 || min_level < 1) { c->Message(0, "ERROR: Level must be greater than 1."); return; } @@ -8797,35 +8789,58 @@ void command_traindisc(Client *c, const Seperator *sep) c->Message(0, "Training disciplines for %s.", t->GetName()); Log(Logs::General, Logs::Normal, "Train disciplines request for %s from %s, levels: %u -> %u", t->GetName(), c->GetName(), min_level, max_level); - for(curspell = 0, count = 0; curspell < SPDAT_RECORDS; curspell++) - { - if - ( - spells[curspell].classes[WARRIOR] != 0 && // check if spell exists - spells[curspell].classes[t->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[t->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 - ) - { - if(IsDiscipline(curspell)){ - //we may want to come up with a function like Client::GetNextAvailableSpellBookSlot() to help speed this up a little - for(int r = 0; r < MAX_PP_DISCIPLINES; r++) { - if(t->GetPP().disciplines.values[r] == curspell) { - t->Message(13, "You already know this discipline."); - break; //continue the 1st loop - } else if(t->GetPP().disciplines.values[r] == 0) { - t->GetPP().disciplines.values[r] = curspell; - database.SaveCharacterDisc(t->CharacterID(), r, curspell); - t->SendDisciplineUpdate(); - t->Message(0, "You have learned a new discipline!"); - count++; //success counter - break; //continue the 1st loop - } //if we get to this point, there's already a discipline in this slot, so we continue onto the next slot - } + int spell_id = 0; + int count = 0; + + bool change = false; + + for( ; spell_id < SPDAT_RECORDS; ++spell_id) { + if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { + c->Message(13, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); + return; + } + + while (true) { + if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists + break; + if (spells[spell_id].classes[t->GetPP().class_ - 1] > max_level) // maximum level + break; + if (spells[spell_id].classes[t->GetPP().class_ - 1] < min_level) // minimum level + break; + if (spells[spell_id].skill == 52) + break; + + uint16 spell_id_ = (uint16)spell_id; + if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { + c->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); + return; } + + if (!IsDiscipline(spell_id_)) + break; + + for (uint32 r = 0; r < MAX_PP_DISCIPLINES; ++r) { + if (t->GetPP().disciplines.values[r] == spell_id_) { + t->Message(13, "You already know this discipline."); + break; // continue the 1st loop + } + else if (t->GetPP().disciplines.values[r] == 0) { + t->GetPP().disciplines.values[r] = spell_id_; + database.SaveCharacterDisc(t->CharacterID(), r, spell_id_); + change = true; + t->Message(0, "You have learned a new discipline!"); + ++count; // success counter + break; // continue the 1st loop + } // if we get to this point, there's already a discipline in this slot, so we continue onto the next slot + } + + break; } } + if (change) + t->SendDisciplineUpdate(); + if (count > 0) { t->Message(0, "Successfully trained %u disciplines.", count); if (t != c) diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index c024c00bd..fb175b1f6 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -974,9 +974,6 @@ void QuestManager::permagender(int gender_id) { } uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { - // rewrote this handler to test for possible type conversion issues - // most of the redundant checks can be removed if proven successful - QuestManagerCurrentQuestVars(); int book_slot = initiator->GetNextAvailableSpellBookSlot(); int spell_id = 0; @@ -1022,29 +1019,30 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { uint16 spell_id_ = (uint16)spell_id; if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - initiator->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%u != %i)", spell_id, spell_id_); + initiator->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); return count; } - if (!IsDiscipline(spell_id_) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed + if (!IsDiscipline(spell_id_) && !initiator->HasSpellScribed(spell_id)) { // isn't a discipline & we don't already have it scribed if (SpellGlobalRule) { - // Bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table + // bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id_, char_id); if (SpellGlobalCheckResult) { initiator->ScribeSpell(spell_id_, book_slot); - count++; + ++count; } } else if (SpellBucketRule) { + // bool to see if the character has the required bucket to train it if one exists in the spell_buckets table SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id_, char_id); if (SpellBucketCheckResult) { initiator->ScribeSpell(spell_id_, book_slot); - count++; + ++count; } } else { initiator->ScribeSpell(spell_id_, book_slot); - count++; + ++count; } } @@ -1054,82 +1052,104 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot); } - return count; //how many spells were scribed successfully + return count; // how many spells were scribed successfully } uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); - uint16 count; - uint16 spell_id; + int spell_id = 0; + int count = 0; uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); - bool SpellGlobalCheckResult = 0; - bool SpellBucketCheckResult = 0; + bool SpellGlobalCheckResult = false; + bool SpellBucketCheckResult = false; - for(spell_id = 0, count = 0; spell_id < SPDAT_RECORDS; spell_id++) - { - if - ( - spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists - spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[spell_id].skill != 52 && - ( !RuleB(Spells, UseCHAScribeHack) || spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 ) - ) - { - if(IsDiscipline(spell_id)){ - //we may want to come up with a function like Client::GetNextAvailableSpellBookSlot() to help speed this up a little - for(uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) { - if(initiator->GetPP().disciplines.values[r] == spell_id) { - initiator->Message(13, "You already know this discipline."); - break; //continue the 1st loop - } - else if(initiator->GetPP().disciplines.values[r] == 0) { - if (SpellGlobalRule) { - // Bool to see if the character has the required QGlobal to train it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); - if (SpellGlobalCheckResult) { - initiator->GetPP().disciplines.values[r] = spell_id; - database.SaveCharacterDisc(char_id, r, spell_id); - initiator->SendDisciplineUpdate(); - initiator->Message(0, "You have learned a new discipline!"); - count++; //success counter - } - break; //continue the 1st loop - } else if (SpellBucketRule) { - // Bool to see if the character has the required bucket to train it if one exists in the spell_buckets table - SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); - if (SpellBucketCheckResult) { - initiator->GetPP().disciplines.values[r] = spell_id; - database.SaveCharacterDisc(char_id, r, spell_id); - initiator->SendDisciplineUpdate(); - initiator->Message(0, "You have learned a new discipline!"); - count++; - } - break; - } - else { - initiator->GetPP().disciplines.values[r] = spell_id; - database.SaveCharacterDisc(char_id, r, spell_id); - initiator->SendDisciplineUpdate(); + bool change = false; + + for( ; spell_id < SPDAT_RECORDS; ++spell_id) { + if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { + initiator->Message(13, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); + return count; + } + + while (true) { + if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists + break; + if (spells[spell_id].classes[initiator->GetPP().class_ - 1] > max_level) // maximum level + break; + if (spells[spell_id].classes[initiator->GetPP().class_ - 1] < min_level) // minimum level + break; + if (spells[spell_id].skill == 52) + break; + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + break; + + uint16 spell_id_ = (uint16)spell_id; + if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { + initiator->Message(13, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); + return count; + } + + if (!IsDiscipline(spell_id_)) + break; + + for (uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) { + if (initiator->GetPP().disciplines.values[r] == spell_id_) { + initiator->Message(13, "You already know this discipline."); + break; // continue the 1st loop + } + else if (initiator->GetPP().disciplines.values[r] == 0) { + if (SpellGlobalRule) { + // bool to see if the character has the required QGlobal to train it if one exists in the Spell_Globals table + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id_, char_id); + if (SpellGlobalCheckResult) { + initiator->GetPP().disciplines.values[r] = spell_id_; + database.SaveCharacterDisc(char_id, r, spell_id_); + change = true; initiator->Message(0, "You have learned a new discipline!"); - count++; //success counter - break; //continue the 1st loop + ++count; // success counter } - } //if we get to this point, there's already a discipline in this slot, so we skip it + break; // continue the 1st loop + } + else if (SpellBucketRule) { + // bool to see if the character has the required bucket to train it if one exists in the spell_buckets table + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id_, char_id); + if (SpellBucketCheckResult) { + initiator->GetPP().disciplines.values[r] = spell_id_; + database.SaveCharacterDisc(char_id, r, spell_id_); + change = true; + initiator->Message(0, "You have learned a new discipline!"); + ++count; + } + break; + } + else { + initiator->GetPP().disciplines.values[r] = spell_id_; + database.SaveCharacterDisc(char_id, r, spell_id_); + change = true;; + initiator->Message(0, "You have learned a new discipline!"); + ++count; // success counter + break; // continue the 1st loop + } } } + + break; } } - return count; //how many disciplines were learned successfully + + if (change) + initiator->SendDisciplineUpdate(); + + return count; // how many disciplines were learned successfully } void QuestManager::unscribespells() { QuestManagerCurrentQuestVars(); initiator->UnscribeSpellAll(); - } +} void QuestManager::untraindiscs() { QuestManagerCurrentQuestVars();