Merge pull request #4 from EQEmu/master

Update
This commit is contained in:
regneq 2020-03-07 18:21:25 -08:00 committed by GitHub
commit dce5dea609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1479 additions and 699 deletions

View File

@ -0,0 +1,21 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.1/containers/ubuntu-18.04-git
{
"name": "Ubuntu 18.04 EQEMU",
// Moved from dockerfile to image so it builds faster
"image": "eqemu/devcontainer:0.0.2",
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["ms-vscode.cpptools", "ms-azuretools.vscode-docker"],
"mounts": ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"],
"remoteEnv": {
"HOST_PROJECT_PATH": "${localWorkspaceFolder}"
}
}

2
.gitignore vendored
View File

@ -17,6 +17,8 @@
*.out
*.app
.bash_history
# CMake
CMakeCache.txt
CMakeFiles

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/mysql"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}

155
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,155 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "make",
"type": "shell",
"command": "cd build && make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "make clean",
"type": "shell",
"command": "cd build && make clean",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "cmake",
"type": "shell",
"command": "mkdir -p build && cd build && rm CMakeCache.txt && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' ..",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher":{
"owner": "cpp",
"fileLocation": "relative",
"pattern":[
{
"regexp": "([\\w+|\\\\]*\\.\\w+)\\((\\d+)\\)\\: (warning|error) (.*)$",
"file": 1,
"location": 2,
"severity": 3,
"message": 4
}
]
}
},
{
"label": "download maps",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && wget https://codeload.github.com/Akkadius/EQEmuMaps/zip/master -O maps.zip && unzip -o maps.zip && rm ./maps -rf && mv EQEmuMaps-master maps && rm maps.zip",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "download quests",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && cd server && git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "download eqemu_config",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && wget --no-check-certificate https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/eqemu_config_docker.json -O eqemu_config.json",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "rebuild database (mariadb must be started)",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && docker run -i --rm --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --network=eqemu -it eqemu/server:0.0.3 bash -c './eqemu_server.pl source_peq_db && ./eqemu_server.pl check_db_updates && ./eqemu_server.pl linux_login_server_setup'",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "zone 7000",
"type": "shell",
"command": "docker stop zone7000 | true && docker network create eqemu | true && docker run -i --rm --name zone7000 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu -p 7000:7000/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./zone dynamic_zone7000:7000",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "zone 7001",
"type": "shell",
"command": "docker stop zone7001 | true && docker network create eqemu | true && docker run -i --rm --name zone7001 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu -p 7001:7001/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./zone dynamic_zone7001:7001",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "loginserver",
"type": "shell",
"command": "docker stop loginserver | true && docker network create eqemu | true && docker run -i --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu --name loginserver -p 5999:5999/udp -p 5998:5998/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./loginserver",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "shared_memory, world",
"type": "shell",
"command": "docker stop sharedmemory | true && docker stop world | true && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --network=eqemu --name sharedmemory eqemu/server:0.0.3 ./shared_memory && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 -e LD_LIBRARY_PATH=/src/ --network=eqemu --name world -p 9000:9000 -p 9000:9000/udp -p 9001:9001 -p 9080:9080 eqemu/server:0.0.3 gdb -ex run ./world",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "queryserv",
"type": "shell",
"command": "docker stop queryserv | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 -e LD_LIBRARY_PATH=/src/ --network=eqemu --name queryserv eqemu/server:0.0.3 gdb -ex run ./queryserv",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "mariadb",
"type": "shell",
"command": "docker stop mariadb | true && cd build/bin && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin/db:/bitnami/mariadb -p 3306:3306 -e MARIADB_DATABASE=peq -e MARIADB_USER=eqemu -e MARIADB_PASSWORD=eqemupass -e ALLOW_EMPTY_PASSWORD=yes --name mariadb --network=eqemu bitnami/mariadb:latest",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}

View File

