Merge remote-tracking branch 'upstream/main'

This commit is contained in:
David Markowitz 2023-01-06 21:13:30 -08:00
commit 9d4d618e63
25 changed files with 354 additions and 333 deletions

6
.gitmodules vendored
View File

@ -14,12 +14,6 @@
path = thirdparty/mariadb-connector-cpp path = thirdparty/mariadb-connector-cpp
url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git
ignore = dirty 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"] [submodule "thirdparty/AccountManager"]
path = thirdparty/AccountManager path = thirdparty/AccountManager
url = https://github.com/DarkflameUniverse/AccountManager url = https://github.com/DarkflameUniverse/AccountManager

View File

@ -4,7 +4,7 @@
- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker) - [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) - [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 ## Run server inside Docker

View File

@ -25,7 +25,7 @@
11. Once the command has completed (you can see you path again and can enter commands), close the window. 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` 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_. 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$\<your linux OS>\...` 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). > If you need the extra performance, place the client files in `\\wsl$\<your linux OS>\...` 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. 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.

View File

@ -10,6 +10,7 @@
#include "GeneralUtils.h" #include "GeneralUtils.h"
#include "Game.h" #include "Game.h"
#include "dLogger.h" #include "dLogger.h"
#include "AssetManager.h"
#include "eSqliteDataType.h" #include "eSqliteDataType.h"
@ -23,26 +24,23 @@ std::map<eSqliteDataType, std::string> FdbToSqlite::Convert::m_SqliteType = {
{ eSqliteDataType::TEXT_8, "text_8"} { eSqliteDataType::TEXT_8, "text_8"}
}; };
FdbToSqlite::Convert::Convert(std::string basePath, std::string binaryOutPath) { FdbToSqlite::Convert::Convert(std::string binaryOutPath) {
this->m_BasePath = basePath;
this->m_BinaryOutPath = binaryOutPath; this->m_BinaryOutPath = binaryOutPath;
m_Fdb.open(m_BasePath + "/cdclient.fdb", std::ios::binary);
} }
FdbToSqlite::Convert::~Convert() { bool FdbToSqlite::Convert::ConvertDatabase(AssetMemoryBuffer& buffer) {
this->m_Fdb.close();
}
bool FdbToSqlite::Convert::ConvertDatabase() {
if (m_ConversionStarted) return false; if (m_ConversionStarted) return false;
std::istream cdClientBuffer(&buffer);
this->m_ConversionStarted = true; this->m_ConversionStarted = true;
try { try {
CDClientDatabase::Connect(m_BinaryOutPath + "/CDServer.sqlite"); CDClientDatabase::Connect(m_BinaryOutPath + "/CDServer.sqlite");
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
int32_t numberOfTables = ReadInt32(); int32_t numberOfTables = ReadInt32(cdClientBuffer);
ReadTables(numberOfTables); ReadTables(numberOfTables, cdClientBuffer);
CDClientDatabase::ExecuteQuery("COMMIT;"); CDClientDatabase::ExecuteQuery("COMMIT;");
} catch (CppSQLite3Exception& e) { } catch (CppSQLite3Exception& e) {
@ -53,130 +51,130 @@ bool FdbToSqlite::Convert::ConvertDatabase() {
return true; return true;
} }
int32_t FdbToSqlite::Convert::ReadInt32() { int32_t FdbToSqlite::Convert::ReadInt32(std::istream& cdClientBuffer) {
int32_t nextInt{}; int32_t nextInt{};
BinaryIO::BinaryRead(m_Fdb, nextInt); BinaryIO::BinaryRead(cdClientBuffer, nextInt);
return nextInt; return nextInt;
} }
int64_t FdbToSqlite::Convert::ReadInt64() { int64_t FdbToSqlite::Convert::ReadInt64(std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
int64_t value{}; int64_t value{};
BinaryIO::BinaryRead(m_Fdb, value); BinaryIO::BinaryRead(cdClientBuffer, value);
m_Fdb.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
return value; return value;
} }
std::string FdbToSqlite::Convert::ReadString() { std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); 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; return readString;
} }
int32_t FdbToSqlite::Convert::SeekPointer() { int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) {
int32_t position{}; int32_t position{};
BinaryIO::BinaryRead(m_Fdb, position); BinaryIO::BinaryRead(cdClientBuffer, position);
int32_t prevPosition = m_Fdb.tellg(); int32_t prevPosition = cdClientBuffer.tellg();
m_Fdb.seekg(position); cdClientBuffer.seekg(position);
return prevPosition; return prevPosition;
} }
std::string FdbToSqlite::Convert::ReadColumnHeader() { std::string FdbToSqlite::Convert::ReadColumnHeader(std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
int32_t numberOfColumns = ReadInt32(); int32_t numberOfColumns = ReadInt32(cdClientBuffer);
std::string tableName = ReadString(); std::string tableName = ReadString(cdClientBuffer);
auto columns = ReadColumns(numberOfColumns); auto columns = ReadColumns(numberOfColumns, cdClientBuffer);
std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");"; std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");";
CDClientDatabase::ExecuteDML(newTable); CDClientDatabase::ExecuteDML(newTable);
m_Fdb.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
return tableName; return tableName;
} }
void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables) { void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables, std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
for (int32_t i = 0; i < numberOfTables; i++) { for (int32_t i = 0; i < numberOfTables; i++) {
auto columnHeader = ReadColumnHeader(); auto columnHeader = ReadColumnHeader(cdClientBuffer);
ReadRowHeader(columnHeader); 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; std::stringstream columnsToCreate;
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
std::string name{}; std::string name{};
eSqliteDataType dataType{}; eSqliteDataType dataType{};
for (int32_t i = 0; i < numberOfColumns; i++) { for (int32_t i = 0; i < numberOfColumns; i++) {
if (i != 0) columnsToCreate << ", "; if (i != 0) columnsToCreate << ", ";
dataType = static_cast<eSqliteDataType>(ReadInt32()); dataType = static_cast<eSqliteDataType>(ReadInt32(cdClientBuffer));
name = ReadString(); name = ReadString(cdClientBuffer);
columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::m_SqliteType[dataType]; columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::m_SqliteType[dataType];
} }
m_Fdb.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
return columnsToCreate.str(); return columnsToCreate.str();
} }
void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName) { void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName, std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); 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 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) { void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName, std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
int32_t rowid = 0; int32_t rowid = 0;
for (int32_t row = 0; row < numberOfAllocatedRows; row++) { for (int32_t row = 0; row < numberOfAllocatedRows; row++) {
int32_t rowPointer = ReadInt32(); int32_t rowPointer = ReadInt32(cdClientBuffer);
if (rowPointer == -1) rowid++; 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) { void FdbToSqlite::Convert::ReadRow(int32_t& position, std::string& tableName, std::istream& cdClientBuffer) {
int32_t prevPosition = m_Fdb.tellg(); int32_t prevPosition = cdClientBuffer.tellg();
m_Fdb.seekg(position); cdClientBuffer.seekg(position);
while (true) { while (true) {
ReadRowInfo(tableName); ReadRowInfo(tableName, cdClientBuffer);
int32_t linked = ReadInt32(); int32_t linked = ReadInt32(cdClientBuffer);
if (linked == -1) break; 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) { void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName, std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
int32_t numberOfColumns = ReadInt32(); int32_t numberOfColumns = ReadInt32(cdClientBuffer);
ReadRowValues(numberOfColumns, tableName); ReadRowValues(numberOfColumns, tableName, cdClientBuffer);
m_Fdb.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
} }
void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName) { void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName, std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(); int32_t prevPosition = SeekPointer(cdClientBuffer);
int32_t emptyValue{}; int32_t emptyValue{};
int32_t intValue{}; int32_t intValue{};
@ -190,26 +188,26 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string&
for (int32_t i = 0; i < numberOfColumns; i++) { for (int32_t i = 0; i < numberOfColumns; i++) {
if (i != 0) insertedRow << ", "; // Only append comma and space after first entry in row. if (i != 0) insertedRow << ", "; // Only append comma and space after first entry in row.
switch (static_cast<eSqliteDataType>(ReadInt32())) { switch (static_cast<eSqliteDataType>(ReadInt32(cdClientBuffer))) {
case eSqliteDataType::NONE: case eSqliteDataType::NONE:
BinaryIO::BinaryRead(m_Fdb, emptyValue); BinaryIO::BinaryRead(cdClientBuffer, emptyValue);
assert(emptyValue == 0); assert(emptyValue == 0);
insertedRow << "NULL"; insertedRow << "NULL";
break; break;
case eSqliteDataType::INT32: case eSqliteDataType::INT32:
intValue = ReadInt32(); intValue = ReadInt32(cdClientBuffer);
insertedRow << intValue; insertedRow << intValue;
break; break;
case eSqliteDataType::REAL: 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 insertedRow << std::fixed << std::setprecision(34) << floatValue; // maximum precision of floating point number
break; break;
case eSqliteDataType::TEXT_4: case eSqliteDataType::TEXT_4:
case eSqliteDataType::TEXT_8: { case eSqliteDataType::TEXT_8: {
stringValue = ReadString(); stringValue = ReadString(cdClientBuffer);
size_t position = 0; size_t position = 0;
// Need to escape quote with a double of ". // 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: case eSqliteDataType::INT_BOOL:
BinaryIO::BinaryRead(m_Fdb, boolValue); BinaryIO::BinaryRead(cdClientBuffer, boolValue);
insertedRow << static_cast<bool>(boolValue); insertedRow << static_cast<bool>(boolValue);
break; break;
case eSqliteDataType::INT64: case eSqliteDataType::INT64:
int64Value = ReadInt64(); int64Value = ReadInt64(cdClientBuffer);
insertedRow << std::to_string(int64Value); insertedRow << std::to_string(int64Value);
break; break;
@ -245,5 +243,5 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string&
auto copiedString = insertedRow.str(); auto copiedString = insertedRow.str();
CDClientDatabase::ExecuteDML(copiedString); CDClientDatabase::ExecuteDML(copiedString);
m_Fdb.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
} }

View File

@ -7,6 +7,8 @@
#include <iosfwd> #include <iosfwd>
#include <map> #include <map>
class AssetMemoryBuffer;
enum class eSqliteDataType : int32_t; enum class eSqliteDataType : int32_t;
namespace FdbToSqlite { namespace FdbToSqlite {
@ -18,33 +20,28 @@ namespace FdbToSqlite {
* @param inputFile The file which ends in .fdb to be converted * @param inputFile The file which ends in .fdb to be converted
* @param binaryPath The base path where the file will be saved * @param binaryPath The base path where the file will be saved
*/ */
Convert(std::string inputFile, std::string binaryOutPath); Convert(std::string binaryOutPath);
/**
* Destroy the convert object and close the fdb file.
*/
~Convert();
/** /**
* Converts the input file to sqlite. Calling multiple times is safe. * Converts the input file to sqlite. Calling multiple times is safe.
* *
* @return true if the database was converted properly, false otherwise. * @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. * @brief Reads a 32 bit int from the fdb file.
* *
* @return The read value * @return The read value
*/ */
int32_t ReadInt32(); int32_t ReadInt32(std::istream& cdClientBuffer);
/** /**
* @brief Reads a 64 bit integer from the fdb file. * @brief Reads a 64 bit integer from the fdb file.
* *
* @return The read value * @return The read value
*/ */
int64_t ReadInt64(); int64_t ReadInt64(std::istream& cdClientBuffer);
/** /**
* @brief Reads a string from the fdb file. * @brief Reads a string from the fdb file.
@ -53,28 +50,28 @@ namespace FdbToSqlite {
* *
* TODO This needs to be translated to latin-1! * TODO This needs to be translated to latin-1!
*/ */
std::string ReadString(); std::string ReadString(std::istream& cdClientBuffer);
/** /**
* @brief Seeks to a pointer position. * @brief Seeks to a pointer position.
* *
* @return The previous position before the seek * @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 * @brief Reads a column header from the fdb file and creates the table in the database
* *
* @return The table name * @return The table name
*/ */
std::string ReadColumnHeader(); std::string ReadColumnHeader(std::istream& cdClientBuffer);
/** /**
* @brief Read the tables from the fdb file. * @brief Read the tables from the fdb file.
* *
* @param numberOfTables The number of tables to read * @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. * @brief Reads the columns from the fdb file.
@ -82,14 +79,14 @@ namespace FdbToSqlite {
* @param numberOfColumns The number of columns to read * @param numberOfColumns The number of columns to read
* @return All columns of the table formatted for a sql query * @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. * @brief Reads the row header from the fdb file.
* *
* @param tableName The tables name * @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., * @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 numberOfAllocatedRows The number of rows that were allocated. Always a power of 2!
* @param tableName The tables name. * @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. * @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 position The position to seek in the fdb to
* @param tableName The tables name * @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. * @brief Reads the row info from the fdb file.
* *
* @param tableName The tables name * @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 * @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 numberOfColumns The number of columns to read in
* @param tableName The tables name * @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: private:
/** /**
@ -133,11 +130,6 @@ namespace FdbToSqlite {
*/ */
std::string m_BasePath{}; 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. * Whether or not a conversion was started. If one was started, do not attempt to convert the file again.
*/ */

View File

@ -45,9 +45,6 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
switch (m_AssetBundleType) { switch (m_AssetBundleType) {
case eAssetBundleType::Packed: { case eAssetBundleType::Packed: {
this->LoadPackIndex(); this->LoadPackIndex();
this->UnpackRequiredAssets();
break; break;
} }
} }
@ -160,31 +157,6 @@ AssetMemoryBuffer AssetManager::GetFileAsBuffer(const char* name) {
return AssetMemoryBuffer(buf, len, success); 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) { uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
size_t i, j; size_t i, j;
uint32_t crc, msb; uint32_t crc, msb;

View File

@ -60,7 +60,6 @@ public:
private: private:
void LoadPackIndex(); void LoadPackIndex();
void UnpackRequiredAssets();
// Modified crc algorithm (mpeg2) // Modified crc algorithm (mpeg2)
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2 // Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2

View File

@ -89,7 +89,7 @@ void dLogger::Log(const std::string& className, const std::string& message) {
void dLogger::LogDebug(const char* className, const char* format, ...) { void dLogger::LogDebug(const char* className, const char* format, ...) {
if (!m_logDebugStatements) return; if (!m_logDebugStatements) return;
va_list args; 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); va_start(args, format);
vLog(log.c_str(), args); vLog(log.c_str(), args);
va_end(args); va_end(args);

View File

@ -23,9 +23,9 @@
#include "RebuildComponent.h" #include "RebuildComponent.h"
#include "DestroyableComponent.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_Target = LWOOBJID_EMPTY;
m_State = AiState::spawn; SetAiState(AiState::spawn);
m_Timer = 1.0f; m_Timer = 1.0f;
m_StartPosition = parent->GetPosition(); m_StartPosition = parent->GetPosition();
m_MovementAI = nullptr; m_MovementAI = nullptr;
@ -179,7 +179,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
if (m_Disabled || m_Parent->GetIsDead()) if (m_Disabled || m_Parent->GetIsDead())
return; return;
bool stunnedThisFrame = m_Stunned;
CalculateCombat(deltaTime); // Putting this here for now CalculateCombat(deltaTime); // Putting this here for now
if (m_StartPosition == NiPoint3::ZERO) { if (m_StartPosition == NiPoint3::ZERO) {
@ -192,7 +192,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
return; return;
} }
if (m_Stunned) { if (stunnedThisFrame) {
m_MovementAI->Stop(); m_MovementAI->Stop();
return; return;
@ -206,7 +206,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
switch (m_State) { switch (m_State) {
case AiState::spawn: case AiState::spawn:
Stun(2.0f); Stun(2.0f);
m_State = AiState::idle; SetAiState(AiState::idle);
break; break;
case AiState::idle: case AiState::idle:
@ -248,13 +248,13 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
if (m_Disabled) return; if (m_Disabled) return;
if (m_StunTime > 0.0f) { if (m_Stunned) {
m_StunTime -= deltaTime; m_StunTime -= deltaTime;
if (m_StunTime > 0.0f) { if (m_StunTime > 0.0f) {
return; return;
} }
m_StunTime = 0.0f;
m_Stunned = false; m_Stunned = false;
} }
@ -320,9 +320,9 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
m_Timer = 0; m_Timer = 0;
} }
m_State = AiState::aggro; SetAiState(AiState::aggro);
} else { } else {
m_State = AiState::idle; SetAiState(AiState::idle);
} }
for (auto i = 0; i < m_SkillEntries.size(); ++i) { for (auto i = 0; i < m_SkillEntries.size(); ++i) {
@ -348,7 +348,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
} }
if (m_Target == LWOOBJID_EMPTY) { if (m_Target == LWOOBJID_EMPTY) {
m_State = AiState::idle; SetAiState(AiState::idle);
return; return;
} }
@ -375,7 +375,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
m_MovementAI->Stop(); m_MovementAI->Stop();
} }
m_State = AiState::aggro; SetAiState(AiState::aggro);
m_Timer = 0; m_Timer = 0;
@ -532,11 +532,20 @@ bool BaseCombatAIComponent::IsMech() {
void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write1(); outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate);
outBitStream->Write(uint32_t(m_State)); if (m_DirtyStateOrTarget || bIsInitialUpdate) {
outBitStream->Write(m_Target); 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 { bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* entity = EntityManager::Instance()->GetEntity(target); auto* entity = EntityManager::Instance()->GetEntity(target);
@ -585,7 +594,10 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
} }
void BaseCombatAIComponent::SetTarget(const LWOOBJID target) { void BaseCombatAIComponent::SetTarget(const LWOOBJID target) {
if (this->m_Target == target) return;
m_Target = target; m_Target = target;
m_DirtyStateOrTarget = true;
EntityManager::Instance()->SerializeEntity(m_Parent);
} }
Entity* BaseCombatAIComponent::GetTargetEntity() const { Entity* BaseCombatAIComponent::GetTargetEntity() const {
@ -700,7 +712,7 @@ void BaseCombatAIComponent::OnAggro() {
m_MovementAI->SetDestination(targetPos); m_MovementAI->SetDestination(targetPos);
m_State = AiState::tether; SetAiState(AiState::tether);
} }
m_Timer += 0.5f; m_Timer += 0.5f;
@ -726,7 +738,7 @@ void BaseCombatAIComponent::OnTether() {
m_MovementAI->SetDestination(m_StartPosition); m_MovementAI->SetDestination(m_StartPosition);
m_State = AiState::aggro; SetAiState(AiState::aggro);
} else { } else {
if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return; if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return;

View File

@ -243,6 +243,12 @@ private:
*/ */
std::vector<LWOOBJID> GetTargetWithinAggroRange() const; std::vector<LWOOBJID> GetTargetWithinAggroRange() const;
/**
* @brief Sets the AiState and prepares the entity for serialization next frame.
*
*/
void SetAiState(AiState newState);
/** /**
* The current state of the AI * The current state of the AI
*/ */
@ -374,6 +380,12 @@ private:
*/ */
bool m_DirtyThreat = false; 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 * Whether the current entity is a mech enemy, needed as mechs tether radius works differently
* @return whether this entity is a mech * @return whether this entity is a mech

View File

@ -30,6 +30,7 @@
#include "PossessorComponent.h" #include "PossessorComponent.h"
#include "InventoryComponent.h" #include "InventoryComponent.h"
#include "dZoneManager.h" #include "dZoneManager.h"
#include "WorldConfig.h"
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_iArmor = 0; m_iArmor = 0;
@ -700,7 +701,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* missions = owner->GetComponent<MissionComponent>(); auto* missions = owner->GetComponent<MissionComponent>();
if (missions != nullptr) { if (missions != nullptr) {
if (team != nullptr && isEnemy) { if (team != nullptr) {
for (const auto memberId : team->members) { for (const auto memberId : team->members) {
auto* member = EntityManager::Instance()->GetEntity(memberId); auto* member = EntityManager::Instance()->GetEntity(memberId);
@ -763,22 +764,17 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (dZoneManager::Instance()->GetPlayerLoseCoinOnDeath()) { if (dZoneManager::Instance()->GetPlayerLoseCoinOnDeath()) {
auto* character = m_Parent->GetCharacter(); auto* character = m_Parent->GetCharacter();
uint64_t coinsTotal = character->GetCoins(); 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 coinsToLose = std::max(static_cast<uint64_t>(coinsTotal * coinPercentageToLose), minCoinsToLose);
uint64_t coinsToLoose = 1; coinsToLose = std::min(maxCoinsToLose, coinsToLose);
if (coinsTotal >= 200) { coinsTotal -= coinsToLose;
float hundreth = (coinsTotal / 100.0f);
coinsToLoose = static_cast<int>(hundreth);
}
if (coinsToLoose > 10000) { LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLose, coinsToLose);
coinsToLoose = 10000;
}
coinsTotal -= coinsToLoose;
LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose);
character->SetCoins(coinsTotal, eLootSourceType::LOOT_SOURCE_PICKUP); character->SetCoins(coinsTotal, eLootSourceType::LOOT_SOURCE_PICKUP);
} }
} }

View File

@ -59,7 +59,7 @@ std::map<LOT, uint32_t> PetComponent::petFlags = {
{ 13067, 838 }, // Skeleton dragon { 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_ComponentId = componentId;
m_Interaction = LWOOBJID_EMPTY; m_Interaction = LWOOBJID_EMPTY;
@ -118,21 +118,23 @@ void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpd
outBitStream->Write(m_Owner); outBitStream->Write(m_Owner);
} }
outBitStream->Write(tamed); if (bIsInitialUpdate) {
if (tamed) { outBitStream->Write(tamed);
outBitStream->Write(m_ModerationStatus); if (tamed) {
outBitStream->Write(m_ModerationStatus);
const auto nameData = GeneralUtils::UTF8ToUTF16(m_Name); const auto nameData = GeneralUtils::UTF8ToUTF16(m_Name);
const auto ownerNameData = GeneralUtils::UTF8ToUTF16(m_OwnerName); const auto ownerNameData = GeneralUtils::UTF8ToUTF16(m_OwnerName);
outBitStream->Write(static_cast<uint8_t>(nameData.size())); outBitStream->Write(static_cast<uint8_t>(nameData.size()));
for (const auto c : nameData) { for (const auto c : nameData) {
outBitStream->Write(c); outBitStream->Write(c);
} }
outBitStream->Write(static_cast<uint8_t>(ownerNameData.size())); outBitStream->Write(static_cast<uint8_t>(ownerNameData.size()));
for (const auto c : ownerNameData) { for (const auto c : ownerNameData) {
outBitStream->Write(c); outBitStream->Write(c);
}
} }
} }
} }
@ -916,16 +918,16 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
return; return;
} }
// If we are out of imagination despawn the pet. // If we are out of imagination despawn the pet.
if (playerDestroyableComponent->GetImagination() == 0) { if (playerDestroyableComponent->GetImagination() == 0) {
this->Deactivate(); this->Deactivate();
auto playerEntity = playerDestroyableComponent->GetParent(); auto playerEntity = playerDestroyableComponent->GetParent();
if (!playerEntity) return; 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);
}); });
} }

View File

@ -7,8 +7,8 @@ PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent) : C
PlayerForcedMovementComponent::~PlayerForcedMovementComponent() {} PlayerForcedMovementComponent::~PlayerForcedMovementComponent() {}
void PlayerForcedMovementComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { void PlayerForcedMovementComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyInfo); outBitStream->Write(m_DirtyInfo || bIsInitialUpdate);
if (m_DirtyInfo) { if (m_DirtyInfo || bIsInitialUpdate) {
outBitStream->Write(m_PlayerOnRail); outBitStream->Write(m_PlayerOnRail);
outBitStream->Write(m_ShowBillboard); outBitStream->Write(m_ShowBillboard);
} }

View File

@ -18,6 +18,7 @@
#include "dZoneManager.h" #include "dZoneManager.h"
#include "InventoryComponent.h" #include "InventoryComponent.h"
#include "Database.h" #include "Database.h"
#include "WorldConfig.h"
Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
m_MissionComponent = missionComponent; m_MissionComponent = missionComponent;
@ -435,9 +436,9 @@ void Mission::YieldRewards() {
int32_t coinsToSend = 0; int32_t coinsToSend = 0;
if (info->LegoScore > 0) { if (info->LegoScore > 0) {
eLootSourceType lootSource = info->isMission ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT; 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. // 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 { } else {
characterComponent->SetUScore(characterComponent->GetUScore() + info->LegoScore); characterComponent->SetUScore(characterComponent->GetUScore() + info->LegoScore);
GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info->LegoScore, lootSource); GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info->LegoScore, lootSource);

View File

@ -22,6 +22,8 @@
#include "MissionComponent.h" #include "MissionComponent.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "Character.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, void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount) { const uint16_t attachmentCount) {
@ -191,7 +193,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd
uint32_t itemID = static_cast<uint32_t>(attachmentID); uint32_t itemID = static_cast<uint32_t>(attachmentID);
LOT itemLOT = 0; LOT itemLOT = 0;
//Inventory::InventoryType itemType; //Inventory::InventoryType itemType;
int mailCost = 25; int mailCost = dZoneManager::Instance()->GetWorldConfig()->mailBaseFee;
int stackSize = 0; int stackSize = 0;
auto inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY)); auto inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
Item* item = nullptr; Item* item = nullptr;
@ -199,7 +201,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd
if (itemID > 0 && attachmentCount > 0 && inv) { if (itemID > 0 && attachmentCount > 0 && inv) {
item = inv->FindItemById(attachmentID); item = inv->FindItemById(attachmentID);
if (item) { if (item) {
mailCost += (item->GetInfo().baseValue * 0.1f); mailCost += (item->GetInfo().baseValue * dZoneManager::Instance()->GetWorldConfig()->mailPercentAttachmentFee);
stackSize = item->GetCount(); stackSize = item->GetCount();
itemLOT = item->GetLot(); itemLOT = item->GetLot();
} else { } else {

View File

@ -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..."); 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"); std::filesystem::copy_file(Game::assetManager->GetResPath() / "CDServer.sqlite", BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite");
} else { } 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.", "%s could not be found in resServer or res. Looking for %s to convert to sqlite.",
(BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").c_str(), (BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").c_str(),
(Game::assetManager->GetResPath() / "cdclient.fdb").c_str()); (Game::assetManager->GetResPath() / "cdclient.fdb").c_str());
if (!fdbExists) {
Game::logger->Log("WorldServer", AssetMemoryBuffer cdClientBuffer = Game::assetManager->GetFileAsBuffer("cdclient.fdb");
"%s could not be opened. Please move cdclient.fdb to %s", if (!cdClientBuffer.m_Success) {
(Game::assetManager->GetResPath() / "cdclient.fdb").c_str(), Game::logger->Log("MasterServer", "Failed to load %s", (Game::assetManager->GetResPath() / "cdclient.fdb").c_str());
(Game::assetManager->GetResPath().c_str())); throw std::runtime_error("Aborting initialization due to missing cdclient.fdb.");
return FinalizeShutdown();
} }
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."); Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite.");
return FinalizeShutdown(); return EXIT_FAILURE;
} }
cdClientBuffer.close();
} }
} }

