diff --git a/.gitignore b/.gitignore index 2f37a33f1..627d3c630 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ compile_flags.txt # CMake Files cmake-build-relwithdebinfo/* +skill-caps.diff diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bc3dd52bd..3610c759e 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -99,6 +99,7 @@ SET(common_sources json/json.hpp json/jsoncpp.cpp zone_store.cpp + memory/ksm.hpp net/console_server.cpp net/console_server_connection.cpp net/crc32.cpp diff --git a/common/database.cpp b/common/database.cpp index a3405970d..f1d3a3757 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -50,6 +50,7 @@ #include "../common/repositories/raid_members_repository.h" #include "../common/repositories/reports_repository.h" #include "../common/repositories/variables_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" // Disgrace: for windows compile @@ -313,6 +314,12 @@ bool Database::ReserveName(uint32 account_id, const std::string& name) return false; } + const auto& p = CharacterPetNameRepository::GetWhere(*this, where_filter); + if (!p.empty()) { + LogInfo("Account [{}] requested name [{}] but name is already taken by an Pet", account_id, name); + return false; + } + auto e = CharacterDataRepository::NewEntity(); e.account_id = account_id; diff --git a/common/database/database_update.cpp b/common/database/database_update.cpp index cd33d2abf..0542d130d 100644 --- a/common/database/database_update.cpp +++ b/common/database/database_update.cpp @@ -169,7 +169,10 @@ bool DatabaseUpdate::UpdateManifest( LogSys.EnableMySQLErrorLogs(); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); - if (!missing_migrations.empty()) { + if (!missing_migrations.empty() && m_skip_backup) { + LogInfo("Skipping database backup"); + } + else if (!missing_migrations.empty()) { LogInfo("Automatically backing up database before applying updates"); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); auto s = DatabaseDumpService(); @@ -271,6 +274,13 @@ DatabaseUpdate *DatabaseUpdate::SetContentDatabase(Database *db) return this; } +DatabaseUpdate *DatabaseUpdate::SetSkipBackup(bool skip) +{ + m_skip_backup = skip; + + return this; +} + bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b) { LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); diff --git a/common/database/database_update.h b/common/database/database_update.h index 0ba7b8df5..be820a9e8 100644 --- a/common/database/database_update.h +++ b/common/database/database_update.h @@ -29,12 +29,15 @@ public: DatabaseUpdate *SetDatabase(Database *db); DatabaseUpdate *SetContentDatabase(Database *db); + DatabaseUpdate *SetSkipBackup(bool skip); bool HasPendingUpdates(); private: + bool m_skip_backup = false; Database *m_database; Database *m_content_database; static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b); void InjectBotsVersionColumn(); + }; #endif //EQEMU_DATABASE_UPDATE_H diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 8e17fb951..57819b700 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -6289,7 +6289,50 @@ INSERT INTO `items_evolving_details` VALUES )", .content_schema_update = true - } + }, + ManifestEntry{ + .version = 9291, + .description = "2025_01_21_add_remove_zone_fields", + .check = "SHOW COLUMNS FROM `zone` LIKE 'client_update_range'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE zone DROP COLUMN IF EXISTS npc_update_range; +ALTER TABLE zone DROP COLUMN IF EXISTS max_movement_update_range; +ALTER TABLE `zone` ADD COLUMN `client_update_range` int(11) NOT NULL DEFAULT 600 AFTER `npc_max_aggro_dist`; +)", + .content_schema_update = true, + }, + ManifestEntry{ + .version = 9292, + .description = "2025_01_21_data_buckets_account_id", + .check = "SHOW COLUMNS FROM `data_buckets` LIKE 'account_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `data_buckets` +ADD COLUMN `account_id` bigint(11) NULL DEFAULT 0 AFTER `expires`, +DROP INDEX `keys`, +ADD UNIQUE INDEX `keys` (`key`, `character_id`, `npc_id`, `bot_id`, `account_id`) USING BTREE; + +-- Add the INDEX for character_id and key +ALTER TABLE `data_buckets` ADD KEY `idx_account_id_key` (`account_id`, `key`); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9293, + .description = "2025_01_10_create_pet_names_table.sql", + .check = "SHOW TABLES LIKE 'character_pet_name'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_pet_name` ( + `character_id` INT(11) NOT NULL PRIMARY KEY, + `name` VARCHAR(64) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +)", + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228, diff --git a/common/database_schema.h b/common/database_schema.h index 0d2b7237d..6c0aac9c9 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -64,6 +64,7 @@ namespace DatabaseSchema { {"character_pet_buffs", "char_id"}, {"character_pet_info", "char_id"}, {"character_pet_inventory", "char_id"}, + {"character_pet_name", "character_id"}, {"character_peqzone_flags", "id"}, {"character_potionbelt", "id"}, {"character_skills", "id"}, diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 6b5ab12ab..eefcdfee8 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -77,6 +77,7 @@ N(OP_CashReward), N(OP_CastSpell), N(OP_ChangeSize), N(OP_ChannelMessage), +N(OP_ChangePetName), N(OP_CharacterCreate), N(OP_CharacterCreateRequest), N(OP_CharInventory), @@ -284,6 +285,8 @@ N(OP_InspectMessageUpdate), N(OP_InspectRequest), N(OP_InstillDoubt), N(OP_InterruptCast), +N(OP_InvokeChangePetName), +N(OP_InvokeChangePetNameImmediate), N(OP_ItemLinkClick), N(OP_ItemLinkResponse), N(OP_ItemLinkText), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index bfe5abf44..f6e2b9ed1 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -5819,6 +5819,21 @@ struct ChangeSize_Struct /*16*/ }; +struct ChangePetName_Struct { +/*00*/ char new_pet_name[64]; +/*40*/ char pet_owner_name[64]; +/*80*/ int response_code; +}; + +enum ChangePetNameResponse : int { + Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name. + Accepted = 1, // 5976 Your request for a name change was successful. + Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes. + NotEligible = -4, // 5980 Your character is not eligible for a name change. + Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change. + Unhandled = -1 +}; + // New OpCode/Struct for SoD+ struct GroupMakeLeader_Struct { diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index c6bc26d14..de2fc72e9 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -143,6 +143,8 @@ namespace Logs { Corpses, XTargets, EvolveItem, + PositionUpdate, + KSM, BotSettings, BotSpellChecks, BotSpellTypeChecks, @@ -249,6 +251,8 @@ namespace Logs { "Corpses", "XTargets", "EvolveItem", + "PositionUpdate", + "KSM" // Kernel Samepage Merging "Bot Settings", "Bot Spell Checks", "Bot Spell Type Checks", diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index bee8a8f1e..2b3e1c83a 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -854,6 +854,26 @@ OutF(LogSys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogPositionUpdate(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::PositionUpdate))\ + OutF(LogSys, Logs::General, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogPositionUpdateDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::PositionUpdate))\ + OutF(LogSys, Logs::Detail, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__); \ +} while (0) + +#define LogKSM(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::KSM))\ + OutF(LogSys, Logs::General, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogKSMDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::KSM))\ + OutF(LogSys, Logs::Detail, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define LogBotSettings(message, ...) do {\ if (LogSys.IsLogEnabled(Logs::General, Logs::BotSettings))\ OutF(LogSys, Logs::General, Logs::BotSettings, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/common/memory/ksm.hpp b/common/memory/ksm.hpp new file mode 100644 index 000000000..079b83f1a --- /dev/null +++ b/common/memory/ksm.hpp @@ -0,0 +1,220 @@ +#ifndef EQEMU_KSM_HPP +#define EQEMU_KSM_HPP + +#include "../eqemu_logsys.h" +#include +#include +#include +#ifdef _WIN32 +#include // For _aligned_malloc, _aligned_free +#include +#else +#include // For madvise +#include // For sysconf, sbrk +#endif + + +// Page-aligned allocator for std::vector +template +class PageAlignedAllocator { +public: + using value_type = T; + + PageAlignedAllocator() noexcept = default; + template + PageAlignedAllocator(const PageAlignedAllocator&) noexcept {} + + T* allocate(std::size_t n) { + void* ptr = nullptr; + size_t size = n * sizeof(T); + +#ifdef _WIN32 + // Simply allocate memory without alignment + ptr = malloc(size); + if (!ptr) throw std::bad_alloc(); +#else + size_t alignment = getPageSize(); // Get the system's page size + if (posix_memalign(&ptr, alignment, size) != 0) { + throw std::bad_alloc(); + } +#endif + return static_cast(ptr); + } + + void deallocate(T* p, std::size_t) noexcept { + free(p); + } + +private: + size_t getPageSize() const + { +#ifdef _WIN32 + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; // Page size in bytes +#else + return static_cast(sysconf(_SC_PAGESIZE)); +#endif + }; +}; + +template +bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return true; +} + +template +bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return false; +} + +// Kernel Samepage Merging (KSM) functionality +namespace KSM { + +#ifdef _WIN32 + // Windows-specific placeholder functions (no-op) + inline void CheckPageAlignment(void* ptr) { + } + + inline void* AllocatePageAligned(size_t size) { + return memset(malloc(size), 0, size); + } + + inline void MarkMemoryForKSM(void* start, size_t size) { + } + + inline void AlignHeapToPageBoundary() { + } + + inline void* MarkHeapStart() { + return nullptr; + } + + inline size_t MeasureHeapUsage(void* start) { + return 0; + } +#else + // Linux-specific functionality + inline void CheckPageAlignment(void* ptr) { + size_t page_size = sysconf(_SC_PAGESIZE); + if (reinterpret_cast(ptr) % page_size == 0) { + LogKSMDetail("Memory is page-aligned [{}]", ptr); + } else { + LogKSMDetail("Memory is NOT page-aligned [{}]", ptr); + } + } + + inline void* AllocatePageAligned(size_t size) { + size_t page_size = sysconf(_SC_PAGESIZE); + void* aligned_ptr = nullptr; + if (posix_memalign(&aligned_ptr, page_size, size) != 0) { + LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size); + } + std::memset(aligned_ptr, 0, size); + return aligned_ptr; + } + + inline void MarkMemoryForKSM(void* start, size_t size) { + if (madvise(start, size, MADV_MERGEABLE) == 0) { + LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size); + } else { + perror("madvise failed"); + } + } + + inline void AlignHeapToPageBoundary() { + size_t page_size = sysconf(_SC_PAGESIZE); + if (page_size == 0) { + LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size); + return; + } + + void* current_break = sbrk(0); + if (current_break == (void*)-1) { + LogKSM("Failed to retrieve the current program break"); + return; + } + + uintptr_t current_address = reinterpret_cast(current_break); + size_t misalignment = current_address % page_size; + + if (misalignment != 0) { + size_t adjustment = page_size - misalignment; + if (sbrk(adjustment) == (void*)-1) { + LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment); + return; + } + } + + LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0)); + } + + inline void* MarkHeapStart() { + void* current_pos = sbrk(0); + AlignHeapToPageBoundary(); + return current_pos; + } + + inline size_t MeasureHeapUsage(void* start) { + void* current_break = sbrk(0); + return static_cast(current_break) - static_cast(start); + } +#endif + + + inline size_t getPageSize() + { +#ifdef _WIN32 + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; // Page size in bytes +#else + return static_cast(sysconf(_SC_PAGESIZE)); // POSIX page size +#endif + }; + + template + inline void PageAlignVectorAligned(std::vector>& vec) { + if (vec.empty()) { + return; + } + + size_t page_size = getPageSize(); + void* start = vec.data(); + size_t size = vec.size() * sizeof(T); + + // Check if the memory is page-aligned + if (reinterpret_cast(start) % page_size != 0) { + // Allocate a new aligned vector + std::vector> aligned_vec(vec.get_allocator()); + aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy + + // Copy elements from the original vector + aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end()); + + // Swap the aligned vector with the original vector + vec.swap(aligned_vec); + + // Clear the temporary aligned vector to free its memory + aligned_vec.clear(); + + // Verify the new alignment + start = vec.data(); + if (reinterpret_cast(start) % page_size != 0) { + throw std::runtime_error("Failed to align vector memory to page boundaries."); + } + + LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size); + } else { + LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size); + } + +#ifndef _WIN32 + // Mark memory for KSM (only on non-Windows systems) + MarkMemoryForKSM(start, size); +#endif + } + +} + +#endif // EQEMU_KSM_HPP diff --git a/common/repositories/base/base_character_pet_name_repository.h b/common/repositories/base/base_character_pet_name_repository.h new file mode 100644 index 000000000..59a8a572f --- /dev/null +++ b/common/repositories/base/base_character_pet_name_repository.h @@ -0,0 +1,392 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterPetNameRepository { +public: + struct CharacterPetName { + int32_t character_id; + std::string name; + }; + + static std::string PrimaryKey() + { + return std::string("character_id"); + } + + static std::vector Columns() + { + return { + "character_id", + "name", + }; + } + + static std::vector SelectColumns() + { + return { + "character_id", + "name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_pet_name"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterPetName NewEntity() + { + CharacterPetName e{}; + + e.character_id = 0; + e.name = ""; + + return e; + } + + static CharacterPetName GetCharacterPetName( + const std::vector &character_pet_names, + int character_pet_name_id + ) + { + for (auto &character_pet_name : character_pet_names) { + if (character_pet_name.character_id == character_pet_name_id) { + return character_pet_name; + } + } + + return NewEntity(); + } + + static CharacterPetName FindOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_pet_name_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_pet_name_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.character_id)); + v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.character_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterPetName InsertOne( + Database& db, + CharacterPetName e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.character_id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/repositories/base/base_data_buckets_repository.h b/common/repositories/base/base_data_buckets_repository.h index 3e7ca05d0..42c85336c 100644 --- a/common/repositories/base/base_data_buckets_repository.h +++ b/common/repositories/base/base_data_buckets_repository.h @@ -23,6 +23,7 @@ public: std::string key_; std::string value; uint32_t expires; + int64_t account_id; int64_t character_id; int64_t npc_id; int64_t bot_id; @@ -36,6 +37,7 @@ public: CEREAL_NVP(key_), CEREAL_NVP(value), CEREAL_NVP(expires), + CEREAL_NVP(account_id), CEREAL_NVP(character_id), CEREAL_NVP(npc_id), CEREAL_NVP(bot_id) @@ -55,6 +57,7 @@ public: "`key`", "value", "expires", + "account_id", "character_id", "npc_id", "bot_id", @@ -68,6 +71,7 @@ public: "`key`", "value", "expires", + "account_id", "character_id", "npc_id", "bot_id", @@ -115,6 +119,7 @@ public: e.key_ = ""; e.value = ""; e.expires = 0; + e.account_id = 0; e.character_id = 0; e.npc_id = 0; e.bot_id = 0; @@ -158,9 +163,10 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.bot_id = row[7] ? strtoll(row[7], nullptr, 10) : 0; return e; } @@ -197,9 +203,10 @@ public: v.push_back(columns[1] + " = '" + Strings::Escape(e.key_) + "'"); v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'"); v.push_back(columns[3] + " = " + std::to_string(e.expires)); - v.push_back(columns[4] + " = " + std::to_string(e.character_id)); - v.push_back(columns[5] + " = " + std::to_string(e.npc_id)); - v.push_back(columns[6] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[4] + " = " + std::to_string(e.account_id)); + v.push_back(columns[5] + " = " + std::to_string(e.character_id)); + v.push_back(columns[6] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[7] + " = " + std::to_string(e.bot_id)); auto results = db.QueryDatabase( fmt::format( @@ -225,6 +232,7 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); @@ -261,6 +269,7 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); @@ -301,9 +310,10 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.bot_id = row[7] ? strtoll(row[7], nullptr, 10) : 0; all_entries.push_back(e); } @@ -332,9 +342,10 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.bot_id = row[7] ? strtoll(row[7], nullptr, 10) : 0; all_entries.push_back(e); } @@ -413,6 +424,7 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); @@ -442,6 +454,7 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); diff --git a/common/repositories/base/base_zone_repository.h b/common/repositories/base/base_zone_repository.h index 3ba11466d..1b4293109 100644 --- a/common/repositories/base/base_zone_repository.h +++ b/common/repositories/base/base_zone_repository.h @@ -111,7 +111,7 @@ public: int32_t fast_regen_mana; int32_t fast_regen_endurance; int32_t npc_max_aggro_dist; - uint32_t max_movement_update_range; + uint32_t client_update_range; int32_t underworld_teleport_index; int32_t lava_damage; int32_t min_lava_damage; @@ -220,7 +220,7 @@ public: "fast_regen_mana", "fast_regen_endurance", "npc_max_aggro_dist", - "max_movement_update_range", + "client_update_range", "underworld_teleport_index", "lava_damage", "min_lava_damage", @@ -325,7 +325,7 @@ public: "fast_regen_mana", "fast_regen_endurance", "npc_max_aggro_dist", - "max_movement_update_range", + "client_update_range", "underworld_teleport_index", "lava_damage", "min_lava_damage", @@ -464,7 +464,7 @@ public: e.fast_regen_mana = 180; e.fast_regen_endurance = 180; e.npc_max_aggro_dist = 600; - e.max_movement_update_range = 600; + e.client_update_range = 600; e.underworld_teleport_index = 0; e.lava_damage = 50; e.min_lava_damage = 10; @@ -599,7 +599,7 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; @@ -730,7 +730,7 @@ public: v.push_back(columns[89] + " = " + std::to_string(e.fast_regen_mana)); v.push_back(columns[90] + " = " + std::to_string(e.fast_regen_endurance)); v.push_back(columns[91] + " = " + std::to_string(e.npc_max_aggro_dist)); - v.push_back(columns[92] + " = " + std::to_string(e.max_movement_update_range)); + v.push_back(columns[92] + " = " + std::to_string(e.client_update_range)); v.push_back(columns[93] + " = " + std::to_string(e.underworld_teleport_index)); v.push_back(columns[94] + " = " + std::to_string(e.lava_damage)); v.push_back(columns[95] + " = " + std::to_string(e.min_lava_damage)); @@ -850,7 +850,7 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); @@ -978,7 +978,7 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); @@ -1110,7 +1110,7 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; @@ -1233,7 +1233,7 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; @@ -1406,7 +1406,7 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); @@ -1527,7 +1527,7 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); diff --git a/common/repositories/character_pet_name_repository.h b/common/repositories/character_pet_name_repository.h new file mode 100644 index 000000000..8fbb1e94b --- /dev/null +++ b/common/repositories/character_pet_name_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_pet_name_repository.h" + +class CharacterPetNameRepository: public BaseCharacterPetNameRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/shareddb.cpp b/common/shareddb.cpp index c51b79885..679bffe9d 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -661,7 +661,7 @@ bool SharedDatabase::GetInventory(Client *c) // Retrieve character inventory auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`", char_id)); auto e_results = CharacterEvolvingItemsRepository::GetWhere( - *this, fmt::format("`char_id` = '{}' AND `deleted_at` IS NULL", char_id) + *this, fmt::format("`character_id` = '{}' AND `deleted_at` IS NULL", char_id) ); if (results.empty()) { diff --git a/common/timer.h b/common/timer.h index 6678be952..599f7b2f2 100644 --- a/common/timer.h +++ b/common/timer.h @@ -86,6 +86,9 @@ struct BenchTimer void reset() { start_time = clock::now(); } // this is seconds double elapsed() { return std::chrono::duration (clock::now() - start_time).count(); } + std::chrono::milliseconds::rep elapsedMilliseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } + std::chrono::microseconds::rep elapsedMicroseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } + std::chrono::nanoseconds::rep elapsedNanoseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } private: std::chrono::time_point start_time; }; diff --git a/common/version.h b/common/version.h index 4324ae027..1cbb41f15 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9290 +#define CURRENT_BINARY_DATABASE_VERSION 9293 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #endif diff --git a/common/zone_store.cpp b/common/zone_store.cpp index 23a59c31e..fa64bd492 100644 --- a/common/zone_store.cpp +++ b/common/zone_store.cpp @@ -676,12 +676,6 @@ int ZoneStore::GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version) return z ? z->npc_max_aggro_dist : DEFAULT_ZONE_MAX_AGGRO_DISTANCE; } -uint32 ZoneStore::GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version) -{ - const auto& z = GetZoneVersionWithFallback(zone_id, version); - return z ? z->max_movement_update_range : DEFAULT_ZONE_MAX_MOVEMENT_UPDATE_RANGE; -} - int8 ZoneStore::GetZoneMinimumExpansion(uint32 zone_id, int version) { const auto& z = GetZoneVersionWithFallback(zone_id, version); diff --git a/common/zone_store.h b/common/zone_store.h index b63e807f2..d8ee7233d 100644 --- a/common/zone_store.h +++ b/common/zone_store.h @@ -94,7 +94,6 @@ public: int GetZoneFastRegenMana(uint32 zone_id, int version = 0); int GetZoneFastRegenEndurance(uint32 zone_id, int version = 0); int GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version = 0); - uint32 GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version = 0); int8 GetZoneMinimumExpansion(uint32 zone_id, int version = 0); int8 GetZoneMaximumExpansion(uint32 zone_id, int version = 0); const std::string GetZoneContentFlags(uint32 zone_id, int version = 0); diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 93d6f0463..a4e6d68ed 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -735,3 +735,12 @@ OP_PickZone=0xaaba #evolve item related OP_EvolveItem=0x7cfb + +# This is bugged in ROF2 +# OP_InvokeChangePetNameImmediate is supposed to write DisablePetNameChangeReminder=0 to reset the 'nag reminder' +# It actually sets DisableNameChangeReminder=0 (player name change nag reminder). Additionally, clicking the +# 'Don't remind me later' button sets DisableNameChangeReminder=1 +# This can be fixed with a client patch. +OP_InvokeChangePetNameImmediate=0x046d +OP_InvokeChangePetName=0x4506 +OP_ChangePetName=0x5dab \ No newline at end of file diff --git a/world/cli/copy_character.cpp b/world/cli/copy_character.cpp index 86c268217..ebe8e1831 100644 --- a/world/cli/copy_character.cpp +++ b/world/cli/copy_character.cpp @@ -11,13 +11,13 @@ void WorldserverCLI::CopyCharacter(int argc, char **argv, argh::parser &cmd, std "destination_account_name" }; std::vector options = {}; - - EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - + if (cmd[{"-h", "--help"}]) { return; } + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + std::string source_character_name = cmd(2).str(); std::string destination_character_name = cmd(3).str(); std::string destination_account_name = cmd(4).str(); diff --git a/world/cli/database_dump.cpp b/world/cli/database_dump.cpp index 046539688..e9068d63b 100644 --- a/world/cli/database_dump.cpp +++ b/world/cli/database_dump.cpp @@ -24,12 +24,12 @@ void WorldserverCLI::DatabaseDump(int argc, char **argv, argh::parser &cmd, std: "--compress" }; - EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - if (cmd[{"-h", "--help"}]) { return; } + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + auto s = new DatabaseDumpService(); bool dump_all = cmd[{"-a", "--all"}]; diff --git a/world/cli/database_updates.cpp b/world/cli/database_updates.cpp index f85316de5..f967349fa 100644 --- a/world/cli/database_updates.cpp +++ b/world/cli/database_updates.cpp @@ -4,12 +4,17 @@ void WorldserverCLI::DatabaseUpdates(int argc, char **argv, argh::parser &cmd, s { description = "Runs database updates manually"; + std::vector options = { + "--skip-backup", + }; + if (cmd[{"-h", "--help"}]) { return; } DatabaseUpdate update; update.SetDatabase(&database) + ->SetSkipBackup(cmd[{"--skip-backup"}] ) ->SetContentDatabase(&content_db) ->CheckDbUpdates(); } diff --git a/world/cli/test.cpp b/world/cli/test.cpp index 738741723..172d46d21 100644 --- a/world/cli/test.cpp +++ b/world/cli/test.cpp @@ -1,6 +1,8 @@ #include #include +#include #include "../../common/events/player_events.h" +#include "../../common/memory/ksm.hpp" void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::string &description) { @@ -10,5 +12,21 @@ void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std:: return; } - + void* start_marker = KSM::MarkHeapStart(); + std::cout << "Start marker: " << start_marker << "\n"; + + std::vector vec = {}; + for (int i = 0; i < 100000; i++) { + vec.push_back("Some random string"); + } + + // Measure allocated memory size + size_t allocated_size = KSM::MeasureHeapUsage(start_marker); + // Convert to MB as a float and output with precision + double allocated_size_mb = static_cast(allocated_size) / (1024 * 1024); + std::cout << std::fixed << std::setprecision(3) + << "Allocated size: " << allocated_size_mb << " MB\n"; + + // Mark memory for KSM + KSM::MarkMemoryForKSM(start_marker, allocated_size); } diff --git a/zone/bot.cpp b/zone/bot.cpp index 6f1870caa..f0192526f 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7549,40 +7549,6 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { return; } -/** - * @param close_mobs - * @param scanning_mob - */ -void EntityList::ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob) -{ - float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); - - close_mobs.clear(); - - for (auto& e : mob_list) { - auto mob = e.second; - - if (!mob->IsClient()) { - continue; - } - - if (mob->GetID() <= 0) { - continue; - } - - float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); - - if (distance <= scan_range) { - close_mobs.insert(std::pair(mob->GetID(), mob)); - } - else if (mob->GetAggroRange() >= scan_range) { - close_mobs.insert(std::pair(mob->GetID(), mob)); - } - } - - LogAIScanCloseDetail("Close Client Mob List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName()); -} - uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool include_pets, Raid* raid) { uint8 need_healed = 0; diff --git a/zone/client.cpp b/zone/client.cpp index c0ec607e2..bc6f720ee 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -67,6 +67,7 @@ extern volatile bool RunLoops; #include "../common/repositories/character_spells_repository.h" #include "../common/repositories/character_disciplines_repository.h" #include "../common/repositories/character_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/repositories/discovered_items_repository.h" #include "../common/repositories/inventory_repository.h" #include "../common/repositories/keyring_repository.h" @@ -159,7 +160,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob( endupkeep_timer(1000), autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), - m_client_zone_wide_full_position_update_timer(5 * 60 * 1000), + m_client_bulk_npc_pos_update_timer(60 * 1000), tribute_timer(Tribute_duration), proximity_timer(ClientProximity_interval), TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), @@ -405,6 +406,7 @@ Client::~Client() { mMovementManager->RemoveClient(this); + DataBucket::DeleteCachedBuckets(DataBucketLoadType::Account, AccountID()); DataBucket::DeleteCachedBuckets(DataBucketLoadType::Client, CharacterID()); if (RuleB(Bots, Enabled)) { @@ -1832,7 +1834,7 @@ void Client::FriendsWho(char *FriendsString) { Message(0, "Error: World server disconnected"); else { auto pack = - new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); + new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer; FriendsWho->FromID = GetID(); strcpy(FriendsWho->FromName, GetName()); @@ -2213,7 +2215,7 @@ void Client::Stand() { } void Client::Sit() { - SetAppearance(eaSitting, false); + SetAppearance(eaSitting, false); } void Client::ChangeLastName(std::string last_name) { @@ -4396,6 +4398,83 @@ void Client::KeyRingList() } } +bool Client::IsPetNameChangeAllowed() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + auto b = DataBucket::GetData(k); + if (!b.value.empty()) { + return true; + } + + return false; +} + +void Client::InvokeChangePetName(bool immediate) { + if (!IsPetNameChangeAllowed()) { + return; + } + + auto packet_op = immediate ? OP_InvokeChangePetNameImmediate : OP_InvokeChangePetName; + + auto outapp = new EQApplicationPacket(packet_op, 0); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::GrantPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + k.value = "true"; + DataBucket::SetData(k); + + InvokeChangePetName(true); +} + +void Client::ClearPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + DataBucket::DeleteData(k); +} + +bool Client::ChangePetName(std::string new_name) +{ + if (new_name.empty()) { + return false; + } + + if (!IsPetNameChangeAllowed()) { + return false; + } + + auto pet = GetPet(); + if (!pet) { + return false; + } + + if (pet->GetName() == new_name) { + return false; + } + + if (!database.CheckNameFilter(new_name) || database.IsNameUsed(new_name)) { + return false; + } + + CharacterPetNameRepository::ReplaceOne( + database, + CharacterPetNameRepository::CharacterPetName{ + .character_id = static_cast(CharacterID()), + .name = new_name + } + ); + + pet->TempName(new_name.c_str()); + + ClearPetNameChange(); + return true; +} + bool Client::IsDiscovered(uint32 item_id) { const auto& l = DiscoveredItemsRepository::GetWhere( database, @@ -5632,13 +5711,13 @@ bool Client::TryReward(uint32 claim_id) if (amt == 1) { query = StringFormat("DELETE FROM account_rewards " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } else { query = StringFormat("UPDATE account_rewards SET amount = (amount-1) " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } @@ -8371,7 +8450,7 @@ int Client::GetQuiverHaste(int delay) for (int r = EQ::invslot::GENERAL_BEGIN; r <= EQ::invslot::GENERAL_END; r++) { pi = GetInv().GetItem(r); if (pi && pi->IsClassBag() && pi->GetItem()->BagType == EQ::item::BagTypeQuiver && - pi->GetItem()->BagWR > 0) + pi->GetItem()->BagWR > 0) break; if (r == EQ::invslot::GENERAL_END) // we will get here if we don't find a valid quiver @@ -9845,7 +9924,7 @@ const ExpeditionLockoutTimer* Client::GetExpeditionLockout( for (const auto& expedition_lockout : m_expedition_lockouts) { if ((include_expired || !expedition_lockout.IsExpired()) && - expedition_lockout.IsSameLockout(expedition_name, event_name)) + expedition_lockout.IsSameLockout(expedition_name, event_name)) { return &expedition_lockout; } @@ -9860,7 +9939,7 @@ std::vector Client::GetExpeditionLockouts( for (const auto& lockout : m_expedition_lockouts) { if ((include_expired || !lockout.IsExpired()) && - lockout.GetExpeditionName() == expedition_name) + lockout.GetExpeditionName() == expedition_name) { lockouts.emplace_back(lockout); } @@ -12944,50 +13023,77 @@ void Client::SendTopLevelInventory() } } -// On a normal basis we limit mob movement updates based on distance -// This ensures we send a periodic full zone update to a client that has started moving after 5 or so minutes -// -// For very large zones we will also force a full update based on distance -// -// We ignore a small distance around us so that we don't interrupt already pathing deltas as those npcs will appear -// to full stop when they are actually still pathing -void Client::CheckSendBulkClientPositionUpdate() +void Client::CheckSendBulkNpcPositions() { float distance_moved = DistanceNoZ(m_last_position_before_bulk_update, GetPosition()); - bool moved_far_enough_before_bulk_update = distance_moved >= zone->GetNpcPositionUpdateDistance(); + float update_range = RuleI(Range, MobCloseScanDistance); + bool moved_far_enough_before_bulk_update = distance_moved >= update_range; bool is_ready_to_update = ( - m_client_zone_wide_full_position_update_timer.Check() || moved_far_enough_before_bulk_update + m_client_bulk_npc_pos_update_timer.Check() || moved_far_enough_before_bulk_update ); - if (IsMoving() && is_ready_to_update) { - LogDebug("[[{}]] Client Zone Wide Position Update NPCs", GetCleanName()); - + int updated_count = 0; + int skipped_count = 0; + if (is_ready_to_update) { auto &mob_movement_manager = MobMovementManager::Get(); - auto &mob_list = entity_list.GetMobList(); - for (auto &it : mob_list) { - Mob *entity = it.second; - if (!entity->IsNPC()) { + for (auto &e: entity_list.GetMobList()) { + Mob *mob = e.second; + if (!mob->IsNPC()) { continue; } int animation_speed = 0; - if (entity->IsMoving()) { - if (entity->IsRunning()) { - animation_speed = (entity->IsFeared() ? entity->GetFearSpeed() : entity->GetRunspeed()); + if (mob->IsMoving()) { + if (mob->IsRunning()) { + animation_speed = (mob->IsFeared() ? mob->GetFearSpeed() : mob->GetRunspeed()); } else { - animation_speed = entity->GetWalkspeed(); + animation_speed = mob->GetWalkspeed(); } } - mob_movement_manager.SendCommandToClients(entity, 0.0, 0.0, 0.0, 0.0, animation_speed, ClientRangeAny, this); + // if we have seen this mob before, and it hasn't moved, skip it + if (m_last_seen_mob_position.contains(mob->GetID())) { + if (m_last_seen_mob_position[mob->GetID()] == mob->GetPosition()) { + LogPositionUpdateDetail( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + GetCleanName() + ); + skipped_count++; + continue; + } + } + + mob_movement_manager.SendCommandToClients( + mob, + 0.0, + 0.0, + 0.0, + 0.0, + animation_speed, + ClientRangeAny, + this + ); + + updated_count++; } + LogPositionUpdate( + "[{}] Sent [{}] bulk updated NPC positions, skipped [{}] distance_moved [{}] update_range [{}]", + GetCleanName(), + updated_count, + skipped_count, + distance_moved, + update_range + ); + m_last_position_before_bulk_update = GetPosition(); } } + const uint16 scan_npc_aggro_timer_idle = RuleI(Aggro, ClientAggroCheckIdleInterval); const uint16 scan_npc_aggro_timer_moving = RuleI(Aggro, ClientAggroCheckMovingInterval); @@ -13278,3 +13384,79 @@ void Client::SetAAEXPPercentage(uint8 percentage) SendAlternateAdvancementStats(); SendAlternateAdvancementTable(); } + +void Client::BroadcastPositionUpdate() +{ + EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct *spu = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer; + + memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct)); + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(GetX()); + spu->y_pos = FloatToEQ19(GetY()); + spu->z_pos = FloatToEQ19(GetZ()); + spu->heading = FloatToEQ12(GetHeading()); + spu->delta_x = FloatToEQ13(0); + spu->delta_y = FloatToEQ13(0); + spu->delta_z = FloatToEQ13(0); + spu->delta_heading = FloatToEQ10(0); + spu->animation = 0; + + entity_list.QueueCloseClients(this, &outapp, true, zone->GetClientUpdateRange()); + + Group *g = GetGroup(); + if (g) { + for (auto &m: g->members) { + if (m && m->IsClient() && m != this) { + m->CastToClient()->QueuePacket(&outapp); + } + } + } +} + +std::string Client::GetAccountBucket(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetData(k).value; +} + +void Client::SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + k.expires = expiration; + k.value = bucket_value; + + DataBucket::SetData(k); +} + +void Client::DeleteAccountBucket(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + DataBucket::DeleteData(k); +} + +std::string Client::GetAccountBucketExpires(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetDataExpires(k); +} + +std::string Client::GetAccountBucketRemaining(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetDataRemaining(k); +} diff --git a/zone/client.h b/zone/client.h index 1ac7f893a..7a1d13e36 100644 --- a/zone/client.h +++ b/zone/client.h @@ -320,6 +320,11 @@ public: void KeyRingAdd(uint32 item_id); bool KeyRingCheck(uint32 item_id); void KeyRingList(); + bool IsPetNameChangeAllowed(); + void GrantPetNameChange(); + void ClearPetNameChange(); + void InvokeChangePetName(bool immediate = true); + bool ChangePetName(std::string new_name); bool IsClient() const override { return true; } bool IsOfClientBot() const override { return true; } bool IsOfClientBotMerc() const override { return true; } @@ -1848,6 +1853,13 @@ public: void SendEvolveXPTransferWindow(); void SendEvolveTransferResults(const EQ::ItemInstance &inst_from, const EQ::ItemInstance &inst_to, const EQ::ItemInstance &inst_from_new, const EQ::ItemInstance &inst_to_new, const uint32 compatibility, const uint32 max_transfer_level); + // Account buckets + std::string GetAccountBucket(std::string bucket_name); + void SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration = ""); + void DeleteAccountBucket(std::string bucket_name); + std::string GetAccountBucketExpires(std::string bucket_name); + std::string GetAccountBucketRemaining(std::string bucket_name); + protected: friend class Mob; void CalcEdibleBonuses(StatBonuses* newbon); @@ -2107,12 +2119,13 @@ private: Timer m_client_npc_aggro_scan_timer; void CheckClientToNpcAggroTimer(); void ClientToNpcAggroProcess(); + void BroadcastPositionUpdate(); // bulk position updates glm::vec4 m_last_position_before_bulk_update; - Timer m_client_zone_wide_full_position_update_timer; + Timer m_client_bulk_npc_pos_update_timer; Timer m_position_update_timer; - void CheckSendBulkClientPositionUpdate(); + void CheckSendBulkNpcPositions(); void BulkSendInventoryItems(); @@ -2300,6 +2313,7 @@ public: const std::string &GetMailKeyFull() const; const std::string &GetMailKey() const; void ShowZoneShardMenu(); + void Handle_OP_ChangePetName(const EQApplicationPacket *app); }; #endif diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 6cfb9b174..f51bac48b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -66,6 +66,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/guild_tributes_repository.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" #include "../common/repositories/character_stats_record_repository.h" @@ -160,6 +161,7 @@ void MapOpcodes() ConnectedOpcodes[OP_CancelTrade] = &Client::Handle_OP_CancelTrade; ConnectedOpcodes[OP_CastSpell] = &Client::Handle_OP_CastSpell; ConnectedOpcodes[OP_ChannelMessage] = &Client::Handle_OP_ChannelMessage; + ConnectedOpcodes[OP_ChangePetName] = &Client::Handle_OP_ChangePetName; ConnectedOpcodes[OP_ClearBlockedBuffs] = &Client::Handle_OP_ClearBlockedBuffs; ConnectedOpcodes[OP_ClearNPCMarks] = &Client::Handle_OP_ClearNPCMarks; ConnectedOpcodes[OP_ClearSurname] = &Client::Handle_OP_ClearSurname; @@ -824,6 +826,10 @@ void Client::CompleteConnect() CharacterID() ) ); + + if (IsPetNameChangeAllowed()) { + InvokeChangePetName(false); + } } if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { @@ -975,6 +981,16 @@ void Client::CompleteConnect() RecordStats(); AutoGrantAAPoints(); + // set initial position for mob tracking + m_last_seen_mob_position.reserve(entity_list.GetMobList().size()); + for (auto& mob : entity_list.GetMobList()) { + if (!mob.second->IsNPC()) { + continue; + } + + m_last_seen_mob_position[mob.second->GetID()] = mob.second->GetPosition(); + } + // enforce some rules.. if (!CanEnterZone()) { LogInfo("Kicking character [{}] from zone, not allowed here (missing requirements)", GetCleanName()); @@ -4573,6 +4589,27 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) return; } +void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) { + if (app->size != sizeof(ChangePetName_Struct)) { + LogError("Got OP_ChangePetName of incorrect size. Expected [{}], got [{}].", sizeof(ChangePetName_Struct), app->size); + return; + } + + auto p = (ChangePetName_Struct *) app->pBuffer; + if (!IsPetNameChangeAllowed()) { + p->response_code = ChangePetNameResponse::NotEligible; + QueuePacket(app); + return; + } + + p->response_code = ChangePetNameResponse::Denied; + if (ChangePetName(p->new_pet_name)) { + p->response_code = ChangePetNameResponse::Accepted; + } + + QueuePacket(app); +} + void Client::Handle_OP_ClearBlockedBuffs(const EQApplicationPacket *app) { if (!RuleB(Spells, EnableBlockedBuffs)) @@ -4971,7 +5008,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { CheckScanCloseMobsMovingTimer(); } - CheckSendBulkClientPositionUpdate(); + CheckSendBulkNpcPositions(); int32 new_animation = ppu->animation; diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 6326efeec..ea3e82252 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -122,7 +122,7 @@ bool Client::Process() { /* I haven't naturally updated my position in 10 seconds, updating manually */ if (!IsMoving() && m_position_update_timer.Check()) { - SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); + BroadcastPositionUpdate(); } if (mana_timer.Check()) diff --git a/zone/data_bucket.cpp b/zone/data_bucket.cpp index db2222348..23d4eb9d6 100644 --- a/zone/data_bucket.cpp +++ b/zone/data_bucket.cpp @@ -16,6 +16,7 @@ void DataBucket::SetData(const std::string &bucket_key, const std::string &bucke .key = bucket_key, .value = bucket_value, .expires = expires_time, + .account_id = 0, .character_id = 0, .npc_id = 0, .bot_id = 0 @@ -37,6 +38,9 @@ void DataBucket::SetData(const DataBucketKey &k) if (k.character_id > 0) { b.character_id = k.character_id; } + else if (k.account_id > 0) { + b.account_id = k.account_id; + } else if (k.npc_id > 0) { b.npc_id = k.npc_id; } @@ -95,9 +99,10 @@ std::string DataBucket::GetData(const std::string &bucket_key) DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, bool ignore_misses_cache) { LogDataBuckets( - "Getting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}]", + "Getting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}]", k.key, k.bot_id, + k.account_id, k.character_id, k.npc_id ); @@ -151,6 +156,7 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b .key_ = k.key, .value = "", .expires = 0, + .account_id = k.account_id, .character_id = k.character_id, .npc_id = k.npc_id, .bot_id = k.bot_id @@ -158,8 +164,9 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b ); LogDataBuckets( - "Key [{}] not found in database, adding to cache as a miss character_id [{}] npc_id [{}] bot_id [{}] cache size before [{}] after [{}]", + "Key [{}] not found in database, adding to cache as a miss account_id [{}] character_id [{}] npc_id [{}] bot_id [{}] cache size before [{}] after [{}]", k.key, + k.account_id, k.character_id, k.npc_id, k.bot_id, @@ -216,8 +223,6 @@ bool DataBucket::DeleteData(const std::string &bucket_key) // GetDataBuckets bulk loads all data buckets for a mob bool DataBucket::GetDataBuckets(Mob *mob) { - DataBucketLoadType::Type t{}; - const uint32 id = mob->GetMobTypeIdentifier(); if (!id) { @@ -225,14 +230,13 @@ bool DataBucket::GetDataBuckets(Mob *mob) } if (mob->IsBot()) { - t = DataBucketLoadType::Bot; + BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id}); } else if (mob->IsClient()) { - t = DataBucketLoadType::Client; + BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id}); + BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id}); } - BulkLoadEntitiesToCache(t, {id}); - return true; } @@ -254,9 +258,10 @@ bool DataBucket::DeleteData(const DataBucketKey &k) ); LogDataBuckets( - "Deleting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}] cache size before [{}] after [{}]", + "Deleting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}] cache size before [{}] after [{}]", k.key, k.bot_id, + k.account_id, k.character_id, k.npc_id, size_before, @@ -277,9 +282,10 @@ bool DataBucket::DeleteData(const DataBucketKey &k) std::string DataBucket::GetDataExpires(const DataBucketKey &k) { LogDataBuckets( - "Getting bucket expiration key [{}] bot_id [{}] character_id [{}] npc_id [{}]", + "Getting bucket expiration key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}]", k.key, k.bot_id, + k.account_id, k.character_id, k.npc_id ); @@ -295,9 +301,10 @@ std::string DataBucket::GetDataExpires(const DataBucketKey &k) std::string DataBucket::GetDataRemaining(const DataBucketKey &k) { LogDataBuckets( - "Getting bucket remaining key [{}] bot_id [{}] character_id [{}] npc_id [{}]", + "Getting bucket remaining key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}]", k.key, k.bot_id, + k.account_id, k.character_id, k.npc_id ); @@ -320,6 +327,13 @@ std::string DataBucket::GetScopedDbFilters(const DataBucketKey &k) query.emplace_back("character_id = 0"); } + if (k.account_id > 0) { + query.emplace_back(fmt::format("account_id = {}", k.account_id)); + } + else { + query.emplace_back("account_id = 0"); + } + if (k.npc_id > 0) { query.emplace_back(fmt::format("npc_id = {}", k.npc_id)); } @@ -346,6 +360,7 @@ bool DataBucket::CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, return ( dbe.key_ == k.key && dbe.bot_id == k.bot_id && + dbe.account_id == k.account_id && dbe.character_id == k.character_id && dbe.npc_id == k.npc_id ); @@ -364,6 +379,9 @@ void DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Type t, std::vector if (t == DataBucketLoadType::Bot) { has_cache = e.bot_id == ids[0]; } + else if (t == DataBucketLoadType::Account) { + has_cache = e.account_id == ids[0]; + } else if (t == DataBucketLoadType::Client) { has_cache = e.character_id == ids[0]; } @@ -384,6 +402,9 @@ void DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Type t, std::vector case DataBucketLoadType::Client: column = "character_id"; break; + case DataBucketLoadType::Account: + column = "account_id"; + break; default: LogError("Incorrect LoadType [{}]", static_cast(t)); break; @@ -442,6 +463,7 @@ void DataBucket::DeleteCachedBuckets(DataBucketLoadType::Type type, uint32 id) [&](DataBucketsRepository::DataBuckets &e) { return ( (type == DataBucketLoadType::Bot && e.bot_id == id) || + (type == DataBucketLoadType::Account && e.account_id == id) || (type == DataBucketLoadType::Client && e.character_id == id) ); } @@ -481,6 +503,7 @@ void DataBucket::DeleteFromMissesCache(DataBucketsRepository::DataBuckets e) g_data_bucket_cache.end(), [&](DataBucketsRepository::DataBuckets &ce) { return ce.id == 0 && ce.key_ == e.key_ && + ce.account_id == e.account_id && ce.character_id == e.character_id && ce.npc_id == e.npc_id && ce.bot_id == e.bot_id; @@ -516,6 +539,8 @@ void DataBucket::DeleteFromCache(uint64 id, DataBucketLoadType::Type type) return e.bot_id == id; case DataBucketLoadType::Client: return e.character_id == id; + case DataBucketLoadType::Account: + return e.account_id == id; default: return false; } @@ -539,7 +564,7 @@ void DataBucket::DeleteFromCache(uint64 id, DataBucketLoadType::Type type) // npcs (ids) can be in multiple zones so we can't cache locally to the zone bool DataBucket::CanCache(const DataBucketKey &key) { - if (key.character_id > 0 || key.bot_id > 0) { + if (key.character_id > 0 || key.account_id > 0 || key.bot_id > 0) { return true; } diff --git a/zone/data_bucket.h b/zone/data_bucket.h index 8503330f2..1bd216630 100644 --- a/zone/data_bucket.h +++ b/zone/data_bucket.h @@ -12,20 +12,23 @@ struct DataBucketKey { std::string key; std::string value; std::string expires; - int64_t character_id; - int64_t npc_id; - int64_t bot_id; + int64_t account_id = 0; + int64_t character_id = 0; + int64_t npc_id = 0; + int64_t bot_id = 0; }; namespace DataBucketLoadType { enum Type : uint8 { Bot, + Account, Client, MaxType }; static const std::string Name[Type::MaxType] = { "Bot", + "Account", "Client", }; } diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index c451db90b..af2d2eaf6 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -5653,16 +5653,6 @@ int Perl__GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version) return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version); } -uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id); -} - -uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version); -} - int8 Perl__GetZoneMinimumExpansion(uint32 zone_id) { return zone_store.GetZoneMinimumExpansion(zone_id); @@ -6112,8 +6102,6 @@ void perl_register_quest() package.add("GetZoneMaximumExpansion", (int8(*)(uint32, int))&Perl__GetZoneMaximumExpansion); package.add("GetZoneMaximumLevel", (uint8(*)(uint32))&Perl__GetZoneMaximumLevel); package.add("GetZoneMaximumLevel", (uint8(*)(uint32, int))&Perl__GetZoneMaximumLevel); - package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32))&Perl__GetZoneMaximumMovementUpdateRange); - package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32, int))&Perl__GetZoneMaximumMovementUpdateRange); package.add("GetZoneMaximumPlayers", (int(*)(uint32))&Perl__GetZoneMaximumPlayers); package.add("GetZoneMaximumPlayers", (int(*)(uint32, int))&Perl__GetZoneMaximumPlayers); package.add("GetZoneMinimumClip", (float(*)(uint32))&Perl__GetZoneMinimumClip); diff --git a/zone/entity.cpp b/zone/entity.cpp index a7ba7f396..ced5f6130 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1717,15 +1717,6 @@ void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *a } } -/** - * @param sender - * @param app - * @param ignore_sender - * @param distance - * @param skipped_mob - * @param is_ack_required - * @param filter - */ void EntityList::QueueCloseClients( Mob *sender, const EQApplicationPacket *app, @@ -1742,7 +1733,7 @@ void EntityList::QueueCloseClients( } if (distance <= 0) { - distance = 600; + distance = zone->GetClientUpdateRange(); } float distance_squared = distance * distance; @@ -2871,6 +2862,8 @@ bool EntityList::RemoveMobFromCloseLists(Mob *mob) ); it->second->m_close_mobs.erase(entity_id); + it->second->m_last_seen_mob_position.erase(entity_id); + ++it; } @@ -2924,6 +2917,9 @@ void EntityList::RemoveAuraFromMobs(Mob *aura) // All of the above makes a tremendous impact on the bottom line of cpu cycle performance because we run an order of magnitude // less checks by focusing our hot path logic down to a very small subset of relevant entities instead of looping an entire // entity list (zone wide) + +BenchTimer g_scan_bench_timer; + void EntityList::ScanCloseMobs(Mob *scanning_mob) { if (!scanning_mob) { @@ -2934,7 +2930,9 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) return; } - float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); + g_scan_bench_timer.reset(); + + float scan_range = RuleI(Range, MobCloseScanDistance); // Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved. // Assuming mob_list.size() as an upper bound for reservation. @@ -2951,7 +2949,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) continue; } - float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); + float distance = Distance(scanning_mob->GetPosition(), mob->GetPosition()); if (distance <= scan_range || mob->GetAggroRange() >= scan_range) { // add mob to scanning_mob's close list and vice versa // check if the mob is already in the close mobs list before inserting @@ -2963,10 +2961,11 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) } LogAIScanClose( - "[{}] Scanning close list > list_size [{}] moving [{}]", + "[{}] Scanning close list > list_size [{}] moving [{}] elapsed [{}] us", scanning_mob->GetCleanName(), scanning_mob->m_close_mobs.size(), - scanning_mob->IsMoving() ? "true" : "false" + scanning_mob->IsMoving() ? "true" : "false", + g_scan_bench_timer.elapsedMicroseconds() ); } @@ -5752,10 +5751,6 @@ void EntityList::ReloadMerchants() { * then we return the full list * * See comments @EntityList::ScanCloseMobs for system explanation - * - * @param mob - * @param distance - * @return */ std::unordered_map &EntityList::GetCloseMobList(Mob *mob, float distance) { diff --git a/zone/entity.h b/zone/entity.h index d1798c6bc..d2bf8eb63 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -630,8 +630,6 @@ private: void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff - void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); - void GetBotList(std::list &b_list); private: std::unordered_map bot_list; diff --git a/zone/gm_commands/loc.cpp b/zone/gm_commands/loc.cpp index 62fdf4be4..4e4085add 100755 --- a/zone/gm_commands/loc.cpp +++ b/zone/gm_commands/loc.cpp @@ -9,6 +9,19 @@ void command_loc(Client *c, const Seperator *sep) auto target_position = target->GetPosition(); + // check los benchmark + BenchTimer timer; + for (int i = 0; i < 1000; i++) { + zone->zonemap->CheckLoS(c->GetPosition(), target_position); + } + c->Message( + Chat::White, + fmt::format( + "CheckLoS benchmark took [{}]", + timer.elapsed() + ).c_str() + ); + c->Message( Chat::White, fmt::format( diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 566510cf9..83e2a3fb3 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3458,12 +3458,54 @@ void Lua_Client::ShowZoneShardMenu() self->ShowZoneShardMenu(); } +void Lua_Client::GrantPetNameChange() +{ + Lua_Safe_Call_Void(); + self->GrantPetNameChange(); +} + void Lua_Client::SetAAEXPPercentage(uint8 percentage) { Lua_Safe_Call_Void(); self->SetAAEXPPercentage(percentage); } +void Lua_Client::SetAccountBucket(std::string bucket_name, std::string bucket_value) +{ + Lua_Safe_Call_Void(); + self->SetAccountBucket(bucket_name, bucket_value); +} + +void Lua_Client::SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration) +{ + Lua_Safe_Call_Void(); + self->SetAccountBucket(bucket_name, bucket_value, expiration); +} + +void Lua_Client::DeleteAccountBucket(std::string bucket_name) +{ + Lua_Safe_Call_Void(); + self->DeleteAccountBucket(bucket_name); +} + +std::string Lua_Client::GetAccountBucket(std::string bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetAccountBucket(bucket_name); +} + +std::string Lua_Client::GetAccountBucketExpires(std::string bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetAccountBucketExpires(bucket_name); +} + +std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetAccountBucketRemaining(bucket_name); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3532,6 +3574,7 @@ luabind::scope lua_register_client() { .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) + .def("GrantPetNameChange", &Lua_Client::GrantPetNameChange) .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) @@ -3552,6 +3595,7 @@ luabind::scope lua_register_client() { .def("CreateTaskDynamicZone", &Lua_Client::CreateTaskDynamicZone) .def("DecreaseByID", (bool(Lua_Client::*)(uint32,int))&Lua_Client::DecreaseByID) .def("DescribeSpecialAbilities", (void(Lua_Client::*)(Lua_NPC))&Lua_Client::DescribeSpecialAbilities) + .def("DeleteAccountBucket", (void(Lua_Client::*)(std::string))&Lua_Client::DeleteAccountBucket) .def("DeleteBucket", (void(Lua_Client::*)(std::string))&Lua_Client::DeleteBucket) .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int))&Lua_Client::DeleteItemInInventory) .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int,bool))&Lua_Client::DeleteItemInInventory) @@ -3630,6 +3674,9 @@ luabind::scope lua_register_client() { .def("GetBotRequiredLevel", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotRequiredLevel) .def("GetBotSpawnLimit", (int(Lua_Client::*)(void))&Lua_Client::GetBotSpawnLimit) .def("GetBotSpawnLimit", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotSpawnLimit) + .def("GetAccountBucket", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucket) + .def("GetAccountBucketExpires", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucketExpires) + .def("GetAccountBucketRemaining", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucketRemaining) .def("GetBucket", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucket) .def("GetBucketExpires", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketExpires) .def("GetBucketRemaining", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketRemaining) @@ -3922,6 +3969,8 @@ luabind::scope lua_register_client() { .def("SetBotRequiredLevel", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotRequiredLevel) .def("SetBotSpawnLimit", (void(Lua_Client::*)(int))&Lua_Client::SetBotSpawnLimit) .def("SetBotSpawnLimit", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotSpawnLimit) + .def("SetAccountBucket", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetAccountBucket) + .def("SetAccountBucket", (void(Lua_Client::*)(std::string,std::string,std::string))&Lua_Client::SetAccountBucket) .def("SetBucket", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetBucket) .def("SetBucket", (void(Lua_Client::*)(std::string,std::string,std::string))&Lua_Client::SetBucket) .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) diff --git a/zone/lua_client.h b/zone/lua_client.h index d86705f2e..ed02e6592 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -512,6 +512,14 @@ public: luabind::object GetInventorySlots(lua_State* L); void SetAAEXPPercentage(uint8 percentage); + // account data buckets + void SetAccountBucket(std::string bucket_name, std::string bucket_value); + void SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration = ""); + void DeleteAccountBucket(std::string bucket_name); + std::string GetAccountBucket(std::string bucket_name); + std::string GetAccountBucketExpires(std::string bucket_name); + std::string GetAccountBucketRemaining(std::string bucket_name); + void ApplySpell(int spell_id); void ApplySpell(int spell_id, int duration); void ApplySpell(int spell_id, int duration, int level); @@ -589,6 +597,7 @@ public: bool ReloadDataBuckets(); void ShowZoneShardMenu(); + void GrantPetNameChange(); Lua_Expedition CreateExpedition(luabind::object expedition_info); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 6567d2476..bc1b04416 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -4668,16 +4668,6 @@ int lua_get_zone_npc_maximum_aggro_distance(uint32 zone_id, int version) return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version); } -uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id); -} - -uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id, int version) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version); -} - int8 lua_get_zone_minimum_expansion(uint32 zone_id) { return zone_store.GetZoneMinimumExpansion(zone_id); @@ -6287,8 +6277,6 @@ luabind::scope lua_register_general() { luabind::def("get_zone_fast_regen_endurance", (int(*)(uint32,int))&lua_get_zone_fast_regen_endurance), luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32))&lua_get_zone_npc_maximum_aggro_distance), luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32,int))&lua_get_zone_npc_maximum_aggro_distance), - luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32))&lua_get_zone_maximum_movement_update_range), - luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32,int))&lua_get_zone_maximum_movement_update_range), luabind::def("get_zone_minimum_expansion", (int8(*)(uint32))&lua_get_zone_minimum_expansion), luabind::def("get_zone_minimum_expansion", (int8(*)(uint32,int))&lua_get_zone_minimum_expansion), luabind::def("get_zone_maximum_expansion", (int8(*)(uint32))&lua_get_zone_maximum_expansion), diff --git a/zone/map.cpp b/zone/map.cpp index 96feddb9c..7e3d8822c 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -7,6 +7,7 @@ #include "raycast_mesh.h" #include "zone.h" #include "../common/file.h" +#include "../common/memory/ksm.hpp" #include #include @@ -953,6 +954,7 @@ bool Map::LoadV2(FILE *f) { return true; } + void Map::RotateVertex(glm::vec3 &v, float rx, float ry, float rz) { glm::vec3 nv = v; diff --git a/zone/mob.cpp b/zone/mob.cpp index e461fe6c7..977859bd9 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -129,6 +129,7 @@ Mob::Mob( position_update_melee_push_timer(500), hate_list_cleanup_timer(6000), m_scan_close_mobs_timer(6000), + m_see_close_mobs_timer(1000), m_mob_check_moving_timer(1000), bot_attack_flag_timer(10000) { @@ -8621,15 +8622,15 @@ std::unordered_map& Mob::GetCloseMobList(float distance) void Mob::ClearDataBucketCache() { if (IsOfClientBot()) { - uint64 id = 0; + uint64 id = 0; DataBucketLoadType::Type t{}; if (IsBot()) { id = CastToBot()->GetBotID(); - t = DataBucketLoadType::Bot; + t = DataBucketLoadType::Bot; } else if (IsClient()) { id = CastToClient()->CharacterID(); - t = DataBucketLoadType::Client; + t = DataBucketLoadType::Client; } DataBucket::DeleteFromCache(id, t); diff --git a/zone/mob.h b/zone/mob.h index b9b72d63e..e8bac4129 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -223,9 +223,11 @@ public: void DisplayInfo(Mob *mob); - std::unordered_map m_close_mobs; - Timer m_scan_close_mobs_timer; - Timer m_mob_check_moving_timer; + std::unordered_map m_close_mobs; + std::unordered_map m_last_seen_mob_position; + Timer m_scan_close_mobs_timer; + Timer m_see_close_mobs_timer; + Timer m_mob_check_moving_timer; // Bot attack flag Timer bot_attack_flag_timer; diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 3bb5996a2..661c046a1 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -851,12 +851,24 @@ void MobMovementManager::SendCommandToClients( _impl->Stats.TotalSentPosition++; } + if (c->m_last_seen_mob_position.contains(mob->GetID())) { + if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) { + LogPositionUpdate( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + c->GetCleanName() + ); + continue; + } + } + c->QueuePacket(&outapp, false); + c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition(); } } else { float short_range = RuleR(Pathing, ShortMovementUpdateRange); - float long_range = zone->GetNpcPositionUpdateDistance(); + float long_range = RuleI(Range, MobCloseScanDistance); for (auto &c : _impl->Clients) { if (single_client && c != single_client) { @@ -901,7 +913,19 @@ void MobMovementManager::SendCommandToClients( _impl->Stats.TotalSentPosition++; } + if (c->m_last_seen_mob_position.contains(mob->GetID())) { + if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) { + LogPositionUpdate( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + c->GetCleanName() + ); + continue; + } + } + c->QueuePacket(&outapp, false); + c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition(); } } } diff --git a/zone/npc.cpp b/zone/npc.cpp index 38f597e63..ba5782c34 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -997,8 +997,8 @@ NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 npc_type->current_hp = 4000000; npc_type->max_hp = 4000000; - npc_type->race = 2254; - npc_type->gender = 2; + npc_type->race = 127; + npc_type->gender = 0; npc_type->class_ = 9; npc_type->deity = 1; npc_type->level = 200; diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index bf0c4830d..98bbc10c6 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3229,11 +3229,46 @@ perl::array Perl_Client_GetInventorySlots(Client* self) return result; } +void Perl_Client_GrantPetNameChange(Client* self) +{ + self->GrantPetNameChange(); +} + void Perl_Client_SetAAEXPPercentage(Client* self, uint8 percentage) { self->SetAAEXPPercentage(percentage); } +void Perl_Client_SetAccountBucket(Client* self, std::string bucket_name, std::string bucket_value) +{ + self->SetAccountBucket(bucket_name, bucket_value); +} + +void Perl_Client_SetAccountBucket(Client* self, std::string bucket_name, std::string bucket_value, std::string expiration = "") +{ + self->SetAccountBucket(bucket_name, bucket_value, expiration); +} + +void Perl_Client_DeleteAccountBucket(Client* self, std::string bucket_name) +{ + self->DeleteAccountBucket(bucket_name); +} + +std::string Perl_Client_GetAccountBucket(Client* self, std::string bucket_name) +{ + return self->GetAccountBucket(bucket_name); +} + +std::string Perl_Client_GetAccountBucketExpires(Client* self, std::string bucket_name) +{ + return self->GetAccountBucketExpires(bucket_name); +} + +std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string bucket_name) +{ + return self->GetAccountBucketRemaining(bucket_name); +} + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3305,6 +3340,7 @@ void perl_register_client() package.add("CanHaveSkill", &Perl_Client_CanHaveSkill); package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); + package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange); package.add("CharacterID", &Perl_Client_CharacterID); package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill); @@ -3325,6 +3361,7 @@ void perl_register_client() package.add("CreateTaskDynamicZone", &Perl_Client_CreateTaskDynamicZone); package.add("DecreaseByID", &Perl_Client_DecreaseByID); package.add("DescribeSpecialAbilities", &Perl_Client_DescribeSpecialAbilities); + package.add("DeleteAccountBucket", &Perl_Client_DeleteAccountBucket); package.add("DeleteItemInInventory", (void(*)(Client*, int16))&Perl_Client_DeleteItemInInventory); package.add("DeleteItemInInventory", (void(*)(Client*, int16, int16))&Perl_Client_DeleteItemInInventory); package.add("DeleteItemInInventory", (void(*)(Client*, int16, int16, bool))&Perl_Client_DeleteItemInInventory); @@ -3362,6 +3399,9 @@ void perl_register_client() package.add("GetAAPoints", &Perl_Client_GetAAPoints); package.add("GetAFK", &Perl_Client_GetAFK); package.add("GetAccountAge", &Perl_Client_GetAccountAge); + package.add("GetAccountBucket", &Perl_Client_GetAccountBucket); + package.add("GetAccountBucketExpires", &Perl_Client_GetAccountBucketExpires); + package.add("GetGetAccountBucketRemaining", &Perl_Client_GetAccountBucketRemaining); package.add("GetAccountFlag", &Perl_Client_GetAccountFlag); package.add("GetAccountFlags", &Perl_Client_GetAccountFlags); package.add("GetAggroCount", &Perl_Client_GetAggroCount); @@ -3668,6 +3708,8 @@ void perl_register_client() package.add("SetAATitle", (void(*)(Client*, std::string, bool))&Perl_Client_SetAATitle); package.add("SetAFK", &Perl_Client_SetAFK); package.add("SetAccountFlag", &Perl_Client_SetAccountFlag); + package.add("SetAccountBucket", (void(*)(Client*, std::string, std::string))&Perl_Client_SetAccountBucket); + package.add("SetAccountBucket", (void(*)(Client*, std::string, std::string, std::string))&Perl_Client_SetAccountBucket); package.add("SetAlternateCurrencyValue", &Perl_Client_SetAlternateCurrencyValue); package.add("SetAnon", &Perl_Client_SetAnon); package.add("SetAutoLoginCharacterName", (bool(*)(Client*))&Perl_Client_SetAutoLoginCharacterName); diff --git a/zone/pets.cpp b/zone/pets.cpp index 08fc523c1..da84ca4e1 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -22,6 +22,7 @@ #include "../common/repositories/pets_repository.h" #include "../common/repositories/pets_beastlord_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "entity.h" #include "client.h" @@ -164,6 +165,12 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, // 4 - Keep DB name // 5 - `s ward + if (IsClient() && !petname) { + const auto vanity_name = CharacterPetNameRepository::FindOne(database, CastToClient()->CharacterID()); + if (!vanity_name.name.empty()) { + petname = vanity_name.name.c_str(); + } + } if (petname != nullptr) { // Name was provided, use it. diff --git a/zone/raycast_mesh.cpp b/zone/raycast_mesh.cpp index 8873c293e..20c3f6b71 100644 --- a/zone/raycast_mesh.cpp +++ b/zone/raycast_mesh.cpp @@ -1,4 +1,6 @@ #include "raycast_mesh.h" +#include "../common/memory/ksm.hpp" +#include "../common/eqemu_logsys.h" #include #include #include @@ -9,7 +11,7 @@ // This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do // high-speed raycasting. // -// There are much better implementations of this available on the internet. In particular I recommend that you use +// There are much better implementations of this available on the internet. In particular I recommend that you use // OPCODE written by Pierre Terdiman. // @see: http://www.codercorner.com/Opcode.htm // @@ -17,7 +19,7 @@ // // I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. // I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, -// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders // of magnitude; so this implementation should work fine for simple tools and utilities. // // It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. @@ -32,14 +34,14 @@ // // The official source can be found at: http://code.google.com/p/raycastmesh/ // -// +// #pragma warning(disable:4100) namespace RAYCAST_MESH { -typedef std::vector< RmUint32 > TriVector; +typedef std::vector> TriVector; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** @@ -365,7 +367,7 @@ public: { RmUint32 ret = 0; - if ( p[0] < mMin[0] ) + if ( p[0] < mMin[0] ) { ret|=CC_MINX; } @@ -374,7 +376,7 @@ public: ret|=CC_MAXX; } - if ( p[1] < mMin[1] ) + if ( p[1] < mMin[1] ) { ret|=CC_MINY; } @@ -383,7 +385,7 @@ public: ret|=CC_MAXY; } - if ( p[2] < mMin[2] ) + if ( p[2] < mMin[2] ) { ret|=CC_MINZ; } @@ -514,7 +516,7 @@ public: // the width of the longest axis is less than the minimum axis size then... // we create the leaf node and copy the triangles into the leaf node triangle array. if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize ) - { + { // Copy the triangle indices into the leaf triangles array mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles. leafTriangles.push_back(count); @@ -542,7 +544,7 @@ public: // and another array that includes all triangles which intersect the 'right' half of the bounding volume node. for (auto i = triangles.begin(); i != triangles.end(); ++i) { - RmUint32 tri = (*i); + RmUint32 tri = (*i); { RmUint32 i1 = indices[tri*3+0]; @@ -590,7 +592,7 @@ public: { leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume. mLeft = callback->getNode(); // get a new AABB node - new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. + new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. // Then recursively split this node. mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); } @@ -662,7 +664,7 @@ public: RmReal nd = nearestDistance; if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) ) { - return; + return; } if ( mLeafTriangleIndex != TRI_EOF ) { @@ -754,28 +756,60 @@ public: { mMaxNodeCount+=pow2Table[i]; } - mNodes = new NodeAABB[mMaxNodeCount]; + // Allocate page-aligned memory + mNodes = static_cast(KSM::AllocatePageAligned(sizeof(NodeAABB) * mMaxNodeCount)); + if (!mNodes) { + throw std::bad_alloc(); + } mNodeCount = 0; + KSM::CheckPageAlignment(mNodes); + + mVertices = static_cast(KSM::AllocatePageAligned(sizeof(RmReal) * 3 * vcount)); + if (!mVertices) { + throw std::bad_alloc(); + } + std::memcpy(mVertices, vertices, sizeof(RmReal) * 3 * vcount); mVcount = vcount; - mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount); - memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount); + + mIndices = static_cast(KSM::AllocatePageAligned(sizeof(RmUint32) * 3 * tcount)); + if (!mIndices) { + throw std::bad_alloc(); + } + std::memcpy(mIndices, indices, sizeof(RmUint32) * 3 * tcount); mTcount = tcount; - mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3); - memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3); - mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32)); - memset(mRaycastTriangles,0,tcount*sizeof(RmUint32)); + + mRaycastTriangles = static_cast(KSM::AllocatePageAligned(sizeof(RmUint32) * tcount)); + if (!mRaycastTriangles) { + throw std::bad_alloc(); + } + std::memset(mRaycastTriangles, 0, sizeof(RmUint32) * tcount); + + mFaceNormals = static_cast(KSM::AllocatePageAligned(sizeof(RmReal) * 3 * tcount)); + if (!mFaceNormals) { + throw std::bad_alloc(); + } + std::memset(mFaceNormals, 0, sizeof(RmReal) * 3 * tcount); + + // Mark memory as mergeable for KSM + KSM::MarkMemoryForKSM(mVertices, sizeof(RmReal) * 3 * vcount); + KSM::MarkMemoryForKSM(mIndices, sizeof(RmUint32) * 3 * tcount); + KSM::MarkMemoryForKSM(mRaycastTriangles, sizeof(RmUint32) * tcount); + KSM::MarkMemoryForKSM(mFaceNormals, sizeof(RmReal) * 3 * tcount); + mRoot = getNode(); mFaceNormals = NULL; new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles); + + KSM::MarkMemoryForKSM(mLeafTriangles.data(), mLeafTriangles.size() * sizeof(RmUint32)); } ~MyRaycastMesh(void) { - delete []mNodes; - ::free(mVertices); - ::free(mIndices); - ::free(mFaceNormals); - ::free(mRaycastTriangles); + if (mNodes) { free(mNodes); } + if (mVertices) { free(mVertices); } + if (mIndices) { free(mIndices); } + if (mRaycastTriangles) { free(mRaycastTriangles); } + if (mFaceNormals) { free(mFaceNormals); } } virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) @@ -812,7 +846,7 @@ public: return mRoot->mBounds.mMax; } - virtual NodeAABB * getNode(void) + virtual NodeAABB * getNode(void) { assert( mNodeCount < mMaxNodeCount ); NodeAABB *ret = &mNodes[mNodeCount]; @@ -820,7 +854,7 @@ public: return ret; } - virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) { if ( mFaceNormals == NULL ) { @@ -938,6 +972,29 @@ RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in t ) { auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, maxDepth, minLeafSize, minAxisSize); + + // Calculate memory usage + size_t vertex_size = vcount * sizeof(RmReal) * 3; // Each vertex has 3 floats + size_t index_size = tcount * 3 * sizeof(RmUint32); // Each triangle has 3 indices + size_t bvh_node_size = m->mNodeCount * sizeof(NodeAABB); // BVH Node memory usage + size_t bvh_leaf_size = m->mLeafTriangles.size() * sizeof(RmUint32); // BVH leaf triangles + + size_t bvh_size = bvh_node_size + bvh_leaf_size; // Total BVH size + size_t total_size = vertex_size + index_size + bvh_size; + + KSM::CheckPageAlignment(m->mNodes); + KSM::CheckPageAlignment(m->mVertices); + + LogInfo( + "Map Raycast Memory Usage | Vertices [{:.2f}] MB Indices [{:.2f}] MB BVH Nodes [{:.2f}] MB BVH Leaves [{:.2f}] MB BVH Total [{:.2f}] MB", + vertex_size / (1024.0 * 1024.0), + index_size / (1024.0 * 1024.0), + bvh_node_size / (1024.0 * 1024.0), + bvh_leaf_size / (1024.0 * 1024.0), + bvh_size / (1024.0 * 1024.0) + ); + LogInfo("Total Raycast Memory [{:.2f}] MB", total_size / (1024.0 * 1024.0)); + return static_cast< RaycastMesh * >(m); } @@ -984,12 +1041,12 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) return; char* buf = rm_buffer.data(); - + chunk_size = sizeof(RmUint32); memcpy(&mVcount, buf, chunk_size); buf += chunk_size; bytes_read += chunk_size; - + chunk_size = (sizeof(RmReal) * (3 * mVcount)); mVertices = (RmReal *)::malloc(chunk_size); memcpy(mVertices, buf, chunk_size); @@ -1037,7 +1094,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) buf += chunk_size; bytes_read += chunk_size; } - + chunk_size = sizeof(RmUint32); memcpy(&mNodeCount, buf, chunk_size); buf += chunk_size; @@ -1071,7 +1128,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) mNodes[index].mLeft = &mNodes[lNodeIndex]; buf += chunk_size; bytes_read += chunk_size; - + RmUint32 rNodeIndex; chunk_size = sizeof(RmUint32); memcpy(&rNodeIndex, buf, chunk_size); @@ -1106,7 +1163,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) void MyRaycastMesh::serialize(std::vector& rm_buffer) { rm_buffer.clear(); - + size_t rm_buffer_size_ = 0; rm_buffer_size_ += sizeof(RmUint32); // mVcount diff --git a/zone/spells.cpp b/zone/spells.cpp index 919706d42..6a9d0a367 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -7513,15 +7513,17 @@ void Mob::SetHP(int64 hp) void Mob::DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec) { - NPC *node = nullptr; + NPC *node = nullptr; + for (const auto &n: entity_list.GetNPCList()) { - if (n.second->GetCleanName() == node_name) { + if (n.second->GetEntityVariable("node_parent_id") == std::to_string(GetID())) { node = n.second; break; } } if (!node) { node = NPC::SpawnNodeNPC(node_name, "", GetPosition()); + node->SetEntityVariable("node_parent_id", std::to_string(GetID())); } } diff --git a/zone/zone.cpp b/zone/zone.cpp index f02be2e2b..5488ed914 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1091,7 +1091,6 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) mMovementManager = &MobMovementManager::Get(); - SetNpcPositionUpdateDistance(0); SetQuestHotReloadQueued(false); } @@ -1374,17 +1373,17 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_version) newzone_data.suspend_buffs = z->suspendbuffs; // local attributes - can_bind = z->canbind != 0; - is_city = z->canbind == 2; - can_combat = z->cancombat != 0; - can_levitate = z->canlevitate != 0; - can_castoutdoor = z->castoutdoor != 0; - is_hotzone = z->hotzone != 0; - max_movement_update_range = z->max_movement_update_range; - default_ruleset = z->ruleset; - allow_mercs = true; - m_graveyard_id = z->graveyard_id; - m_max_clients = z->maxclients; + can_bind = z->canbind != 0; + is_city = z->canbind == 2; + can_combat = z->cancombat != 0; + can_levitate = z->canlevitate != 0; + can_castoutdoor = z->castoutdoor != 0; + is_hotzone = z->hotzone != 0; + m_client_update_range = z->client_update_range; + default_ruleset = z->ruleset; + allow_mercs = true; + m_graveyard_id = z->graveyard_id; + m_max_clients = z->maxclients; SetIdleWhenEmpty(z->idle_when_empty); SetSecondsBeforeIdle(z->seconds_before_idle); @@ -1538,10 +1537,6 @@ bool Zone::Process() { if (adv_data && !did_adventure_actions) { DoAdventureActions(); } - - if (GetNpcPositionUpdateDistance() == 0) { - CalculateNpcUpdateDistanceSpread(); - } } if (hot_reload_timer.Check() && IsQuestHotReloadQueued()) { @@ -2735,62 +2730,6 @@ void Zone::SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp) { m_ucss_available = ucss_available; } -int Zone::GetNpcPositionUpdateDistance() const -{ - return npc_position_update_distance; -} - -void Zone::SetNpcPositionUpdateDistance(int in_npc_position_update_distance) -{ - Zone::npc_position_update_distance = in_npc_position_update_distance; -} - -void Zone::CalculateNpcUpdateDistanceSpread() -{ - float max_x = 0; - float max_y = 0; - float min_x = 0; - float min_y = 0; - - auto &mob_list = entity_list.GetMobList(); - - for (auto &it : mob_list) { - Mob *entity = it.second; - if (!entity->IsNPC()) { - continue; - } - - if (entity->GetX() <= min_x) { - min_x = entity->GetX(); - } - - if (entity->GetY() <= min_y) { - min_y = entity->GetY(); - } - - if (entity->GetX() >= max_x) { - max_x = entity->GetX(); - } - - if (entity->GetY() >= max_y) { - max_y = entity->GetY(); - } - } - - int x_spread = int(std::abs(max_x - min_x)); - int y_spread = int(std::abs(max_y - min_y)); - int combined_spread = int(std::abs((x_spread + y_spread) / 2)); - int update_distance = EQ::ClampLower(int(combined_spread / 4), int(zone->GetMaxMovementUpdateRange())); - - SetNpcPositionUpdateDistance(update_distance); - - Log(Logs::General, Logs::Debug, - "NPC update spread distance set to [%i] combined_spread [%i]", - update_distance, - combined_spread - ); -} - bool Zone::IsQuestHotReloadQueued() const { return quest_hot_reload_queued; diff --git a/zone/zone.h b/zone/zone.h index c29a89b29..1d46a0415 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -156,9 +156,6 @@ public: bool SaveZoneCFG(); bool DoesAlternateCurrencyExist(uint32 currency_id); - int GetNpcPositionUpdateDistance() const; - void SetNpcPositionUpdateDistance(int in_npc_position_update_distance); - char *adv_data; const char *GetSpellBlockedMessage(uint32 spell_id, const glm::vec3 &location); @@ -417,7 +414,7 @@ public: SendDiscordMessage(webhook_id, message_prefix + Discord::FormatDiscordMessage(log_category, message)); }; - double GetMaxMovementUpdateRange() const { return max_movement_update_range; } + double GetClientUpdateRange() const { return m_client_update_range; } void SetIsHotzone(bool is_hotzone); @@ -469,7 +466,7 @@ private: bool staticzone; bool zone_has_current_time; bool quest_hot_reload_queued; - double max_movement_update_range; + double m_client_update_range; char *long_name; char *map_name; char *short_name;