diff --git a/.env.example b/.env.example index 22661252..6ea77deb 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,16 @@ NET_VERSION=171022 ACCOUNT_MANAGER_SECRET= # Should be the externally facing IP of your server host EXTERNAL_IP=localhost + +# The database type that will be used. +# Acceptable values are `sqlite`, `mysql`, `mariadb`, `maria`. +# Case insensitive. +DATABASE_TYPE=mariadb +SQLITE_DATABASE_PATH=resServer/dlu.sqlite + # Database values # Be careful with special characters here. It is more safe to use normal characters and/or numbers. MARIADB_USER=darkflame MARIADB_PASSWORD= MARIADB_DATABASE=darkflame +SKIP_ACCOUNT_CREATION=1 diff --git a/.gitignore b/.gitignore index 3ad1009e..d7af5d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ docker/configs # Third party libraries thirdparty/mysql/ thirdparty/mysql_linux/ -CMakeVariables.txt # Build folders build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ff4e6b3..be72a3a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,16 +175,18 @@ foreach(resource_file ${RESOURCE_FILES}) list(GET line_split 0 variable_name) if(NOT ${parsed_current_file_contents} MATCHES ${variable_name}) - message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file}) - set(line_to_add ${line_to_add} ${line}) + # For backwards compatibility with older setup versions, dont add this option. + if(NOT ${variable_name} MATCHES "database_type") + message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file}) + set(line_to_add ${line_to_add} ${line}) - foreach(line_to_append ${line_to_add}) - file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append}) - endforeach() + foreach(line_to_append ${line_to_add}) + file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append}) + endforeach() - file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n") + file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n") + endif() endif() - set(line_to_add "") else() set(line_to_add ${line_to_add} ${line}) @@ -214,21 +216,8 @@ foreach(file ${VANITY_FILES}) endforeach() # Move our migrations for MasterServer to run -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/) -file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql) - -foreach(file ${SQL_FILES}) - get_filename_component(file ${file} NAME) - configure_file(${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file}) -endforeach() - -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/cdserver/) -file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/cdserver/*.sql) - -foreach(file ${SQL_FILES}) - get_filename_component(file ${file} NAME) - configure_file(${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file}) -endforeach() +file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/migrations) +file(COPY ${CMAKE_SOURCE_DIR}/migrations DESTINATION ${CMAKE_BINARY_DIR}) # Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux) if (APPLE) diff --git a/README.md b/README.md index 487b68ca..a402a163 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,32 @@ Darkflame Universe is licensed under AGPLv3, please read [LICENSE](LICENSE). Som * You must disclose any changes you make to the code when you distribute it * Hosting a server for others counts as distribution -## Disclaimers -### Setup difficulty -Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous. - ### Hosting a server We do not recommend hosting public servers. Darkflame Universe is intended for small scale deployment, for example within a group of friends. It has not been tested for large scale deployment which comes with additional security risks. ### Supply of resource files Darkflame Universe is a server emulator and does not distribute any LEGO® Universe files. A separate game client is required to setup this server emulator and play the game, which we cannot supply. Users are strongly suggested to refer to the safe checksums listed [here](#verifying-your-client-files) to see if a client will work. -## Step by step walkthrough for a single-player server -If you would like a setup for a single player server only on a Windows machine, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README. +## Setting up a single player server +* If you don't know what WSL is, skip this warning. + Warning: WSL version 1 does NOT support using sqlite as a database due to how it handles filesystem synchronization. + You must use Version 2 if you must run the server under WSL. Not doing so will result in save data loss. +* Single player installs now no longer require building the server from source or installing development tools. +* Download the [latest release](https://github.com/DarkflameUniverse/DarkflameServer/releases) and extract the files into a folder inside your client. +* You should be able to see the folder with the server executables in the same folder as `legouniverse.exe`. +* Open `sharedconfig.ini` and find the line that says `client_location` and put `..` after it so the line reads `client_location=..`. +* To run the server, double-click `MasterServer.exe`. +* You will be asked to create an account the first time you run the server. +* When shutting down the server, it is highly recommended to click the `MasterServer.exe` window and hold `ctrl` while pressing `c` to stop the server. +* We are working on a way to make it so when you close the game, the server saves automatically alongside when you open the game, the server starts automatically. -## Steps to setup server +**If you are not planning on hosting a server for others, working in the codebase or wanting to use MariaDB for a database, you can stop reading here.** + +If you would like to use a MariaDB as a database instead of the default of sqlite, follow the steps [here](#database-setup). + +# Steps to setup a development environment * [Clone this repository](#clone-the-repository) +* [Setting up a development environment](#setting-up-a-development-environment) * [Install dependencies](#install-dependencies) * [Database setup](#database-setup) * [Build the server](#build-the-server) @@ -39,6 +50,13 @@ If you would like a setup for a single player server only on a Windows machine, * [User Guide](#user-guide) * [Docker](#docker) +## Disclaimers +### Setup difficulty +Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous. + +## Step by step walkthrough for building a single-player Windows server from source +If you would like a setup for a single player server only on a Windows machine built from source, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README. + ## Clone the repository If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win) @@ -266,8 +284,8 @@ systemctl stop darkflame.service journalctl -xeu darkflame.service ``` -### First admin user -Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users! +### First user or adding more users. +The first time you run `MasterServer`, you will be prompted to create an account. To create more accounts from the command line, `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users! ### Account management tool (Nexus Dashboard) **If you are just using this server for yourself, you can skip setting up Nexus Dashboard** diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index d020ff72..18fda0ed 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -37,7 +37,6 @@ target_include_directories(dCommon "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" "${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase" - "${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include" ) if (UNIX) diff --git a/dDatabase/GameDatabase/CMakeLists.txt b/dDatabase/GameDatabase/CMakeLists.txt index 32fe414a..fc5500ec 100644 --- a/dDatabase/GameDatabase/CMakeLists.txt +++ b/dDatabase/GameDatabase/CMakeLists.txt @@ -8,6 +8,12 @@ foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES}) set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}") endforeach() +add_subdirectory(SQLite) + +foreach(file ${DDATABSE_DATABSES_SQLITE_SOURCES}) + set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "SQLite/${file}") +endforeach() + add_subdirectory(TestSQL) foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES}) @@ -16,13 +22,14 @@ endforeach() add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES}) target_include_directories(dDatabaseGame PUBLIC "." - "ITables" PRIVATE "MySQL" "TestSQL" + "ITables" PRIVATE "MySQL" "SQLite" "TestSQL" "${PROJECT_SOURCE_DIR}/dCommon" "${PROJECT_SOURCE_DIR}/dCommon/dEnums" ) + target_link_libraries(dDatabaseGame - PUBLIC MariaDB::ConnCpp - INTERFACE dCommon) + INTERFACE dCommon + PRIVATE sqlite3 MariaDB::ConnCpp) # Glob together all headers that need to be precompiled file( diff --git a/dDatabase/GameDatabase/Database.cpp b/dDatabase/GameDatabase/Database.cpp index fef9ab39..73626988 100644 --- a/dDatabase/GameDatabase/Database.cpp +++ b/dDatabase/GameDatabase/Database.cpp @@ -2,22 +2,46 @@ #include "Game.h" #include "dConfig.h" #include "Logger.h" -#include "MySQLDatabase.h" #include "DluAssert.h" +#include "SQLiteDatabase.h" +#include "MySQLDatabase.h" + +#include + #pragma warning (disable:4251) //Disables SQL warnings namespace { GameDatabase* database = nullptr; } +std::string Database::GetMigrationFolder() { + const std::set validMysqlTypes = { "mysql", "mariadb", "maria" }; + auto databaseType = Game::config->GetValue("database_type"); + std::ranges::transform(databaseType, databaseType.begin(), ::tolower); + if (databaseType == "sqlite") return "sqlite"; + else if (validMysqlTypes.contains(databaseType)) return "mysql"; + else { + LOG("No database specified, using MySQL"); + return "mysql"; + } +} + void Database::Connect() { if (database) { LOG("Tried to connect to database when it's already connected!"); return; } - database = new MySQLDatabase(); + const auto databaseType = GetMigrationFolder(); + + if (databaseType == "sqlite") database = new SQLiteDatabase(); + else if (databaseType == "mysql") database = new MySQLDatabase(); + else { + LOG("Invalid database type specified in config, using MySQL"); + database = new MySQLDatabase(); + } + database->Connect(); } diff --git a/dDatabase/GameDatabase/Database.h b/dDatabase/GameDatabase/Database.h index cd0e93e3..cb74431c 100644 --- a/dDatabase/GameDatabase/Database.h +++ b/dDatabase/GameDatabase/Database.h @@ -12,4 +12,6 @@ namespace Database { // Used for assigning a test database as the handler for database logic. // Do not use in production code. void _setDatabase(GameDatabase* const db); + + std::string GetMigrationFolder(); }; diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index dcfac4a2..d0b5c866 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -26,13 +26,8 @@ #include "IBehaviors.h" #include "IUgcModularBuild.h" -namespace sql { - class Statement; - class PreparedStatement; -}; - #ifdef _DEBUG -# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0) +# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0) #else # define DLU_SQL_TRY_CATCH_RETHROW(x) x #endif // _DEBUG @@ -50,7 +45,6 @@ public: virtual void Connect() = 0; virtual void Destroy(std::string source = "") = 0; virtual void ExecuteCustomQuery(const std::string_view query) = 0; - virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0; virtual void Commit() = 0; virtual bool GetAutoCommit() = 0; virtual void SetAutoCommit(bool value) = 0; diff --git a/dDatabase/GameDatabase/ITables/IAccounts.h b/dDatabase/GameDatabase/ITables/IAccounts.h index a0377f4b..13ecf29b 100644 --- a/dDatabase/GameDatabase/ITables/IAccounts.h +++ b/dDatabase/GameDatabase/ITables/IAccounts.h @@ -36,6 +36,8 @@ public: // Update the GameMaster level of an account. virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0; + + virtual uint32_t GetAccountCount() = 0; }; #endif //!__IACCOUNTS__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp index 20e92677..26693631 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp @@ -14,6 +14,7 @@ namespace { }; void MySQLDatabase::Connect() { + LOG("Using MySQL database"); driver = sql::mariadb::get_driver_instance(); // The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where @@ -67,7 +68,7 @@ void MySQLDatabase::ExecuteCustomQuery(const std::string_view query) { sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) { if (!con) { - Connect(); + Database::Get()->Connect(); LOG("Trying to reconnect to MySQL"); } @@ -76,7 +77,7 @@ sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& quer con = nullptr; - Connect(); + Database::Get()->Connect(); LOG("Trying to reconnect to MySQL from invalid or closed connection"); } diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 29fd7ea8..08168141 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -30,7 +30,6 @@ public: void Connect() override; void Destroy(std::string source = "") override; - sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override; void Commit() override; bool GetAutoCommit() override; void SetAutoCommit(bool value) override; @@ -125,6 +124,8 @@ public: void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override; void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; void DeleteUgcBuild(const LWOOBJID bigId) override; + sql::PreparedStatement* CreatePreppedStmt(const std::string& query); + uint32_t GetAccountCount() override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp index 9e9812f3..f4310dd8 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp @@ -39,3 +39,8 @@ void MySQLDatabase::InsertNewAccount(const std::string_view username, const std: void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) { ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast(gmLevel), accountId); } + +uint32_t MySQLDatabase::GetAccountCount() { + auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;"); + return res->next() ? res->getUInt("count") : 0; +} diff --git a/dDatabase/GameDatabase/SQLite/CMakeLists.txt b/dDatabase/GameDatabase/SQLite/CMakeLists.txt new file mode 100644 index 00000000..6553ad01 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/CMakeLists.txt @@ -0,0 +1,11 @@ +SET(DDATABSE_DATABSES_SQLITE_SOURCES + "SQLiteDatabase.cpp" +) + +add_subdirectory(Tables) + +foreach(file ${DDATABASES_DATABASES_SQLITE_TABLES_SOURCES}) + set(DDATABSE_DATABSES_SQLITE_SOURCES ${DDATABSE_DATABSES_SQLITE_SOURCES} "Tables/${file}") +endforeach() + +set(DDATABSE_DATABSES_SQLITE_SOURCES ${DDATABSE_DATABSES_SQLITE_SOURCES} PARENT_SCOPE) diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp new file mode 100644 index 00000000..635ca8fb --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp @@ -0,0 +1,73 @@ +#include "SQLiteDatabase.h" + +#include "Database.h" +#include "Game.h" +#include "dConfig.h" +#include "Logger.h" +#include "dPlatforms.h" + +// Static Variables + +// Status Variables +namespace { + CppSQLite3DB* con = nullptr; + bool isConnected = false; +}; + +void SQLiteDatabase::Connect() { + LOG("Using SQLite database"); + con = new CppSQLite3DB(); + con->open(Game::config->GetValue("sqlite_database_path").c_str()); + isConnected = true; + + // Make sure wal is enabled for the database. + con->execQuery("PRAGMA journal_mode = WAL;"); +} + +void SQLiteDatabase::Destroy(std::string source) { + if (!con) return; + + if (source.empty()) LOG("Destroying SQLite connection!"); + else LOG("Destroying SQLite connection from %s!", source.c_str()); + + con->close(); + delete con; + con = nullptr; +} + +void SQLiteDatabase::ExecuteCustomQuery(const std::string_view query) { + con->compileStatement(query.data()).execDML(); +} + +CppSQLite3Statement SQLiteDatabase::CreatePreppedStmt(const std::string& query) { + return con->compileStatement(query.c_str()); +} + +void SQLiteDatabase::Commit() { + if (!con->IsAutoCommitOn()) con->compileStatement("COMMIT;").execDML(); +} + +bool SQLiteDatabase::GetAutoCommit() { + return con->IsAutoCommitOn(); +} + +void SQLiteDatabase::SetAutoCommit(bool value) { + if (value) { + if (GetAutoCommit()) con->compileStatement("BEGIN;").execDML(); + } else { + if (!GetAutoCommit()) con->compileStatement("COMMIT;").execDML(); + } +} + +void SQLiteDatabase::DeleteCharacter(const uint32_t characterId) { + ExecuteDelete("DELETE FROM charxml WHERE id=?;", characterId); + ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId); + ExecuteDelete("DELETE FROM leaderboard WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId); + ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId); + ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId); + ExecuteDelete("DELETE FROM charinfo WHERE id=?;", characterId); +} diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h new file mode 100644 index 00000000..a09c72c9 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -0,0 +1,270 @@ +#ifndef SQLITEDATABASE_H +#define SQLITEDATABASE_H + +#include "CppSQLite3.h" + +#include "GameDatabase.h" + +using PreppedStmtRef = CppSQLite3Statement&; + +// Purposefully no definition for this to provide linker errors in the case someone tries to +// bind a parameter to a type that isn't defined. +template +inline void SetParam(PreppedStmtRef stmt, const int index, const ParamType param); + +// This is a function to set each parameter in a prepared statement. +// This is accomplished with a combination of parameter packing and Fold Expressions. +// The constexpr if statement is used to prevent the compiler from trying to call SetParam with 0 arguments. +template +void SetParams(PreppedStmtRef stmt, Args&&... args) { + if constexpr (sizeof...(args) != 0) { + int i = 1; + (SetParam(stmt, i++, args), ...); + } +} + +class SQLiteDatabase : public GameDatabase { +public: + void Connect() override; + void Destroy(std::string source = "") override; + + void Commit() override; + bool GetAutoCommit() override; + void SetAutoCommit(bool value) override; + void ExecuteCustomQuery(const std::string_view query) override; + + // Overloaded queries + std::optional GetMasterInfo() override; + + std::vector GetApprovedCharacterNames() override; + + std::vector GetFriendsList(uint32_t charID) override; + + std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override; + void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override; + void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; + void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; + void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override; + void DeleteUgcModelData(const LWOOBJID& modelId) override; + void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override; + std::vector GetAllUgcModels() override; + void CreateMigrationHistoryTable() override; + bool IsMigrationRun(const std::string_view str) override; + void InsertMigration(const std::string_view str) override; + std::optional GetCharacterInfo(const uint32_t charId) override; + std::optional GetCharacterInfo(const std::string_view charId) override; + std::string GetCharacterXml(const uint32_t accountId) override; + void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override; + std::optional GetAccountInfo(const std::string_view username) override; + void InsertNewCharacter(const ICharInfo::Info info) override; + void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override; + std::vector GetAccountCharacterIds(uint32_t accountId) override; + void DeleteCharacter(const uint32_t characterId) override; + void SetCharacterName(const uint32_t characterId, const std::string_view name) override; + void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override; + void UpdateLastLoggedInCharacter(const uint32_t characterId) override; + void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; + std::optional GetPetNameInfo(const LWOOBJID& petId) override; + std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; + void UpdatePropertyModerationInfo(const IProperty::Info& info) override; + void UpdatePropertyDetails(const IProperty::Info& info) override; + void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override; + std::vector GetPropertyModels(const LWOOBJID& propertyId) override; + void RemoveUnreferencedUgcModels() override; + void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; + void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) override; + void RemoveModel(const LWOOBJID& modelId) override; + void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; + void InsertNewBugReport(const IBugReports::Info& info) override; + void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override; + void InsertNewMail(const IMail::MailInfo& mail) override; + void InsertNewUgcModel( + std::istringstream& sd0Data, + const uint32_t blueprintId, + const uint32_t accountId, + const uint32_t characterId) override; + std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; + std::optional GetMail(const uint64_t mailId) override; + uint32_t GetUnreadMailCount(const uint32_t characterId) override; + void MarkMailRead(const uint64_t mailId) override; + void DeleteMail(const uint64_t mailId) override; + void ClaimMailItem(const uint64_t mailId) override; + void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override; + void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; + void UpdateAccountBan(const uint32_t accountId, const bool banned) override; + void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; + void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; + void SetMasterIp(const std::string_view ip, const uint32_t port) override; + std::optional GetCurrentPersistentId() override; + void InsertDefaultPersistentId() override; + void UpdatePersistentId(const uint32_t id) override; + std::optional GetDonationTotal(const uint32_t activityId) override; + std::optional IsPlaykeyActive(const int32_t playkeyId) override; + std::vector GetUgcModels(const LWOOBJID& propertyId) override; + void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; + void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; + std::vector GetIgnoreList(const uint32_t playerId) override; + void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; + std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; + void AddBehavior(const IBehaviors::Info& info) override; + std::string GetBehavior(const int32_t behaviorId) override; + void RemoveBehavior(const int32_t characterId) override; + void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; + std::optional GetProperties(const IProperty::PropertyLookup& params) override; + std::vector GetDescendingLeaderboard(const uint32_t activityId) override; + std::vector GetAscendingLeaderboard(const uint32_t activityId) override; + std::vector GetNsLeaderboard(const uint32_t activityId) override; + std::vector GetAgsLeaderboard(const uint32_t activityId) override; + void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; + void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; + std::optional GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override; + void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override; + void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override; + void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; + void DeleteUgcBuild(const LWOOBJID bigId) override; + uint32_t GetAccountCount() override; +private: + CppSQLite3Statement CreatePreppedStmt(const std::string& query); + + // Generic query functions that can be used for any query. + // Return type may be different depending on the query, so it is up to the caller to check the return type. + // The first argument is the query string, and the rest are the parameters to bind to the query. + // The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope + template + inline std::pair ExecuteSelect(const std::string& query, Args&&... args) { + std::pair toReturn; + toReturn.first = CreatePreppedStmt(query); + SetParams(toReturn.first, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(toReturn.second = toReturn.first.execQuery()); + return toReturn; + } + + template + inline void ExecuteDelete(const std::string& query, Args&&... args) { + auto preppedStmt = CreatePreppedStmt(query); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(preppedStmt.execDML()); + } + + template + inline int32_t ExecuteUpdate(const std::string& query, Args&&... args) { + auto preppedStmt = CreatePreppedStmt(query); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt.execDML()); + } + + template + inline int ExecuteInsert(const std::string& query, Args&&... args) { + auto preppedStmt = CreatePreppedStmt(query); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt.execDML()); + } +}; + +// Below are each of the definitions of SetParam for each supported type. + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) { + LOG("%s", param.data()); + stmt.bind(index, param.data()); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) { + LOG("%s", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) { + LOG("%s", param.c_str()); + stmt.bind(index, param.c_str()); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) { + LOG("%u", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) { + LOG("%d", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) { + LOG("%u", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) { + LOG("%d", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) { + LOG("%u", param); + stmt.bind(index, static_cast(param)); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) { + LOG("%d", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) { + LOG("%llu", param); + stmt.bind(index, static_cast(param)); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) { + LOG("%llu", param); + stmt.bind(index, static_cast(param)); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const float param) { + LOG("%f", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const double param) { + LOG("%f", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) { + LOG("%d", param); + stmt.bind(index, param); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) { + LOG("Blob"); + // This is the one time you will ever see me use const_cast. + std::stringstream stream; + stream << param->rdbuf(); + stmt.bind(index, reinterpret_cast(stream.str().c_str()), stream.str().size()); +} + +template<> +inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional param) { + if (param) { + LOG("%d", param.value()); + stmt.bind(index, static_cast(param.value())); + } else { + LOG("Null"); + stmt.bindNull(index); + } +} + +#endif //!SQLITEDATABASE_H diff --git a/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp b/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp new file mode 100644 index 00000000..9431d407 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp @@ -0,0 +1,49 @@ +#include "SQLiteDatabase.h" + +#include "eGameMasterLevel.h" +#include "Database.h" + +std::optional SQLiteDatabase::GetAccountInfo(const std::string_view username) { + auto [_, result] = ExecuteSelect("SELECT * FROM accounts WHERE name = ? LIMIT 1", username); + + if (result.eof()) { + return std::nullopt; + } + + IAccounts::Info toReturn; + toReturn.id = result.getIntField("id"); + toReturn.maxGmLevel = static_cast(result.getIntField("gm_level")); + toReturn.bcryptPassword = result.getStringField("password"); + toReturn.banned = result.getIntField("banned"); + toReturn.locked = result.getIntField("locked"); + toReturn.playKeyId = result.getIntField("play_key_id"); + + return toReturn; +} + +void SQLiteDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) { + ExecuteUpdate("UPDATE accounts SET mute_expire = ? WHERE id = ?;", timeToUnmute, accountId); +} + +void SQLiteDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) { + ExecuteUpdate("UPDATE accounts SET banned = ? WHERE id = ?;", banned, accountId); +} + +void SQLiteDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) { + ExecuteUpdate("UPDATE accounts SET password = ? WHERE id = ?;", bcryptpassword, accountId); +} + +void SQLiteDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) { + ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast(eGameMasterLevel::OPERATOR)); +} + +void SQLiteDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) { + ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast(gmLevel), accountId); +} + +uint32_t SQLiteDatabase::GetAccountCount() { + auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;"); + if (res.eof()) return 0; + + return res.getIntField("count"); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp b/dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp new file mode 100644 index 00000000..0359ee69 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp @@ -0,0 +1,17 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) { + ExecuteInsert("INSERT OR IGNORE INTO accounts_rewardcodes (account_id, rewardcode) VALUES (?, ?);", account_id, reward_code); +} + +std::vector SQLiteDatabase::GetRewardCodesByAccountID(const uint32_t account_id) { + auto [_, result] = ExecuteSelect("SELECT rewardcode FROM accounts_rewardcodes WHERE account_id = ?;", account_id); + + std::vector toReturn; + while (!result.eof()) { + toReturn.push_back(result.getIntField("rewardcode")); + result.nextRow(); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp b/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp new file mode 100644 index 00000000..33f81429 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp @@ -0,0 +1,6 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) { + ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);", + characterId, static_cast(activityType), static_cast(time(NULL)), mapId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp b/dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp new file mode 100644 index 00000000..05cadbcd --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp @@ -0,0 +1,19 @@ +#include "IBehaviors.h" + +#include "SQLiteDatabase.h" + +void SQLiteDatabase::AddBehavior(const IBehaviors::Info& info) { + ExecuteInsert( + "INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON CONFLICT(behavior_id) DO UPDATE SET behavior_info = ?", + info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo + ); +} + +void SQLiteDatabase::RemoveBehavior(const int32_t behaviorId) { + ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); +} + +std::string SQLiteDatabase::GetBehavior(const int32_t behaviorId) { + auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); + return !result.eof() ? result.getStringField("behavior_info") : ""; +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp b/dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp new file mode 100644 index 00000000..f4960941 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp @@ -0,0 +1,6 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertNewBugReport(const IBugReports::Info& info) { + ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)", + info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt b/dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt new file mode 100644 index 00000000..91d5b5e2 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt @@ -0,0 +1,26 @@ +set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES + "Accounts.cpp" + "AccountsRewardCodes.cpp" + "ActivityLog.cpp" + "Behaviors.cpp" + "BugReports.cpp" + "CharInfo.cpp" + "CharXml.cpp" + "CommandLog.cpp" + "Friends.cpp" + "IgnoreList.cpp" + "Leaderboard.cpp" + "Mail.cpp" + "MigrationHistory.cpp" + "ObjectIdTracker.cpp" + "PetNames.cpp" + "PlayerCheatDetections.cpp" + "PlayKeys.cpp" + "Property.cpp" + "PropertyContents.cpp" + "Servers.cpp" + "Ugc.cpp" + "UgcModularBuild.cpp" + PARENT_SCOPE +) + diff --git a/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp b/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp new file mode 100644 index 00000000..27ae3611 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp @@ -0,0 +1,79 @@ +#include "SQLiteDatabase.h" + +std::vector SQLiteDatabase::GetApprovedCharacterNames() { + auto [_, result] = ExecuteSelect("SELECT name FROM charinfo;"); + + std::vector toReturn; + + while (!result.eof()) { + toReturn.push_back(result.getStringField("name")); + result.nextRow(); + } + + return toReturn; +} + +std::optional CharInfoFromQueryResult(CppSQLite3Query stmt) { + if (stmt.eof()) { + return std::nullopt; + } + + ICharInfo::Info toReturn; + + toReturn.id = stmt.getIntField("id"); + toReturn.name = stmt.getStringField("name"); + toReturn.pendingName = stmt.getStringField("pending_name"); + toReturn.needsRename = stmt.getIntField("needs_rename"); + toReturn.cloneId = stmt.getInt64Field("prop_clone_id"); + toReturn.accountId = stmt.getIntField("account_id"); + toReturn.permissionMap = static_cast(stmt.getIntField("permission_map")); + + return toReturn; +} + +std::optional SQLiteDatabase::GetCharacterInfo(const uint32_t charId) { + return CharInfoFromQueryResult( + ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId).second + ); +} + +std::optional SQLiteDatabase::GetCharacterInfo(const std::string_view name) { + return CharInfoFromQueryResult( + ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name).second + ); +} + +std::vector SQLiteDatabase::GetAccountCharacterIds(const uint32_t accountId) { + auto [_, result] = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId); + + std::vector toReturn; + while (!result.eof()) { + toReturn.push_back(result.getIntField("id")); + result.nextRow(); + } + + return toReturn; +} + +void SQLiteDatabase::InsertNewCharacter(const ICharInfo::Info info) { + ExecuteInsert( + "INSERT INTO `charinfo`(`id`, `account_id`, `name`, `pending_name`, `needs_rename`, `last_login`, `prop_clone_id`) VALUES (?,?,?,?,?,?,(SELECT IFNULL(MAX(`prop_clone_id`), 0) + 1 FROM `charinfo`))", + info.id, + info.accountId, + info.name, + info.pendingName, + false, + static_cast(time(NULL))); +} + +void SQLiteDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) { + ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast(time(NULL)), characterId); +} + +void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) { + ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast(time(NULL)), characterId); +} + +void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { + ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast(time(NULL)), characterId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp b/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp new file mode 100644 index 00000000..56085101 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp @@ -0,0 +1,19 @@ +#include "SQLiteDatabase.h" + +std::string SQLiteDatabase::GetCharacterXml(const uint32_t charId) { + auto [_, result] = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId); + + if (result.eof()) { + return ""; + } + + return result.getStringField("xml_data"); +} + +void SQLiteDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) { + ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId); +} + +void SQLiteDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) { + ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp b/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp new file mode 100644 index 00000000..db39046f --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp @@ -0,0 +1,5 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) { + ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp b/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp new file mode 100644 index 00000000..7ac41459 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp @@ -0,0 +1,73 @@ +#include "SQLiteDatabase.h" + +std::vector SQLiteDatabase::GetFriendsList(const uint32_t charId) { + auto [_, friendsList] = ExecuteSelect( + R"QUERY( + SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM + ( + SELECT CASE + WHEN player_id = ? THEN friend_id + WHEN friend_id = ? THEN player_id + END AS requested_player, best_friend FROM friends + ) AS fr + JOIN charinfo AS ci ON ci.id = fr.requested_player + WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?; + )QUERY", charId, charId, charId); + + std::vector toReturn; + + while (!friendsList.eof()) { + FriendData fd; + fd.friendID = friendsList.getIntField("player"); + fd.isBestFriend = friendsList.getIntField("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs + fd.friendName = friendsList.getStringField("name"); + + toReturn.push_back(fd); + friendsList.nextRow(); + } + + return toReturn; +} + +std::optional SQLiteDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + auto [_, result] = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); + + if (result.eof()) { + return std::nullopt; + } + + IFriends::BestFriendStatus toReturn; + toReturn.playerCharacterId = result.getIntField("player_id"); + toReturn.friendCharacterId = result.getIntField("friend_id"); + toReturn.bestFriendStatus = result.getIntField("best_friend"); + + return toReturn; +} + +void SQLiteDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) { + ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);", + bestFriendStatus, + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); +} + +void SQLiteDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + ExecuteInsert("INSERT OR IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId); +} + +void SQLiteDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);", + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp b/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp new file mode 100644 index 00000000..e7f5a3e0 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp @@ -0,0 +1,22 @@ +#include "SQLiteDatabase.h" + +std::vector SQLiteDatabase::GetIgnoreList(const uint32_t playerId) { + auto [_, result] = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId); + + std::vector ignoreList; + + while (!result.eof()) { + ignoreList.push_back(IIgnoreList::Info{ result.getStringField("name"), static_cast(result.getIntField("ignore_id")) }); + result.nextRow(); + } + + return ignoreList; +} + +void SQLiteDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { + ExecuteInsert("INSERT OR IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId); +} + +void SQLiteDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { + ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp b/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp new file mode 100644 index 00000000..ee0423dd --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp @@ -0,0 +1,91 @@ +#include "SQLiteDatabase.h" + +#include "Game.h" +#include "Logger.h" +#include "dConfig.h" + +std::optional SQLiteDatabase::GetDonationTotal(const uint32_t activityId) { + auto [_, donation_total] = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId); + + if (donation_total.eof()) { + return std::nullopt; + } + + return donation_total.getIntField("donation_total"); +} + +std::vector ProcessQuery(CppSQLite3Query& rows) { + std::vector entries; + + while (!rows.eof()) { + auto& entry = entries.emplace_back(); + + entry.charId = rows.getIntField("character_id"); + entry.lastPlayedTimestamp = rows.getIntField("lp_unix"); + entry.primaryScore = rows.getFloatField("primaryScore"); + entry.secondaryScore = rows.getFloatField("secondaryScore"); + entry.tertiaryScore = rows.getFloatField("tertiaryScore"); + entry.numWins = rows.getIntField("numWins"); + entry.numTimesPlayed = rows.getIntField("timesPlayed"); + entry.name = rows.getStringField("char_name"); + // entry.ranking is never set because its calculated in leaderboard in code. + rows.nextRow(); + } + + return entries; +} + +std::vector SQLiteDatabase::GetDescendingLeaderboard(const uint32_t activityId) { + auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;", activityId); + return ProcessQuery(result); +} + +std::vector SQLiteDatabase::GetAscendingLeaderboard(const uint32_t activityId) { + auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore ASC, secondaryscore ASC, tertiaryScore ASC, last_played ASC;", activityId); + return ProcessQuery(result); +} + +std::vector SQLiteDatabase::GetAgsLeaderboard(const uint32_t activityId) { + auto query = Game::config->GetValue("classic_survival_scoring") != "1" ? + "SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;" : + "SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY secondaryscore DESC, primaryscore DESC, tertiaryScore DESC, last_played ASC;"; + auto [_, result] = ExecuteSelect(query, activityId); + return ProcessQuery(result); +} + +std::vector SQLiteDatabase::GetNsLeaderboard(const uint32_t activityId) { + auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore ASC, tertiaryScore DESC, last_played ASC;", activityId); + return ProcessQuery(result); +} + +void SQLiteDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { + ExecuteInsert("INSERT INTO leaderboard (primaryScore, secondaryScore, tertiaryScore, character_id, game_id, last_played) VALUES (?,?,?,?,?,CURRENT_TIMESTAMP) ;", + score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); +} + +void SQLiteDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { + ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", + score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); +} + +std::optional SQLiteDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) { + std::optional toReturn = std::nullopt; + auto [_, res] = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId); + if (!res.eof()) { + toReturn = ILeaderboard::Score{ + .primaryScore = static_cast(res.getFloatField("primaryScore")), + .secondaryScore = static_cast(res.getFloatField("secondaryScore")), + .tertiaryScore = static_cast(res.getFloatField("tertiaryScore")) + }; + } + + return toReturn; +} + +void SQLiteDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) { + ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId); +} + +void SQLiteDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) { + ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp new file mode 100644 index 00000000..48c1e320 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp @@ -0,0 +1,83 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) { + ExecuteInsert( + "INSERT INTO `mail` " + "(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)" + " VALUES (?,?,?,?,?,?,?,?,?,?,?,0)", + mail.senderId, + mail.senderUsername, + mail.receiverId, + mail.recipient, + static_cast(time(NULL)), + mail.subject, + mail.body, + mail.itemID, + mail.itemLOT, + 0, + mail.itemCount); +} + +std::vector SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { + auto [_, res] = ExecuteSelect( + "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" + " FROM mail WHERE receiver_id=? limit ?;", + characterId, numberOfMail); + + std::vector toReturn; + + while (!res.eof()) { + IMail::MailInfo mail; + mail.id = res.getInt64Field("id"); + mail.subject = res.getStringField("subject"); + mail.body = res.getStringField("body"); + mail.senderUsername = res.getStringField("sender_name"); + mail.itemID = res.getIntField("attachment_id"); + mail.itemLOT = res.getIntField("attachment_lot"); + mail.itemSubkey = res.getIntField("attachment_subkey"); + mail.itemCount = res.getIntField("attachment_count"); + mail.timeSent = res.getInt64Field("time_sent"); + mail.wasRead = res.getIntField("was_read"); + + toReturn.push_back(std::move(mail)); + res.nextRow(); + } + + return toReturn; +} + +std::optional SQLiteDatabase::GetMail(const uint64_t mailId) { + auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); + + if (res.eof()) { + return std::nullopt; + } + + IMail::MailInfo toReturn; + toReturn.itemLOT = res.getIntField("attachment_lot"); + toReturn.itemCount = res.getIntField("attachment_count"); + + return toReturn; +} + +uint32_t SQLiteDatabase::GetUnreadMailCount(const uint32_t characterId) { + auto [_, res] = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId); + + if (res.eof()) { + return 0; + } + + return res.getIntField("number_unread"); +} + +void SQLiteDatabase::MarkMailRead(const uint64_t mailId) { + ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=?;", mailId); +} + +void SQLiteDatabase::ClaimMailItem(const uint64_t mailId) { + ExecuteUpdate("UPDATE mail SET attachment_lot=0 WHERE id=?;", mailId); +} + +void SQLiteDatabase::DeleteMail(const uint64_t mailId) { + ExecuteDelete("DELETE FROM mail WHERE id=?;", mailId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp b/dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp new file mode 100644 index 00000000..dbb1c268 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp @@ -0,0 +1,13 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::CreateMigrationHistoryTable() { + ExecuteInsert("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);"); +} + +bool SQLiteDatabase::IsMigrationRun(const std::string_view str) { + return !ExecuteSelect("SELECT name FROM migration_history WHERE name = ?;", str).second.eof(); +} + +void SQLiteDatabase::InsertMigration(const std::string_view str) { + ExecuteInsert("INSERT INTO migration_history (name) VALUES (?);", str); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp new file mode 100644 index 00000000..af8014dd --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp @@ -0,0 +1,17 @@ +#include "SQLiteDatabase.h" + +std::optional SQLiteDatabase::GetCurrentPersistentId() { + auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); + if (result.eof()) { + return std::nullopt; + } + return result.getIntField("last_object_id"); +} + +void SQLiteDatabase::InsertDefaultPersistentId() { + ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); +} + +void SQLiteDatabase::UpdatePersistentId(const uint32_t newId) { + ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp b/dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp new file mode 100644 index 00000000..2216e1d0 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp @@ -0,0 +1,26 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) { + ExecuteInsert( + "INSERT INTO `pet_names` (`id`, `pet_name`, `approved`) VALUES (?, ?, ?) " + "ON CONFLICT(id) DO UPDATE SET pet_name = ?, approved = ?;", + petId, + info.petName, + info.approvalStatus, + info.petName, + info.approvalStatus); +} + +std::optional SQLiteDatabase::GetPetNameInfo(const LWOOBJID& petId) { + auto [_, result] = ExecuteSelect("SELECT pet_name, approved FROM pet_names WHERE id = ? LIMIT 1;", petId); + + if (result.eof()) { + return std::nullopt; + } + + IPetNames::Info toReturn; + toReturn.petName = result.getStringField("pet_name"); + toReturn.approvalStatus = result.getIntField("approved"); + + return toReturn; +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp b/dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp new file mode 100644 index 00000000..1900de97 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp @@ -0,0 +1,11 @@ +#include "SQLiteDatabase.h" + +std::optional SQLiteDatabase::IsPlaykeyActive(const int32_t playkeyId) { + auto [_, keyCheckRes] = ExecuteSelect("SELECT active FROM `play_keys` WHERE id=?", playkeyId); + + if (keyCheckRes.eof()) { + return std::nullopt; + } + + return keyCheckRes.getIntField("active"); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PlayerCheatDetections.cpp b/dDatabase/GameDatabase/SQLite/Tables/PlayerCheatDetections.cpp new file mode 100644 index 00000000..a47ae340 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/PlayerCheatDetections.cpp @@ -0,0 +1,7 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& info) { + ExecuteInsert( + "INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)", + info.userId, info.username, info.extraMessage, info.systemAddress); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp new file mode 100644 index 00000000..7374e941 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp @@ -0,0 +1,195 @@ +#include "SQLiteDatabase.h" +#include "ePropertySortType.h" + +std::optional SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { + std::optional result; + std::string query; + std::pair propertiesRes; + + if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + AND p.owner_id IN ( + SELECT fr.requested_player AS player FROM ( + SELECT CASE + WHEN player_id = ? THEN friend_id + WHEN friend_id = ? THEN player_id + END AS requested_player FROM friends + ) AS fr + JOIN charinfo AS ci ON ci.id = fr.requested_player + WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ? + ) ORDER BY ci.name ASC + )QUERY"; + const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;"; + propertiesRes = ExecuteSelect( + completeQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.playerId, + params.playerId, + params.playerId, + params.numResults, + params.startIndex + ); + const auto countQuery = "SELECT COUNT(*) as count" + query + ";"; + auto [_, count] = ExecuteSelect( + countQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.playerId, + params.playerId, + params.playerId + ); + if (!count.eof()) { + result = IProperty::PropertyEntranceResult(); + result->totalEntriesMatchingQuery = count.getIntField("count"); + } + } else { + if (params.sortChoice == SORT_TYPE_REPUTATION) { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + ORDER BY p.reputation DESC, p.last_updated DESC + )QUERY"; + } else { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + ORDER BY p.last_updated DESC + )QUERY"; + } + const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;"; + propertiesRes = ExecuteSelect( + completeQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.numResults, + params.startIndex + ); + const auto countQuery = "SELECT COUNT(*) as count" + query + ";"; + auto [_, count] = ExecuteSelect( + countQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort + ); + if (!count.eof()) { + result = IProperty::PropertyEntranceResult(); + result->totalEntriesMatchingQuery = count.getIntField("count"); + } + } + + auto& [_, properties] = propertiesRes; + if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult(); + while (!properties.eof()) { + auto& entry = result->entries.emplace_back(); + entry.id = properties.getInt64Field("id"); + entry.ownerId = properties.getInt64Field("owner_id"); + entry.cloneId = properties.getInt64Field("clone_id"); + entry.name = properties.getStringField("name"); + entry.description = properties.getStringField("description"); + entry.privacyOption = properties.getIntField("privacy_option"); + entry.rejectionReason = properties.getStringField("rejection_reason"); + entry.lastUpdatedTime = properties.getIntField("last_updated"); + entry.claimedTime = properties.getIntField("time_claimed"); + entry.reputation = properties.getIntField("reputation"); + entry.modApproved = properties.getIntField("mod_approved"); + entry.performanceCost = properties.getFloatField("performance_cost"); + properties.nextRow(); + } + + return result; +} + +std::optional SQLiteDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) { + auto [_, propertyEntry] = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " + "FROM properties WHERE zone_id = ? AND clone_id = ?;", mapId, cloneId); + + if (propertyEntry.eof()) { + return std::nullopt; + } + + IProperty::Info toReturn; + toReturn.id = propertyEntry.getInt64Field("id"); + toReturn.ownerId = propertyEntry.getInt64Field("owner_id"); + toReturn.cloneId = propertyEntry.getInt64Field("clone_id"); + toReturn.name = propertyEntry.getStringField("name"); + toReturn.description = propertyEntry.getStringField("description"); + toReturn.privacyOption = propertyEntry.getIntField("privacy_option"); + toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason"); + toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated"); + toReturn.claimedTime = propertyEntry.getIntField("time_claimed"); + toReturn.reputation = propertyEntry.getIntField("reputation"); + toReturn.modApproved = propertyEntry.getIntField("mod_approved"); + toReturn.performanceCost = propertyEntry.getFloatField("performance_cost"); + + return toReturn; +} + +void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { + ExecuteUpdate("UPDATE properties SET privacy_option = ?, rejection_reason = ?, mod_approved = ? WHERE id = ?;", + info.privacyOption, + info.rejectionReason, + info.modApproved, + info.id); +} + +void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) { + ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id); +} + +void SQLiteDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) { + ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ?;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID()); +} + +void SQLiteDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) { + auto insertion = ExecuteInsert( + "INSERT INTO properties" + " (id, owner_id, template_id, clone_id, name, description, zone_id, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, performance_cost)" + " VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, 0, CAST(strftime('%s', 'now') as INT), CAST(strftime('%s', 'now') as INT), '', 0, 0.0)", + info.id, + info.ownerId, + templateId, + zoneId.GetCloneID(), + info.name, + info.description, + zoneId.GetMapID() + ); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp new file mode 100644 index 00000000..6a8d7028 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp @@ -0,0 +1,65 @@ +#include "SQLiteDatabase.h" + +std::vector SQLiteDatabase::GetPropertyModels(const LWOOBJID& propertyId) { + auto [_, result] = ExecuteSelect( + "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "FROM properties_contents WHERE property_id = ?;", propertyId); + + std::vector toReturn; + while (!result.eof()) { + IPropertyContents::Model model; + model.id = result.getInt64Field("id"); + model.lot = static_cast(result.getIntField("lot")); + model.position.x = result.getFloatField("x"); + model.position.y = result.getFloatField("y"); + model.position.z = result.getFloatField("z"); + model.rotation.w = result.getFloatField("rw"); + model.rotation.x = result.getFloatField("rx"); + model.rotation.y = result.getFloatField("ry"); + model.rotation.z = result.getFloatField("rz"); + model.ugcId = result.getInt64Field("ugc_id"); + model.behaviors[0] = result.getIntField("behavior_1"); + model.behaviors[1] = result.getIntField("behavior_2"); + model.behaviors[2] = result.getIntField("behavior_3"); + model.behaviors[3] = result.getIntField("behavior_4"); + model.behaviors[4] = result.getIntField("behavior_5"); + + toReturn.push_back(std::move(model)); + result.nextRow(); + } + return toReturn; +} + +void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) { + try { + ExecuteInsert( + "INSERT INTO properties_contents" + "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 + model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), + model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, + name, "", // Model description. TODO implement this. + model.behaviors[0], // behavior 1 + model.behaviors[1], // behavior 2 + model.behaviors[2], // behavior 3 + model.behaviors[3], // behavior 4 + model.behaviors[4] // behavior 5 + ); + } catch (std::exception& e) { + LOG("Error inserting new property model: %s", e.what()); + } +} + +void SQLiteDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) { + ExecuteUpdate( + "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " + "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId); +} + +void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) { + ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Servers.cpp b/dDatabase/GameDatabase/SQLite/Tables/Servers.cpp new file mode 100644 index 00000000..8c136a30 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Servers.cpp @@ -0,0 +1,23 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) { + // We only want our 1 entry anyways, so we can just delete all and reinsert the one we want + // since it would be two queries anyways. + ExecuteDelete("DELETE FROM servers;"); + ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port); +} + +std::optional SQLiteDatabase::GetMasterInfo() { + auto [_, result] = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;"); + + if (result.eof()) { + return std::nullopt; + } + + MasterInfo toReturn; + + toReturn.ip = result.getStringField("ip"); + toReturn.port = result.getIntField("port"); + + return toReturn; +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp new file mode 100644 index 00000000..048b53ab --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -0,0 +1,72 @@ +#include "SQLiteDatabase.h" + +std::vector SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) { + auto [_, result] = ExecuteSelect( + "SELECT lxfml, u.id FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", + propertyId); + + std::vector toReturn; + + while (!result.eof()) { + IUgc::Model model; + + int blobSize{}; + const auto* blob = result.getBlobField("lxfml", blobSize); + model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); + model.id = result.getInt64Field("id"); + toReturn.push_back(std::move(model)); + result.nextRow(); + } + + return toReturn; +} + +std::vector SQLiteDatabase::GetAllUgcModels() { + auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;"); + + std::vector models; + while (!result.eof()) { + IUgc::Model model; + model.id = result.getInt64Field("id"); + + int blobSize{}; + const auto* blob = result.getBlobField("lxfml", blobSize); + model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); + models.push_back(std::move(model)); + result.nextRow(); + } + + return models; +} + +void SQLiteDatabase::RemoveUnreferencedUgcModels() { + ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);"); +} + +void SQLiteDatabase::InsertNewUgcModel( + std::istringstream& sd0Data, // cant be const sad + const uint32_t blueprintId, + const uint32_t accountId, + const uint32_t characterId) { + const std::istream stream(sd0Data.rdbuf()); + ExecuteInsert( + "INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)", + blueprintId, + accountId, + characterId, + 0, + &stream, + false, + "weedeater.lxfml" + ); +} + +void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) { + ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId); + ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId); +} + +void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) { + const std::istream stream(lxfml.rdbuf()); + ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp new file mode 100644 index 00000000..4e806384 --- /dev/null +++ b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp @@ -0,0 +1,9 @@ +#include "SQLiteDatabase.h" + +void SQLiteDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) { + ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId); +} + +void SQLiteDatabase::DeleteUgcBuild(const LWOOBJID bigId) { + ExecuteDelete("DELETE FROM ugc_modular_build WHERE ugc_id = ?;", bigId); +} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index e44cd1f7..0263a6e3 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -8,10 +8,6 @@ void TestSQLDatabase::Destroy(std::string source) { } -sql::PreparedStatement* TestSQLDatabase::CreatePreppedStmt(const std::string& query) { - return nullptr; -} - void TestSQLDatabase::Commit() { } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 49e954ae..9d4b184f 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -7,7 +7,6 @@ class TestSQLDatabase : public GameDatabase { void Connect() override; void Destroy(std::string source = "") override; - sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override; void Commit() override; bool GetAutoCommit() override; void SetAutoCommit(bool value) override; @@ -102,6 +101,7 @@ class TestSQLDatabase : public GameDatabase { void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override {}; void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override {}; void DeleteUgcBuild(const LWOOBJID bigId) override {}; + uint32_t GetAccountCount() override { return 0; }; }; #endif //!TESTSQLDATABASE_H diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 44ccaa99..e6dfb042 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -10,9 +10,9 @@ #include -Migration LoadMigration(std::string path) { +Migration LoadMigration(std::string folder, std::string path) { Migration migration{}; - std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / folder / path); if (file.is_open()) { std::string line; @@ -34,10 +34,19 @@ Migration LoadMigration(std::string path) { void MigrationRunner::RunMigrations() { Database::Get()->CreateMigrationHistoryTable(); + // has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again. + + const auto migrationFolder = Database::GetMigrationFolder(); + if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") { + LOG("Running migration: 17_migration_for_migrations.sql"); + Database::Get()->ExecuteCustomQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 5) WHERE `name` LIKE \"dlu%\";"); + Database::Get()->InsertMigration("17_migration_for_migrations.sql"); + } + std::string finalSQL = ""; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { - auto migration = LoadMigration("dlu/" + entry); + for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) { + auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry); if (migration.data.empty()) { continue; @@ -46,7 +55,7 @@ void MigrationRunner::RunMigrations() { if (Database::Get()->IsMigrationRun(migration.name)) continue; LOG("Running migration: %s", migration.name.c_str()); - if (migration.name == "dlu/5_brick_model_sd0.sql") { + if (migration.name == "5_brick_model_sd0.sql") { runSd0Migrations = true; } else { finalSQL.append(migration.data.c_str()); @@ -86,10 +95,14 @@ void MigrationRunner::RunSQLiteMigrations() { cdstmt.execQuery().finalize(); cdstmt.finalize(); - Database::Get()->CreateMigrationHistoryTable(); + if (CDClientDatabase::ExecuteQuery("select * from migration_history where name = \"7_migration_for_migrations.sql\";").eof()) { + LOG("Running migration: 7_migration_for_migrations.sql"); + CDClientDatabase::ExecuteQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 10) WHERE `name` LIKE \"cdserver%\";"); + CDClientDatabase::ExecuteQuery("INSERT INTO migration_history (name) VALUES (\"7_migration_for_migrations.sql\");"); + } for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { - auto migration = LoadMigration("cdserver/" + entry); + auto migration = LoadMigration("cdserver/", entry); if (migration.data.empty()) continue; diff --git a/dGame/CMakeLists.txt b/dGame/CMakeLists.txt index 26eb859a..661c3688 100644 --- a/dGame/CMakeLists.txt +++ b/dGame/CMakeLists.txt @@ -26,7 +26,6 @@ target_include_directories(dGameBase PUBLIC "." "dEntity" "${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" - "${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include" # dPhysics "${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include" "${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include" diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index c60e135f..c6e72f29 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -65,7 +65,6 @@ target_include_directories(dComponents PUBLIC "." "${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" - "${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include" # dPhysics (via dpWorld.h) "${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include" "${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include" diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 9a54a64e..e01ca255 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -176,12 +176,16 @@ int main(int argc, char** argv) { } // Run migrations should any need to be run. - MigrationRunner::RunSQLiteMigrations(); + MigrationRunner::RunSQLiteMigrations(); //If the first command line argument is -a or --account then make the user //input a username and password, with the password being hidden. - if (argc > 1 && - (strcmp(argv[1], "-a") == 0 || strcmp(argv[1], "--account") == 0)) { + bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1"; + if (createAccount) { + LOG("No accounts exist in the database. Please create an account."); + } + if ((argc > 1 && + (strcmp(argv[1], "-a") == 0 || strcmp(argv[1], "--account") == 0)) || createAccount) { std::string username; std::string password; diff --git a/dMasterServer/Start.cpp b/dMasterServer/Start.cpp index 1fb9c212..d35392f1 100644 --- a/dMasterServer/Start.cpp +++ b/dMasterServer/Start.cpp @@ -13,7 +13,7 @@ void StartChatServer() { //macOS doesn't need sudo to run on ports < 1024 auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); #elif _WIN32 - auto result = system(("start " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); + auto result = system(("start /B " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) { auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); @@ -31,7 +31,7 @@ void StartAuthServer() { #ifdef __APPLE__ auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); #elif _WIN32 - auto result = system(("start " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); + auto result = system(("start /B " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) { auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); @@ -43,7 +43,7 @@ void StartAuthServer() { void StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) { #ifdef _WIN32 - std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; + std::string cmd = "start /B " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; #else std::string cmd; if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) { diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 380eb8f0..eb1d1c8f 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -82,14 +82,14 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else bitStream.Write(ServiceId::General); - bitStream.Write(215523470896); + bitStream.Write(219818241584); server->Send(bitStream, sysAddr, false); } std::string CleanReceivedString(const std::string& str) { std::string toReturn = str; - const auto removed = std::ranges::find_if(toReturn, [](char c) { return isprint(c) == 0 && isblank(c) == 0; }); + const auto removed = std::ranges::find_if(toReturn, [](unsigned char c) { return isprint(c) == 0 && isblank(c) == 0; }); toReturn.erase(removed, toReturn.end()); return toReturn; } diff --git a/dNet/CMakeLists.txt b/dNet/CMakeLists.txt index 15cdda42..172aee20 100644 --- a/dNet/CMakeLists.txt +++ b/dNet/CMakeLists.txt @@ -19,7 +19,6 @@ target_include_directories(dNet PRIVATE "${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" - "${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include" "${PROJECT_SOURCE_DIR}/dGame" # UserManager.h "${PROJECT_SOURCE_DIR}/dGame/dComponents" diff --git a/docker-compose.yml b/docker-compose.yml index 8f5a3d09..dbd16603 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,9 @@ services: - CLIENT_LOCATION=/app/luclient - DLU_CONFIG_DIR=/app/configs - DUMP_FOLDER=/app/dump + - DATABASE_TYPE=mariadb + - SQLITE_DATABASE_PATH=${SQLITE_DATABASE_PATH:-resServer/dlu.sqlite} + - SKIP_ACCOUNT_CREATION=${SKIP_ACCOUNT_CREATION:-1} - MYSQL_HOST=darkflamedb - MYSQL_DATABASE=${MARIADB_DATABASE:-darkflame} - MYSQL_USERNAME=${MARIADB_USER:-darkflame} diff --git a/migrations/cdserver/7_migration_for_migrations.sql b/migrations/cdserver/7_migration_for_migrations.sql new file mode 100644 index 00000000..e69de29b diff --git a/migrations/dlu/0_initial.sql b/migrations/dlu/mysql/0_initial.sql similarity index 100% rename from migrations/dlu/0_initial.sql rename to migrations/dlu/mysql/0_initial.sql diff --git a/migrations/dlu/10_Security_updates.sql b/migrations/dlu/mysql/10_Security_updates.sql similarity index 100% rename from migrations/dlu/10_Security_updates.sql rename to migrations/dlu/mysql/10_Security_updates.sql diff --git a/migrations/dlu/11_fix_cheat_detection_table.sql b/migrations/dlu/mysql/11_fix_cheat_detection_table.sql similarity index 100% rename from migrations/dlu/11_fix_cheat_detection_table.sql rename to migrations/dlu/mysql/11_fix_cheat_detection_table.sql diff --git a/migrations/dlu/12_modular_build_ugc.sql b/migrations/dlu/mysql/12_modular_build_ugc.sql similarity index 100% rename from migrations/dlu/12_modular_build_ugc.sql rename to migrations/dlu/mysql/12_modular_build_ugc.sql diff --git a/migrations/dlu/13_ignore_list.sql b/migrations/dlu/mysql/13_ignore_list.sql similarity index 100% rename from migrations/dlu/13_ignore_list.sql rename to migrations/dlu/mysql/13_ignore_list.sql diff --git a/migrations/dlu/14_reward_codes.sql b/migrations/dlu/mysql/14_reward_codes.sql similarity index 100% rename from migrations/dlu/14_reward_codes.sql rename to migrations/dlu/mysql/14_reward_codes.sql diff --git a/migrations/dlu/15_behavior_owner.sql b/migrations/dlu/mysql/15_behavior_owner.sql similarity index 100% rename from migrations/dlu/15_behavior_owner.sql rename to migrations/dlu/mysql/15_behavior_owner.sql diff --git a/migrations/dlu/16_big_behaviors.sql b/migrations/dlu/mysql/16_big_behaviors.sql similarity index 100% rename from migrations/dlu/16_big_behaviors.sql rename to migrations/dlu/mysql/16_big_behaviors.sql diff --git a/migrations/dlu/mysql/17_migration_for_migrations.sql b/migrations/dlu/mysql/17_migration_for_migrations.sql new file mode 100644 index 00000000..59d3484c --- /dev/null +++ b/migrations/dlu/mysql/17_migration_for_migrations.sql @@ -0,0 +1 @@ +-- see MigrationRunner.cpp for what this does diff --git a/migrations/dlu/1_unique_charinfo_names.sql b/migrations/dlu/mysql/1_unique_charinfo_names.sql similarity index 100% rename from migrations/dlu/1_unique_charinfo_names.sql rename to migrations/dlu/mysql/1_unique_charinfo_names.sql diff --git a/migrations/dlu/2_reporter_id.sql b/migrations/dlu/mysql/2_reporter_id.sql similarity index 100% rename from migrations/dlu/2_reporter_id.sql rename to migrations/dlu/mysql/2_reporter_id.sql diff --git a/migrations/dlu/3_add_performance_cost.sql b/migrations/dlu/mysql/3_add_performance_cost.sql similarity index 100% rename from migrations/dlu/3_add_performance_cost.sql rename to migrations/dlu/mysql/3_add_performance_cost.sql diff --git a/migrations/dlu/4_friends_list_objectids.sql b/migrations/dlu/mysql/4_friends_list_objectids.sql similarity index 100% rename from migrations/dlu/4_friends_list_objectids.sql rename to migrations/dlu/mysql/4_friends_list_objectids.sql diff --git a/migrations/dlu/5_brick_model_sd0.sql b/migrations/dlu/mysql/5_brick_model_sd0.sql similarity index 100% rename from migrations/dlu/5_brick_model_sd0.sql rename to migrations/dlu/mysql/5_brick_model_sd0.sql diff --git a/migrations/dlu/6_property_behaviors.sql b/migrations/dlu/mysql/6_property_behaviors.sql similarity index 100% rename from migrations/dlu/6_property_behaviors.sql rename to migrations/dlu/mysql/6_property_behaviors.sql diff --git a/migrations/dlu/7_make_play_key_id_nullable.sql b/migrations/dlu/mysql/7_make_play_key_id_nullable.sql similarity index 100% rename from migrations/dlu/7_make_play_key_id_nullable.sql rename to migrations/dlu/mysql/7_make_play_key_id_nullable.sql diff --git a/migrations/dlu/8_foreign_play_key.sql b/migrations/dlu/mysql/8_foreign_play_key.sql similarity index 100% rename from migrations/dlu/8_foreign_play_key.sql rename to migrations/dlu/mysql/8_foreign_play_key.sql diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/mysql/9_Update_Leaderboard_Storage.sql similarity index 100% rename from migrations/dlu/9_Update_Leaderboard_Storage.sql rename to migrations/dlu/mysql/9_Update_Leaderboard_Storage.sql diff --git a/migrations/dlu/sqlite/0_initial.sql b/migrations/dlu/sqlite/0_initial.sql new file mode 100644 index 00000000..887c61da --- /dev/null +++ b/migrations/dlu/sqlite/0_initial.sql @@ -0,0 +1,198 @@ +CREATE TABLE IF NOT EXISTS accounts ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +name TEXT NOT NULL UNIQUE, +password TEXT NOT NULL, +gm_level BIGINT NOT NULL DEFAULT 0, +locked INTEGER NOT NULL DEFAULT FALSE, +banned INTEGER NOT NULL DEFAULT FALSE, +play_key_id INTEGER DEFAULT NULL, +created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +mute_expire BIGINT NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS charinfo ( +id BIGINT NOT NULL PRIMARY KEY, +account_id INTEGER NOT NULL REFERENCES accounts(id), +name TEXT NOT NULL UNIQUE, +pending_name TEXT NOT NULL, +needs_rename INTEGER NOT NULL DEFAULT FALSE, +prop_clone_id INTEGER UNIQUE, +last_login BIGINT NOT NULL DEFAULT 0, +permission_map BIGINT NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS charxml ( +id BIGINT NOT NULL PRIMARY KEY REFERENCES charinfo(id), +xml_data TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS command_log ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +character_id BIGINT NOT NULL REFERENCES charinfo(id), +command TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS friends ( +player_id BIGINT NOT NULL REFERENCES charinfo(id), +friend_id BIGINT NOT NULL REFERENCES charinfo(id), +best_friend INTEGER NOT NULL DEFAULT FALSE, + +PRIMARY KEY (player_id, friend_id) +); + +CREATE TABLE IF NOT EXISTS leaderboard ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +game_id INTEGER NOT NULL DEFAULT 0, +last_played DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +character_id BIGINT NOT NULL REFERENCES charinfo(id), +primaryScore DOUBLE NOT NULL DEFAULT 0, +secondaryScore DOUBLE NOT NULL DEFAULT 0, +tertiaryScore DOUBLE NOT NULL DEFAULT 0, +numWins INTEGER NOT NULL DEFAULT 0, +timesPlayed INTEGER NOT NULL DEFAULT 1 +); + +CREATE TABLE IF NOT EXISTS mail ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +sender_id INTEGER NOT NULL DEFAULT 0, +sender_name TEXT NOT NULL DEFAULT '', +receiver_id BIGINT NOT NULL REFERENCES charinfo(id), +receiver_name TEXT NOT NULL, +time_sent BIGINT NOT NULL, +subject TEXT NOT NULL, +body TEXT NOT NULL, +attachment_id BIGINT NOT NULL DEFAULT 0, +attachment_lot INTEGER NOT NULL DEFAULT 0, +attachment_subkey BIGINT NOT NULL DEFAULT 0, +attachment_count INTEGER NOT NULL DEFAULT 0, +was_read INTEGER NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS object_id_tracker ( +last_object_id BIGINT NOT NULL DEFAULT 0 PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS pet_names ( +id BIGINT NOT NULL PRIMARY KEY, +pet_name TEXT NOT NULL, +approved INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS play_keys ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +key_string TEXT NOT NULL UNIQUE, +key_uses INTEGER NOT NULL DEFAULT 1, +created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +active INTEGER NOT NULL DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS properties ( +id BIGINT NOT NULL PRIMARY KEY, +owner_id BIGINT NOT NULL REFERENCES charinfo(id), +template_id INTEGER NOT NULL, +clone_id BIGINT REFERENCES charinfo(prop_clone_id), +name TEXT NOT NULL, +description TEXT NOT NULL, +rent_amount INTEGER NOT NULL, +rent_due BIGINT NOT NULL, +privacy_option INTEGER NOT NULL, +mod_approved INTEGER NOT NULL DEFAULT FALSE, +last_updated BIGINT NOT NULL, +time_claimed BIGINT NOT NULL, +rejection_reason TEXT NOT NULL, +reputation BIGINT NOT NULL, +zone_id INTEGER NOT NULL, +performance_cost DOUBLE DEFAULT 0.0 +); + +CREATE TABLE IF NOT EXISTS ugc ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +account_id INTEGER NOT NULL REFERENCES accounts(id), +character_id BIGINT NOT NULL REFERENCES charinfo(id), +is_optimized INTEGER NOT NULL DEFAULT FALSE, +lxfml BLOB NOT NULL, +bake_ao INTEGER NOT NULL DEFAULT FALSE, +filename TEXT NOT NULL DEFAULT ('') +); + +CREATE TABLE IF NOT EXISTS properties_contents ( +id BIGINT NOT NULL PRIMARY KEY, +property_id BIGINT NOT NULL REFERENCES properties(id), +ugc_id INTEGER NULL REFERENCES ugc(id), +lot INTEGER NOT NULL, +x DOUBLE NOT NULL, +y DOUBLE NOT NULL, +z DOUBLE NOT NULL, +rx DOUBLE NOT NULL, +ry DOUBLE NOT NULL, +rz DOUBLE NOT NULL, +rw DOUBLE NOT NULL, +model_name TEXT NOT NULL DEFAULT (''), +model_description TEXT NOT NULL DEFAULT (''), +behavior_1 INTEGER NOT NULL DEFAULT 0, +behavior_2 INTEGER NOT NULL DEFAULT 0, +behavior_3 INTEGER NOT NULL DEFAULT 0, +behavior_4 INTEGER NOT NULL DEFAULT 0, +behavior_5 INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS activity_log ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +character_id BIGINT NOT NULL REFERENCES charinfo(id), +activity INTEGER NOT NULL, +time BIGINT NOT NULL, +map_id INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS bug_reports ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +body TEXT NOT NULL, +client_version TEXT NOT NULL, +other_player_id TEXT NOT NULL, +selection TEXT NOT NULL, +submitted DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +reporter_id INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS servers ( +id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, +name TEXT NOT NULL, +ip TEXT NOT NULL, +port INTEGER NOT NULL, +state INTEGER NOT NULL, +version INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS player_cheat_detections ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + account_id INTEGER REFERENCES accounts(id), + name TEXT NOT NULL, + violation_msg TEXT NOT NULL, + violation_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + violation_system_address TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS ugc_modular_build ( + ugc_id BIGINT NOT NULL PRIMARY KEY, + character_id BIGINT NOT NULL REFERENCES charinfo(id) ON DELETE CASCADE, + ldf_config VARCHAR(60) NOT NULL +); + +CREATE TABLE IF NOT EXISTS ignore_list ( + player_id BIGINT NOT NULL REFERENCES charinfo(id) ON DELETE CASCADE, + ignored_player_id BIGINT NOT NULL REFERENCES charinfo(id) ON DELETE CASCADE, + + PRIMARY KEY (player_id, ignored_player_id) +); + +CREATE TABLE IF NOT EXISTS accounts_rewardcodes ( + account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + rewardcode INTEGER NOT NULL, + PRIMARY KEY (account_id, rewardcode) +); + +CREATE TABLE IF NOT EXISTS behaviors ( + behavior_info TEXT NOT NULL, + behavior_id BIGINT NOT NULL PRIMARY KEY, + character_id BIGINT NOT NULL DEFAULT 0 +); diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index e487058d..aa8b5eb4 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -65,4 +65,11 @@ version_minor=64 # The port the chat server is started and listening on # Used in chat and world servers -chat_server_port=2005 \ No newline at end of file +chat_server_port=2005 + +sqlite_database_path=resServer/dlu.sqlite + +database_type=sqlite + +# Skips the account creation check in master. Used for non-interactive setups. +skip_account_creation=0 diff --git a/thirdparty/SQLite/CMakeLists.txt b/thirdparty/SQLite/CMakeLists.txt index e745c46a..4a9e9311 100644 --- a/thirdparty/SQLite/CMakeLists.txt +++ b/thirdparty/SQLite/CMakeLists.txt @@ -3,7 +3,7 @@ set (SQLITE3_SOURCES "sqlite3.c" ) -add_library (sqlite3 ${SQLITE3_SOURCES}) +add_library(sqlite3 ${SQLITE3_SOURCES}) if(UNIX) # Add warning disable flags and link Unix libraries to sqlite3 diff --git a/thirdparty/SQLite/CppSQLite3.cpp b/thirdparty/SQLite/CppSQLite3.cpp index f816ac42..21e2811e 100644 --- a/thirdparty/SQLite/CppSQLite3.cpp +++ b/thirdparty/SQLite/CppSQLite3.cpp @@ -1016,6 +1016,20 @@ void CppSQLite3Statement::bind(int nParam, const int nValue) } +void CppSQLite3Statement::bind(int nParam, const sqlite_int64 nValue) +{ + checkVM(); + int nRes = sqlite3_bind_int64(mpVM, nParam, nValue); + + if (nRes != SQLITE_OK) + { + throw CppSQLite3Exception(nRes, + (char*)"Error binding int64 param", + DONT_DELETE_MSG); + } +} + + void CppSQLite3Statement::bind(int nParam, const double dValue) { checkVM(); @@ -1097,6 +1111,12 @@ void CppSQLite3Statement::bind(const char* szParam, const int nValue) bind(nParam, nValue); } +void CppSQLite3Statement::bind(const char* szParam, const sqlite_int64 nValue) +{ + int nParam = bindParameterIndex(szParam); + bind(nParam, nValue); +} + void CppSQLite3Statement::bind(const char* szParam, const double dwValue) { int nParam = bindParameterIndex(szParam); diff --git a/thirdparty/SQLite/CppSQLite3.h b/thirdparty/SQLite/CppSQLite3.h index 70c4b8e8..a98277b1 100644 --- a/thirdparty/SQLite/CppSQLite3.h +++ b/thirdparty/SQLite/CppSQLite3.h @@ -252,6 +252,7 @@ public: void bind(int nParam, const char* szValue); void bind(int nParam, const int nValue); void bind(int nParam, const double dwValue); + void bind(int nParam, const sqlite_int64 llValue); void bind(int nParam, const unsigned char* blobValue, int nLen); void bindNull(int nParam); @@ -259,6 +260,7 @@ public: void bind(const char* szParam, const char* szValue); void bind(const char* szParam, const int nValue); void bind(const char* szParam, const double dwValue); + void bind(const char* szParam, const sqlite_int64 llValue); void bind(const char* szParam, const unsigned char* blobValue, int nLen); void bindNull(const char* szParam); diff --git a/versions.txt b/versions.txt index fa7ea86c..682d3437 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +3.0 - Single player with minimal setup is fully functional with SQLite database 2.3 - Dragonmaw functional, new slash command system, vanity system overhaul 2.2 - Code cleanup and QoL fixes 2.1 - Bug and crash fixes