View File

@ -0,0 +1,67 @@
#ifndef __WORLDCONFIG__H__
#define __WORLDCONFIG__H__
#include <cstdint>
#include <string>
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__

View File

@ -8,6 +8,7 @@
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
#include "GameMessages.h" #include "GameMessages.h"
#include "VanityUtilities.h" #include "VanityUtilities.h"
#include "WorldConfig.h"
#include <chrono> #include <chrono>
#include "../dWorldServer/ObjectIDManager.h" #include "../dWorldServer/ObjectIDManager.h"
@ -53,6 +54,8 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
LoadWorldConfig();
Game::logger->Log("dZoneManager", "Zone prepared in: %llu ms", (endTime - startTime)); Game::logger->Log("dZoneManager", "Zone prepared in: %llu ms", (endTime - startTime));
VanityUtilities::SpawnVanity(); VanityUtilities::SpawnVanity();
@ -69,6 +72,7 @@ dZoneManager::~dZoneManager() {
m_Spawners.erase(p.first); m_Spawners.erase(p.first);
} }
if (m_WorldConfig) delete m_WorldConfig;
} }
Zone* dZoneManager::GetZone() { Zone* dZoneManager::GetZone() {
@ -117,24 +121,6 @@ LWOZONEID dZoneManager::GetZoneID() const {
return m_ZoneID; 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) { void dZoneManager::Update(float deltaTime) {
for (auto spawner : m_Spawners) { for (auto spawner : m_Spawners) {
spawner.second->Update(deltaTime); spawner.second->Update(deltaTime);
@ -249,3 +235,77 @@ uint32_t dZoneManager::GetUniqueMissionIdStartingValue() {
} }
return m_UniqueMissionIdStart; 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");
}

View File

@ -4,6 +4,8 @@
#include "Spawner.h" #include "Spawner.h"
#include <map> #include <map>
class WorldConfig;
class dZoneManager { class dZoneManager {
public: public:
enum class dZoneNotifier { enum class dZoneNotifier {
@ -16,6 +18,12 @@ public:
InvalidNotifier InvalidNotifier
}; };
private:
/**
* Reads the WorldConfig from the CDClientDatabase into memory
*/
void LoadWorldConfig();
public: public:
static dZoneManager* Instance() { static dZoneManager* Instance() {
if (!m_Address) { 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 NotifyZone(const dZoneNotifier& notifier, const LWOOBJID& objectID); //Notifies the zone of a certain event or command.
void AddSpawner(LWOOBJID id, Spawner* spawner); void AddSpawner(LWOOBJID id, Spawner* spawner);
LWOZONEID GetZoneID() const; LWOZONEID GetZoneID() const;
uint32_t GetMaxLevel();
int32_t GetLevelCapCurrencyConversion();
LWOOBJID MakeSpawner(SpawnerInfo info); LWOOBJID MakeSpawner(SpawnerInfo info);
Spawner* GetSpawner(LWOOBJID id); Spawner* GetSpawner(LWOOBJID id);
void RemoveSpawner(LWOOBJID id); void RemoveSpawner(LWOOBJID id);
@ -45,27 +51,24 @@ public:
bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; } bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; }
uint32_t GetUniqueMissionIdStartingValue(); uint32_t GetUniqueMissionIdStartingValue();
// The world config should not be modified by a caller.
const WorldConfig* GetWorldConfig() {
if (!m_WorldConfig) LoadWorldConfig();
return m_WorldConfig;
};
private: 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. * The starting unique mission ID.
*/ */
uint32_t m_UniqueMissionIdStart = 0; uint32_t m_UniqueMissionIdStart = 0;
static dZoneManager* m_Address; //Singleton static dZoneManager* m_Address; //Singleton
Zone* m_pZone; Zone* m_pZone = nullptr;
LWOZONEID m_ZoneID; LWOZONEID m_ZoneID;
bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed
std::map<LWOOBJID, Spawner*> m_Spawners; std::map<LWOOBJID, Spawner*> m_Spawners;
WorldConfig* m_WorldConfig = nullptr;
Entity* m_ZoneControlObject; Entity* m_ZoneControlObject = nullptr;
}; };

View File

@ -6,7 +6,7 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
echo "Install build dependencies" && \ echo "Install build dependencies" && \
apt update && \ apt update && \
apt remove -y libmysqlcppconn7v5 libmysqlcppconn-dev && \ 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/* rm -rf /var/lib/apt/lists/*
COPY dAuthServer/ /build/dAuthServer COPY dAuthServer/ /build/dAuthServer
@ -35,12 +35,11 @@ ARG BUILD_VERSION=171022
RUN echo "Build server" && \ RUN echo "Build server" && \
mkdir -p cmake_build && \ mkdir -p cmake_build && \
cd 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 && \ cmake .. -DCMAKE_BUILD_RPATH_USE_ORIGIN=TRUE && \
make -j $BUILD_THREADS make -j $BUILD_THREADS
RUN unzip /build/resources/navmeshes.zip -d /build/cmake_build/res/maps
FROM gcc:11 as runtime FROM gcc:11 as runtime
RUN --mount=type=cache,id=runtime-apt-cache,target=/var/cache/apt \ RUN --mount=type=cache,id=runtime-apt-cache,target=/var/cache/apt \

View File

@ -1,22 +1,11 @@
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 FROM python:3.10-alpine3.14 as prep
RUN apk add sqlite bash --no-cache RUN apk add bash --no-cache
WORKDIR /setup WORKDIR /setup
# copy needed files from repo # copy needed files from repo
COPY resources/ resources/ 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 COPY docker/setup.sh /setup.sh

View File

@ -7,7 +7,7 @@ function update_ini() {
FILE="/docker/configs/$1" FILE="/docker/configs/$1"
KEY=$2 KEY=$2
NEW_VALUE=$3 NEW_VALUE=$3
sed -i "/^$KEY=/s/=.*/=$NEW_VALUE/" $FILE sed -i "s~$2=.*~$2=$3~" $FILE
} }
function update_database_ini_values_for() { function update_database_ini_values_for() {
@ -32,62 +32,11 @@ function update_ini_values() {
cp resources/worldconfig.ini /docker/configs/ cp resources/worldconfig.ini /docker/configs/
cp resources/sharedconfig.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 # always use the internal docker hostname
update_ini masterconfig.ini master_ip "darkflame" update_ini masterconfig.ini master_ip "darkflame"
update_ini sharedconfig.ini client_location "/client"
update_database_ini_values_for sharedconfig.ini 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 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

27
docker/start_server.sh Normal file → Executable file
View File

@ -1,25 +1,5 @@
#!/bin/bash #!/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() { function symlink_config_files() {
echo "Creating symlinks for config files" echo "Creating symlinks for config files"
rm /app/*.ini rm /app/*.ini
@ -30,15 +10,8 @@ function symlink_config_files() {
ln -s /shared_configs/configs/sharedconfig.ini /app/sharedconfig.ini 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 if [[ ! -f "/app/initialized" ]]; then
# setup symlinks for volume files # setup symlinks for volume files
symlink_client_files
symlink_config_files symlink_config_files
# do not run symlinks more than once # do not run symlinks more than once
touch /app/initialized touch /app/initialized

1
thirdparty/LUnpack vendored

@ -1 +0,0 @@
Subproject commit f8d7e442a78910b298fe1cd5780f07c9c9285b8c

@ -1 +0,0 @@
Subproject commit 3f0129e0939ce5ccf41f0808dcbbe71a6243e37f