@ -17,7 +17,7 @@
|:---:|:---:|:---:|
|**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)|
### > Windows
* [Install](https://github.com/EQEmu/Server/wiki/Windows-Server)
* [Install](https://eqemu.gitbook.io/server/categories/how-to-guides/installation/server-installation-windows)
### > Debian/Ubuntu/CentOS/Fedora
* You can use curl or wget to kick off the installer (whichever your OS has)

View File

@ -45,6 +45,7 @@
#include "eq_packet_structs.h"
#include "extprofile.h"
#include "string_util.h"
#include "database_schema.h"
extern Client client;
@ -338,6 +339,21 @@ bool Database::ReserveName(uint32 account_id, char* name) {
query = StringFormat("INSERT INTO `character_data` SET `account_id` = %i, `name` = '%s'", account_id, name);
results = QueryDatabase(query);
if (!results.Success() || results.ErrorMessage() != ""){ return false; }
// Put character into the default guild if rule is being used.
int guild_id = RuleI(Character, DefaultGuild);
if (guild_id != 0) {
int character_id=results.LastInsertedID();
if (character_id > -1) {
query = StringFormat("INSERT INTO `guild_members` SET `char_id` = %i, `guild_id` = '%i'", character_id, guild_id);
results = QueryDatabase(query);
if (!results.Success() || results.ErrorMessage() != ""){
LogInfo("Could not put character [{}] into default Guild", name);
}
}
}
return true;
}
@ -386,46 +402,18 @@ bool Database::DeleteCharacter(char *character_name) {
LogInfo("DeleteCharacter | Character [{}] ({}) is being [{}]", character_name, character_id, delete_type);
query = StringFormat("DELETE FROM `quest_globals` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_activities` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_enabledtasks` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_tasks` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `completed_tasks` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `friends` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `mail` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `timers` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `inventory` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `char_recipe_list` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `adventure_stats` WHERE `player_id` ='%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `zone_flags` WHERE `charID` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `titles` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `player_titlesets` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `keyring` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `faction_values` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `instance_list_player` WHERE `charid` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_data` WHERE `id` = '%d'", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_skills` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_languages` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_bind` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_currency` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_data` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_spells` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_memmed_spells` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_disciplines` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_material` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_tribute` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_bandolier` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_potionbelt` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_inspect_messages` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_leadership_abilities` WHERE `id` = %u", character_id); QueryDatabase(query);
query = StringFormat("DELETE FROM `character_alt_currency` WHERE `char_id` = '%d'", character_id); QueryDatabase(query);
for (const auto& iter : DatabaseSchema::GetCharacterTables()) {
std::string table_name = iter.first;
std::string character_id_column_name = iter.second;
QueryDatabase(fmt::format("DELETE FROM {} WHERE {} = {}", table_name, character_id_column_name, character_id));
}
#ifdef BOTS
query = StringFormat("DELETE FROM `guild_members` WHERE `char_id` = '%d' AND GetMobTypeById(%i) = 'C'", character_id); // note: only use of GetMobTypeById()
#else
query = StringFormat("DELETE FROM `guild_members` WHERE `char_id` = '%d'", character_id);
#endif
QueryDatabase(query);
#endif
return true;
}

View File

@ -22,17 +22,76 @@
#define EQEMU_DATABASE_SCHEMA_H
#include <vector>
#include <map>
namespace DatabaseSchema {
/**
* Gets player tables
* Character-specific tables
*
* Does not included related meta-data tables such as 'guilds', 'accounts'
* @return
*/
static std::map<std::string, std::string> GetCharacterTables()
{
return {
{"adventure_stats", "player_id"},
{"buyer", "charid"},
{"char_recipe_list", "char_id"},
{"character_activities", "charid"},
{"character_alt_currency", "char_id"},
{"character_alternate_abilities", "id"},
{"character_auras", "id"},
{"character_bandolier", "id"},
{"character_bind", "id"},
{"character_buffs", "character_id"},
{"character_corpses", "id"},
{"character_currency", "id"},
{"character_data", "id"},
{"character_disciplines", "id"},
{"character_enabledtasks", "charid"},
{"character_inspect_messages", "id"},
{"character_item_recast", "id"},
{"character_languages", "id"},
{"character_leadership_abilities", "id"},
{"character_material", "id"},
{"character_memmed_spells", "id"},
{"character_pet_buffs", "char_id"},
{"character_pet_info", "char_id"},
{"character_pet_inventory", "char_id"},
{"character_potionbelt", "id"},
{"character_skills", "id"},
{"character_spells", "id"},
{"character_tasks", "charid"},
{"character_tribute", "id"},
{"completed_tasks", "charid"},
{"data_buckets", "id"},
{"faction_values", "char_id"},
{"friends", "charid"},
{"guild_members", "char_id"},
{"guilds", "id"},
{"instance_list_player", "id"},
{"inventory", "charid"},
{"inventory_snapshots", "charid"},
{"keyring", "char_id"},
{"mail", "charid"},
{"player_titlesets", "char_id"},
{"quest_globals", "charid"},
{"timers", "char_id"},
{"titles", "char_id"},
{"trader", "char_id"},
{"zone_flags", "charID"}
};
}
/**
* Gets all player and meta-data tables
*
* @return
*/
static std::vector<std::string> GetPlayerTables()
{
std::vector<std::string> tables = {
return {
"account",
"account_ip",
"account_flags",
@ -91,8 +150,6 @@ namespace DatabaseSchema {
"trader_audit",
"zone_flags"
};
return tables;
}
/**
@ -102,7 +159,7 @@ namespace DatabaseSchema {
*/
static std::vector<std::string> GetContentTables()
{
std::vector<std::string> tables = {
return {
"aa_ability",
"aa_actions",
"aa_effects",
@ -188,8 +245,6 @@ namespace DatabaseSchema {
"zone_server",
"zoneserver_auth",
};
return tables;
}
/**
@ -199,7 +254,7 @@ namespace DatabaseSchema {
*/
static std::vector<std::string> GetServerTables()
{
std::vector<std::string> tables = {
return {
"banned_ips",
"bugs",
"bug_reports",
@ -225,8 +280,6 @@ namespace DatabaseSchema {
"saylink",
"variables",
};
return tables;
}
/**
@ -237,7 +290,7 @@ namespace DatabaseSchema {
*/
static std::vector<std::string> GetStateTables()
{
std::vector<std::string> tables = {
return {
"adventure_members",
"chatchannels",
"group_id",
@ -253,8 +306,6 @@ namespace DatabaseSchema {
"spell_buckets",
"spell_globals",
};
return tables;
}
/**
@ -264,15 +315,13 @@ namespace DatabaseSchema {
*/
static std::vector<std::string> GetLoginTables()
{
std::vector<std::string> tables = {
return {
"login_accounts",
"login_api_tokens",
"login_server_admins",
"login_server_list_types",
"login_world_servers",
};
return tables;
}
/**
@ -282,12 +331,10 @@ namespace DatabaseSchema {
*/
static std::vector<std::string> GetVersionTables()
{
std::vector<std::string> tables = {
return {
"db_version",
"inventory_versions",
};
return tables;
}
}

View File

@ -115,14 +115,14 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
auto errorBuffer = new char[MYSQL_ERRMSG_SIZE];
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
/* Implement Logging at the Root */
/**
* Error logging
*/
if (mysql_errno(&mysql) > 0 && strlen(query) > 0) {
if (LogSys.log_settings[Logs::MySQLError].is_category_enabled == 1)
Log(Logs::General, Logs::MySQLError, "%i: %s \n %s", mysql_errno(&mysql), mysql_error(&mysql), query);
LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(&mysql), mysql_error(&mysql), query);
}
return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(&mysql), errorBuffer);
}
// successful query. get results.
@ -143,9 +143,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) {
if ((strncasecmp(query, "select", 6) == 0)) {
LogF(
Logs::General,
Logs::MySQLQuery,
LogMySQLQuery(
"{0} ({1} row{2} returned) ({3}s)",
query,
requestResult.RowCount(),
@ -154,9 +152,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
);
}
else {
LogF(
Logs::General,
Logs::MySQLQuery,
LogMySQLQuery(
"{0} ({1} row{2} affected) ({3}s)",
query,
requestResult.RowsAffected(),

View File

@ -317,6 +317,15 @@ namespace EQEmu
QuestControlGrid = -1
};
namespace consent {
enum eConsentType : uint8 {
Normal = 0,
Group,
Raid,
Guild
};
}; // namespace consent
} /*EQEmu*/
#endif /*COMMON_EMU_CONSTANTS_H*/

View File

@ -36,6 +36,7 @@ static const uint32 MAX_MERC_GRADES = 10;
static const uint32 MAX_MERC_STANCES = 10;
static const uint32 BLOCKED_BUFF_COUNT = 20;
static const uint32 QUESTREWARD_COUNT = 8;
static const uint32 ADVANCED_LORE_LENGTH = 8192;
/*
@ -2966,6 +2967,12 @@ struct ItemViewRequest_Struct {
/*046*/ char unknown046[2];
};
struct ItemAdvancedLoreText_Struct {
int32 item_id;
char item_name[64];
char advanced_lore[ADVANCED_LORE_LENGTH];
};
struct LDONItemViewRequest_Struct {
uint32 item_id;
uint8 unknown004[4];

View File

@ -124,6 +124,8 @@ void EQEmuLogSys::LoadLogSettingsDefaults()
log_settings[Logs::Loginserver].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::HeadlessClient].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::NPCScaling].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::HotReload].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::HotReload].log_to_console = static_cast<uint8>(Logs::General);
/**
* RFC 5424

View File

@ -114,6 +114,7 @@ namespace Logs {
EntityManagement,
Flee,
Aura,
HotReload,
MaxCategoryID /* Don't Remove this */
};
@ -187,6 +188,7 @@ namespace Logs {
"Entity Management",
"Flee",
"Aura",
"HotReload",
};
}

View File

@ -561,6 +561,16 @@
OutF(LogSys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogHotReload(message, ...) do {\
if (LogSys.log_settings[Logs::HotReload].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogHotReloadDetail(message, ...) do {\
if (LogSys.log_settings[Logs::HotReload].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@ -894,6 +904,12 @@
#define LogAuraDetail(message, ...) do {\
} while (0)
#define LogHotReload(message, ...) do {\
} while (0)
#define LogHotReloadDetail(message, ...) do {\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
} while (0)

View File

@ -1047,12 +1047,14 @@ void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t leng
uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
bool send_uncompressed = true;
if (length > 30) {
new_length = Deflate(buffer, (uint32_t)length, new_buffer + 1, 2048) + 1;
new_buffer[0] = 0x5a;
send_uncompressed = (new_length > length);
}
else {
if (send_uncompressed) {
memcpy(new_buffer + 1, buffer, length);
new_buffer[0] = 0xa5;
new_length = length + 1;
@ -1380,7 +1382,7 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
}
auto stream = &m_streams[stream_id];
auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size();
auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size() - 1; // -1 for compress flag
size_t length = p.Length();
if (length > max_raw_size) {
DaybreakReliableFragmentHeader first_header;

View File

@ -89,9 +89,9 @@ namespace EQ {
public:
StaticPacket(void *data, size_t size) { m_data = data; m_data_length = size; m_max_data_length = size; }
virtual ~StaticPacket() { }
StaticPacket(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; }
StaticPacket(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; m_max_data_length = o.m_max_data_length; }
StaticPacket& operator=(const StaticPacket &o) { m_data = o.m_data; m_data_length = o.m_data_length; return *this; }
StaticPacket(StaticPacket &&o) { m_data = o.m_data; m_data_length = o.m_data_length; }
StaticPacket(StaticPacket &&o) noexcept { m_data = o.m_data; m_data_length = o.m_data_length; }
virtual const void *Data() const { return m_data; }
virtual void *Data() { return m_data; }
@ -112,7 +112,7 @@ namespace EQ {
public:
DynamicPacket() { }
virtual ~DynamicPacket() { }
DynamicPacket(DynamicPacket &&o) { m_data = std::move(o.m_data); }
DynamicPacket(DynamicPacket &&o) noexcept { m_data = std::move(o.m_data); }
DynamicPacket(const DynamicPacket &o) { m_data = o.m_data; }
DynamicPacket& operator=(const DynamicPacket &o) { m_data = o.m_data; return *this; }
@ -127,4 +127,4 @@ namespace EQ {
std::vector<char> m_data;
};
}
}
}

View File

@ -4401,7 +4401,7 @@ struct SendAA_Struct {
/*0104*/ uint32 special_category;
/*0108*/ uint8 shroud;
/*0109*/ uint8 unknown109;
/*0110*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter?
/*0110*/ uint8 reset_on_death; // timer is reset on death
/*0111*/ uint8 unknown111;
/*0112*/ uint32 total_abilities;
/*0116*/ AA_Ability abilities[0];

View File

@ -4341,7 +4341,7 @@ struct SendAA_Struct {
/*0104*/ uint32 special_category;
/*0108*/ uint8 shroud;
/*0109*/ uint8 unknown109;
/*0110*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter?
/*0110*/ uint8 reset_on_death; // timer is reset on death
/*0111*/ uint8 unknown111;
/*0112*/ uint32 total_abilities;
/*0116*/ AA_Ability abilities[0];

View File

@ -3780,7 +3780,7 @@ struct SendAA_Struct {
/*0092*/ uint32 special_category;
/*0096*/ uint8 shroud;
/*0097*/ uint8 unknown97;
/*0098*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter?
/*0098*/ uint8 reset_on_death; // timer is reset on death
/*0099*/ uint8 unknown99;
/*0100*/ uint32 total_abilities;
/*0104*/ AA_Ability abilities[0];

View File

@ -3704,7 +3704,7 @@ struct SendAA_Struct {
/*0088*/ uint32 aa_expansion;
/*0092*/ uint32 special_category;
/*0096*/ uint8 shroud;
/*0097*/ uint8 unknown97;
/*0097*/ uint8 reset_on_death; // timer is reset on death -- guess
/*0098*/ uint32 total_abilities;
/*0102*/ AA_Ability abilities[0];
};

View File

@ -3835,7 +3835,7 @@ struct SendAA_Struct {
/*0092*/ uint32 special_category;
/*0096*/ uint8 shroud;
/*0097*/ uint8 unknown97;
/*0098*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter?
/*0098*/ uint8 reset_on_death; // timer is reset on death
/*0099*/ uint8 unknown99;
/*0100*/ uint32 total_abilities;
/*0104*/ AA_Ability abilities[0];

View File

@ -121,6 +121,7 @@ RULE_BOOL(Character, EnableAggroMeter, true, "Enable Aggro Meter, for users with
RULE_BOOL(Character, KeepLevelOverMax, false, "Don't delevel a character that has somehow gone over the level cap")
RULE_INT(Character, FoodLossPerUpdate, 32, "How much food/water you lose per stamina update")
RULE_BOOL(Character, EnableHungerPenalties, false, "being hungry/thirsty has negative effects -- it does appear normal live servers do not have penalties")
RULE_BOOL(Character, EnableFoodRequirement, true, "if disabled, food is no longer required")
RULE_INT(Character, BaseInstrumentSoftCap, 36, "Softcap for instrument mods, 36 commonly referred to as \"3.6\" as well")
RULE_BOOL(Character, UseSpellFileSongCap, true, "When they removed the AA that increased the cap they removed the above and just use the spell field")
RULE_INT(Character, BaseRunSpeedCap, 158, "Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225")
@ -159,6 +160,7 @@ RULE_BOOL(Character, PetsUseReagents, true, "Pets use reagent on spells")
RULE_BOOL(Character, DismountWater, true, "Dismount horses when entering water")
RULE_BOOL(Character, UseNoJunkFishing, false, "Disregards junk items when fishing")
RULE_BOOL(Character, SoftDeletes, true, "When characters are deleted in character select, they are only soft deleted")
RULE_INT(Character, DefaultGuild, 0, "If not 0, new characters placed into the guild # indicated")
RULE_CATEGORY_END()
RULE_CATEGORY(Mercs)
@ -195,6 +197,7 @@ RULE_INT(Skills, SwimmingStartValue, 100, "")
RULE_BOOL(Skills, TrainSenseHeading, false, "")
RULE_INT(Skills, SenseHeadingStartValue, 200, "")
RULE_BOOL(Skills, SelfLanguageLearning, true, "")
RULE_BOOL(Skills, RequireTomeHandin, false, "Disable click-to-learn and force turnin to Guild Master")
RULE_CATEGORY_END()
RULE_CATEGORY(Pets)
@ -294,6 +297,7 @@ RULE_BOOL(Pathing, Find, true, "Enable pathing for FindPerson requests from the
RULE_BOOL(Pathing, Fear, true, "Enable pathing for fear")
RULE_REAL(Pathing, NavmeshStepSize, 100.0f, "")
RULE_REAL(Pathing, ShortMovementUpdateRange, 130.0f, "")
RULE_INT(Pathing, MaxNavmeshNodes, 4092, "Max navmesh nodes in a traversable path")
RULE_CATEGORY_END()
RULE_CATEGORY(Watermap)
@ -492,6 +496,7 @@ RULE_INT(Combat, NPCAssistCapTimer, 6000, "Time in milliseconds a NPC will take
RULE_BOOL(Combat, UseRevampHandToHand, false, "use h2h revamped dmg/delays I believe this was implemented during SoF")
RULE_BOOL(Combat, ClassicMasterWu, false, "classic master wu uses a random special, modern doesn't")
RULE_INT(Combat, LevelToStopDamageCaps, 0, "1 will effectively disable them, 20 should give basically same results as old incorrect system")
RULE_INT(Combat, LevelToStopACTwinkControl, 50, "1 will effectively disable it, 50 should give basically same results as current system")
RULE_BOOL(Combat, ClassicNPCBackstab, false, "true disables npc facestab - npcs get normal attack if not behind")
RULE_BOOL(Combat, UseNPCDamageClassLevelMods, true, "Uses GetClassLevelDamageMod calc in npc_scale_manager")
RULE_BOOL(Combat, UseExtendedPoisonProcs, false, "Allow old school poisons to last until characrer zones, at a lower proc rate")
@ -523,6 +528,7 @@ RULE_INT(NPC, NPCGatePercent, 20, "% at which the NPC Will attempt to gate at")
RULE_BOOL(NPC, NPCGateNearBind, false, "Will NPC attempt to gate when near bind location?")
RULE_INT(NPC, NPCGateDistanceBind, 75, "Distance from bind before NPC will attempt to gate")
RULE_BOOL(NPC, NPCHealOnGate, true, "Will the NPC Heal on Gate")
RULE_BOOL(NPC, UseMeditateBasedManaRegen, false, "Based NPC ooc regen on Meditate skill")
RULE_REAL(NPC, NPCHealOnGateAmount, 25, "How much the npc will heal on gate if enabled")
RULE_CATEGORY_END()
@ -762,6 +768,12 @@ RULE_CATEGORY(Logging)
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
RULE_CATEGORY_END()
RULE_CATEGORY(HotReload)
RULE_BOOL(HotReload, QuestsRepopWithReload, true, "When a hot reload is triggered, the zone will repop")
RULE_BOOL(HotReload, QuestsRepopWhenPlayersNotInCombat, true, "When a hot reload is triggered, the zone will repop when no clients are in combat")
RULE_BOOL(HotReload, QuestsResetTimersWithReload, true, "When a hot reload is triggered, quest timers will be reset")
RULE_CATEGORY_END()
#undef RULE_CATEGORY
#undef RULE_INT
#undef RULE_REAL

View File

@ -197,7 +197,11 @@
#define ServerOP_CZSetEntityVariableByClientName 0x4012
#define ServerOP_UCSServerStatusRequest 0x4013
#define ServerOP_UCSServerStatusReply 0x4014
/* Query Server OP Codes */
#define ServerOP_HotReloadQuests 0x4015
/**
* QueryServer
*/
#define ServerOP_QSPlayerLogTrades 0x5010
#define ServerOP_QSPlayerLogHandins 0x5011
#define ServerOP_QSPlayerLogNPCKills 0x5012
@ -866,10 +870,12 @@ struct SpawnPlayerCorpse_Struct {
struct ServerOP_Consent_Struct {
char grantname[64];
char ownername[64];
char zonename[32];
uint8 permission;
uint32 zone_id;
uint16 instance_id;
uint32 message_string_id;
uint8 consent_type; // 0 = normal, 1 = group, 2 = raid, 3 = guild
uint32 consent_id;
};
struct ReloadTasks_Struct {
@ -1349,12 +1355,16 @@ struct CZSetEntVarByClientName_Struct {
char m_var[256];
};
struct ReloadWorld_Struct{
struct ReloadWorld_Struct {
uint32 Option;
};
struct HotReloadQuestsStruct {
char zone_short_name[200];
};
struct ServerRequestTellQueue_Struct {
char name[64];
char name[64];
};
struct UCSServerStatus_Struct {

View File

@ -34,7 +34,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9147
#define CURRENT_BINARY_DATABASE_VERSION 9151
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9026

View File

@ -331,7 +331,7 @@ OP_LDoNButton=0x596e
OP_SetStartCity=0x7936 # Was 0x2d1b
OP_VoiceMacroIn=0x202e
OP_VoiceMacroOut=0x3920
OP_ItemViewUnknown=0x0b64
OP_ItemAdvancedLoreText=0x0b64
OP_VetRewardsAvaliable=0x05d9
OP_VetClaimRequest=0xcdde
OP_VetClaimReply=0x361b

View File

@ -448,6 +448,8 @@ OP_FinishWindow2=0x40ef
OP_ItemVerifyRequest=0x189c
OP_ItemVerifyReply=0x097b
OP_ItemAdvancedLoreText=0x023b
# merchant stuff
OP_ShopPlayerSell=0x791b
OP_ShopRequest=0x4fed

View File

@ -327,7 +327,7 @@ OP_LDoNButton=0x41b5 # C
OP_SetStartCity=0x7bf6 # C
OP_VoiceMacroIn=0x31b1 # C
OP_VoiceMacroOut=0x7880 # C
OP_ItemViewUnknown=0x21c7 # C
OP_ItemAdvancedLoreText=0x21c7 # C
OP_VetRewardsAvaliable=0x4e4e # C
OP_VetClaimRequest=0x771f # C
OP_VetClaimReply=0x2f95 # C

View File

@ -576,7 +576,7 @@ OP_QueryResponseThing=0x0000 #
# realityincarnate: these are just here to stop annoying several thousand byte packet dumps
OP_LoginUnknown1=0x22cf
OP_LoginUnknown2=0x43ba
OP_ItemViewUnknown=0x4db4
OP_ItemAdvancedLoreText=0x4db4
#Petition Opcodes
OP_PetitionSearch=0x0000 #search term for petition

View File

@ -336,7 +336,7 @@ OP_LDoNButton=0x1031 # C
OP_SetStartCity=0x68f0 # C
OP_VoiceMacroIn=0x1524 # C
OP_VoiceMacroOut=0x1d99 # C
OP_ItemViewUnknown=0x4eb3 # C
OP_ItemAdvancedLoreText=0x4eb3 # C
OP_VetRewardsAvaliable=0x0baa # C Mispelled?
OP_VetClaimRequest=0x34f8 # C
OP_VetClaimReply=0x6a5d # C

View File

@ -493,9 +493,7 @@ sub do_installer_routines {
#::: Download PEQ latest
fetch_peq_db_full();
print "[Database] Fetching Latest Database Updates...\n";
main_db_management();
print "[Database] Applying Latest Database Updates...\n";
print "[Database] Fetching and Applying Latest Database Updates...\n";
main_db_management();
remove_duplicate_rule_values();
@ -531,31 +529,7 @@ sub check_for_world_bootup_database_update {
}
$binary_database_version = trim($db_version[1]);
$local_database_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1"));
#::: Bots
$bots_binary_version = trim($db_version[2]);
if ($bots_binary_version > 0) {
$bots_local_db_version = get_bots_db_version();
#::: We ran world - Database needs to update, lets backup and run updates and continue world bootup
if ($bots_local_db_version < $bots_binary_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots Database not up to date with binaries... Automatically updating...\n";
print "[Update] Issuing database backup first...\n";
database_dump_compress();
print "[Update] Updating bots database...\n";
sleep(1);
bots_db_management();
run_database_check();
print "[Update] Continuing bootup\n";
analytics_insertion("auto database bots upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
exit;
}
else {
print "[Update] Bots database up to Date: Continuing World Bootup...\n";
}
}
$local_database_version = get_main_db_version();
if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Database up to date...\n";
@ -567,22 +541,61 @@ sub check_for_world_bootup_database_update {
print "[Update] Database not up to date with binaries... Automatically updating...\n";
print "[Update] Issuing database backup first...\n";
database_dump_compress();
$db_already_backed_up = 1;
print "[Update] Updating database...\n";
sleep(1);
main_db_management();
main_db_management();
print "[Update] Continuing bootup\n";
analytics_insertion("auto database upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
exit;
}
#::: Make sure that we didn't pass any arugments to the script
else {
if ($local_database_version > $binary_database_version) {
print "[Update] Database version is ahead of current binaries...\n";
}
if (!$db) { print "[eqemu_server.pl] No database connection found... Running without\n"; }
show_menu_prompt();
}
}
#::: Bots
$binary_database_version = trim($db_version[2]);
if ($binary_database_version > 0) {
$local_database_version = get_bots_db_version();
#::: We ran world - Database needs to update, lets backup and run updates and continue world bootup
if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots database up to date...\n";
}
else {
if ($local_database_version < $binary_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots Database not up to date with binaries... Automatically updating...\n";
if (!$db_already_backed_up) {
print "[Update] Issuing database backup first...\n";
database_dump_compress();
}
print "[Update] Updating bots database...\n";
sleep(1);
bots_db_management();
analytics_insertion("auto database bots upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
}
#::: Make sure that we didn't pass any arugments to the script
else {
if ($local_database_version > $binary_database_version) {
print "[Update] Bots database version is ahead of current binaries...\n";
}
if (!$db) { print "[eqemu_server.pl] No database connection found... Running without\n"; }
show_menu_prompt();
}
}
}
print "[Update] Continuing bootup\n";
}
sub check_internet_connection {
@ -629,7 +642,7 @@ sub do_self_update_check_routine {
#::: Check for internet connection before updating
if (!$has_internet_connection) {
print "[Update] Cannot check update without internet connection...\n";
print "[Update] Cannot check self-update without internet connection...\n";
return;
}
@ -819,7 +832,6 @@ sub setup_bots {
build_linux_source("bots");
}
bots_db_management();
run_database_check();
print "Bots should be setup, run your server and the bot command should be available in-game (type '^help')\n";
}
@ -953,13 +965,11 @@ sub show_menu_prompt {
$dc = 1;
}
elsif ($input eq "check_db_updates") {
main_db_management();
main_db_management();
$dc = 1;
}
elsif ($input eq "check_bot_db_updates") {
bots_db_management();
run_database_check();
$dc = 1;
}
elsif ($input eq "setup_loginserver") {
@ -1400,6 +1410,7 @@ sub remove_duplicate_rule_values {
sub copy_file {
$l_source_file = $_[0];
$l_destination_file = $_[1];
if ($l_destination_file =~ /\//i) {
my @directory_path = split('/', $l_destination_file);
$build_path = "";
@ -1418,6 +1429,7 @@ sub copy_file {
$directory_index++;
}
}
copy $l_source_file, $l_destination_file;
}
@ -2001,15 +2013,6 @@ sub do_bots_db_schema_drop {
print get_mysql_result_from_file("db_update/drop_bots.sql");
print "[Database] Removing bot database tables...\n";
print get_mysql_result("DELETE FROM `rule_values` WHERE `rule_name` LIKE 'Bots:%';");
if (get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && $db) {
print get_mysql_result("DELETE FROM `commands` WHERE `command` LIKE 'bot';");
}
if (get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && $db) {
print get_mysql_result("DELETE FROM `command_settings` WHERE `command` LIKE 'bot';");
}
if (get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db) {
print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;");
@ -2043,70 +2046,6 @@ sub modify_db_for_bots {
}
print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY USING BTREE(`groupid`, `charid`, `name`, `ismerc`);");
if (get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && get_mysql_result("SELECT `command` FROM `command_settings` WHERE `command` LIKE 'bot'") eq "" && $db) {
print get_mysql_result("INSERT INTO `command_settings` VALUES ('bot', '0', '');");
}
if (get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && get_mysql_result("SELECT `command` FROM `commands` WHERE `command` LIKE 'bot'") eq "" && $db) {
print get_mysql_result("INSERT INTO `commands` VALUES ('bot', '0');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotAAExpansion'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:AAExpansion' WHERE `rule_name` LIKE 'Bots:BotAAExpansion';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:AAExpansion'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:AAExpansion', '8', 'The expansion through which bots will obtain AAs');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreateBotCount'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:CreationLimit' WHERE `rule_name` LIKE 'Bots:CreateBotCount';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreationLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:CreationLimit', '150', 'Number of bots that each account can create');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotFinishBuffing'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:FinishBuffing' WHERE `rule_name` LIKE 'Bots:BotFinishBuffing';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:FinishBuffing'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:FinishBuffing', 'false', 'Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotGroupBuffing'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:GroupBuffing' WHERE `rule_name` LIKE 'Bots:BotGroupBuffing';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:GroupBuffing'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:GroupBuffing', 'false', 'Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotManaRegen'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:ManaRegen' WHERE `rule_name` LIKE 'Bots:BotManaRegen';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:ManaRegen'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:ManaRegen', '3.0', 'Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotQuest'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpawnLimit' WHERE `rule_name` LIKE 'Bots:BotQuest';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpawnLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpawnLimit', 'false', 'Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotSpellQuest'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpells' WHERE `rule_name` LIKE 'Bots:BotSpellQuest';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpells'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpells', 'false', 'Anita Thrall\\\'s (Anita_Thrall.pl) Bot Spell Scriber quests.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnBotCount'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:SpawnLimit' WHERE `rule_name` LIKE 'Bots:SpawnBotCount';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:SpawnLimit', '71', 'Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid');");
}
convert_existing_bot_data();
}
@ -2205,54 +2144,49 @@ sub convert_existing_bot_data {
}
}
sub get_main_db_version {
$main_local_db_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1"));
return $main_local_db_version;
}
sub get_bots_db_version {
#::: Check if bots_version column exists...
if (get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'bots_version'") eq "" && $db) {
print get_mysql_result("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version;");
print "[Database] Column 'bots_version' does not exists.... Adding to 'db_version' table...\n\n";
}
$bots_local_db_version = trim(get_mysql_result("SELECT bots_version FROM db_version LIMIT 1"));
return $bots_local_db_version;
}
#::: Safe for call from world startup or menu option
sub bots_db_management {
my $world_path = "world";
if (-e "bin/world") {
$world_path = "bin/world";
}
#::: Get Binary DB version
if ($OS eq "Windows") {
@db_version = split(': ', `$world_path db_version`);
}
if ($OS eq "Linux") {
@db_version = split(': ', `./$world_path db_version`);
}
#::: Main Binary Database version
$binary_database_version = trim($db_version[2]);
#::: If we have stale data from main db run
if ($db_run_stage > 0 && $bots_db_management == 0) {
clear_database_runs();
}
#::: Main Binary Database version
$binary_database_version = trim($db_version[2]);
if ($binary_database_version == 0) {
print "[Database] Your server binaries (world/zone) are not compiled for bots...\n\n";
return;
}
$local_database_version = get_bots_db_version();
#::: Set on flag for running bot updates...
$bots_db_management = 1;
$bots_local_db_version = get_bots_db_version();
$local_database_version = $bots_local_db_version;
if ($local_database_version > $binary_database_version) {
print "[Update] Bots database version is ahead of current binaries...\n";
return;
}
run_database_check();
}
#::: Safe for call from world startup or menu option
sub main_db_management {
#::: If we have stale data from bots db run
if ($db_run_stage > 0 && $bots_db_management == 1) {
@ -2261,8 +2195,15 @@ sub main_db_management {
#::: Main Binary Database version
$binary_database_version = trim($db_version[1]);
$local_database_version = get_main_db_version();
$bots_db_management = 0;
if ($local_database_version > $binary_database_version) {
print "[Update] Database version is ahead of current binaries...\n";
return;
}
run_database_check();
}
@ -2272,148 +2213,70 @@ sub clear_database_runs {
%m_d = ();
#::: Clear updates...
@total_updates = ();
#::: Clear stage
$db_run_stage = 0;
}
#::: Responsible for Database Upgrade Routines
sub run_database_check {
if (!$db) {
print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n";
return;
}
if (!@total_updates) {
#::: Pull down bots database manifest
if ($bots_db_management == 1) {
print "[Database] Retrieving latest bots database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
#::: Pull down mainstream database manifest
else {
print "[Database] Retrieving latest database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
#::: Pull down bots database manifest
if ($bots_db_management == 1) {
print "[Database] Retrieving latest bots database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
#::: Run 2 - Running pending updates...
if (@total_updates || $db_run_stage == 1) {
@total_updates = sort @total_updates;
foreach my $val (@total_updates) {
$file_name = trim($m_d{$val}[1]);
print "[Database] Running Update: " . $val . " - " . $file_name . "\n";
print get_mysql_result_from_file("db_update/$file_name");
print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val");
if ($bots_db_management == 1 && $val == 9000) {
modify_db_for_bots();
}
if ($val == 9138) {
fix_quest_factions();
}
}
$db_run_stage = 2;
}
#::: Run 1 - Initial checking of needed updates...
#::: Pull down mainstream database manifest
else {
print "[Database] Reading manifest...\n";
use Data::Dumper;
open(FILE, "db_update/db_update_manifest.txt");
while (<FILE>) {
chomp;
$o = $_;
if ($o =~ /#/i) {
next;
}
@manifest = split('\|', $o);
$m_d{$manifest[0]} = [ @manifest ];
}
#::: Setting Manifest stage...
$db_run_stage = 1;
print "[Database] Retrieving latest database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
@total_updates = ();
#::: Parse manifest
print "[Database] Reading manifest...\n";
use Data::Dumper;
open(FILE, "db_update/db_update_manifest.txt");
while (<FILE>) {
chomp;
$o = $_;
if ($o =~ /#/i) {
next;
}
@manifest = split('\|', $o);
$m_d{$manifest[0]} = [ @manifest ];
}
#::: This is where we set checkpoints for where a database might be so we don't check so far back in the manifest...
if ($local_database_version >= 9000) {
$revision_check = $local_database_version;
$revision_check = $local_database_version + 1;
}
else {
#::: This does not negatively affect bots
$revision_check = 1000;
if (get_mysql_result("SHOW TABLES LIKE 'character_data'") ne "") {
$revision_check = 8999;
}
}
#::: Iterate through Manifest backwards from binary version down to local version...
@total_updates = ();
#::: Fetch and register sqls for this database update cycle
for ($i = $revision_check; $i <= $binary_database_version; $i++) {
if (!defined($m_d{$i}[0])) {
next;
}
$file_name = trim($m_d{$i}[1]);
$query_check = trim($m_d{$i}[2]);
$match_type = trim($m_d{$i}[3]);
$match_text = trim($m_d{$i}[4]);
#::: Match type update
if ($match_type eq "contains") {
if (trim(get_mysql_result($query_check)) =~ /$match_text/i) {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "missing") {
if (get_mysql_result($query_check) =~ /$match_text/i) {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
next;
}
else {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
print_match_debug();
print_break();
}
if ($match_type eq "empty") {
if (get_mysql_result($query_check) eq "") {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "not_empty") {
if (get_mysql_result($query_check) ne "") {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
$file_name = trim($m_d{$i}[1]);
print "[Database] fetching update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
print "\n";
if (scalar(@total_updates) == 0 && $db_run_stage == 2) {
if (scalar(@total_updates) == 0) {
print "[Database] No updates need to be run...\n";
if ($bots_db_management == 1) {
print "[Database] Setting Database to Bots Binary Version (" . $binary_database_version . ") if not already...\n\n";
@ -2423,24 +2286,106 @@ sub run_database_check {
print "[Database] Setting Database to Binary Version (" . $binary_database_version . ") if not already...\n\n";
get_mysql_result("UPDATE db_version SET version = $binary_database_version ");
}
clear_database_runs();
return;
}
#::: Execute pending updates
@total_updates = sort @total_updates;
foreach my $val (@total_updates) {
$file_name = trim($m_d{$val}[1]);
$query_check = trim($m_d{$val}[2]);
$match_type = trim($m_d{$val}[3]);
$match_text = trim($m_d{$val}[4]);
#::: Match type update
if ($match_type eq "contains") {
if (trim(get_mysql_result($query_check)) =~ /$match_text/i) {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
print_match_debug();
print_break();
}
if ($match_type eq "missing") {
if (get_mysql_result($query_check) =~ /$match_text/i) {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
else {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
print_match_debug();
print_break();
}
if ($match_type eq "empty") {
if (get_mysql_result($query_check) eq "") {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "not_empty") {
if (get_mysql_result($query_check) ne "") {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
print_match_debug();
print_break();
}
if ($bots_db_management == 1) {
print get_mysql_result("UPDATE db_version SET bots_version = $val WHERE bots_version < $val");
if ($val == 9000) {
modify_db_for_bots();
}
}
else {
print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val");
if ($val == 9138) {
fix_quest_factions();
}
}
}
if ($bots_db_management == 1) {
print "[Database] Bots database update cycle complete at version [" . get_bots_db_version() . "]\n";
}
else {
print "[Database] Mainstream database update cycle complete at version [" . get_main_db_version() . "]\n";
}
}
sub fetch_missing_db_update {
$db_update = $_[0];
$update_file = $_[1];
if ($db_update >= 9000) {
if ($bots_db_management == 1) {
if ($bots_db_management == 1) {
if ($db_update >= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/required/" . $update_file, "db_update/" . $update_file . "");
}
else {
}
else {
if ($db_update >= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/git/required/" . $update_file, "db_update/" . $update_file . "");
}
}
elsif ($db_update >= 5000 && $db_update <= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/svn/" . $update_file, "db_update/" . $update_file . "");
elsif ($db_update >= 5000 && $db_update <= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/svn/" . $update_file, "db_update/" . $update_file . "");
}
}
}

View File

@ -401,6 +401,10 @@
9145|2019_12_24_banned_ips_update.sql|SHOW TABLES LIKE 'Banned_IPs'|not_empty|
9146|2020_01_10_character_soft_deletes.sql|SHOW COLUMNS FROM `character_data` LIKE 'deleted_at'|empty|
9147|2020_01_24_grid_centerpoint_wp.sql|SHOW COLUMNS FROM `grid_entries` LIKE 'centerpoint'|empty|
9148|2020_01_28_corpse_guild_consent_id.sql|SHOW COLUMNS FROM `character_corpses` LIKE 'guild_consent_id'|empty|
9149|2020_02_06_globalloot.sql|SHOW COLUMNS FROM `global_loot` LIKE 'hot_zone'|empty|
9150|2020_02_06_aa_reset_on_death.sql|SHOW COLUMNS FROM `aa_ability` LIKE 'reset_on_death'|empty|
9151|2020_03_05_npc_always_aggro.sql|SHOW COLUMNS FROM `npc_types` LIKE 'always_aggro'|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 @@
ALTER TABLE `character_corpses` ADD COLUMN `guild_consent_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_of_death`;

View File

@ -0,0 +1,2 @@
ALTER TABLE `aa_ability` ADD `reset_on_death` TINYINT(4) NOT NULL DEFAULT '0';
UPDATE `aa_ability` SET `reset_on_death` = '1' WHERE `id` = 6001;

View File

@ -0,0 +1,2 @@
ALTER TABLE `global_loot` ADD `hot_zone` TINYINT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE `npc_types` ADD COLUMN `always_aggro` tinyint(1) NOT NULL DEFAULT 0;

View File

@ -802,9 +802,9 @@ void ConsoleIpLookup(
const std::vector<std::string> &args
)
{
if (args.size() > 0) {
if (!args.empty()) {
WorldConsoleTCPConnection console_connection(connection);
client_list.SendCLEList(connection->Admin(), 0, &console_connection, args[0].c_str());
client_list.SendCLEList(connection->Admin(), nullptr, &console_connection, args[0].c_str());
}
}
@ -855,6 +855,34 @@ void ConsoleReloadWorld(
safe_delete(pack);
}
/**
* @param connection
* @param command
* @param args
*/
void ConsoleReloadZoneQuests(
EQ::Net::ConsoleServerConnection *connection,
const std::string &command,
const std::vector<std::string> &args
)
{
if (args.empty()) {
connection->SendLine("[zone_short_name] required as argument");
return;
}
std::string zone_short_name = args[0];
connection->SendLine(fmt::format("Reloading Zone [{}]...", zone_short_name));
auto pack = new ServerPacket(ServerOP_HotReloadQuests, sizeof(HotReloadQuestsStruct));
auto *hot_reload_quests = (HotReloadQuestsStruct *) pack->pBuffer;
strn0cpy(hot_reload_quests->zone_short_name, (char *) zone_short_name.c_str(), 200);
zoneserver_list.SendPacket(pack);
safe_delete(pack);
}
/**
* @param connection
* @param command
@ -892,18 +920,19 @@ void RegisterConsoleFunctions(std::unique_ptr<EQ::Net::ConsoleServer>& console)
console->RegisterCall("md5", 50, "md5", std::bind(ConsoleMd5, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("ooc", 50, "ooc [message]", std::bind(ConsoleOOC, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("reloadworld", 200, "reloadworld", std::bind(ConsoleReloadWorld, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("setpass", 200, "setpass [accountname] [newpass]", std::bind(ConsoleSetPass, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("reloadzonequests", 200, "reloadzonequests [zone_short_name]", std::bind(ConsoleReloadZoneQuests, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("setpass", 200, "setpass [account_name] [new_password]", std::bind(ConsoleSetPass, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("signalcharbyname", 50, "signalcharbyname charname ID", std::bind(ConsoleSignalCharByName, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("tell", 50, "tell [name] [message]", std::bind(ConsoleTell, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("unlock", 150, "unlock", std::bind(ConsoleUnlock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("uptime", 50, "uptime [zoneID#]", std::bind(ConsoleUptime, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("uptime", 50, "uptime [zone_server_id]", std::bind(ConsoleUptime, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("version", 50, "version", std::bind(ConsoleVersion, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("who", 50, "who", std::bind(ConsoleWho, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("whoami", 50, "whoami", std::bind(ConsoleWhoami, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("worldshutdown", 200, "worldshutdown", std::bind(ConsoleWorldShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonebootup", 150, "zonebootup [ZoneServerID] [zonename]", std::bind(ConsoleZoneBootup, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonelock", 150, "zonelock [list|lock|unlock] [zonename]", std::bind(ConsoleZoneLock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zoneshutdown", 150, "zoneshutdown [zonename or ZoneServerID]", std::bind(ConsoleZoneShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonebootup", 150, "zonebootup [zone_server_id] [zone_short_name]", std::bind(ConsoleZoneBootup, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonelock", 150, "zonelock [list|lock|unlock] [zone_short_name]", std::bind(ConsoleZoneLock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zoneshutdown", 150, "zoneshutdown [zone_short_name or zone_server_id]", std::bind(ConsoleZoneShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonestatus", 50, "zonestatus", std::bind(ConsoleZoneStatus, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));console->RegisterCall("ping", 50, "ping", std::bind(ConsoleNull, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("quit", 50, "quit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("exit", 50, "exit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

View File

@ -1057,110 +1057,42 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
break;
}
case ServerOP_Consent: {
// Message string id's likely to be used here are:
// CONSENT_YOURSELF = 399
// CONSENT_INVALID_NAME = 397
// TARGET_NOT_FOUND = 101
ZoneServer* zs;
ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer;
ClientListEntry* cle = client_list.FindCharacter(s->grantname);
if (cle) {
if (cle->instance() != 0)
{
zs = zoneserver_list.FindByInstanceID(cle->instance());
if (zs) {
zs->SendPacket(pack);
}
else
{
auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strcpy(scs->grantname, s->grantname);
strcpy(scs->ownername, s->ownername);
scs->permission = s->permission;
scs->zone_id = s->zone_id;
scs->instance_id = s->instance_id;
scs->message_string_id = 101;
zs = zoneserver_list.FindByInstanceID(s->instance_id);
if (zs) {
zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->instance_id);
}
safe_delete(pack);
}
}
else
{
zs = zoneserver_list.FindByZoneID(cle->zone());
if (zs) {
zs->SendPacket(pack);
}
else {
// send target not found back to requester
auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strcpy(scs->grantname, s->grantname);
strcpy(scs->ownername, s->ownername);
scs->permission = s->permission;
scs->zone_id = s->zone_id;
scs->message_string_id = 101;
zs = zoneserver_list.FindByZoneID(s->zone_id);
if (zs) {
zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id);
}
safe_delete(pack);
}
}
}
else {
// send target not found back to requester
auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strcpy(scs->grantname, s->grantname);
strcpy(scs->ownername, s->ownername);
scs->permission = s->permission;
scs->zone_id = s->zone_id;
scs->message_string_id = 397;
zs = zoneserver_list.FindByZoneID(s->zone_id);
if (zs) {
zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id);
}
safe_delete(pack);
}
zoneserver_list.SendPacket(pack); // update corpses in all zones
break;
}
case ServerOP_Consent_Response: {
// Message string id's likely to be used here are:
// CONSENT_YOURSELF = 399
// CONSENT_INVALID_NAME = 397
// TARGET_NOT_FOUND = 101
ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer;
if (s->instance_id != 0)
{
ZoneServer* zs = zoneserver_list.FindByInstanceID(s->instance_id);
if (zs) {
zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->instance_id);
}
ZoneServer* owner_zs = nullptr;
if (s->instance_id == 0) {
owner_zs = zoneserver_list.FindByZoneID(s->zone_id);
}
else
{
ZoneServer* zs = zoneserver_list.FindByZoneID(s->zone_id);
if (zs) {
zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for zone id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id);
else {
owner_zs = zoneserver_list.FindByInstanceID(s->instance_id);
}
if (owner_zs) {
owner_zs->SendPacket(pack);
}
else {
LogInfo("Unable to locate zone record for zone id [{}] or instance id [{}] in zoneserver list for ServerOP_Consent_Response operation", s->zone_id, s->instance_id);
}
if (s->consent_type == EQEmu::consent::Normal) {
// send the message to the client being granted or denied permission
ClientListEntry* cle = client_list.FindCharacter(s->grantname);
if (cle) {
ZoneServer* granted_zs = nullptr;
if (cle->instance() == 0) {
granted_zs = zoneserver_list.FindByZoneID(cle->zone());
}
else {
granted_zs = zoneserver_list.FindByInstanceID(cle->instance());
}
// avoid sending twice if owner and granted are in same zone
if (granted_zs && granted_zs != owner_zs) {
granted_zs->SendPacket(pack);
}
}
}
break;

View File

@ -141,6 +141,7 @@ SET(zone_sources
zone.cpp
zone_config.cpp
zonedb.cpp
zone_reload.cpp
zoning.cpp
)
@ -247,7 +248,8 @@ SET(zone_headers
zone.h
zone_config.h
zonedb.h
zonedump.h)
zonedump.h
zone_reload.h )
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})

View File

@ -1023,6 +1023,24 @@ void Client::ResetAlternateAdvancementTimers() {
safe_delete(outapp);
}
void Client::ResetOnDeathAlternateAdvancement() {
for (const auto &aa : aa_ranks) {
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
auto ability = ability_rank.first;
auto rank = ability_rank.second;
if (!ability)
continue;
if (!rank)
continue;
// since they're dying, we just need to clear the DB
if (ability->reset_on_death)
p_timers.Clear(&database, rank->spell_type + pTimerAAStart);
}
}
void Client::PurchaseAlternateAdvancementRank(int rank_id) {
AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id);
if(!rank) {
@ -1646,7 +1664,7 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map<int, std
LogInfo("Loading Alternate Advancement Abilities");
abilities.clear();
std::string query = "SELECT id, name, category, classes, races, deities, drakkin_heritage, status, type, charges, "
"grant_only, first_rank_id FROM aa_ability WHERE enabled = 1";
"grant_only, reset_on_death, first_rank_id FROM aa_ability WHERE enabled = 1";
auto results = QueryDatabase(query);
if(results.Success()) {
for(auto row = results.begin(); row != results.end(); ++row) {
@ -1663,7 +1681,8 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map<int, std
ability->type = atoi(row[8]);
ability->charges = atoi(row[9]);
ability->grant_only = atoi(row[10]) != 0 ? true : false;
ability->first_rank_id = atoi(row[11]);
ability->reset_on_death = atoi(row[11]) != 0 ? true : false;
ability->first_rank_id = atoi(row[12]);
ability->first = nullptr;
abilities[ability->id] = std::unique_ptr<AA::Ability>(ability);

View File

@ -50,6 +50,7 @@ public:
int drakkin_heritage;
int status;
bool grant_only;
bool reset_on_death;
int type;
int charges;
int first_rank_id;

View File

@ -139,7 +139,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) {
if (RuleB(Aggro, UseLevelAggro))
{
if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3)
if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3 && !AlwaysAggro())
{
towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2);
return;
@ -147,7 +147,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) {
}
else
{
if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY ) {
if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY && !AlwaysAggro()) {
towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(),
dist2, iAggroRange2);
return;
@ -318,7 +318,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//old InZone check taken care of above by !mob->CastToClient()->Connected()
(
( GetLevel() >= RuleI(Aggro, MinAggroLevel))
||(GetBodyType() == 3)
||(GetBodyType() == 3) || AlwaysAggro()
||( mob->IsClient() && mob->CastToClient()->IsSitting() )
||( mob->GetLevelCon(GetLevel()) != CON_GRAY)
@ -352,6 +352,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//old InZone check taken care of above by !mob->CastToClient()->Connected()
(
( GetINT() <= RuleI(Aggro, IntAggroThreshold) )
|| AlwaysAggro()
||( mob->IsClient() && mob->CastToClient()->IsSitting() )
||( mob->GetLevelCon(GetLevel()) != CON_GRAY)
@ -383,6 +384,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
LogAggro("Dist^2: [{}]\n", dist2);
LogAggro("Range^2: [{}]\n", iAggroRange2);
LogAggro("Faction: [{}]\n", fv);
LogAggro("AlwaysAggroFlag: [{}]\n", AlwaysAggro());
LogAggro("Int: [{}]\n", GetINT());
LogAggro("Con: [{}]\n", GetLevelCon(mob->GetLevel()));

View File

@ -799,7 +799,7 @@ int Mob::ACSum()
// EQ math
ac = (ac * 4) / 3;
// anti-twink
if (IsClient() && GetLevel() < 50)
if (IsClient() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl))
ac = std::min(ac, 25 + 6 * GetLevel());
ac = std::max(0, ac + GetClassRaceACBonus());
if (IsNPC()) {
@ -1852,6 +1852,17 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk
BuffFadeDetrimental();
}
/*
Reset AA reuse timers that need to be, live-like this is only Lay on Hands
*/
ResetOnDeathAlternateAdvancement();
/*
Reset reuse timer for classic skill based Lay on Hands (For tit I guess)
*/
if (GetClass() == PALADIN) // we could check if it's not expired I guess, but should be fine not to
p_timers.Clear(&database, pTimerLayHands);
/*
Finally, send em home

View File

@ -56,7 +56,7 @@ Beacon::Beacon(Mob *at_mob, int lifetime)
:Mob
(
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, at_mob->GetPosition(), 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false
),
remove_timer(lifetime),
spell_timer(0)

View File

@ -6421,32 +6421,52 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
bool taunt_time = taunt_timer.Check();
bool ca_time = classattack_timer.Check(false);
bool ma_time = monkattack_timer.Check(false);
bool ka_time = knightattack_timer.Check(false);
if((taunt_time || ca_time || ka_time) && !IsAttackAllowed(target))
if (taunt_time) {
// Bots without this skill shouldn't be 'checking' on this timer..let's just disable it and avoid the extra IsAttackAllowed() checks
// Note: this is done here instead of NPC::ctor() because taunt skill can be acquired during level ups (the timer is re-enabled in CalcBotStats())
if (!GetSkill(EQEmu::skills::SkillTaunt)) {
taunt_timer.Disable();
return;
}
if (!IsAttackAllowed(target)) {
return;
}
}
if ((ca_time || ma_time || ka_time) && !IsAttackAllowed(target)) {
return;
}
if(ka_time){
int knightreuse = 1000;
switch(GetClass()){
case SHADOWKNIGHT:
case SHADOWKNIGHTGM: {
case SHADOWKNIGHT: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
knightreuse = (HarmTouchReuseTime * 1000);
knightattack_timer.Start(HarmTouchReuseTime * 1000);
break;
}
case PALADIN:
case PALADINGM: {
case PALADIN: {
if(GetHPRatio() < 20) {
CastSpell(SPELL_LAY_ON_HANDS, GetID());
knightreuse = (LayOnHandsReuseTime * 1000);
knightattack_timer.Start(LayOnHandsReuseTime * 1000);
}
else {
knightattack_timer.Start(2000);
}
else
knightreuse = 2000;
break;
}
default: {
break;
}
}
knightattack_timer.Start(knightreuse);
}
if(taunting && target && target->IsNPC() && taunt_time) {
@ -6457,8 +6477,66 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
}
if(!ca_time)
if (ma_time) {
switch (GetClass()) {
case MONK: {
int reuse = (MonkSpecialAttack(target, EQEmu::skills::SkillTigerClaw) - 1);
// Live AA - Technique of Master Wu
int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if (wuchance) {
const int MonkSPA[5] = {
EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) {
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
Mob* bo = GetBotOwner();
if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
bo->Message(
GENERIC_EMOTE,
"The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
GetCleanName(),
GetCleanName(),
extra
);
}
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQEmu::skills::SkillTigerClaw));
--extra;
}
}
float HasteModifier = (GetHaste() * 0.01f);
monkattack_timer.Start((reuse * 1000) / HasteModifier);
break;
}
default:
break;;
}
}
if (!ca_time) {
return;
}
float HasteModifier = (GetHaste() * 0.01f);
uint16 skill_to_use = -1;
@ -6493,18 +6571,22 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
break;
case MONK:
if(GetLevel() >= 30)
if (GetLevel() >= 30) {
skill_to_use = EQEmu::skills::SkillFlyingKick;
else if(GetLevel() >= 25)
}
else if (GetLevel() >= 25) {
skill_to_use = EQEmu::skills::SkillDragonPunch;
else if(GetLevel() >= 20)
}
else if (GetLevel() >= 20) {
skill_to_use = EQEmu::skills::SkillEagleStrike;
else if(GetLevel() >= 10)
skill_to_use = EQEmu::skills::SkillTigerClaw;
else if(GetLevel() >= 5)
}
else if (GetLevel() >= 5) {
skill_to_use = EQEmu::skills::SkillRoundKick;
else
}
else {
skill_to_use = EQEmu::skills::SkillKick;
}
break;
case ROGUE:
skill_to_use = EQEmu::skills::SkillBackstab;
@ -6555,19 +6637,54 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
}
if (skill_to_use == EQEmu::skills::SkillFlyingKick || skill_to_use == EQEmu::skills::SkillDragonPunch || skill_to_use == EQEmu::skills::SkillEagleStrike || skill_to_use == EQEmu::skills::SkillTigerClaw || skill_to_use == EQEmu::skills::SkillRoundKick) {
if (
skill_to_use == EQEmu::skills::SkillFlyingKick ||
skill_to_use == EQEmu::skills::SkillDragonPunch ||
skill_to_use == EQEmu::skills::SkillEagleStrike ||
skill_to_use == EQEmu::skills::SkillRoundKick
) {
reuse = (MonkSpecialAttack(target, skill_to_use) - 1);
MonkSpecialAttack(target, skill_to_use);
uint32 bDoubleSpecialAttack = (itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack);
if(bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0, 100))) {
int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick };
MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
int TripleChance = 25;
if (bDoubleSpecialAttack > 100)
TripleChance += (TripleChance * (100 - bDoubleSpecialAttack) / 100);
// Live AA - Technique of Master Wu
int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if(TripleChance > zone->random.Int(0,100))
MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
if (wuchance) {
const int MonkSPA[5] = {
EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) {
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
Mob* bo = GetBotOwner();
if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
bo->Message(
GENERIC_EMOTE,
"The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
GetCleanName(),
GetCleanName(),
extra
);
}
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use));
--extra;
}
}
reuse *= 1000;
@ -8966,6 +9083,12 @@ void Bot::CalcBotStats(bool showtext) {
skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel());
}
taunt_timer.Start(1000);
if (GetClass() == MONK && GetLevel() >= 10) {
monkattack_timer.Start(1000);
}
LoadAAs();
GenerateSpecialAttacks();

View File

@ -3928,6 +3928,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</tr>"
"<tr>"
"<td><c \"#CCCCCC\">monkwumessage</td>"
"<td><c \"#00CC00\">enable <c \"#CCCCCC\">| <c \"#00CC00\">disable</td>"
"<td><c \"#888888\">displays monk wu trigger messages</td>"
"</tr>"
"<tr>"
"<td></td>"
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</tr>"
"<tr>"
"<td><c \"#CCCCCC\">current</td>"
"<td></td>"
@ -4103,6 +4113,22 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled"));
}
else if (!owner_option.compare("monkwumessage")) {
if (!argument.compare("enable")) {
c->SetBotOption(Client::booMonkWuMessage, true);
}
else if (!argument.compare("disable")) {
c->SetBotOption(Client::booMonkWuMessage, false);
}
else {
c->SetBotOption(Client::booMonkWuMessage, !c->GetBotOption(Client::booMonkWuMessage));
}
database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage));
c->Message(m_action, "Bot 'monk wu message' is now %s.", (c->GetBotOption(Client::booMonkWuMessage) == true ? "enabled" : "disabled"));
}
else if (!owner_option.compare("current")) {
std::string window_title = "Current Bot Owner Options Settings";
@ -4112,13 +4138,14 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#FFFFFF\">Option<br>------</td>"
"<td><c \"#00FF00\">Argument<br>-------</td>"
"</tr>"
"<tr>" "<td><c \"#CCCCCC\">deathmarquee</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">statsupdate</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">altcombat</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">buffcounter</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">deathmarquee</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">statsupdate</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">altcombat</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">buffcounter</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">monkwumessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"</table>",
(c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"),
@ -4126,7 +4153,8 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
(c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"),
(RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"),
(RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"),
(c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")
(c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled")
);
c->SendPopupToClient(window_title.c_str(), window_text.c_str());

View File

@ -2257,6 +2257,7 @@ bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool
case Client::booAltCombat:
case Client::booAutoDefend:
case Client::booBuffCounter:
case Client::booMonkWuMessage:
{
query = fmt::format(
"REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')",

View File

@ -121,7 +121,8 @@ Client::Client(EQStreamInterface* ieqs)
0,
0,
0,
0
0,
false
),
hpupdate_timer(2000),
camp_timer(29000),
@ -165,6 +166,7 @@ Client::Client(EQStreamInterface* ieqs)
hp_self_update_throttle_timer(300),
hp_other_update_throttle_timer(500),
position_update_timer(10000),
consent_throttle_timer(2000),
tmSitting(0)
{
@ -357,6 +359,7 @@ Client::Client(EQStreamInterface* ieqs)
bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat);
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
bot_owner_options[booBuffCounter] = false;
bot_owner_options[booMonkWuMessage] = false;
SetBotPulling(false);
SetBotPrecombat(false);
@ -6254,6 +6257,52 @@ void Client::DragCorpses()
}
}
void Client::ConsentCorpses(std::string consent_name, bool deny)
{
if (strcasecmp(consent_name.c_str(), GetName()) == 0) {
MessageString(Chat::Red, CONSENT_YOURSELF);
}
else if (!consent_throttle_timer.Check()) {
MessageString(Chat::Red, CONSENT_WAIT);
}
else {
auto pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strn0cpy(scs->grantname, consent_name.c_str(), sizeof(scs->grantname));
strn0cpy(scs->ownername, GetName(), sizeof(scs->ownername));
strn0cpy(scs->zonename, "Unknown", sizeof(scs->zonename));
scs->permission = deny ? 0 : 1;
scs->zone_id = zone->GetZoneID();
scs->instance_id = zone->GetInstanceID();
scs->consent_type = EQEmu::consent::Normal;
scs->consent_id = 0;
if (strcasecmp(scs->grantname, "group") == 0) {
if (!deny) {
Group* grp = GetGroup();
scs->consent_id = grp ? grp->GetID() : 0;
}
scs->consent_type = EQEmu::consent::Group;
}
else if (strcasecmp(scs->grantname, "raid") == 0) {
if (!deny) {
Raid* raid = GetRaid();
scs->consent_id = raid ? raid->GetID() : 0;
}
scs->consent_type = EQEmu::consent::Raid;
}
else if (strcasecmp(scs->grantname, "guild") == 0) {
if (!deny) {
scs->consent_id = GuildID();
}
scs->consent_type = EQEmu::consent::Guild;
// update all corpses in db so buried/unloaded corpses see new consent id
database.UpdateCharacterCorpseConsent(CharacterID(), scs->consent_id);
}
worldserver.SendPacket(pack);
safe_delete(pack);
}
}
void Client::Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration)
{
if(!target || !IsValidSpell(spell_id) || this->GetID() == target->GetID())

View File

@ -793,6 +793,9 @@ public:
virtual void UpdateEquipmentLight() { m_Light.Type[EQEmu::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQEmu::lightsource::LightEquipment] = EQEmu::lightsource::TypeToLevel(m_Light.Type[EQEmu::lightsource::LightEquipment]); }
inline bool AutoSplitEnabled() { return m_pp.autosplit != 0; }
inline bool AutoConsentGroupEnabled() const { return m_pp.groupAutoconsent != 0; }
inline bool AutoConsentRaidEnabled() const { return m_pp.raidAutoconsent != 0; }
inline bool AutoConsentGuildEnabled() const { return m_pp.guildAutoconsent != 0; }
void SummonHorse(uint16 spell_id);
void SetHorseId(uint16 horseid_in);
@ -835,6 +838,7 @@ public:
void SendAlternateAdvancementTimers();
void ResetAlternateAdvancementTimer(int ability);
void ResetAlternateAdvancementTimers();
void ResetOnDeathAlternateAdvancement();
void SetAAPoints(uint32 points) { m_pp.aapoints = points; SendAlternateAdvancementStats(); }
void AddAAPoints(uint32 points) { m_pp.aapoints += points; SendAlternateAdvancementStats(); }
@ -897,14 +901,14 @@ public:
void BreakFeignDeathWhenCastOn(bool IsResisted);
void LeaveGroup();
bool Hungry() const {if (GetGM()) return false; return m_pp.hunger_level <= 3000;}
bool Thirsty() const {if (GetGM()) return false; return m_pp.thirst_level <= 3000;}
bool Hungry() const {if (GetGM() || !RuleB(Character, EnableFoodRequirement)) return false; return m_pp.hunger_level <= 3000;}
bool Thirsty() const {if (GetGM() || !RuleB(Character, EnableFoodRequirement)) return false; return m_pp.thirst_level <= 3000;}
int32 GetHunger() const { return m_pp.hunger_level; }
int32 GetThirst() const { return m_pp.thirst_level; }
void SetHunger(int32 in_hunger);
void SetThirst(int32 in_thirst);
void SetConsumption(int32 in_hunger, int32 in_thirst);
bool IsStarved() const { if (GetGM() || !RuleB(Character, EnableHungerPenalties)) return false; return m_pp.hunger_level == 0 || m_pp.thirst_level == 0; }
bool IsStarved() const { if (GetGM() || !RuleB(Character, EnableFoodRequirement) || !RuleB(Character, EnableHungerPenalties)) return false; return m_pp.hunger_level == 0 || m_pp.thirst_level == 0; }
bool CheckTradeLoreConflict(Client* other);
bool CheckTradeNonDroppable();
@ -957,7 +961,6 @@ public:
void EnteringMessages(Client* client);
void SendRules(Client* client);
std::list<std::string> consent_list;
const bool GetGMSpeed() const { return (gmspeed > 0); }
bool CanUseReport;
@ -1137,6 +1140,7 @@ public:
inline bool IsDraggingCorpse() { return (DraggedCorpses.size() > 0); }
void DragCorpses();
inline void ClearDraggedCorpses() { DraggedCorpses.clear(); }
void ConsentCorpses(std::string consent_name, bool deny = false);
void SendAltCurrencies();
void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount);
void AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0);
@ -1528,6 +1532,7 @@ private:
Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */
Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */
Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */
Timer consent_throttle_timer;
glm::vec3 m_Proximity;
glm::vec4 last_position_before_bulk_update;
@ -1641,6 +1646,7 @@ public:
booAltCombat,
booAutoDefend,
booBuffCounter,
booMonkWuMessage,
_booCount
};

View File

@ -4643,43 +4643,16 @@ void Client::Handle_OP_Consent(const EQApplicationPacket *app)
{
if (app->size<64) {
Consent_Struct* c = (Consent_Struct*)app->pBuffer;
if (strcmp(c->name, GetName()) != 0) {
auto pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strcpy(scs->grantname, c->name);
strcpy(scs->ownername, GetName());
scs->message_string_id = 0;
scs->permission = 1;
scs->zone_id = zone->GetZoneID();
scs->instance_id = zone->GetInstanceID();
//consent_list.push_back(scs->grantname);
worldserver.SendPacket(pack);
safe_delete(pack);
}
else {
MessageString(Chat::White, CONSENT_YOURSELF);
}
ConsentCorpses(c->name, false);
}
return;
}
void Client::Handle_OP_ConsentDeny(const EQApplicationPacket *app)
{
if (app->size<64) {
Consent_Struct* c = (Consent_Struct*)app->pBuffer;
auto pack = new ServerPacket(ServerOP_Consent, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer;
strcpy(scs->grantname, c->name);
strcpy(scs->ownername, GetName());
scs->message_string_id = 0;
scs->permission = 0;
scs->zone_id = zone->GetZoneID();
scs->instance_id = zone->GetInstanceID();
//consent_list.remove(scs->grantname);
worldserver.SendPacket(pack);
safe_delete(pack);
ConsentCorpses(c->name, true);
}
return;
}
void Client::Handle_OP_Consider(const EQApplicationPacket *app)
@ -8680,7 +8653,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
}
else if (inst->IsClassCommon())
{
if (item->ItemType == EQEmu::item::ItemTypeSpell && (strstr((const char*)item->Name, "Tome of ") || strstr((const char*)item->Name, "Skill: ")))
if (!RuleB(Skills, RequireTomeHandin) && item->ItemType == EQEmu::item::ItemTypeSpell && (strstr((const char*)item->Name, "Tome of ") || strstr((const char*)item->Name, "Skill: ")))
{
DeleteItemInInventory(slot_id, 1, true);
TrainDiscipline(item->ID);
@ -13320,6 +13293,21 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
entity_list.QueueClients(this, app, true);
}
}
else if (sa->type == AT_GroupConsent)
{
m_pp.groupAutoconsent = (sa->parameter == 1);
ConsentCorpses("Group", (sa->parameter != 1));
}
else if (sa->type == AT_RaidConsent)
{
m_pp.raidAutoconsent = (sa->parameter == 1);
ConsentCorpses("Raid", (sa->parameter != 1));
}
else if (sa->type == AT_GuildConsent)
{
m_pp.guildAutoconsent = (sa->parameter == 1);
ConsentCorpses("Guild", (sa->parameter != 1));
}
else {
std::cout << "Unknown SpawnAppearance type: 0x" << std::hex << std::setw(4) << std::setfill('0') << sa->type << std::dec
<< " value: 0x" << std::hex << std::setw(8) << std::setfill('0') << sa->parameter << std::dec << std::endl;

View File

@ -2543,7 +2543,7 @@ void command_size(Client *c, const Seperator *sep)
else if (!target)
c->Message(Chat::White,"Error: this command requires a target");
else {
uint16 Race = target->GetRace();
uint16 Race = target->GetModel();
uint8 Gender = target->GetGender();
uint8 Texture = 0xFF;
uint8 HelmTexture = 0xFF;

View File

@ -73,7 +73,7 @@ void Corpse::SendLootReqErrorPacket(Client* client, LootResponse response) {
safe_delete(outapp);
}
Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard) {
Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id) {
uint32 item_count = database.GetCharacterCorpseItemCount(in_dbid);
auto buffer =
new char[sizeof(PlayerCorpse_Struct) + (item_count * sizeof(player_lootitem::ServerLootItem_Struct))];
@ -138,6 +138,7 @@ Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std:
pc->drakkin_details = pcs->drakkin_details;
pc->IsRezzed(rezzed);
pc->become_npc = false;
pc->consented_guild_id = guild_consent_id;
pc->UpdateEquipmentLight(); // itemlist populated above..need to determine actual values
@ -153,7 +154,7 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP
in_npc->GetPosition(), in_npc->GetInnateLightType(), in_npc->GetTexture(),in_npc->GetHelmTexture(),
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,EQEmu::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
(*in_npctypedata)->use_model),
(*in_npctypedata)->use_model, false),
corpse_decay_timer(in_decaytime),
corpse_rez_timer(0),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
@ -259,8 +260,9 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
0, // uint8 in_bracertexture,
0, // uint8 in_handtexture,
0, // uint8 in_legtexture,
0,
0 // uint8 in_feettexture,
0, // uint8 in_feettexture,
0, // uint8 in_usemodel,
0 // bool in_always_aggro
),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
@ -282,6 +284,18 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
allowed_looters[i] = 0;
}
if (client->AutoConsentGroupEnabled()) {
Group* grp = client->GetGroup();
consented_group_id = grp ? grp->GetID() : 0;
}
if (client->AutoConsentRaidEnabled()) {
Raid* raid = client->GetRaid();
consented_raid_id = raid ? raid->GetID() : 0;
}
consented_guild_id = client->AutoConsentGuildEnabled() ? client->GuildID() : 0;
is_corpse_changed = true;
rez_experience = in_rezexp;
can_corpse_be_rezzed = true;
@ -487,7 +501,8 @@ EQEmu::TintProfile(),
0,
0,
0,
0),
0,
false),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
@ -611,11 +626,11 @@ bool Corpse::Save() {
/* Create New Corpse*/
if (corpse_db_id == 0) {
corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position);
corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, consented_guild_id);
}
/* Update Corpse Data */
else{
corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, IsRezzed());
corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, consented_guild_id, IsRezzed());
}
safe_delete_array(dbpc);
@ -647,6 +662,25 @@ void Corpse::DepopPlayerCorpse() {
player_corpse_depop = true;
}
void Corpse::AddConsentName(std::string consent_player_name)
{
for (const auto& consented_player_name : consented_player_names) {
if (strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0) {
return;
}
}
consented_player_names.emplace_back(consent_player_name);
}
void Corpse::RemoveConsentName(std::string consent_player_name)
{
consented_player_names.erase(std::remove_if(consented_player_names.begin(), consented_player_names.end(),
[consent_player_name](const std::string& consented_player_name) {
return strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0;
}
), consented_player_names.end());
}
uint32 Corpse::CountItems() {
return itemlist.size();
}
@ -1434,29 +1468,50 @@ bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) {
is_corpse_changed = true;
}
else {
client->Message(Chat::White, "Corpse is too far away.");
client->MessageString(Chat::Red, CORPSE_TOO_FAR);
return false;
}
}
else
{
bool consented = false;
std::list<std::string>::iterator itr;
for(itr = client->consent_list.begin(); itr != client->consent_list.end(); ++itr) {
if(strcmp(this->GetOwnerName(), itr->c_str()) == 0) {
if (!CheckDistance || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) {
GMMove(client->GetX(), client->GetY(), client->GetZ());
is_corpse_changed = true;
}
else {
client->Message(Chat::White, "Corpse is too far away.");
return false;
}
for (const auto& consented_player_name : consented_player_names) {
if (strcasecmp(client->GetName(), consented_player_name.c_str()) == 0) {
consented = true;
break;
}
}
if (!consented && consented_guild_id && consented_guild_id != GUILD_NONE) {
if (client->GuildID() == consented_guild_id) {
consented = true;
}
}
if(!consented) {
client->Message(Chat::White, "You do not have permission to move this corpse.");
if (!consented && consented_group_id) {
Group* grp = client->GetGroup();
if (grp && grp->GetID() == consented_group_id) {
consented = true;
}
}
if (!consented && consented_raid_id) {
Raid* raid = client->GetRaid();
if (raid && raid->GetID() == consented_raid_id) {
consented = true;
}
}
if (consented) {
if (!CheckDistance || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) {
GMMove(client->GetX(), client->GetY(), client->GetZ());
is_corpse_changed = true;
}
else {
client->MessageString(Chat::Red, CORPSE_TOO_FAR);
return false;
}
}
else {
client->MessageString(Chat::Red, CONSENT_DENIED);
return false;
}
}

View File

@ -48,7 +48,7 @@ class Corpse : public Mob {
Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture, uint32 in_rezexp, bool wasAtGraveyard = false);
~Corpse();
static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard);
static Corpse* LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id);
/* Corpse: General */
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; }
@ -74,6 +74,11 @@ class Corpse : public Mob {
uint32 GetDecayTime() { if (!corpse_decay_timer.Enabled()) return 0xFFFFFFFF; else return corpse_decay_timer.GetRemainingTime(); }
uint32 GetRezTime() { if (!corpse_rez_timer.Enabled()) return 0; else return corpse_rez_timer.GetRemainingTime(); }
void SetDecayTimer(uint32 decay_time);
void SetConsentGroupID(uint32 group_id) { if (IsPlayerCorpse()) { consented_group_id = group_id; } }
void SetConsentRaidID(uint32 raid_id) { if (IsPlayerCorpse()) { consented_raid_id = raid_id; } }
void SetConsentGuildID(uint32 guild_id) { if (IsPlayerCorpse()) { consented_guild_id = guild_id; } }
void AddConsentName(std::string consent_player_name);
void RemoveConsentName(std::string consent_player_name);
void Delete();
void Bury();
@ -142,6 +147,9 @@ private:
int32 player_kill_item; /* Determines if Player Kill Item */
uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */
uint32 char_id; /* Character ID */
uint32 consented_group_id = 0;
uint32 consented_raid_id = 0;
uint32 consented_guild_id = 0;
ItemList itemlist; /* Internal Item list used for corpses */
uint32 copper;
uint32 silver;
@ -160,6 +168,7 @@ private:
Timer corpse_graveyard_timer;
Timer loot_cooldown_timer; /* Delay between loot actions on the corpse entity */
EQEmu::TintProfile item_tint;
std::vector<std::string> consented_player_names;
LootRequestType loot_request_type;
};

View File

@ -36,7 +36,7 @@ Encounter::Encounter(const char* enc_name)
:Mob
(
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, glm::vec4(0,0,0,0), 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false
)
{
encounter_name[0] = 0;

View File

@ -1,6 +1,9 @@
#include "global_loot_manager.h"
#include "npc.h"
#include "client.h"
#include "zone.h"
extern Zone *zone;
std::vector<int> GlobalLootManager::GetGlobalLootTables(NPC *mob) const
{
@ -78,6 +81,12 @@ bool GlobalLootEntry::PassesRules(NPC *mob) const
if (mob->GetBodyType() == r.value)
bPassesBodyType = true;
break;
case GlobalLoot::RuleTypes::HotZone: // value == 0 must not be hot_zone, value != must be hot_zone
if (zone->IsHotzone() && !r.value)
return false;
if (!zone->IsHotzone() && r.value)
return false;
break;
default:
break;
}

View File

@ -17,6 +17,7 @@ enum class RuleTypes {
BodyType = 4,
Rare = 5,
Raid = 6,
HotZone = 7,
Max
};

View File

@ -464,7 +464,7 @@ void NPC::CheckGlobalLootTables()
void ZoneDatabase::LoadGlobalLoot()
{
auto query = StringFormat("SELECT id, loottable_id, description, min_level, max_level, rare, raid, race, "
"class, bodytype, zone FROM global_loot WHERE enabled = 1");
"class, bodytype, zone, hot_zone FROM global_loot WHERE enabled = 1");
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() == 0)
@ -518,9 +518,13 @@ void ZoneDatabase::LoadGlobalLoot()
auto bodytypes = SplitString(row[9], '|');
for (auto &b : bodytypes)
e.AddRule(GlobalLoot::RuleTypes::Class, std::stoi(b));
e.AddRule(GlobalLoot::RuleTypes::BodyType, std::stoi(b));
}
// null is not used
if (row[11])
e.AddRule(GlobalLoot::RuleTypes::HotZone, atoi(row[11]));
zone->AddGlobalLootEntry(e);
}
}

View File

@ -547,6 +547,12 @@ void Lua_NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_del
self->SetSimpleRoamBox(box_size, move_distance, move_delay);
}
void Lua_NPC::RecalculateSkills()
{
Lua_Safe_Call_Void();
self->RecalculateSkills();
}
luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>())
@ -657,7 +663,8 @@ luabind::scope lua_register_npc() {
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop)
.def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC)
.def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating);
.def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating)
.def("RecalculateSkills", (void(Lua_NPC::*)(void))&Lua_NPC::RecalculateSkills);
}
#endif

View File

@ -134,6 +134,7 @@ public:
void SetSimpleRoamBox(float box_size);
void SetSimpleRoamBox(float box_size, float move_distance);
void SetSimpleRoamBox(float box_size, float move_distance, int move_delay);
void RecalculateSkills();
};
#endif

View File

@ -93,7 +93,8 @@ Mob::Mob(
uint8 in_handtexture,
uint8 in_legtexture,
uint8 in_feettexture,
uint16 in_usemodel
uint16 in_usemodel,
bool in_always_aggro
) :
attack_timer(2000),
attack_dw_timer(2000),
@ -275,6 +276,7 @@ Mob::Mob(
qglobal = 0;
spawned = false;
rare_spawn = false;
always_aggro = in_always_aggro;
InitializeBuffSlots();
@ -685,7 +687,7 @@ int Mob::_GetRunSpeed() const {
int runspeedcap = RuleI(Character,BaseRunSpeedCap);
runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap;
aa_mod = itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap;
aa_mod += aabonuses.BaseMovementSpeed + aabonuses.movementspeed;
int spell_mod = spellbonuses.movementspeed + itembonuses.movementspeed;
int movemod = 0;

View File

@ -161,7 +161,8 @@ public:
uint8 in_handtexture,
uint8 in_legtexture,
uint8 in_feettexture,
uint16 in_usemodel
uint16 in_usemodel,
bool in_always_aggros_foes
);
virtual ~Mob();
@ -578,6 +579,7 @@ public:
inline const GravityBehavior GetFlyMode() const { return flymode; }
bool IsBoat() const;
bool IsControllableBoat() const;
inline const bool AlwaysAggro() const { return always_aggro; }
//Group
virtual bool HasRaid() = 0;
@ -1389,6 +1391,7 @@ protected:
Timer ranged_timer;
float attack_speed; //% increase/decrease in attack speed (not haste)
int attack_delay; //delay between attacks in 10ths of seconds
bool always_aggro;
int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%)
Timer tic_timer;
Timer mana_timer;

View File

@ -1300,7 +1300,9 @@ void MobMovementManager::PushEvadeCombat(MobMovementEntry &mob_movement_entry)
*/
void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z, MobMovementMode mob_movement_mode)
{
auto sb = who->GetStuckBehavior();
LogDebug("Handle stuck behavior for {0} at ({1}, {2}, {3}) with movement_mode {4}", who->GetName(), x, y, z, mob_movement_mode);
auto sb = who->GetStuckBehavior();
MobStuckBehavior behavior = RunToTarget;
if (sb >= 0 && sb < MaxStuckBehavior) {
@ -1308,7 +1310,7 @@ void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z
}
auto eiter = _impl->Entries.find(who);
auto &ent = (*eiter);
auto &ent = (*eiter);
switch (sb) {
case RunToTarget:
@ -1323,8 +1325,7 @@ void MobMovementManager::HandleStuckBehavior(Mob *who, float x, float y, float z
PushStopMoving(ent.second);
break;
case EvadeCombat:
//PushEvadeCombat(ent.second);
PushStopMoving(ent.second);
PushEvadeCombat(ent.second);
break;
}
}

View File

@ -113,11 +113,13 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
npc_type_data->handtexture,
npc_type_data->legtexture,
npc_type_data->feettexture,
npc_type_data->use_model
npc_type_data->use_model,
npc_type_data->always_aggro
),
attacked_timer(CombatEventTimer_expire),
swarm_timer(100),
classattack_timer(1000),
monkattack_timer(1000),
knightattack_timer(1000),
assist_timer(AIassistcheck_delay),
qglobal_purge_timer(30000),
@ -307,7 +309,15 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
// some overrides -- really we need to be able to set skills for mobs in the DB
// There are some known low level SHM/BST pets that do not follow this, which supports
// the theory of needing to be able to set skills for each mob separately
if (!IsBot()) {
if (IsBot()) {
if (GetClass() != PALADIN && GetClass() != SHADOWKNIGHT) {
knightattack_timer.Disable();
}
else if (GetClass() != MONK || GetLevel() < 10) {
monkattack_timer.Disable();
}
}
else {
if (moblevel > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250;
@ -778,7 +788,20 @@ bool NPC::Process()
}
if (GetMana() < GetMaxMana()) {
SetMana(GetMana() + mana_regen + npc_sitting_regen_bonus);
if (RuleB(NPC, UseMeditateBasedManaRegen)) {
int32 npc_idle_mana_regen_bonus = 2;
uint16 meditate_skill = GetSkill(EQEmu::skills::SkillMeditate);
if (!IsEngaged() && meditate_skill > 0) {
uint8 clevel = GetLevel();
npc_idle_mana_regen_bonus =
(((meditate_skill / 10) +
(clevel - (clevel / 4))) / 4) + 4;
}
SetMana(GetMana() + mana_regen + npc_idle_mana_regen_bonus);
}
else {
SetMana(GetMana() + mana_regen + npc_sitting_regen_bonus);
}
}
SendHPUpdate();
@ -3195,4 +3218,29 @@ void NPC::AIYellForHelp(Mob *sender, Mob *attacker)
}
}
}
}
void NPC::RecalculateSkills()
{
int r;
for (r = 0; r <= EQEmu::skills::HIGHEST_SKILL; r++) {
skills[r] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)r, level);
}
// some overrides -- really we need to be able to set skills for mobs in the DB
// There are some known low level SHM/BST pets that do not follow this, which supports
// the theory of needing to be able to set skills for each mob separately
if (!IsBot()) {
if (level > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250;
}
else if (level > 3) {
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack];
}
else {
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
}
}
}

View File

@ -476,6 +476,8 @@ public:
inline bool IsSkipAutoScale() const { return skip_auto_scale; }
void RecalculateSkills();
protected:
const NPCType* NPCTypedata;
@ -497,6 +499,7 @@ protected:
Timer attacked_timer; //running while we are being attacked (damaged)
Timer swarm_timer;
Timer monkattack_timer; //additional timer for tiger claw usage
Timer classattack_timer;
Timer knightattack_timer;
Timer assist_timer; //ask for help from nearby mobs

View File

@ -12,8 +12,6 @@
extern Zone *zone;
const int MaxNavmeshNodes = 1024;
struct PathfinderNavmesh::Implementation
{
dtNavMesh *nav_mesh;
@ -36,19 +34,19 @@ PathfinderNavmesh::~PathfinderNavmesh()
IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags)
{
partial = false;
if (!m_impl->nav_mesh) {
return IPath();
}
if (!m_impl->query) {
m_impl->query = dtAllocNavMeshQuery();
}
m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes);
m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes));
glm::vec3 current_location(start.x, start.z, start.y);
glm::vec3 dest_location(end.x, end.z, end.y);
dtQueryFilter filter;
filter.setIncludeFlags(flags);
filter.setAreaCost(0, 1.0f); //Normal
@ -61,48 +59,48 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl
filter.setAreaCost(8, 1.0f); //General Area
filter.setAreaCost(9, 0.1f); //Portal
filter.setAreaCost(10, 0.1f); //Prefer
dtPolyRef start_ref;
dtPolyRef end_ref;
glm::vec3 ext(5.0f, 100.0f, 5.0f);
m_impl->query->findNearestPoly(&current_location[0], &ext[0], &filter, &start_ref, 0);
m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0);
if (!start_ref || !end_ref) {
return IPath();
}
int npoly = 0;
dtPolyRef path[1024] = { 0 };
auto status = m_impl->query->findPath(start_ref, end_ref, &current_location[0], &dest_location[0], &filter, path, &npoly, 1024);
if (npoly) {
glm::vec3 epos = dest_location;
if (path[npoly - 1] != end_ref) {
m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0);
partial = true;
auto dist = DistanceSquared(epos, current_location);
if (dist < 10000.0f) {
stuck = true;
}
}
float straight_path[2048 * 3];
unsigned char straight_path_flags[2048];
int n_straight_polys;
dtPolyRef straight_path_polys[2048];
status = m_impl->query->findStraightPath(&current_location[0], &epos[0], path, npoly,
straight_path, straight_path_flags,
straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS);
if (dtStatusFailed(status)) {
return IPath();
}
if (n_straight_polys) {
IPath Route;
for (int i = 0; i < n_straight_polys; ++i)
@ -111,9 +109,9 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl
node.x = straight_path[i * 3];
node.z = straight_path[i * 3 + 1];
node.y = straight_path[i * 3 + 2];
Route.push_back(node);
unsigned short flag = 0;
if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) {
if (flag & 512) {
@ -121,11 +119,11 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl
}
}
}
return Route;
}
}
IPath Route;
Route.push_back(end);
return Route;
@ -134,19 +132,19 @@ IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const gl
IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts)
{
partial = false;
if (!m_impl->nav_mesh) {
return IPath();
}
if (!m_impl->query) {
m_impl->query = dtAllocNavMeshQuery();
}
m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes);
m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes));
glm::vec3 current_location(start.x, start.z, start.y);
glm::vec3 dest_location(end.x, end.z, end.y);
dtQueryFilter filter;
filter.setIncludeFlags(opts.flags);
filter.setAreaCost(0, opts.flag_cost[0]); //Normal
@ -159,83 +157,83 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
filter.setAreaCost(8, opts.flag_cost[7]); //General Area
filter.setAreaCost(9, opts.flag_cost[8]); //Portal
filter.setAreaCost(10, opts.flag_cost[9]); //Prefer
static const int max_polys = 256;
dtPolyRef start_ref;
dtPolyRef end_ref;
glm::vec3 ext(10.0f, 200.0f, 10.0f);
m_impl->query->findNearestPoly(&current_location[0], &ext[0], &filter, &start_ref, 0);
m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0);
if (!start_ref || !end_ref) {
return IPath();
}
int npoly = 0;
dtPolyRef path[max_polys] = { 0 };
m_impl->query->findPath(start_ref, end_ref, &current_location[0], &dest_location[0], &filter, path, &npoly, max_polys);
auto status = m_impl->query->findPath(start_ref, end_ref, &current_location[0], &dest_location[0], &filter, path, &npoly, max_polys);
if (npoly) {
glm::vec3 epos = dest_location;
if (path[npoly - 1] != end_ref) {
m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0);
partial = true;
auto dist = DistanceSquared(epos, current_location);
if (dist < 10000.0f) {
stuck = true;
}
}
int n_straight_polys;
glm::vec3 straight_path[max_polys];
unsigned char straight_path_flags[max_polys];
dtPolyRef straight_path_polys[max_polys];
auto status = m_impl->query->findStraightPath(&current_location[0], &epos[0], path, npoly,
(float*)&straight_path[0], straight_path_flags,
straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS);
if (dtStatusFailed(status)) {
return IPath();
}
if (n_straight_polys) {
if (opts.smooth_path) {
IPath Route;
//Add the first point
{
auto &flag = straight_path_flags[0];
if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) {
auto &p = straight_path[0];
Route.push_back(glm::vec3(p.x, p.z, p.y));
}
else {
auto &p = straight_path[0];
float h = 0.0f;
if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, p, &h))) {
p.y = h + opts.offset;
}
Route.push_back(glm::vec3(p.x, p.z, p.y));
}
}
for (int i = 0; i < n_straight_polys - 1; ++i)
{
auto &flag = straight_path_flags[i];
if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) {
auto &poly = straight_path_polys[i];
auto &p2 = straight_path[i + 1];
glm::vec3 node(p2.x, p2.z, p2.y);
Route.push_back(node);
unsigned short pflag = 0;
if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &pflag))) {
if (pflag & 512) {
@ -250,12 +248,12 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
auto dir = glm::normalize(p2 - p1);
float total = 0.0f;
glm::vec3 previous_pt = p1;
while (total < dist) {
glm::vec3 current_pt;
float dist_to_move = opts.step_size;
float ff = opts.step_size / 2.0f;
if (total + dist_to_move + ff >= dist) {
current_pt = p2;
total = dist;
@ -264,18 +262,18 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
total += dist_to_move;
current_pt = p1 + dir * total;
}
float h = 0.0f;
if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, current_pt, &h))) {
current_pt.y = h + opts.offset;
}
Route.push_back(glm::vec3(current_pt.x, current_pt.z, current_pt.y));
previous_pt = current_pt;
}
}
}
return Route;
}
else {
@ -285,7 +283,7 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
auto &current = straight_path[i];
glm::vec3 node(current.x, current.z, current.y);
Route.push_back(node);
unsigned short flag = 0;
if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) {
if (flag & 512) {
@ -293,7 +291,7 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
}
}
}
return Route;
}
}
@ -313,7 +311,7 @@ glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start)
if (!m_impl->query) {
m_impl->query = dtAllocNavMeshQuery();
m_impl->query->init(m_impl->nav_mesh, MaxNavmeshNodes);
m_impl->query->init(m_impl->nav_mesh, RuleI(Pathing, MaxNavmeshNodes));
}
dtQueryFilter filter;

