Merge branch 'master' into resend

This commit is contained in:
Akkadius 2019-02-17 03:25:55 -06:00
commit 3a8f206841
49 changed files with 1604 additions and 767 deletions

View File

@ -1,6 +1,52 @@
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)
- Five options are available under this command..
-- 'list' - shows the current list of banned words
-- 'clear' - clears the current list of banned words
-- 'add <word>' - adds <word> to the banned word list
-- 'del <word>' - deletes <word> 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
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
- Fixes bots not moving on spawn/grouping issue
- Report any issues with non movement-related behavior
== 1/24/2019 ==
Uleat: Extended server spellbook entries to RoF2 standard and added per-client restriction of spell id max
- Bumped server spellbook entry capacity to 720 spells
- Server keeps all 'learned' spells as found
-- Access is limited by the clients' limitations of spellbook capacities and max spell ids
-- This is done to avoid losing spells by switching from newer clients to older ones
-- Existing behavior is kept in place for illegal access conditions
- Each client is still restricted to its spellbook capacity (400, 480, 480, 720, 720, 720 - respectively)
- Each client is restricted to its max supported spell id (9999, 15999, 23000, 28000, 45000, 45000 - respectively)
- Please report any abnormal behavior so it may be addressed
Uleat: Removed server-side checksum of player profile..wasted calculation since it's performed again in all translators
== 1/20/2019 ==
Uleat: Added 'spells' entry to EQDictionary
Akkadius:

View File

@ -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
@ -180,6 +181,7 @@ SET(common_headers
packet_functions.h
platform.h
proc_launcher.h
profanity_manager.h
profiler.h
ptimer.h
queue.h

View File

@ -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;
}

View File

@ -203,6 +203,26 @@ 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 int STANCE_TYPE_FIRST = stancePassive;
const int STANCE_TYPE_LAST = stanceBurnAE;
const int STANCE_TYPE_COUNT = stanceBurnAE;
} /*constants*/
namespace profile {
@ -243,7 +263,7 @@ namespace EQEmu
};
using RoF2::spells::SPELL_ID_MAX;
using SoD::spells::SPELLBOOK_SIZE;
using RoF2::spells::SPELLBOOK_SIZE;
using UF::spells::SPELL_GEM_COUNT; // RoF+ clients define more than UF client..but, they are not valid beyond UF
using RoF2::spells::LONG_BUFFS;

View File

@ -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;
}

View File

@ -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*/

View File

@ -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
),
@ -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,

View File

@ -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*/

View File

@ -909,7 +909,7 @@ sed -e 's/_t//g' -e 's/MAX_AA/MAX_PP_AA_ARRAY/g' \
struct PlayerProfile_Struct
{
/*0000*/ uint32 checksum; // Checksum from CRC32::SetEQChecksum
// /*0000*/ uint32 checksum; // Checksum from CRC32::SetEQChecksum
/*0004*/ char name[64]; // Name of player sizes not right
/*0068*/ char last_name[32]; // Last name of player sizes not right
/*0100*/ uint32 gender; // Player Gender - 0 Male, 1 Female
@ -1091,6 +1091,18 @@ struct PlayerProfile_Struct
/*19559*/ uint8 unknown19595[5]; // ***Placeholder (6/29/2005)
/*19564*/ uint32 RestTimer;
/*19568*/
// All player profile packets are translated and this overhead is ignored in out-bound packets
PlayerProfile_Struct() : m_player_profile_version(EQEmu::versions::MobVersion::Unknown) { }
EQEmu::versions::MobVersion PlayerProfileVersion() { return m_player_profile_version; }
void SetPlayerProfileVersion(EQEmu::versions::MobVersion mob_version) { m_player_profile_version = EQEmu::versions::ValidateMobVersion(mob_version); }
void SetPlayerProfileVersion(EQEmu::versions::ClientVersion client_version) { SetPlayerProfileVersion(EQEmu::versions::ConvertClientVersionToMobVersion(client_version)); }
private:
// No need for gm flag since pp already has one
// No need for lookup pointer since this struct is not tied to any one system
EQEmu::versions::MobVersion m_player_profile_version;
};

View File

@ -2126,15 +2126,26 @@ namespace RoF
outapp->WriteUInt32(spells::SPELLBOOK_SIZE); // Spellbook slots
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++)
{
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
outapp->WriteUInt32(emu->spell_book[r]);
}
// zeroes for the rest of the spellbook slots
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE; r++)
{
else
outapp->WriteUInt32(0xFFFFFFFFU);
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
outapp->WriteUInt32(emu->spell_book[r]);
else
outapp->WriteUInt32(0xFFFFFFFFU);
}
// invalidate the rest of the spellbook slots
for (uint32 r = EQEmu::spells::SPELLBOOK_SIZE; r < spells::SPELLBOOK_SIZE; r++) {
outapp->WriteUInt32(0xFFFFFFFFU);
}
}
outapp->WriteUInt32(spells::SPELL_GEM_COUNT); // Memorised spell slots

View File

@ -2202,15 +2202,26 @@ namespace RoF2
outapp->WriteUInt32(spells::SPELLBOOK_SIZE); // Spellbook slots
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++)
{
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
outapp->WriteUInt32(emu->spell_book[r]);
}
// zeroes for the rest of the spellbook slots
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE; r++)
{
else
outapp->WriteUInt32(0xFFFFFFFFU);
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
outapp->WriteUInt32(emu->spell_book[r]);
else
outapp->WriteUInt32(0xFFFFFFFFU);
}
// invalidate the rest of the spellbook slots
for (uint32 r = EQEmu::spells::SPELLBOOK_SIZE; r < spells::SPELLBOOK_SIZE; r++) {
outapp->WriteUInt32(0xFFFFFFFFU);
}
}
outapp->WriteUInt32(spells::SPELL_GEM_COUNT); // Memorised spell slots

View File

@ -1492,7 +1492,26 @@ namespace SoD
OUT(WIS);
OUT(face);
// OUT(unknown02264[47]);
OUT_array(spell_book, spells::SPELLBOOK_SIZE);
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
// invalidate the rest of the spellbook slots
memset(&eq->spell_book[EQEmu::spells::SPELLBOOK_SIZE], 0xFF, (sizeof(uint32) * (spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE)));
}
// OUT(unknown4184[128]);
OUT_array(mem_spells, spells::SPELL_GEM_COUNT);
// OUT(unknown04396[32]);

View File

