diff --git a/.gitmodules b/.gitmodules index 6fb56bde..33193447 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,12 +14,6 @@ path = thirdparty/mariadb-connector-cpp url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git ignore = dirty -[submodule "thirdparty/docker-utils"] - path = thirdparty/docker-utils - url = https://github.com/lcdr/utils.git -[submodule "thirdparty/LUnpack"] - path = thirdparty/LUnpack - url = https://github.com/Xiphoseer/LUnpack.git [submodule "thirdparty/AccountManager"] path = thirdparty/AccountManager url = https://github.com/DarkflameUniverse/AccountManager diff --git a/Docker.md b/Docker.md index 54d0ce19..b06bd0fe 100644 --- a/Docker.md +++ b/Docker.md @@ -4,7 +4,7 @@ - [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker) - [Docker Compose](https://docs.docker.com/compose/install/) (Included in Docker Desktop) -- LEGO® Universe packed Client. Check the main [README](./README.md) for details on this. +- LEGO® Universe Client. Check the main [README](./README.md) for details on this. ## Run server inside Docker diff --git a/Docker_Windows.md b/Docker_Windows.md index 1cc633cc..984bbe57 100644 --- a/Docker_Windows.md +++ b/Docker_Windows.md @@ -25,7 +25,7 @@ 11. Once the command has completed (you can see you path again and can enter commands), close the window. 12. Inside the downloaded folder, copy `.env.example` and name the copy `.env` 13. Open `.env` with Notepad by right-clicking it and selecting _Open With_ -> _More apps_ -> _Notepad_. -14. Change the text after `CLIENT_PATH=` to the location of your client. The folder you are pointing to must contain a folder called `client` which should contain the client files. +14. Change the text after `CLIENT_PATH=` to the location of your client. This folder must contain either a folder `client` or `legouniverse.exe`. > If you need the extra performance, place the client files in `\\wsl$\\...` to avoid working across file systems, see [Docker Best Practices](https://docs.docker.com/desktop/windows/wsl/#best-practices) and [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/filesystems#file-storage-and-performance-across-file-systems). 15. Optionally, you can change the number after `BUILD_THREADS=` to the number of cores / threads your processor has. If your computer crashes while building, you can try to reduce this value. diff --git a/dCommon/FdbToSqlite.cpp b/dCommon/FdbToSqlite.cpp index 80251e89..e05286a9 100644 --- a/dCommon/FdbToSqlite.cpp +++ b/dCommon/FdbToSqlite.cpp @@ -10,6 +10,7 @@ #include "GeneralUtils.h" #include "Game.h" #include "dLogger.h" +#include "AssetManager.h" #include "eSqliteDataType.h" @@ -23,26 +24,23 @@ std::map FdbToSqlite::Convert::m_SqliteType = { { eSqliteDataType::TEXT_8, "text_8"} }; -FdbToSqlite::Convert::Convert(std::string basePath, std::string binaryOutPath) { - this->m_BasePath = basePath; +FdbToSqlite::Convert::Convert(std::string binaryOutPath) { this->m_BinaryOutPath = binaryOutPath; - m_Fdb.open(m_BasePath + "/cdclient.fdb", std::ios::binary); } -FdbToSqlite::Convert::~Convert() { - this->m_Fdb.close(); -} - -bool FdbToSqlite::Convert::ConvertDatabase() { +bool FdbToSqlite::Convert::ConvertDatabase(AssetMemoryBuffer& buffer) { if (m_ConversionStarted) return false; + + std::istream cdClientBuffer(&buffer); + this->m_ConversionStarted = true; try { CDClientDatabase::Connect(m_BinaryOutPath + "/CDServer.sqlite"); CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); - int32_t numberOfTables = ReadInt32(); - ReadTables(numberOfTables); + int32_t numberOfTables = ReadInt32(cdClientBuffer); + ReadTables(numberOfTables, cdClientBuffer); CDClientDatabase::ExecuteQuery("COMMIT;"); } catch (CppSQLite3Exception& e) { @@ -53,130 +51,130 @@ bool FdbToSqlite::Convert::ConvertDatabase() { return true; } -int32_t FdbToSqlite::Convert::ReadInt32() { +int32_t FdbToSqlite::Convert::ReadInt32(std::istream& cdClientBuffer) { int32_t nextInt{}; - BinaryIO::BinaryRead(m_Fdb, nextInt); + BinaryIO::BinaryRead(cdClientBuffer, nextInt); return nextInt; } -int64_t FdbToSqlite::Convert::ReadInt64() { - int32_t prevPosition = SeekPointer(); +int64_t FdbToSqlite::Convert::ReadInt64(std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); int64_t value{}; - BinaryIO::BinaryRead(m_Fdb, value); + BinaryIO::BinaryRead(cdClientBuffer, value); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); return value; } -std::string FdbToSqlite::Convert::ReadString() { - int32_t prevPosition = SeekPointer(); +std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); - auto readString = BinaryIO::ReadString(m_Fdb); + auto readString = BinaryIO::ReadString(cdClientBuffer); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); return readString; } -int32_t FdbToSqlite::Convert::SeekPointer() { +int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) { int32_t position{}; - BinaryIO::BinaryRead(m_Fdb, position); - int32_t prevPosition = m_Fdb.tellg(); - m_Fdb.seekg(position); + BinaryIO::BinaryRead(cdClientBuffer, position); + int32_t prevPosition = cdClientBuffer.tellg(); + cdClientBuffer.seekg(position); return prevPosition; } -std::string FdbToSqlite::Convert::ReadColumnHeader() { - int32_t prevPosition = SeekPointer(); +std::string FdbToSqlite::Convert::ReadColumnHeader(std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); - int32_t numberOfColumns = ReadInt32(); - std::string tableName = ReadString(); + int32_t numberOfColumns = ReadInt32(cdClientBuffer); + std::string tableName = ReadString(cdClientBuffer); - auto columns = ReadColumns(numberOfColumns); + auto columns = ReadColumns(numberOfColumns, cdClientBuffer); std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");"; CDClientDatabase::ExecuteDML(newTable); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); return tableName; } -void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables) { - int32_t prevPosition = SeekPointer(); +void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables, std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); for (int32_t i = 0; i < numberOfTables; i++) { - auto columnHeader = ReadColumnHeader(); - ReadRowHeader(columnHeader); + auto columnHeader = ReadColumnHeader(cdClientBuffer); + ReadRowHeader(columnHeader, cdClientBuffer); } - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } -std::string FdbToSqlite::Convert::ReadColumns(int32_t& numberOfColumns) { +std::string FdbToSqlite::Convert::ReadColumns(int32_t& numberOfColumns, std::istream& cdClientBuffer) { std::stringstream columnsToCreate; - int32_t prevPosition = SeekPointer(); + int32_t prevPosition = SeekPointer(cdClientBuffer); std::string name{}; eSqliteDataType dataType{}; for (int32_t i = 0; i < numberOfColumns; i++) { if (i != 0) columnsToCreate << ", "; - dataType = static_cast(ReadInt32()); - name = ReadString(); + dataType = static_cast(ReadInt32(cdClientBuffer)); + name = ReadString(cdClientBuffer); columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::m_SqliteType[dataType]; } - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); return columnsToCreate.str(); } -void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName) { - int32_t prevPosition = SeekPointer(); +void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName, std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); - int32_t numberOfAllocatedRows = ReadInt32(); + int32_t numberOfAllocatedRows = ReadInt32(cdClientBuffer); if (numberOfAllocatedRows != 0) assert((numberOfAllocatedRows & (numberOfAllocatedRows - 1)) == 0); // assert power of 2 allocation size - ReadRows(numberOfAllocatedRows, tableName); + ReadRows(numberOfAllocatedRows, tableName, cdClientBuffer); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } -void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName) { - int32_t prevPosition = SeekPointer(); +void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName, std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); int32_t rowid = 0; for (int32_t row = 0; row < numberOfAllocatedRows; row++) { - int32_t rowPointer = ReadInt32(); + int32_t rowPointer = ReadInt32(cdClientBuffer); if (rowPointer == -1) rowid++; - else ReadRow(rowPointer, tableName); + else ReadRow(rowPointer, tableName, cdClientBuffer); } - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } -void FdbToSqlite::Convert::ReadRow(int32_t& position, std::string& tableName) { - int32_t prevPosition = m_Fdb.tellg(); - m_Fdb.seekg(position); +void FdbToSqlite::Convert::ReadRow(int32_t& position, std::string& tableName, std::istream& cdClientBuffer) { + int32_t prevPosition = cdClientBuffer.tellg(); + cdClientBuffer.seekg(position); while (true) { - ReadRowInfo(tableName); - int32_t linked = ReadInt32(); + ReadRowInfo(tableName, cdClientBuffer); + int32_t linked = ReadInt32(cdClientBuffer); if (linked == -1) break; - m_Fdb.seekg(linked); + cdClientBuffer.seekg(linked); } - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } -void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName) { - int32_t prevPosition = SeekPointer(); +void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName, std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); - int32_t numberOfColumns = ReadInt32(); - ReadRowValues(numberOfColumns, tableName); + int32_t numberOfColumns = ReadInt32(cdClientBuffer); + ReadRowValues(numberOfColumns, tableName, cdClientBuffer); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } -void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName) { - int32_t prevPosition = SeekPointer(); +void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName, std::istream& cdClientBuffer) { + int32_t prevPosition = SeekPointer(cdClientBuffer); int32_t emptyValue{}; int32_t intValue{}; @@ -190,26 +188,26 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& 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())) { + switch (static_cast(ReadInt32(cdClientBuffer))) { case eSqliteDataType::NONE: - BinaryIO::BinaryRead(m_Fdb, emptyValue); + BinaryIO::BinaryRead(cdClientBuffer, emptyValue); assert(emptyValue == 0); insertedRow << "NULL"; break; case eSqliteDataType::INT32: - intValue = ReadInt32(); + intValue = ReadInt32(cdClientBuffer); insertedRow << intValue; break; case eSqliteDataType::REAL: - BinaryIO::BinaryRead(m_Fdb, floatValue); + BinaryIO::BinaryRead(cdClientBuffer, 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(); + stringValue = ReadString(cdClientBuffer); size_t position = 0; // Need to escape quote with a double of ". @@ -225,12 +223,12 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& } case eSqliteDataType::INT_BOOL: - BinaryIO::BinaryRead(m_Fdb, boolValue); + BinaryIO::BinaryRead(cdClientBuffer, boolValue); insertedRow << static_cast(boolValue); break; case eSqliteDataType::INT64: - int64Value = ReadInt64(); + int64Value = ReadInt64(cdClientBuffer); insertedRow << std::to_string(int64Value); break; @@ -245,5 +243,5 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& auto copiedString = insertedRow.str(); CDClientDatabase::ExecuteDML(copiedString); - m_Fdb.seekg(prevPosition); + cdClientBuffer.seekg(prevPosition); } diff --git a/dCommon/FdbToSqlite.h b/dCommon/FdbToSqlite.h index cfa5263b..7aad2703 100644 --- a/dCommon/FdbToSqlite.h +++ b/dCommon/FdbToSqlite.h @@ -7,6 +7,8 @@ #include #include +class AssetMemoryBuffer; + enum class eSqliteDataType : int32_t; namespace FdbToSqlite { @@ -18,33 +20,28 @@ namespace FdbToSqlite { * @param inputFile The file which ends in .fdb to be converted * @param binaryPath The base path where the file will be saved */ - Convert(std::string inputFile, std::string binaryOutPath); - - /** - * Destroy the convert object and close the fdb file. - */ - ~Convert(); + Convert(std::string binaryOutPath); /** * Converts the input file to sqlite. Calling multiple times is safe. * * @return true if the database was converted properly, false otherwise. */ - bool ConvertDatabase(); + bool ConvertDatabase(AssetMemoryBuffer& buffer); /** * @brief Reads a 32 bit int from the fdb file. * * @return The read value */ - int32_t ReadInt32(); + int32_t ReadInt32(std::istream& cdClientBuffer); /** * @brief Reads a 64 bit integer from the fdb file. * * @return The read value */ - int64_t ReadInt64(); + int64_t ReadInt64(std::istream& cdClientBuffer); /** * @brief Reads a string from the fdb file. @@ -53,28 +50,28 @@ namespace FdbToSqlite { * * TODO This needs to be translated to latin-1! */ - std::string ReadString(); + std::string ReadString(std::istream& cdClientBuffer); /** * @brief Seeks to a pointer position. * * @return The previous position before the seek */ - int32_t SeekPointer(); + int32_t SeekPointer(std::istream& cdClientBuffer); /** * @brief Reads a column header from the fdb file and creates the table in the database * * @return The table name */ - std::string ReadColumnHeader(); + std::string ReadColumnHeader(std::istream& cdClientBuffer); /** * @brief Read the tables from the fdb file. * * @param numberOfTables The number of tables to read */ - void ReadTables(int32_t& numberOfTables); + void ReadTables(int32_t& numberOfTables, std::istream& cdClientBuffer); /** * @brief Reads the columns from the fdb file. @@ -82,14 +79,14 @@ namespace FdbToSqlite { * @param numberOfColumns The number of columns to read * @return All columns of the table formatted for a sql query */ - std::string ReadColumns(int32_t& numberOfColumns); + std::string ReadColumns(int32_t& numberOfColumns, std::istream& cdClientBuffer); /** * @brief Reads the row header from the fdb file. * * @param tableName The tables name */ - void ReadRowHeader(std::string& tableName); + void ReadRowHeader(std::string& tableName, std::istream& cdClientBuffer); /** * @brief Read the rows from the fdb file., @@ -97,7 +94,7 @@ namespace FdbToSqlite { * @param numberOfAllocatedRows The number of rows that were allocated. Always a power of 2! * @param tableName The tables name. */ - void ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName); + void ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName, std::istream& cdClientBuffer); /** * @brief Reads a row from the fdb file. @@ -105,14 +102,14 @@ namespace FdbToSqlite { * @param position The position to seek in the fdb to * @param tableName The tables name */ - void ReadRow(int32_t& position, std::string& tableName); + void ReadRow(int32_t& position, std::string& tableName, std::istream& cdClientBuffer); /** * @brief Reads the row info from the fdb file. * * @param tableName The tables name */ - void ReadRowInfo(std::string& tableName); + void ReadRowInfo(std::string& tableName, std::istream& cdClientBuffer); /** * @brief Reads each row and its values from the fdb file and inserts them into the database @@ -120,7 +117,7 @@ namespace FdbToSqlite { * @param numberOfColumns The number of columns to read in * @param tableName The tables name */ - void ReadRowValues(int32_t& numberOfColumns, std::string& tableName); + void ReadRowValues(int32_t& numberOfColumns, std::string& tableName, std::istream& cdClientBuffer); private: /** @@ -132,11 +129,6 @@ namespace FdbToSqlite { * Base path of the folder containing the fdb file */ std::string m_BasePath{}; - - /** - * ifstream containing the fdb file - */ - std::ifstream m_Fdb{}; /** * Whether or not a conversion was started. If one was started, do not attempt to convert the file again. diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index dc3db0a1..ed86d719 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -45,9 +45,6 @@ AssetManager::AssetManager(const std::filesystem::path& path) { switch (m_AssetBundleType) { case eAssetBundleType::Packed: { this->LoadPackIndex(); - - this->UnpackRequiredAssets(); - break; } } @@ -160,31 +157,6 @@ AssetMemoryBuffer AssetManager::GetFileAsBuffer(const char* name) { return AssetMemoryBuffer(buf, len, success); } -void AssetManager::UnpackRequiredAssets() { - if (std::filesystem::exists(m_ResPath / "cdclient.fdb")) return; - - char* data; - uint32_t size; - - bool success = this->GetFile("cdclient.fdb", &data, &size); - - if (!success) { - Game::logger->Log("AssetManager", "Failed to extract required files from the packs."); - - delete data; - - return; - } - - std::ofstream cdclientOutput(m_ResPath / "cdclient.fdb", std::ios::out | std::ios::binary); - cdclientOutput.write(data, size); - cdclientOutput.close(); - - delete data; - - return; -} - uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) { size_t i, j; uint32_t crc, msb; diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index 73b24f18..bc7e5ff7 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -60,7 +60,6 @@ public: private: void LoadPackIndex(); - void UnpackRequiredAssets(); // Modified crc algorithm (mpeg2) // Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2 diff --git a/dCommon/dLogger.cpp b/dCommon/dLogger.cpp index d4e6a96e..7785a070 100644 --- a/dCommon/dLogger.cpp +++ b/dCommon/dLogger.cpp @@ -89,7 +89,7 @@ void dLogger::Log(const std::string& className, const std::string& message) { void dLogger::LogDebug(const char* className, const char* format, ...) { if (!m_logDebugStatements) return; va_list args; - std::string log = "[" + std::string(className) + "] " + std::string(format); + std::string log = "[" + std::string(className) + "] " + std::string(format) + "\n"; va_start(args, format); vLog(log.c_str(), args); va_end(args); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index c035a807..081496a4 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -23,9 +23,9 @@ #include "RebuildComponent.h" #include "DestroyableComponent.h" -BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) : Component(parent) { +BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { m_Target = LWOOBJID_EMPTY; - m_State = AiState::spawn; + SetAiState(AiState::spawn); m_Timer = 1.0f; m_StartPosition = parent->GetPosition(); m_MovementAI = nullptr; @@ -179,7 +179,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) { if (m_Disabled || m_Parent->GetIsDead()) return; - + bool stunnedThisFrame = m_Stunned; CalculateCombat(deltaTime); // Putting this here for now if (m_StartPosition == NiPoint3::ZERO) { @@ -192,7 +192,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) { return; } - if (m_Stunned) { + if (stunnedThisFrame) { m_MovementAI->Stop(); return; @@ -206,7 +206,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) { switch (m_State) { case AiState::spawn: Stun(2.0f); - m_State = AiState::idle; + SetAiState(AiState::idle); break; case AiState::idle: @@ -248,13 +248,13 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { if (m_Disabled) return; - if (m_StunTime > 0.0f) { + if (m_Stunned) { m_StunTime -= deltaTime; if (m_StunTime > 0.0f) { return; } - + m_StunTime = 0.0f; m_Stunned = false; } @@ -320,9 +320,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { m_Timer = 0; } - m_State = AiState::aggro; + SetAiState(AiState::aggro); } else { - m_State = AiState::idle; + SetAiState(AiState::idle); } for (auto i = 0; i < m_SkillEntries.size(); ++i) { @@ -348,7 +348,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { } if (m_Target == LWOOBJID_EMPTY) { - m_State = AiState::idle; + SetAiState(AiState::idle); return; } @@ -375,7 +375,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { m_MovementAI->Stop(); } - m_State = AiState::aggro; + SetAiState(AiState::aggro); m_Timer = 0; @@ -532,11 +532,20 @@ bool BaseCombatAIComponent::IsMech() { void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write1(); - outBitStream->Write(uint32_t(m_State)); - outBitStream->Write(m_Target); + outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate); + if (m_DirtyStateOrTarget || bIsInitialUpdate) { + outBitStream->Write(uint32_t(m_State)); + outBitStream->Write(m_Target); + m_DirtyStateOrTarget = false; + } } +void BaseCombatAIComponent::SetAiState(AiState newState) { + if (newState == this->m_State) return; + this->m_State = newState; + m_DirtyStateOrTarget = true; + EntityManager::Instance()->SerializeEntity(m_Parent); +} bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { auto* entity = EntityManager::Instance()->GetEntity(target); @@ -585,7 +594,10 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { } void BaseCombatAIComponent::SetTarget(const LWOOBJID target) { + if (this->m_Target == target) return; m_Target = target; + m_DirtyStateOrTarget = true; + EntityManager::Instance()->SerializeEntity(m_Parent); } Entity* BaseCombatAIComponent::GetTargetEntity() const { @@ -700,7 +712,7 @@ void BaseCombatAIComponent::OnAggro() { m_MovementAI->SetDestination(targetPos); - m_State = AiState::tether; + SetAiState(AiState::tether); } m_Timer += 0.5f; @@ -726,7 +738,7 @@ void BaseCombatAIComponent::OnTether() { m_MovementAI->SetDestination(m_StartPosition); - m_State = AiState::aggro; + SetAiState(AiState::aggro); } else { if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return; diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 70a88a42..1f17d562 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -243,6 +243,12 @@ private: */ std::vector GetTargetWithinAggroRange() const; + /** + * @brief Sets the AiState and prepares the entity for serialization next frame. + * + */ + void SetAiState(AiState newState); + /** * The current state of the AI */ @@ -374,6 +380,12 @@ private: */ bool m_DirtyThreat = false; + /** + * Whether or not the Component has dirty information and should update next frame + * + */ + bool m_DirtyStateOrTarget = false; + /** * Whether the current entity is a mech enemy, needed as mechs tether radius works differently * @return whether this entity is a mech diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index d832ef93..1b127c41 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -30,6 +30,7 @@ #include "PossessorComponent.h" #include "InventoryComponent.h" #include "dZoneManager.h" +#include "WorldConfig.h" DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { m_iArmor = 0; @@ -700,7 +701,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType auto* missions = owner->GetComponent(); if (missions != nullptr) { - if (team != nullptr && isEnemy) { + if (team != nullptr) { for (const auto memberId : team->members) { auto* member = EntityManager::Instance()->GetEntity(memberId); @@ -763,22 +764,17 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType if (dZoneManager::Instance()->GetPlayerLoseCoinOnDeath()) { auto* character = m_Parent->GetCharacter(); uint64_t coinsTotal = character->GetCoins(); + const uint64_t minCoinsToLose = dZoneManager::Instance()->GetWorldConfig()->coinsLostOnDeathMin; + if (coinsTotal >= minCoinsToLose) { + const uint64_t maxCoinsToLose = dZoneManager::Instance()->GetWorldConfig()->coinsLostOnDeathMax; + const float coinPercentageToLose = dZoneManager::Instance()->GetWorldConfig()->coinsLostOnDeathPercent; - if (coinsTotal > 0) { - uint64_t coinsToLoose = 1; + uint64_t coinsToLose = std::max(static_cast(coinsTotal * coinPercentageToLose), minCoinsToLose); + coinsToLose = std::min(maxCoinsToLose, coinsToLose); - if (coinsTotal >= 200) { - float hundreth = (coinsTotal / 100.0f); - coinsToLoose = static_cast(hundreth); - } + coinsTotal -= coinsToLose; - if (coinsToLoose > 10000) { - coinsToLoose = 10000; - } - - coinsTotal -= coinsToLoose; - - LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose); + LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLose, coinsToLose); character->SetCoins(coinsTotal, eLootSourceType::LOOT_SOURCE_PICKUP); } } diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 0b136a5c..d11331e7 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -59,7 +59,7 @@ std::map PetComponent::petFlags = { { 13067, 838 }, // Skeleton dragon }; -PetComponent::PetComponent(Entity* parent, uint32_t componentId) : Component(parent) { +PetComponent::PetComponent(Entity* parent, uint32_t componentId): Component(parent) { m_ComponentId = componentId; m_Interaction = LWOOBJID_EMPTY; @@ -118,21 +118,23 @@ void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpd outBitStream->Write(m_Owner); } - outBitStream->Write(tamed); - if (tamed) { - outBitStream->Write(m_ModerationStatus); + if (bIsInitialUpdate) { + outBitStream->Write(tamed); + if (tamed) { + outBitStream->Write(m_ModerationStatus); - const auto nameData = GeneralUtils::UTF8ToUTF16(m_Name); - const auto ownerNameData = GeneralUtils::UTF8ToUTF16(m_OwnerName); + const auto nameData = GeneralUtils::UTF8ToUTF16(m_Name); + const auto ownerNameData = GeneralUtils::UTF8ToUTF16(m_OwnerName); - outBitStream->Write(static_cast(nameData.size())); - for (const auto c : nameData) { - outBitStream->Write(c); - } + outBitStream->Write(static_cast(nameData.size())); + for (const auto c : nameData) { + outBitStream->Write(c); + } - outBitStream->Write(static_cast(ownerNameData.size())); - for (const auto c : ownerNameData) { - outBitStream->Write(c); + outBitStream->Write(static_cast(ownerNameData.size())); + for (const auto c : ownerNameData) { + outBitStream->Write(c); + } } } } @@ -916,16 +918,16 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { return; } - // If we are out of imagination despawn the pet. - if (playerDestroyableComponent->GetImagination() == 0) { - this->Deactivate(); - auto playerEntity = playerDestroyableComponent->GetParent(); - if (!playerEntity) return; + // If we are out of imagination despawn the pet. + if (playerDestroyableComponent->GetImagination() == 0) { + this->Deactivate(); + auto playerEntity = playerDestroyableComponent->GetParent(); + if (!playerEntity) return; - GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), UseItemResponse::NoImaginationForPet); - } + GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), UseItemResponse::NoImaginationForPet); + } - this->AddDrainImaginationTimer(item); + this->AddDrainImaginationTimer(item); }); } diff --git a/dGame/dComponents/PlayerForcedMovementComponent.cpp b/dGame/dComponents/PlayerForcedMovementComponent.cpp index 3bcad677..76993507 100644 --- a/dGame/dComponents/PlayerForcedMovementComponent.cpp +++ b/dGame/dComponents/PlayerForcedMovementComponent.cpp @@ -7,8 +7,8 @@ PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent) : C PlayerForcedMovementComponent::~PlayerForcedMovementComponent() {} void PlayerForcedMovementComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write(m_DirtyInfo); - if (m_DirtyInfo) { + outBitStream->Write(m_DirtyInfo || bIsInitialUpdate); + if (m_DirtyInfo || bIsInitialUpdate) { outBitStream->Write(m_PlayerOnRail); outBitStream->Write(m_ShowBillboard); } diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 61b41992..a1ae724a 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -18,6 +18,7 @@ #include "dZoneManager.h" #include "InventoryComponent.h" #include "Database.h" +#include "WorldConfig.h" Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { m_MissionComponent = missionComponent; @@ -435,9 +436,9 @@ void Mission::YieldRewards() { int32_t coinsToSend = 0; if (info->LegoScore > 0) { eLootSourceType lootSource = info->isMission ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT; - if (levelComponent->GetLevel() >= dZoneManager::Instance()->GetMaxLevel()) { + if (levelComponent->GetLevel() >= dZoneManager::Instance()->GetWorldConfig()->levelCap) { // Since the character is at the level cap we reward them with coins instead of UScore. - coinsToSend += info->LegoScore * dZoneManager::Instance()->GetLevelCapCurrencyConversion(); + coinsToSend += info->LegoScore * dZoneManager::Instance()->GetWorldConfig()->levelCapCurrencyConversion; } else { characterComponent->SetUScore(characterComponent->GetUScore() + info->LegoScore); GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info->LegoScore, lootSource); diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 7047972f..a4ac63e1 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -22,6 +22,8 @@ #include "MissionComponent.h" #include "ChatPackets.h" #include "Character.h" +#include "dZoneManager.h" +#include "WorldConfig.h" void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount) { @@ -191,7 +193,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd uint32_t itemID = static_cast(attachmentID); LOT itemLOT = 0; //Inventory::InventoryType itemType; - int mailCost = 25; + int mailCost = dZoneManager::Instance()->GetWorldConfig()->mailBaseFee; int stackSize = 0; auto inv = static_cast(entity->GetComponent(COMPONENT_TYPE_INVENTORY)); Item* item = nullptr; @@ -199,7 +201,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd if (itemID > 0 && attachmentCount > 0 && inv) { item = inv->FindItemById(attachmentID); if (item) { - mailCost += (item->GetInfo().baseValue * 0.1f); + mailCost += (item->GetInfo().baseValue * dZoneManager::Instance()->GetWorldConfig()->mailPercentAttachmentFee); stackSize = item->GetCount(); itemLOT = item->GetLot(); } else { diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 34ce59c7..82645797 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -156,22 +156,25 @@ int main(int argc, char** argv) { Game::logger->Log("MasterServer", "CDServer.sqlite is not located at resServer, but is located at res path. Copying file..."); std::filesystem::copy_file(Game::assetManager->GetResPath() / "CDServer.sqlite", BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite"); } else { - Game::logger->Log("WorldServer", + Game::logger->Log("MasterServer", "%s could not be found in resServer or res. Looking for %s to convert to sqlite.", (BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").c_str(), (Game::assetManager->GetResPath() / "cdclient.fdb").c_str()); - if (!fdbExists) { - Game::logger->Log("WorldServer", - "%s could not be opened. Please move cdclient.fdb to %s", - (Game::assetManager->GetResPath() / "cdclient.fdb").c_str(), - (Game::assetManager->GetResPath().c_str())); - return FinalizeShutdown(); + + AssetMemoryBuffer cdClientBuffer = Game::assetManager->GetFileAsBuffer("cdclient.fdb"); + if (!cdClientBuffer.m_Success) { + Game::logger->Log("MasterServer", "Failed to load %s", (Game::assetManager->GetResPath() / "cdclient.fdb").c_str()); + throw std::runtime_error("Aborting initialization due to missing cdclient.fdb."); } - Game::logger->Log("WorldServer", "Found cdclient.fdb. Converting to SQLite"); - if (FdbToSqlite::Convert(Game::assetManager->GetResPath().string(), (BinaryPathFinder::GetBinaryDir() / "resServer").string()).ConvertDatabase() == false) { + + Game::logger->Log("MasterServer", "Found %s. Converting to SQLite", (Game::assetManager->GetResPath() / "cdclient.fdb").c_str()); + Game::logger->Flush(); + + if (FdbToSqlite::Convert((BinaryPathFinder::GetBinaryDir() / "resServer").string()).ConvertDatabase(cdClientBuffer) == false) { Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite."); - return FinalizeShutdown(); + return EXIT_FAILURE; } + cdClientBuffer.close(); } } diff --git a/dZoneManager/WorldConfig.h b/dZoneManager/WorldConfig.h new file mode 100644 index 00000000..a98433a1 --- /dev/null +++ b/dZoneManager/WorldConfig.h @@ -0,0 +1,67 @@ +#ifndef __WORLDCONFIG__H__ +#define __WORLDCONFIG__H__ + +#include +#include + +struct WorldConfig { + int32_t worldConfigID{}; //! Primary key for WorlcConfig table + float peGravityValue{}; //! Unknown + float peBroadphaseWorldSize{}; //! Unknown + float peGameObjScaleFactor{}; //! Unknown + float characterRotationSpeed{}; //! The players' rotation speed + float characterWalkForwardSpeed{}; //! The players' walk forward speed + float characterWalkBackwardSpeed{}; //! The players' walk backwards speed + float characterWalkStrafeSpeed{}; //! The players' strafe speed + float characterWalkStrafeForwardSpeed{}; //! The players' walk strafe forward speed + float characterWalkStrafeBackwardSpeed{}; //! The players' walk strage backwards speed + float characterRunBackwardSpeed{}; //! The players' run backwards speed + float characterRunStrafeSpeed{}; //! The players' run strafe speed + float characterRunStrafeForwardSpeed{}; //! The players' run strafe forward speed + float characterRunStrafeBackwardSpeed{}; //! The players' run strage backwards speed + float globalCooldown{}; //! The global ability cooldown + float characterGroundedTime{}; //! Unknown + float characterGroundedSpeed{}; //! Unknown + float globalImmunityTime{}; //! Unknown + float characterMaxSlope{}; //! Unknown + float defaultRespawnTime{}; //! Unknown + float missionTooltipTimeout{}; + float vendorBuyMultiplier{}; //! The buy scalar for buying from vendors + float petFollowRadius{}; //! The players' pet follow radius + float characterEyeHeight{}; //! The players' eye height + float flightVerticalVelocity{}; //! Unknown + float flightAirspeed{}; //! Unknown + float flightFuelRatio{}; //! Unknown + float flightMaxAirspeed{}; //! Unknown + float fReputationPerVote{}; //! Unknown + int32_t propertyCloneLimit{}; //! Unknown + int32_t defaultHomespaceTemplate{}; //! Unknown + float coinsLostOnDeathPercent{}; //! The percentage of coins to lose on a player death + int32_t coinsLostOnDeathMin{}; //! The minimum number of coins to lose on a player death + int32_t coinsLostOnDeathMax{}; //! The maximum number of coins to lose on a player death + int32_t characterVotesPerDay{}; //! Unknown + int32_t propertyModerationRequestApprovalCost{};//! Unknown + int32_t propertyModerationRequestReviewCost{}; //! Unknown + int32_t propertyModRequestsAllowedSpike{}; //! Unknown + int32_t propertyModRequestsAllowedInterval{}; //! Unknown + int32_t propertyModRequestsAllowedTotal{}; //! Unknown + int32_t propertyModRequestsSpikeDuration{}; //! Unknown + int32_t propertyModRequestsIntervalDuration{}; //! Unknown + bool modelModerateOnCreate{}; //! Unknown + float defaultPropertyMaxHeight{}; //! Unknown + float reputationPerVoteCast{}; //! Unknown + float reputationPerVoteReceived{}; //! Unknown + int32_t showcaseTopModelConsiderationBattles{}; //! Unknown + float reputationPerBattlePromotion{}; //! Unknown + float coinsLostOnDeathMinTimeout{}; //! Unknown + float coinsLostOnDeathMaxTimeout{}; //! Unknown + int32_t mailBaseFee{}; //! The base fee to take when a player sends mail + float mailPercentAttachmentFee{}; //! The scalar multiplied by an items base cost to determine how much that item costs to be mailed + int32_t propertyReputationDelay{}; //! Unknown + int32_t levelCap{}; //! The maximum player level + std::string levelUpBehaviorEffect{}; //! Unknown + int32_t characterVersion{}; //! Unknown + int32_t levelCapCurrencyConversion{}; //! The ratio of UScore (LEGO Score) to coins +}; + +#endif //! __WORLDCONFIG__H__ diff --git a/dZoneManager/dZoneManager.cpp b/dZoneManager/dZoneManager.cpp index 102fb3af..0b7844a3 100644 --- a/dZoneManager/dZoneManager.cpp +++ b/dZoneManager/dZoneManager.cpp @@ -8,6 +8,7 @@ #include "DestroyableComponent.h" #include "GameMessages.h" #include "VanityUtilities.h" +#include "WorldConfig.h" #include #include "../dWorldServer/ObjectIDManager.h" @@ -53,6 +54,8 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) { endTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + LoadWorldConfig(); + Game::logger->Log("dZoneManager", "Zone prepared in: %llu ms", (endTime - startTime)); VanityUtilities::SpawnVanity(); @@ -69,6 +72,7 @@ dZoneManager::~dZoneManager() { m_Spawners.erase(p.first); } + if (m_WorldConfig) delete m_WorldConfig; } Zone* dZoneManager::GetZone() { @@ -117,24 +121,6 @@ LWOZONEID dZoneManager::GetZoneID() const { return m_ZoneID; } -uint32_t dZoneManager::GetMaxLevel() { - if (m_MaxLevel == 0) { - auto tableData = CDClientDatabase::ExecuteQuery("SELECT LevelCap FROM WorldConfig WHERE WorldConfigID = 1 LIMIT 1;"); - m_MaxLevel = tableData.getIntField(0, -1); - tableData.finalize(); - } - return m_MaxLevel; -} - -int32_t dZoneManager::GetLevelCapCurrencyConversion() { - if (m_CurrencyConversionRate == 0) { - auto tableData = CDClientDatabase::ExecuteQuery("SELECT LevelCapCurrencyConversion FROM WorldConfig WHERE WorldConfigID = 1 LIMIT 1;"); - m_CurrencyConversionRate = tableData.getIntField(0, -1); - tableData.finalize(); - } - return m_CurrencyConversionRate; -} - void dZoneManager::Update(float deltaTime) { for (auto spawner : m_Spawners) { spawner.second->Update(deltaTime); @@ -249,3 +235,77 @@ uint32_t dZoneManager::GetUniqueMissionIdStartingValue() { } return m_UniqueMissionIdStart; } + +void dZoneManager::LoadWorldConfig() { + Game::logger->Log("dZoneManager", "Loading WorldConfig into memory"); + + auto worldConfig = CDClientDatabase::ExecuteQuery("SELECT * FROM WorldConfig;"); + + if (!m_WorldConfig) m_WorldConfig = new WorldConfig(); + + if (worldConfig.eof()) { + Game::logger->Log("dZoneManager", "WorldConfig table is empty. Is this intended?"); + return; + } + + // Now read in the giant table + m_WorldConfig->worldConfigID = worldConfig.getIntField("WorldConfigID"); + m_WorldConfig->peGravityValue = worldConfig.getFloatField("pegravityvalue"); + m_WorldConfig->peBroadphaseWorldSize = worldConfig.getFloatField("pebroadphaseworldsize"); + m_WorldConfig->peGameObjScaleFactor = worldConfig.getFloatField("pegameobjscalefactor"); + m_WorldConfig->characterRotationSpeed = worldConfig.getFloatField("character_rotation_speed"); + m_WorldConfig->characterWalkForwardSpeed = worldConfig.getFloatField("character_walk_forward_speed"); + m_WorldConfig->characterWalkBackwardSpeed = worldConfig.getFloatField("character_walk_backward_speed"); + m_WorldConfig->characterWalkStrafeSpeed = worldConfig.getFloatField("character_walk_strafe_speed"); + m_WorldConfig->characterWalkStrafeForwardSpeed = worldConfig.getFloatField("character_walk_strafe_forward_speed"); + m_WorldConfig->characterWalkStrafeBackwardSpeed = worldConfig.getFloatField("character_walk_strafe_backward_speed"); + m_WorldConfig->characterRunBackwardSpeed = worldConfig.getFloatField("character_run_backward_speed"); + m_WorldConfig->characterRunStrafeSpeed = worldConfig.getFloatField("character_run_strafe_speed"); + m_WorldConfig->characterRunStrafeForwardSpeed = worldConfig.getFloatField("character_run_strafe_forward_speed"); + m_WorldConfig->characterRunStrafeBackwardSpeed = worldConfig.getFloatField("character_run_strafe_backward_speed"); + m_WorldConfig->globalCooldown = worldConfig.getFloatField("global_cooldown"); + m_WorldConfig->characterGroundedTime = worldConfig.getFloatField("characterGroundedTime"); + m_WorldConfig->characterGroundedSpeed = worldConfig.getFloatField("characterGroundedSpeed"); + m_WorldConfig->globalImmunityTime = worldConfig.getFloatField("globalImmunityTime"); + m_WorldConfig->characterMaxSlope = worldConfig.getFloatField("character_max_slope"); + m_WorldConfig->defaultRespawnTime = worldConfig.getFloatField("defaultrespawntime"); + m_WorldConfig->missionTooltipTimeout = worldConfig.getFloatField("mission_tooltip_timeout"); + m_WorldConfig->vendorBuyMultiplier = worldConfig.getFloatField("vendor_buy_multiplier"); + m_WorldConfig->petFollowRadius = worldConfig.getFloatField("pet_follow_radius"); + m_WorldConfig->characterEyeHeight = worldConfig.getFloatField("character_eye_height"); + m_WorldConfig->flightVerticalVelocity = worldConfig.getFloatField("flight_vertical_velocity"); + m_WorldConfig->flightAirspeed = worldConfig.getFloatField("flight_airspeed"); + m_WorldConfig->flightFuelRatio = worldConfig.getFloatField("flight_fuel_ratio"); + m_WorldConfig->flightMaxAirspeed = worldConfig.getFloatField("flight_max_airspeed"); + m_WorldConfig->fReputationPerVote = worldConfig.getFloatField("fReputationPerVote"); + m_WorldConfig->propertyCloneLimit = worldConfig.getIntField("nPropertyCloneLimit"); + m_WorldConfig->defaultHomespaceTemplate = worldConfig.getIntField("defaultHomespaceTemplate"); + m_WorldConfig->coinsLostOnDeathPercent = worldConfig.getFloatField("coins_lost_on_death_percent"); + m_WorldConfig->coinsLostOnDeathMin = worldConfig.getIntField("coins_lost_on_death_min"); + m_WorldConfig->coinsLostOnDeathMax = worldConfig.getIntField("coins_lost_on_death_max"); + m_WorldConfig->characterVotesPerDay = worldConfig.getIntField("character_votes_per_day"); + m_WorldConfig->propertyModerationRequestApprovalCost = worldConfig.getIntField("property_moderation_request_approval_cost"); + m_WorldConfig->propertyModerationRequestReviewCost = worldConfig.getIntField("property_moderation_request_review_cost"); + m_WorldConfig->propertyModRequestsAllowedSpike = worldConfig.getIntField("propertyModRequestsAllowedSpike"); + m_WorldConfig->propertyModRequestsAllowedInterval = worldConfig.getIntField("propertyModRequestsAllowedInterval"); + m_WorldConfig->propertyModRequestsAllowedTotal = worldConfig.getIntField("propertyModRequestsAllowedTotal"); + m_WorldConfig->propertyModRequestsSpikeDuration = worldConfig.getIntField("propertyModRequestsSpikeDuration"); + m_WorldConfig->propertyModRequestsIntervalDuration = worldConfig.getIntField("propertyModRequestsIntervalDuration"); + m_WorldConfig->modelModerateOnCreate = worldConfig.getIntField("modelModerateOnCreate") != 0; + m_WorldConfig->defaultPropertyMaxHeight = worldConfig.getFloatField("defaultPropertyMaxHeight"); + m_WorldConfig->reputationPerVoteCast = worldConfig.getFloatField("reputationPerVoteCast"); + m_WorldConfig->reputationPerVoteReceived = worldConfig.getFloatField("reputationPerVoteReceived"); + m_WorldConfig->showcaseTopModelConsiderationBattles = worldConfig.getIntField("showcaseTopModelConsiderationBattles"); + m_WorldConfig->reputationPerBattlePromotion = worldConfig.getFloatField("reputationPerBattlePromotion"); + m_WorldConfig->coinsLostOnDeathMinTimeout = worldConfig.getFloatField("coins_lost_on_death_min_timeout"); + m_WorldConfig->coinsLostOnDeathMaxTimeout = worldConfig.getFloatField("coins_lost_on_death_max_timeout"); + m_WorldConfig->mailBaseFee = worldConfig.getIntField("mail_base_fee"); + m_WorldConfig->mailPercentAttachmentFee = worldConfig.getFloatField("mail_percent_attachment_fee"); + m_WorldConfig->propertyReputationDelay = worldConfig.getIntField("propertyReputationDelay"); + m_WorldConfig->levelCap = worldConfig.getIntField("LevelCap"); + m_WorldConfig->levelUpBehaviorEffect = worldConfig.getStringField("LevelUpBehaviorEffect"); + m_WorldConfig->characterVersion = worldConfig.getIntField("CharacterVersion"); + m_WorldConfig->levelCapCurrencyConversion = worldConfig.getIntField("LevelCapCurrencyConversion"); + worldConfig.finalize(); + Game::logger->Log("dZoneManager", "Loaded WorldConfig into memory"); +} diff --git a/dZoneManager/dZoneManager.h b/dZoneManager/dZoneManager.h index b2fef1e3..c1776e79 100644 --- a/dZoneManager/dZoneManager.h +++ b/dZoneManager/dZoneManager.h @@ -4,6 +4,8 @@ #include "Spawner.h" #include +class WorldConfig; + class dZoneManager { public: enum class dZoneNotifier { @@ -16,6 +18,12 @@ public: InvalidNotifier }; +private: + /** + * Reads the WorldConfig from the CDClientDatabase into memory + */ + void LoadWorldConfig(); + public: static dZoneManager* Instance() { if (!m_Address) { @@ -33,8 +41,6 @@ public: void NotifyZone(const dZoneNotifier& notifier, const LWOOBJID& objectID); //Notifies the zone of a certain event or command. void AddSpawner(LWOOBJID id, Spawner* spawner); LWOZONEID GetZoneID() const; - uint32_t GetMaxLevel(); - int32_t GetLevelCapCurrencyConversion(); LWOOBJID MakeSpawner(SpawnerInfo info); Spawner* GetSpawner(LWOOBJID id); void RemoveSpawner(LWOOBJID id); @@ -45,27 +51,24 @@ public: bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; } uint32_t GetUniqueMissionIdStartingValue(); + // The world config should not be modified by a caller. + const WorldConfig* GetWorldConfig() { + if (!m_WorldConfig) LoadWorldConfig(); + return m_WorldConfig; + }; + private: - /** - * The maximum level of the world. - */ - uint32_t m_MaxLevel = 0; - - /** - * The ratio of LEGO Score to currency when the character has hit the max level. - */ - int32_t m_CurrencyConversionRate = 0; - /** * The starting unique mission ID. */ uint32_t m_UniqueMissionIdStart = 0; static dZoneManager* m_Address; //Singleton - Zone* m_pZone; + Zone* m_pZone = nullptr; LWOZONEID m_ZoneID; bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed std::map m_Spawners; + WorldConfig* m_WorldConfig = nullptr; - Entity* m_ZoneControlObject; + Entity* m_ZoneControlObject = nullptr; }; diff --git a/docker/Dockerfile b/docker/Dockerfile index 50f5b083..a7d91855 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \ echo "Install build dependencies" && \ apt update && \ apt remove -y libmysqlcppconn7v5 libmysqlcppconn-dev && \ - apt install cmake zlib1g zlib1g-dev unzip -yqq --no-install-recommends && \ + apt install cmake zlib1g zlib1g-dev -yqq --no-install-recommends && \ rm -rf /var/lib/apt/lists/* COPY dAuthServer/ /build/dAuthServer @@ -35,12 +35,11 @@ ARG BUILD_VERSION=171022 RUN echo "Build server" && \ mkdir -p cmake_build && \ cd cmake_build && \ - sed -i -e "s/171022/${BUILD_VERSION}/g" ../CMakeVariables.txt && \ + sed -i -e "s/NET_VERSION=.*/NET_VERSION=${BUILD_VERSION}/g" ../CMakeVariables.txt && \ + sed -i -e "s/__maria_db_connector_compile_jobs__=.*/__maria_db_connector_compile_jobs__=${BUILD_THREADS}/g" ../CMakeVariables.txt && \ cmake .. -DCMAKE_BUILD_RPATH_USE_ORIGIN=TRUE && \ make -j $BUILD_THREADS -RUN unzip /build/resources/navmeshes.zip -d /build/cmake_build/res/maps - FROM gcc:11 as runtime RUN --mount=type=cache,id=runtime-apt-cache,target=/var/cache/apt \ diff --git a/docker/setup.Dockerfile b/docker/setup.Dockerfile index 2664e2fa..18bb2d06 100644 --- a/docker/setup.Dockerfile +++ b/docker/setup.Dockerfile @@ -1,23 +1,12 @@ -FROM rust:alpine3.14 as LUnpack - -WORKDIR /build_LUnpack - -COPY ./thirdparty/LUnpack . - -RUN apk add musl-dev --no-cache && cargo build --release - FROM python:3.10-alpine3.14 as prep -RUN apk add sqlite bash --no-cache +RUN apk add bash --no-cache WORKDIR /setup # copy needed files from repo COPY resources/ resources/ -COPY migrations/cdserver/ migrations/cdserver -COPY --from=LUnpack /build_LUnpack/target/release/lunpack /usr/local/bin/lunpack -ADD thirdparty/docker-utils/utils/*.py utils/ COPY docker/setup.sh /setup.sh -CMD [ "/setup.sh" ] \ No newline at end of file +CMD [ "/setup.sh" ] diff --git a/docker/setup.sh b/docker/setup.sh index 0f5c0d2e..ade67d2e 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -7,7 +7,7 @@ function update_ini() { FILE="/docker/configs/$1" KEY=$2 NEW_VALUE=$3 - sed -i "/^$KEY=/s/=.*/=$NEW_VALUE/" $FILE + sed -i "s~$2=.*~$2=$3~" $FILE } function update_database_ini_values_for() { @@ -32,62 +32,11 @@ function update_ini_values() { cp resources/worldconfig.ini /docker/configs/ cp resources/sharedconfig.ini /docker/configs/ - update_ini worldconfig.ini chat_server_port $CHAT_SERVER_PORT - update_ini worldconfig.ini max_clients $MAX_CLIENTS - # always use the internal docker hostname update_ini masterconfig.ini master_ip "darkflame" + update_ini sharedconfig.ini client_location "/client" update_database_ini_values_for sharedconfig.ini } -function fdb_to_sqlite() { - echo "Run fdb_to_sqlite" - python3 utils/fdb_to_sqlite.py /client/client/res/cdclient.fdb --sqlite_path /client/client/res/CDServer.sqlite - - ( - cd migrations/cdserver - readarray -d '' entries < <(printf '%s\0' *.sql | sort -zV) - for entry in "${entries[@]}"; do - echo "Execute $entry" - sqlite3 /client/client/res/CDServer.sqlite < $entry - done - ) -} - update_ini_values - -if [[ ! -d "/client" ]]; then - echo "Client not found." - echo "Did you forget to mount the client into the \"/client\" directory?" - exit 1 -fi - -if [[ ! -f "/client/extracted" ]]; then - echo "Start client resource extraction" - - touch globs.txt - - echo "client/res/macros/**" >> globs.txt - echo "client/res/BrickModels/**" >> globs.txt - echo "client/res/maps/**" >> globs.txt - echo "*.fdb" >> globs.txt - - lunpack -g ./globs.txt /client/ - - touch /client/extracted -else - echo "Client already extracted. Skip this step..." - echo "If you want to force a re-extract, just delete the file called \"extracted\" in the client directory" -fi - -if [[ ! -f "/client/migrated" ]]; then - echo "Start client db migration" - - fdb_to_sqlite - - touch /client/migrated -else - echo "Client db already migrated. Skip this step..." - echo "If you want to force a re-migrate, just delete the file called \"migrated\" in the client directory" -fi diff --git a/docker/start_server.sh b/docker/start_server.sh old mode 100644 new mode 100755 index 4fd6e2be..2e2e8c28 --- a/docker/start_server.sh +++ b/docker/start_server.sh @@ -1,25 +1,5 @@ #!/bin/bash -function symlink_client_files() { - echo "Creating symlinks for client files" - ln -s /client/client/res/macros/ /app/res/macros - ln -s /client/client/res/BrickModels/ /app/res/BrickModels - 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 - - # 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 - ( - cd /client/client/res/maps - readarray -d '' entries < <(printf '%s\0' * | sort -zV) - for entry in "${entries[@]}"; do - ln -s /client/client/res/maps/$entry /app/res/maps/ - done - ) -} - function symlink_config_files() { echo "Creating symlinks for config files" rm /app/*.ini @@ -30,15 +10,8 @@ function symlink_config_files() { ln -s /shared_configs/configs/sharedconfig.ini /app/sharedconfig.ini } -# check to make sure the setup has completed -while [ ! -f "/client/extracted" ] || [ ! -f "/client/migrated" ]; do - echo "Client setup not finished. Waiting for setup container to complete..." - sleep 5 -done - if [[ ! -f "/app/initialized" ]]; then # setup symlinks for volume files - symlink_client_files symlink_config_files # do not run symlinks more than once touch /app/initialized @@ -49,4 +22,4 @@ fi # start the server echo "Starting MasterServer" ./MasterServer -tail -f /dev/null \ No newline at end of file +tail -f /dev/null diff --git a/thirdparty/LUnpack b/thirdparty/LUnpack deleted file mode 160000 index f8d7e442..00000000 --- a/thirdparty/LUnpack +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8d7e442a78910b298fe1cd5780f07c9c9285b8c diff --git a/thirdparty/docker-utils b/thirdparty/docker-utils deleted file mode 160000 index 3f0129e0..00000000 --- a/thirdparty/docker-utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f0129e0939ce5ccf41f0808dcbbe71a6243e37f