View File

@ -2451,6 +2451,28 @@ XS(XS_NPC_SetSimpleRoamBox) {
XSRETURN_EMPTY;
}
XS(XS_NPC_RecalculateSkills); /* prototype to pass -Wmissing-prototypes */
XS(XS_NPC_RecalculateSkills) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::RecalculateSkills(THIS)");
{
NPC *THIS;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV *) SvRV(ST(0)));
THIS = INT2PTR(NPC *, tmp);
} else
Perl_croak(aTHX_ "THIS is not of type NPC");
if (THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->RecalculateSkills();
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus
extern "C"
#endif
@ -2565,6 +2587,7 @@ XS(boot_NPC) {
newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$");
newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$");
newXSproto(strcpy(buf, "SetSimpleRoamBox"), XS_NPC_SetSimpleRoamBox, file, "$$;$$");
newXSproto(strcpy(buf, "RecalculateSkills"), XS_NPC_RecalculateSkills, file, "$");
XSRETURN_YES;
}

View File

@ -379,31 +379,35 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk)
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
// Live AA - Technique of Master Wu
int wuchance =
itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if (wuchance) {
const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick};
const int MonkSPA[5] = {
EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) {
if (zone->random.Roll(wuchance))
extra++;
else
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
// They didn't add a string ID for this.
std::string msg = StringFormat(
"The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
// live uses 400 here -- not sure if it's the best for all clients though
SendColoredText(400, msg);
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(),
classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill);
extra--;
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill));
--extra;
}
}
@ -1294,7 +1298,6 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
//consume ammo
DeleteItemInInventory(ammo_slot, 1, true);
CheckIncreaseSkill(EQEmu::skills::SkillThrowing, GetTarget());
CommonBreakInvisibleFromCombat();
}
@ -1408,6 +1411,9 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon
else
TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, false, EQEmu::invslot::slotRange);
}
if (IsClient()) {
CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillThrowing, GetTarget());
}
}
void Mob::SendItemAnimation(Mob *to, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, float velocity) {

View File

@ -1230,7 +1230,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
// handle the components for traditional casters
else {
if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) {
if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) {
//bypass reagent cost
}
else if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found
@ -1263,7 +1263,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
return;
}
}
else if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) {
else if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) {
//bypass reagent cost
}
else if (!bard_song_mode)