@ -1156,7 +1156,26 @@ namespace SoF
OUT(WIS);
OUT(face);
// OUT(unknown02264[47]);
OUT_array(spell_book, spells::SPELLBOOK_SIZE);
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
// invalidate the rest of the spellbook slots
memset(&eq->spell_book[EQEmu::spells::SPELLBOOK_SIZE], 0xFF, (sizeof(uint32) * (spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE)));
}
// OUT(unknown4184[128]);
OUT_array(mem_spells, spells::SPELL_GEM_COUNT);
// OUT(unknown04396[32]);

View File

@ -1012,7 +1012,26 @@ namespace Titanium
OUT(WIS);
OUT(face);
// OUT(unknown02264[47]);
OUT_array(spell_book, spells::SPELLBOOK_SIZE);
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
// invalidate the rest of the spellbook slots
memset(&eq->spell_book[EQEmu::spells::SPELLBOOK_SIZE], 0xFF, (sizeof(uint32) * (spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE)));
}
// OUT(unknown4184[448]);
OUT_array(mem_spells, spells::SPELL_GEM_COUNT);
// OUT(unknown04396[32]);

View File

@ -1724,8 +1724,26 @@ namespace UF
OUT(WIS);
OUT(face);
// OUT(unknown02264[47]);
memset(eq->spell_book, 0xFF, sizeof(uint32)* spells::SPELLBOOK_SIZE);
OUT_array(spell_book, 480U);
if (spells::SPELLBOOK_SIZE <= EQEmu::spells::SPELLBOOK_SIZE) {
for (uint32 r = 0; r < spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
}
else {
for (uint32 r = 0; r < EQEmu::spells::SPELLBOOK_SIZE; r++) {
if (emu->spell_book[r] <= spells::SPELL_ID_MAX)
eq->spell_book[r] = emu->spell_book[r];
else
eq->spell_book[r] = 0xFFFFFFFFU;
}
// invalidate the rest of the spellbook slots
memset(&eq->spell_book[EQEmu::spells::SPELLBOOK_SIZE], 0xFF, (sizeof(uint32) * (spells::SPELLBOOK_SIZE - EQEmu::spells::SPELLBOOK_SIZE)));
}
// OUT(unknown4184[128]);
OUT_array(mem_spells, spells::SPELL_GEM_COUNT);
// OUT(unknown04396[32]);

View File

@ -0,0 +1,250 @@
/* 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 <ctype.h>
#include <cstring>
#include <algorithm>
static std::list<std::string> 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<const std::string&>(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<std::string> &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;
}

View File

@ -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 <string>
#include <list>
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<std::string> &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*/

View File

@ -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

View File

@ -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

View File

@ -30,9 +30,9 @@
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
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9022
#else
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0
#endif

View File

@ -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_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

View File

@ -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'|not_empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -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';

View File

@ -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');

View File

@ -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);
@ -1446,6 +1445,7 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc)
ExtendedProfile_Struct ext;
EQEmu::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQEmu::versions::ConvertClientVersionToMobVersion(EQEmu::versions::ConvertClientVersionBitToClientVersion(m_ClientVersionBit)));
inv.SetInventoryVersion(EQEmu::versions::ConvertClientVersionBitToClientVersion(m_ClientVersionBit));
inv.SetGMInventory(false); // character cannot have gm flag at this point
@ -1528,12 +1528,9 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc)
// strcpy(pp.servername, WorldConfig::get()->ShortName.c_str());
memset(pp.spell_book, 0xFF, (sizeof(uint32) * EQEmu::spells::SPELLBOOK_SIZE));
for (i = 0; i < EQEmu::spells::SPELLBOOK_SIZE; i++)
pp.spell_book[i] = 0xFFFFFFFF;
for(i = 0; i < EQEmu::spells::SPELL_GEM_COUNT; i++)
pp.mem_spells[i] = 0xFFFFFFFF;
memset(pp.mem_spells, 0xFF, (sizeof(uint32) * EQEmu::spells::SPELL_GEM_COUNT));
for(i = 0; i < BUFF_COUNT; i++)
pp.buffs[i].spellid = 0xFFFF;

View File

@ -98,6 +98,7 @@ void WorldDatabase::GetCharSelectInfo(uint32 accountID, EQApplicationPacket **ou
PlayerProfile_Struct pp;
EQEmu::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQEmu::versions::ConvertClientVersionToMobVersion(client_version));
inv.SetInventoryVersion(client_version);
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support

View File

@ -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;

View File

@ -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;

View File

