mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-22 13:33:35 +00:00
Merge branch 'main' into ub-fixes
This commit is contained in:
commit
6699081080
10
.env.example
10
.env.example
@ -3,12 +3,20 @@ CLIENT_PATH=./client
|
|||||||
# Updates NET_VERSION in CMakeVariables.txt
|
# Updates NET_VERSION in CMakeVariables.txt
|
||||||
NET_VERSION=171022
|
NET_VERSION=171022
|
||||||
# make sure this is a long random string
|
# make sure this is a long random string
|
||||||
# grab a "SHA 256-bit Key" from here: https://keygen.io/
|
# generate a "SHA 256-bit Key" from here: https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex')
|
||||||
ACCOUNT_MANAGER_SECRET=
|
ACCOUNT_MANAGER_SECRET=
|
||||||
# Should be the externally facing IP of your server host
|
# Should be the externally facing IP of your server host
|
||||||
EXTERNAL_IP=localhost
|
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
|
# Database values
|
||||||
# Be careful with special characters here. It is more safe to use normal characters and/or numbers.
|
# Be careful with special characters here. It is more safe to use normal characters and/or numbers.
|
||||||
MARIADB_USER=darkflame
|
MARIADB_USER=darkflame
|
||||||
MARIADB_PASSWORD=
|
MARIADB_PASSWORD=
|
||||||
MARIADB_DATABASE=darkflame
|
MARIADB_DATABASE=darkflame
|
||||||
|
SKIP_ACCOUNT_CREATION=1
|
||||||
|
1
.github/workflows/build-and-test.yml
vendored
1
.github/workflows/build-and-test.yml
vendored
@ -43,6 +43,7 @@ jobs:
|
|||||||
build/*/*.ini
|
build/*/*.ini
|
||||||
build/*/*.so
|
build/*/*.so
|
||||||
build/*/*.dll
|
build/*/*.dll
|
||||||
|
build/*/*.dylib
|
||||||
build/*/vanity/
|
build/*/vanity/
|
||||||
build/*/navmeshes/
|
build/*/navmeshes/
|
||||||
build/*/migrations/
|
build/*/migrations/
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,7 +7,6 @@ valgrind-out.txt
|
|||||||
# Third party libraries
|
# Third party libraries
|
||||||
thirdparty/mysql/
|
thirdparty/mysql/
|
||||||
thirdparty/mysql_linux/
|
thirdparty/mysql_linux/
|
||||||
CMakeVariables.txt
|
|
||||||
|
|
||||||
# Build folders
|
# Build folders
|
||||||
build/
|
build/
|
||||||
@ -96,6 +95,7 @@ ipch/
|
|||||||
|
|
||||||
# Exceptions:
|
# Exceptions:
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
|
CMakeUserPresets.json
|
||||||
*.vcxproj
|
*.vcxproj
|
||||||
*.filters
|
*.filters
|
||||||
*.cmake
|
*.cmake
|
||||||
|
@ -78,6 +78,7 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
|
|||||||
# Disabled no-register
|
# Disabled no-register
|
||||||
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
|
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
|
add_link_options("-Wl,-rpath,$ORIGIN/")
|
||||||
add_compile_options("-fPIC")
|
add_compile_options("-fPIC")
|
||||||
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
|
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
|
||||||
|
|
||||||
@ -186,6 +187,8 @@ foreach(resource_file ${RESOURCE_FILES})
|
|||||||
list(GET line_split 0 variable_name)
|
list(GET line_split 0 variable_name)
|
||||||
|
|
||||||
if(NOT ${parsed_current_file_contents} MATCHES ${variable_name})
|
if(NOT ${parsed_current_file_contents} MATCHES ${variable_name})
|
||||||
|
# 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})
|
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
|
||||||
set(line_to_add ${line_to_add} ${line})
|
set(line_to_add ${line_to_add} ${line})
|
||||||
|
|
||||||
@ -195,7 +198,7 @@ foreach(resource_file ${RESOURCE_FILES})
|
|||||||
|
|
||||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n")
|
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n")
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
set(line_to_add "")
|
set(line_to_add "")
|
||||||
else()
|
else()
|
||||||
set(line_to_add ${line_to_add} ${line})
|
set(line_to_add ${line_to_add} ${line})
|
||||||
@ -225,21 +228,8 @@ foreach(file ${VANITY_FILES})
|
|||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Move our migrations for MasterServer to run
|
# Move our migrations for MasterServer to run
|
||||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/)
|
file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/migrations)
|
||||||
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
|
file(COPY ${CMAKE_SOURCE_DIR}/migrations DESTINATION ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
|
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
@ -324,7 +314,7 @@ add_subdirectory(dPhysics)
|
|||||||
add_subdirectory(dServer)
|
add_subdirectory(dServer)
|
||||||
|
|
||||||
# Create a list of common libraries shared between all binaries
|
# Create a list of common libraries shared between all binaries
|
||||||
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum")
|
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
|
||||||
|
|
||||||
# Add platform specific common libraries
|
# Add platform specific common libraries
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
|
@ -11,9 +11,6 @@
|
|||||||
"displayName": "Default configure step",
|
"displayName": "Default configure step",
|
||||||
"description": "Use 'build' dir and Unix makefiles",
|
"description": "Use 'build' dir and Unix makefiles",
|
||||||
"binaryDir": "${sourceDir}/build",
|
"binaryDir": "${sourceDir}/build",
|
||||||
"environment": {
|
|
||||||
"DLU_CONFIG_DIR": "${sourceDir}/build"
|
|
||||||
},
|
|
||||||
"generator": "Unix Makefiles"
|
"generator": "Unix Makefiles"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
39
README.md
39
README.md
@ -13,21 +13,33 @@ 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
|
* You must disclose any changes you make to the code when you distribute it
|
||||||
* Hosting a server for others counts as distribution
|
* 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
|
### 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.
|
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
|
### 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.
|
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
|
## Setting up 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.
|
* 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 windows release](https://github.com/DarkflameUniverse/DarkflameServer/releases) (or whichever release you need) and extract the files into a folder inside your client. Note that this setup is expecting that when double clicking the folder that you put in the same folder as `legouniverse.exe`, the file `MasterServer.exe` is in there.
|
||||||
|
* You should be able to see the folder with the server files in the same folder as `legouniverse.exe`.
|
||||||
|
* Go into the server files folder and open `sharedconfig.ini`. 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. After you have created the account, the server will shutdown and need to be restarted.
|
||||||
|
* To connect to the server, either delete the file `boot.cfg` which is found in your LEGO Universe client, rename the file `boot.cfg` to something else or follow the steps [here](#allowing-a-user-to-connect-to-your-server) if you wish to keep the file.
|
||||||
|
* 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 stops automatically alongside when you open the game, the server starts automatically.
|
||||||
|
|
||||||
## Steps to setup server
|
<font size="32">**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.**</font>
|
||||||
|
|
||||||
|
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)
|
* [Clone this repository](#clone-the-repository)
|
||||||
|
* [Setting up a development environment](#setting-up-a-development-environment)
|
||||||
* [Install dependencies](#install-dependencies)
|
* [Install dependencies](#install-dependencies)
|
||||||
* [Database setup](#database-setup)
|
* [Database setup](#database-setup)
|
||||||
* [Build the server](#build-the-server)
|
* [Build the server](#build-the-server)
|
||||||
@ -39,6 +51,13 @@ If you would like a setup for a single player server only on a Windows machine,
|
|||||||
* [User Guide](#user-guide)
|
* [User Guide](#user-guide)
|
||||||
* [Docker](#docker)
|
* [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
|
## Clone the repository
|
||||||
If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win)
|
If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win)
|
||||||
|
|
||||||
@ -266,8 +285,8 @@ systemctl stop darkflame.service
|
|||||||
journalctl -xeu darkflame.service
|
journalctl -xeu darkflame.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### First admin user
|
### First user or adding more users.
|
||||||
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!
|
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)
|
### Account management tool (Nexus Dashboard)
|
||||||
**If you are just using this server for yourself, you can skip setting up Nexus Dashboard**
|
**If you are just using this server for yourself, you can skip setting up Nexus Dashboard**
|
||||||
@ -371,7 +390,7 @@ at once. For that:
|
|||||||
- Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env`
|
- Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env`
|
||||||
- You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows).
|
- You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows).
|
||||||
- Update the `ACCOUNT_MANAGER_SECRET` and `MARIADB_PASSWORD` with strong random passwords.
|
- Update the `ACCOUNT_MANAGER_SECRET` and `MARIADB_PASSWORD` with strong random passwords.
|
||||||
- Use a password generator like <https://keygen.io>
|
- Use a password generator <https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex')>
|
||||||
- Avoid `:` and `@` characters
|
- Avoid `:` and `@` characters
|
||||||
- Once the database user is created, changing the password will not update it, so the server will just fail to connect.
|
- Once the database user is created, changing the password will not update it, so the server will just fail to connect.
|
||||||
- Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family
|
- Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family
|
||||||
|
@ -60,7 +60,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Database::Connect();
|
Database::Connect();
|
||||||
} catch (sql::SQLException& ex) {
|
} catch (std::exception& ex) {
|
||||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||||
Database::Destroy("AuthServer");
|
Database::Destroy("AuthServer");
|
||||||
delete Game::server;
|
delete Game::server;
|
||||||
|
@ -81,7 +81,7 @@ int main(int argc, char** argv) {
|
|||||||
//Connect to the MySQL Database
|
//Connect to the MySQL Database
|
||||||
try {
|
try {
|
||||||
Database::Connect();
|
Database::Connect();
|
||||||
} catch (sql::SQLException& ex) {
|
} catch (std::exception& ex) {
|
||||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||||
Database::Destroy("ChatServer");
|
Database::Destroy("ChatServer");
|
||||||
delete Game::server;
|
delete Game::server;
|
||||||
|
@ -123,7 +123,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
|
|||||||
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
|
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
|
||||||
LOG("Updated model %i to sd0", model.id);
|
LOG("Updated model %i to sd0", model.id);
|
||||||
updatedModels++;
|
updatedModels++;
|
||||||
} catch (sql::SQLException exception) {
|
} catch (std::exception& exception) {
|
||||||
LOG("Failed to update model %i. This model should be inspected manually to see why."
|
LOG("Failed to update model %i. This model should be inspected manually to see why."
|
||||||
"The database error is %s", model.id, exception.what());
|
"The database error is %s", model.id, exception.what());
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ target_include_directories(dCommon
|
|||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase"
|
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
|
@ -293,6 +293,7 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
|
|||||||
// Because we dont know how large the initial number before the first _ is we need to make it a map like so.
|
// Because we dont know how large the initial number before the first _ is we need to make it a map like so.
|
||||||
std::map<uint32_t, std::string> filenames{};
|
std::map<uint32_t, std::string> filenames{};
|
||||||
for (const auto& t : std::filesystem::directory_iterator(folder)) {
|
for (const auto& t : std::filesystem::directory_iterator(folder)) {
|
||||||
|
if (t.is_directory() || t.is_symlink()) continue;
|
||||||
auto filename = t.path().filename().string();
|
auto filename = t.path().filename().string();
|
||||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||||
filenames.emplace(index, std::move(filename));
|
filenames.emplace(index, std::move(filename));
|
||||||
|
@ -2,6 +2,12 @@ add_subdirectory(CDClientDatabase)
|
|||||||
add_subdirectory(GameDatabase)
|
add_subdirectory(GameDatabase)
|
||||||
|
|
||||||
add_library(dDatabase STATIC "MigrationRunner.cpp")
|
add_library(dDatabase STATIC "MigrationRunner.cpp")
|
||||||
|
|
||||||
|
add_custom_target(conncpp_dylib
|
||||||
|
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
|
||||||
|
|
||||||
|
add_dependencies(dDatabase conncpp_dylib)
|
||||||
|
|
||||||
target_include_directories(dDatabase PUBLIC ".")
|
target_include_directories(dDatabase PUBLIC ".")
|
||||||
target_link_libraries(dDatabase
|
target_link_libraries(dDatabase
|
||||||
PUBLIC dDatabaseCDClient dDatabaseGame)
|
PUBLIC dDatabaseCDClient dDatabaseGame)
|
||||||
|
@ -8,6 +8,12 @@ foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES})
|
|||||||
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
|
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
add_subdirectory(SQLite)
|
||||||
|
|
||||||
|
foreach(file ${DDATABSE_DATABSES_SQLITE_SOURCES})
|
||||||
|
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "SQLite/${file}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
add_subdirectory(TestSQL)
|
add_subdirectory(TestSQL)
|
||||||
|
|
||||||
foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES})
|
foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES})
|
||||||
@ -16,13 +22,14 @@ endforeach()
|
|||||||
|
|
||||||
add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES})
|
add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES})
|
||||||
target_include_directories(dDatabaseGame PUBLIC "."
|
target_include_directories(dDatabaseGame PUBLIC "."
|
||||||
"ITables" PRIVATE "MySQL" "TestSQL"
|
"ITables" PRIVATE "MySQL" "SQLite" "TestSQL"
|
||||||
"${PROJECT_SOURCE_DIR}/dCommon"
|
"${PROJECT_SOURCE_DIR}/dCommon"
|
||||||
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
|
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(dDatabaseGame
|
target_link_libraries(dDatabaseGame
|
||||||
PUBLIC MariaDB::ConnCpp
|
INTERFACE dCommon
|
||||||
INTERFACE dCommon)
|
PRIVATE sqlite3 MariaDB::ConnCpp)
|
||||||
|
|
||||||
# Glob together all headers that need to be precompiled
|
# Glob together all headers that need to be precompiled
|
||||||
file(
|
file(
|
||||||
|
@ -2,22 +2,46 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "dConfig.h"
|
#include "dConfig.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "MySQLDatabase.h"
|
|
||||||
#include "DluAssert.h"
|
#include "DluAssert.h"
|
||||||
|
|
||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
#include "MySQLDatabase.h"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
#pragma warning (disable:4251) //Disables SQL warnings
|
#pragma warning (disable:4251) //Disables SQL warnings
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
GameDatabase* database = nullptr;
|
GameDatabase* database = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Database::GetMigrationFolder() {
|
||||||
|
const std::set<std::string> 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() {
|
void Database::Connect() {
|
||||||
if (database) {
|
if (database) {
|
||||||
LOG("Tried to connect to database when it's already connected!");
|
LOG("Tried to connect to database when it's already connected!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = new MySQLDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
database->Connect();
|
database->Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <conncpp.hpp>
|
|
||||||
|
|
||||||
#include "GameDatabase.h"
|
#include "GameDatabase.h"
|
||||||
|
|
||||||
@ -13,4 +12,6 @@ namespace Database {
|
|||||||
// Used for assigning a test database as the handler for database logic.
|
// Used for assigning a test database as the handler for database logic.
|
||||||
// Do not use in production code.
|
// Do not use in production code.
|
||||||
void _setDatabase(GameDatabase* const db);
|
void _setDatabase(GameDatabase* const db);
|
||||||
|
|
||||||
|
std::string GetMigrationFolder();
|
||||||
};
|
};
|
||||||
|
@ -24,14 +24,10 @@
|
|||||||
#include "IIgnoreList.h"
|
#include "IIgnoreList.h"
|
||||||
#include "IAccountsRewardCodes.h"
|
#include "IAccountsRewardCodes.h"
|
||||||
#include "IBehaviors.h"
|
#include "IBehaviors.h"
|
||||||
|
#include "IUgcModularBuild.h"
|
||||||
namespace sql {
|
|
||||||
class Statement;
|
|
||||||
class PreparedStatement;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#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
|
#else
|
||||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) x
|
# define DLU_SQL_TRY_CATCH_RETHROW(x) x
|
||||||
#endif // _DEBUG
|
#endif // _DEBUG
|
||||||
@ -42,14 +38,13 @@ class GameDatabase :
|
|||||||
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
||||||
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
||||||
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
||||||
public IBehaviors {
|
public IBehaviors, public IUgcModularBuild {
|
||||||
public:
|
public:
|
||||||
virtual ~GameDatabase() = default;
|
virtual ~GameDatabase() = default;
|
||||||
// TODO: These should be made private.
|
// TODO: These should be made private.
|
||||||
virtual void Connect() = 0;
|
virtual void Connect() = 0;
|
||||||
virtual void Destroy(std::string source = "") = 0;
|
virtual void Destroy(std::string source = "") = 0;
|
||||||
virtual void ExecuteCustomQuery(const std::string_view query) = 0;
|
virtual void ExecuteCustomQuery(const std::string_view query) = 0;
|
||||||
virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0;
|
|
||||||
virtual void Commit() = 0;
|
virtual void Commit() = 0;
|
||||||
virtual bool GetAutoCommit() = 0;
|
virtual bool GetAutoCommit() = 0;
|
||||||
virtual void SetAutoCommit(bool value) = 0;
|
virtual void SetAutoCommit(bool value) = 0;
|
||||||
|
@ -36,6 +36,8 @@ public:
|
|||||||
|
|
||||||
// Update the GameMaster level of an account.
|
// Update the GameMaster level of an account.
|
||||||
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
|
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
|
||||||
|
|
||||||
|
virtual uint32_t GetAccountCount() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__IACCOUNTS__H__
|
#endif //!__IACCOUNTS__H__
|
||||||
|
@ -3,12 +3,45 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ILeaderboard {
|
class ILeaderboard {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
uint32_t charId{};
|
||||||
|
uint32_t lastPlayedTimestamp{};
|
||||||
|
float primaryScore{};
|
||||||
|
float secondaryScore{};
|
||||||
|
uint32_t tertiaryScore{};
|
||||||
|
uint32_t numWins{};
|
||||||
|
uint32_t numTimesPlayed{};
|
||||||
|
uint32_t ranking{};
|
||||||
|
std::string name{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Score {
|
||||||
|
auto operator<=>(const Score& rhs) const = default;
|
||||||
|
|
||||||
|
float primaryScore{ 0.0f };
|
||||||
|
float secondaryScore{ 0.0f };
|
||||||
|
float tertiaryScore{ 0.0f };
|
||||||
|
};
|
||||||
|
|
||||||
// Get the donation total for the given activity id.
|
// Get the donation total for the given activity id.
|
||||||
virtual std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) = 0;
|
virtual std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) = 0;
|
||||||
|
virtual std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) = 0;
|
||||||
|
virtual std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) = 0;
|
||||||
|
virtual std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) = 0;
|
||||||
|
virtual std::optional<Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) = 0;
|
||||||
|
|
||||||
|
virtual void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
|
||||||
|
virtual void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
|
||||||
|
virtual void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) = 0;
|
||||||
|
virtual void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__ILEADERBOARD__H__
|
#endif //!__ILEADERBOARD__H__
|
||||||
|
14
dDatabase/GameDatabase/ITables/IUgcModularBuild.h
Normal file
14
dDatabase/GameDatabase/ITables/IUgcModularBuild.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef IUGCMODULARBUILD_H
|
||||||
|
#define IUGCMODULARBUILD_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class IUgcModularBuild {
|
||||||
|
public:
|
||||||
|
virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) = 0;
|
||||||
|
virtual void DeleteUgcBuild(const LWOOBJID bigId) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //!IUGCMODULARBUILD_H
|
@ -14,6 +14,7 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void MySQLDatabase::Connect() {
|
void MySQLDatabase::Connect() {
|
||||||
|
LOG("Using MySQL database");
|
||||||
driver = sql::mariadb::get_driver_instance();
|
driver = sql::mariadb::get_driver_instance();
|
||||||
|
|
||||||
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
|
// 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) {
|
sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) {
|
||||||
if (!con) {
|
if (!con) {
|
||||||
Connect();
|
Database::Get()->Connect();
|
||||||
LOG("Trying to reconnect to MySQL");
|
LOG("Trying to reconnect to MySQL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& quer
|
|||||||
|
|
||||||
con = nullptr;
|
con = nullptr;
|
||||||
|
|
||||||
Connect();
|
Database::Get()->Connect();
|
||||||
LOG("Trying to reconnect to MySQL from invalid or closed connection");
|
LOG("Trying to reconnect to MySQL from invalid or closed connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "GameDatabase.h"
|
#include "GameDatabase.h"
|
||||||
|
|
||||||
typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef;
|
typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef;
|
||||||
|
typedef std::unique_ptr<sql::ResultSet> UniqueResultSet;
|
||||||
|
|
||||||
// Purposefully no definition for this to provide linker errors in the case someone tries to
|
// 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.
|
// bind a parameter to a type that isn't defined.
|
||||||
@ -29,7 +30,6 @@ public:
|
|||||||
void Connect() override;
|
void Connect() override;
|
||||||
void Destroy(std::string source = "") override;
|
void Destroy(std::string source = "") override;
|
||||||
|
|
||||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
|
|
||||||
void Commit() override;
|
void Commit() override;
|
||||||
bool GetAutoCommit() override;
|
bool GetAutoCommit() override;
|
||||||
void SetAutoCommit(bool value) override;
|
void SetAutoCommit(bool value) override;
|
||||||
@ -113,6 +113,19 @@ public:
|
|||||||
void RemoveBehavior(const int32_t characterId) override;
|
void RemoveBehavior(const int32_t characterId) override;
|
||||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> 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<ILeaderboard::Score> 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<uint32_t> characterId) override;
|
||||||
|
void DeleteUgcBuild(const LWOOBJID bigId) override;
|
||||||
|
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
|
||||||
|
uint32_t GetAccountCount() override;
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Generic query functions that can be used for any query.
|
// Generic query functions that can be used for any query.
|
||||||
|
@ -39,3 +39,8 @@ void MySQLDatabase::InsertNewAccount(const std::string_view username, const std:
|
|||||||
void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
|
void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
|
||||||
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
|
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t MySQLDatabase::GetAccountCount() {
|
||||||
|
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
|
||||||
|
return res->next() ? res->getUInt("count") : 0;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
|
|||||||
"PropertyContents.cpp"
|
"PropertyContents.cpp"
|
||||||
"Servers.cpp"
|
"Servers.cpp"
|
||||||
"Ugc.cpp"
|
"Ugc.cpp"
|
||||||
|
"UgcModularBuild.cpp"
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
#include "MySQLDatabase.h"
|
#include "MySQLDatabase.h"
|
||||||
|
|
||||||
|
#include "Game.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "dConfig.h"
|
||||||
|
|
||||||
std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityId) {
|
std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityId) {
|
||||||
auto donation_total = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId);
|
auto donation_total = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId);
|
||||||
|
|
||||||
@ -9,3 +13,79 @@ std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityI
|
|||||||
|
|
||||||
return donation_total->getUInt("donation_total");
|
return donation_total->getUInt("donation_total");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> ProcessQuery(UniqueResultSet& rows) {
|
||||||
|
std::vector<ILeaderboard::Entry> entries;
|
||||||
|
entries.reserve(rows->rowsCount());
|
||||||
|
|
||||||
|
while (rows->next()) {
|
||||||
|
auto& entry = entries.emplace_back();
|
||||||
|
|
||||||
|
entry.charId = rows->getUInt("character_id");
|
||||||
|
entry.lastPlayedTimestamp = rows->getUInt("lp_unix");
|
||||||
|
entry.primaryScore = rows->getFloat("primaryScore");
|
||||||
|
entry.secondaryScore = rows->getFloat("secondaryScore");
|
||||||
|
entry.tertiaryScore = rows->getFloat("tertiaryScore");
|
||||||
|
entry.numWins = rows->getUInt("numWins");
|
||||||
|
entry.numTimesPlayed = rows->getUInt("timesPlayed");
|
||||||
|
entry.name = rows->getString("char_name");
|
||||||
|
// entry.ranking is never set because its calculated in leaderboard in code.
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> MySQLDatabase::GetDescendingLeaderboard(const uint32_t activityId) {
|
||||||
|
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) 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(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAscendingLeaderboard(const uint32_t activityId) {
|
||||||
|
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) 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(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAgsLeaderboard(const uint32_t activityId) {
|
||||||
|
auto query = Game::config->GetValue("classic_survival_scoring") != "1" ?
|
||||||
|
"SELECT *, UNIX_TIMESTAMP(last_played) 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 *, UNIX_TIMESTAMP(last_played) 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 leaderboard = ExecuteSelect(query, activityId);
|
||||||
|
return ProcessQuery(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> MySQLDatabase::GetNsLeaderboard(const uint32_t activityId) {
|
||||||
|
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) 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(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MySQLDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||||
|
ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;",
|
||||||
|
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||||
|
ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;",
|
||||||
|
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
|
||||||
|
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
|
||||||
|
std::optional<ILeaderboard::Score> toReturn = std::nullopt;
|
||||||
|
auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||||
|
if (res->next()) {
|
||||||
|
toReturn = ILeaderboard::Score{
|
||||||
|
.primaryScore = res->getFloat("primaryScore"),
|
||||||
|
.secondaryScore = res->getFloat("secondaryScore"),
|
||||||
|
.tertiaryScore = res->getFloat("tertiaryScore")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MySQLDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) {
|
||||||
|
ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||||
|
}
|
||||||
|
9
dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp
Normal file
9
dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include "MySQLDatabase.h"
|
||||||
|
|
||||||
|
void MySQLDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) {
|
||||||
|
ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MySQLDatabase::DeleteUgcBuild(const LWOOBJID bigId) {
|
||||||
|
ExecuteDelete("DELETE FROM ugc_modular_build WHERE ugc_id = ?;", bigId);
|
||||||
|
}
|
11
dDatabase/GameDatabase/SQLite/CMakeLists.txt
Normal file
11
dDatabase/GameDatabase/SQLite/CMakeLists.txt
Normal file
@ -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)
|
73
dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp
Normal file
73
dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp
Normal file
@ -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);
|
||||||
|
}
|
270
dDatabase/GameDatabase/SQLite/SQLiteDatabase.h
Normal file
270
dDatabase/GameDatabase/SQLite/SQLiteDatabase.h
Normal file
@ -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<typename ParamType>
|
||||||
|
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<typename... Args>
|
||||||
|
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<IServers::MasterInfo> GetMasterInfo() override;
|
||||||
|
|
||||||
|
std::vector<std::string> GetApprovedCharacterNames() override;
|
||||||
|
|
||||||
|
std::vector<FriendData> GetFriendsList(uint32_t charID) override;
|
||||||
|
|
||||||
|
std::optional<IFriends::BestFriendStatus> 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<IUgc::Model> GetAllUgcModels() override;
|
||||||
|
void CreateMigrationHistoryTable() override;
|
||||||
|
bool IsMigrationRun(const std::string_view str) override;
|
||||||
|
void InsertMigration(const std::string_view str) override;
|
||||||
|
std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) override;
|
||||||
|
std::optional<ICharInfo::Info> 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<IAccounts::Info> 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<uint32_t> 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<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
|
||||||
|
std::optional<IProperty::Info> 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<IPropertyContents::Model> 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<std::pair<int32_t, std::string>, 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<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
|
||||||
|
std::optional<IMail::MailInfo> 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<uint32_t> GetCurrentPersistentId() override;
|
||||||
|
void InsertDefaultPersistentId() override;
|
||||||
|
void UpdatePersistentId(const uint32_t id) override;
|
||||||
|
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
|
||||||
|
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
|
||||||
|
std::vector<IUgc::Model> 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<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
|
||||||
|
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
|
||||||
|
std::vector<uint32_t> 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<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
|
||||||
|
std::vector<ILeaderboard::Entry> 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<ILeaderboard::Score> 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<uint32_t> 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<typename... Args>
|
||||||
|
inline std::pair<CppSQLite3Statement, CppSQLite3Query> ExecuteSelect(const std::string& query, Args&&... args) {
|
||||||
|
std::pair<CppSQLite3Statement, CppSQLite3Query> toReturn;
|
||||||
|
toReturn.first = CreatePreppedStmt(query);
|
||||||
|
SetParams(toReturn.first, std::forward<Args>(args)...);
|
||||||
|
DLU_SQL_TRY_CATCH_RETHROW(toReturn.second = toReturn.first.execQuery());
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
inline void ExecuteDelete(const std::string& query, Args&&... args) {
|
||||||
|
auto preppedStmt = CreatePreppedStmt(query);
|
||||||
|
SetParams(preppedStmt, std::forward<Args>(args)...);
|
||||||
|
DLU_SQL_TRY_CATCH_RETHROW(preppedStmt.execDML());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
inline int32_t ExecuteUpdate(const std::string& query, Args&&... args) {
|
||||||
|
auto preppedStmt = CreatePreppedStmt(query);
|
||||||
|
SetParams(preppedStmt, std::forward<Args>(args)...);
|
||||||
|
DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt.execDML());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
inline int ExecuteInsert(const std::string& query, Args&&... args) {
|
||||||
|
auto preppedStmt = CreatePreppedStmt(query);
|
||||||
|
SetParams(preppedStmt, std::forward<Args>(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<int32_t>(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<sqlite_int64>(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) {
|
||||||
|
LOG("%llu", param);
|
||||||
|
stmt.bind(index, static_cast<sqlite_int64>(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<const unsigned char*>(stream.str().c_str()), stream.str().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
|
||||||
|
if (param) {
|
||||||
|
LOG("%d", param.value());
|
||||||
|
stmt.bind(index, static_cast<int>(param.value()));
|
||||||
|
} else {
|
||||||
|
LOG("Null");
|
||||||
|
stmt.bindNull(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //!SQLITEDATABASE_H
|
49
dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp
Normal file
49
dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
#include "eGameMasterLevel.h"
|
||||||
|
#include "Database.h"
|
||||||
|
|
||||||
|
std::optional<IAccounts::Info> 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<eGameMasterLevel>(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<int32_t>(eGameMasterLevel::OPERATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SQLiteDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
|
||||||
|
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SQLiteDatabase::GetAccountCount() {
|
||||||
|
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
|
||||||
|
if (res.eof()) return 0;
|
||||||
|
|
||||||
|
return res.getIntField("count");
|
||||||
|
}
|
17
dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp
Normal file
17
dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp
Normal file
@ -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<uint32_t> SQLiteDatabase::GetRewardCodesByAccountID(const uint32_t account_id) {
|
||||||
|
auto [_, result] = ExecuteSelect("SELECT rewardcode FROM accounts_rewardcodes WHERE account_id = ?;", account_id);
|
||||||
|
|
||||||
|
std::vector<uint32_t> toReturn;
|
||||||
|
while (!result.eof()) {
|
||||||
|
toReturn.push_back(result.getIntField("rewardcode"));
|
||||||
|
result.nextRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
6
dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp
Normal file
6
dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp
Normal file
@ -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<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
|
||||||
|
}
|
19
dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp
Normal file
19
dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp
Normal file
@ -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") : "";
|
||||||
|
}
|
6
dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp
Normal file
6
dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp
Normal file
@ -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);
|
||||||
|
}
|
26
dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt
Normal file
26
dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt
Normal file
@ -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
|
||||||
|
)
|
||||||
|
|
79
dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp
Normal file
79
dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::vector<std::string> SQLiteDatabase::GetApprovedCharacterNames() {
|
||||||
|
auto [_, result] = ExecuteSelect("SELECT name FROM charinfo;");
|
||||||
|
|
||||||
|
std::vector<std::string> toReturn;
|
||||||
|
|
||||||
|
while (!result.eof()) {
|
||||||
|
toReturn.push_back(result.getStringField("name"));
|
||||||
|
result.nextRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ICharInfo::Info> 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<ePermissionMap>(stmt.getIntField("permission_map"));
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ICharInfo::Info> 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<ICharInfo::Info> 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<uint32_t> 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<uint32_t> 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<uint32_t>(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<uint32_t>(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<uint32_t>(time(NULL)), characterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
|
||||||
|
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), characterId);
|
||||||
|
}
|
19
dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp
Normal file
19
dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp
Normal file
@ -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);
|
||||||
|
}
|
5
dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp
Normal file
5
dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp
Normal file
@ -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);
|
||||||
|
}
|
73
dDatabase/GameDatabase/SQLite/Tables/Friends.cpp
Normal file
73
dDatabase/GameDatabase/SQLite/Tables/Friends.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::vector<FriendData> 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<FriendData> 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<IFriends::BestFriendStatus> 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
|
||||||
|
);
|
||||||
|
}
|
22
dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp
Normal file
22
dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::vector<IIgnoreList::Info> 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<IIgnoreList::Info> ignoreList;
|
||||||
|
|
||||||
|
while (!result.eof()) {
|
||||||
|
ignoreList.push_back(IIgnoreList::Info{ result.getStringField("name"), static_cast<uint32_t>(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);
|
||||||
|
}
|
91
dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp
Normal file
91
dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
#include "Game.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "dConfig.h"
|
||||||
|
|
||||||
|
std::optional<uint32_t> 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<ILeaderboard::Entry> ProcessQuery(CppSQLite3Query& rows) {
|
||||||
|
std::vector<ILeaderboard::Entry> 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<ILeaderboard::Entry> 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<ILeaderboard::Entry> 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<ILeaderboard::Entry> 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<ILeaderboard::Entry> 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<ILeaderboard::Score> SQLiteDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
|
||||||
|
std::optional<ILeaderboard::Score> 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<float>(res.getFloatField("primaryScore")),
|
||||||
|
.secondaryScore = static_cast<float>(res.getFloatField("secondaryScore")),
|
||||||
|
.tertiaryScore = static_cast<float>(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);
|
||||||
|
}
|
83
dDatabase/GameDatabase/SQLite/Tables/Mail.cpp
Normal file
83
dDatabase/GameDatabase/SQLite/Tables/Mail.cpp
Normal file
@ -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<uint32_t>(time(NULL)),
|
||||||
|
mail.subject,
|
||||||
|
mail.body,
|
||||||
|
mail.itemID,
|
||||||
|
mail.itemLOT,
|
||||||
|
0,
|
||||||
|
mail.itemCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<IMail::MailInfo> 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<IMail::MailInfo> 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<IMail::MailInfo> 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);
|
||||||
|
}
|
13
dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp
Normal file
13
dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp
Normal file
@ -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);
|
||||||
|
}
|
17
dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp
Normal file
17
dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::optional<uint32_t> 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);
|
||||||
|
}
|
26
dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp
Normal file
26
dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp
Normal file
@ -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<IPetNames::Info> 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;
|
||||||
|
}
|
11
dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp
Normal file
11
dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::optional<bool> 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");
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
195
dDatabase/GameDatabase/SQLite/Tables/Property.cpp
Normal file
195
dDatabase/GameDatabase/SQLite/Tables/Property.cpp
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
#include "ePropertySortType.h"
|
||||||
|
|
||||||
|
std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||||
|
std::optional<IProperty::PropertyEntranceResult> result;
|
||||||
|
std::string query;
|
||||||
|
std::pair<CppSQLite3Statement, CppSQLite3Query> 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<IProperty::Info> 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()
|
||||||
|
);
|
||||||
|
}
|
65
dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp
Normal file
65
dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::vector<IPropertyContents::Model> 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<IPropertyContents::Model> toReturn;
|
||||||
|
while (!result.eof()) {
|
||||||
|
IPropertyContents::Model model;
|
||||||
|
model.id = result.getInt64Field("id");
|
||||||
|
model.lot = static_cast<LOT>(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<uint32_t>(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<std::pair<int32_t, std::string>, 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);
|
||||||
|
}
|
23
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
23
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
@ -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<IServers::MasterInfo> 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;
|
||||||
|
}
|
72
dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp
Normal file
72
dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
std::vector<IUgc::Model> 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<IUgc::Model> toReturn;
|
||||||
|
|
||||||
|
while (!result.eof()) {
|
||||||
|
IUgc::Model model;
|
||||||
|
|
||||||
|
int blobSize{};
|
||||||
|
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||||
|
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
|
||||||
|
model.id = result.getInt64Field("id");
|
||||||
|
toReturn.push_back(std::move(model));
|
||||||
|
result.nextRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
|
||||||
|
auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||||
|
|
||||||
|
std::vector<IUgc::Model> 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<const char*>(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);
|
||||||
|
}
|
9
dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp
Normal file
9
dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
void SQLiteDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> 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);
|
||||||
|
}
|
@ -8,10 +8,6 @@ void TestSQLDatabase::Destroy(std::string source) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sql::PreparedStatement* TestSQLDatabase::CreatePreppedStmt(const std::string& query) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestSQLDatabase::Commit() {
|
void TestSQLDatabase::Commit() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ class TestSQLDatabase : public GameDatabase {
|
|||||||
void Connect() override;
|
void Connect() override;
|
||||||
void Destroy(std::string source = "") override;
|
void Destroy(std::string source = "") override;
|
||||||
|
|
||||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
|
|
||||||
void Commit() override;
|
void Commit() override;
|
||||||
bool GetAutoCommit() override;
|
bool GetAutoCommit() override;
|
||||||
void SetAutoCommit(bool value) override;
|
void SetAutoCommit(bool value) override;
|
||||||
@ -91,6 +90,18 @@ class TestSQLDatabase : public GameDatabase {
|
|||||||
void RemoveBehavior(const int32_t behaviorId) override;
|
void RemoveBehavior(const int32_t behaviorId) override;
|
||||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
|
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
|
||||||
|
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
|
||||||
|
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; };
|
||||||
|
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override { return {}; };
|
||||||
|
std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override { return {}; };
|
||||||
|
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<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override { return {}; };
|
||||||
|
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<uint32_t> characterId) override {};
|
||||||
|
void DeleteUgcBuild(const LWOOBJID bigId) override {};
|
||||||
|
uint32_t GetAccountCount() override { return 0; };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!TESTSQLDATABASE_H
|
#endif //!TESTSQLDATABASE_H
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
Migration LoadMigration(std::string path) {
|
Migration LoadMigration(std::string folder, std::string path) {
|
||||||
Migration migration{};
|
Migration migration{};
|
||||||
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path);
|
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / folder / path);
|
||||||
|
|
||||||
if (file.is_open()) {
|
if (file.is_open()) {
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -34,10 +34,19 @@ Migration LoadMigration(std::string path) {
|
|||||||
void MigrationRunner::RunMigrations() {
|
void MigrationRunner::RunMigrations() {
|
||||||
Database::Get()->CreateMigrationHistoryTable();
|
Database::Get()->CreateMigrationHistoryTable();
|
||||||
|
|
||||||
sql::SQLString finalSQL = "";
|
// 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;
|
bool runSd0Migrations = false;
|
||||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) {
|
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
||||||
auto migration = LoadMigration("dlu/" + entry);
|
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
||||||
|
|
||||||
if (migration.data.empty()) {
|
if (migration.data.empty()) {
|
||||||
continue;
|
continue;
|
||||||
@ -46,7 +55,7 @@ void MigrationRunner::RunMigrations() {
|
|||||||
if (Database::Get()->IsMigrationRun(migration.name)) continue;
|
if (Database::Get()->IsMigrationRun(migration.name)) continue;
|
||||||
|
|
||||||
LOG("Running migration: %s", migration.name.c_str());
|
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;
|
runSd0Migrations = true;
|
||||||
} else {
|
} else {
|
||||||
finalSQL.append(migration.data.c_str());
|
finalSQL.append(migration.data.c_str());
|
||||||
@ -61,12 +70,12 @@ void MigrationRunner::RunMigrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!finalSQL.empty()) {
|
if (!finalSQL.empty()) {
|
||||||
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';');
|
auto migration = GeneralUtils::SplitString(finalSQL, ';');
|
||||||
for (auto& query : migration) {
|
for (auto& query : migration) {
|
||||||
try {
|
try {
|
||||||
if (query.empty()) continue;
|
if (query.empty()) continue;
|
||||||
Database::Get()->ExecuteCustomQuery(query.c_str());
|
Database::Get()->ExecuteCustomQuery(query);
|
||||||
} catch (sql::SQLException& e) {
|
} catch (std::exception& e) {
|
||||||
LOG("Encountered error running migration: %s", e.what());
|
LOG("Encountered error running migration: %s", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,10 +95,14 @@ void MigrationRunner::RunSQLiteMigrations() {
|
|||||||
cdstmt.execQuery().finalize();
|
cdstmt.execQuery().finalize();
|
||||||
cdstmt.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())) {
|
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;
|
if (migration.data.empty()) continue;
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ target_include_directories(dGameBase PUBLIC "." "dEntity"
|
|||||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
|
||||||
# dPhysics
|
# dPhysics
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
#include "ItemComponent.h"
|
#include "ItemComponent.h"
|
||||||
#include "GhostComponent.h"
|
#include "GhostComponent.h"
|
||||||
#include "AchievementVendorComponent.h"
|
#include "AchievementVendorComponent.h"
|
||||||
|
#include "VanityUtilities.h"
|
||||||
|
|
||||||
// Table includes
|
// Table includes
|
||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
@ -96,6 +97,8 @@
|
|||||||
#include "CDSkillBehaviorTable.h"
|
#include "CDSkillBehaviorTable.h"
|
||||||
#include "CDZoneTableTable.h"
|
#include "CDZoneTableTable.h"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
|
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
|
||||||
|
|
||||||
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
|
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
|
||||||
@ -285,8 +288,9 @@ void Entity::Initialize() {
|
|||||||
AddComponent<PropertyEntranceComponent>(propertyEntranceComponentID);
|
AddComponent<PropertyEntranceComponent>(propertyEntranceComponentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS) > 0) {
|
const int32_t controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS);
|
||||||
auto* controllablePhysics = AddComponent<ControllablePhysicsComponent>();
|
if (controllablePhysicsComponentID > 0) {
|
||||||
|
auto* controllablePhysics = AddComponent<ControllablePhysicsComponent>(controllablePhysicsComponentID);
|
||||||
|
|
||||||
if (m_Character) {
|
if (m_Character) {
|
||||||
controllablePhysics->LoadFromXml(m_Character->GetXMLDoc());
|
controllablePhysics->LoadFromXml(m_Character->GetXMLDoc());
|
||||||
@ -329,16 +333,19 @@ void Entity::Initialize() {
|
|||||||
AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID);
|
AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) > 0) {
|
const int32_t rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS);
|
||||||
AddComponent<RigidbodyPhantomPhysicsComponent>();
|
if (rigidBodyPhantomPhysicsComponentID > 0) {
|
||||||
|
AddComponent<RigidbodyPhantomPhysicsComponent>(rigidBodyPhantomPhysicsComponentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (markedAsPhantom || compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS) > 0) {
|
const int32_t phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS);
|
||||||
AddComponent<PhantomPhysicsComponent>()->SetPhysicsEffectActive(false);
|
if (markedAsPhantom || phantomPhysicsComponentID > 0) {
|
||||||
|
AddComponent<PhantomPhysicsComponent>(phantomPhysicsComponentID)->SetPhysicsEffectActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) > 0) {
|
const int32_t havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS);
|
||||||
auto* havokVehiclePhysicsComponent = AddComponent<HavokVehiclePhysicsComponent>();
|
if (havokVehiclePhysicsComponentID > 0) {
|
||||||
|
auto* havokVehiclePhysicsComponent = AddComponent<HavokVehiclePhysicsComponent>(havokVehiclePhysicsComponentID);
|
||||||
havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition);
|
havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition);
|
||||||
havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation);
|
havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation);
|
||||||
}
|
}
|
||||||
@ -1271,6 +1278,7 @@ void Entity::Update(const float deltaTime) {
|
|||||||
auto timerName = timer.GetName();
|
auto timerName = timer.GetName();
|
||||||
m_Timers.erase(m_Timers.begin() + timerPosition);
|
m_Timers.erase(m_Timers.begin() + timerPosition);
|
||||||
GetScript()->OnTimerDone(this, timerName);
|
GetScript()->OnTimerDone(this, timerName);
|
||||||
|
VanityUtilities::OnTimerDone(this, timerName);
|
||||||
|
|
||||||
TriggerEvent(eTriggerEventType::TIMER_DONE, this);
|
TriggerEvent(eTriggerEventType::TIMER_DONE, this);
|
||||||
} else {
|
} else {
|
||||||
@ -1334,6 +1342,7 @@ void Entity::OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxN
|
|||||||
if (!other) return;
|
if (!other) return;
|
||||||
|
|
||||||
GetScript()->OnProximityUpdate(this, other, proxName, status);
|
GetScript()->OnProximityUpdate(this, other, proxName, status);
|
||||||
|
VanityUtilities::OnProximityUpdate(this, other, proxName, status);
|
||||||
|
|
||||||
RocketLaunchpadControlComponent* rocketComp = GetComponent<RocketLaunchpadControlComponent>();
|
RocketLaunchpadControlComponent* rocketComp = GetComponent<RocketLaunchpadControlComponent>();
|
||||||
if (!rocketComp) return;
|
if (!rocketComp) return;
|
||||||
@ -1351,6 +1360,11 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
|
|||||||
callback(other);
|
callback(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwitchComponent* switchComp = GetComponent<SwitchComponent>();
|
||||||
|
if (switchComp) {
|
||||||
|
switchComp->OnUse(other);
|
||||||
|
}
|
||||||
|
|
||||||
TriggerEvent(eTriggerEventType::ENTER, other);
|
TriggerEvent(eTriggerEventType::ENTER, other);
|
||||||
|
|
||||||
// POI system
|
// POI system
|
||||||
@ -2153,7 +2167,19 @@ void Entity::SetRespawnPos(const NiPoint3& position) {
|
|||||||
auto* characterComponent = GetComponent<CharacterComponent>();
|
auto* characterComponent = GetComponent<CharacterComponent>();
|
||||||
if (characterComponent) characterComponent->SetRespawnPos(position);
|
if (characterComponent) characterComponent->SetRespawnPos(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
||||||
auto* characterComponent = GetComponent<CharacterComponent>();
|
auto* characterComponent = GetComponent<CharacterComponent>();
|
||||||
if (characterComponent) characterComponent->SetRespawnRot(rotation);
|
if (characterComponent) characterComponent->SetRespawnRot(rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t Entity::GetCollisionGroup() const {
|
||||||
|
for (const auto* component : m_Components | std::views::values) {
|
||||||
|
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
|
||||||
|
if (compToCheck) {
|
||||||
|
return compToCheck->GetCollisionGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -107,6 +107,11 @@ public:
|
|||||||
|
|
||||||
const SystemAddress& GetSystemAddress() const;
|
const SystemAddress& GetSystemAddress() const;
|
||||||
|
|
||||||
|
// Returns the collision group for this entity.
|
||||||
|
// Because the collision group is stored on a base component, this will look for a physics component
|
||||||
|
// then return the collision group from that.
|
||||||
|
int32_t GetCollisionGroup() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setters
|
* Setters
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "LeaderboardManager.h"
|
#include "LeaderboardManager.h"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -72,197 +73,191 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const {
|
|||||||
bitStream.Write0();
|
bitStream.Write0();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
|
// Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
|
||||||
Clear();
|
// to send it to a client.
|
||||||
if (rows->rowsCount() == 0) return;
|
void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>& leaderboardEntries) {
|
||||||
|
using enum Leaderboard::Type;
|
||||||
|
leaderboard.Clear();
|
||||||
|
if (leaderboardEntries.empty()) return;
|
||||||
|
|
||||||
this->entries.reserve(rows->rowsCount());
|
for (const auto& leaderboardEntry : leaderboardEntries) {
|
||||||
while (rows->next()) {
|
|
||||||
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
|
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
|
||||||
this->entries.push_back(std::vector<LDFBaseData*>());
|
auto& entry = leaderboard.PushBackEntry();
|
||||||
auto& entry = this->entries.back();
|
|
||||||
entry.reserve(MAX_NUM_DATA_PER_ROW);
|
entry.reserve(MAX_NUM_DATA_PER_ROW);
|
||||||
entry.push_back(new LDFData<uint64_t>(u"CharacterID", rows->getInt("character_id")));
|
entry.push_back(new LDFData<uint64_t>(u"CharacterID", leaderboardEntry.charId));
|
||||||
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", rows->getUInt64("lastPlayed")));
|
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp));
|
||||||
entry.push_back(new LDFData<int32_t>(u"NumPlayed", rows->getInt("timesPlayed")));
|
entry.push_back(new LDFData<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed));
|
||||||
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(rows->getString("name").c_str())));
|
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)));
|
||||||
entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking")));
|
entry.push_back(new LDFData<uint64_t>(u"RowNumber", leaderboardEntry.ranking));
|
||||||
switch (leaderboardType) {
|
switch (leaderboard.GetLeaderboardType()) {
|
||||||
case Type::ShootingGallery:
|
case ShootingGallery:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||||
// Score:1
|
// Score:1
|
||||||
entry.push_back(new LDFData<int32_t>(u"Streak", rows->getInt("secondaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Streak", leaderboardEntry.secondaryScore));
|
||||||
// Streak:1
|
// Streak:1
|
||||||
entry.push_back(new LDFData<float>(u"HitPercentage", (rows->getInt("tertiaryScore") / 100.0f)));
|
entry.push_back(new LDFData<float>(u"HitPercentage", (leaderboardEntry.tertiaryScore / 100.0f)));
|
||||||
// HitPercentage:3 between 0 and 1
|
// HitPercentage:3 between 0 and 1
|
||||||
break;
|
break;
|
||||||
case Type::Racing:
|
case Racing:
|
||||||
entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("primaryScore")));
|
entry.push_back(new LDFData<float>(u"BestTime", leaderboardEntry.primaryScore));
|
||||||
// BestLapTime:3
|
// BestLapTime:3
|
||||||
entry.push_back(new LDFData<float>(u"BestLapTime", rows->getDouble("secondaryScore")));
|
entry.push_back(new LDFData<float>(u"BestLapTime", leaderboardEntry.secondaryScore));
|
||||||
// BestTime:3
|
// BestTime:3
|
||||||
entry.push_back(new LDFData<int32_t>(u"License", 1));
|
entry.push_back(new LDFData<int32_t>(u"License", 1));
|
||||||
// License:1 - 1 if player has completed mission 637 and 0 otherwise
|
// License:1 - 1 if player has completed mission 637 and 0 otherwise
|
||||||
entry.push_back(new LDFData<int32_t>(u"NumWins", rows->getInt("numWins")));
|
entry.push_back(new LDFData<int32_t>(u"NumWins", leaderboardEntry.numWins));
|
||||||
// NumWins:1
|
// NumWins:1
|
||||||
break;
|
break;
|
||||||
case Type::UnusedLeaderboard4:
|
case UnusedLeaderboard4:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||||
// Points:1
|
// Points:1
|
||||||
break;
|
break;
|
||||||
case Type::MonumentRace:
|
case MonumentRace:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||||
// Time:1(?)
|
// Time:1(?)
|
||||||
break;
|
break;
|
||||||
case Type::FootRace:
|
case FootRace:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||||
// Time:1
|
// Time:1
|
||||||
break;
|
break;
|
||||||
case Type::Survival:
|
case Survival:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||||
// Points:1
|
// Points:1
|
||||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
|
||||||
// Time:1
|
// Time:1
|
||||||
break;
|
break;
|
||||||
case Type::SurvivalNS:
|
case SurvivalNS:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Wave", leaderboardEntry.primaryScore));
|
||||||
// Wave:1
|
// Wave:1
|
||||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
|
||||||
// Time:1
|
// Time:1
|
||||||
break;
|
break;
|
||||||
case Type::Donations:
|
case Donations:
|
||||||
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
|
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||||
// Score:1
|
// Score:1
|
||||||
break;
|
break;
|
||||||
case Type::None:
|
case None:
|
||||||
// This type is included here simply to resolve a compiler warning on mac about unused enum types
|
[[fallthrough]];
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
|
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
|
||||||
// Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better
|
std::vector<ILeaderboard::Entry> toReturn;
|
||||||
|
|
||||||
|
int32_t index = 0;
|
||||||
|
// for friends and top, we dont need to find this players index.
|
||||||
|
if (infoType == Leaderboard::InfoType::MyStanding || infoType == Leaderboard::InfoType::Friends) {
|
||||||
|
for (; index < leaderboard.size(); index++) {
|
||||||
|
if (leaderboard[index].charId == relatedPlayer) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leaderboard.size() < 10) {
|
||||||
|
toReturn.assign(leaderboard.begin(), leaderboard.end());
|
||||||
|
index = 0;
|
||||||
|
} else if (index < 10) {
|
||||||
|
toReturn.assign(leaderboard.begin(), leaderboard.begin() + 10); // get the top 10 since we are in the top 10
|
||||||
|
index = 0;
|
||||||
|
} else if (index > leaderboard.size() - 10) {
|
||||||
|
toReturn.assign(leaderboard.end() - 10, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
|
||||||
|
index = leaderboard.size() - 10;
|
||||||
|
} else {
|
||||||
|
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
|
||||||
|
index -= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t i = index;
|
||||||
|
for (auto& entry : toReturn) {
|
||||||
|
entry.ranking = ++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
|
||||||
|
// Filter the leaderboard to only include entries from the last week
|
||||||
|
const auto currentTime = std::chrono::system_clock::now();
|
||||||
|
auto epochTime = currentTime.time_since_epoch().count();
|
||||||
|
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
|
||||||
|
for (const auto& entry : leaderboard) {
|
||||||
|
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
|
||||||
|
weeklyLeaderboard.push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weeklyLeaderboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> FilterFriends(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer) {
|
||||||
|
// Filter the leaderboard to only include friends of the player
|
||||||
|
auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer);
|
||||||
|
std::vector<ILeaderboard::Entry> friendsLeaderboard;
|
||||||
|
for (const auto& entry : leaderboard) {
|
||||||
|
const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
|
||||||
|
return entry.charId == data.friendID || entry.charId == relatedPlayer;
|
||||||
|
});
|
||||||
|
if (res != friendOfPlayer.cend()) {
|
||||||
|
friendsLeaderboard.push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return friendsLeaderboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ILeaderboard::Entry> ProcessLeaderboard(
|
||||||
|
const std::vector<ILeaderboard::Entry>& leaderboard,
|
||||||
|
const bool weekly,
|
||||||
|
const Leaderboard::InfoType infoType,
|
||||||
|
const uint32_t relatedPlayer) {
|
||||||
|
std::vector<ILeaderboard::Entry> toReturn;
|
||||||
|
|
||||||
|
if (infoType == Leaderboard::InfoType::Friends) {
|
||||||
|
const auto friendsLeaderboard = FilterFriends(leaderboard, relatedPlayer);
|
||||||
|
toReturn = FilterTo10(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType);
|
||||||
|
} else {
|
||||||
|
toReturn = FilterTo10(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Leaderboard::SetupLeaderboard(bool weekly) {
|
||||||
|
const auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID);
|
||||||
|
std::vector<ILeaderboard::Entry> leaderboardRes;
|
||||||
|
|
||||||
switch (leaderboardType) {
|
switch (leaderboardType) {
|
||||||
case Type::Racing:
|
|
||||||
case Type::MonumentRace:
|
|
||||||
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC";
|
|
||||||
case Type::Survival:
|
|
||||||
return Game::config->GetValue("classic_survival_scoring") == "1" ?
|
|
||||||
"secondaryScore DESC, primaryScore DESC, tertiaryScore DESC" :
|
|
||||||
"primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
|
|
||||||
case Type::SurvivalNS:
|
case Type::SurvivalNS:
|
||||||
return "primaryScore DESC, secondaryScore ASC, tertiaryScore DESC";
|
leaderboardRes = Database::Get()->GetNsLeaderboard(gameID);
|
||||||
|
break;
|
||||||
|
case Type::Survival:
|
||||||
|
leaderboardRes = Database::Get()->GetAgsLeaderboard(gameID);
|
||||||
|
break;
|
||||||
|
case Type::Racing:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Type::MonumentRace:
|
||||||
|
leaderboardRes = Database::Get()->GetAscendingLeaderboard(gameID);
|
||||||
|
break;
|
||||||
case Type::ShootingGallery:
|
case Type::ShootingGallery:
|
||||||
|
[[fallthrough]];
|
||||||
case Type::FootRace:
|
case Type::FootRace:
|
||||||
case Type::UnusedLeaderboard4:
|
[[fallthrough]];
|
||||||
case Type::Donations:
|
case Type::Donations:
|
||||||
|
[[fallthrough]];
|
||||||
case Type::None:
|
case Type::None:
|
||||||
|
[[fallthrough]];
|
||||||
default:
|
default:
|
||||||
return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
|
leaderboardRes = Database::Get()->GetDescendingLeaderboard(gameID);
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) {
|
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
|
||||||
resultStart++;
|
|
||||||
resultEnd++;
|
|
||||||
// We need everything except 1 column so i'm selecting * from leaderboard
|
|
||||||
const std::string queryBase =
|
|
||||||
R"QUERY(
|
|
||||||
WITH leaderboardsRanked AS (
|
|
||||||
SELECT leaderboard.*, charinfo.name,
|
|
||||||
RANK() OVER
|
|
||||||
(
|
|
||||||
ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC
|
|
||||||
) AS ranking
|
|
||||||
FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id
|
|
||||||
WHERE game_id = ? %s
|
|
||||||
),
|
|
||||||
myStanding AS (
|
|
||||||
SELECT
|
|
||||||
ranking as myRank
|
|
||||||
FROM leaderboardsRanked
|
|
||||||
WHERE id = ?
|
|
||||||
),
|
|
||||||
lowestRanking AS (
|
|
||||||
SELECT MAX(ranking) AS lowestRank
|
|
||||||
FROM leaderboardsRanked
|
|
||||||
)
|
|
||||||
SELECT leaderboardsRanked.*, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking
|
|
||||||
WHERE leaderboardsRanked.ranking
|
|
||||||
BETWEEN
|
|
||||||
LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), CAST(lowestRanking.lowestRank AS SIGNED) - 9)
|
|
||||||
AND
|
|
||||||
LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank)
|
|
||||||
ORDER BY ranking ASC;
|
|
||||||
)QUERY";
|
|
||||||
|
|
||||||
std::string friendsFilter =
|
QueryToLdf(*this, processedLeaderboard);
|
||||||
R"QUERY(
|
|
||||||
AND (
|
|
||||||
character_id IN (
|
|
||||||
SELECT fr.requested_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
|
|
||||||
)
|
|
||||||
OR character_id = ?
|
|
||||||
)
|
|
||||||
)QUERY";
|
|
||||||
|
|
||||||
std::string weeklyFilter = " AND UNIX_TIMESTAMP(last_played) BETWEEN UNIX_TIMESTAMP(date_sub(now(),INTERVAL 1 WEEK)) AND UNIX_TIMESTAMP(now()) ";
|
|
||||||
|
|
||||||
std::string filter;
|
|
||||||
// Setup our filter based on the query type
|
|
||||||
if (this->infoType == InfoType::Friends) filter += friendsFilter;
|
|
||||||
if (this->weekly) filter += weeklyFilter;
|
|
||||||
const auto orderBase = GetOrdering(this->leaderboardType);
|
|
||||||
|
|
||||||
// For top query, we want to just rank all scores, but for all others we need the scores around a specific player
|
|
||||||
std::string baseLookup;
|
|
||||||
if (this->infoType == InfoType::Top) {
|
|
||||||
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " ORDER BY ";
|
|
||||||
baseLookup += orderBase.data();
|
|
||||||
} else {
|
|
||||||
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " AND character_id = ";
|
|
||||||
baseLookup += std::to_string(static_cast<uint32_t>(this->relatedPlayer));
|
|
||||||
}
|
|
||||||
baseLookup += " LIMIT 1";
|
|
||||||
LOG_DEBUG("query is %s", baseLookup.c_str());
|
|
||||||
std::unique_ptr<sql::PreparedStatement> baseQuery(Database::Get()->CreatePreppedStmt(baseLookup));
|
|
||||||
baseQuery->setInt(1, this->gameID);
|
|
||||||
std::unique_ptr<sql::ResultSet> baseResult(baseQuery->executeQuery());
|
|
||||||
|
|
||||||
if (!baseResult->next()) return; // In this case, there are no entries in the leaderboard for this game.
|
|
||||||
|
|
||||||
uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id");
|
|
||||||
|
|
||||||
// Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow
|
|
||||||
constexpr uint16_t STRING_LENGTH = 4096;
|
|
||||||
std::unique_ptr<char[]> lookupBuffer = std::make_unique<char[]>(STRING_LENGTH);
|
|
||||||
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd);
|
|
||||||
DluAssert(res != -1);
|
|
||||||
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt(lookupBuffer.get()));
|
|
||||||
LOG_DEBUG("Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId);
|
|
||||||
query->setInt(1, this->gameID);
|
|
||||||
if (this->infoType == InfoType::Friends) {
|
|
||||||
query->setInt(2, this->relatedPlayer);
|
|
||||||
query->setInt(3, this->relatedPlayer);
|
|
||||||
query->setInt(4, this->relatedPlayer);
|
|
||||||
query->setInt(5, relatedPlayerLeaderboardId);
|
|
||||||
} else {
|
|
||||||
query->setInt(2, relatedPlayerLeaderboardId);
|
|
||||||
}
|
|
||||||
std::unique_ptr<sql::ResultSet> result(query->executeQuery());
|
|
||||||
QueryToLdf(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Leaderboard::Send(const LWOOBJID targetID) const {
|
void Leaderboard::Send(const LWOOBJID targetID) const {
|
||||||
@ -272,129 +267,43 @@ void Leaderboard::Send(const LWOOBJID targetID) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) {
|
|
||||||
std::string insertStatement;
|
|
||||||
if (useUpdate) {
|
|
||||||
insertStatement =
|
|
||||||
R"QUERY(
|
|
||||||
UPDATE leaderboard
|
|
||||||
SET primaryScore = %f, secondaryScore = %f, tertiaryScore = %f,
|
|
||||||
timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;
|
|
||||||
)QUERY";
|
|
||||||
} else {
|
|
||||||
insertStatement =
|
|
||||||
R"QUERY(
|
|
||||||
INSERT leaderboard SET
|
|
||||||
primaryScore = %f, secondaryScore = %f, tertiaryScore = %f,
|
|
||||||
character_id = ?, game_id = ?;
|
|
||||||
)QUERY";
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint16_t STRING_LENGTH = 400;
|
|
||||||
// Then fill in our score
|
|
||||||
char finishedQuery[STRING_LENGTH];
|
|
||||||
int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore());
|
|
||||||
DluAssert(res != -1);
|
|
||||||
return finishedQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) {
|
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) {
|
||||||
const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId);
|
const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId);
|
||||||
|
|
||||||
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"));
|
const auto oldScore = Database::Get()->GetPlayerScore(playerID, activityId);
|
||||||
query->setInt(1, playerID);
|
|
||||||
query->setInt(2, activityId);
|
|
||||||
std::unique_ptr<sql::ResultSet> myScoreResult(query->executeQuery());
|
|
||||||
|
|
||||||
std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;");
|
ILeaderboard::Score newScore{ .primaryScore = primaryScore, .secondaryScore = secondaryScore, .tertiaryScore = tertiaryScore };
|
||||||
Score newScore(primaryScore, secondaryScore, tertiaryScore);
|
if (oldScore.has_value()) {
|
||||||
if (myScoreResult->next()) {
|
bool lowerScoreBetter = leaderboardType == Leaderboard::Type::Racing || leaderboardType == Leaderboard::Type::MonumentRace;
|
||||||
Score oldScore;
|
|
||||||
bool lowerScoreBetter = false;
|
|
||||||
switch (leaderboardType) {
|
|
||||||
// Higher score better
|
|
||||||
case Leaderboard::Type::ShootingGallery: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
|
|
||||||
oldScore.SetTertiaryScore(myScoreResult->getInt("tertiaryScore"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::FootRace: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::Survival: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::SurvivalNS: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::UnusedLeaderboard4:
|
|
||||||
case Leaderboard::Type::Donations: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
newScore.SetPrimaryScore(oldScore.GetPrimaryScore() + newScore.GetPrimaryScore());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::Racing: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
|
|
||||||
|
|
||||||
// For wins we dont care about the score, just the time, so zero out the tertiary.
|
|
||||||
// Wins are updated later.
|
|
||||||
oldScore.SetTertiaryScore(0);
|
|
||||||
newScore.SetTertiaryScore(0);
|
|
||||||
lowerScoreBetter = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::MonumentRace: {
|
|
||||||
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
|
|
||||||
lowerScoreBetter = true;
|
|
||||||
// Do score checking here
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Leaderboard::Type::None:
|
|
||||||
default:
|
|
||||||
LOG("Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, activityId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
|
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
|
||||||
// Nimbus station has a weird leaderboard where we need a custom scoring system
|
// Nimbus station has a weird leaderboard where we need a custom scoring system
|
||||||
if (leaderboardType == Leaderboard::Type::SurvivalNS) {
|
if (leaderboardType == Leaderboard::Type::SurvivalNS) {
|
||||||
newHighScore = newScore.GetPrimaryScore() > oldScore.GetPrimaryScore() ||
|
newHighScore = newScore.primaryScore > oldScore->primaryScore ||
|
||||||
(newScore.GetPrimaryScore() == oldScore.GetPrimaryScore() && newScore.GetSecondaryScore() < oldScore.GetSecondaryScore());
|
(newScore.primaryScore == oldScore->primaryScore && newScore.secondaryScore < oldScore->secondaryScore);
|
||||||
} else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") {
|
} else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") {
|
||||||
Score oldScoreFlipped(oldScore.GetSecondaryScore(), oldScore.GetPrimaryScore());
|
ILeaderboard::Score oldScoreFlipped{oldScore->secondaryScore, oldScore->primaryScore, oldScore->tertiaryScore};
|
||||||
Score newScoreFlipped(newScore.GetSecondaryScore(), newScore.GetPrimaryScore());
|
ILeaderboard::Score newScoreFlipped{newScore.secondaryScore, newScore.primaryScore, newScore.tertiaryScore};
|
||||||
newHighScore = newScoreFlipped > oldScoreFlipped;
|
newHighScore = newScoreFlipped > oldScoreFlipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newHighScore) {
|
if (newHighScore) {
|
||||||
saveQuery = FormatInsert(leaderboardType, newScore, true);
|
Database::Get()->UpdateScore(playerID, activityId, newScore);
|
||||||
|
} else {
|
||||||
|
Database::Get()->IncrementTimesPlayed(playerID, activityId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
saveQuery = FormatInsert(leaderboardType, newScore, false);
|
Database::Get()->SaveScore(playerID, activityId, newScore);
|
||||||
}
|
}
|
||||||
LOG("save query %s %i %i", saveQuery.c_str(), playerID, activityId);
|
|
||||||
std::unique_ptr<sql::PreparedStatement> saveStatement(Database::Get()->CreatePreppedStmt(saveQuery));
|
|
||||||
saveStatement->setInt(1, playerID);
|
|
||||||
saveStatement->setInt(2, activityId);
|
|
||||||
saveStatement->execute();
|
|
||||||
|
|
||||||
// track wins separately
|
// track wins separately
|
||||||
if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) {
|
if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) {
|
||||||
std::unique_ptr<sql::PreparedStatement> winUpdate(Database::Get()->CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;"));
|
Database::Get()->IncrementNumWins(playerID, activityId);
|
||||||
winUpdate->setInt(1, playerID);
|
|
||||||
winUpdate->setInt(2, activityId);
|
|
||||||
winUpdate->execute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) {
|
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID) {
|
||||||
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
|
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
|
||||||
leaderboard.SetupLeaderboard(weekly, resultStart, resultEnd);
|
leaderboard.SetupLeaderboard(weekly);
|
||||||
leaderboard.Send(targetID);
|
leaderboard.Send(targetID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,46 +9,10 @@
|
|||||||
#include "dCommonVars.h"
|
#include "dCommonVars.h"
|
||||||
#include "LDFFormat.h"
|
#include "LDFFormat.h"
|
||||||
|
|
||||||
namespace sql {
|
|
||||||
class ResultSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace RakNet {
|
namespace RakNet {
|
||||||
class BitStream;
|
class BitStream;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Score {
|
|
||||||
public:
|
|
||||||
Score() {
|
|
||||||
primaryScore = 0;
|
|
||||||
secondaryScore = 0;
|
|
||||||
tertiaryScore = 0;
|
|
||||||
}
|
|
||||||
Score(const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0) {
|
|
||||||
this->primaryScore = primaryScore;
|
|
||||||
this->secondaryScore = secondaryScore;
|
|
||||||
this->tertiaryScore = tertiaryScore;
|
|
||||||
}
|
|
||||||
bool operator<(const Score& rhs) const {
|
|
||||||
return primaryScore < rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore < rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore < rhs.tertiaryScore);
|
|
||||||
}
|
|
||||||
bool operator>(const Score& rhs) const {
|
|
||||||
return primaryScore > rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore > rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore > rhs.tertiaryScore);
|
|
||||||
}
|
|
||||||
void SetPrimaryScore(const float score) { primaryScore = score; }
|
|
||||||
float GetPrimaryScore() const { return primaryScore; }
|
|
||||||
|
|
||||||
void SetSecondaryScore(const float score) { secondaryScore = score; }
|
|
||||||
float GetSecondaryScore() const { return secondaryScore; }
|
|
||||||
|
|
||||||
void SetTertiaryScore(const float score) { tertiaryScore = score; }
|
|
||||||
float GetTertiaryScore() const { return tertiaryScore; }
|
|
||||||
private:
|
|
||||||
float primaryScore;
|
|
||||||
float secondaryScore;
|
|
||||||
float tertiaryScore;
|
|
||||||
};
|
|
||||||
|
|
||||||
using GameID = uint32_t;
|
using GameID = uint32_t;
|
||||||
|
|
||||||
class Leaderboard {
|
class Leaderboard {
|
||||||
@ -96,20 +60,16 @@ public:
|
|||||||
* @param resultStart The index to start the leaderboard at. Zero indexed.
|
* @param resultStart The index to start the leaderboard at. Zero indexed.
|
||||||
* @param resultEnd The index to end the leaderboard at. Zero indexed.
|
* @param resultEnd The index to end the leaderboard at. Zero indexed.
|
||||||
*/
|
*/
|
||||||
void SetupLeaderboard(bool weekly, uint32_t resultStart = 0, uint32_t resultEnd = 10);
|
void SetupLeaderboard(bool weekly);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the leaderboard to the client specified by targetID.
|
* Sends the leaderboard to the client specified by targetID.
|
||||||
*/
|
*/
|
||||||
void Send(const LWOOBJID targetID) const;
|
void Send(const LWOOBJID targetID) const;
|
||||||
|
|
||||||
// Helper function to get the columns, ordering and insert format for a leaderboard
|
|
||||||
static const std::string_view GetOrdering(Type leaderboardType);
|
|
||||||
private:
|
|
||||||
// Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
|
|
||||||
// to send it to a client.
|
|
||||||
void QueryToLdf(std::unique_ptr<sql::ResultSet>& rows);
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
using LeaderboardEntry = std::vector<LDFBaseData*>;
|
using LeaderboardEntry = std::vector<LDFBaseData*>;
|
||||||
using LeaderboardEntries = std::vector<LeaderboardEntry>;
|
using LeaderboardEntries = std::vector<LeaderboardEntry>;
|
||||||
|
|
||||||
@ -119,10 +79,18 @@ private:
|
|||||||
InfoType infoType;
|
InfoType infoType;
|
||||||
Leaderboard::Type leaderboardType;
|
Leaderboard::Type leaderboardType;
|
||||||
bool weekly;
|
bool weekly;
|
||||||
|
public:
|
||||||
|
LeaderboardEntry& PushBackEntry() {
|
||||||
|
return entries.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
Type GetLeaderboardType() const {
|
||||||
|
return leaderboardType;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace LeaderboardManager {
|
namespace LeaderboardManager {
|
||||||
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart = 0, const uint32_t resultEnd = 10);
|
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
|
||||||
|
|
||||||
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);
|
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "DestroyableComponent.h"
|
#include "DestroyableComponent.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
|
|||||||
m_Disabled = false;
|
m_Disabled = false;
|
||||||
m_SkillEntries = {};
|
m_SkillEntries = {};
|
||||||
m_SoftTimer = 5.0f;
|
m_SoftTimer = 5.0f;
|
||||||
|
m_ForcedTetherTime = 0.0f;
|
||||||
|
|
||||||
//Grab the aggro information from BaseCombatAI:
|
//Grab the aggro information from BaseCombatAI:
|
||||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||||
@ -170,6 +172,17 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
|||||||
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
|
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
|
||||||
m_TetherEffectActive = false;
|
m_TetherEffectActive = false;
|
||||||
}
|
}
|
||||||
|
m_ForcedTetherTime -= deltaTime;
|
||||||
|
if (m_ForcedTetherTime >= 0) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto entry = m_RemovedThreatList.begin(); entry != m_RemovedThreatList.end();) {
|
||||||
|
entry->second -= deltaTime;
|
||||||
|
if (entry->second <= 0.0f) {
|
||||||
|
entry = m_RemovedThreatList.erase(entry);
|
||||||
|
} else {
|
||||||
|
++entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_SoftTimer <= 0.0f) {
|
if (m_SoftTimer <= 0.0f) {
|
||||||
@ -287,40 +300,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_TetherEffectActive && m_OutOfCombat && (m_OutOfCombatTime -= deltaTime) <= 0) {
|
if (!m_TetherEffectActive && m_OutOfCombat && (m_OutOfCombatTime -= deltaTime) <= 0) {
|
||||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
TetherLogic();
|
||||||
|
|
||||||
if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) {
|
|
||||||
auto serilizationRequired = false;
|
|
||||||
|
|
||||||
if (destroyableComponent->GetHealth() != destroyableComponent->GetMaxHealth()) {
|
|
||||||
destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth());
|
|
||||||
|
|
||||||
serilizationRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destroyableComponent->GetArmor() != destroyableComponent->GetMaxArmor()) {
|
|
||||||
destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor());
|
|
||||||
|
|
||||||
serilizationRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serilizationRequired) {
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 6270, u"tether", "tether");
|
|
||||||
|
|
||||||
m_TetherEffectActive = true;
|
|
||||||
|
|
||||||
m_TetherTime = 3.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Speed towards start position
|
|
||||||
if (m_MovementAI != nullptr) {
|
|
||||||
m_MovementAI->SetHaltDistance(0);
|
|
||||||
m_MovementAI->SetMaxSpeed(m_PursuitSpeed);
|
|
||||||
m_MovementAI->SetDestination(m_StartPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_OutOfCombat = false;
|
m_OutOfCombat = false;
|
||||||
m_OutOfCombatTime = 0.0f;
|
m_OutOfCombatTime = 0.0f;
|
||||||
@ -499,7 +479,7 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
|
|||||||
|
|
||||||
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());
|
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());
|
||||||
|
|
||||||
if (distance > m_AggroRadius * m_AggroRadius) continue;
|
if (distance > m_AggroRadius * m_AggroRadius || m_RemovedThreatList.contains(id)) continue;
|
||||||
|
|
||||||
targets.push_back(id);
|
targets.push_back(id);
|
||||||
}
|
}
|
||||||
@ -626,6 +606,7 @@ const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
|
|||||||
|
|
||||||
void BaseCombatAIComponent::ClearThreat() {
|
void BaseCombatAIComponent::ClearThreat() {
|
||||||
m_ThreatEntries.clear();
|
m_ThreatEntries.clear();
|
||||||
|
m_Target = LWOOBJID_EMPTY;
|
||||||
|
|
||||||
m_DirtyThreat = true;
|
m_DirtyThreat = true;
|
||||||
}
|
}
|
||||||
@ -806,3 +787,55 @@ void BaseCombatAIComponent::Wake() {
|
|||||||
m_dpEntity->SetSleeping(false);
|
m_dpEntity->SetSleeping(false);
|
||||||
m_dpEntityEnemy->SetSleeping(false);
|
m_dpEntityEnemy->SetSleeping(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseCombatAIComponent::TetherLogic() {
|
||||||
|
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||||
|
|
||||||
|
if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) {
|
||||||
|
auto serilizationRequired = false;
|
||||||
|
|
||||||
|
if (destroyableComponent->GetHealth() != destroyableComponent->GetMaxHealth()) {
|
||||||
|
destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth());
|
||||||
|
|
||||||
|
serilizationRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destroyableComponent->GetArmor() != destroyableComponent->GetMaxArmor()) {
|
||||||
|
destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor());
|
||||||
|
|
||||||
|
serilizationRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serilizationRequired) {
|
||||||
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 6270, u"tether", "tether");
|
||||||
|
|
||||||
|
m_TetherEffectActive = true;
|
||||||
|
|
||||||
|
m_TetherTime = 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed towards start position
|
||||||
|
if (m_MovementAI != nullptr) {
|
||||||
|
m_MovementAI->SetHaltDistance(0);
|
||||||
|
m_MovementAI->SetMaxSpeed(m_PursuitSpeed);
|
||||||
|
m_MovementAI->SetDestination(m_StartPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseCombatAIComponent::ForceTether() {
|
||||||
|
SetTarget(LWOOBJID_EMPTY);
|
||||||
|
m_ThreatEntries.clear();
|
||||||
|
TetherLogic();
|
||||||
|
m_ForcedTetherTime = m_TetherTime;
|
||||||
|
|
||||||
|
SetAiState(AiState::aggro);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float value) {
|
||||||
|
m_RemovedThreatList[threat] = value;
|
||||||
|
SetThreat(threat, 0.0f);
|
||||||
|
m_Target = LWOOBJID_EMPTY;
|
||||||
|
}
|
||||||
|
@ -224,6 +224,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
void Wake();
|
void Wake();
|
||||||
|
|
||||||
|
// Force this entity to tether and ignore all other actions
|
||||||
|
void ForceTether();
|
||||||
|
|
||||||
|
// heals the entity to full health and armor
|
||||||
|
// and tethers them to their spawn point
|
||||||
|
void TetherLogic();
|
||||||
|
|
||||||
|
// Ignore a threat for a certain amount of time
|
||||||
|
void IgnoreThreat(const LWOOBJID target, const float time);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Returns the current target or the target that currently is the largest threat to this entity
|
* Returns the current target or the target that currently is the largest threat to this entity
|
||||||
@ -382,6 +392,12 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool m_DirtyStateOrTarget = false;
|
bool m_DirtyStateOrTarget = false;
|
||||||
|
|
||||||
|
// The amount of time the entity will be forced to tether for
|
||||||
|
float m_ForcedTetherTime = 0.0f;
|
||||||
|
|
||||||
|
// The amount of time a removed threat will be ignored for.
|
||||||
|
std::map<LWOOBJID, float> m_RemovedThreatList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current entity is a mech enemy, needed as mechs tether radius works differently
|
* Whether the current entity is a mech enemy, needed as mechs tether radius works differently
|
||||||
* @return whether this entity is a mech
|
* @return whether this entity is a mech
|
||||||
|
@ -7,7 +7,6 @@ set(DGAME_DCOMPONENTS_SOURCES
|
|||||||
"BuildBorderComponent.cpp"
|
"BuildBorderComponent.cpp"
|
||||||
"CharacterComponent.cpp"
|
"CharacterComponent.cpp"
|
||||||
"CollectibleComponent.cpp"
|
"CollectibleComponent.cpp"
|
||||||
"Component.cpp"
|
|
||||||
"ControllablePhysicsComponent.cpp"
|
"ControllablePhysicsComponent.cpp"
|
||||||
"DestroyableComponent.cpp"
|
"DestroyableComponent.cpp"
|
||||||
"DonationVendorComponent.cpp"
|
"DonationVendorComponent.cpp"
|
||||||
@ -65,7 +64,6 @@ target_include_directories(dComponents PUBLIC "."
|
|||||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
|
||||||
# dPhysics (via dpWorld.h)
|
# dPhysics (via dpWorld.h)
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
#include "Component.h"
|
|
||||||
|
|
||||||
|
|
||||||
Component::Component(Entity* parent) {
|
|
||||||
m_Parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
Component::~Component() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity* Component::GetParent() const {
|
|
||||||
return m_Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::Update(float deltaTime) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::OnUse(Entity* originator) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::UpdateXml(tinyxml2::XMLDocument& doc) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::LoadFromXml(const tinyxml2::XMLDocument& doc) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "tinyxml2.h"
|
namespace tinyxml2 {
|
||||||
|
class XMLDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace RakNet {
|
||||||
|
class BitStream;
|
||||||
|
}
|
||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
|
|
||||||
@ -9,40 +15,40 @@ class Entity;
|
|||||||
*/
|
*/
|
||||||
class Component {
|
class Component {
|
||||||
public:
|
public:
|
||||||
Component(Entity* parent);
|
Component(Entity* parent) : m_Parent{ parent } {}
|
||||||
virtual ~Component();
|
virtual ~Component() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the owner of this component
|
* Gets the owner of this component
|
||||||
* @return the owner of this component
|
* @return the owner of this component
|
||||||
*/
|
*/
|
||||||
Entity* GetParent() const;
|
Entity* GetParent() const { return m_Parent; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the component in the game loop
|
* Updates the component in the game loop
|
||||||
* @param deltaTime time passed since last update
|
* @param deltaTime time passed since last update
|
||||||
*/
|
*/
|
||||||
virtual void Update(float deltaTime);
|
virtual void Update(float deltaTime) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event called when this component is being used, e.g. when some entity interacted with it
|
* Event called when this component is being used, e.g. when some entity interacted with it
|
||||||
* @param originator
|
* @param originator
|
||||||
*/
|
*/
|
||||||
virtual void OnUse(Entity* originator);
|
virtual void OnUse(Entity* originator) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save data from this componennt to character XML
|
* Save data from this componennt to character XML
|
||||||
* @param doc the document to write data to
|
* @param doc the document to write data to
|
||||||
*/
|
*/
|
||||||
virtual void UpdateXml(tinyxml2::XMLDocument& doc);
|
virtual void UpdateXml(tinyxml2::XMLDocument& doc) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load base data for this component from character XML
|
* Load base data for this component from character XML
|
||||||
* @param doc the document to read data from
|
* @param doc the document to read data from
|
||||||
*/
|
*/
|
||||||
virtual void LoadFromXml(const tinyxml2::XMLDocument& doc);
|
virtual void LoadFromXml(const tinyxml2::XMLDocument& doc) {}
|
||||||
|
|
||||||
virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction);
|
virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
#include "LevelProgressionComponent.h"
|
#include "LevelProgressionComponent.h"
|
||||||
#include "eStateChangeType.h"
|
#include "eStateChangeType.h"
|
||||||
|
|
||||||
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : PhysicsComponent(entity) {
|
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) {
|
||||||
m_Velocity = {};
|
m_Velocity = {};
|
||||||
m_AngularVelocity = {};
|
m_AngularVelocity = {};
|
||||||
m_InJetpackMode = false;
|
m_InJetpackMode = false;
|
||||||
|
@ -23,7 +23,7 @@ class ControllablePhysicsComponent : public PhysicsComponent {
|
|||||||
public:
|
public:
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
|
||||||
|
|
||||||
ControllablePhysicsComponent(Entity* entity);
|
ControllablePhysicsComponent(Entity* entity, int32_t componentId);
|
||||||
~ControllablePhysicsComponent() override;
|
~ControllablePhysicsComponent() override;
|
||||||
|
|
||||||
void Update(float deltaTime) override;
|
void Update(float deltaTime) override;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include "HavokVehiclePhysicsComponent.h"
|
#include "HavokVehiclePhysicsComponent.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
|
|
||||||
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
|
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
|
||||||
m_Velocity = NiPoint3Constant::ZERO;
|
m_Velocity = NiPoint3Constant::ZERO;
|
||||||
m_AngularVelocity = NiPoint3Constant::ZERO;
|
m_AngularVelocity = NiPoint3Constant::ZERO;
|
||||||
m_IsOnGround = true;
|
m_IsOnGround = true;
|
||||||
|
@ -13,7 +13,7 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent {
|
|||||||
public:
|
public:
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS;
|
||||||
|
|
||||||
HavokVehiclePhysicsComponent(Entity* parentEntity);
|
HavokVehiclePhysicsComponent(Entity* parentEntity, int32_t componentId);
|
||||||
|
|
||||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "eStateChangeType.h"
|
#include "eStateChangeType.h"
|
||||||
#include "eUseItemResponse.h"
|
#include "eUseItemResponse.h"
|
||||||
#include "Mail.h"
|
#include "Mail.h"
|
||||||
|
#include "ProximityMonitorComponent.h"
|
||||||
|
|
||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
#include "CDInventoryComponentTable.h"
|
#include "CDInventoryComponentTable.h"
|
||||||
@ -829,6 +830,30 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (item->GetLot() == 8092) {
|
||||||
|
// Trying to equip a car
|
||||||
|
const auto proximityObjects = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR);
|
||||||
|
|
||||||
|
// look for car instancers and check if we are in its setup range
|
||||||
|
for (auto* const entity : proximityObjects) {
|
||||||
|
if (!entity) continue;
|
||||||
|
|
||||||
|
auto* proximityMonitorComponent = entity->GetComponent<ProximityMonitorComponent>();
|
||||||
|
if (!proximityMonitorComponent) continue;
|
||||||
|
|
||||||
|
if (proximityMonitorComponent->IsInProximity("Interaction_Distance", m_Parent->GetObjectID())) {
|
||||||
|
// in the range of a car instancer
|
||||||
|
entity->OnUse(m_Parent);
|
||||||
|
GameMessages::UseItemOnClient itemMsg;
|
||||||
|
itemMsg.target = entity->GetObjectID();
|
||||||
|
itemMsg.itemLOT = item->GetLot();
|
||||||
|
itemMsg.itemToUse = item->GetId();
|
||||||
|
itemMsg.playerId = m_Parent->GetObjectID();
|
||||||
|
itemMsg.Send(m_Parent->GetSystemAddress());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1141,6 +1166,25 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
|||||||
SetSkill(slot, skill);
|
SetSkill(slot, skill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InventoryComponent::FixInvisibleItems() {
|
||||||
|
const auto numberItemsLoadedPerFrame = 12.0f;
|
||||||
|
const auto callbackTime = 0.125f;
|
||||||
|
const auto arbitaryInventorySize = 300.0f; // max in live + dlu is less than 300, seems like a good number.
|
||||||
|
auto* const items = GetInventory(eInventoryType::ITEMS);
|
||||||
|
if (!items) return;
|
||||||
|
|
||||||
|
// Add an extra update to make sure the client can see all the items.
|
||||||
|
const auto something = static_cast<int32_t>(std::ceil(items->GetItems().size() / arbitaryInventorySize)) + 1;
|
||||||
|
LOG_DEBUG("Fixing invisible items with %i updates", something);
|
||||||
|
|
||||||
|
for (int32_t i = 1; i < something + 1; i++) {
|
||||||
|
// client loads 12 items every 1/8 seconds, we're adding a small hack to fix invisible inventory items due to closing the news screen too fast.
|
||||||
|
m_Parent->AddCallbackTimer((arbitaryInventorySize / numberItemsLoadedPerFrame) * callbackTime * i, [this]() {
|
||||||
|
GameMessages::SendUpdateInventoryUi(m_Parent->GetObjectID(), m_Parent->GetSystemAddress());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||||
const auto info = Inventory::FindItemComponent(lot);
|
const auto info = Inventory::FindItemComponent(lot);
|
||||||
|
|
||||||
|
@ -404,6 +404,8 @@ public:
|
|||||||
void UpdateGroup(const GroupUpdate& groupUpdate);
|
void UpdateGroup(const GroupUpdate& groupUpdate);
|
||||||
void RemoveGroup(const std::string& groupId);
|
void RemoveGroup(const std::string& groupId);
|
||||||
|
|
||||||
|
void FixInvisibleItems();
|
||||||
|
|
||||||
~InventoryComponent() override;
|
~InventoryComponent() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
#include "dpShapeBox.h"
|
#include "dpShapeBox.h"
|
||||||
#include "dpShapeSphere.h"
|
#include "dpShapeSphere.h"
|
||||||
|
|
||||||
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
|
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
|
||||||
m_Position = m_Parent->GetDefaultPosition();
|
m_Position = m_Parent->GetDefaultPosition();
|
||||||
m_Rotation = m_Parent->GetDefaultRotation();
|
m_Rotation = m_Parent->GetDefaultRotation();
|
||||||
m_Scale = m_Parent->GetDefaultScale();
|
m_Scale = m_Parent->GetDefaultScale();
|
||||||
|
@ -30,7 +30,7 @@ class PhantomPhysicsComponent final : public PhysicsComponent {
|
|||||||
public:
|
public:
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS;
|
||||||
|
|
||||||
PhantomPhysicsComponent(Entity* parent);
|
PhantomPhysicsComponent(Entity* parent, int32_t componentId);
|
||||||
~PhantomPhysicsComponent() override;
|
~PhantomPhysicsComponent() override;
|
||||||
void Update(float deltaTime) override;
|
void Update(float deltaTime) override;
|
||||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||||
|
@ -14,10 +14,21 @@
|
|||||||
|
|
||||||
#include "EntityInfo.h"
|
#include "EntityInfo.h"
|
||||||
|
|
||||||
PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) {
|
PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) {
|
||||||
m_Position = NiPoint3Constant::ZERO;
|
m_Position = NiPoint3Constant::ZERO;
|
||||||
m_Rotation = NiQuaternionConstant::IDENTITY;
|
m_Rotation = NiQuaternionConstant::IDENTITY;
|
||||||
m_DirtyPosition = false;
|
m_DirtyPosition = false;
|
||||||
|
|
||||||
|
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable<CDPhysicsComponentTable>();
|
||||||
|
|
||||||
|
if (physicsComponentTable) {
|
||||||
|
auto* info = physicsComponentTable->GetByID(componentId);
|
||||||
|
if (info) {
|
||||||
|
m_CollisionGroup = info->collisionGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Parent->HasVar(u"CollisionGroupID")) m_CollisionGroup = m_Parent->GetVar<int32_t>(u"CollisionGroupID");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
||||||
|
@ -15,7 +15,7 @@ class dpEntity;
|
|||||||
|
|
||||||
class PhysicsComponent : public Component {
|
class PhysicsComponent : public Component {
|
||||||
public:
|
public:
|
||||||
PhysicsComponent(Entity* parent);
|
PhysicsComponent(Entity* parent, int32_t componentId);
|
||||||
virtual ~PhysicsComponent() = default;
|
virtual ~PhysicsComponent() = default;
|
||||||
|
|
||||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||||
@ -25,6 +25,9 @@ public:
|
|||||||
|
|
||||||
const NiQuaternion& GetRotation() const { return m_Rotation; }
|
const NiQuaternion& GetRotation() const { return m_Rotation; }
|
||||||
virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; }
|
virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; }
|
||||||
|
|
||||||
|
int32_t GetCollisionGroup() const noexcept { return m_CollisionGroup; }
|
||||||
|
void SetCollisionGroup(int32_t group) noexcept { m_CollisionGroup = group; }
|
||||||
protected:
|
protected:
|
||||||
dpEntity* CreatePhysicsEntity(eReplicaComponentType type);
|
dpEntity* CreatePhysicsEntity(eReplicaComponentType type);
|
||||||
|
|
||||||
@ -37,6 +40,8 @@ protected:
|
|||||||
NiQuaternion m_Rotation;
|
NiQuaternion m_Rotation;
|
||||||
|
|
||||||
bool m_DirtyPosition;
|
bool m_DirtyPosition;
|
||||||
|
|
||||||
|
int32_t m_CollisionGroup{};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__PHYSICSCOMPONENT__H__
|
#endif //!__PHYSICSCOMPONENT__H__
|
||||||
|
@ -38,7 +38,7 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std::
|
|||||||
m_ProximitiesData.insert(std::make_pair(name, entity));
|
m_ProximitiesData.insert(std::make_pair(name, entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) {
|
const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) const {
|
||||||
const auto iter = m_ProximitiesData.find(name);
|
const auto iter = m_ProximitiesData.find(name);
|
||||||
|
|
||||||
if (iter == m_ProximitiesData.cend()) {
|
if (iter == m_ProximitiesData.cend()) {
|
||||||
|
@ -46,7 +46,7 @@ public:
|
|||||||
* @param name the proximity name to retrieve physics objects for
|
* @param name the proximity name to retrieve physics objects for
|
||||||
* @return a set of physics entity object IDs for this name
|
* @return a set of physics entity object IDs for this name
|
||||||
*/
|
*/
|
||||||
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name);
|
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the passed object is in proximity of the named proximity sensor
|
* Checks if the passed object is in proximity of the named proximity sensor
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
#include "dpShapeSphere.h"
|
#include "dpShapeSphere.h"
|
||||||
#include"EntityInfo.h"
|
#include"EntityInfo.h"
|
||||||
|
|
||||||
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
|
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
|
||||||
m_Position = m_Parent->GetDefaultPosition();
|
m_Position = m_Parent->GetDefaultPosition();
|
||||||
m_Rotation = m_Parent->GetDefaultRotation();
|
m_Rotation = m_Parent->GetDefaultRotation();
|
||||||
m_Scale = m_Parent->GetDefaultScale();
|
m_Scale = m_Parent->GetDefaultScale();
|
||||||
|
@ -21,7 +21,7 @@ class RigidbodyPhantomPhysicsComponent : public PhysicsComponent {
|
|||||||
public:
|
public:
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS;
|
||||||
|
|
||||||
RigidbodyPhantomPhysicsComponent(Entity* parent);
|
RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId);
|
||||||
|
|
||||||
void Update(const float deltaTime) override;
|
void Update(const float deltaTime) override;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
|
|
||||||
SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, uint32_t componentID) : PhysicsComponent(parent) {
|
SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, int32_t componentID) : PhysicsComponent(parent, componentID) {
|
||||||
m_Position = m_Parent->GetDefaultPosition();
|
m_Position = m_Parent->GetDefaultPosition();
|
||||||
m_Rotation = m_Parent->GetDefaultRotation();
|
m_Rotation = m_Parent->GetDefaultRotation();
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class SimplePhysicsComponent : public PhysicsComponent {
|
|||||||
public:
|
public:
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS;
|
||||||
|
|
||||||
SimplePhysicsComponent(Entity* parent, uint32_t componentID);
|
SimplePhysicsComponent(Entity* parent, int32_t componentID);
|
||||||
~SimplePhysicsComponent() override;
|
~SimplePhysicsComponent() override;
|
||||||
|
|
||||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||||
|
@ -82,7 +82,6 @@ void SwitchComponent::EntityEnter(Entity* entity) {
|
|||||||
RenderComponent::PlayAnimation(m_Parent, u"engaged");
|
RenderComponent::PlayAnimation(m_Parent, u"engaged");
|
||||||
m_PetBouncer->SetPetBouncerEnabled(true);
|
m_PetBouncer->SetPetBouncerEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
GameMessages::SendKnockback(entity->GetObjectID(), m_Parent->GetObjectID(), m_Parent->GetObjectID(), 0.0f, NiPoint3(0.0f, 17.0f, 0.0f));
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,18 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
|
||||||
|
case MessageType::Game::SELECT_SKILL: {
|
||||||
|
auto var = entity->GetVar<bool>(u"dlu_first_time_load");
|
||||||
|
if (var) {
|
||||||
|
entity->SetVar<bool>(u"dlu_first_time_load", false);
|
||||||
|
InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
|
||||||
|
if (inventoryComponent) inventoryComponent->FixInvisibleItems();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case MessageType::Game::PLAYER_LOADED: {
|
case MessageType::Game::PLAYER_LOADED: {
|
||||||
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
|
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
|
||||||
entity->SetPlayerReadyForUpdates();
|
entity->SetPlayerReadyForUpdates();
|
||||||
|
@ -1691,7 +1691,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream
|
|||||||
|
|
||||||
bool weekly = inStream.ReadBit();
|
bool weekly = inStream.ReadBit();
|
||||||
|
|
||||||
LeaderboardManager::SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd);
|
LeaderboardManager::SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream& inStream, Entity* entity) {
|
void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream& inStream, Entity* entity) {
|
||||||
@ -5066,9 +5066,7 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream& inStream, E
|
|||||||
|
|
||||||
item->Disassemble(TEMP_MODELS);
|
item->Disassemble(TEMP_MODELS);
|
||||||
|
|
||||||
std::unique_ptr<sql::PreparedStatement> stmt(Database::Get()->CreatePreppedStmt("DELETE FROM ugc_modular_build where ugc_id = ?"));
|
Database::Get()->DeleteUgcBuild(item->GetSubKey());
|
||||||
stmt->setUInt64(1, item->GetSubKey());
|
|
||||||
stmt->execute();
|
|
||||||
|
|
||||||
item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD);
|
item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD);
|
||||||
}
|
}
|
||||||
@ -5082,6 +5080,12 @@ void GameMessages::HandleSetFlag(RakNet::BitStream& inStream, Entity* entity) {
|
|||||||
|
|
||||||
auto character = entity->GetCharacter();
|
auto character = entity->GetCharacter();
|
||||||
if (character) character->SetPlayerFlag(iFlagID, bFlag);
|
if (character) character->SetPlayerFlag(iFlagID, bFlag);
|
||||||
|
|
||||||
|
// This is always set the first time a player loads into a world from character select
|
||||||
|
// and is used to know when to refresh the players inventory items so they show up.
|
||||||
|
if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
|
||||||
|
entity->SetVar<bool>(u"dlu_first_time_load", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
|
void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
|
||||||
@ -5394,6 +5398,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En
|
|||||||
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
||||||
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
|
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
|
||||||
item->DisassembleModel(iStackCount);
|
item->DisassembleModel(iStackCount);
|
||||||
|
} else if (itemType == eItemType::VEHICLE) {
|
||||||
|
Database::Get()->DeleteUgcBuild(item->GetSubKey());
|
||||||
}
|
}
|
||||||
auto lot = item->GetLot();
|
auto lot = item->GetLot();
|
||||||
item->SetCount(item->GetCount() - iStackCount, true);
|
item->SetCount(item->GetCount() - iStackCount, true);
|
||||||
@ -5569,12 +5575,8 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
|
|||||||
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
|
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<sql::PreparedStatement> stmt(Database::Get()->CreatePreppedStmt("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)"));
|
|
||||||
stmt->setUInt64(1, newIdBig);
|
|
||||||
stmt->setString(2, GeneralUtils::UTF16ToWTF8(modules).c_str());
|
|
||||||
auto* pCharacter = character->GetCharacter();
|
auto* pCharacter = character->GetCharacter();
|
||||||
pCharacter ? stmt->setUInt(3, pCharacter->GetID()) : stmt->setNull(3, sql::DataType::BIGINT);
|
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
|
||||||
stmt->execute();
|
|
||||||
|
|
||||||
auto* missionComponent = character->GetComponent<MissionComponent>();
|
auto* missionComponent = character->GetComponent<MissionComponent>();
|
||||||
|
|
||||||
@ -6328,3 +6330,69 @@ void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling
|
|||||||
auto sysAddr = entity->GetSystemAddress();
|
auto sysAddr = entity->GetSystemAddress();
|
||||||
SEND_PACKET;
|
SEND_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GameMessages::SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr) {
|
||||||
|
CBITSTREAM;
|
||||||
|
CMSGHEADER;
|
||||||
|
|
||||||
|
bitStream.Write(objectId);
|
||||||
|
bitStream.Write(MessageType::Game::UPDATE_INVENTORY_UI);
|
||||||
|
|
||||||
|
SEND_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GameMessages {
|
||||||
|
void GameMsg::Send(const SystemAddress& sysAddr) const {
|
||||||
|
CBITSTREAM;
|
||||||
|
CMSGHEADER;
|
||||||
|
|
||||||
|
bitStream.Write(target); // Who this message will be sent to on the (a) client
|
||||||
|
bitStream.Write(msgId); // the ID of this message
|
||||||
|
|
||||||
|
Serialize(bitStream); // write the message data
|
||||||
|
|
||||||
|
// Send to everyone if someone sent unassigned system address, or to one specific client.
|
||||||
|
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
|
||||||
|
SEND_PACKET_BROADCAST;
|
||||||
|
} else {
|
||||||
|
SEND_PACKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayTooltip::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(doOrDie);
|
||||||
|
bitStream.Write(noRepeat);
|
||||||
|
bitStream.Write(noRevive);
|
||||||
|
bitStream.Write(isPropertyTooltip);
|
||||||
|
bitStream.Write(show);
|
||||||
|
bitStream.Write(translate);
|
||||||
|
bitStream.Write(time);
|
||||||
|
bitStream.Write<int32_t>(id.size());
|
||||||
|
bitStream.Write(id);
|
||||||
|
|
||||||
|
std::string toWrite;
|
||||||
|
for (const auto* item : localizeParams) {
|
||||||
|
toWrite += item->GetString() + "\n";
|
||||||
|
}
|
||||||
|
if (!toWrite.empty()) toWrite.pop_back();
|
||||||
|
bitStream.Write<int32_t>(toWrite.size());
|
||||||
|
bitStream.Write(GeneralUtils::ASCIIToUTF16(toWrite));
|
||||||
|
if (!toWrite.empty()) bitStream.Write<uint16_t>(0x00); // Null Terminator
|
||||||
|
|
||||||
|
bitStream.Write<int32_t>(imageName.size());
|
||||||
|
bitStream.Write(imageName);
|
||||||
|
bitStream.Write<int32_t>(text.size());
|
||||||
|
bitStream.Write(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UseItemOnClient::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(itemLOT);
|
||||||
|
bitStream.Write(itemToUse);
|
||||||
|
bitStream.Write(itemType);
|
||||||
|
bitStream.Write(playerId);
|
||||||
|
bitStream.Write(targetPosition.x);
|
||||||
|
bitStream.Write(targetPosition.y);
|
||||||
|
bitStream.Write(targetPosition.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "eCyclingMode.h"
|
#include "eCyclingMode.h"
|
||||||
#include "eLootSourceType.h"
|
#include "eLootSourceType.h"
|
||||||
#include "Brick.h"
|
#include "Brick.h"
|
||||||
|
#include "MessageType/Game.h"
|
||||||
|
|
||||||
class AMFBaseValue;
|
class AMFBaseValue;
|
||||||
class Entity;
|
class Entity;
|
||||||
@ -20,6 +21,7 @@ class User;
|
|||||||
class Leaderboard;
|
class Leaderboard;
|
||||||
class PropertySelectQueryProperty;
|
class PropertySelectQueryProperty;
|
||||||
class TradeItem;
|
class TradeItem;
|
||||||
|
class LDFBaseData;
|
||||||
|
|
||||||
enum class eAnimationFlags : uint32_t;
|
enum class eAnimationFlags : uint32_t;
|
||||||
|
|
||||||
@ -47,6 +49,15 @@ enum class eCameraTargetCyclingMode : int32_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace GameMessages {
|
namespace GameMessages {
|
||||||
|
struct GameMsg {
|
||||||
|
GameMsg(MessageType::Game gmId) : msgId{ gmId } {}
|
||||||
|
virtual ~GameMsg() = default;
|
||||||
|
void Send(const SystemAddress& sysAddr) const;
|
||||||
|
virtual void Serialize(RakNet::BitStream& bitStream) const {}
|
||||||
|
MessageType::Game msgId;
|
||||||
|
LWOOBJID target{ LWOOBJID_EMPTY };
|
||||||
|
};
|
||||||
|
|
||||||
class PropertyDataMessage;
|
class PropertyDataMessage;
|
||||||
void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender);
|
void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender);
|
||||||
void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false);
|
void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false);
|
||||||
@ -677,6 +688,35 @@ namespace GameMessages {
|
|||||||
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||||
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||||
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
|
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
|
||||||
|
|
||||||
|
// This is a client gm however its default values are exactly what we need to get around the invisible inventory item issues.
|
||||||
|
void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr);
|
||||||
|
|
||||||
|
struct DisplayTooltip : public GameMsg {
|
||||||
|
DisplayTooltip() : GameMsg(MessageType::Game::DISPLAY_TOOLTIP) {}
|
||||||
|
bool doOrDie{};
|
||||||
|
bool noRepeat{};
|
||||||
|
bool noRevive{};
|
||||||
|
bool isPropertyTooltip{};
|
||||||
|
bool show{};
|
||||||
|
bool translate{};
|
||||||
|
int32_t time{};
|
||||||
|
std::u16string id{};
|
||||||
|
std::vector<LDFBaseData*> localizeParams{};
|
||||||
|
std::u16string imageName{};
|
||||||
|
std::u16string text{};
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UseItemOnClient : public GameMsg {
|
||||||
|
UseItemOnClient() : GameMsg(MessageType::Game::USE_ITEM_ON_CLIENT) {}
|
||||||
|
LWOOBJID playerId{};
|
||||||
|
LWOOBJID itemToUse{};
|
||||||
|
uint32_t itemType{};
|
||||||
|
LOT itemLOT{};
|
||||||
|
NiPoint3 targetPosition{};
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GAMEMESSAGES_H
|
#endif // GAMEMESSAGES_H
|
||||||
|
@ -8,7 +8,6 @@ class AMFArrayValue;
|
|||||||
/**
|
/**
|
||||||
* @brief Sent when a player moves a Behavior A at position B to their inventory.
|
* @brief Sent when a player moves a Behavior A at position B to their inventory.
|
||||||
*/
|
*/
|
||||||
#pragma warning("This Control Behavior Message does not have a test yet. Non-developers can ignore this warning.")
|
|
||||||
class MoveToInventoryMessage : public BehaviorMessageBase {
|
class MoveToInventoryMessage : public BehaviorMessageBase {
|
||||||
public:
|
public:
|
||||||
MoveToInventoryMessage(const AMFArrayValue& arguments);
|
MoveToInventoryMessage(const AMFArrayValue& arguments);
|
||||||
|
@ -137,7 +137,7 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat
|
|||||||
|
|
||||||
return inventoryComponent->GetLotCount(value) >= count;
|
return inventoryComponent->GetLotCount(value) >= count;
|
||||||
case PreconditionType::DoesNotHaveItem:
|
case PreconditionType::DoesNotHaveItem:
|
||||||
return inventoryComponent->IsEquipped(value) < count;
|
return inventoryComponent->IsEquipped(value) && count > 0;
|
||||||
case PreconditionType::HasAchievement:
|
case PreconditionType::HasAchievement:
|
||||||
if (missionComponent == nullptr) return false;
|
if (missionComponent == nullptr) return false;
|
||||||
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
|
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
|
||||||
|
@ -287,8 +287,8 @@ void SlashCommandHandler::Startup() {
|
|||||||
RegisterCommand(SpawnPhysicsVertsCommand);
|
RegisterCommand(SpawnPhysicsVertsCommand);
|
||||||
|
|
||||||
Command TeleportCommand{
|
Command TeleportCommand{
|
||||||
.help = "Teleports you",
|
.help = "Teleports you to a position or a player to another player.",
|
||||||
.info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z)",
|
.info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Any of the coordinates can use the syntax of an exact position (10.0), or a relative position (~+10.0). A ~ means use the current value of that axis as the base value. Addition or subtraction is supported (~+10) (~-10). If source player and target player are players that exist in the world, then the source player will be teleported to target player.",
|
||||||
.aliases = { "teleport", "tele", "tp" },
|
.aliases = { "teleport", "tele", "tp" },
|
||||||
.handle = DEVGMCommands::Teleport,
|
.handle = DEVGMCommands::Teleport,
|
||||||
.requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER
|
.requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER
|
||||||
@ -1056,6 +1056,15 @@ void SlashCommandHandler::Startup() {
|
|||||||
};
|
};
|
||||||
RegisterCommand(InstanceInfoCommand);
|
RegisterCommand(InstanceInfoCommand);
|
||||||
|
|
||||||
|
Command ServerUptimeCommand{
|
||||||
|
.help = "Display the time the current world server has been active",
|
||||||
|
.info = "Display the time the current world server has been active",
|
||||||
|
.aliases = { "uptime" },
|
||||||
|
.handle = GMZeroCommands::ServerUptime,
|
||||||
|
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||||
|
};
|
||||||
|
RegisterCommand(ServerUptimeCommand);
|
||||||
|
|
||||||
//Commands that are handled by the client
|
//Commands that are handled by the client
|
||||||
|
|
||||||
Command faqCommand{
|
Command faqCommand{
|
||||||
|
@ -555,25 +555,45 @@ namespace DEVGMCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<float> ParseRelativeAxis(const float sourcePos, const std::string& toParse) {
|
||||||
|
if (toParse.empty()) return std::nullopt;
|
||||||
|
|
||||||
|
// relative offset from current position
|
||||||
|
if (toParse[0] == '~') {
|
||||||
|
if (toParse.size() == 1) return sourcePos;
|
||||||
|
|
||||||
|
if (toParse.size() < 3 || !(toParse[1] != '+' || toParse[1] != '-')) return std::nullopt;
|
||||||
|
|
||||||
|
const auto offset = GeneralUtils::TryParse<float>(toParse.substr(2));
|
||||||
|
if (!offset.has_value()) return std::nullopt;
|
||||||
|
|
||||||
|
bool isNegative = toParse[1] == '-';
|
||||||
|
return isNegative ? sourcePos - offset.value() : sourcePos + offset.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GeneralUtils::TryParse<float>(toParse);
|
||||||
|
}
|
||||||
|
|
||||||
void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||||
|
|
||||||
|
const auto& sourcePos = entity->GetPosition();
|
||||||
NiPoint3 pos{};
|
NiPoint3 pos{};
|
||||||
|
auto* sourceEntity = entity;
|
||||||
if (splitArgs.size() == 3) {
|
if (splitArgs.size() == 3) {
|
||||||
|
const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]);
|
||||||
const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0));
|
|
||||||
if (!x) {
|
if (!x) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1));
|
const auto y = ParseRelativeAxis(sourcePos.y, splitArgs[1]);
|
||||||
if (!y) {
|
if (!y) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid y.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid y.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2));
|
const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[2]);
|
||||||
if (!z) {
|
if (!z) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z.");
|
||||||
return;
|
return;
|
||||||
@ -584,32 +604,39 @@ namespace DEVGMCommands {
|
|||||||
pos.SetZ(z.value());
|
pos.SetZ(z.value());
|
||||||
|
|
||||||
LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z);
|
LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z);
|
||||||
GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr);
|
|
||||||
} else if (splitArgs.size() == 2) {
|
} else if (splitArgs.size() == 2) {
|
||||||
|
const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]);
|
||||||
|
auto* sourcePlayer = PlayerManager::GetPlayer(splitArgs[0]);
|
||||||
|
if (!x && !sourcePlayer) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x or source player not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sourcePlayer) sourceEntity = sourcePlayer;
|
||||||
|
|
||||||
const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0));
|
const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[1]);
|
||||||
if (!x) {
|
const auto* const targetPlayer = PlayerManager::GetPlayer(splitArgs[1]);
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x.");
|
if (!z && !targetPlayer) {
|
||||||
return;
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z or target player not found.");
|
||||||
}
|
|
||||||
|
|
||||||
const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1));
|
|
||||||
if (!z) {
|
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (x && z) {
|
||||||
pos.SetX(x.value());
|
pos.SetX(x.value());
|
||||||
pos.SetY(0.0f);
|
pos.SetY(0.0f);
|
||||||
pos.SetZ(z.value());
|
pos.SetZ(z.value());
|
||||||
|
} else if (sourcePlayer && targetPlayer) {
|
||||||
|
pos = targetPlayer->GetPosition();
|
||||||
|
} else {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"Unable to teleport.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z);
|
LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z);
|
||||||
GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr);
|
|
||||||
} else {
|
} else {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object).");
|
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object).");
|
||||||
}
|
}
|
||||||
|
GameMessages::SendTeleport(sourceEntity->GetObjectID(), pos, sourceEntity->GetRotation(), sourceEntity->GetSystemAddress());
|
||||||
|
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessorComponent = sourceEntity->GetComponent<PossessorComponent>();
|
||||||
if (possessorComponent) {
|
if (possessorComponent) {
|
||||||
auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
||||||
|
|
||||||
|
@ -225,8 +225,13 @@ namespace GMZeroCommands {
|
|||||||
ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID())));
|
ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display the server uptime
|
||||||
|
void ServerUptime(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
|
const auto time = Game::server->GetUptime();
|
||||||
|
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(time).count();
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"Server has been up for " + GeneralUtils::to_u16string(seconds) + u" s");
|
||||||
|
}
|
||||||
|
|
||||||
//For client side commands
|
//For client side commands
|
||||||
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {}
|
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ namespace GMZeroCommands {
|
|||||||
void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
|
void ServerUptime(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,26 +403,39 @@ void SetupNPCTalk(Entity* npc) {
|
|||||||
npc->SetProximityRadius(20.0f, "talk");
|
npc->SetProximityRadius(20.0f, "talk");
|
||||||
}
|
}
|
||||||
|
|
||||||
void NPCTalk(Entity* npc) {
|
void VanityUtilities::OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name) {
|
||||||
auto* proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
|
if (proxName != "talk") return;
|
||||||
|
const auto* const proximityMonitorComponent = entity->GetComponent<ProximityMonitorComponent>();
|
||||||
|
if (!proximityMonitorComponent) return;
|
||||||
|
|
||||||
if (!proximityMonitorComponent->GetProximityObjects("talk").empty()) {
|
if (name == "ENTER" && !entity->HasTimer("talk")) {
|
||||||
|
NPCTalk(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VanityUtilities::OnTimerDone(Entity* npc, const std::string& name) {
|
||||||
|
if (name == "talk") {
|
||||||
|
const auto* const proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
|
||||||
|
if (!proximityMonitorComponent || proximityMonitorComponent->GetProximityObjects("talk").empty()) return;
|
||||||
|
|
||||||
|
NPCTalk(npc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NPCTalk(Entity* npc) {
|
||||||
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
|
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
|
||||||
|
|
||||||
if (chats.empty()) {
|
if (chats.empty()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& selected
|
const auto& selected
|
||||||
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
|
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
|
||||||
|
|
||||||
GameMessages::SendNotifyClientZoneObject(
|
GameMessages::SendNotifyClientZoneObject(
|
||||||
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
|
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
}
|
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(npc);
|
Game::entityManager->SerializeEntity(npc);
|
||||||
|
|
||||||
const float nextTime = GeneralUtils::GenerateRandomNumber<float>(15, 60);
|
const float nextTime = GeneralUtils::GenerateRandomNumber<float>(15, 60);
|
||||||
|
|
||||||
npc->AddCallbackTimer(nextTime, [npc]() { NPCTalk(npc); });
|
npc->AddTimer("talk", nextTime);
|
||||||
}
|
}
|
||||||
|
@ -31,4 +31,8 @@ namespace VanityUtilities {
|
|||||||
std::string ParseMarkdown(
|
std::string ParseMarkdown(
|
||||||
const std::string& file
|
const std::string& file
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name);
|
||||||
|
|
||||||
|
void OnTimerDone(Entity* entity, const std::string& name);
|
||||||
};
|
};
|
||||||
|
@ -103,7 +103,7 @@ int main(int argc, char** argv) {
|
|||||||
//Connect to the MySQL Database
|
//Connect to the MySQL Database
|
||||||
try {
|
try {
|
||||||
Database::Connect();
|
Database::Connect();
|
||||||
} catch (sql::SQLException& ex) {
|
} catch (std::exception& ex) {
|
||||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||||
LOG("Migrations not run");
|
LOG("Migrations not run");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
@ -126,6 +126,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
MigrationRunner::RunMigrations();
|
MigrationRunner::RunMigrations();
|
||||||
const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer";
|
const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer";
|
||||||
|
std::filesystem::create_directories(resServerPath);
|
||||||
const bool cdServerExists = std::filesystem::exists(resServerPath / "CDServer.sqlite");
|
const bool cdServerExists = std::filesystem::exists(resServerPath / "CDServer.sqlite");
|
||||||
const bool oldCDServerExists = std::filesystem::exists(Game::assetManager->GetResPath() / "CDServer.sqlite");
|
const bool oldCDServerExists = std::filesystem::exists(Game::assetManager->GetResPath() / "CDServer.sqlite");
|
||||||
const bool fdbExists = std::filesystem::exists(Game::assetManager->GetResPath() / "cdclient.fdb");
|
const bool fdbExists = std::filesystem::exists(Game::assetManager->GetResPath() / "cdclient.fdb");
|
||||||
@ -180,8 +181,12 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
//If the first command line argument is -a or --account then make the user
|
//If the first command line argument is -a or --account then make the user
|
||||||
//input a username and password, with the password being hidden.
|
//input a username and password, with the password being hidden.
|
||||||
if (argc > 1 &&
|
bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1";
|
||||||
(strcmp(argv[1], "-a") == 0 || strcmp(argv[1], "--account") == 0)) {
|
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 username;
|
||||||
std::string password;
|
std::string password;
|
||||||
|
|
||||||
@ -264,7 +269,7 @@ int main(int argc, char** argv) {
|
|||||||
//Create account
|
//Create account
|
||||||
try {
|
try {
|
||||||
Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE));
|
Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE));
|
||||||
} catch (sql::SQLException& e) {
|
} catch (std::exception& e) {
|
||||||
LOG("A SQL error occurred!:\n %s", e.what());
|
LOG("A SQL error occurred!:\n %s", e.what());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ void PersistentIDManager::Initialize() {
|
|||||||
LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
|
LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
|
||||||
throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
|
throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
|
||||||
}
|
}
|
||||||
} catch (sql::SQLException& e) {
|
} catch (std::exception& e) {
|
||||||
LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions.");
|
LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions.");
|
||||||
LOG("SQL error: %s", e.what());
|
LOG("Error: %s", e.what());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ void StartChatServer() {
|
|||||||
//macOS doesn't need sudo to run on ports < 1024
|
//macOS doesn't need sudo to run on ports < 1024
|
||||||
auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
|
auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
|
||||||
#elif _WIN32
|
#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
|
#else
|
||||||
if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) {
|
if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) {
|
||||||
auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
|
auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
|
||||||
@ -31,7 +31,7 @@ void StartAuthServer() {
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str());
|
auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str());
|
||||||
#elif _WIN32
|
#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
|
#else
|
||||||
if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) {
|
if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) {
|
||||||
auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").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) {
|
void StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone ";
|
std::string cmd = "start /B " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone ";
|
||||||
#else
|
#else
|
||||||
std::string cmd;
|
std::string cmd;
|
||||||
if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) {
|
if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) {
|
||||||
|
@ -82,11 +82,18 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c
|
|||||||
if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth);
|
if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth);
|
||||||
else if (serverType == ServerType::World) bitStream.Write(ServiceId::World);
|
else if (serverType == ServerType::World) bitStream.Write(ServiceId::World);
|
||||||
else bitStream.Write(ServiceId::General);
|
else bitStream.Write(ServiceId::General);
|
||||||
bitStream.Write<uint64_t>(215523470896);
|
bitStream.Write<uint64_t>(219818241584);
|
||||||
|
|
||||||
server->Send(bitStream, sysAddr, false);
|
server->Send(bitStream, sysAddr, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CleanReceivedString(const std::string& str) {
|
||||||
|
std::string toReturn = str;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) {
|
void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) {
|
||||||
CINSTREAM_SKIP_HEADER;
|
CINSTREAM_SKIP_HEADER;
|
||||||
|
|
||||||
@ -111,11 +118,11 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) {
|
|||||||
|
|
||||||
LUWString memoryStats(256);
|
LUWString memoryStats(256);
|
||||||
inStream.Read(memoryStats);
|
inStream.Read(memoryStats);
|
||||||
LOG_DEBUG("Memory Stats [%s]", memoryStats.GetAsString().c_str());
|
LOG_DEBUG("Memory Stats [%s]", CleanReceivedString(memoryStats.GetAsString()).c_str());
|
||||||
|
|
||||||
LUWString videoCard(128);
|
LUWString videoCard(128);
|
||||||
inStream.Read(videoCard);
|
inStream.Read(videoCard);
|
||||||
LOG_DEBUG("VideoCard Info: [%s]", videoCard.GetAsString().c_str());
|
LOG_DEBUG("VideoCard Info: [%s]", CleanReceivedString(videoCard.GetAsString()).c_str());
|
||||||
|
|
||||||
// Processor/CPU info
|
// Processor/CPU info
|
||||||
uint32_t numOfProcessors;
|
uint32_t numOfProcessors;
|
||||||
|
@ -19,7 +19,6 @@ target_include_directories(dNet PRIVATE
|
|||||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
|
||||||
|
|
||||||
"${PROJECT_SOURCE_DIR}/dGame" # UserManager.h
|
"${PROJECT_SOURCE_DIR}/dGame" # UserManager.h
|
||||||
"${PROJECT_SOURCE_DIR}/dGame/dComponents"
|
"${PROJECT_SOURCE_DIR}/dGame/dComponents"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include "RakPeerInterface.h"
|
#include "RakPeerInterface.h"
|
||||||
#include "ReplicaManager.h"
|
#include "ReplicaManager.h"
|
||||||
@ -80,6 +81,11 @@ public:
|
|||||||
|
|
||||||
const ServerType GetServerType() const { return mServerType; }
|
const ServerType GetServerType() const { return mServerType; }
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::chrono::steady_clock::duration GetUptime() const {
|
||||||
|
return std::chrono::steady_clock::now() - mStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Startup();
|
bool Startup();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
@ -114,4 +120,5 @@ protected:
|
|||||||
SystemAddress mMasterSystemAddress;
|
SystemAddress mMasterSystemAddress;
|
||||||
std::string mMasterIP;
|
std::string mMasterIP;
|
||||||
int mMasterPort;
|
int mMasterPort;
|
||||||
|
std::chrono::steady_clock::time_point mStartTime = std::chrono::steady_clock::now();
|
||||||
};
|
};
|
||||||
|
@ -3,104 +3,107 @@
|
|||||||
#include "ScriptedActivityComponent.h"
|
#include "ScriptedActivityComponent.h"
|
||||||
#include "GameMessages.h"
|
#include "GameMessages.h"
|
||||||
#include "LeaderboardManager.h"
|
#include "LeaderboardManager.h"
|
||||||
|
#include "dServer.h"
|
||||||
#include "eMissionTaskType.h"
|
#include "eMissionTaskType.h"
|
||||||
#include "eMissionState.h"
|
#include "eMissionState.h"
|
||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
#include <ctime>
|
#include <chrono>
|
||||||
|
|
||||||
void NpcAgCourseStarter::OnStartup(Entity* self) {
|
void NpcAgCourseStarter::OnStartup(Entity* self) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void NpcAgCourseStarter::OnUse(Entity* self, Entity* user) {
|
void NpcAgCourseStarter::OnUse(Entity* self, Entity* user) {
|
||||||
auto* scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
auto* const scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
||||||
|
if (!scriptedActivityComponent) return;
|
||||||
|
|
||||||
if (scriptedActivityComponent == nullptr) {
|
const auto selfId = self->GetObjectID();
|
||||||
return;
|
const auto userId = user->GetObjectID();
|
||||||
}
|
const auto& userSysAddr = user->GetSystemAddress();
|
||||||
|
|
||||||
if (scriptedActivityComponent->GetActivityPlayerData(user->GetObjectID()) != nullptr) {
|
if (scriptedActivityComponent->GetActivityPlayerData(userId) != nullptr) {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"exit", 0, 0, LWOOBJID_EMPTY, "", user->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"exit", 0, 0, LWOOBJID_EMPTY, "", userSysAddr);
|
||||||
} else {
|
} else {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"start", 0, 0, LWOOBJID_EMPTY, "", user->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"start", 0, 0, LWOOBJID_EMPTY, "", userSysAddr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcAgCourseStarter::OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) {
|
void NpcAgCourseStarter::OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) {
|
||||||
auto* scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
auto* const scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
||||||
|
if (!scriptedActivityComponent) return;
|
||||||
|
|
||||||
if (scriptedActivityComponent == nullptr) {
|
const auto selfId = self->GetObjectID();
|
||||||
return;
|
const auto senderId = sender->GetObjectID();
|
||||||
}
|
const auto& senderSysAddr = sender->GetSystemAddress();
|
||||||
|
|
||||||
if (identifier == u"player_dialog_cancel_course" && button == 1) {
|
if (identifier == u"player_dialog_cancel_course" && button == 1) {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"stop_timer", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"stop_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
|
GameMessages::SendNotifyClientObject(selfId, u"cancel_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
|
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"cancel_timer", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
scriptedActivityComponent->RemoveActivityPlayerData(senderId);
|
||||||
|
|
||||||
scriptedActivityComponent->RemoveActivityPlayerData(sender->GetObjectID());
|
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(self);
|
Game::entityManager->SerializeEntity(self);
|
||||||
} else if (identifier == u"player_dialog_start_course" && button == 1) {
|
} else if (identifier == u"player_dialog_start_course" && button == 1) {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"start_timer", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"start_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
|
GameMessages::SendActivityStart(selfId, senderSysAddr);
|
||||||
GameMessages::SendActivityStart(self->GetObjectID(), sender->GetSystemAddress());
|
|
||||||
|
|
||||||
auto* data = scriptedActivityComponent->AddActivityPlayerData(sender->GetObjectID());
|
|
||||||
|
|
||||||
|
auto* const data = scriptedActivityComponent->AddActivityPlayerData(senderId);
|
||||||
if (data->values[1] != 0) return;
|
if (data->values[1] != 0) return;
|
||||||
|
|
||||||
// Convert to 32 bit time (Note: could try and fix the 2038 problem here by using a different epoch maybe?)
|
const auto raceStartTime = Game::server->GetUptime() + std::chrono::seconds(4); // Offset for starting timer
|
||||||
const time_t startTime = std::time(0) + 4; // Offset for starting timer
|
const auto fRaceStartTime = std::chrono::duration<float, std::ratio<1>>(raceStartTime).count();
|
||||||
data->values[1] = std::bit_cast<float>(static_cast<int32_t>(startTime));
|
data->values[1] = fRaceStartTime;
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(self);
|
Game::entityManager->SerializeEntity(self);
|
||||||
} else if (identifier == u"FootRaceCancel") {
|
} else if (identifier == u"FootRaceCancel") {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"stop_timer", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"stop_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
|
|
||||||
if (scriptedActivityComponent->GetActivityPlayerData(sender->GetObjectID()) != nullptr) {
|
if (scriptedActivityComponent->GetActivityPlayerData(senderId) != nullptr) {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"exit", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"exit", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
} else {
|
} else {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"start", 0, 0, LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
GameMessages::SendNotifyClientObject(selfId, u"start", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptedActivityComponent->RemoveActivityPlayerData(sender->GetObjectID());
|
scriptedActivityComponent->RemoveActivityPlayerData(senderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
|
void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
|
||||||
auto* scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
auto* const scriptedActivityComponent = self->GetComponent<ScriptedActivityComponent>();
|
||||||
if (scriptedActivityComponent == nullptr) return;
|
if (!scriptedActivityComponent) return;
|
||||||
|
|
||||||
auto* data = scriptedActivityComponent->GetActivityPlayerData(sender->GetObjectID());
|
const auto selfId = self->GetObjectID();
|
||||||
if (data == nullptr) return;
|
const auto senderId = sender->GetObjectID();
|
||||||
|
const auto& senderSysAddr = sender->GetSystemAddress();
|
||||||
|
|
||||||
|
auto* const data = scriptedActivityComponent->GetActivityPlayerData(senderId);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
if (args == "course_cancel") {
|
if (args == "course_cancel") {
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"cancel_timer", 0, 0,
|
GameMessages::SendNotifyClientObject(selfId, u"cancel_timer", 0, 0,
|
||||||
LWOOBJID_EMPTY, "", sender->GetSystemAddress());
|
LWOOBJID_EMPTY, "", senderSysAddr);
|
||||||
scriptedActivityComponent->RemoveActivityPlayerData(sender->GetObjectID());
|
scriptedActivityComponent->RemoveActivityPlayerData(senderId);
|
||||||
} else if (args == "course_finish") {
|
} else if (args == "course_finish") {
|
||||||
const time_t endTime = std::time(0);
|
|
||||||
const time_t startTime = std::bit_cast<int32_t>(data->values[1]);
|
|
||||||
const time_t finish = endTime - startTime;
|
|
||||||
data->values[2] = std::bit_cast<float>(static_cast<int32_t>(finish));
|
|
||||||
|
|
||||||
auto* missionComponent = sender->GetComponent<MissionComponent>();
|
const auto raceEndTime = Game::server->GetUptime();
|
||||||
|
const auto fRaceEndTime = std::chrono::duration<float, std::ratio<1>>(raceEndTime).count();
|
||||||
|
const auto raceTimeElapsed = fRaceEndTime - data->values[1];
|
||||||
|
data->values[2] = raceTimeElapsed;
|
||||||
|
|
||||||
|
auto* const missionComponent = sender->GetComponent<MissionComponent>();
|
||||||
if (missionComponent != nullptr) {
|
if (missionComponent != nullptr) {
|
||||||
missionComponent->ForceProgressTaskType(1884, 1, 1, false);
|
missionComponent->ForceProgressTaskType(1884, 1, 1, false);
|
||||||
missionComponent->Progress(eMissionTaskType::PERFORM_ACTIVITY, -finish, self->GetObjectID(),
|
missionComponent->Progress(eMissionTaskType::PERFORM_ACTIVITY, -raceTimeElapsed, selfId,
|
||||||
"performact_time");
|
"performact_time");
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(self);
|
Game::entityManager->SerializeEntity(self);
|
||||||
LeaderboardManager::SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), static_cast<float>(finish));
|
LeaderboardManager::SaveScore(senderId, scriptedActivityComponent->GetActivityID(), raceTimeElapsed);
|
||||||
|
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard",
|
GameMessages::SendNotifyClientObject(selfId, u"ToggleLeaderBoard",
|
||||||
scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(),
|
scriptedActivityComponent->GetActivityID(), 0, senderId,
|
||||||
"", sender->GetSystemAddress());
|
"", senderSysAddr);
|
||||||
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"stop_timer", 1, finish, LWOOBJID_EMPTY, "",
|
GameMessages::SendNotifyClientObject(selfId, u"stop_timer", 1, raceTimeElapsed, LWOOBJID_EMPTY, "",
|
||||||
sender->GetSystemAddress());
|
senderSysAddr);
|
||||||
|
|
||||||
scriptedActivityComponent->RemoveActivityPlayerData(sender->GetObjectID());
|
scriptedActivityComponent->RemoveActivityPlayerData(senderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_GENERAL
|
|||||||
"BankInteractServer.cpp"
|
"BankInteractServer.cpp"
|
||||||
"BaseInteractDropLootServer.cpp"
|
"BaseInteractDropLootServer.cpp"
|
||||||
"Binoculars.cpp"
|
"Binoculars.cpp"
|
||||||
|
"EnemyClearThreat.cpp"
|
||||||
"ExplodingAsset.cpp"
|
"ExplodingAsset.cpp"
|
||||||
"FrictionVolumeServer.cpp"
|
"FrictionVolumeServer.cpp"
|
||||||
"ForceVolumeServer.cpp"
|
"ForceVolumeServer.cpp"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user