View File

@ -122,11 +122,13 @@
#define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one.
#define PICK_LORE 379 //You cannot pick up a lore item you already possess.
#define POISON_TOO_HIGH 382 // This poison is too high level for you to apply.
#define CORPSE_TOO_FAR 389 //The corpse is too far away to summon.
#define CONSENT_DENIED 390 //You do not have consent to summon that corpse.
#define DISCIPLINE_RDY 393 //You are ready to use a new discipline now.
#define CONSENT_INVALID_NAME 397 //Not a valid consent name.
#define CONSENT_NPC 398 //You cannot consent NPC\'s.
#define CONSENT_YOURSELF 399 //You cannot consent yourself.
#define CONSENT_WAIT 400 //You must wait 2 seconds between consents.
#define SONG_NEEDS_DRUM 405 //You need to play a percussion instrument for this song
#define SONG_NEEDS_WIND 406 //You need to play a wind instrument for this song
#define SONG_NEEDS_STRINGS 407 //You need to play a stringed instrument for this song

View File

@ -51,6 +51,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "worldserver.h"
#include "zone.h"
#include "zone_config.h"
#include "zone_reload.h"
extern EntityList entity_list;
@ -1442,50 +1443,65 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
case ServerOP_Consent: {
ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer;
Client* client = entity_list.GetClientByName(s->grantname);
if (client) {
if (s->permission == 1)
client->consent_list.push_back(s->ownername);
else
client->consent_list.remove(s->ownername);
auto outapp =
new EQApplicationPacket(OP_ConsentResponse, sizeof(ConsentResponse_Struct));
ConsentResponse_Struct* crs = (ConsentResponse_Struct*)outapp->pBuffer;
strcpy(crs->grantname, s->grantname);
strcpy(crs->ownername, s->ownername);
crs->permission = s->permission;
strcpy(crs->zonename, "all zones");
client->QueuePacket(outapp);
safe_delete(outapp);
bool found_corpse = false;
for (auto const& it : entity_list.GetCorpseList()) {
if (it.second->IsPlayerCorpse() && strcmp(it.second->GetOwnerName(), s->ownername) == 0) {
if (s->consent_type == EQEmu::consent::Normal) {
if (s->permission == 1) {
it.second->AddConsentName(s->grantname);
}
else {
it.second->RemoveConsentName(s->grantname);
}
}
else if (s->consent_type == EQEmu::consent::Group) {
it.second->SetConsentGroupID(s->consent_id);
}
else if (s->consent_type == EQEmu::consent::Raid) {
it.second->SetConsentRaidID(s->consent_id);
}
else if (s->consent_type == EQEmu::consent::Guild) {
it.second->SetConsentGuildID(s->consent_id);
}
found_corpse = true;
}
}
else {
// target not found
// Message string id's likely to be used here are:
// CONSENT_YOURSELF = 399
// CONSENT_INVALID_NAME = 397
// TARGET_NOT_FOUND = 101
auto scs_pack =
new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)scs_pack->pBuffer;
strcpy(scs->grantname, s->grantname);
strcpy(scs->ownername, s->ownername);
scs->permission = s->permission;
scs->zone_id = s->zone_id;
scs->instance_id = s->instance_id;
scs->message_string_id = TARGET_NOT_FOUND;
worldserver.SendPacket(scs_pack);
safe_delete(scs_pack);
if (found_corpse) {
// forward the grant/deny message for this zone to both owner and granted
auto outapp = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct));
ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)outapp->pBuffer;
memcpy(outapp->pBuffer, s, sizeof(ServerOP_Consent_Struct));
if (zone) {
strn0cpy(scs->zonename, zone->GetLongName(), sizeof(scs->zonename));
}
worldserver.SendPacket(outapp);
safe_delete(outapp);
}
break;
}
case ServerOP_Consent_Response: {
ServerOP_Consent_Struct* s = (ServerOP_Consent_Struct*)pack->pBuffer;
Client* client = entity_list.GetClientByName(s->ownername);
if (client) {
client->MessageString(Chat::White, s->message_string_id);
Client* owner_client = entity_list.GetClientByName(s->ownername);
Client* grant_client = nullptr;
if (s->consent_type == EQEmu::consent::Normal) {
grant_client = entity_list.GetClientByName(s->grantname);
}
if (owner_client || grant_client) {
auto outapp = new EQApplicationPacket(OP_ConsentResponse, sizeof(ConsentResponse_Struct));
ConsentResponse_Struct* crs = (ConsentResponse_Struct*)outapp->pBuffer;
strn0cpy(crs->grantname, s->grantname, sizeof(crs->grantname));
strn0cpy(crs->ownername, s->ownername, sizeof(crs->ownername));
crs->permission = s->permission;
strn0cpy(crs->zonename, s->zonename, sizeof(crs->zonename));
if (owner_client) {
owner_client->QueuePacket(outapp); // confirmation message to the owner
}
if (grant_client) {
grant_client->QueuePacket(outapp); // message to the client being granted/denied
}
safe_delete(outapp);
}
break;
}
@ -1930,15 +1946,40 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
iter++;
}
}
case ServerOP_ReloadWorld:
{
ReloadWorld_Struct* RW = (ReloadWorld_Struct*)pack->pBuffer;
auto* reload_world = (ReloadWorld_Struct*)pack->pBuffer;
if (zone) {
zone->ReloadWorld(RW->Option);
zone->ReloadWorld(reload_world->Option);
}
break;
}
case ServerOP_HotReloadQuests:
{
if (!zone) {
break;
}
auto *hot_reload_quests = (HotReloadQuestsStruct *) pack->pBuffer;
LogHotReloadDetail(
"Receiving request [HotReloadQuests] | request_zone [{}] current_zone [{}]",
hot_reload_quests->zone_short_name,
zone->GetShortName()
);
std::string request_zone_short_name = hot_reload_quests->zone_short_name;
std::string local_zone_short_name = zone->GetShortName();
if (request_zone_short_name == local_zone_short_name || request_zone_short_name == "all"){
zone->SetQuestHotReloadQueued(true);
}
break;
}
case ServerOP_ChangeSharedMem:
{
std::string hotfix_name = std::string((char*)pack->pBuffer);

View File

@ -55,6 +55,7 @@
#include "mob_movement_manager.h"
#include "npc_scale_manager.h"
#include "../common/data_verification.h"
#include "zone_reload.h"
#include <time.h>
#include <ctime>
@ -771,6 +772,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
autoshutdown_timer((RuleI(Zone, AutoShutdownDelay))),
clientauth_timer(AUTHENTICATION_TIMEOUT * 1000),
spawn2_timer(1000),
hot_reload_timer(1000),
qglobal_purge_timer(30000),
hotzone_timer(120000),
m_SafePoint(0.0f,0.0f,0.0f),
@ -874,6 +876,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
mMovementManager = &MobMovementManager::Get();
SetNpcPositionUpdateDistance(0);
SetQuestHotReloadQueued(false);
}
Zone::~Zone() {
@ -1231,6 +1234,27 @@ bool Zone::Process() {
}
}
if (hot_reload_timer.Check() && IsQuestHotReloadQueued()) {
LogHotReloadDetail("Hot reload timer check...");
bool perform_reload = true;
if (RuleB(HotReload, QuestsRepopWhenPlayersNotInCombat)) {
for (auto &it : entity_list.GetClientList()) {
auto client = it.second;
if (client->GetAggroCount() > 0) {
perform_reload = false;
break;
}
}
}
if (perform_reload) {
ZoneReload::HotReloadQuests();
}
}
if(initgrids_timer.Check()) {
//delayed grid loading stuff.
initgrids_timer.Disable();
@ -1540,7 +1564,6 @@ void Zone::RepopClose(const glm::vec4& client_position, uint32 repop_distance)
void Zone::Repop(uint32 delay)
{
if (!Depop()) {
return;
}
@ -2422,3 +2445,13 @@ void Zone::CalculateNpcUpdateDistanceSpread()
combined_spread
);
}
bool Zone::IsQuestHotReloadQueued() const
{
return quest_hot_reload_queued;
}
void Zone::SetQuestHotReloadQueued(bool in_quest_hot_reload_queued)
{
quest_hot_reload_queued = in_quest_hot_reload_queued;
}