@ -29,7 +29,9 @@
extern volatile bool is_zone_loaded;
// This constructor is used during the bot create command
Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1) {
Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) {
GiveNPCTypeData(npcTypeData);
if(botOwner) {
this->SetBotOwner(botOwner);
this->_botOwnerCharacterID = botOwner->CharacterID();
@ -46,23 +48,23 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm
_lastTotalPlayTime = 0;
_startTotalPlayTime = time(&_startTotalPlayTime);
_lastZoneId = 0;
_baseMR = npcTypeData.MR;
_baseCR = npcTypeData.CR;
_baseDR = npcTypeData.DR;
_baseFR = npcTypeData.FR;
_basePR = npcTypeData.PR;
_baseCorrup = npcTypeData.Corrup;
_baseAC = npcTypeData.AC;
_baseSTR = npcTypeData.STR;
_baseSTA = npcTypeData.STA;
_baseDEX = npcTypeData.DEX;
_baseAGI = npcTypeData.AGI;
_baseINT = npcTypeData.INT;
_baseWIS = npcTypeData.WIS;
_baseCHA = npcTypeData.CHA;
_baseATK = npcTypeData.ATK;
_baseRace = npcTypeData.race;
_baseGender = npcTypeData.gender;
_baseMR = npcTypeData->MR;
_baseCR = npcTypeData->CR;
_baseDR = npcTypeData->DR;
_baseFR = npcTypeData->FR;
_basePR = npcTypeData->PR;
_baseCorrup = npcTypeData->Corrup;
_baseAC = npcTypeData->AC;
_baseSTR = npcTypeData->STR;
_baseSTA = npcTypeData->STA;
_baseDEX = npcTypeData->DEX;
_baseAGI = npcTypeData->AGI;
_baseINT = npcTypeData->INT;
_baseWIS = npcTypeData->WIS;
_baseCHA = npcTypeData->CHA;
_baseATK = npcTypeData->ATK;
_baseRace = npcTypeData->race;
_baseGender = npcTypeData->gender;
RestRegenHP = 0;
RestRegenMana = 0;
RestRegenEndurance = 0;
@ -80,6 +82,7 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm
SetShowHelm(true);
SetPauseAI(false);
rest_timer.Disable();
ping_timer.Disable();
SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT);
if (IsCasterClass(GetClass()))
SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel));
@ -104,9 +107,11 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm
}
// This constructor is used when the bot is loaded out of the database
Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType npcTypeData)
: NPC(&npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1)
Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData)
: NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1)
{
GiveNPCTypeData(npcTypeData);
this->_botOwnerCharacterID = botOwnerCharacterID;
if(this->_botOwnerCharacterID > 0)
this->SetBotOwner(entity_list.GetClientByCharID(this->_botOwnerCharacterID));
@ -122,25 +127,25 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
_startTotalPlayTime = time(&_startTotalPlayTime);
_lastZoneId = lastZoneId;
berserk = false;
_baseMR = npcTypeData.MR;
_baseCR = npcTypeData.CR;
_baseDR = npcTypeData.DR;
_baseFR = npcTypeData.FR;
_basePR = npcTypeData.PR;
_baseCorrup = npcTypeData.Corrup;
_baseAC = npcTypeData.AC;
_baseSTR = npcTypeData.STR;
_baseSTA = npcTypeData.STA;
_baseDEX = npcTypeData.DEX;
_baseAGI = npcTypeData.AGI;
_baseINT = npcTypeData.INT;
_baseWIS = npcTypeData.WIS;
_baseCHA = npcTypeData.CHA;
_baseATK = npcTypeData.ATK;
_baseRace = npcTypeData.race;
_baseGender = npcTypeData.gender;
current_hp = npcTypeData.current_hp;
current_mana = npcTypeData.Mana;
_baseMR = npcTypeData->MR;
_baseCR = npcTypeData->CR;
_baseDR = npcTypeData->DR;
_baseFR = npcTypeData->FR;
_basePR = npcTypeData->PR;
_baseCorrup = npcTypeData->Corrup;
_baseAC = npcTypeData->AC;
_baseSTR = npcTypeData->STR;
_baseSTA = npcTypeData->STA;
_baseDEX = npcTypeData->DEX;
_baseAGI = npcTypeData->AGI;
_baseINT = npcTypeData->INT;
_baseWIS = npcTypeData->WIS;
_baseCHA = npcTypeData->CHA;
_baseATK = npcTypeData->ATK;
_baseRace = npcTypeData->race;
_baseGender = npcTypeData->gender;
current_hp = npcTypeData->current_hp;
current_mana = npcTypeData->Mana;
RestRegenHP = 0;
RestRegenMana = 0;
RestRegenEndurance = 0;
@ -158,10 +163,11 @@ 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();
ping_timer.Disable();
SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT);
if (IsCasterClass(GetClass()))
SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel));
@ -313,112 +319,252 @@ bool Bot::IsStanding() {
return result;
}
NPCType Bot::FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack) {
NPCType BotNPCType;
int CopyLength = 0;
CopyLength = botName.copy(BotNPCType.name, 63);
BotNPCType.name[CopyLength] = '\0';
CopyLength = 0;
CopyLength = botLastName.copy(BotNPCType.lastname, 69);
BotNPCType.lastname[CopyLength] = '\0';
CopyLength = 0;
BotNPCType.npc_spells_id = botSpellsID;
BotNPCType.level = botLevel;
BotNPCType.race = botRace;
BotNPCType.class_ = botClass;
BotNPCType.gender = gender;
BotNPCType.size = size;
BotNPCType.luclinface = face;
BotNPCType.hairstyle = hairStyle;
BotNPCType.haircolor = hairColor;
BotNPCType.eyecolor1 = eyeColor;
BotNPCType.eyecolor2 = eyeColor2;
BotNPCType.beardcolor = beardColor;
BotNPCType.beard = beard;
BotNPCType.drakkin_heritage = drakkinHeritage;
BotNPCType.drakkin_tattoo = drakkinTattoo;
BotNPCType.drakkin_details = drakkinDetails;
BotNPCType.current_hp = hp;
BotNPCType.Mana = mana;
BotNPCType.MR = mr;
BotNPCType.CR = cr;
BotNPCType.DR = dr;
BotNPCType.FR = fr;
BotNPCType.PR = pr;
BotNPCType.Corrup = corrup;
BotNPCType.AC = ac;
BotNPCType.STR = str;
BotNPCType.STA = sta;
BotNPCType.DEX = dex;
BotNPCType.AGI = agi;
BotNPCType.INT = _int;
BotNPCType.WIS = wis;
BotNPCType.CHA = cha;
BotNPCType.ATK = attack;
BotNPCType.npc_id = 0;
BotNPCType.texture = 0;
BotNPCType.d_melee_texture1 = 0;
BotNPCType.d_melee_texture2 = 0;
BotNPCType.qglobal = false;
BotNPCType.attack_speed = 0;
BotNPCType.runspeed = 0.7f;
BotNPCType.bodytype = 1;
BotNPCType.findable = 0;
BotNPCType.hp_regen = 1;
BotNPCType.mana_regen = 1;
BotNPCType.maxlevel = botLevel;
BotNPCType.light = 0; // due to the way that bots are coded..this is sent post-spawn
return BotNPCType;
NPCType *Bot::FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack) {
auto bot_npc_type = new NPCType{ 0 };
int copy_length = 0;
copy_length = botName.copy(bot_npc_type->name, 63);
bot_npc_type->name[copy_length] = '\0';
copy_length = 0;
copy_length = botLastName.copy(bot_npc_type->lastname, 69);
bot_npc_type->lastname[copy_length] = '\0';
copy_length = 0;
bot_npc_type->current_hp = hp;
bot_npc_type->max_hp = hp;
bot_npc_type->size = size;
bot_npc_type->runspeed = 0.7f;
bot_npc_type->gender = gender;
bot_npc_type->race = botRace;
bot_npc_type->class_ = botClass;
bot_npc_type->bodytype = 1;
bot_npc_type->deity = EQEmu::deity::DeityAgnostic;
bot_npc_type->level = botLevel;
//bot_npc_type->npc_id = 0;
//bot_npc_type->texture = 0;
//bot_npc_type->helmtexture = 0;
//bot_npc_type->herosforgemodel = 0;
//bot_npc_type->loottable_id = 0;
bot_npc_type->npc_spells_id = botSpellsID;
//bot_npc_type->npc_spells_effects_id = 0;
//bot_npc_type->npc_faction_id = 0;
//bot_npc_type->merchanttype = 0;
//bot_npc_type->alt_currency_type = 0;
//bot_npc_type->adventure_template = 0;
//bot_npc_type->trap_template = 0;
//bot_npc_type->light = 0;
bot_npc_type->AC = ac;
bot_npc_type->Mana = mana;
bot_npc_type->ATK = attack;
bot_npc_type->STR = str;
bot_npc_type->STA = sta;
bot_npc_type->DEX = dex;
bot_npc_type->AGI = agi;
bot_npc_type->INT = _int;
bot_npc_type->WIS = wis;
bot_npc_type->CHA = cha;
bot_npc_type->MR = mr;
bot_npc_type->FR = fr;
bot_npc_type->CR = cr;
bot_npc_type->PR = pr;
bot_npc_type->DR = dr;
bot_npc_type->Corrup = corrup;
//bot_npc_type->PhR = 0;
bot_npc_type->haircolor = hairColor;
bot_npc_type->beardcolor = beardColor;
bot_npc_type->eyecolor1 = eyeColor;
bot_npc_type->eyecolor2 = eyeColor2;
bot_npc_type->hairstyle = hairStyle;
bot_npc_type->luclinface = face;
bot_npc_type->beard = beard;
bot_npc_type->drakkin_heritage = drakkinHeritage;
bot_npc_type->drakkin_tattoo = drakkinTattoo;
bot_npc_type->drakkin_details = drakkinDetails;
//bot_npc_type->armor_tint = { 0 };
//bot_npc_type->min_dmg = 0;
//bot_npc_type->max_dmg = 0;
//bot_npc_type->charm_ac = 0;
//bot_npc_type->charm_min_dmg = 0;
//bot_npc_type->charm_max_dmg = 0;
//bot_npc_type->charm_attack_delay = 0;
//bot_npc_type->charm_accuracy_rating = 0;
//bot_npc_type->charm_avoidance_rating = 0;
//bot_npc_type->charm_atk = 0;
//bot_npc_type->attack_count = 0;
//*bot_npc_type->special_abilities = { 0 };
//bot_npc_type->d_melee_texture1 = 0;
//bot_npc_type->d_melee_texture2 = 0;
//*bot_npc_type->ammo_idfile = { 0 };
//bot_npc_type->prim_melee_type = 0;
//bot_npc_type->sec_melee_type = 0;
//bot_npc_type->ranged_type = 0;
bot_npc_type->hp_regen = 1;
bot_npc_type->mana_regen = 1;
//bot_npc_type->aggroradius = 0;
//bot_npc_type->assistradius = 0;
//bot_npc_type->see_invis = 0;
//bot_npc_type->see_invis_undead = false;
//bot_npc_type->see_hide = false;
//bot_npc_type->see_improved_hide = false;
//bot_npc_type->qglobal = false;
//bot_npc_type->npc_aggro = false;
//bot_npc_type->spawn_limit = 0;
//bot_npc_type->mount_color = 0;
//bot_npc_type->attack_speed = 0.0f;
//bot_npc_type->attack_delay = 0;
//bot_npc_type->accuracy_rating = 0;
//bot_npc_type->avoidance_rating = 0;
//bot_npc_type->findable = false;
bot_npc_type->trackable = true;
//bot_npc_type->slow_mitigation = 0;
bot_npc_type->maxlevel = botLevel;
//bot_npc_type->scalerate = 0;
//bot_npc_type->private_corpse = false;
//bot_npc_type->unique_spawn_by_name = false;
//bot_npc_type->underwater = false;
//bot_npc_type->emoteid = 0;
//bot_npc_type->spellscale = 0.0f;
//bot_npc_type->healscale = 0.0f;
//bot_npc_type->no_target_hotkey = false;
//bot_npc_type->raid_target = false;
//bot_npc_type->armtexture = 0;
//bot_npc_type->bracertexture = 0;
//bot_npc_type->handtexture = 0;
//bot_npc_type->legtexture = 0;
//bot_npc_type->feettexture = 0;
//bot_npc_type->ignore_despawn = false;
bot_npc_type->show_name = true;
//bot_npc_type->untargetable = false;
bot_npc_type->skip_global_loot = true;
//bot_npc_type->rare_spawn = false;
bot_npc_type->stuck_behavior = Ground;
return bot_npc_type;
}
NPCType Bot::CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender) {
NPCType Result;
int CopyLength = 0;
CopyLength = botName.copy(Result.name, 63);
Result.name[CopyLength] = '\0';
CopyLength = 0;
CopyLength = botLastName.copy(Result.lastname, 69);
Result.lastname[CopyLength] = '\0';
CopyLength = 0;
Result.level = botLevel;
Result.race = botRace;
Result.class_ = botClass;
Result.gender = gender;
// default values
Result.maxlevel = botLevel;
Result.size = 6.0;
Result.npc_id = 0;
Result.current_hp = 0;
Result.drakkin_details = 0;
Result.drakkin_heritage = 0;
Result.drakkin_tattoo = 0;
Result.runspeed = 0.7f;
Result.bodytype = 1;
Result.findable = 0;
Result.hp_regen = 1;
Result.mana_regen = 1;
Result.texture = 0;
Result.d_melee_texture1 = 0;
Result.d_melee_texture2 = 0;
Result.qglobal = false;
Result.npc_spells_id = 0;
Result.attack_speed = 0;
Result.STR = 75;
Result.STA = 75;
Result.DEX = 75;
Result.AGI = 75;
Result.WIS = 75;
Result.INT = 75;
Result.CHA = 75;
Result.ATK = 75;
Result.MR = 25;
Result.FR = 25;
Result.DR = 15;
Result.PR = 15;
Result.CR = 25;
Result.Corrup = 15;
Result.AC = 12;
return Result;
NPCType *Bot::CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender) {
auto bot_npc_type = new NPCType{ 0 };
int copy_length = 0;
copy_length = botName.copy(bot_npc_type->name, 63);
bot_npc_type->name[copy_length] = '\0';
copy_length = 0;
copy_length = botLastName.copy(bot_npc_type->lastname, 69);
bot_npc_type->lastname[copy_length] = '\0';
copy_length = 0;
//bot_npc_type->current_hp = 0;
//bot_npc_type->max_hp = 0;
bot_npc_type->size = 6.0f;
bot_npc_type->runspeed = 0.7f;
bot_npc_type->gender = gender;
bot_npc_type->race = botRace;
bot_npc_type->class_ = botClass;
bot_npc_type->bodytype = 1;
bot_npc_type->deity = EQEmu::deity::DeityAgnostic;
bot_npc_type->level = botLevel;
//bot_npc_type->npc_id = 0;
//bot_npc_type->texture = 0;
//bot_npc_type->helmtexture = 0;
//bot_npc_type->herosforgemodel = 0;
//bot_npc_type->loottable_id = 0;
//bot_npc_type->npc_spells_id = 0;
//bot_npc_type->npc_spells_effects_id = 0;
//bot_npc_type->npc_faction_id = 0;
//bot_npc_type->merchanttype = 0;
//bot_npc_type->alt_currency_type = 0;
//bot_npc_type->adventure_template = 0;
//bot_npc_type->trap_template = 0;
//bot_npc_type->light = 0;
bot_npc_type->AC = 12;
//bot_npc_type->Mana = 0;
bot_npc_type->ATK = 75;
bot_npc_type->STR = 75;
bot_npc_type->STA = 75;
bot_npc_type->DEX = 75;
bot_npc_type->AGI = 75;
bot_npc_type->INT = 75;
bot_npc_type->WIS = 75;
bot_npc_type->CHA = 75;
bot_npc_type->MR = 25;
bot_npc_type->FR = 25;
bot_npc_type->CR = 25;
bot_npc_type->PR = 15;
bot_npc_type->DR = 15;
bot_npc_type->Corrup = 15;
//bot_npc_type->PhR = 0;
//bot_npc_type->haircolor = 0;
//bot_npc_type->beardcolor = 0;
//bot_npc_type->eyecolor1 = 0;
//bot_npc_type->eyecolor2 = 0;
//bot_npc_type->hairstyle = 0;
//bot_npc_type->luclinface = 0;
//bot_npc_type->beard = 0;
//bot_npc_type->drakkin_heritage = 0;
//bot_npc_type->drakkin_tattoo = 0;
//bot_npc_type->drakkin_details = 0;
//bot_npc_type->armor_tint = { 0 };
//bot_npc_type->min_dmg = 0;
//bot_npc_type->max_dmg = 0;
//bot_npc_type->charm_ac = 0;
//bot_npc_type->charm_min_dmg = 0;
//bot_npc_type->charm_max_dmg = 0;
//bot_npc_type->charm_attack_delay = 0;
//bot_npc_type->charm_accuracy_rating = 0;
//bot_npc_type->charm_avoidance_rating = 0;
//bot_npc_type->charm_atk = 0;
//bot_npc_type->attack_count = 0;
//*bot_npc_type->special_abilities = { 0 };
//bot_npc_type->d_melee_texture1 = 0;
//bot_npc_type->d_melee_texture2 = 0;
//*bot_npc_type->ammo_idfile = { 0 };
//bot_npc_type->prim_melee_type = 0;
//bot_npc_type->sec_melee_type = 0;
//bot_npc_type->ranged_type = 0;
bot_npc_type->hp_regen = 1;
bot_npc_type->mana_regen = 1;
//bot_npc_type->aggroradius = 0;
//bot_npc_type->assistradius = 0;
//bot_npc_type->see_invis = 0;
//bot_npc_type->see_invis_undead = false;
//bot_npc_type->see_hide = false;
//bot_npc_type->see_improved_hide = false;
//bot_npc_type->qglobal = false;
//bot_npc_type->npc_aggro = false;
//bot_npc_type->spawn_limit = 0;
//bot_npc_type->mount_color = 0;
//bot_npc_type->attack_speed = 0.0f;
//bot_npc_type->attack_delay = 0;
//bot_npc_type->accuracy_rating = 0;
//bot_npc_type->avoidance_rating = 0;
//bot_npc_type->findable = false;
bot_npc_type->trackable = true;
//bot_npc_type->slow_mitigation = 0;
bot_npc_type->maxlevel = botLevel;
//bot_npc_type->scalerate = 0;
//bot_npc_type->private_corpse = false;
//bot_npc_type->unique_spawn_by_name = false;
//bot_npc_type->underwater = false;
//bot_npc_type->emoteid = 0;
//bot_npc_type->spellscale = 0.0f;
//bot_npc_type->healscale = 0.0f;
//bot_npc_type->no_target_hotkey = false;
//bot_npc_type->raid_target = false;
//bot_npc_type->armtexture = 0;
//bot_npc_type->bracertexture = 0;
//bot_npc_type->handtexture = 0;
//bot_npc_type->legtexture = 0;
//bot_npc_type->feettexture = 0;
//bot_npc_type->ignore_despawn = false;
bot_npc_type->show_name = true;
//bot_npc_type->untargetable = false;
bot_npc_type->skip_global_loot = true;
//bot_npc_type->rare_spawn = false;
bot_npc_type->stuck_behavior = Ground;
return bot_npc_type;
}
void Bot::GenerateBaseStats()
@ -1867,6 +2013,17 @@ 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;
@ -2091,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()) {
@ -2104,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) {
@ -2603,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..)
@ -2730,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;
}
@ -2738,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())
@ -2796,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);
}
@ -2841,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;
@ -3111,6 +3274,7 @@ bool Bot::Spawn(Client* botCharacterOwner) {
// Load pet
LoadPet();
SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0);
ping_timer.Start(8000);
// there is something askew with spawn struct appearance fields...
// I re-enabled this until I can sort it out
uint32 itemID = 0;
@ -8049,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;
}
@ -8104,16 +8268,16 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl
Group *g = caster->GetGroup();
float hpRatioToHeal = 25.0f;
switch(caster->GetBotStance()) {
case BotStanceReactive:
case BotStanceBalanced:
case EQEmu::constants::stanceReactive:
case EQEmu::constants::stanceBalanced:
hpRatioToHeal = 50.0f;
break;
case BotStanceBurn:
case BotStanceBurnAE:
case EQEmu::constants::stanceBurn:
case EQEmu::constants::stanceBurnAE:
hpRatioToHeal = 20.0f;
break;
case BotStanceAggressive:
case BotStanceEfficient:
case EQEmu::constants::stanceAggressive:
case EQEmu::constants::stanceEfficient:
default:
hpRatioToHeal = 25.0f;
break;
@ -8655,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;
}
@ -8932,4 +9096,6 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name)
return saylink;
}
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 };
#endif

