diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..589bb2ed4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -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}" + } +} diff --git a/.gitignore b/.gitignore index 89dd521c5..287c07412 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ *.out *.app +.bash_history + # CMake CMakeCache.txt CMakeFiles diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 000000000..4aee577ae --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/mysql" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..1c6e5c871 --- /dev/null +++ b/.vscode/tasks.json @@ -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 + } + } + ] +} diff --git a/README.md b/README.md index 5e4f919fe..f3b829732 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/common/database.cpp b/common/database.cpp index ac5ae16bf..352ecbbc1 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -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; } diff --git a/common/database_schema.h b/common/database_schema.h index 73637c877..b497ad474 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -22,17 +22,76 @@ #define EQEMU_DATABASE_SCHEMA_H #include +#include namespace DatabaseSchema { /** - * Gets player tables + * Character-specific tables + * + * Does not included related meta-data tables such as 'guilds', 'accounts' + * @return + */ + static std::map 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 GetPlayerTables() { - std::vector 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 GetContentTables() { - std::vector 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 GetServerTables() { - std::vector 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 GetStateTables() { - std::vector 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 GetLoginTables() { - std::vector 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 GetVersionTables() { - std::vector tables = { + return { "db_version", "inventory_versions", }; - - return tables; } } diff --git a/common/dbcore.cpp b/common/dbcore.cpp index 46c7fbe12..638fab4ae 100644 --- a/common/dbcore.cpp +++ b/common/dbcore.cpp @@ -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(), diff --git a/common/emu_constants.h b/common/emu_constants.h index c6491b3b3..945f513b5 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -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*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 3fe2430db..e9e7c5ef3 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -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]; diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index f5d58a5fd..dca47dfb8 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -124,6 +124,8 @@ void EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::Loginserver].log_to_console = static_cast(Logs::General); log_settings[Logs::HeadlessClient].log_to_console = static_cast(Logs::General); log_settings[Logs::NPCScaling].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::HotReload].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::HotReload].log_to_console = static_cast(Logs::General); /** * RFC 5424 diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 9fa164d98..cb880e038 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -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", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index ccfc54f3a..2ca5127b6 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -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) diff --git a/common/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index 8448049f5..bd0c39489 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -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; diff --git a/common/net/packet.h b/common/net/packet.h index 1ce173a10..4ff5f8510 100644 --- a/common/net/packet.h +++ b/common/net/packet.h @@ -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 m_data; }; } -} \ No newline at end of file +} diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index cecafb767..10ce35654 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -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]; diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 7f466f09b..03f1a951a 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -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]; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 0803432db..76a534e6a 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -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]; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 0b175b643..1507630bd 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -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]; }; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index e165da794..b6eb414e1 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -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]; diff --git a/common/ruletypes.h b/common/ruletypes.h index dab1d02e1..f1d00529f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -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 diff --git a/common/servertalk.h b/common/servertalk.h index b9cb6202e..032df1ddc 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -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 { diff --git a/common/version.h b/common/version.h index 5d00daaf4..359c27576 100644 --- a/common/version.h +++ b/common/version.h @@ -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 diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 9467ab337..31b091ff8 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -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 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index f410a3193..a39fde999 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -448,6 +448,8 @@ OP_FinishWindow2=0x40ef OP_ItemVerifyRequest=0x189c OP_ItemVerifyReply=0x097b +OP_ItemAdvancedLoreText=0x023b + # merchant stuff OP_ShopPlayerSell=0x791b OP_ShopRequest=0x4fed diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index d836c6360..619f986c1 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -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 diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index a10715918..6a5d41fb4 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -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 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 71966dcbb..69d6bb1e5 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -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 diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index 2c769e811..a5ae9e7b8 100755 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -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 () { - 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 () { + 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 . ""); + } } } diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 53a71e109..9be222856 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -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 diff --git a/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql b/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql new file mode 100644 index 000000000..b42f257ea --- /dev/null +++ b/utils/sql/git/required/2020_01_28_corpse_guild_consent_id.sql @@ -0,0 +1 @@ +ALTER TABLE `character_corpses` ADD COLUMN `guild_consent_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_of_death`; diff --git a/utils/sql/git/required/2020_02_06_aa_reset_on_death.sql b/utils/sql/git/required/2020_02_06_aa_reset_on_death.sql new file mode 100644 index 000000000..91edb5d4a --- /dev/null +++ b/utils/sql/git/required/2020_02_06_aa_reset_on_death.sql @@ -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; diff --git a/utils/sql/git/required/2020_02_06_globalloot.sql b/utils/sql/git/required/2020_02_06_globalloot.sql new file mode 100644 index 000000000..83144bbb8 --- /dev/null +++ b/utils/sql/git/required/2020_02_06_globalloot.sql @@ -0,0 +1,2 @@ +ALTER TABLE `global_loot` ADD `hot_zone` TINYINT NULL; + diff --git a/utils/sql/git/required/2020_03_05_npc_always_aggro.sql b/utils/sql/git/required/2020_03_05_npc_always_aggro.sql new file mode 100644 index 000000000..83641998c --- /dev/null +++ b/utils/sql/git/required/2020_03_05_npc_always_aggro.sql @@ -0,0 +1 @@ +ALTER TABLE `npc_types` ADD COLUMN `always_aggro` tinyint(1) NOT NULL DEFAULT 0; diff --git a/world/console.cpp b/world/console.cpp index 90e5f7c71..b3f36c923 100644 --- a/world/console.cpp +++ b/world/console.cpp @@ -802,9 +802,9 @@ void ConsoleIpLookup( const std::vector &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 &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& 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)); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index dd55fac39..08ccbfaff 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -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; diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index f75e963f9..80415f6ff 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -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}) diff --git a/zone/aa.cpp b/zone/aa.cpp index 1383ad2f9..1ccc6f656 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -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_maptype = 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(ability); diff --git a/zone/aa_ability.h b/zone/aa_ability.h index 0d6a240c4..5ec08d986 100644 --- a/zone/aa_ability.h +++ b/zone/aa_ability.h @@ -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; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index ff0077353..87b98d599 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -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())); diff --git a/zone/attack.cpp b/zone/attack.cpp index 308592041..4da160608 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -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 diff --git a/zone/beacon.cpp b/zone/beacon.cpp index 81b964290..ac4beaacc 100644 --- a/zone/beacon.cpp +++ b/zone/beacon.cpp @@ -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) diff --git a/zone/bot.cpp b/zone/bot.cpp index 5579b4bc5..6d14ce740 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -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(); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index dc9d40aab..9c202fed8 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -3928,6 +3928,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep) "null" "(toggles)" "" + "" + "monkwumessage" + "enable | disable" + "displays monk wu trigger messages" + "" + "" + "" + "null" + "(toggles)" + "" "" "current" "" @@ -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) "Option
------" "Argument
-------" "" - "" "deathmarquee" "{}" "" - "" "statsupdate" "{}" "" - "" "spawnmessage" "{}" "" - "" "spawnmessage" "{}" "" - "" "altcombat" "{}" "" - "" "autodefend" "{}" "" - "" "buffcounter" "{}" "" + "" "deathmarquee" "{}" "" + "" "statsupdate" "{}" "" + "" "spawnmessage" "{}" "" + "" "spawnmessage" "{}" "" + "" "altcombat" "{}" "" + "" "autodefend" "{}" "" + "" "buffcounter" "{}" "" + "" "monkwumessage" "{}" "" "", (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()); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 2f6f9c1c6..28b5288e0 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -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 ('{}', '{}', '{}')", diff --git a/zone/client.cpp b/zone/client.cpp index 6b366afba..8b60b391a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -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()) diff --git a/zone/client.h b/zone/client.h index 300073bb5..58b0d9af8 100644 --- a/zone/client.h +++ b/zone/client.h @@ -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 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 }; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index dfb7ab673..7173dd342 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -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; diff --git a/zone/command.cpp b/zone/command.cpp index 877529c2b..757e2d522 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -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; diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 09661826d..fd4174868 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -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::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; } } diff --git a/zone/corpse.h b/zone/corpse.h index e7a7c5805..d453c7e9e 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -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 consented_player_names; LootRequestType loot_request_type; }; diff --git a/zone/encounter.cpp b/zone/encounter.cpp index e0ec83d90..7d37844b6 100644 --- a/zone/encounter.cpp +++ b/zone/encounter.cpp @@ -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; diff --git a/zone/global_loot_manager.cpp b/zone/global_loot_manager.cpp index ccdf88840..706769f77 100644 --- a/zone/global_loot_manager.cpp +++ b/zone/global_loot_manager.cpp @@ -1,6 +1,9 @@ #include "global_loot_manager.h" #include "npc.h" #include "client.h" +#include "zone.h" + +extern Zone *zone; std::vector 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; } diff --git a/zone/global_loot_manager.h b/zone/global_loot_manager.h index fec5a7215..1de6e7831 100644 --- a/zone/global_loot_manager.h +++ b/zone/global_loot_manager.h @@ -17,6 +17,7 @@ enum class RuleTypes { BodyType = 4, Rare = 5, Raid = 6, + HotZone = 7, Max }; diff --git a/zone/loottables.cpp b/zone/loottables.cpp index 3b5ae520f..0e5ba8d60 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -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); } } diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 4a8bd12a9..bf52b9276 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -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_("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 diff --git a/zone/lua_npc.h b/zone/lua_npc.h index 92ad73cd7..1c7c879f7 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -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 diff --git a/zone/mob.cpp b/zone/mob.cpp index a6de82eee..5d25c42f0 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -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; diff --git a/zone/mob.h b/zone/mob.h index 7c095a001..f1c128080 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -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; diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 5e40dfeee..4ac0c7f23 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -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; } } diff --git a/zone/npc.cpp b/zone/npc.cpp index ed5f37bb5..6df237d5d 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -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) } } -} \ No newline at end of file +} + +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; + } + } +} diff --git a/zone/npc.h b/zone/npc.h index d0ecdf1ca..2b8edc9b9 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -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 diff --git a/zone/pathfinder_nav_mesh.cpp b/zone/pathfinder_nav_mesh.cpp index 5be10b64f..998515a35 100644 --- a/zone/pathfinder_nav_mesh.cpp +++ b/zone/pathfinder_nav_mesh.cpp @@ -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(¤t_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, ¤t_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(¤t_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(¤t_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, ¤t_location[0], &dest_location[0], &filter, path, &npoly, max_polys); - + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_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(¤t_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 ¤t = 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; diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 2dae9e776..1d40474f3 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -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; } diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index b7cdebf02..9334e4f6c 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -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) { diff --git a/zone/spells.cpp b/zone/spells.cpp index a1e926a1c..5b8ee33f8 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -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) diff --git a/zone/string_ids.h b/zone/string_ids.h index 25321b3c9..e543007a0 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -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 diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index a5180963d..60f770c92 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -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); diff --git a/zone/zone.cpp b/zone/zone.cpp index b88ddd071..16cdc0af8 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -55,6 +55,7 @@ #include "mob_movement_manager.h" #include "npc_scale_manager.h" #include "../common/data_verification.h" +#include "zone_reload.h" #include #include @@ -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; +} diff --git a/zone/zone.h b/zone/zone.h index 4ed017627..1243e18c2 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -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; diff --git a/zone/zone_reload.cpp b/zone/zone_reload.cpp new file mode 100644 index 000000000..3dca89d71 --- /dev/null +++ b/zone/zone_reload.cpp @@ -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() + ); +} diff --git a/zone/zone_reload.h b/zone/zone_reload.h new file mode 100644 index 000000000..1e4f95826 --- /dev/null +++ b/zone/zone_reload.h @@ -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 diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index d5346877d..cad4e1df8 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -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 ); } diff --git a/zone/zonedb.h b/zone/zonedb.h index d5c02c693..b1d88539d 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -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); diff --git a/zone/zonedump.h b/zone/zonedump.h index 6010c9480..0a1bcd07d 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -147,6 +147,7 @@ struct NPCType int8 stuck_behavior; uint16 use_model; int8 flymode; + bool always_aggro; }; namespace player_lootitem {