View File

@ -204,6 +204,7 @@ public:
time_t weather_timer;
Timer spawn2_timer;
Timer hot_reload_timer;
uint8 weather_intensity;
uint8 zone_weather;
@ -270,6 +271,9 @@ public:
void UpdateQGlobal(uint32 qid, QGlobal newGlobal);
void weatherSend(Client *client = nullptr);
bool IsQuestHotReloadQueued() const;
void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued);
WaterMap *watermap;
ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f);
ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f);
@ -340,6 +344,9 @@ private:
bool m_ucss_available;
bool staticzone;
bool zone_has_current_time;
bool quest_hot_reload_queued;
private:
double max_movement_update_range;
char *long_name;
char *map_name;

46
zone/zone_reload.cpp Normal file
View File

@ -0,0 +1,46 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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 "zone_reload.h"
#include "quest_parser_collection.h"
void ZoneReload::HotReloadQuests()
{
BenchTimer timer;
entity_list.ClearAreas();
parse->ReloadQuests(RuleB(HotReload, QuestsResetTimersWithReload));
if (RuleB(HotReload, QuestsRepopWithReload)) {
zone->Repop(0);
}
zone->SetQuestHotReloadQueued(false);
LogHotReload(
"[Quests] Reloading [{}] repop [{}] reset_timers [{}] repop_when_not_in_combat [{}] Time [{:.4f}]",
zone->GetShortName(),
(RuleB(HotReload, QuestsRepopWithReload) ? "true" : "false"),
(RuleB(HotReload, QuestsResetTimersWithReload) ? "true" : "false"),
(RuleB(HotReload, QuestsRepopWhenPlayersNotInCombat) ? "true" : "false"),
timer.elapsed()
);
}