View File

@ -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
@ -52,91 +54,7 @@ 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] =
{
"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
@ -226,32 +144,38 @@ 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);
Bot(NPCType *npcTypeData, Client* botOwner);
Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData);
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
@ -337,9 +261,10 @@ public:
void Stand();
bool IsSitting();
bool IsStanding();
int GetBotWalkspeed() const { return (int)((float)_GetWalkSpeed() * 1.786f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
int GetBotRunspeed() const { return (int)((float)_GetRunSpeed() * 1.786f); }
int GetBotFearSpeed() const { return (int)((float)_GetFearSpeed() * 1.786f); }
virtual int GetWalkspeed() const { return (int)((float)_GetWalkSpeed() * 1.785714f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
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);
bool UseDiscipline(uint32 spell_id, uint32 target);
uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets);
bool GetNeedsCured(Mob *tar);
@ -494,7 +419,7 @@ public:
static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target);
static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target);
static NPCType CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender);
static NPCType *CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender);
// Static Bot Group Methods
static bool AddBotToGroup(Bot* bot, Group* group);
@ -516,7 +441,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; }
@ -630,7 +555,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;}
@ -656,7 +586,7 @@ public:
virtual void BotRangedAttack(Mob* other);
// Publicized private functions
static NPCType FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack);
static NPCType *FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack);
void BotRemoveEquipItem(int16 slot);
void RemoveBotItemBySlot(uint32 slotID, std::string* errorMessage);
uint32 GetTotalPlayTime();
@ -724,12 +654,13 @@ 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;
Timer rest_timer;
Timer ping_timer;
int32 base_end;
int32 cur_end;
int32 max_end;
@ -788,6 +719,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[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQEmu::constants::STANCE_TYPE_COUNT][cntHSND];
};
#endif // BOTS

