diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dfef867..163196fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,9 +92,6 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # Create a /res directory make_directory(${CMAKE_BINARY_DIR}/res) -# Create a /locale directory -make_directory(${CMAKE_BINARY_DIR}/locale) - # Create a /logs directory make_directory(${CMAKE_BINARY_DIR}/logs) @@ -155,6 +152,8 @@ endforeach() # Create our list of include directories set(INCLUDED_DIRECTORIES "dCommon" + "dCommon/dClient" + "dCommon/dEnums" "dChatFilter" "dGame" "dGame/dBehaviors" @@ -165,7 +164,6 @@ set(INCLUDED_DIRECTORIES "dGame/dEntity" "dGame/dPropertyBehaviors" "dGame/dUtilities" - "dCommon/dClient" "dPhysics" "dNavigation" "dNavigation/dTerrain" diff --git a/CMakePresets.json b/CMakePresets.json index 15eb729e..3968b3ce 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,7 +25,7 @@ "description": "Same as default, Used in GitHub actions workflow", "inherits": "default", "cacheVariables": { - "OPENSSL_ROOT_DIR": "/usr/local/Cellar/openssl@3/3.0.5/" + "OPENSSL_ROOT_DIR": "/usr/local/opt/openssl@3/" } }, { diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 73cb4212..a9f02f53 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -11,6 +11,7 @@ #include "Database.h" #include "dConfig.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -82,7 +83,7 @@ int main(int argc, char** argv) { if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str()); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth); + Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config); //Run it until server gets a kill message from Master: auto t = std::chrono::high_resolution_clock::now(); @@ -150,7 +151,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/AuthServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/AuthServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index fc8bdd5b..e3c6d6e9 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -13,6 +13,7 @@ #include "dChatFilter.h" #include "Diagnostics.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" #include "PlayerContainer.h" #include "ChatPacketHandler.h" @@ -53,9 +54,14 @@ int main(int argc, char** argv) { Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = config.GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("ChatServer", "Got an error while setting up assets: %s", ex.what()); @@ -97,7 +103,7 @@ int main(int argc, char** argv) { if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str()); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat); + Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf")))); @@ -167,7 +173,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/ChatServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/ChatServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dCommon/BinaryPathFinder.cpp b/dCommon/BinaryPathFinder.cpp new file mode 100644 index 00000000..b1c2afef --- /dev/null +++ b/dCommon/BinaryPathFinder.cpp @@ -0,0 +1,71 @@ +#include +#include +#include "BinaryPathFinder.h" +#include "dPlatforms.h" + +#if defined(DARKFLAME_PLATFORM_WIN32) +#include +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) +#include +#elif defined(DARKFLAME_PLATFORM_FREEBSD) +#include +#include +#include +#endif + +std::filesystem::path BinaryPathFinder::binaryDir; + +std::filesystem::path BinaryPathFinder::GetBinaryDir() { + if (!binaryDir.empty()) { + return binaryDir; + } + + std::string pathStr; + + // Derived from boost::dll::program_location, licensed under the Boost Software License: http://www.boost.org/LICENSE_1_0.txt +#if defined(DARKFLAME_PLATFORM_WIN32) + char path[MAX_PATH]; + GetModuleFileName(NULL, path, MAX_PATH); + pathStr = std::string(path); +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) + char path[1024]; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) == 0) { + pathStr = std::string(path); + } else { + // The filepath size is greater than our initial buffer size, so try again with the size + // that _NSGetExecutablePath told us it actually is + char *p = new char[size]; + if (_NSGetExecutablePath(p, &size) != 0) { + throw std::runtime_error("Failed to get binary path from _NSGetExecutablePath"); + } + + pathStr = std::string(p); + delete[] p; + } +#elif defined(DARKFLAME_PLATFORM_FREEBSD) + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + char buf[10240]; + size_t cb = sizeof(buf); + sysctl(mib, 4, buf, &cb, NULL, 0); + pathStr = std::string(buf); +#else // DARKFLAME_PLATFORM_LINUX || DARKFLAME_PLATFORM_UNIX || DARKFLAME_PLATFORM_ANDROID + pathStr = std::filesystem::read_symlink("/proc/self/exe"); +#endif + + // Some methods like _NSGetExecutablePath could return a symlink + // Either way, we need to get the parent path because we want the directory, not the binary itself + // We also ensure that it is an absolute path so that it is valid if we need to construct a path + // to exucute on unix systems (eg sudo BinaryPathFinder::GetBinaryDir() / WorldServer) + if (std::filesystem::is_symlink(pathStr)) { + binaryDir = std::filesystem::absolute(std::filesystem::read_symlink(pathStr).parent_path()); + } else { + binaryDir = std::filesystem::absolute(std::filesystem::path(pathStr).parent_path()); + } + + return binaryDir; +} diff --git a/dCommon/BinaryPathFinder.h b/dCommon/BinaryPathFinder.h new file mode 100644 index 00000000..c4ca6da2 --- /dev/null +++ b/dCommon/BinaryPathFinder.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef __BINARYPATHFINDER__H__ +#define __BINARYPATHFINDER__H__ + +#include + +class BinaryPathFinder { +private: + static std::filesystem::path binaryDir; +public: + static std::filesystem::path GetBinaryDir(); +}; + +#endif //!__BINARYPATHFINDER__H__ diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 46102b74..549acfb2 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -15,6 +15,8 @@ set(DCOMMON_SOURCES "AMFFormat.cpp" "Type.cpp" "ZCompression.cpp" "BrickByBrickFix.cpp" + "BinaryPathFinder.cpp" + "FdbToSqlite.cpp" ) add_subdirectory(dClient) diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 4c2c8beb..3025f083 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -1,4 +1,6 @@ #include "Diagnostics.h" +#include "Game.h" +#include "dLogger.h" // If we're on Win32, we'll include our minidump writer #ifdef _WIN32 @@ -26,7 +28,7 @@ void make_minidump(EXCEPTION_POINTERS* e) { "_%4d%02d%02d_%02d%02d%02d.dmp", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); } - + Game::logger->Log("Diagnostics", "Creating crash dump %s", name); auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) return; @@ -81,6 +83,7 @@ struct bt_ctx { static inline void Bt(struct backtrace_state* state) { std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; + Game::logger->Log("Diagnostics", "backtrace is enabled, crash dump located at %s", fileName.c_str()); FILE* file = fopen(fileName.c_str(), "w+"); if (file != nullptr) { backtrace_print(state, 2, file); @@ -114,6 +117,8 @@ void GenerateDump() { void CatchUnhandled(int sig) { #ifndef __include_backtrace__ + std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; + Game::logger->Log("Diagnostics", "Encountered signal %i, creating crash dump %s", sig, fileName.c_str()); if (Diagnostics::GetProduceMemoryDump()) { GenerateDump(); } @@ -124,7 +129,6 @@ void CatchUnhandled(int sig) { // get void*'s for all entries on the stack size = backtrace(array, 10); - printf("Fatal error %i\nStacktrace:\n", sig); #if defined(__GNUG__) and defined(__dynamic) // Loop through the returned addresses, and get the symbols to be demangled @@ -142,19 +146,18 @@ void CatchUnhandled(int sig) { demangled = demangle(functionName.c_str()); if (demangled.empty()) { - printf("[%02zu] %s\n", i, demangled.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, demangled.c_str()); } else { - printf("[%02zu] %s\n", i, functionName.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str()); } } else { - printf("[%02zu] %s\n", i, functionName.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str()); } } #else backtrace_symbols_fd(array, size, STDOUT_FILENO); #endif - std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; FILE* file = fopen(fileName.c_str(), "w+"); if (file != NULL) { // print out all the frames to stderr diff --git a/dCommon/FdbToSqlite.cpp b/dCommon/FdbToSqlite.cpp new file mode 100644 index 00000000..6015fd3a --- /dev/null +++ b/dCommon/FdbToSqlite.cpp @@ -0,0 +1,248 @@ +#include "FdbToSqlite.h" + +#include +#include +#include +#include + +#include "BinaryIO.h" +#include "CDClientDatabase.h" +#include "GeneralUtils.h" +#include "Game.h" +#include "dLogger.h" + +#include "eSqliteDataType.h" + +std::map FdbToSqlite::Convert::sqliteType = { + { eSqliteDataType::NONE, "none"}, + { eSqliteDataType::INT32, "int32"}, + { eSqliteDataType::REAL, "real"}, + { eSqliteDataType::TEXT_4, "text_4"}, + { eSqliteDataType::INT_BOOL, "int_bool"}, + { eSqliteDataType::INT64, "int64"}, + { eSqliteDataType::TEXT_8, "text_8"} +}; + +FdbToSqlite::Convert::Convert(std::string basePath) { + this->basePath = basePath; +} + +bool FdbToSqlite::Convert::ConvertDatabase() { + fdb.open(basePath + "/cdclient.fdb", std::ios::binary); + + try { + CDClientDatabase::Connect(basePath + "/CDServer.sqlite"); + + CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); + + int32_t numberOfTables = ReadInt32(); + ReadTables(numberOfTables); + + CDClientDatabase::ExecuteQuery("COMMIT;"); + } catch (CppSQLite3Exception& e) { + Game::logger->Log("FdbToSqlite", "Encountered error %s converting FDB to SQLite", e.errorMessage()); + return false; + } + + fdb.close(); + return true; +} + +int32_t FdbToSqlite::Convert::ReadInt32() { + int32_t nextInt{}; + BinaryIO::BinaryRead(fdb, nextInt); + return nextInt; +} + +int64_t FdbToSqlite::Convert::ReadInt64() { + int32_t prevPosition = SeekPointer(); + + int64_t value{}; + BinaryIO::BinaryRead(fdb, value); + + fdb.seekg(prevPosition); + return value; +} + +std::string FdbToSqlite::Convert::ReadString() { + int32_t prevPosition = SeekPointer(); + + auto readString = BinaryIO::ReadString(fdb); + + fdb.seekg(prevPosition); + return readString; +} + +int32_t FdbToSqlite::Convert::SeekPointer() { + int32_t position{}; + BinaryIO::BinaryRead(fdb, position); + int32_t prevPosition = fdb.tellg(); + fdb.seekg(position); + return prevPosition; +} + +std::string FdbToSqlite::Convert::ReadColumnHeader() { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfColumns = ReadInt32(); + std::string tableName = ReadString(); + + auto columns = ReadColumns(numberOfColumns); + std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");"; + CDClientDatabase::ExecuteDML(newTable); + + fdb.seekg(prevPosition); + + return tableName; +} + +void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables) { + int32_t prevPosition = SeekPointer(); + + for (int32_t i = 0; i < numberOfTables; i++) { + auto columnHeader = ReadColumnHeader(); + ReadRowHeader(columnHeader); + } + + fdb.seekg(prevPosition); +} + +std::string FdbToSqlite::Convert::ReadColumns(int32_t& numberOfColumns) { + std::stringstream columnsToCreate; + int32_t prevPosition = SeekPointer(); + + std::string name{}; + eSqliteDataType dataType{}; + for (int32_t i = 0; i < numberOfColumns; i++) { + if (i != 0) columnsToCreate << ", "; + dataType = static_cast(ReadInt32()); + name = ReadString(); + columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::sqliteType[dataType]; + } + + fdb.seekg(prevPosition); + return columnsToCreate.str(); +} + +void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfAllocatedRows = ReadInt32(); + if (numberOfAllocatedRows != 0) assert((numberOfAllocatedRows & (numberOfAllocatedRows - 1)) == 0); // assert power of 2 allocation size + ReadRows(numberOfAllocatedRows, tableName); + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t rowid = 0; + for (int32_t row = 0; row < numberOfAllocatedRows; row++) { + int32_t rowPointer = ReadInt32(); + if (rowPointer == -1) rowid++; + else ReadRow(rowid, rowPointer, tableName); + } + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRow(int32_t& rowid, int32_t& position, std::string& tableName) { + int32_t prevPosition = fdb.tellg(); + fdb.seekg(position); + + while (true) { + ReadRowInfo(tableName); + int32_t linked = ReadInt32(); + + rowid += 1; + + if (linked == -1) break; + + fdb.seekg(linked); + } + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfColumns = ReadInt32(); + ReadRowValues(numberOfColumns, tableName); + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t emptyValue{}; + int32_t intValue{}; + float_t floatValue{}; + std::string stringValue{}; + int32_t boolValue{}; + int64_t int64Value{}; + bool insertedFirstEntry = false; + std::stringstream insertedRow; + insertedRow << "INSERT INTO " << tableName << " values ("; + + for (int32_t i = 0; i < numberOfColumns; i++) { + if (i != 0) insertedRow << ", "; // Only append comma and space after first entry in row. + switch (static_cast(ReadInt32())) { + case eSqliteDataType::NONE: + BinaryIO::BinaryRead(fdb, emptyValue); + assert(emptyValue == 0); + insertedRow << "NULL"; + break; + + case eSqliteDataType::INT32: + intValue = ReadInt32(); + insertedRow << intValue; + break; + + case eSqliteDataType::REAL: + BinaryIO::BinaryRead(fdb, floatValue); + insertedRow << std::fixed << std::setprecision(34) << floatValue; // maximum precision of floating point number + break; + + case eSqliteDataType::TEXT_4: + case eSqliteDataType::TEXT_8: { + stringValue = ReadString(); + size_t position = 0; + + // Need to escape quote with a double of ". + while (position < stringValue.size()) { + if (stringValue.at(position) == '\"') { + stringValue.insert(position, "\""); + position++; + } + position++; + } + insertedRow << "\"" << stringValue << "\""; + break; + } + + case eSqliteDataType::INT_BOOL: + BinaryIO::BinaryRead(fdb, boolValue); + insertedRow << static_cast(boolValue); + break; + + case eSqliteDataType::INT64: + int64Value = ReadInt64(); + insertedRow << std::to_string(int64Value); + break; + + default: + throw std::invalid_argument("Unsupported SQLite type encountered."); + break; + + } + } + + insertedRow << ");"; + + auto copiedString = insertedRow.str(); + CDClientDatabase::ExecuteDML(copiedString); + fdb.seekg(prevPosition); +} diff --git a/dCommon/FdbToSqlite.h b/dCommon/FdbToSqlite.h new file mode 100644 index 00000000..a9611220 --- /dev/null +++ b/dCommon/FdbToSqlite.h @@ -0,0 +1,49 @@ +#ifndef __FDBTOSQLITE__H__ +#define __FDBTOSQLITE__H__ + +#pragma once + +#include +#include +#include + +enum class eSqliteDataType : int32_t; + +namespace FdbToSqlite { + class Convert { + public: + Convert(std::string inputFile); + + bool ConvertDatabase(); + + int32_t ReadInt32(); + + int64_t ReadInt64(); + + std::string ReadString(); + + int32_t SeekPointer(); + + std::string ReadColumnHeader(); + + void ReadTables(int32_t& numberOfTables); + + std::string ReadColumns(int32_t& numberOfColumns); + + void ReadRowHeader(std::string& tableName); + + void ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName); + + void ReadRow(int32_t& rowid, int32_t& position, std::string& tableName); + + void ReadRowInfo(std::string& tableName); + + void ReadRowValues(int32_t& numberOfColumns, std::string& tableName); + private: + static std::map sqliteType; + std::string basePath{}; + std::ifstream fdb{}; + }; // class FdbToSqlite +}; //! namespace FdbToSqlite + +#endif //!__FDBTOSQLITE__H__ diff --git a/dCommon/Game.h b/dCommon/Game.h index 616c7fbf..1c4bbc85 100644 --- a/dCommon/Game.h +++ b/dCommon/Game.h @@ -8,7 +8,6 @@ class InstanceManager; class dpWorld; class dChatFilter; class dConfig; -class dLocale; class RakPeerInterface; class AssetManager; struct SystemAddress; @@ -20,7 +19,6 @@ namespace Game { extern dpWorld* physicsWorld; extern dChatFilter* chatFilter; extern dConfig* config; - extern dLocale* locale; extern std::mt19937 randomEngine; extern RakPeerInterface* chatServer; extern AssetManager* assetManager; diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 24ea72a0..54ef5661 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include template inline size_t MinSize(size_t size, const std::basic_string_view& string) { @@ -290,51 +292,30 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream* inStream) { return string; } -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -std::vector GeneralUtils::GetFileNamesFromFolder(const std::string& folder) { - std::vector names; - std::string search_path = folder + "/*.*"; - WIN32_FIND_DATA fd; - HANDLE hFind = ::FindFirstFile(search_path.c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) { - do { - if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - names.push_back(fd.cFileName); - } - } while (::FindNextFile(hFind, &fd)); - ::FindClose(hFind); - } - return names; -} -#else -#include -#include -#include -#include -#include -#include - -std::vector GeneralUtils::GetFileNamesFromFolder(const std::string& folder) { - std::vector names; - struct dirent* entry; - DIR* dir = opendir(folder.c_str()); - if (dir == NULL) { - return names; +std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) { + // Because we dont know how large the initial number before the first _ is we need to make it a map like so. + std::map filenames{}; + for (auto& t : std::filesystem::directory_iterator(folder)) { + auto filename = t.path().filename().string(); + auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); + filenames.insert(std::make_pair(index, filename)); } - while ((entry = readdir(dir)) != NULL) { - std::string value(entry->d_name, strlen(entry->d_name)); - if (value == "." || value == "..") { - continue; + // Now sort the map by the oldest migration. + std::vector sortedFiles{}; + auto fileIterator = filenames.begin(); + std::map::iterator oldest = filenames.begin(); + while (!filenames.empty()) { + if (fileIterator == filenames.end()) { + sortedFiles.push_back(oldest->second); + filenames.erase(oldest); + fileIterator = filenames.begin(); + oldest = filenames.begin(); + continue; } - names.push_back(value); + if (oldest->first > fileIterator->first) oldest = fileIterator; + fileIterator++; } - closedir(dir); - - return names; + return sortedFiles; } -#endif diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index 898616d2..af7c7012 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -12,6 +12,7 @@ #include #include "Game.h" +#include "dLogger.h" /*! \file GeneralUtils.hpp @@ -138,7 +139,7 @@ namespace GeneralUtils { std::vector SplitString(const std::string& str, char delimiter); - std::vector GetFileNamesFromFolder(const std::string& folder); + std::vector GetSqlFileNamesFromFolder(const std::string& folder); template T Parse(const char* value); diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index 349b5271..dc3db0a1 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -1,15 +1,17 @@ +#include + #include "AssetManager.h" #include "Game.h" #include "dLogger.h" #include -AssetManager::AssetManager(const std::string& path) { +AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::is_directory(path)) { - throw std::runtime_error("Attempted to load asset bundle (" + path + ") however it is not a valid directory."); + throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory."); } - m_Path = std::filesystem::path(path); + m_Path = path; if (std::filesystem::exists(m_Path / "client") && std::filesystem::exists(m_Path / "versions")) { m_AssetBundleType = eAssetBundleType::Packed; diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index 87653845..73b24f18 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -48,7 +48,7 @@ struct AssetMemoryBuffer : std::streambuf { class AssetManager { public: - AssetManager(const std::string& path); + AssetManager(const std::filesystem::path& path); ~AssetManager(); std::filesystem::path GetResPath(); diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index c22c91cc..f09a44c1 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,60 +1,53 @@ #include "dConfig.h" + #include +#include "BinaryPathFinder.h" +#include "GeneralUtils.h" + dConfig::dConfig(const std::string& filepath) { - m_EmptyString = ""; - std::ifstream in(filepath); + m_ConfigFilePath = filepath; + LoadConfig(); +} + +void dConfig::LoadConfig() { + std::ifstream in(BinaryPathFinder::GetBinaryDir() / m_ConfigFilePath); if (!in.good()) return; - std::string line; + std::string line{}; while (std::getline(in, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } - std::ifstream sharedConfig("sharedconfig.ini", std::ios::in); + std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in); if (!sharedConfig.good()) return; + line.clear(); while (std::getline(sharedConfig, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } } -dConfig::~dConfig(void) { +void dConfig::ReloadConfig() { + this->m_ConfigValues.clear(); + LoadConfig(); } const std::string& dConfig::GetValue(std::string key) { - for (size_t i = 0; i < m_Keys.size(); ++i) { - if (m_Keys[i] == key) return m_Values[i]; - } - - return m_EmptyString; + return this->m_ConfigValues[key]; } void dConfig::ProcessLine(const std::string& line) { - std::stringstream ss(line); - std::string segment; - std::vector seglist; + auto splitLine = GeneralUtils::SplitString(line, '='); - while (std::getline(ss, segment, '=')) { - seglist.push_back(segment); - } - - if (seglist.size() != 2) return; + if (splitLine.size() != 2) return; //Make sure that on Linux, we remove special characters: - if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r') - seglist[1].erase(seglist[1].size() - 1); + auto& key = splitLine.at(0); + auto& value = splitLine.at(1); + if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1); - for (const auto& key : m_Keys) { - if (seglist[0] == key) { - return; // first loaded key is preferred due to loading shared config secondarily - } - } + if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return; - m_Keys.push_back(seglist[0]); - m_Values.push_back(seglist[1]); + this->m_ConfigValues.insert(std::make_pair(key, value)); } diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 7764fa54..a6dd5df7 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,20 +1,34 @@ #pragma once #include +#include #include -#include class dConfig { public: dConfig(const std::string& filepath); - ~dConfig(void); + /** + * Gets the specified key from the config. Returns an empty string if the value is not found. + * + * @param key Key to find + * @return The keys value in the config + */ const std::string& GetValue(std::string key); + /** + * Loads the config from a file + */ + void LoadConfig(); + + /** + * Reloads the config file to reset values + */ + void ReloadConfig(); + private: void ProcessLine(const std::string& line); private: - std::vector m_Keys; - std::vector m_Values; - std::string m_EmptyString; + std::map m_ConfigValues; + std::string m_ConfigFilePath; }; diff --git a/dCommon/AddFriendResponseCode.h b/dCommon/dEnums/AddFriendResponseCode.h similarity index 100% rename from dCommon/AddFriendResponseCode.h rename to dCommon/dEnums/AddFriendResponseCode.h diff --git a/dCommon/AddFriendResponseType.h b/dCommon/dEnums/AddFriendResponseType.h similarity index 100% rename from dCommon/AddFriendResponseType.h rename to dCommon/dEnums/AddFriendResponseType.h diff --git a/dGame/dInventory/ItemSetPassiveAbilityID.h b/dCommon/dEnums/ItemSetPassiveAbilityID.h similarity index 100% rename from dGame/dInventory/ItemSetPassiveAbilityID.h rename to dCommon/dEnums/ItemSetPassiveAbilityID.h diff --git a/dGame/dMission/MissionLockState.h b/dCommon/dEnums/MissionLockState.h similarity index 100% rename from dGame/dMission/MissionLockState.h rename to dCommon/dEnums/MissionLockState.h diff --git a/dGame/dMission/MissionState.h b/dCommon/dEnums/MissionState.h similarity index 100% rename from dGame/dMission/MissionState.h rename to dCommon/dEnums/MissionState.h diff --git a/dGame/dMission/MissionTaskType.h b/dCommon/dEnums/MissionTaskType.h similarity index 100% rename from dGame/dMission/MissionTaskType.h rename to dCommon/dEnums/MissionTaskType.h diff --git a/dGame/dMission/RacingTaskParam.h b/dCommon/dEnums/RacingTaskParam.h similarity index 100% rename from dGame/dMission/RacingTaskParam.h rename to dCommon/dEnums/RacingTaskParam.h diff --git a/dCommon/dCommonVars.h b/dCommon/dEnums/dCommonVars.h similarity index 99% rename from dCommon/dCommonVars.h rename to dCommon/dEnums/dCommonVars.h index 4c0e15fa..f64d496f 100644 --- a/dCommon/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -1,5 +1,8 @@ #pragma once +#ifndef __DCOMMONVARS__H__ +#define __DCOMMONVARS__H__ + #include #include #include @@ -30,6 +33,8 @@ typedef uint32_t LWOCLONEID; //!< Used for Clone IDs typedef uint16_t LWOMAPID; //!< Used for Map IDs typedef uint16_t LWOINSTANCEID; //!< Used for Instance IDs typedef uint32_t PROPERTYCLONELIST; //!< Used for Property Clone IDs +typedef uint32_t STRIPID; +typedef uint32_t BEHAVIORSTATE; typedef int32_t PetTamingPiece; //!< Pet Taming Pieces @@ -429,6 +434,7 @@ enum eInventoryType : uint32_t { ITEMS = 0, VAULT_ITEMS, BRICKS, + MODELS_IN_BBB, TEMP_ITEMS = 4, MODELS, TEMP_MODELS, @@ -554,6 +560,7 @@ enum ePlayerFlags { ENTER_BBB_FROM_PROPERTY_EDIT_CONFIRMATION_DIALOG = 64, AG_FIRST_COMBAT_COMPLETE = 65, AG_COMPLETE_BOB_MISSION = 66, + IS_NEWS_SCREEN_VISIBLE = 114, NJ_GARMADON_CINEMATIC_SEEN = 125, ELEPHANT_PET_3050 = 801, CAT_PET_3054 = 802, @@ -648,3 +655,5 @@ inline T const& clamp(const T& val, const T& low, const T& high) { return val; } + +#endif //!__DCOMMONVARS__H__ diff --git a/dNet/dMessageIdentifiers.h b/dCommon/dEnums/dMessageIdentifiers.h similarity index 99% rename from dNet/dMessageIdentifiers.h rename to dCommon/dEnums/dMessageIdentifiers.h index 7dc08711..5a5c9088 100644 --- a/dNet/dMessageIdentifiers.h +++ b/dCommon/dEnums/dMessageIdentifiers.h @@ -433,6 +433,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_ORIENT_TO_POSITION = 906, GAME_MSG_ORIENT_TO_ANGLE = 907, GAME_MSG_BOUNCER_ACTIVE_STATUS = 942, + GAME_MSG_UN_USE_BBB_MODEL = 999, GAME_MSG_BBB_LOAD_ITEM_REQUEST = 1000, GAME_MSG_BBB_SAVE_REQUEST = 1001, GAME_MSG_BBB_SAVE_RESPONSE = 1006, @@ -537,6 +538,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_REMOVE_RUN_SPEED_MODIFIER = 1506, GAME_MSG_UPDATE_PROPERTY_PERFORMANCE_COST = 1547, GAME_MSG_PROPERTY_ENTRANCE_BEGIN = 1553, + GAME_MSG_REMOVE_BUFF = 1648, GAME_MSG_REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1666, GAME_MSG_RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1667, GAME_MSG_PLAYER_SET_CAMERA_CYCLING_MODE = 1676, diff --git a/dCommon/dPlatforms.h b/dCommon/dEnums/dPlatforms.h similarity index 100% rename from dCommon/dPlatforms.h rename to dCommon/dEnums/dPlatforms.h diff --git a/dPhysics/dpCollisionGroups.h b/dCommon/dEnums/dpCollisionGroups.h similarity index 100% rename from dPhysics/dpCollisionGroups.h rename to dCommon/dEnums/dpCollisionGroups.h diff --git a/dPhysics/dpCommon.h b/dCommon/dEnums/dpCommon.h similarity index 100% rename from dPhysics/dpCommon.h rename to dCommon/dEnums/dpCommon.h diff --git a/dCommon/eAninmationFlags.h b/dCommon/dEnums/eAninmationFlags.h similarity index 100% rename from dCommon/eAninmationFlags.h rename to dCommon/dEnums/eAninmationFlags.h diff --git a/dCommon/eItemType.h b/dCommon/dEnums/eItemType.h similarity index 100% rename from dCommon/eItemType.h rename to dCommon/dEnums/eItemType.h diff --git a/dCommon/dEnums/eSqliteDataType.h b/dCommon/dEnums/eSqliteDataType.h new file mode 100644 index 00000000..26e1233b --- /dev/null +++ b/dCommon/dEnums/eSqliteDataType.h @@ -0,0 +1,16 @@ +#ifndef __ESQLITEDATATYPE__H__ +#define __ESQLITEDATATYPE__H__ + +#include + +enum class eSqliteDataType : int32_t { + NONE = 0, + INT32, + REAL = 3, + TEXT_4, + INT_BOOL, + INT64, + TEXT_8 = 8 +}; + +#endif //!__ESQLITEDATATYPE__H__ diff --git a/dCommon/eUnequippableActiveType.h b/dCommon/dEnums/eUnequippableActiveType.h similarity index 100% rename from dCommon/eUnequippableActiveType.h rename to dCommon/dEnums/eUnequippableActiveType.h diff --git a/dCommon/eBlueprintSaveResponseType.h b/dCommon/eBlueprintSaveResponseType.h new file mode 100644 index 00000000..29d15695 --- /dev/null +++ b/dCommon/eBlueprintSaveResponseType.h @@ -0,0 +1,26 @@ +#pragma once + +#ifndef __EBLUEPRINTSAVERESPONSETYPE__H__ +#define __EBLUEPRINTSAVERESPONSETYPE__H__ + +#include + +enum class eBlueprintSaveResponseType : uint32_t { + EverythingWorked = 0, + SaveCancelled, + CantBeginTransaction, + SaveBlueprintFailed, + SaveUgobjectFailed, + CantEndTransaction, + SaveFilesFailed, + BadInput, + NotEnoughBricks, + InventoryFull, + ModelGenerationFailed, + PlacementFailed, + GmLevelInsufficient, + WaitForPreviousSave, + FindMatchesFailed +}; + +#endif //!__EBLUEPRINTSAVERESPONSETYPE__H__ diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 25312608..54def9e2 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -6,12 +6,13 @@ #include "Game.h" #include "GeneralUtils.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include Migration LoadMigration(std::string path) { Migration migration{}; - std::ifstream file("./migrations/" + path); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path); if (file.is_open()) { std::string line; @@ -37,7 +38,7 @@ void MigrationRunner::RunMigrations() { sql::SQLString finalSQL = ""; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/dlu/")) { + for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { auto migration = LoadMigration("dlu/" + entry); if (migration.data.empty()) { @@ -93,26 +94,49 @@ void MigrationRunner::RunMigrations() { } void MigrationRunner::RunSQLiteMigrations() { + auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);"); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); + auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());"); stmt->execute(); delete stmt; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/cdserver/")) { + for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { auto migration = LoadMigration("cdserver/" + entry); if (migration.data.empty()) continue; + // Check if there is an entry in the migration history table on the cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + auto cdres = cdstmt.execQuery(); + bool doExit = !cdres.eof(); + cdres.finalize(); + cdstmt.finalize(); + + if (doExit) continue; + + // Check first if there is entry in the migration history table on the main database. stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); stmt->setString(1, migration.name.c_str()); auto* res = stmt->executeQuery(); - bool doExit = res->next(); + doExit = res->next(); delete res; delete stmt; - if (doExit) continue; + if (doExit) { + // Insert into cdclient database if there is an entry in the main database but not the cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); + continue; + } // Doing these 1 migration at a time since one takes a long time and some may think it is crashing. // This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated". Game::logger->Log("MigrationRunner", "Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str()); + CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) { if (dml.empty()) continue; try { @@ -121,10 +145,14 @@ void MigrationRunner::RunSQLiteMigrations() { Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage()); } } - stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - stmt->setString(1, migration.name); - stmt->execute(); - delete stmt; + + // Insert into cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); + CDClientDatabase::ExecuteQuery("COMMIT;"); } + Game::logger->Log("MigrationRunner", "CDServer database is up to date."); } diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 440c30c1..a9cffc65 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -264,14 +264,17 @@ void Character::DoQuickXMLDataParse() { if (flags) { auto* currentChild = flags->FirstChildElement(); while (currentChild) { - uint32_t index = 0; - uint64_t value = 0; const auto* temp = currentChild->Attribute("v"); + const auto* id = currentChild->Attribute("id"); + if (temp && id) { + uint32_t index = 0; + uint64_t value = 0; - index = std::stoul(currentChild->Attribute("id")); - value = std::stoull(temp); + index = std::stoul(id); + value = std::stoull(temp); - m_PlayerFlags.insert(std::make_pair(index, value)); + m_PlayerFlags.insert(std::make_pair(index, value)); + } currentChild = currentChild->NextSiblingElement(); } } @@ -351,6 +354,13 @@ void Character::SaveXMLToDatabase() { flags->LinkEndChild(f); } + // Prevents the news feed from showing up on world transfers + if (GetPlayerFlag(ePlayerFlags::IS_NEWS_SCREEN_VISIBLE)) { + auto* s = m_Doc->NewElement("s"); + s->SetAttribute("si", ePlayerFlags::IS_NEWS_SCREEN_VISIBLE); + flags->LinkEndChild(s); + } + SaveXmlRespawnCheckpoints(); //Call upon the entity to update our xmlDoc: @@ -361,6 +371,31 @@ void Character::SaveXMLToDatabase() { m_OurEntity->UpdateXMLDoc(m_Doc); + WriteToDatabase(); + + //For metrics, log the time it took to save: + auto end = std::chrono::system_clock::now(); + std::chrono::duration elapsed = end - start; + Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count()); +} + +void Character::SetIsNewLogin() { + // If we dont have a flag element, then we cannot have a s element as a child of flag. + auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + if (!flags) return; + + auto* currentChild = flags->FirstChildElement(); + while (currentChild) { + if (currentChild->Attribute("si")) { + flags->DeleteChild(currentChild); + Game::logger->Log("Character", "Removed isLoggedIn flag from character %i, saving character to database", GetID()); + WriteToDatabase(); + } + currentChild = currentChild->NextSiblingElement(); + } +} + +void Character::WriteToDatabase() { //Dump our xml into m_XMLData: auto* printer = new tinyxml2::XMLPrinter(0, true, 0); m_Doc->Print(printer); @@ -372,12 +407,6 @@ void Character::SaveXMLToDatabase() { stmt->setUInt(2, m_ID); stmt->execute(); delete stmt; - - //For metrics, log the time it took to save: - auto end = std::chrono::system_clock::now(); - std::chrono::duration elapsed = end - start; - Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count()); - delete printer; } diff --git a/dGame/Character.h b/dGame/Character.h index 52bff83d..07bde36c 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -23,6 +23,10 @@ public: Character(uint32_t id, User* parentUser); ~Character(); + /** + * Write the current m_Doc to the database for saving. + */ + void WriteToDatabase(); void SaveXMLToDatabase(); void UpdateFromDatabase(); @@ -32,6 +36,15 @@ public: const std::string& GetXMLData() const { return m_XMLData; } tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; } + /** + * Out of abundance of safety and clarity of what this saves, this is its own function. + * + * Clears the s element from the flag element and saves the xml to the database. Used to prevent the news + * feed from showing up on world transfers. + * + */ + void SetIsNewLogin(); + /** * Gets the database ID of the character * @return the database ID of the character @@ -427,7 +440,7 @@ public: /** * @brief Get the flying state - * @return value of the flying state + * @return value of the flying state */ bool GetIsFlying() { return m_IsFlying; } diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index e6f59a19..69ef81ff 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -454,7 +454,7 @@ void Entity::Initialize() { */ CDScriptComponentTable* scriptCompTable = CDClientManager::Instance()->GetTable("ScriptComponent"); - int scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT); + int32_t scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT, -1); std::string scriptName = ""; bool client = false; @@ -496,7 +496,7 @@ void Entity::Initialize() { scriptName = customScriptServer; } - if (!scriptName.empty() || client || m_Character) { + if (!scriptName.empty() || client || m_Character || scriptComponentID >= 0) { m_Components.insert(std::make_pair(COMPONENT_TYPE_SCRIPT, new ScriptComponent(this, scriptName, true, client && scriptName.empty()))); } diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 7da26b9a..6779a42c 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -20,6 +20,7 @@ #include "Entity.h" #include "EntityManager.h" #include "SkillComponent.h" +#include "AssetManager.h" UserManager* UserManager::m_Address = nullptr; @@ -32,43 +33,59 @@ inline void StripCR(std::string& str) { } void UserManager::Initialize() { - std::string firstNamePath = "./res/names/minifigname_first.txt"; - std::string middleNamePath = "./res/names/minifigname_middle.txt"; - std::string lastNamePath = "./res/names/minifigname_last.txt"; std::string line; - std::fstream fnFile(firstNamePath, std::ios::in); - std::fstream mnFile(middleNamePath, std::ios::in); - std::fstream lnFile(lastNamePath, std::ios::in); - - while (std::getline(fnFile, line, '\n')) { + AssetMemoryBuffer fnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_first.txt"); + if (!fnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_first.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream fnStream = std::istream(&fnBuff); + while (std::getline(fnStream, line, '\n')) { std::string name = line; StripCR(name); m_FirstNames.push_back(name); } + fnBuff.close(); - while (std::getline(mnFile, line, '\n')) { + AssetMemoryBuffer mnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_middle.txt"); + if (!mnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_middle.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream mnStream = std::istream(&mnBuff); + while (std::getline(mnStream, line, '\n')) { std::string name = line; StripCR(name); m_MiddleNames.push_back(name); } + mnBuff.close(); - while (std::getline(lnFile, line, '\n')) { + AssetMemoryBuffer lnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_last.txt"); + if (!lnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_last.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream lnStream = std::istream(&lnBuff); + while (std::getline(lnStream, line, '\n')) { std::string name = line; StripCR(name); m_LastNames.push_back(name); } - - fnFile.close(); - mnFile.close(); - lnFile.close(); + lnBuff.close(); //Load our pre-approved names: - std::fstream chatList("./res/chatplus_en_us.txt", std::ios::in); - while (std::getline(chatList, line, '\n')) { + AssetMemoryBuffer chatListBuff = Game::assetManager->GetFileAsBuffer("chatplus_en_us.txt"); + if (!chatListBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "chatplus_en_us.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing chat whitelist file."); + } + std::istream chatListStream = std::istream(&chatListBuff); + while (std::getline(chatListStream, line, '\n')) { StripCR(line); m_PreapprovedNames.push_back(line); } + chatListBuff.close(); } UserManager::~UserManager() { @@ -210,6 +227,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) { while (res->next()) { LWOOBJID objID = res->getUInt64(1); Character* character = new Character(uint32_t(objID), u); + character->SetIsNewLogin(); chars.push_back(character); } } diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 046df117..d0d6192b 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -42,6 +42,7 @@ #include "SkillCastFailedBehavior.h" #include "SpawnBehavior.h" #include "ForceMovementBehavior.h" +#include "RemoveBuffBehavior.h" #include "ImmunityBehavior.h" #include "InterruptBehavior.h" #include "PlayEffectBehavior.h" @@ -226,7 +227,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) { break; case BehaviorTemplates::BEHAVIOR_ALTER_CHAIN_DELAY: break; case BehaviorTemplates::BEHAVIOR_CAMERA: break; - case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF: break; + case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF: + behavior = new RemoveBuffBehavior(behaviorId); + break; case BehaviorTemplates::BEHAVIOR_GRAB: break; case BehaviorTemplates::BEHAVIOR_MODULAR_BUILD: break; case BehaviorTemplates::BEHAVIOR_NPC_COMBAT_SKILL: diff --git a/dGame/dBehaviors/CMakeLists.txt b/dGame/dBehaviors/CMakeLists.txt index fe8e89b8..297c389b 100644 --- a/dGame/dBehaviors/CMakeLists.txt +++ b/dGame/dBehaviors/CMakeLists.txt @@ -34,6 +34,7 @@ set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp" "PlayEffectBehavior.cpp" "ProjectileAttackBehavior.cpp" "PullToPointBehavior.cpp" + "RemoveBuffBehavior.cpp" "RepairBehavior.cpp" "SkillCastFailedBehavior.cpp" "SkillEventBehavior.cpp" diff --git a/dGame/dBehaviors/ForceMovementBehavior.cpp b/dGame/dBehaviors/ForceMovementBehavior.cpp index 55cda622..e1ac6133 100644 --- a/dGame/dBehaviors/ForceMovementBehavior.cpp +++ b/dGame/dBehaviors/ForceMovementBehavior.cpp @@ -75,5 +75,5 @@ void ForceMovementBehavior::SyncCalculation(BehaviorContext* context, RakNet::Bi this->m_hitAction->Calculate(context, bitStream, branch); this->m_hitEnemyAction->Calculate(context, bitStream, branch); - this->m_hitEnemyAction->Calculate(context, bitStream, branch); + this->m_hitFactionAction->Calculate(context, bitStream, branch); } diff --git a/dGame/dBehaviors/RemoveBuffBehavior.cpp b/dGame/dBehaviors/RemoveBuffBehavior.cpp new file mode 100644 index 00000000..be3066ac --- /dev/null +++ b/dGame/dBehaviors/RemoveBuffBehavior.cpp @@ -0,0 +1,21 @@ +#include "RemoveBuffBehavior.h" + +#include "BehaviorBranchContext.h" +#include "BehaviorContext.h" +#include "EntityManager.h" +#include "BuffComponent.h" + +void RemoveBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + auto* entity = EntityManager::Instance()->GetEntity(context->caster); + if (!entity) return; + + auto* buffComponent = entity->GetComponent(); + if (!buffComponent) return; + + buffComponent->RemoveBuff(m_BuffId, false, m_RemoveImmunity); +} + +void RemoveBuffBehavior::Load() { + this->m_RemoveImmunity = GetBoolean("remove_immunity"); + this->m_BuffId = GetInt("buff_id"); +} diff --git a/dGame/dBehaviors/RemoveBuffBehavior.h b/dGame/dBehaviors/RemoveBuffBehavior.h new file mode 100644 index 00000000..f2d8547b --- /dev/null +++ b/dGame/dBehaviors/RemoveBuffBehavior.h @@ -0,0 +1,22 @@ +#pragma once +#include "Behavior.h" + +class RemoveBuffBehavior final : public Behavior +{ +public: + + /* + * Inherited + */ + + explicit RemoveBuffBehavior(const uint32_t behaviorId) : Behavior(behaviorId) { + } + + void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void Load() override; + +private: + bool m_RemoveImmunity; + uint32_t m_BuffId; +}; diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index ecb10017..af20fc00 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -123,13 +123,15 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO m_Buffs.emplace(id, buff); } -void BuffComponent::RemoveBuff(int32_t id) { +void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) { const auto& iter = m_Buffs.find(id); if (iter == m_Buffs.end()) { return; } + GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id); + m_Buffs.erase(iter); RemoveBuffEffect(id); diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index aa669b13..7f7c6b0f 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -78,8 +78,9 @@ public: /** * Removes a buff from the parent entity, reversing its effects * @param id the id of the buff to remove + * @param removeImmunity whether or not to remove immunity on removing the buff */ - void RemoveBuff(int32_t id); + void RemoveBuff(int32_t id, bool fromUnEquip = false, bool removeImmunity = false); /** * Returns whether or not the entity has a buff identified by `id` diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 930ace8d..8215664c 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -209,9 +209,11 @@ void InventoryComponent::AddItem( auto stack = static_cast(info.stackSize); + bool isBrick = inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1); + // info.itemType of 1 is item type brick - if (inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1)) { - stack = 999; + if (isBrick) { + stack = UINT32_MAX; } else if (stack == 0) { stack = 1; } @@ -232,7 +234,8 @@ void InventoryComponent::AddItem( } } - while (left > 0) { + // If we have some leftover and we aren't bricks, make a new stack + while (left > 0 && (!isBrick || (isBrick && !existing))) { const auto size = std::min(left, stack); left -= size; @@ -327,7 +330,9 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto lot = item->GetLot(); - if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP)) { + const auto subkey = item->GetSubKey(); + + if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) { auto left = std::min(count, origin->GetLotCount(lot)); while (left > 0) { @@ -358,7 +363,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto delta = std::min(item->GetCount(), count); - AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot); + AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot); item->SetCount(item->GetCount() - delta, false, false); } @@ -605,16 +610,17 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { return; } - std::vector inventories; + std::vector inventoriesToSave; + // Need to prevent some transfer inventories from being saved for (const auto& pair : this->m_Inventories) { auto* inventory = pair.second; - if (inventory->GetType() == VENDOR_BUYBACK) { + if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) { continue; } - inventories.push_back(inventory); + inventoriesToSave.push_back(inventory); } inventoryElement->SetAttribute("csl", m_Consumable); @@ -629,7 +635,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { bags->DeleteChildren(); - for (const auto* inventory : inventories) { + for (const auto* inventory : inventoriesToSave) { auto* bag = document->NewElement("b"); bag->SetAttribute("t", inventory->GetType()); @@ -648,7 +654,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { items->DeleteChildren(); - for (auto* inventory : inventories) { + for (auto* inventory : inventoriesToSave) { if (inventory->GetSize() == 0) { continue; } @@ -985,6 +991,7 @@ void InventoryComponent::ApplyBuff(Item* item) const { } } +// TODO Something needs to send the remove buff GameMessage as well when it is unequipping items that would remove buffs. void InventoryComponent::RemoveBuff(Item* item) const { const auto buffs = FindBuffs(item, false); @@ -1258,7 +1265,7 @@ BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) { } bool InventoryComponent::IsTransferInventory(eInventoryType type) { - return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS; + return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB; } uint32_t InventoryComponent::FindSkill(const LOT lot) { diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index d196e935..eaade8be 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -474,7 +474,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet settings.push_back(propertyObjectID); settings.push_back(modelType); - inventoryComponent->AddItem(6662, 1, eLootSourceType::LOOT_SOURCE_DELETION, eInventoryType::HIDDEN, settings, LWOOBJID_EMPTY, false, false, spawnerId); + inventoryComponent->AddItem(6662, 1, eLootSourceType::LOOT_SOURCE_DELETION, eInventoryType::MODELS_IN_BBB, settings, LWOOBJID_EMPTY, false, false, spawnerId); auto* item = inventoryComponent->FindItemBySubKey(spawnerId); if (item == nullptr) { diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index 4b5ec41d..f6d50d66 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -19,8 +19,9 @@ #include "DestroyableComponent.h" ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activityID) : Component(parent) { + m_ActivityID = activityID; CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); - std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == activityID); }); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); for (CDActivities activity : activities) { m_ActivityInfo = activity; @@ -88,6 +89,21 @@ void ScriptedActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool } } +void ScriptedActivityComponent::ReloadConfig() { + CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); + for (auto activity : activities) { + auto mapID = m_ActivityInfo.instanceMapID; + if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") { + m_ActivityInfo.minTeamSize = 1; + m_ActivityInfo.minTeams = 1; + } else { + m_ActivityInfo.minTeamSize = activity.minTeamSize; + m_ActivityInfo.minTeams = activity.minTeams; + } + } +} + void ScriptedActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) { if (m_ActivityInfo.ActivityID == 103) { return; diff --git a/dGame/dComponents/ScriptedActivityComponent.h b/dGame/dComponents/ScriptedActivityComponent.h index 66ca799f..8bb17093 100644 --- a/dGame/dComponents/ScriptedActivityComponent.h +++ b/dGame/dComponents/ScriptedActivityComponent.h @@ -276,6 +276,12 @@ public: */ ActivityInstance* GetInstance(const LWOOBJID playerID); + /** + * @brief Reloads the config settings for this component + * + */ + void ReloadConfig(); + /** * Removes all the instances */ @@ -361,6 +367,12 @@ private: * LMIs for team sizes */ std::unordered_map m_ActivityLootMatrices; + + /** + * The activity id + * + */ + int32_t m_ActivityID; }; #endif // SCRIPTEDACTIVITYCOMPONENT_H diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 2ce79966..b7687c19 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -46,6 +46,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System switch (messageID) { + case GAME_MSG_UN_USE_BBB_MODEL: { + GameMessages::HandleUnUseModel(inStream, entity, sysAddr); + break; + } case GAME_MSG_PLAY_EMOTE: { GameMessages::HandlePlayEmote(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 18d7e0f6..fcc25fdc 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -70,6 +70,7 @@ #include "TradingManager.h" #include "ControlBehaviors.h" #include "AMFDeserialize.h" +#include "eBlueprintSaveResponseType.h" void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; @@ -2139,6 +2140,32 @@ void GameMessages::HandleSetPropertyAccess(RakNet::BitStream* inStream, Entity* PropertyManagementComponent::Instance()->SetPrivacyOption(static_cast(accessType)); } +void GameMessages::HandleUnUseModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + bool unknown{}; + LWOOBJID objIdToAddToInventory{}; + inStream->Read(unknown); + inStream->Read(objIdToAddToInventory); + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(eInventoryType::MODELS_IN_BBB); + auto* item = inventory->FindItemById(objIdToAddToInventory); + if (item) { + inventoryComponent->MoveItemToInventory(item, eInventoryType::MODELS, 1); + } else { + Game::logger->Log("GameMessages", "item id %llu not found in MODELS_IN_BBB inventory, likely because it does not exist", objIdToAddToInventory); + } + } + + if (unknown) { + CBITSTREAM; + PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes + bitStream.Write(eBlueprintSaveResponseType::PlacementFailed); // Sending a non-zero error code here prevents the client from deleting its in progress build for some reason? + bitStream.Write(0); + SEND_PACKET; + } +} + void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { bool isProperty{}; LWOOBJID objectId{}; @@ -2353,16 +2380,40 @@ void GameMessages::HandleDeletePropertyModel(RakNet::BitStream* inStream, Entity } void GameMessages::HandleBBBLoadItemRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID itemID = LWOOBJID_EMPTY; - inStream->Read(itemID); + LWOOBJID previousItemID = LWOOBJID_EMPTY; + inStream->Read(previousItemID); - Game::logger->Log("BBB", "Load item request for: %lld", itemID); + Game::logger->Log("BBB", "Load item request for: %lld", previousItemID); + LWOOBJID newId = previousItemID; + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(eInventoryType::MODELS); + auto* itemToMove = inventory->FindItemById(previousItemID); + if (itemToMove) { + LOT previousLot = itemToMove->GetLot(); + inventoryComponent->MoveItemToInventory(itemToMove, eInventoryType::MODELS_IN_BBB, 1, false); + + auto* destinationInventory = inventoryComponent->GetInventory(eInventoryType::MODELS_IN_BBB); + if (destinationInventory) { + auto* movedItem = destinationInventory->FindItemByLot(previousLot); + if (movedItem) newId = movedItem->GetId(); + } + } else { + Game::logger->Log("GameMessages", "item id %llu not found in MODELS inventory, likely because it does not exist", previousItemID); + } + } + + // Second argument always true (successful) for now + SendBlueprintLoadItemResponse(sysAddr, true, previousItemID, newId); +} + +void GameMessages::SendBlueprintLoadItemResponse(const SystemAddress& sysAddr, bool success, LWOOBJID oldItemId, LWOOBJID newItemId) { CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_LOAD_RESPONSE_ITEMID); - bitStream.Write(static_cast(1)); - bitStream.Write(itemID); - bitStream.Write(itemID); + bitStream.Write(static_cast(success)); + bitStream.Write(oldItemId); + bitStream.Write(newItemId); SEND_PACKET; } @@ -2413,7 +2464,7 @@ void GameMessages::HandleControlBehaviors(RakNet::BitStream* inStream, Entity* e } auto owner = PropertyManagementComponent::Instance()->GetOwner(); - if (!owner) return; + if (!owner) return; ControlBehaviors::ProcessCommand(entity, sysAddr, static_cast(amfArguments.get()), command, owner); } @@ -2609,8 +2660,8 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, CLIENT::MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); bitStream.Write(localId); - bitStream.Write(0); - bitStream.Write(1); + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(1); bitStream.Write(blueprintID); bitStream.Write(sd0Size); @@ -2620,7 +2671,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent } SEND_PACKET; - // PacketUtils::SavePacket("MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); //Now we have to construct this object: @@ -3476,6 +3526,20 @@ void GameMessages::SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID ta SEND_PACKET; } +void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(GAME_MSG::GAME_MSG_REMOVE_BUFF); + + bitStream.Write(false); // bFromRemoveBehavior but setting this to true makes the GM not do anything on the client? + bitStream.Write(fromUnEquip); + bitStream.Write(removeImmunity); + bitStream.Write(buffId); + + SEND_PACKET_BROADCAST; +} void GameMessages::SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr) { CBITSTREAM; @@ -4398,13 +4462,13 @@ void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uin // NT void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bAllowPartial; + bool bAllowPartial{}; int32_t destSlot = -1; int32_t iStackCount = 1; eInventoryType invTypeDst = ITEMS; eInventoryType invTypeSrc = ITEMS; LWOOBJID itemID = LWOOBJID_EMPTY; - bool showFlyingLoot; + bool showFlyingLoot{}; LWOOBJID subkey = LWOOBJID_EMPTY; LOT itemLOT = 0; @@ -4428,12 +4492,12 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (item == nullptr) { - return; - } + if (!item) return; - if (inventoryComponent->IsPet(item->GetSubKey()) || !item->GetConfig().empty()) { - return; + // Despawn the pet if we are moving that pet to the vault. + auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); + if (petComponent && petComponent->GetDatabaseId() == item->GetSubKey()) { + inventoryComponent->DespawnPet(); } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); @@ -4894,27 +4958,27 @@ void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) inStream->Read(emoteID); inStream->Read(targetID); - Game::logger->Log("GameMessages", "Emote (%i) (%llu)", emoteID, targetID); + Game::logger->LogDebug("GameMessages", "Emote (%i) (%llu)", emoteID, targetID); //TODO: If targetID != 0, and we have one of the "perform emote" missions, complete them. if (emoteID == 0) return; std::string sAnimationName = "deaded"; //Default name in case we fail to get the emote - MissionComponent* mission = static_cast(entity->GetComponent(COMPONENT_TYPE_MISSION)); - if (mission) { - mission->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, targetID); - } + MissionComponent* missionComponent = entity->GetComponent(); + if (!missionComponent) return; if (targetID != LWOOBJID_EMPTY) { auto* targetEntity = EntityManager::Instance()->GetEntity(targetID); - Game::logger->Log("GameMessages", "Emote target found (%d)", targetEntity != nullptr); + Game::logger->LogDebug("GameMessages", "Emote target found (%d)", targetEntity != nullptr); if (targetEntity != nullptr) { targetEntity->OnEmoteReceived(emoteID, entity); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, targetID); } } else { + Game::logger->LogDebug("GameMessages", "Target ID is empty, using backup"); const auto scriptedEntities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPT); const auto& referencePoint = entity->GetPosition(); @@ -4923,6 +4987,7 @@ void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) if (Vector3::DistanceSquared(scripted->GetPosition(), referencePoint) > 5.0f * 5.0f) continue; scripted->OnEmoteReceived(emoteID, entity); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, scripted->GetObjectID()); } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 31bdebb3..f9968d14 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -121,6 +121,7 @@ namespace GameMessages { void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response); void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, int type); + void HandleUnUseModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); void SendStartCelebrationEffect(Entity* entity, const SystemAddress& sysAddr, int celebrationID); /** @@ -197,6 +198,16 @@ namespace GameMessages { void SendDownloadPropertyData(LWOOBJID objectId, const PropertyDataMessage& data, const SystemAddress& sysAddr); + /** + * @brief Send an updated item id to the client when they load a blueprint in brick build mode + * + * @param sysAddr SystemAddress to respond to + * @param oldItemId The item ID that was requested to be loaded + * @param newItemId The new item ID of the loaded item + * + */ + void SendBlueprintLoadItemResponse(const SystemAddress& sysAddr, bool success, LWOOBJID oldItemId, LWOOBJID newItemId); + void SendPropertyRentalResponse(LWOOBJID objectId, LWOCLONEID cloneId, uint32_t code, LWOOBJID propertyId, int64_t rentDue, const SystemAddress& sysAddr); void SendLockNodeRotation(Entity* entity, std::string nodeName); @@ -566,6 +577,8 @@ namespace GameMessages { void HandleReportBug(RakNet::BitStream* inStream, Entity* entity); + void SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId); + /* Message to synchronize a skill cast */ class EchoSyncSkill { static const GAME_MSG MsgID = GAME_MSG_ECHO_SYNC_SKILL; diff --git a/dGame/dInventory/Inventory.h b/dGame/dInventory/Inventory.h index 30f753da..6c6a4306 100644 --- a/dGame/dInventory/Inventory.h +++ b/dGame/dInventory/Inventory.h @@ -95,7 +95,7 @@ public: * @param lot the lot to find items for * @param ignoreEquipped ignores equipped items * @param ignoreBound ignores bound items - * @return item in the inventory for the provided LOT + * @return item with the lowest stack count in the inventory for the provided LOT */ Item* FindItemByLot(LOT lot, bool ignoreEquipped = false, bool ignoreBound = false) const; diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index c05c0672..02739ec2 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -253,7 +253,7 @@ bool Item::Consume() { } } - Game::logger->Log("Item", "Consumed (%i) / (%llu) with (%d)", lot, id, success); + Game::logger->LogDebug("Item", "Consumed LOT (%i) itemID (%llu). Success=(%d)", lot, id, success); GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success); @@ -265,14 +265,34 @@ bool Item::Consume() { } void Item::UseNonEquip() { + LOT thisLot = this->GetLot(); + if (!GetInventory()) { + Game::logger->LogDebug("Item", "item %i has no inventory??", this->GetLot()); + return; + } + + auto* playerInventoryComponent = GetInventory()->GetComponent(); + if (!playerInventoryComponent) { + Game::logger->LogDebug("Item", "no inventory component attached to item id %llu lot %i", this->GetId(), this->GetLot()); + return; + } + + auto* playerEntity = playerInventoryComponent->GetParent(); + if (!playerEntity) { + Game::logger->LogDebug("Item", "no player entity attached to inventory? item id is %llu", this->GetId()); + return; + } + const auto type = static_cast(info->itemType); if (type == eItemType::ITEM_TYPE_MOUNT) { - GetInventory()->GetComponent()->HandlePossession(this); + playerInventoryComponent->HandlePossession(this); + // TODO Check if mounts are allowed to be spawned } else if (type == eItemType::ITEM_TYPE_PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { - const auto& databasePet = GetInventory()->GetComponent()->GetDatabasePet(subKey); + const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); if (databasePet.lot != LOT_NULL) { - GetInventory()->GetComponent()->SpawnPet(this); + playerInventoryComponent->SpawnPet(this); } + // This precondition response is taken care of in SpawnPet(). } else { auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, COMPONENT_TYPE_PACKAGE); @@ -282,18 +302,41 @@ void Item::UseNonEquip() { auto* packCompTable = CDClientManager::Instance()->GetTable("PackageComponent"); auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); - const auto success = !packages.empty(); + auto success = !packages.empty(); if (success) { - auto* entityParent = inventory->GetComponent()->GetParent(); - for (auto& pack : packages) { - std::unordered_map result{}; - result = LootGenerator::Instance().RollLootMatrix(entityParent, pack.LootMatrixIndex); - if (!inventory->GetComponent()->HasSpaceForLoot(result)) { + if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) { + auto* entityParent = playerInventoryComponent->GetParent(); + // Roll the loot for all the packages then see if it all fits. If it fits, give it to the player, otherwise don't. + std::unordered_map rolledLoot{}; + for (auto& pack : packages) { + auto thisPackage = LootGenerator::Instance().RollLootMatrix(entityParent, pack.LootMatrixIndex); + for (auto& loot : thisPackage) { + // If we already rolled this lot, add it to the existing one, otherwise create a new entry. + auto existingLoot = rolledLoot.find(loot.first); + if (existingLoot == rolledLoot.end()) { + rolledLoot.insert(loot); + } else { + existingLoot->second += loot.second; + } + } } - LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION); + if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { + LootGenerator::Instance().GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::LOOT_SOURCE_CONSUMPTION); + playerInventoryComponent->RemoveItem(lot, 1); + } else { + success = false; + } + } else { + GameMessages::SendUseItemRequirementsResponse( + playerInventoryComponent->GetParent()->GetObjectID(), + playerInventoryComponent->GetParent()->GetSystemAddress(), + UseItemResponse::FailedPrecondition + ); + success = false; } - inventory->GetComponent()->RemoveItem(lot, 1); } + Game::logger->LogDebug("Item", "Player %llu %s used item %i", playerEntity->GetObjectID(), success ? "successfully" : "unsuccessfully", thisLot); + GameMessages::SendUseItemResult(playerInventoryComponent->GetParent(), thisLot, success); } } diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 6020e51c..0a8a57fe 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -13,7 +13,6 @@ #include "Mail.h" #include "MissionComponent.h" #include "RacingTaskParam.h" -#include "dLocale.h" #include "dLogger.h" #include "dServer.h" #include "dZoneManager.h" @@ -335,13 +334,10 @@ void Mission::Complete(const bool yieldRewards) { for (const auto& email : missionEmails) { const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_"; - const auto senderLocale = missionEmailBase + "senderName"; - const auto announceLocale = missionEmailBase + "announceText"; - - if (email.messageType == 1 && Game::locale->HasPhrase(senderLocale)) { - const auto subject = dLocale::GetTemplate(missionEmailBase + "subjectText"); - const auto body = dLocale::GetTemplate(missionEmailBase + "bodyText"); - const auto sender = dLocale::GetTemplate(senderLocale); + if (email.messageType == 1) { + const auto subject = "%[" + missionEmailBase + "subjectText]"; + const auto body = "%[" + missionEmailBase + "bodyText]"; + const auto sender = "%[" + missionEmailBase + "senderName]"; Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1); } diff --git a/dGame/dPropertyBehaviors/BehaviorStates.h b/dGame/dPropertyBehaviors/BehaviorStates.h new file mode 100644 index 00000000..e09e45ba --- /dev/null +++ b/dGame/dPropertyBehaviors/BehaviorStates.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef __BEHAVIORSTATES__H__ +#define __BEHAVIORSTATES__H__ + +#include +#include + +#include "dCommonVars.h" + +enum States : BEHAVIORSTATE { + HOME_STATE = 0, //!< The HOME behavior state + CIRCLE_STATE, //!< The CIRCLE behavior state + SQUARE_STATE, //!< The SQUARE behavior state + DIAMOND_STATE, //!< The DIAMOND behavior state + TRIANGLE_STATE, //!< The TRIANGLE behavior state + STAR_STATE //!< The STAR behavior state +}; + +#endif //!__BEHAVIORSTATES__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index 4e922ee0..d5b71a3a 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -5,50 +5,67 @@ #include "Game.h" #include "GameMessages.h" #include "ModelComponent.h" +#include "../../dWorldServer/ObjectIDManager.h" #include "dLogger.h" -void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) { - if (!modelEntity || !modelOwner || !arguments) return; +uint32_t GetBehaviorIDFromArgument(AMFArrayValue* arguments, const std::string& key = "BehaviorID") { + auto* behaviorIDValue = arguments->FindValue(key); + uint32_t behaviorID = -1; - if (command == "sendBehaviorListToClient") - SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); - else if (command == "modelTypeChanged") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "toggleExecutionUpdates") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "addStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "removeStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "mergeStrips") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "splitStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "updateStripUI") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "addAction") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "migrateActions") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "rearrangeStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "add") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "removeActions") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "rename") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "sendBehaviorBlocksToClient") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "moveToInventory") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "updateAction") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else - Game::logger->Log("ControlBehaviors", "Unknown behavior command (%s)\n", command.c_str()); + if (behaviorIDValue) { + behaviorID = std::stoul(behaviorIDValue->GetStringValue()); + } else if (arguments->FindValue(key) == nullptr){ + throw std::invalid_argument("Unable to find behavior ID from argument \"" + key + "\""); + } + + return behaviorID; } -void ControlBehaviors::SendBehaviorListToClient( +BEHAVIORSTATE GetBehaviorStateFromArgument(AMFArrayValue* arguments, const std::string& key = "stateID") { + auto* stateIDValue = arguments->FindValue(key); + if (!stateIDValue) throw std::invalid_argument("Unable to find behavior state from argument \"" + key + "\""); + + BEHAVIORSTATE stateID = static_cast(stateIDValue->GetDoubleValue()); + + return stateID; +} + +STRIPID GetStripIDFromArgument(AMFArrayValue* arguments, const std::string& key = "stripID") { + auto* stripIDValue = arguments->FindValue(key); + if (!stripIDValue) throw std::invalid_argument("Unable to find strip ID from argument \"" + key + "\""); + + STRIPID stripID = static_cast(stripIDValue->GetDoubleValue()); + + return stripID; +} + +void RequestUpdatedID(int32_t behaviorID, ModelComponent* modelComponent, Entity* modelOwner, const SystemAddress& sysAddr) { + // auto behavior = modelComponent->FindBehavior(behaviorID); + // if (behavior->GetBehaviorID() == -1 || behavior->GetShouldSetNewID()) { + // ObjectIDManager::Instance()->RequestPersistentID( + // [behaviorID, behavior, modelComponent, modelOwner, sysAddr](uint32_t persistentId) { + // behavior->SetShouldGetNewID(false); + // behavior->SetIsTemplated(false); + // behavior->SetBehaviorID(persistentId); + + // // This updates the behavior ID of the behavior should this be a new behavior + // AMFArrayValue args; + + // AMFStringValue* behaviorIDString = new AMFStringValue(); + // behaviorIDString->SetStringValue(std::to_string(persistentId)); + // args.InsertValue("behaviorID", behaviorIDString); + + // AMFStringValue* objectIDAsString = new AMFStringValue(); + // objectIDAsString->SetStringValue(std::to_string(modelComponent->GetParent()->GetObjectID())); + // args.InsertValue("objectID", objectIDAsString); + + // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorID", &args); + // ControlBehaviors::SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); + // }); + // } +} + +void SendBehaviorListToClient( Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner @@ -57,7 +74,7 @@ void ControlBehaviors::SendBehaviorListToClient( if (!modelComponent) return; - AMFArrayValue behaviorsToSerialize; + AMFArrayValue behaviorsToSerialize; AMFArrayValue* behaviors = new AMFArrayValue(); // Empty for now @@ -79,3 +96,506 @@ void ControlBehaviors::SendBehaviorListToClient( behaviorsToSerialize.InsertValue("objectID", amfStringValueForObjectID); GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorList", &behaviorsToSerialize); } + +void ModelTypeChanged(AMFArrayValue* arguments, ModelComponent* ModelComponent) { + auto* modelTypeAmf = arguments->FindValue("ModelType"); + if (!modelTypeAmf) return; + + uint32_t modelType = static_cast(modelTypeAmf->GetDoubleValue()); + + //TODO Update the model type here +} + +void ToggleExecutionUpdates() { + //TODO do something with this info +} + +void AddStrip(AMFArrayValue* arguments) { + auto* strip = arguments->FindValue("strip"); + if (!strip) return; + + auto* actions = strip->FindValue("actions"); + if (!actions) return; + + auto* uiArray = arguments->FindValue("ui"); + if (!uiArray) return; + + auto* xPositionValue = uiArray->FindValue("x"); + if (!xPositionValue) return; + + double xPosition = xPositionValue->GetDoubleValue(); + + auto* yPositionValue = uiArray->FindValue("y"); + if (!yPositionValue) return; + + double yPosition = yPositionValue->GetDoubleValue(); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + for (uint32_t position = 0; position < actions->GetDenseValueSize(); position++) { + auto* actionAsArray = actions->GetValueAt(position); + if (!actionAsArray) continue; + + for (auto& typeValueMap : actionAsArray->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + // modelComponent->AddStrip(stateID, stripID, type, behaviorID, valueParameterName, valueParameterString, valueParameterDouble, "", xPosition, yPosition); + type.clear(); + valueParameterName.clear(); + valueParameterString.clear(); + valueParameterDouble = 0.0; + } + // RequestUpdatedID(behaviorID); +} + +void RemoveStrip(AMFArrayValue* arguments) { + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->RemoveStrip(stateID, stripID, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MergeStrips(AMFArrayValue* arguments) { + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + auto* dstActionIndexValue = arguments->FindValue("dstActionIndex"); + if (!dstActionIndexValue) return; + + uint32_t dstActionIndex = static_cast(dstActionIndexValue->GetDoubleValue()); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->MergeStrips(srcStripID, dstStripID, srcStateID, dstStateID, behaviorID, dstActionIndex); + + // RequestUpdatedID(behaviorID); +} + +void SplitStrip(AMFArrayValue* arguments) { + auto* srcActionIndexValue = arguments->FindValue("srcActionIndex"); + if (!srcActionIndexValue) return; + + uint32_t srcActionIndex = static_cast(srcActionIndexValue->GetDoubleValue()); + + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + auto* dstStripUIArray = arguments->FindValue("dstStripUI"); + if (!dstStripUIArray) return; + + auto* xPositionValue = dstStripUIArray->FindValue("x"); + auto* yPositionValue = dstStripUIArray->FindValue("y"); + if (!xPositionValue || !yPositionValue) return; + + // x and y position 15 are just where the game puts the strip by default if none is given. + double yPosition = yPositionValue->GetDoubleValue(); + double xPosition = xPositionValue->GetDoubleValue(); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->SplitStrip(srcActionIndex, srcStripID, srcStateID, dstStripID, dstStateID, behaviorID, yPosition, xPosition); + + // RequestUpdatedID(behaviorID); +} + +void UpdateStripUI(AMFArrayValue* arguments) { + auto* uiArray = arguments->FindValue("ui"); + if (!uiArray) return; + + auto* xPositionValue = uiArray->FindValue("x"); + auto* yPositionValue = uiArray->FindValue("y"); + if (!xPositionValue || !yPositionValue) return; + + double yPosition = yPositionValue->GetDoubleValue(); + double xPosition = xPositionValue->GetDoubleValue(); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->UpdateUIOfStrip(stateID, stripID, xPosition, yPosition, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void AddAction(AMFArrayValue* arguments) { + auto* actionIndexAmf = arguments->FindValue("actionIndex"); + if (!actionIndexAmf) return; + + uint32_t actionIndex = static_cast(actionIndexAmf->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + auto* action = arguments->FindValue("action"); + if (!action) return; + + for (auto& typeValueMap : action->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->AddAction(stateID, stripID, type, valueParameterName, valueParameterString, valueParameterDouble, "", actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MigrateActions(AMFArrayValue* arguments) { + auto* srcActionIndexAmf = arguments->FindValue("srcActionIndex"); + if (!srcActionIndexAmf) return; + + uint32_t srcActionIndex = static_cast(srcActionIndexAmf->GetDoubleValue()); + + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + auto* dstActionIndexAmf = arguments->FindValue("dstActionIndex"); + if (!dstActionIndexAmf) return; + + uint32_t dstActionIndex = static_cast(dstActionIndexAmf->GetDoubleValue()); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->MigrateActions(srcActionIndex, srcStripID, srcStateID, dstActionIndex, dstStripID, dstStateID, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void RearrangeStrip(AMFArrayValue* arguments) { + auto* srcActionIndexValue = arguments->FindValue("srcActionIndex"); + uint32_t srcActionIndex = static_cast(srcActionIndexValue->GetDoubleValue()); + + uint32_t stripID = GetStripIDFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* dstActionIndexValue = arguments->FindValue("dstActionIndex"); + uint32_t dstActionIndex = static_cast(dstActionIndexValue->GetDoubleValue()); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->RearrangeStrip(stateID, stripID, srcActionIndex, dstActionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void Add(AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + uint32_t behaviorIndex = 0; + auto* behaviorIndexAmf = arguments->FindValue("BehaviorIndex"); + + if (!behaviorIndexAmf) return; + + behaviorIndex = static_cast(behaviorIndexAmf->GetDoubleValue()); + + // modelComponent->AddBehavior(behaviorID, behaviorIndex, modelOwner); + + // SendBehaviorListToClient(); +} + +void RemoveActions(AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* actionIndexAmf = arguments->FindValue("actionIndex"); + if (!actionIndexAmf) return; + + uint32_t actionIndex = static_cast(actionIndexAmf->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->RemoveAction(stateID, stripID, actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void Rename(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* nameAmf = arguments->FindValue("Name"); + if (!nameAmf) return; + + auto name = nameAmf->GetStringValue(); + + // modelComponent->Rename(behaviorID, name); + + SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); + + // RequestUpdatedID(behaviorID); +} + +// TODO This is also supposed to serialize the state of the behaviors in progress but those aren't implemented yet +void SendBehaviorBlocksToClient(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // auto modelBehavior = modelComponent->FindBehavior(behaviorID); + + // if (!modelBehavior) return; + + // modelBehavior->VerifyStates(); + + // auto states = modelBehavior->GetBehaviorStates(); + + // // Begin serialization. + + // /** + // * for each state + // * strip id + // * ui info + // * x + // * y + // * actions + // * action1 + // * action2 + // * ... + // * behaviorID of strip + // * objectID of strip + // */ + // LWOOBJID targetObjectID = LWOOBJID_EMPTY; + // behaviorID = 0; + // AMFArrayValue behaviorInfo; + + // AMFArrayValue* stateSerialize = new AMFArrayValue(); + + // for (auto it = states.begin(); it != states.end(); it++) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of state %i!\n", it->first); + // AMFArrayValue* state = new AMFArrayValue(); + + // AMFDoubleValue* stateAsDouble = new AMFDoubleValue(); + // stateAsDouble->SetDoubleValue(it->first); + // state->InsertValue("id", stateAsDouble); + + // AMFArrayValue* strips = new AMFArrayValue(); + // auto stripsInState = it->second->GetStrips(); + // for (auto strip = stripsInState.begin(); strip != stripsInState.end(); strip++) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of strip %i!\n", strip->first); + // AMFArrayValue* thisStrip = new AMFArrayValue(); + + // AMFDoubleValue* stripID = new AMFDoubleValue(); + // stripID->SetDoubleValue(strip->first); + // thisStrip->InsertValue("id", stripID); + + // AMFArrayValue* uiArray = new AMFArrayValue(); + // AMFDoubleValue* yPosition = new AMFDoubleValue(); + // yPosition->SetDoubleValue(strip->second->GetYPosition()); + // uiArray->InsertValue("y", yPosition); + + // AMFDoubleValue* xPosition = new AMFDoubleValue(); + // xPosition->SetDoubleValue(strip->second->GetXPosition()); + // uiArray->InsertValue("x", xPosition); + + // thisStrip->InsertValue("ui", uiArray); + // targetObjectID = modelComponent->GetParent()->GetObjectID(); + // behaviorID = modelBehavior->GetBehaviorID(); + + // AMFArrayValue* stripSerialize = new AMFArrayValue(); + // for (auto behaviorAction : strip->second->GetActions()) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of action %s!\n", behaviorAction->actionName.c_str()); + // AMFArrayValue* thisAction = new AMFArrayValue(); + + // AMFStringValue* actionName = new AMFStringValue(); + // actionName->SetStringValue(behaviorAction->actionName); + // thisAction->InsertValue("Type", actionName); + + // if (behaviorAction->parameterValueString != "") + // { + // AMFStringValue* valueAsString = new AMFStringValue(); + // valueAsString->SetStringValue(behaviorAction->parameterValueString); + // thisAction->InsertValue(behaviorAction->parameterName, valueAsString); + // } + // else if (behaviorAction->parameterValueDouble != 0.0) + // { + // AMFDoubleValue* valueAsDouble = new AMFDoubleValue(); + // valueAsDouble->SetDoubleValue(behaviorAction->parameterValueDouble); + // thisAction->InsertValue(behaviorAction->parameterName, valueAsDouble); + // } + // stripSerialize->PushBackValue(thisAction); + // } + // thisStrip->InsertValue("actions", stripSerialize); + // strips->PushBackValue(thisStrip); + // } + // state->InsertValue("strips", strips); + // stateSerialize->PushBackValue(state); + // } + // behaviorInfo.InsertValue("states", stateSerialize); + + // AMFStringValue* objectidAsString = new AMFStringValue(); + // objectidAsString->SetStringValue(std::to_string(targetObjectID)); + // behaviorInfo.InsertValue("objectID", objectidAsString); + + // AMFStringValue* behaviorIDAsString = new AMFStringValue(); + // behaviorIDAsString->SetStringValue(std::to_string(behaviorID)); + // behaviorInfo.InsertValue("BehaviorID", behaviorIDAsString); + + // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorBlocks", &behaviorInfo); +} + +void UpdateAction(AMFArrayValue* arguments) { + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + auto* actionAsArray = arguments->FindValue("action"); + if (!actionAsArray) return; + for (auto& typeValueMap : actionAsArray->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* actionIndexValue = arguments->FindValue("actionIndex"); + if (!actionIndexValue) return; + + uint32_t actionIndex = static_cast(actionIndexValue->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->UpdateAction(stateID, stripID, type, valueParameterName, valueParameterString, valueParameterDouble, "", actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MoveToInventory(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + // This closes the UI menu should it be open while the player is removing behaviors + AMFArrayValue args; + + AMFFalseValue* stateToPop = new AMFFalseValue(); + args.InsertValue("visible", stateToPop); + + GameMessages::SendUIMessageServerToSingleClient(modelOwner, modelOwner->GetParentUser()->GetSystemAddress(), "ToggleBehaviorEditor", &args); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* behaviorIndexValue = arguments->FindValue("BehaviorIndex"); + if (!behaviorIndexValue) return; + + uint32_t behaviorIndex = static_cast(behaviorIndexValue->GetDoubleValue()); + + // modelComponent->MoveBehaviorToInventory(behaviorID, behaviorIndex, modelOwner); + + SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); +} + +void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) { + if (!modelEntity || !modelOwner || !arguments) return; + auto* modelComponent = modelEntity->GetComponent(); + + if (!modelComponent) return; + + if (command == "sendBehaviorListToClient") + SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); + else if (command == "modelTypeChanged") + ModelTypeChanged(arguments, modelComponent); + else if (command == "toggleExecutionUpdates") + ToggleExecutionUpdates(); + else if (command == "addStrip") + AddStrip(arguments); + else if (command == "removeStrip") + RemoveStrip(arguments); + else if (command == "mergeStrips") + MergeStrips(arguments); + else if (command == "splitStrip") + SplitStrip(arguments); + else if (command == "updateStripUI") + UpdateStripUI(arguments); + else if (command == "addAction") + AddAction(arguments); + else if (command == "migrateActions") + MigrateActions(arguments); + else if (command == "rearrangeStrip") + RearrangeStrip(arguments); + else if (command == "add") + Add(arguments); + else if (command == "removeActions") + RemoveActions(arguments); + else if (command == "rename") + Rename(modelEntity, sysAddr, modelOwner, arguments); + else if (command == "sendBehaviorBlocksToClient") + SendBehaviorBlocksToClient(modelComponent, sysAddr, modelOwner, arguments); + else if (command == "moveToInventory") + MoveToInventory(modelComponent, sysAddr, modelOwner, arguments); + else if (command == "updateAction") + UpdateAction(arguments); + else + Game::logger->Log("ControlBehaviors", "Unknown behavior command (%s)\n", command.c_str()); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.h b/dGame/dPropertyBehaviors/ControlBehaviors.h index 7c24da68..d487f929 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.h +++ b/dGame/dPropertyBehaviors/ControlBehaviors.h @@ -9,6 +9,7 @@ class Entity; class AMFArrayValue; +class ModelComponent; namespace ControlBehaviors { /** @@ -21,15 +22,6 @@ namespace ControlBehaviors { * @param modelOwner The owner of the model which sent this command */ void ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner); - - /** - * @brief Helper function to send the behavior list to the client - * - * @param modelEntity The model that sent this command - * @param sysAddr The SystemAddress to respond to - * @param modelOwner The owner of the model which sent this command - */ - void SendBehaviorListToClient(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner); }; #endif //!__CONTROLBEHAVIORS__H__ diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 0c848bf4..2c453a2e 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -1,5 +1,4 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" - "dLocale.cpp" "GameConfig.cpp" "GUID.cpp" "Loot.cpp" diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index 1f719433..c23ac53b 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -154,7 +154,7 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat case PreconditionType::MissionComplete: mission = missionComponent->GetMission(value); - return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE; + return mission == nullptr ? false : mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE; case PreconditionType::PetDeployed: return false; // TODO case PreconditionType::HasFlag: @@ -277,11 +277,6 @@ bool PreconditionExpression::Check(Entity* player, bool evaluateCosts) const { return true; } - if (player->GetGMLevel() >= 9) // Developers can skip this for testing - { - return true; - } - const auto a = Preconditions::Check(player, condition, evaluateCosts); if (!a) { diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 8f4ac1a8..89f11346 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -64,6 +64,8 @@ #include "ScriptedActivityComponent.h" #include "LevelProgressionComponent.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" +#include "dConfig.h" void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { std::string chatCommand; @@ -210,22 +212,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (chatCommand == "skip-ags") { - auto* missionComponent = entity->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(479)) { - missionComponent->CompleteMission(479); - } - } - - if (chatCommand == "skip-sg") { - auto* missionComponent = entity->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(229)) { - missionComponent->CompleteMission(229); - } - } - if (chatCommand == "fix-stats") { // Reset skill component and buff component auto* skillComponent = entity->GetComponent(); @@ -248,7 +234,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown("./vanity/CREDITS.md") : VanityUtilities::ParseMarkdown("./vanity/INFO.md"); + const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); { AMFArrayValue args; @@ -1263,6 +1249,57 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit EntityManager::Instance()->ConstructEntity(newEntity); } + if (chatCommand == "spawngroup" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() >= 3) { + auto controllablePhysicsComponent = entity->GetComponent(); + if (!controllablePhysicsComponent) return; + + LOT lot{}; + uint32_t numberToSpawn{}; + float radiusToSpawnWithin{}; + + if (!GeneralUtils::TryParse(args[0], lot)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + if (!GeneralUtils::TryParse(args[1], numberToSpawn) && numberToSpawn > 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); + return; + } + + // Must spawn within a radius of at least 0.0f + if (!GeneralUtils::TryParse(args[2], radiusToSpawnWithin) && radiusToSpawnWithin < 0.0f) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); + return; + } + + EntityInfo info; + info.lot = lot; + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + auto playerPosition = controllablePhysicsComponent->GetPosition(); + while (numberToSpawn > 0) { + auto randomAngle = GeneralUtils::GenerateRandomNumber(0.0f, 2 * PI); + auto randomRadius = GeneralUtils::GenerateRandomNumber(0.0f, radiusToSpawnWithin); + + // Set the position to the generated random position plus the player position. This will + // spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate. + info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius); + info.rot = NiQuaternion(); + + auto newEntity = EntityManager::Instance()->CreateEntity(info); + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + EntityManager::Instance()->ConstructEntity(newEntity); + numberToSpawn--; + } + } + if ((chatCommand == "giveuscore") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { int32_t uscore; @@ -1714,6 +1751,21 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + Game::config->ReloadConfig(); + VanityUtilities::SpawnVanity(); + dpWorld::Instance().Reload(); + auto entities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY); + for (auto entity : entities) { + auto* scriptedActivityComponent = entity->GetComponent(); + if (!scriptedActivityComponent) continue; + + scriptedActivityComponent->ReloadConfig(); + } + Game::server->UpdateBandwidthLimit(); + ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); + } + if (chatCommand == "rollloot" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && args.size() >= 3) { uint32_t lootMatrixIndex = 0; uint32_t targetLot = 0; diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 733d94c4..3a1259a2 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -13,6 +13,7 @@ #include "tinyxml2.h" #include "Game.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include @@ -27,7 +28,7 @@ void VanityUtilities::SpawnVanity() { const uint32_t zoneID = Game::server->GetZoneID(); - ParseXML("./vanity/NPC.xml"); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); // Loop through all parties for (const auto& party : m_Parties) { @@ -131,7 +132,7 @@ void VanityUtilities::SpawnVanity() { info.spawnerID = EntityManager::Instance()->GetZoneControlEntity()->GetObjectID(); info.settings = { new LDFData(u"hasCustomText", true), - new LDFData(u"customText", ParseMarkdown("./vanity/TESTAMENT.md")) }; + new LDFData(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())) }; auto* entity = EntityManager::Instance()->CreateEntity(info); diff --git a/dGame/dUtilities/dLocale.cpp b/dGame/dUtilities/dLocale.cpp deleted file mode 100644 index 65ef3a58..00000000 --- a/dGame/dUtilities/dLocale.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "dLocale.h" - -#include -#include -#include -#include -#include - -#include "tinyxml2.h" -#include "Game.h" -#include "dConfig.h" - -dLocale::dLocale() { - if (Game::config->GetValue("locale_enabled") != "1") { - return; - } - - std::ifstream file(m_LocalePath); - - if (!file.good()) { - return; - } - - std::stringstream data; - data << file.rdbuf(); - - if (data.str().empty()) { - return; - } - - auto* doc = new tinyxml2::XMLDocument(); - - if (doc == nullptr) { - return; - } - - if (doc->Parse(data.str().c_str(), data.str().size()) != 0) { - return; - } - - std::hash hash; - - auto* localization = doc->FirstChildElement("localization"); - auto* phrases = localization->FirstChildElement("phrases"); - - auto* phrase = phrases->FirstChildElement("phrase"); - - while (phrase != nullptr) { - // Add the phrase hash to the vector - m_Phrases.push_back(hash(phrase->Attribute("id"))); - phrase = phrase->NextSiblingElement("phrase"); - } - - file.close(); - - delete doc; -} - -dLocale::~dLocale() = default; - -std::string dLocale::GetTemplate(const std::string& phraseID) { - return "%[" + phraseID + "]"; -} - -bool dLocale::HasPhrase(const std::string& phraseID) { - if (Game::config->GetValue("locale_enabled") != "1") { - return true; - } - - // Compute the hash and see if it's in the vector - std::hash hash; - std::size_t hashValue = hash(phraseID); - return std::find(m_Phrases.begin(), m_Phrases.end(), hashValue) != m_Phrases.end(); -} - -/*std::string dLocale::GetPhrase(const std::string& phraseID) { - if (m_Phrases.find(phraseID) == m_Phrases.end()) { - return ""; - } - return m_Phrases[phraseID]; -}*/ diff --git a/dGame/dUtilities/dLocale.h b/dGame/dUtilities/dLocale.h deleted file mode 100644 index db5d2a4f..00000000 --- a/dGame/dUtilities/dLocale.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include -#include -#include -#include - -class dLocale { -public: - dLocale(); - ~dLocale(); - static std::string GetTemplate(const std::string& phraseID); - bool HasPhrase(const std::string& phraseID); - //std::string GetPhrase(const std::string& phraseID); - -private: - std::string m_LocalePath = "./locale/locale.xml"; - std::string m_Locale = "en_US"; // TODO: add to config - std::vector m_Phrases; -}; diff --git a/dMasterServer/InstanceManager.cpp b/dMasterServer/InstanceManager.cpp index 0c605ed6..83378dbb 100644 --- a/dMasterServer/InstanceManager.cpp +++ b/dMasterServer/InstanceManager.cpp @@ -10,6 +10,7 @@ #include "dMessageIdentifiers.h" #include "MasterPackets.h" #include "PacketUtils.h" +#include "BinaryPathFinder.h" InstanceManager::InstanceManager(dLogger* logger, const std::string& externalIP) { mLogger = logger; @@ -48,13 +49,13 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW //Start the actual process: #ifdef _WIN32 - std::string cmd = "start ./WorldServer.exe -zone "; + std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; #else std::string cmd; if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) { - cmd = "sudo ./WorldServer -zone "; + cmd = "sudo " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } else { - cmd = "./WorldServer -zone "; + cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } #endif @@ -300,10 +301,10 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon instance = new Instance(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, maxPlayers, maxPlayers, true, password); //Start the actual process: - std::string cmd = "start ./WorldServer.exe -zone "; + std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; #ifndef _WIN32 - cmd = "./WorldServer -zone "; + cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; #endif cmd.append(std::to_string(mapID)); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 061a91c7..4acb9b26 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -26,6 +26,7 @@ #include "dLogger.h" #include "dServer.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -39,6 +40,7 @@ #include "ObjectIDManager.h" #include "PacketUtils.h" #include "dMessageIdentifiers.h" +#include "FdbToSqlite.h" namespace Game { dLogger* logger; @@ -81,17 +83,15 @@ int main(int argc, char** argv) { Game::logger->Log("MasterServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); Game::logger->Log("MasterServer", "Compiled on: %s", __TIMESTAMP__); - //Read our config: - dConfig config("masterconfig.ini"); - Game::config = &config; - Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console")))); - Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); + Game::config = new dConfig("masterconfig.ini"); + Game::logger->SetLogToConsole(bool(std::stoi(Game::config->GetValue("log_to_console")))); + Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1"); //Connect to the MySQL Database - std::string mysql_host = config.GetValue("mysql_host"); - std::string mysql_database = config.GetValue("mysql_database"); - std::string mysql_username = config.GetValue("mysql_username"); - std::string mysql_password = config.GetValue("mysql_password"); + std::string mysql_host = Game::config->GetValue("mysql_host"); + std::string mysql_database = Game::config->GetValue("mysql_database"); + std::string mysql_username = Game::config->GetValue("mysql_username"); + std::string mysql_password = Game::config->GetValue("mysql_password"); try { Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); @@ -102,9 +102,14 @@ int main(int argc, char** argv) { } try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = Game::config->GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("MasterServer", "Got an error while setting up assets: %s", ex.what()); @@ -122,23 +127,12 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - Game::logger->Log("WorldServer", "Found cdclient.fdb. Clearing cdserver migration_history then copying and converting to sqlite."); - auto stmt = Database::CreatePreppedStmt(R"#(DELETE FROM migration_history WHERE name LIKE "%cdserver%";)#"); - stmt->executeUpdate(); - delete stmt; + Game::logger->Log("WorldServer", "Found cdclient.fdb. Converting to SQLite"); - std::string res = "python3 ../thirdparty/docker-utils/utils/fdb_to_sqlite.py " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); - - int result = system(res.c_str()); - if (result != 0) { + if (FdbToSqlite::Convert(Game::assetManager->GetResPath().string()).ConvertDatabase() == false) { Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite"); return EXIT_FAILURE; } - - if (std::rename("./cdclient.sqlite", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()) != 0) { - Game::logger->Log("MasterServer", "Failed to move cdclient file."); - return EXIT_FAILURE; - } } //Connect to CDClient @@ -219,16 +213,16 @@ int main(int argc, char** argv) { int maxClients = 999; int ourPort = 1000; - if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); - if (config.GetValue("port") != "") ourPort = std::stoi(config.GetValue("port")); + if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); + if (Game::config->GetValue("port") != "") ourPort = std::stoi(Game::config->GetValue("port")); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config); //Query for the database for a server labeled "master" auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); auto* result = masterLookupStatement->executeQuery(); - auto master_server_ip = config.GetValue("master_ip"); + auto master_server_ip = Game::config->GetValue("master_ip"); if (master_server_ip == "") { master_server_ip = Game::server->GetIP(); @@ -256,7 +250,7 @@ int main(int argc, char** argv) { Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); //Depending on the config, start up servers: - if (config.GetValue("prestart_servers") != "" && config.GetValue("prestart_servers") == "1") { + if (Game::config->GetValue("prestart_servers") != "" && Game::config->GetValue("prestart_servers") == "1") { StartChatServer(); Game::im->GetInstance(0, false, 0)->SetIsReady(true); @@ -363,7 +357,7 @@ int main(int argc, char** argv) { dLogger* SetupLogger() { std::string logPath = - "./logs/MasterServer_" + std::to_string(time(nullptr)) + ".log"; + (BinaryPathFinder::GetBinaryDir() / ("logs/MasterServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG @@ -738,28 +732,28 @@ void HandlePacket(Packet* packet) { void StartChatServer() { #ifdef __APPLE__ //macOS doesn't need sudo to run on ports < 1024 - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); #elif _WIN32 - system("start ./ChatServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) { - system("sudo ./ChatServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } else { - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } #endif } void StartAuthServer() { #ifdef __APPLE__ - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); #elif _WIN32 - system("start ./AuthServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) { - system("sudo ./AuthServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } else { - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } #endif } @@ -839,6 +833,7 @@ void ShutdownSequence() { int FinalizeShutdown() { //Delete our objects here: Database::Destroy("MasterServer"); + if (Game::config) delete Game::config; if (Game::im) delete Game::im; if (Game::server) delete Game::server; if (Game::logger) delete Game::logger; diff --git a/dMasterServer/ObjectIDManager.cpp b/dMasterServer/ObjectIDManager.cpp index 0f3b98c9..83dde8dd 100644 --- a/dMasterServer/ObjectIDManager.cpp +++ b/dMasterServer/ObjectIDManager.cpp @@ -51,13 +51,13 @@ uint32_t ObjectIDManager::GeneratePersistentID(void) { uint32_t toReturn = ++this->currentPersistentID; // So we peroidically save our ObjID to the database: - if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT! + // if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT! sql::PreparedStatement* stmt = Database::CreatePreppedStmt( "UPDATE object_id_tracker SET last_object_id=?"); stmt->setUInt(1, toReturn); stmt->execute(); delete stmt; - } + // } return toReturn; } diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index e5ba0129..fdba4a2d 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -7,6 +7,7 @@ #include "dPlatforms.h" #include "NiPoint3.h" #include "BinaryIO.h" +#include "BinaryPathFinder.h" #include "dZoneManager.h" @@ -43,7 +44,7 @@ dNavMesh::~dNavMesh() { void dNavMesh::LoadNavmesh() { - std::string path = "./navmeshes/" + std::to_string(m_ZoneId) + ".bin"; + std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string(); if (!BinaryIO::DoesFileExist(path)) { return; diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index c46b156c..481667b8 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -2,6 +2,7 @@ #include "dServer.h" #include "dNetCommon.h" #include "dLogger.h" +#include "dConfig.h" #include "RakNetworkFactory.h" #include "MessageIdentifiers.h" @@ -35,7 +36,7 @@ public: } } ReceiveDownloadCompleteCB; -dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, unsigned int zoneID) { +dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, dConfig* config, unsigned int zoneID) { mIP = ip; mPort = port; mZoneID = zoneID; @@ -50,6 +51,7 @@ dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnect mNetIDManager = nullptr; mReplicaManager = nullptr; mServerType = serverType; + mConfig = config; //Attempt to start our server here: mIsOkay = Startup(); @@ -181,7 +183,7 @@ bool dServer::Startup() { if (mIsInternal) { mPeer->SetIncomingPassword("3.25 DARKFLAME1", 15); } else { - //mPeer->SetPerConnectionOutgoingBandwidthLimit(800000); //100Kb/s + UpdateBandwidthLimit(); mPeer->SetIncomingPassword("3.25 ND1", 8); } @@ -191,6 +193,11 @@ bool dServer::Startup() { return true; } +void dServer::UpdateBandwidthLimit() { + auto newBandwidth = mConfig->GetValue("maximum_outgoing_bandwidth"); + mPeer->SetPerConnectionOutgoingBandwidthLimit(!newBandwidth.empty() ? std::stoi(newBandwidth) : 0); +} + void dServer::Shutdown() { if (mPeer) { mPeer->Shutdown(1000); diff --git a/dNet/dServer.h b/dNet/dServer.h index 264932ee..0fbdecce 100644 --- a/dNet/dServer.h +++ b/dNet/dServer.h @@ -5,6 +5,7 @@ #include "NetworkIDManager.h" class dLogger; +class dConfig; enum class ServerType : uint32_t { Master, @@ -17,7 +18,7 @@ class dServer { public: // Default constructor should only used for testing! dServer() {}; - dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, unsigned int zoneID = 0); + dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, dConfig* config, unsigned int zoneID = 0); ~dServer(); Packet* ReceiveFromMaster(); @@ -42,6 +43,7 @@ public: const int GetInstanceID() const { return mInstanceID; } ReplicaManager* GetReplicaManager() { return mReplicaManager; } void UpdateReplica(); + void UpdateBandwidthLimit(); int GetPing(const SystemAddress& sysAddr) const; int GetLatestPing(const SystemAddress& sysAddr) const; @@ -58,6 +60,7 @@ private: private: dLogger* mLogger = nullptr; + dConfig* mConfig = nullptr; RakPeerInterface* mPeer = nullptr; ReplicaManager* mReplicaManager = nullptr; NetworkIDManager* mNetIDManager = nullptr; diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index 6e44ade9..b4fe385e 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -6,6 +6,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { NUM_CELLS = numCells; CELL_SIZE = cellSize; + m_DeleteGrid = true; //dumb method but i can't be bothered @@ -23,6 +24,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { } dpGrid::~dpGrid() { + if (!this->m_DeleteGrid) return; for (auto& x : m_Cells) { //x for (auto& y : x) { //y for (auto en : y) { diff --git a/dPhysics/dpGrid.h b/dPhysics/dpGrid.h index a10f165e..229e7449 100644 --- a/dPhysics/dpGrid.h +++ b/dPhysics/dpGrid.h @@ -23,6 +23,15 @@ public: void Update(float deltaTime); + /** + * Sets the delete grid parameter to value. When false, the grid will not clean up memory. + * + * @param value Whether or not to delete entities on deletion of the grid. + */ + void SetDeleteGrid(bool value) { this->m_DeleteGrid = value; }; + + std::vector>> GetCells() { return this->m_Cells; }; + private: void HandleEntity(dpEntity* entity, dpEntity* other); void HandleCell(int x, int z, float deltaTime); @@ -31,4 +40,5 @@ private: //cells on X, cells on Y for that X, then another vector that contains the entities within that cell. std::vector>> m_Cells; std::map m_GargantuanObjects; + bool m_DeleteGrid = true; }; diff --git a/dPhysics/dpWorld.cpp b/dPhysics/dpWorld.cpp index 510da518..70fbfa3a 100644 --- a/dPhysics/dpWorld.cpp +++ b/dPhysics/dpWorld.cpp @@ -9,7 +9,7 @@ #include "dLogger.h" #include "dConfig.h" -void dpWorld::Initialize(unsigned int zoneID) { +void dpWorld::Initialize(unsigned int zoneID, bool generateNewNavMesh) { phys_sp_tilecount = std::atoi(Game::config->GetValue("phys_sp_tilecount").c_str()); phys_sp_tilesize = std::atoi(Game::config->GetValue("phys_sp_tilesize").c_str()); @@ -21,13 +21,37 @@ void dpWorld::Initialize(unsigned int zoneID) { m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize); } - m_NavMesh = new dNavMesh(zoneID); + if (generateNewNavMesh) m_NavMesh = new dNavMesh(zoneID); Game::logger->Log("dpWorld", "Physics world initialized!"); + m_ZoneID = zoneID; +} + +void dpWorld::Reload() { + if (m_Grid) { + m_Grid->SetDeleteGrid(false); + auto oldGridCells = m_Grid->GetCells(); + delete m_Grid; + m_Grid = nullptr; + + Initialize(m_ZoneID, false); + for (auto column : oldGridCells) { + for (auto row : column) { + for (auto entity : row) { + AddEntity(entity); + } + } + } + Game::logger->Log("dpWorld", "Successfully reloaded physics world!"); + } else { + Game::logger->Log("dpWorld", "No physics world to reload!"); + } } dpWorld::~dpWorld() { if (m_Grid) { + // Triple check this is true + m_Grid->SetDeleteGrid(true); delete m_Grid; m_Grid = nullptr; } @@ -103,14 +127,14 @@ bool dpWorld::ShouldUseSP(unsigned int zoneID) { // Only large maps should be added as tiling likely makes little difference on small maps. switch (zoneID) { - case 1100: // Avant Gardens - case 1200: // Nimbus Station - case 1300: // Gnarled Forest - case 1400: // Forbidden Valley - case 1800: // Crux Prime - case 1900: // Nexus Tower - case 2000: // Ninjago - return true; + case 1100: // Avant Gardens + case 1200: // Nimbus Station + case 1300: // Gnarled Forest + case 1400: // Forbidden Valley + case 1800: // Crux Prime + case 1900: // Nexus Tower + case 2000: // Ninjago + return true; } return false; diff --git a/dPhysics/dpWorld.h b/dPhysics/dpWorld.h index 45e550cb..d48435d0 100644 --- a/dPhysics/dpWorld.h +++ b/dPhysics/dpWorld.h @@ -19,7 +19,8 @@ class dpGrid; class dpWorld : public Singleton { public: - void Initialize(unsigned int zoneID); + void Initialize(unsigned int zoneID, bool generateNewNavMesh = true); + void Reload(); ~dpWorld(); @@ -43,4 +44,5 @@ private: std::vector m_DynamicEntites; dNavMesh* m_NavMesh = nullptr; + uint32_t m_ZoneID = 0; }; diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 18d72311..36e85b11 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -124,7 +124,7 @@ void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerN auto* timer = new ActivityTimer{ timerName, updateInterval, stopTime }; activeTimers.push_back(timer); - Game::logger->Log("ActivityManager", "Starting timer '%s', %f, %f", timerName.c_str(), updateInterval, stopTime); + Game::logger->LogDebug("ActivityManager", "Starting timer '%s', %f, %f", timerName.c_str(), updateInterval, stopTime); self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval); } @@ -205,10 +205,10 @@ void ActivityManager::OnTimerDone(Entity* self, std::string timerName) { activeTimers.erase(std::remove(activeTimers.begin(), activeTimers.end(), timer), activeTimers.end()); delete timer; - Game::logger->Log("ActivityManager", "Executing timer '%s'", activityTimerName.c_str()); + Game::logger->LogDebug("ActivityManager", "Executing timer '%s'", activityTimerName.c_str()); OnActivityTimerDone(self, activityTimerName); } else { - Game::logger->Log("ActivityManager", "Updating timer '%s'", activityTimerName.c_str()); + Game::logger->LogDebug("ActivityManager", "Updating timer '%s'", activityTimerName.c_str()); OnActivityTimerUpdate(self, timer->name, timer->stopTime - timer->runTime, timer->runTime); self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval); } diff --git a/dScripts/ai/FV/FvNinjaGuard.cpp b/dScripts/ai/FV/FvNinjaGuard.cpp index 6a841ccf..58267999 100644 --- a/dScripts/ai/FV/FvNinjaGuard.cpp +++ b/dScripts/ai/FV/FvNinjaGuard.cpp @@ -19,12 +19,6 @@ void FvNinjaGuard::OnEmoteReceived(Entity* self, const int32_t emote, Entity* ta GameMessages::SendPlayAnimation(self, u"scared"); - auto* missionComponent = target->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(737)) { - missionComponent->ForceProgressTaskType(737, 5, 1, false); - } - if (self->GetLOT() == 7412) { auto* rightGuard = EntityManager::Instance()->GetEntity(m_RightGuard); @@ -39,4 +33,3 @@ void FvNinjaGuard::OnEmoteReceived(Entity* self, const int32_t emote, Entity* ta } } } - diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index d4e55cb5..acd38ad3 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -17,6 +17,7 @@ #include "Metrics.hpp" #include "PerformanceManager.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -47,7 +48,6 @@ #include "GameMessageHandler.h" #include "GameMessages.h" #include "Mail.h" -#include "dLocale.h" #include "TeamManager.h" #include "SkillComponent.h" #include "DestroyableComponent.h" @@ -56,6 +56,7 @@ #include "Player.h" #include "PropertyManagementComponent.h" #include "AssetManager.h" +#include "eBlueprintSaveResponseType.h" #include "ZCompression.h" @@ -66,7 +67,6 @@ namespace Game { dpWorld* physicsWorld; dChatFilter* chatFilter; dConfig* config; - dLocale* locale; std::mt19937 randomEngine; AssetManager* assetManager; @@ -146,9 +146,13 @@ int main(int argc, char** argv) { if (config.GetValue("disable_chat") == "1") chatDisabled = true; try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = config.GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("WorldServer", "Got an error while setting up assets: %s", ex.what()); @@ -204,7 +208,7 @@ int main(int argc, char** argv) { LootGenerator::Instance(); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf")))); - Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, zoneID); + Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, zoneID); //Connect to the chat server: int chatPort = 1501; @@ -217,7 +221,6 @@ int main(int argc, char** argv) { //Set up other things: Game::randomEngine = std::mt19937(time(0)); - Game::locale = new dLocale(); //Run it until server gets a kill message from Master: auto lastTime = std::chrono::high_resolution_clock::now(); @@ -247,7 +250,6 @@ int main(int argc, char** argv) { //Load our level: if (zoneID != 0) { dpWorld::Instance().Initialize(zoneID); - Game::physicsWorld = &dpWorld::Instance(); //just in case some old code references it dZoneManager::Instance()->Initialize(LWOZONEID(zoneID, instanceID, cloneID)); g_CloneID = cloneID; @@ -489,7 +491,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger(int zoneID, int instanceID) { - std::string logPath = "./logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG @@ -1077,9 +1079,9 @@ void HandlePacket(Packet* packet) { CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); - bitStream.Write(0); //always zero so that a check on the client passes - bitStream.Write(0); - bitStream.Write(1); + bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(1); bitStream.Write(blueprintID); bitStream.Write(lxfmlSize); @@ -1278,7 +1280,6 @@ void WorldShutdownSequence() { void FinalizeShutdown() { //Delete our objects here: - if (Game::physicsWorld) Game::physicsWorld = nullptr; if (Game::zoneManager) delete Game::zoneManager; Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), instanceID); diff --git a/docker/start_server.sh b/docker/start_server.sh old mode 100755 new mode 100644 index ca1a49a0..4fd6e2be --- a/docker/start_server.sh +++ b/docker/start_server.sh @@ -7,7 +7,7 @@ function symlink_client_files() { ln -s /client/client/res/chatplus_en_us.txt /app/res/chatplus_en_us.txt ln -s /client/client/res/names/ /app/res/names ln -s /client/client/res/CDServer.sqlite /app/res/CDServer.sqlite - ln -s /client/client/locale/locale.xml /app/locale/locale.xml + # need to create this file so the server knows the client is unpacked (see `dCommon/dClient/AssetManager.cpp`) touch /app/res/cdclient.fdb # need to iterate over entries in maps due to maps already being a directory with navmeshes/ in it diff --git a/docs/Commands.md b/docs/Commands.md index 95ec28f9..7a15bf37 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -14,8 +14,6 @@ Here is a summary of the commands available in-game. All commands are prefixed b |pvp|`/pvp`|Toggle your PVP flag.|| |resurrect|`/resurrect`|Resurrects the player.|| |requestmailcount|`/requestmailcount`|Sends notification with number of unread messages in the player's mailbox.|| -|skip-ags|`/skip-ags`|Skips the Avant Gardens Survival minigame mission, "Impress the Sentinel Faction".|| -|skip-sg|`/skip-sg`|Skips the Shooting Gallery minigame mission, "Monarch of the Sea".|| |who|`/who`|Displays in chat all players on the instance.|| ## Moderation Commands diff --git a/migrations/dlu/7_make_play_key_id_nullable.sql b/migrations/dlu/7_make_play_key_id_nullable.sql new file mode 100644 index 00000000..11239967 --- /dev/null +++ b/migrations/dlu/7_make_play_key_id_nullable.sql @@ -0,0 +1 @@ +ALTER TABLE accounts MODIFY play_key_id INT DEFAULT 0; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 847a6b7c..439ffe2f 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -25,3 +25,6 @@ dump_folder= # The location of the client # Either the folder with /res or with /client and /versions client_location= + +# The maximum outgoing bandwidth in bits +maximum_outgoing_bandwidth=80000 diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 68192b3f..ba7d4d1c 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -5,6 +5,9 @@ set(DGAMETEST_SOURCES add_subdirectory(dComponentsTests) list(APPEND DGAMETEST_SOURCES ${DCOMPONENTS_TESTS}) +add_subdirectory(dGameMessagesTests) +list(APPEND DGAMETEST_SOURCES ${DGAMEMESSAGES_TESTS}) + # Add the executable. Remember to add all tests above this! add_executable(dGameTests ${DGAMETEST_SOURCES}) diff --git a/tests/dGameTests/GameDependencies.cpp b/tests/dGameTests/GameDependencies.cpp index 8a572668..5ac3339a 100644 --- a/tests/dGameTests/GameDependencies.cpp +++ b/tests/dGameTests/GameDependencies.cpp @@ -7,7 +7,6 @@ namespace Game { dpWorld* physicsWorld; dChatFilter* chatFilter; dConfig* config; - dLocale* locale; std::mt19937 randomEngine; RakPeerInterface* chatServer; AssetManager* assetManager; diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h index 593ec0fc..ee52dec6 100644 --- a/tests/dGameTests/GameDependencies.h +++ b/tests/dGameTests/GameDependencies.h @@ -5,15 +5,18 @@ #include "dLogger.h" #include "dServer.h" #include "EntityManager.h" -class dZoneManager; -class AssetManager; #include +class dZoneManager; +class AssetManager; + class dServerMock : public dServer { + RakNet::BitStream* sentBitStream = nullptr; public: dServerMock() {}; ~dServerMock() {}; - void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override {}; + RakNet::BitStream* GetMostRecentBitStream() { return sentBitStream; }; + void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override { sentBitStream = bitStream; }; }; class GameDependenciesTest : public ::testing::Test { diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt new file mode 100644 index 00000000..2417d29c --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -0,0 +1,9 @@ +SET(DGAMEMESSAGES_TESTS + "GameMessageTests.cpp") + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM DGAMEMESSAGES_TESTS PREPEND "${thisFolderName}/") + +# Export to parent scope +set(DGAMEMESSAGES_TESTS ${DGAMEMESSAGES_TESTS} PARENT_SCOPE) diff --git a/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp new file mode 100644 index 00000000..3d8b2d04 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp @@ -0,0 +1,52 @@ +#include "GameMessages.h" +#include "GameDependencies.h" +#include + +class GameMessageTests : public GameDependenciesTest { + protected: + void SetUp() override { + SetUpDependencies(); + } + void TearDown() override { + TearDownDependencies(); + } +}; + +/** + * @brief Tests that the serialization struct BlueprintLoadItemResponse is serialized correctly + * + */ +TEST_F(GameMessageTests, SendBlueprintLoadItemResponse) { + GameMessages::SendBlueprintLoadItemResponse(UNASSIGNED_SYSTEM_ADDRESS, true, 515, 990); + auto* bitStream = static_cast(Game::server)->GetMostRecentBitStream(); + ASSERT_NE(bitStream, nullptr); + ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 200); + // First read in the packets' header + uint8_t rakNetPacketId{}; + uint16_t remoteConnectionType{}; + uint32_t packetId{}; + uint8_t always0{}; + + bitStream->Read(rakNetPacketId); + bitStream->Read(remoteConnectionType); + bitStream->Read(packetId); + bitStream->Read(always0); + ASSERT_EQ(rakNetPacketId, 0x53); + ASSERT_EQ(remoteConnectionType, 0x05); + ASSERT_EQ(packetId, 0x17); + ASSERT_EQ(always0, 0x00); + + // Next read in packet data + + uint8_t bSuccess{}; // unsigned bool + LWOOBJID previousId{}; + LWOOBJID newId{}; + bitStream->Read(bSuccess); + bitStream->Read(previousId); + bitStream->Read(newId); + ASSERT_EQ(bSuccess, static_cast(true)); + ASSERT_EQ(previousId, 515); + ASSERT_EQ(newId, 990); + + ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 0); +}