31
zone/zone_reload.h Normal file
View File

@ -0,0 +1,31 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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 EQEMU_ZONE_RELOAD_H
#define EQEMU_ZONE_RELOAD_H
class ZoneReload {
public:
static void HotReloadQuests();
};
#endif //EQEMU_ZONE_RELOAD_H

View File

@ -2506,7 +2506,8 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
"npc_types.rare_spawn, "
"npc_types.stuck_behavior, "
"npc_types.model, "
"npc_types.flymode "
"npc_types.flymode, "
"npc_types.always_aggro "
"FROM npc_types %s",
where_condition.c_str()
);
@ -2703,11 +2704,12 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
temp_npctype_data->charm_avoidance_rating = atoi(row[105]);
temp_npctype_data->charm_atk = atoi(row[106]);
temp_npctype_data->skip_global_loot = atoi(row[107]) != 0;
temp_npctype_data->rare_spawn = atoi(row[108]) != 0;
temp_npctype_data->stuck_behavior = atoi(row[109]);
temp_npctype_data->use_model = atoi(row[110]);
temp_npctype_data->flymode = atoi(row[111]);
temp_npctype_data->skip_global_loot = atoi(row[107]) != 0;
temp_npctype_data->rare_spawn = atoi(row[108]) != 0;
temp_npctype_data->stuck_behavior = atoi(row[109]);
temp_npctype_data->use_model = atoi(row[110]);
temp_npctype_data->flymode = atoi(row[111]);
temp_npctype_data->always_aggro = atoi(row[112]);
temp_npctype_data->skip_auto_scale = false; // hardcoded here for now
@ -4288,10 +4290,10 @@ uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){
return 0;
}
uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const char* char_name, uint32 zone_id, uint16 instance_id, PlayerCorpse_Struct* dbpc, const glm::vec4& position, bool is_rezzed) {
uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const char* char_name, uint32 zone_id, uint16 instance_id, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guild_id, bool is_rezzed) {
std::string query = StringFormat("UPDATE `character_corpses` "
"SET `charname` = '%s', `zone_id` = %u, `instance_id` = %u, `charid` = %d, "
"`x` = %1.1f,`y` = %1.1f,`z` = %1.1f, `heading` = %1.1f, "
"`x` = %1.1f,`y` = %1.1f,`z` = %1.1f, `heading` = %1.1f, `guild_consent_id` = %u, "
"`is_locked` = %d, `exp` = %u, `size` = %f, `level` = %u, "
"`race` = %u, `gender` = %u, `class` = %u, `deity` = %u, "
"`texture` = %u, `helm_texture` = %u, `copper` = %u, "
@ -4303,7 +4305,7 @@ uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const c
"`wc_7` = %u, `wc_8` = %u, `wc_9` = %u "
"WHERE `id` = %u",
EscapeString(char_name).c_str(), zone_id, instance_id, char_id,
position.x, position.y, position.z, position.w,
position.x, position.y, position.z, position.w, guild_id,
dbpc->locked, dbpc->exp, dbpc->size, dbpc->level, dbpc->race,
dbpc->gender, dbpc->class_, dbpc->deity, dbpc->texture,
dbpc->helmtexture, dbpc->copper, dbpc->silver, dbpc->gold,
@ -4319,12 +4321,19 @@ uint32 ZoneDatabase::UpdateCharacterCorpse(uint32 db_id, uint32 char_id, const c
return db_id;
}
uint32 ZoneDatabase::UpdateCharacterCorpseConsent(uint32 charid, uint32 guildid)
{
std::string query = fmt::format("UPDATE `character_corpses` SET `guild_consent_id` = '{}' WHERE charid = '{}'", guildid, charid);
auto results = QueryDatabase(query);
return results.RowsAffected();
}
void ZoneDatabase::MarkCorpseAsRezzed(uint32 db_id) {
std::string query = StringFormat("UPDATE `character_corpses` SET `is_rezzed` = 1 WHERE `id` = %i", db_id);
auto results = QueryDatabase(query);
}
uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position) {
uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid) {
/* Dump Basic Corpse Data */
std::string query = StringFormat(
"INSERT INTO `character_corpses` "
@ -4336,6 +4345,7 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui
"`y` = %1.1f, "
"`z` = %1.1f, "
"`heading` = %1.1f, "
"`guild_consent_id` = %u, "
"`time_of_death` = NOW(), "
"`is_buried` = 0, "
"`is_locked` = %d, "
@ -4379,6 +4389,7 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui
position.y,
position.z,
position.w,
guildid,
dbpc->locked,
dbpc->exp,
dbpc->size,
@ -4637,7 +4648,7 @@ bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, PlayerCorpse_Struct
Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_zone_id, uint16 dest_instance_id, const glm::vec4& position) {
Corpse* corpse = nullptr;
std::string query = StringFormat("SELECT `id`, `charname`, `time_of_death`, `is_rezzed` "
std::string query = StringFormat("SELECT `id`, `charname`, `time_of_death`, `is_rezzed`, `guild_consent_id` "
"FROM `character_corpses` "
"WHERE `charid` = '%u' AND `is_buried` = 1 "
"ORDER BY `time_of_death` LIMIT 1",
@ -4652,7 +4663,8 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(uint32 char_id, uint32 dest_z
position,
row[2], // char* time_of_death
atoi(row[3]) == 1, // bool rezzed
false // bool was_at_graveyard
false, // bool was_at_graveyard
atoul(row[4]) // uint32 guild_consent_id
);
if (!corpse)
continue;
@ -4678,7 +4690,7 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id
auto results = QueryDatabase(query);
query = StringFormat(
"SELECT `id`, `charname`, `time_of_death`, `is_rezzed` FROM `character_corpses` WHERE `charid` = '%u'"
"SELECT `id`, `charname`, `time_of_death`, `is_rezzed`, `guild_consent_id` FROM `character_corpses` WHERE `charid` = '%u'"
"ORDER BY time_of_death",
char_id);
results = QueryDatabase(query);
@ -4691,7 +4703,8 @@ bool ZoneDatabase::SummonAllCharacterCorpses(uint32 char_id, uint32 dest_zone_id
position,
row[2],
atoi(row[3]) == 1,
false);
false,
atoul(row[4]));
if (corpse) {
entity_list.AddCorpse(corpse);
@ -4766,7 +4779,7 @@ bool ZoneDatabase::UnburyCharacterCorpse(uint32 db_id, uint32 new_zone_id, uint1
Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) {
Corpse* NewCorpse = 0;
std::string query = StringFormat(
"SELECT `id`, `charid`, `charname`, `x`, `y`, `z`, `heading`, `time_of_death`, `is_rezzed`, `was_at_graveyard` FROM `character_corpses` WHERE `id` = '%u' LIMIT 1",
"SELECT `id`, `charid`, `charname`, `x`, `y`, `z`, `heading`, `time_of_death`, `is_rezzed`, `was_at_graveyard`, `guild_consent_id` FROM `character_corpses` WHERE `id` = '%u' LIMIT 1",
player_corpse_id
);
auto results = QueryDatabase(query);
@ -4779,7 +4792,8 @@ Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) {
position,
row[7], // time_of_death char* time_of_death
atoi(row[8]) == 1, // is_rezzed bool rezzed
atoi(row[9]) // was_at_graveyard bool was_at_graveyard
atoi(row[9]), // was_at_graveyard bool was_at_graveyard
atoul(row[10]) // guild_consent_id uint32 guild_consent_id
);
entity_list.AddCorpse(NewCorpse);
}
@ -4789,10 +4803,10 @@ Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 player_corpse_id) {
bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) {
std::string query;
if (!RuleB(Zone, EnableShadowrest)){
query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, was_at_graveyard FROM character_corpses WHERE zone_id='%u' AND instance_id='%u'", zone_id, instance_id);
query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, was_at_graveyard, guild_consent_id FROM character_corpses WHERE zone_id='%u' AND instance_id='%u'", zone_id, instance_id);
}
else{
query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, 0 as was_at_graveyard FROM character_corpses WHERE zone_id='%u' AND instance_id='%u' AND is_buried=0", zone_id, instance_id);
query = StringFormat("SELECT id, charid, charname, x, y, z, heading, time_of_death, is_rezzed, 0 as was_at_graveyard, guild_consent_id FROM character_corpses WHERE zone_id='%u' AND instance_id='%u' AND is_buried=0", zone_id, instance_id);
}
auto results = QueryDatabase(query);
@ -4806,7 +4820,8 @@ bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id) {
position,
row[7], // time_of_death char* time_of_death
atoi(row[8]) == 1, // is_rezzed bool rezzed
atoi(row[9]))
atoi(row[9]),
atoul(row[10])) // guild_consent_id uint32 guild_consent_id
);
}