View File

@ -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()
);
}
}
@ -7220,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;
}
@ -7230,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;
}
@ -7333,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...");
@ -7383,7 +7393,7 @@ void bot_subcommand_inventory_window(Client *c, const Seperator *sep)
item = inst->GetItem();
window_text.append("<c \"#FFFFFF\">");
window_text.append(GetBotEquipSlotName(i));
window_text.append(EQEmu::invslot::GetInvPossessionsSlotName(i));
window_text.append(": ");
if (item) {
//window_text.append("</c>");
@ -7707,16 +7717,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
return bot_id;
}
auto DefaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(
bot_name.c_str(),
"",
bot_owner->GetLevel(),
bot_race,
bot_class,
bot_gender
);
auto my_bot = new Bot(DefaultNPCTypeStruct, bot_owner);
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name.c_str(), "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
if (!my_bot->Save()) {
bot_owner->Message(m_unknown, "Failed to create '%s' due to unknown cause", my_bot->GetCleanName());

View File

@ -83,12 +83,8 @@ bool BotDatabase::LoadBotCommandSettings(std::map<std::string, std::pair<uint8,
return true;
}
static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][MaxStances][cntHSND];
bool BotDatabase::LoadBotSpellCastingChances()
{
memset(spell_casting_chances, 0, sizeof(spell_casting_chances));
query =
"SELECT"
" `spell_type_index`,"
@ -119,14 +115,14 @@ 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)
continue;
--class_index;
uint8 stance_index = atoi(row[2]);
if (stance_index >= MaxStances)
if (stance_index >= EQEmu::constants::STANCE_TYPE_COUNT)
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;
}
}
@ -357,8 +353,8 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot)
// TODO: Consider removing resists and basic attributes from the load query above since we're using defaultNPCType values instead
auto row = results.begin();
NPCType defaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(std::string(row[2]), std::string(row[3]), atoi(row[10]), atoi(row[8]), atoi(row[9]), atoi(row[7]));
NPCType tempNPCStruct = Bot::FillNPCTypeStruct(
auto defaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(std::string(row[2]), std::string(row[3]), atoi(row[10]), atoi(row[8]), atoi(row[9]), atoi(row[7]));
auto tempNPCStruct = Bot::FillNPCTypeStruct(
atoi(row[1]),
std::string(row[2]),
std::string(row[3]),
@ -379,21 +375,21 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot)
atoi(row[25]),
atoi(row[28]),
atoi(row[29]),
defaultNPCTypeStruct.MR,
defaultNPCTypeStruct.CR,
defaultNPCTypeStruct.DR,
defaultNPCTypeStruct.FR,
defaultNPCTypeStruct.PR,
defaultNPCTypeStruct.Corrup,
defaultNPCTypeStruct.AC,
defaultNPCTypeStruct.STR,
defaultNPCTypeStruct.STA,
defaultNPCTypeStruct.DEX,
defaultNPCTypeStruct.AGI,
defaultNPCTypeStruct.INT,
defaultNPCTypeStruct.WIS,
defaultNPCTypeStruct.CHA,
defaultNPCTypeStruct.ATK
defaultNPCTypeStruct->MR,
defaultNPCTypeStruct->CR,
defaultNPCTypeStruct->DR,
defaultNPCTypeStruct->FR,
defaultNPCTypeStruct->PR,
defaultNPCTypeStruct->Corrup,
defaultNPCTypeStruct->AC,
defaultNPCTypeStruct->STR,
defaultNPCTypeStruct->STA,
defaultNPCTypeStruct->DEX,
defaultNPCTypeStruct->AGI,
defaultNPCTypeStruct->INT,
defaultNPCTypeStruct->WIS,
defaultNPCTypeStruct->CHA,
defaultNPCTypeStruct->ATK
);
loaded_bot = new Bot(bot_id, atoi(row[0]), atoi(row[1]), atof(row[14]), atoi(row[6]), tempNPCStruct);
@ -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;
@ -2853,16 +2849,16 @@ 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;
if (stance_index >= MaxStances)
if (stance_index >= EQEmu::constants::STANCE_TYPE_COUNT)
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];
}

View File

@ -192,20 +192,19 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
else {
float hpRatioToCast = 0.0f;
switch(this->GetBotStance())
{
case BotStanceEfficient:
case BotStanceAggressive:
switch(this->GetBotStance()) {
case EQEmu::constants::stanceEfficient:
case EQEmu::constants::stanceAggressive:
hpRatioToCast = isPrimaryHealer?90.0f:50.0f;
break;
case BotStanceBalanced:
case EQEmu::constants::stanceBalanced:
hpRatioToCast = isPrimaryHealer?95.0f:75.0f;
break;
case BotStanceReactive:
case EQEmu::constants::stanceReactive:
hpRatioToCast = isPrimaryHealer?100.0f:90.0f;
break;
case BotStanceBurn:
case BotStanceBurnAE:
case EQEmu::constants::stanceBurn:
case EQEmu::constants::stanceBurnAE:
hpRatioToCast = isPrimaryHealer?75.0f:25.0f;
break;
default:
@ -381,18 +380,17 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
{
float manaRatioToCast = 75.0f;
switch(this->GetBotStance())
{
case BotStanceEfficient:
switch(this->GetBotStance()) {
case EQEmu::constants::stanceEfficient:
manaRatioToCast = 90.0f;
break;
case BotStanceBalanced:
case BotStanceAggressive:
case EQEmu::constants::stanceBalanced:
case EQEmu::constants::stanceAggressive:
manaRatioToCast = 75.0f;
break;
case BotStanceReactive:
case BotStanceBurn:
case BotStanceBurnAE:
case EQEmu::constants::stanceReactive:
case EQEmu::constants::stanceBurn:
case EQEmu::constants::stanceBurnAE:
manaRatioToCast = 50.0f;
break;
default:
@ -461,20 +459,19 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
{
float manaRatioToCast = 75.0f;
switch(this->GetBotStance())
{
case BotStanceEfficient:
switch(this->GetBotStance()) {
case EQEmu::constants::stanceEfficient:
manaRatioToCast = 90.0f;
break;
case BotStanceBalanced:
case EQEmu::constants::stanceBalanced:
manaRatioToCast = 75.0f;
break;
case BotStanceReactive:
case BotStanceAggressive:
case EQEmu::constants::stanceReactive:
case EQEmu::constants::stanceAggressive:
manaRatioToCast = 50.0f;
break;
case BotStanceBurn:
case BotStanceBurnAE:
case EQEmu::constants::stanceBurn:
case EQEmu::constants::stanceBurnAE:
manaRatioToCast = 25.0f;
break;
default:
@ -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.");
@ -2573,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();
@ -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;

View File

@ -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) {
@ -2640,6 +2648,11 @@ bool Client::CheckAccess(int16 iDBLevel, int16 iDefaultLevel) {
}
void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing){
if (slot < 0 || slot >= EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize)
return;
if ((spellid < 3 || spellid > EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellIdMax) && spellid != 0xFFFFFFFF)
return;
auto outapp = new EQApplicationPacket(OP_MemorizeSpell, sizeof(MemorizeSpell_Struct));
MemorizeSpell_Struct* mss=(MemorizeSpell_Struct*)outapp->pBuffer;
mss->scribing=scribing;

View File

@ -530,10 +530,14 @@ void Client::CompleteConnect()
SendAppearancePacket(AT_GuildID, GuildID(), false);
SendAppearancePacket(AT_GuildRank, rank, false);
}
for (uint32 spellInt = 0; spellInt < EQEmu::spells::SPELLBOOK_SIZE; spellInt++) {
if (m_pp.spell_book[spellInt] < 3 || m_pp.spell_book[spellInt] > 50000)
// moved to dbload and translators since we iterate there also .. keep m_pp values whatever they are when they get here
/*const auto sbs = EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize;
for (uint32 spellInt = 0; spellInt < sbs; ++spellInt) {
if (m_pp.spell_book[spellInt] < 3 || m_pp.spell_book[spellInt] > EQEmu::spells::SPELL_ID_MAX)
m_pp.spell_book[spellInt] = 0xFFFFFFFF;
}
}*/
//SendAATable();
if (GetHideMe()) Message(13, "[GM] You are currently hidden to all clients");
@ -1154,6 +1158,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
SetClientVersion(Connection()->ClientVersion());
m_ClientVersionBit = EQEmu::versions::ConvertClientVersionToClientVersionBit(Connection()->ClientVersion());
m_pp.SetPlayerProfileVersion(m_ClientVersion);
m_inv.SetInventoryVersion(m_ClientVersion);
/* Antighost code
@ -1409,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)) {
@ -1587,8 +1591,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate)))
m_pp.RestTimer = 0;
/* This checksum should disappear once dynamic structs are in... each struct strategy will do it */
CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct) - 4);
/* This checksum should disappear once dynamic structs are in... each struct strategy will do it */ // looks to be in place now
//CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct) - sizeof(m_pp.m_player_profile_version) - 4);
// m_pp.checksum = 0; // All server out-bound player profile packets are now translated - no need to waste cycles calculating this...
outapp = new EQApplicationPacket(OP_PlayerProfile, sizeof(PlayerProfile_Struct));
@ -2807,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) {
@ -5263,7 +5263,7 @@ void Client::Handle_OP_DeleteSpell(const EQApplicationPacket *app)
EQApplicationPacket* outapp = app->Copy();
DeleteSpell_Struct* dss = (DeleteSpell_Struct*)outapp->pBuffer;
if (dss->spell_slot < 0 || dss->spell_slot > int(EQEmu::spells::SPELLBOOK_SIZE))
if (dss->spell_slot < 0 || dss->spell_slot >= EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize)
return;
if (m_pp.spell_book[dss->spell_slot] != SPELLBOOK_UNKNOWN) {
@ -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());
@ -13337,7 +13337,10 @@ void Client::Handle_OP_SwapSpell(const EQApplicationPacket *app)
const SwapSpell_Struct* swapspell = (const SwapSpell_Struct*)app->pBuffer;
int swapspelltemp;
if (swapspell->from_slot < 0 || swapspell->from_slot > EQEmu::spells::SPELLBOOK_SIZE || swapspell->to_slot < 0 || swapspell->to_slot > EQEmu::spells::SPELLBOOK_SIZE)
const auto sbs = EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize;
if (swapspell->from_slot < 0 || swapspell->from_slot >= sbs)
return;
if (swapspell->to_slot < 0 || swapspell->to_slot >= sbs)
return;
swapspelltemp = m_pp.spell_book[swapspell->from_slot];

View File

@ -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) ||
@ -6405,34 +6407,29 @@ 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;
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 <max level> <min level>");
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)
{
if(max_level < 1 || min_level < 1) {
c->Message(0, "ERROR: Level must be greater than 1.");
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;
}
@ -6441,34 +6438,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);
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 (%u)", t->GetName(), spells[curspell].name, curspell);
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(!IsDiscipline(curspell) && !t->HasSpellScribed(curspell)) { //isn't a discipline & we don't already have it scribed
t->ScribeSpell(curspell, book_slot);
count++;
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 (%i != %u)", 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());
@ -8724,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;
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 <max level> <min level>");
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;
}
@ -8759,34 +8789,57 @@ 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) {
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] = curspell;
database.SaveCharacterDisc(t->CharacterID(), r, curspell);
t->SendDisciplineUpdate();
}
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
++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);
@ -11035,6 +11088,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("<br>");
}
if (list.empty())
popup.append("** Censorship Inactive **<br>");
else
popup.append("** End of List **<br>");
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] [<word>] - adds entry");
c->Message(0, "Usage: #profanity [del] [<word>] - 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]) {

View File

@ -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);