View File

@ -367,8 +367,9 @@ public:
uint32 SendCharacterCorpseToGraveyard(uint32 dbid, uint32 zoneid, uint16 instanceid, const glm::vec4& position);
uint32 CreateGraveyardRecord(uint32 graveyard_zoneid, const glm::vec4& position);
uint32 AddGraveyardIDToZone(uint32 zone_id, uint32 graveyard_id);
uint32 SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position);
uint32 UpdateCharacterCorpse(uint32 dbid, uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, bool rezzed = false);
uint32 SaveCharacterCorpse(uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid);
uint32 UpdateCharacterCorpse(uint32 dbid, uint32 charid, const char* charname, uint32 zoneid, uint16 instanceid, PlayerCorpse_Struct* dbpc, const glm::vec4& position, uint32 guildid, bool rezzed = false);
uint32 UpdateCharacterCorpseConsent(uint32 charid, uint32 guildid);
uint32 GetFirstCorpseID(uint32 char_id);
uint32 GetCharacterCorpseCount(uint32 char_id);
uint32 GetCharacterCorpseID(uint32 char_id, uint8 corpse);

View File

@ -147,6 +147,7 @@ struct NPCType
int8 stuck_behavior;
uint16 use_model;
int8 flymode;
bool always_aggro;
};
namespace player_lootitem {