View File

@ -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;

View File

@ -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)
@ -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..
@ -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());

View File

@ -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;

View File

@ -576,8 +576,8 @@ public:
virtual void SetMoving(bool move) { moving = move; m_Delta = glm::vec4(); }
virtual void GoToBind(uint8 bindnum = 0) { }
virtual void Gate(uint8 bindnum = 0);
int GetWalkspeed() const { return(_GetWalkSpeed()); }
int GetRunspeed() const { return(_GetRunSpeed()); }
virtual int GetWalkspeed() const { return(_GetWalkSpeed()); }
virtual int GetRunspeed() const { return(_GetRunSpeed()); }
int GetBaseRunspeed() const { return base_runspeed; }
int GetBaseWalkspeed() const { return base_walkspeed; }
int GetBaseFearSpeed() const { return base_fearspeed; }
@ -998,8 +998,8 @@ public:
inline bool CheckAggro(Mob* other) {return hate_list.IsEntOnHateList(other);}
float CalculateHeadingToTarget(float in_x, float in_y) { return HeadingAngleToMob(in_x, in_y); }
void WalkTo(float x, float y, float z);
void RunTo(float x, float y, float z);
virtual void WalkTo(float x, float y, float z);
virtual void RunTo(float x, float y, float z);
void NavigateTo(float x, float y, float z);
void RotateTo(float new_heading);
void RotateToWalking(float new_heading);

View File

@ -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])

View File

@ -759,7 +759,7 @@ void MobMovementManager::FillCommandStruct(PlayerPositionUpdateServer_Struct *sp
spu->delta_y = FloatToEQ13(dy);
spu->delta_z = FloatToEQ13(dz);
spu->delta_heading = FloatToEQ10(dh);
spu->animation = anim;
spu->animation = (m->IsBot() ? (int)((float)anim / 1.785714f) : anim);
}
void MobMovementManager::UpdatePath(Mob *who, float x, float y, float z, MobMovementMode mode)

View File

@ -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)

View File

@ -975,119 +975,174 @@ void QuestManager::permagender(int gender_id) {
uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
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 < 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
);
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
break;
if(!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed
}
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 (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;
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_) && !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);
// 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++;
initiator->ScribeSpell(spell_id_, book_slot);
++count;
}
} else if (SpellBucketRule) {
SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id);
}
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++;
}
} else {
initiator->ScribeSpell(spell_id, book_slot);
count++;
initiator->ScribeSpell(spell_id_, book_slot);
++count;
}
}
else {
initiator->ScribeSpell(spell_id_, book_slot);
++count;
}
}
break;
}
book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot);
}
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;
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(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) {
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);
// 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->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
++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);
}
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->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++;
++count;
}
break;
}
else {
initiator->GetPP().disciplines.values[r] = spell_id;
database.SaveCharacterDisc(char_id, r, spell_id);
initiator->SendDisciplineUpdate();
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
++count; // success counter
break; // continue the 1st loop
}
} //if we get to this point, there's already a discipline in this slot, so we skip it
}
}
break;
}
}
if (change)
initiator->SendDisciplineUpdate();
return count; // how many disciplines were learned successfully
}
@ -2135,8 +2190,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level
return false;
}
NPCType DefaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(name, lastname, level, race, botclass, gender);
Bot* NewBot = new Bot(DefaultNPCTypeStruct, initiator);
Bot* NewBot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(name, lastname, level, race, botclass, gender), initiator);
if(NewBot)
{

View File

@ -5053,7 +5053,7 @@ void Client::UnscribeSpell(int slot, bool update_client)
m_pp.spell_book[slot] = 0xFFFFFFFF;
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot);
if(update_client)
if(update_client && slot < EQEmu::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize)
{
auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct));
DeleteSpell_Struct* del = (DeleteSpell_Struct*)outapp->pBuffer;
@ -5066,9 +5066,7 @@ void Client::UnscribeSpell(int slot, bool update_client)
void Client::UnscribeSpellAll(bool update_client)
{
int i;
for(i = 0; i < EQEmu::spells::SPELLBOOK_SIZE; i++)
for(int i = 0; i < EQEmu::spells::SPELLBOOK_SIZE; i++)
{
if(m_pp.spell_book[i] != 0xFFFFFFFF)
UnscribeSpell(i, update_client);

View File

@ -1063,3 +1063,26 @@ void NPC::RestoreGuardSpotCharm()
{
m_GuardPoint = m_GuardPointSaved;
}
/******************
* Bot-specific overloads to make them play nice with the new movement system
*/
#ifdef BOTS
#include "bot.h"
void Bot::WalkTo(float x, float y, float z)
{
if (IsSitting())
Stand();
Mob::WalkTo(x, y, z);
}
void Bot::RunTo(float x, float y, float z)
{
if (IsSitting())
Stand();
Mob::RunTo(x, y, z);
}
#endif

View File

@ -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;

View File

@ -1225,17 +1225,28 @@ bool ZoneDatabase::LoadCharacterSpellBook(uint32 character_id, PlayerProfile_Str
"`character_spells` "
"WHERE `id` = %u ORDER BY `slot_id`", character_id);
auto results = database.QueryDatabase(query);
int i = 0;
/* Initialize Spells */
for (i = 0; i < EQEmu::spells::SPELLBOOK_SIZE; i++){
pp->spell_book[i] = 0xFFFFFFFF;
}
memset(pp->spell_book, 0xFF, (sizeof(uint32) * EQEmu::spells::SPELLBOOK_SIZE));
// We have the ability to block loaded spells by max id on a per-client basis..
// but, we do not have to ability to keep players from using older clients after
// they have scribed spells on a newer one that exceeds the older one's limit.
// Load them all so that server actions are valid..but, nix them in translators.
for (auto row = results.begin(); row != results.end(); ++row) {
i = atoi(row[0]);
if (i < EQEmu::spells::SPELLBOOK_SIZE && atoi(row[1]) <= SPDAT_RECORDS){
pp->spell_book[i] = atoi(row[1]);
}
int idx = atoi(row[0]);
int id = atoi(row[1]);
if (idx < 0 || idx >= EQEmu::spells::SPELLBOOK_SIZE)
continue;
if (id < 3 || id > SPDAT_RECORDS) // 3 ("Summon Corpse") is the first scribable spell in spells_us.txt
continue;
pp->spell_book[idx] = id;
}
return true;
}