mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-17 03:37:08 -06:00
Compare commits
59 Commits
fix-no-opt
...
quickbuild
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0913ff23d | ||
|
|
b738504812 | ||
|
|
c968dc9028 | ||
|
|
2209a4432f | ||
|
|
c855a6b9cf | ||
|
|
8abc545bd1 | ||
|
|
136133dde2 | ||
|
|
23551d4ed8 | ||
|
|
7599a2e81e | ||
| a6c6d892cf | |||
|
|
fb32534ae3 | ||
|
|
c8fcb3788d | ||
|
|
86b419735b | ||
|
|
9936bb0d00 | ||
|
|
beffad42ea | ||
|
|
3ecbd1013b | ||
|
|
ff4546c027 | ||
|
|
71baa5ce90 | ||
|
|
5ccd15a7d8 | ||
|
|
35bcaf6e95 | ||
|
|
900c9b6abe | ||
|
|
94e7cfc211 | ||
|
|
021db0ecd1 | ||
|
|
0b261e934f | ||
|
|
1b9f7e44c7 | ||
|
|
08a168de88 | ||
|
|
0c948a8df6 | ||
|
|
6ed6efa921 | ||
|
|
dcc9e023a6 | ||
|
|
8509ec8856 | ||
|
|
18295017c1 | ||
|
|
e8f011b830 | ||
|
|
a787673baf | ||
|
|
e869c0ad03 | ||
|
|
b2af3fa9d4 | ||
|
|
2560bb00da | ||
|
|
1ae21c423f | ||
|
|
0ae9eb4a96 | ||
|
|
fced6d753a | ||
|
|
15dc5feeb5 | ||
|
|
a60865cd19 | ||
|
|
77b42daca1 | ||
|
|
ba364800fe | ||
|
|
e1c20192f7 | ||
|
|
0f8c5b436d | ||
| 53242ad5d5 | |||
|
|
a8919c8c14 | ||
|
|
5ff121612e | ||
| 34618607c3 | |||
|
|
02b76adb7a | ||
|
|
3beb414b55 | ||
|
|
1dadeeb36f | ||
|
|
aa7c3b9061 | ||
|
|
1644d9448d | ||
|
|
8b56b0b7ba | ||
|
|
4a1c289fb1 | ||
|
|
32a1e5ece5 | ||
|
|
7fcbb9507b | ||
|
|
730533c690 |
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
@@ -6,7 +6,6 @@ docker/configs
|
|||||||
# Third party libraries
|
# Third party libraries
|
||||||
thirdparty/mysql/
|
thirdparty/mysql/
|
||||||
thirdparty/mysql_linux/
|
thirdparty/mysql_linux/
|
||||||
CMakeVariables.txt
|
|
||||||
|
|
||||||
# Build folders
|
# Build folders
|
||||||
build/
|
build/
|
||||||
@@ -95,6 +94,7 @@ ipch/
|
|||||||
|
|
||||||
# Exceptions:
|
# Exceptions:
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
|
CMakeUserPresets.json
|
||||||
*.vcxproj
|
*.vcxproj
|
||||||
*.filters
|
*.filters
|
||||||
*.cmake
|
*.cmake
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "thirdparty/cpp-httplib"]
|
|
||||||
path = thirdparty/cpp-httplib
|
|
||||||
url = https://github.com/yhirose/cpp-httplib
|
|
||||||
[submodule "thirdparty/tinyxml2"]
|
[submodule "thirdparty/tinyxml2"]
|
||||||
path = thirdparty/tinyxml2
|
path = thirdparty/tinyxml2
|
||||||
url = https://github.com/leethomason/tinyxml2
|
url = https://github.com/leethomason/tinyxml2
|
||||||
|
|||||||
@@ -66,6 +66,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)
|
||||||
|
|
||||||
@@ -174,16 +175,18 @@ 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})
|
||||||
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
|
# For backwards compatibility with older setup versions, dont add this option.
|
||||||
set(line_to_add ${line_to_add} ${line})
|
if(NOT ${variable_name} MATCHES "database_type")
|
||||||
|
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
|
||||||
|
set(line_to_add ${line_to_add} ${line})
|
||||||
|
|
||||||
foreach(line_to_append ${line_to_add})
|
foreach(line_to_append ${line_to_add})
|
||||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append})
|
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
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})
|
||||||
@@ -213,21 +216,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)
|
||||||
@@ -257,7 +247,6 @@ include_directories(
|
|||||||
"thirdparty/recastnavigation"
|
"thirdparty/recastnavigation"
|
||||||
"thirdparty/SQLite"
|
"thirdparty/SQLite"
|
||||||
"thirdparty/cpplinq"
|
"thirdparty/cpplinq"
|
||||||
"thirdparty/cpp-httplib"
|
|
||||||
"thirdparty/MD5"
|
"thirdparty/MD5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -312,7 +301,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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
PROJECT_VERSION_MAJOR=2
|
PROJECT_VERSION_MAJOR=3
|
||||||
PROJECT_VERSION_MINOR=3
|
PROJECT_VERSION_MINOR=0
|
||||||
PROJECT_VERSION_PATCH=0
|
PROJECT_VERSION_PATCH=0
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
|
|||||||
93
README.md
93
README.md
@@ -13,21 +13,35 @@ 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.
|
||||||
|
* If you are not setting a server up on mac, you can ignore this note
|
||||||
|
* Note: you'll need to allow through System Preferences `AuthServer`, `ChatServer`, `MasterServer`, `WorldServer` and `libmariadbcpp.dylib` to run. The initial pop-up will block it due to the binaries being unsigned, after allowing them to run the servers will run as normal.
|
||||||
|
|
||||||
## 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 +53,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)
|
||||||
|
|
||||||
@@ -49,6 +70,12 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
|
|||||||
|
|
||||||
## Install dependencies
|
## Install dependencies
|
||||||
|
|
||||||
|
### Required compiler versions
|
||||||
|
- g++11 or greater
|
||||||
|
- MSVC unchecked
|
||||||
|
- clang unchecked
|
||||||
|
- appleclang unchecked
|
||||||
|
|
||||||
### Windows packages
|
### Windows packages
|
||||||
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
|
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
|
||||||
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
|
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
|
||||||
@@ -183,6 +210,7 @@ If you would like to build the server faster, append `-j<number>` where number i
|
|||||||
### Notes
|
### Notes
|
||||||
Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building:
|
Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building:
|
||||||
* If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory.
|
* If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory.
|
||||||
|
* By default it should be set to the correct directory.
|
||||||
* If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023.
|
* If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023.
|
||||||
|
|
||||||
## Configuring your server
|
## Configuring your server
|
||||||
@@ -205,28 +233,41 @@ Navigate to `build/sharedconfig.ini` and fill in the following fields:
|
|||||||
* `chatconfig.ini` contains a port option.
|
* `chatconfig.ini` contains a port option.
|
||||||
* `masterconfig.ini` contains options related to permissions you want to run your servers with.
|
* `masterconfig.ini` contains options related to permissions you want to run your servers with.
|
||||||
* `sharedconfig.ini` contains several options that are shared across all servers
|
* `sharedconfig.ini` contains several options that are shared across all servers
|
||||||
* `worldconfig.ini` contains several options to turn on QOL improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off.
|
* `worldconfig.ini` contains several options to turn on Quality of Life improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off.
|
||||||
|
|
||||||
## Verify your setup
|
## Verify your setup
|
||||||
Your build directory should now look like this:
|
Your build directory should contain at a minimum all of the following files.
|
||||||
* AuthServer
|
All listed files are required for a server to start.
|
||||||
* ChatServer
|
`ini` files can be located at the environment variable `DLU_CONFIG_DIR` and do not need to be located in this directory.
|
||||||
* MasterServer
|
(windows will have .exe at the end of the executables):
|
||||||
* WorldServer
|
|
||||||
* authconfig.ini
|
|
||||||
* chatconfig.ini
|
|
||||||
* masterconfig.ini
|
|
||||||
* sharedconfig.ini
|
* sharedconfig.ini
|
||||||
|
* AuthServer(.exe)
|
||||||
|
* authconfig.ini
|
||||||
|
* ChatServer(.exe)
|
||||||
|
* chatconfig.ini
|
||||||
|
* MasterServer(.exe)
|
||||||
|
* masterconfig.ini
|
||||||
|
* WorldServer(.exe)
|
||||||
* worldconfig.ini
|
* worldconfig.ini
|
||||||
* ...
|
* blocklist.dcf
|
||||||
|
* migrations
|
||||||
|
* vanity
|
||||||
|
* navmeshes
|
||||||
|
* 1 of the following lists based on platform
|
||||||
|
* windows
|
||||||
|
* libmariadb.dll
|
||||||
|
* mariadbcpp.dll
|
||||||
|
* zlib.dll
|
||||||
|
* MacOS
|
||||||
|
* libmariadbcpp.dylib
|
||||||
|
* *nix
|
||||||
|
* libmariadbcpp.so
|
||||||
|
|
||||||
## Running the server
|
## Running the server
|
||||||
If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you either have to give the `AuthServer` binary network permissions or run it under sudo.
|
If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you have to give the `AuthServer` binary network permissions by running the following command:
|
||||||
To give `AuthServer` network permissions and not require sudo, run the following command
|
|
||||||
```bash
|
```bash
|
||||||
sudo setcap 'cap_net_bind_service=+ep' AuthServer
|
sudo setcap 'cap_net_bind_service=+ep' AuthServer
|
||||||
```
|
```
|
||||||
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
|
|
||||||
|
|
||||||
### Linux Service
|
### Linux Service
|
||||||
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
|
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
|
||||||
@@ -266,8 +307,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**
|
||||||
@@ -289,8 +330,14 @@ To connect to a server follow these steps:
|
|||||||
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
|
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
|
||||||
* Next locate the line `UGCUSE3DSERVICES=7:`
|
* Next locate the line `UGCUSE3DSERVICES=7:`
|
||||||
* Ensure the number after the 7 is a `0`
|
* Ensure the number after the 7 is a `0`
|
||||||
|
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
|
||||||
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
|
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
|
||||||
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
|
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
|
||||||
|
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78
|
||||||
|
```cfg
|
||||||
|
AUTHSERVERIP=0:12.34.56.78,
|
||||||
|
UGCUSE3DSERVICES=7:0
|
||||||
|
```
|
||||||
|
|
||||||
## Updating your server
|
## Updating your server
|
||||||
To update your server to the latest version navigate to your cloned directory
|
To update your server to the latest version navigate to your cloned directory
|
||||||
@@ -371,7 +418,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
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Server.h"
|
#include "Server.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Game {
|
namespace Game {
|
||||||
Logger* logger = nullptr;
|
Logger* logger = nullptr;
|
||||||
dServer* server = nullptr;
|
dServer* server = nullptr;
|
||||||
@@ -71,12 +70,15 @@ int main(int argc, char** argv) {
|
|||||||
//Find out the master's IP:
|
//Find out the master's IP:
|
||||||
std::string masterIP;
|
std::string masterIP;
|
||||||
uint32_t masterPort = 1500;
|
uint32_t masterPort = 1500;
|
||||||
|
std::string masterPassword;
|
||||||
|
|
||||||
auto masterInfo = Database::Get()->GetMasterInfo();
|
auto masterInfo = Database::Get()->GetMasterInfo();
|
||||||
if (masterInfo) {
|
if (masterInfo) {
|
||||||
masterIP = masterInfo->ip;
|
masterIP = masterInfo->ip;
|
||||||
masterPort = masterInfo->port;
|
masterPort = masterInfo->port;
|
||||||
|
masterPassword = masterInfo->password;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
|
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
|
||||||
|
|
||||||
Game::randomEngine = std::mt19937(time(0));
|
Game::randomEngine = std::mt19937(time(0));
|
||||||
@@ -90,7 +92,7 @@ int main(int argc, char** argv) {
|
|||||||
const auto externalIPString = Game::config->GetValue("external_ip");
|
const auto externalIPString = Game::config->GetValue("external_ip");
|
||||||
if (!externalIPString.empty()) ourIP = externalIPString;
|
if (!externalIPString.empty()) ourIP = externalIPString;
|
||||||
|
|
||||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal);
|
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal, masterPassword);
|
||||||
|
|
||||||
//Run it until server gets a kill message from Master:
|
//Run it until server gets a kill message from Master:
|
||||||
auto t = std::chrono::high_resolution_clock::now();
|
auto t = std::chrono::high_resolution_clock::now();
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
add_executable(AuthServer "AuthServer.cpp")
|
add_executable(AuthServer "AuthServer.cpp")
|
||||||
|
|
||||||
target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer)
|
target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer)
|
||||||
|
|
||||||
target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer)
|
target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer)
|
||||||
|
|
||||||
add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
|
add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
|||||||
fd.zoneID = fr.zoneID;
|
fd.zoneID = fr.zoneID;
|
||||||
|
|
||||||
//Since this friend is online, we need to update them on the fact that we've just logged in:
|
//Since this friend is online, we need to update them on the fact that we've just logged in:
|
||||||
SendFriendUpdate(fr, player, 1, fd.isBestFriend);
|
if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend);
|
||||||
} else {
|
} else {
|
||||||
fd.isOnline = false;
|
fd.isOnline = false;
|
||||||
fd.zoneID = LWOZONEID();
|
fd.zoneID = LWOZONEID();
|
||||||
@@ -103,7 +103,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto& requestee = Game::playerContainer.GetPlayerDataMutable(playerName);
|
// Intentional copy
|
||||||
|
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
|
||||||
|
|
||||||
// Check if player is online first
|
// Check if player is online first
|
||||||
if (isBestFriendRequest && !requestee) {
|
if (isBestFriendRequest && !requestee) {
|
||||||
@@ -140,7 +141,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
|||||||
|
|
||||||
// Prevent GM friend spam
|
// Prevent GM friend spam
|
||||||
// If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process
|
// If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process
|
||||||
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN ) {
|
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) {
|
||||||
SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN);
|
SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -188,19 +189,24 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
|||||||
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
|
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
|
||||||
// Sent the best friend update here if the value is 3
|
// Sent the best friend update here if the value is 3
|
||||||
if (bestFriendStatus == 3U) {
|
if (bestFriendStatus == 3U) {
|
||||||
requestee.countOfBestFriends += 1;
|
|
||||||
requestor.countOfBestFriends += 1;
|
|
||||||
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
||||||
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
||||||
|
|
||||||
for (auto& friendData : requestor.friends) {
|
for (auto& friendData : requestor.friends) {
|
||||||
if (friendData.friendID == requestee.playerID) {
|
if (friendData.friendID == requestee.playerID) {
|
||||||
friendData.isBestFriend = true;
|
friendData.isBestFriend = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto& friendData : requestee.friends) {
|
requestor.countOfBestFriends += 1;
|
||||||
if (friendData.friendID == requestor.playerID) {
|
|
||||||
friendData.isBestFriend = true;
|
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName);
|
||||||
|
if (toModify) {
|
||||||
|
for (auto& friendData : toModify.friends) {
|
||||||
|
if (friendData.friendID == requestor.playerID) {
|
||||||
|
friendData.isBestFriend = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
toModify.countOfBestFriends += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,8 +406,8 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
|
|||||||
bitStream.Write(Game::playerContainer.GetSimCount());
|
bitStream.Write(Game::playerContainer.GetSimCount());
|
||||||
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
|
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
|
||||||
bitStream.Write<uint8_t>(request.displayZoneData);
|
bitStream.Write<uint8_t>(request.displayZoneData);
|
||||||
if (request.displayZoneData || request.displayIndividualPlayers){
|
if (request.displayZoneData || request.displayIndividualPlayers) {
|
||||||
for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
|
for (auto& [playerID, playerData] : Game::playerContainer.GetAllPlayers()) {
|
||||||
if (!playerData) continue;
|
if (!playerData) continue;
|
||||||
bitStream.Write<uint8_t>(0); // structure packing
|
bitStream.Write<uint8_t>(0); // structure packing
|
||||||
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
|
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
|
||||||
|
|||||||
@@ -92,10 +92,12 @@ int main(int argc, char** argv) {
|
|||||||
//Find out the master's IP:
|
//Find out the master's IP:
|
||||||
std::string masterIP;
|
std::string masterIP;
|
||||||
uint32_t masterPort = 1000;
|
uint32_t masterPort = 1000;
|
||||||
|
std::string masterPassword;
|
||||||
auto masterInfo = Database::Get()->GetMasterInfo();
|
auto masterInfo = Database::Get()->GetMasterInfo();
|
||||||
if (masterInfo) {
|
if (masterInfo) {
|
||||||
masterIP = masterInfo->ip;
|
masterIP = masterInfo->ip;
|
||||||
masterPort = masterInfo->port;
|
masterPort = masterInfo->port;
|
||||||
|
masterPassword = masterInfo->password;
|
||||||
}
|
}
|
||||||
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
|
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
|
||||||
std::string ourIP = "localhost";
|
std::string ourIP = "localhost";
|
||||||
@@ -104,7 +106,7 @@ int main(int argc, char** argv) {
|
|||||||
const auto externalIPString = Game::config->GetValue("external_ip");
|
const auto externalIPString = Game::config->GetValue("external_ip");
|
||||||
if (!externalIPString.empty()) ourIP = externalIPString;
|
if (!externalIPString.empty()) ourIP = externalIPString;
|
||||||
|
|
||||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal);
|
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword);
|
||||||
|
|
||||||
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
|
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
|
||||||
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
|
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
|
||||||
@@ -122,6 +124,8 @@ int main(int argc, char** argv) {
|
|||||||
uint32_t framesSinceMasterDisconnect = 0;
|
uint32_t framesSinceMasterDisconnect = 0;
|
||||||
uint32_t framesSinceLastSQLPing = 0;
|
uint32_t framesSinceLastSQLPing = 0;
|
||||||
|
|
||||||
|
auto lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
Game::logger->Flush(); // once immediately before main loop
|
Game::logger->Flush(); // once immediately before main loop
|
||||||
while (!Game::ShouldShutdown()) {
|
while (!Game::ShouldShutdown()) {
|
||||||
//Check if we're still connected to master:
|
//Check if we're still connected to master:
|
||||||
@@ -132,7 +136,11 @@ int main(int argc, char** argv) {
|
|||||||
break; //Exit our loop, shut down.
|
break; //Exit our loop, shut down.
|
||||||
} else framesSinceMasterDisconnect = 0;
|
} else framesSinceMasterDisconnect = 0;
|
||||||
|
|
||||||
//In world we'd update our other systems here.
|
const auto currentTime = std::chrono::high_resolution_clock::now();
|
||||||
|
const float deltaTime = std::chrono::duration<float>(currentTime - lastTime).count();
|
||||||
|
lastTime = currentTime;
|
||||||
|
|
||||||
|
Game::playerContainer.Update(deltaTime);
|
||||||
|
|
||||||
//Check for packets here:
|
//Check for packets here:
|
||||||
Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed.
|
Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed.
|
||||||
@@ -168,7 +176,7 @@ int main(int argc, char** argv) {
|
|||||||
t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps"
|
t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps"
|
||||||
std::this_thread::sleep_until(t);
|
std::this_thread::sleep_until(t);
|
||||||
}
|
}
|
||||||
|
Game::playerContainer.Shutdown();
|
||||||
//Delete our objects here:
|
//Delete our objects here:
|
||||||
Database::Destroy("ChatServer");
|
Database::Destroy("ChatServer");
|
||||||
delete Game::server;
|
delete Game::server;
|
||||||
@@ -197,150 +205,150 @@ void HandlePacket(Packet* packet) {
|
|||||||
inStream.Read(chatMessageID);
|
inStream.Read(chatMessageID);
|
||||||
|
|
||||||
switch (chatMessageID) {
|
switch (chatMessageID) {
|
||||||
case MessageType::Chat::GM_MUTE:
|
case MessageType::Chat::GM_MUTE:
|
||||||
Game::playerContainer.MuteUpdate(packet);
|
Game::playerContainer.MuteUpdate(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::CREATE_TEAM:
|
case MessageType::Chat::CREATE_TEAM:
|
||||||
Game::playerContainer.CreateTeamServer(packet);
|
Game::playerContainer.CreateTeamServer(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::GET_FRIENDS_LIST:
|
case MessageType::Chat::GET_FRIENDS_LIST:
|
||||||
ChatPacketHandler::HandleFriendlistRequest(packet);
|
ChatPacketHandler::HandleFriendlistRequest(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::GET_IGNORE_LIST:
|
case MessageType::Chat::GET_IGNORE_LIST:
|
||||||
ChatIgnoreList::GetIgnoreList(packet);
|
ChatIgnoreList::GetIgnoreList(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::ADD_IGNORE:
|
case MessageType::Chat::ADD_IGNORE:
|
||||||
ChatIgnoreList::AddIgnore(packet);
|
ChatIgnoreList::AddIgnore(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::REMOVE_IGNORE:
|
case MessageType::Chat::REMOVE_IGNORE:
|
||||||
ChatIgnoreList::RemoveIgnore(packet);
|
ChatIgnoreList::RemoveIgnore(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_GET_STATUS:
|
case MessageType::Chat::TEAM_GET_STATUS:
|
||||||
ChatPacketHandler::HandleTeamStatusRequest(packet);
|
ChatPacketHandler::HandleTeamStatusRequest(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::ADD_FRIEND_REQUEST:
|
case MessageType::Chat::ADD_FRIEND_REQUEST:
|
||||||
//this involves someone sending the initial request, the response is below, response as in from the other player.
|
//this involves someone sending the initial request, the response is below, response as in from the other player.
|
||||||
//We basically just check to see if this player is online or not and route the packet.
|
//We basically just check to see if this player is online or not and route the packet.
|
||||||
ChatPacketHandler::HandleFriendRequest(packet);
|
ChatPacketHandler::HandleFriendRequest(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::ADD_FRIEND_RESPONSE:
|
case MessageType::Chat::ADD_FRIEND_RESPONSE:
|
||||||
//This isn't the response a server sent, rather it is a player's response to a received request.
|
//This isn't the response a server sent, rather it is a player's response to a received request.
|
||||||
//Here, we'll actually have to add them to eachother's friend lists depending on the response code.
|
//Here, we'll actually have to add them to eachother's friend lists depending on the response code.
|
||||||
ChatPacketHandler::HandleFriendResponse(packet);
|
ChatPacketHandler::HandleFriendResponse(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::REMOVE_FRIEND:
|
case MessageType::Chat::REMOVE_FRIEND:
|
||||||
ChatPacketHandler::HandleRemoveFriend(packet);
|
ChatPacketHandler::HandleRemoveFriend(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::GENERAL_CHAT_MESSAGE:
|
case MessageType::Chat::GENERAL_CHAT_MESSAGE:
|
||||||
ChatPacketHandler::HandleChatMessage(packet);
|
ChatPacketHandler::HandleChatMessage(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::PRIVATE_CHAT_MESSAGE:
|
case MessageType::Chat::PRIVATE_CHAT_MESSAGE:
|
||||||
//This message is supposed to be echo'd to both the sender and the receiver
|
//This message is supposed to be echo'd to both the sender and the receiver
|
||||||
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
|
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
|
||||||
ChatPacketHandler::HandlePrivateChatMessage(packet);
|
ChatPacketHandler::HandlePrivateChatMessage(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_INVITE:
|
case MessageType::Chat::TEAM_INVITE:
|
||||||
ChatPacketHandler::HandleTeamInvite(packet);
|
ChatPacketHandler::HandleTeamInvite(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_INVITE_RESPONSE:
|
case MessageType::Chat::TEAM_INVITE_RESPONSE:
|
||||||
ChatPacketHandler::HandleTeamInviteResponse(packet);
|
ChatPacketHandler::HandleTeamInviteResponse(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_LEAVE:
|
case MessageType::Chat::TEAM_LEAVE:
|
||||||
ChatPacketHandler::HandleTeamLeave(packet);
|
ChatPacketHandler::HandleTeamLeave(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_SET_LEADER:
|
case MessageType::Chat::TEAM_SET_LEADER:
|
||||||
ChatPacketHandler::HandleTeamPromote(packet);
|
ChatPacketHandler::HandleTeamPromote(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_KICK:
|
case MessageType::Chat::TEAM_KICK:
|
||||||
ChatPacketHandler::HandleTeamKick(packet);
|
ChatPacketHandler::HandleTeamKick(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::Chat::TEAM_SET_LOOT:
|
case MessageType::Chat::TEAM_SET_LOOT:
|
||||||
ChatPacketHandler::HandleTeamLootOption(packet);
|
ChatPacketHandler::HandleTeamLootOption(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::GMLEVEL_UPDATE:
|
case MessageType::Chat::GMLEVEL_UPDATE:
|
||||||
ChatPacketHandler::HandleGMLevelUpdate(packet);
|
ChatPacketHandler::HandleGMLevelUpdate(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::LOGIN_SESSION_NOTIFY:
|
case MessageType::Chat::LOGIN_SESSION_NOTIFY:
|
||||||
Game::playerContainer.InsertPlayer(packet);
|
Game::playerContainer.InsertPlayer(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::GM_ANNOUNCE:{
|
case MessageType::Chat::GM_ANNOUNCE: {
|
||||||
// we just forward this packet to every connected server
|
// we just forward this packet to every connected server
|
||||||
inStream.ResetReadPointer();
|
inStream.ResetReadPointer();
|
||||||
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
|
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::UNEXPECTED_DISCONNECT:
|
case MessageType::Chat::UNEXPECTED_DISCONNECT:
|
||||||
Game::playerContainer.RemovePlayer(packet);
|
Game::playerContainer.ScheduleRemovePlayer(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::WHO:
|
case MessageType::Chat::WHO:
|
||||||
ChatPacketHandler::HandleWho(packet);
|
ChatPacketHandler::HandleWho(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::SHOW_ALL:
|
case MessageType::Chat::SHOW_ALL:
|
||||||
ChatPacketHandler::HandleShowAll(packet);
|
ChatPacketHandler::HandleShowAll(packet);
|
||||||
break;
|
break;
|
||||||
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
|
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
|
||||||
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
|
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
|
||||||
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
|
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
|
||||||
case MessageType::Chat::WORLD_PARCEL_RESPONSE:
|
case MessageType::Chat::WORLD_PARCEL_RESPONSE:
|
||||||
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK:
|
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK:
|
||||||
case MessageType::Chat::GUILD_CREATE:
|
case MessageType::Chat::GUILD_CREATE:
|
||||||
case MessageType::Chat::GUILD_INVITE:
|
case MessageType::Chat::GUILD_INVITE:
|
||||||
case MessageType::Chat::GUILD_INVITE_RESPONSE:
|
case MessageType::Chat::GUILD_INVITE_RESPONSE:
|
||||||
case MessageType::Chat::GUILD_LEAVE:
|
case MessageType::Chat::GUILD_LEAVE:
|
||||||
case MessageType::Chat::GUILD_KICK:
|
case MessageType::Chat::GUILD_KICK:
|
||||||
case MessageType::Chat::GUILD_GET_STATUS:
|
case MessageType::Chat::GUILD_GET_STATUS:
|
||||||
case MessageType::Chat::GUILD_GET_ALL:
|
case MessageType::Chat::GUILD_GET_ALL:
|
||||||
case MessageType::Chat::BLUEPRINT_MODERATED:
|
case MessageType::Chat::BLUEPRINT_MODERATED:
|
||||||
case MessageType::Chat::BLUEPRINT_MODEL_READY:
|
case MessageType::Chat::BLUEPRINT_MODEL_READY:
|
||||||
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL:
|
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL:
|
||||||
case MessageType::Chat::PROPERTY_MODERATION_CHANGED:
|
case MessageType::Chat::PROPERTY_MODERATION_CHANGED:
|
||||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED:
|
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED:
|
||||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT:
|
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT:
|
||||||
case MessageType::Chat::MAIL:
|
case MessageType::Chat::MAIL:
|
||||||
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST:
|
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST:
|
||||||
case MessageType::Chat::REPUTATION_UPDATE:
|
case MessageType::Chat::REPUTATION_UPDATE:
|
||||||
case MessageType::Chat::SEND_CANNED_TEXT:
|
case MessageType::Chat::SEND_CANNED_TEXT:
|
||||||
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST:
|
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST:
|
||||||
case MessageType::Chat::CSR_REQUEST:
|
case MessageType::Chat::CSR_REQUEST:
|
||||||
case MessageType::Chat::CSR_REPLY:
|
case MessageType::Chat::CSR_REPLY:
|
||||||
case MessageType::Chat::GM_KICK:
|
case MessageType::Chat::GM_KICK:
|
||||||
case MessageType::Chat::WORLD_ROUTE_PACKET:
|
case MessageType::Chat::WORLD_ROUTE_PACKET:
|
||||||
case MessageType::Chat::GET_ZONE_POPULATIONS:
|
case MessageType::Chat::GET_ZONE_POPULATIONS:
|
||||||
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE:
|
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE:
|
||||||
case MessageType::Chat::MATCH_REQUEST:
|
case MessageType::Chat::MATCH_REQUEST:
|
||||||
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE:
|
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE:
|
||||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE:
|
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE:
|
||||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
|
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
|
||||||
case MessageType::Chat::UGCC_REQUEST:
|
case MessageType::Chat::UGCC_REQUEST:
|
||||||
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
|
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
|
||||||
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
||||||
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
|
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
|
||||||
case MessageType::Chat::PLAYER_READY:
|
case MessageType::Chat::PLAYER_READY:
|
||||||
case MessageType::Chat::GET_DONATION_TOTAL:
|
case MessageType::Chat::GET_DONATION_TOTAL:
|
||||||
case MessageType::Chat::UPDATE_DONATION:
|
case MessageType::Chat::UPDATE_DONATION:
|
||||||
case MessageType::Chat::PRG_CSR_COMMAND:
|
case MessageType::Chat::PRG_CSR_COMMAND:
|
||||||
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD:
|
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD:
|
||||||
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS:
|
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS:
|
||||||
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
|
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG("Unknown CHAT Message id: %i", chatMessageID);
|
LOG("Unknown CHAT Message id: %i", chatMessageID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isLogin = !m_Players.contains(playerId);
|
||||||
auto& data = m_Players[playerId];
|
auto& data = m_Players[playerId];
|
||||||
|
data = PlayerData();
|
||||||
|
data.isLogin = isLogin;
|
||||||
data.playerID = playerId;
|
data.playerID = playerId;
|
||||||
|
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
@@ -57,13 +60,32 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
|
|||||||
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
|
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
|
||||||
|
|
||||||
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
|
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
|
||||||
|
m_PlayersToRemove.erase(playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerContainer::RemovePlayer(Packet* packet) {
|
void PlayerContainer::ScheduleRemovePlayer(Packet* packet) {
|
||||||
CINSTREAM_SKIP_HEADER;
|
CINSTREAM_SKIP_HEADER;
|
||||||
LWOOBJID playerID;
|
LWOOBJID playerID{ LWOOBJID_EMPTY };
|
||||||
inStream.Read(playerID);
|
inStream.Read(playerID);
|
||||||
|
constexpr float updatePlayerOnLogoutTime = 20.0f;
|
||||||
|
if (playerID != LWOOBJID_EMPTY) m_PlayersToRemove.insert_or_assign(playerID, updatePlayerOnLogoutTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerContainer::Update(const float deltaTime) {
|
||||||
|
for (auto it = m_PlayersToRemove.begin(); it != m_PlayersToRemove.end();) {
|
||||||
|
auto& [id, time] = *it;
|
||||||
|
time -= deltaTime;
|
||||||
|
|
||||||
|
if (time <= 0.0f) {
|
||||||
|
RemovePlayer(id);
|
||||||
|
it = m_PlayersToRemove.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||||
//Before they get kicked, we need to also send a message to their friends saying that they disconnected.
|
//Before they get kicked, we need to also send a message to their friends saying that they disconnected.
|
||||||
const auto& player = GetPlayerData(playerID);
|
const auto& player = GetPlayerData(playerID);
|
||||||
|
|
||||||
@@ -417,3 +439,13 @@ const PlayerData& PlayerContainer::GetPlayerData(const LWOOBJID& playerID) {
|
|||||||
const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) {
|
const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) {
|
||||||
return GetPlayerDataMutable(playerName);
|
return GetPlayerDataMutable(playerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerContainer::Shutdown() {
|
||||||
|
m_Players.erase(LWOOBJID_EMPTY);
|
||||||
|
while (!m_Players.empty()) {
|
||||||
|
const auto& [id, playerData] = *m_Players.begin();
|
||||||
|
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
|
||||||
|
m_Players.erase(m_Players.begin());
|
||||||
|
}
|
||||||
|
for (auto* team : mTeams) if (team) delete team;
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ struct PlayerData {
|
|||||||
std::vector<IgnoreData> ignoredPlayers;
|
std::vector<IgnoreData> ignoredPlayers;
|
||||||
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
|
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
|
||||||
bool isFTP = false;
|
bool isFTP = false;
|
||||||
|
bool isLogin = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TeamData {
|
struct TeamData {
|
||||||
@@ -62,10 +63,12 @@ class PlayerContainer {
|
|||||||
public:
|
public:
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void InsertPlayer(Packet* packet);
|
void InsertPlayer(Packet* packet);
|
||||||
void RemovePlayer(Packet* packet);
|
void ScheduleRemovePlayer(Packet* packet);
|
||||||
|
void RemovePlayer(const LWOOBJID playerID);
|
||||||
void MuteUpdate(Packet* packet);
|
void MuteUpdate(Packet* packet);
|
||||||
void CreateTeamServer(Packet* packet);
|
void CreateTeamServer(Packet* packet);
|
||||||
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
|
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
const PlayerData& GetPlayerData(const LWOOBJID& playerID);
|
const PlayerData& GetPlayerData(const LWOOBJID& playerID);
|
||||||
const PlayerData& GetPlayerData(const std::string& playerName);
|
const PlayerData& GetPlayerData(const std::string& playerName);
|
||||||
@@ -89,11 +92,15 @@ public:
|
|||||||
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
|
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
|
||||||
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
|
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
|
||||||
|
|
||||||
|
void Update(const float deltaTime);
|
||||||
|
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LWOOBJID m_TeamIDCounter = 0;
|
LWOOBJID m_TeamIDCounter = 0;
|
||||||
std::map<LWOOBJID, PlayerData> m_Players;
|
std::map<LWOOBJID, PlayerData> m_Players;
|
||||||
std::vector<TeamData*> mTeams;
|
std::vector<TeamData*> mTeams;
|
||||||
std::unordered_map<LWOOBJID, std::u16string> m_Names;
|
std::unordered_map<LWOOBJID, std::u16string> m_Names;
|
||||||
|
std::map<LWOOBJID, float> m_PlayersToRemove;
|
||||||
uint32_t m_MaxNumberOfBestFriends = 5;
|
uint32_t m_MaxNumberOfBestFriends = 5;
|
||||||
uint32_t m_MaxNumberOfFriends = 50;
|
uint32_t m_MaxNumberOfFriends = 50;
|
||||||
uint32_t m_PlayerCount = 0;
|
uint32_t m_PlayerCount = 0;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -257,10 +258,10 @@ public:
|
|||||||
*
|
*
|
||||||
* @param key The key to remove from the associative portion
|
* @param key The key to remove from the associative portion
|
||||||
*/
|
*/
|
||||||
void Remove(const std::string& key, const bool deleteValue = true) {
|
void Remove(const std::string& key) {
|
||||||
const AMFAssociative::const_iterator it = m_Associative.find(key);
|
const AMFAssociative::const_iterator it = m_Associative.find(key);
|
||||||
if (it != m_Associative.cend()) {
|
if (it != m_Associative.cend()) {
|
||||||
if (deleteValue) m_Associative.erase(it);
|
m_Associative.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +344,11 @@ public:
|
|||||||
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr;
|
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
m_Associative.clear();
|
||||||
|
m_Dense.clear();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* The associative portion. These values are key'd with strings to an AMFValue.
|
* The associative portion. These values are key'd with strings to an AMFValue.
|
||||||
|
|||||||
@@ -2,16 +2,25 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
//For reading null-terminated strings
|
//For reading null-terminated strings
|
||||||
std::string BinaryIO::ReadString(std::istream& instream) {
|
template<typename StringType>
|
||||||
std::string toReturn;
|
StringType ReadString(std::istream& instream) {
|
||||||
char buffer;
|
StringType toReturn{};
|
||||||
|
typename StringType::value_type buffer{};
|
||||||
|
|
||||||
BinaryIO::BinaryRead(instream, buffer);
|
BinaryIO::BinaryRead(instream, buffer);
|
||||||
|
|
||||||
while (buffer != 0x00) {
|
while (buffer != 0x00) {
|
||||||
toReturn += buffer;
|
toReturn += buffer;
|
||||||
BinaryRead(instream, buffer);
|
BinaryIO::BinaryRead(instream, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string BinaryIO::ReadString(std::istream& instream) {
|
||||||
|
return ::ReadString<std::string>(instream);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u8string BinaryIO::ReadU8String(std::istream& instream) {
|
||||||
|
return ::ReadString<std::u8string>(instream);
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ namespace BinaryIO {
|
|||||||
|
|
||||||
std::string ReadString(std::istream& instream);
|
std::string ReadString(std::istream& instream);
|
||||||
|
|
||||||
|
std::u8string ReadU8String(std::istream& instream);
|
||||||
|
|
||||||
inline bool DoesFileExist(const std::string& name) {
|
inline bool DoesFileExist(const std::string& name) {
|
||||||
std::ifstream f(name.c_str());
|
std::ifstream f(name.c_str());
|
||||||
return f.good();
|
return f.good();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -65,13 +65,14 @@ int64_t FdbToSqlite::Convert::ReadInt64(std::istream& cdClientBuffer) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cdclient is encoded in latin1
|
||||||
std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) {
|
std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) {
|
||||||
int32_t prevPosition = SeekPointer(cdClientBuffer);
|
int32_t prevPosition = SeekPointer(cdClientBuffer);
|
||||||
|
|
||||||
auto readString = BinaryIO::ReadString(cdClientBuffer);
|
const auto readString = BinaryIO::ReadU8String(cdClientBuffer);
|
||||||
|
|
||||||
cdClientBuffer.seekg(prevPosition);
|
cdClientBuffer.seekg(prevPosition);
|
||||||
return readString;
|
return GeneralUtils::Latin1ToUTF8(readString);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) {
|
int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) {
|
||||||
|
|||||||
@@ -167,6 +167,15 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const s
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GeneralUtils::Latin1ToUTF8(const std::u8string_view string, const size_t size) {
|
||||||
|
std::string toReturn{};
|
||||||
|
|
||||||
|
for (const auto u : string) {
|
||||||
|
PushUTF8CodePoint(toReturn, u);
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
//! Converts a (potentially-ill-formed) UTF-16 string to UTF-8
|
//! Converts a (potentially-ill-formed) UTF-16 string to UTF-8
|
||||||
//! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16>
|
//! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16>
|
||||||
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) {
|
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) {
|
||||||
@@ -175,9 +184,9 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const si
|
|||||||
ret.reserve(newSize);
|
ret.reserve(newSize);
|
||||||
|
|
||||||
for (size_t i = 0; i < newSize; ++i) {
|
for (size_t i = 0; i < newSize; ++i) {
|
||||||
const char16_t u = string[i];
|
const auto u = string[i];
|
||||||
if (IsLeadSurrogate(u) && (i + 1) < newSize) {
|
if (IsLeadSurrogate(u) && (i + 1) < newSize) {
|
||||||
const char16_t next = string[i + 1];
|
const auto next = string[i + 1];
|
||||||
if (IsTrailSurrogate(next)) {
|
if (IsTrailSurrogate(next)) {
|
||||||
i += 1;
|
i += 1;
|
||||||
const char32_t cp = 0x10000
|
const char32_t cp = 0x10000
|
||||||
@@ -291,11 +300,12 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
|
|||||||
|
|
||||||
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) {
|
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) {
|
||||||
// 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)) {
|
||||||
auto filename = t.path().filename().string();
|
if (t.is_directory() || t.is_symlink()) continue;
|
||||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
auto filename = t.path().filename().string();
|
||||||
filenames.emplace(index, std::move(filename));
|
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||||
|
filenames.emplace(index, std::move(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now sort the map by the oldest migration.
|
// Now sort the map by the oldest migration.
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ namespace GeneralUtils {
|
|||||||
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
|
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Converts a Latin1 string to a UTF-8 string
|
||||||
|
/*!
|
||||||
|
\param string The string to convert
|
||||||
|
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
|
||||||
|
\return An UTF-8 representation of the string
|
||||||
|
*/
|
||||||
|
std::string Latin1ToUTF8(const std::u8string_view string, const size_t size = SIZE_MAX);
|
||||||
|
|
||||||
//! Converts a UTF-16 string to a UTF-8 string
|
//! Converts a UTF-16 string to a UTF-8 string
|
||||||
/*!
|
/*!
|
||||||
\param string The string to convert
|
\param string The string to convert
|
||||||
|
|||||||
@@ -957,6 +957,7 @@ namespace MessageType {
|
|||||||
MODIFY_PLAYER_ZONE_STATISTIC = 1046,
|
MODIFY_PLAYER_ZONE_STATISTIC = 1046,
|
||||||
APPLY_EXTERNAL_FORCE = 1049,
|
APPLY_EXTERNAL_FORCE = 1049,
|
||||||
GET_APPLIED_EXTERNAL_FORCE = 1050,
|
GET_APPLIED_EXTERNAL_FORCE = 1050,
|
||||||
|
ACTIVITY_NOTIFY = 1051,
|
||||||
ITEM_EQUIPPED = 1052,
|
ITEM_EQUIPPED = 1052,
|
||||||
ACTIVITY_STATE_CHANGE_REQUEST = 1053,
|
ACTIVITY_STATE_CHANGE_REQUEST = 1053,
|
||||||
OVERRIDE_FRICTION = 1054,
|
OVERRIDE_FRICTION = 1054,
|
||||||
@@ -1253,6 +1254,7 @@ namespace MessageType {
|
|||||||
VEHICLE_NOTIFY_HIT_EXPLODER = 1385,
|
VEHICLE_NOTIFY_HIT_EXPLODER = 1385,
|
||||||
CHECK_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1386,
|
CHECK_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1386,
|
||||||
REQUEST_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1387,
|
REQUEST_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1387,
|
||||||
|
CONFIGURE_RACING_CONTROL = 1388,
|
||||||
CONFIGURE_RACING_CONTROL_CLIENT = 1389,
|
CONFIGURE_RACING_CONTROL_CLIENT = 1389,
|
||||||
NOTIFY_RACING_CLIENT = 1390,
|
NOTIFY_RACING_CLIENT = 1390,
|
||||||
RACING_PLAYER_HACK_CAR = 1391,
|
RACING_PLAYER_HACK_CAR = 1391,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
database = new MySQLDatabase();
|
const auto databaseType = GetMigrationFolder();
|
||||||
|
|
||||||
|
if (databaseType == "sqlite") database = new SQLiteDatabase();
|
||||||
|
else if (databaseType == "mysql") database = new MySQLDatabase();
|
||||||
|
else {
|
||||||
|
LOG("Invalid database type specified in config, using MySQL");
|
||||||
|
database = new MySQLDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
database->Connect();
|
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{};
|
||||||
|
float 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__
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ public:
|
|||||||
struct MasterInfo {
|
struct MasterInfo {
|
||||||
std::string ip;
|
std::string ip;
|
||||||
uint32_t port{};
|
uint32_t port{};
|
||||||
|
std::string password{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the master server ip and port.
|
// Set the master server ip and port.
|
||||||
virtual void SetMasterIp(const std::string_view ip, const uint32_t port) = 0;
|
virtual void SetMasterInfo(const MasterInfo& info) = 0;
|
||||||
|
|
||||||
// Get the master server info.
|
// Get the master server info.
|
||||||
virtual std::optional<MasterInfo> GetMasterInfo() = 0;
|
virtual std::optional<MasterInfo> GetMasterInfo() = 0;
|
||||||
|
|||||||
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;
|
||||||
@@ -96,7 +96,7 @@ public:
|
|||||||
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
|
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
|
||||||
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) 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 InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
|
||||||
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
|
void SetMasterInfo(const IServers::MasterInfo& info) override;
|
||||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||||
void InsertDefaultPersistentId() override;
|
void InsertDefaultPersistentId() override;
|
||||||
void UpdatePersistentId(const uint32_t id) override;
|
void UpdatePersistentId(const uint32_t id) 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
|||||||
params.playerId
|
params.playerId
|
||||||
);
|
);
|
||||||
if (count->next()) {
|
if (count->next()) {
|
||||||
|
if (!result) result = IProperty::PropertyEntranceResult();
|
||||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -109,11 +110,13 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
|||||||
params.playerSort
|
params.playerSort
|
||||||
);
|
);
|
||||||
if (count->next()) {
|
if (count->next()) {
|
||||||
|
if (!result) result = IProperty::PropertyEntranceResult();
|
||||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (properties->next()) {
|
while (properties->next()) {
|
||||||
|
if (!result) result = IProperty::PropertyEntranceResult();
|
||||||
auto& entry = result->entries.emplace_back();
|
auto& entry = result->entries.emplace_back();
|
||||||
entry.id = properties->getUInt64("id");
|
entry.id = properties->getUInt64("id");
|
||||||
entry.ownerId = properties->getUInt64("owner_id");
|
entry.ownerId = properties->getUInt64("owner_id");
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "MySQLDatabase.h"
|
#include "MySQLDatabase.h"
|
||||||
|
|
||||||
void MySQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
void MySQLDatabase::SetMasterInfo(const MasterInfo& info) {
|
||||||
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
|
// 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.
|
// since it would be two queries anyways.
|
||||||
ExecuteDelete("TRUNCATE TABLE servers;");
|
ExecuteDelete("TRUNCATE TABLE servers;");
|
||||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
|
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`, `master_password`) VALUES ('master', ?, ?, 0, 171022, ?)", info.ip, info.port, info.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
|
std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
|
||||||
auto result = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
|
auto result = ExecuteSelect("SELECT ip, port, master_password FROM servers WHERE name='master' LIMIT 1;");
|
||||||
|
|
||||||
if (!result->next()) {
|
if (!result->next()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -18,6 +18,7 @@ std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
|
|||||||
|
|
||||||
toReturn.ip = result->getString("ip").c_str();
|
toReturn.ip = result->getString("ip").c_str();
|
||||||
toReturn.port = result->getInt("port");
|
toReturn.port = result->getInt("port");
|
||||||
|
toReturn.password = result->getString("master_password").c_str();
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|||||||
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)
|
||||||
81
dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp
Normal file
81
dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
#include "Database.h"
|
||||||
|
#include "Game.h"
|
||||||
|
#include "dConfig.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "dPlatforms.h"
|
||||||
|
#include "BinaryPathFinder.h"
|
||||||
|
|
||||||
|
// Static Variables
|
||||||
|
|
||||||
|
// Status Variables
|
||||||
|
namespace {
|
||||||
|
CppSQLite3DB* con = nullptr;
|
||||||
|
bool isConnected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SQLiteDatabase::Connect() {
|
||||||
|
LOG("Using SQLite database");
|
||||||
|
con = new CppSQLite3DB();
|
||||||
|
const auto path = BinaryPathFinder::GetBinaryDir() / Game::config->GetValue("sqlite_database_path");
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(path)) {
|
||||||
|
LOG("Creating sqlite path %s", path.string().c_str());
|
||||||
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
con->open(path.string().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 SetMasterInfo(const IServers::MasterInfo& info) 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);
|
||||||
|
}
|
||||||
24
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
24
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#include "SQLiteDatabase.h"
|
||||||
|
|
||||||
|
void SQLiteDatabase::SetMasterInfo(const MasterInfo& info) {
|
||||||
|
// 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`, `master_password`) VALUES ('master', ?, ?, 0, 171022 ?)", info.ip, info.port, info.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
|
||||||
|
auto [_, result] = ExecuteSelect("SELECT ip, port, master_password 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");
|
||||||
|
toReturn.password = result.getStringField("master_password");
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -240,7 +236,7 @@ void TestSQLDatabase::InsertNewAccount(const std::string_view username, const st
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestSQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -74,7 +73,7 @@ class TestSQLDatabase : public GameDatabase {
|
|||||||
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
|
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
|
||||||
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) 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 InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
|
||||||
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
|
void SetMasterInfo(const IServers::MasterInfo& info) override;
|
||||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||||
void InsertDefaultPersistentId() override;
|
void InsertDefaultPersistentId() override;
|
||||||
void UpdatePersistentId(const uint32_t id) override;
|
void UpdatePersistentId(const uint32_t id) 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();
|
||||||
|
|
||||||
|
// 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 = "";
|
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());
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -296,6 +296,12 @@ void Character::SaveXMLToDatabase() {
|
|||||||
flags->LinkEndChild(s);
|
flags->LinkEndChild(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GetPlayerFlag(ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR)) {
|
||||||
|
auto* s = m_Doc.NewElement("s");
|
||||||
|
s->SetAttribute("si", ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR);
|
||||||
|
flags->LinkEndChild(s);
|
||||||
|
}
|
||||||
|
|
||||||
SaveXmlRespawnCheckpoints();
|
SaveXmlRespawnCheckpoints();
|
||||||
|
|
||||||
//Call upon the entity to update our xmlDoc:
|
//Call upon the entity to update our xmlDoc:
|
||||||
@@ -357,49 +363,62 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the index first
|
if (flagId == EQUPPED_TRIAL_FACTION_GEAR || flagId == IS_NEWS_SCREEN_VISIBLE) {
|
||||||
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
if (value) m_SessionFlags.insert(flagId);
|
||||||
|
else m_SessionFlags.erase(flagId);
|
||||||
const auto shiftedValue = 1ULL << flagId % 64;
|
|
||||||
|
|
||||||
auto it = m_PlayerFlags.find(flagIndex);
|
|
||||||
|
|
||||||
// Check if flag index exists
|
|
||||||
if (it != m_PlayerFlags.end()) {
|
|
||||||
// Update the value
|
|
||||||
if (value) {
|
|
||||||
it->second |= shiftedValue;
|
|
||||||
} else {
|
|
||||||
it->second &= ~shiftedValue;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (value) {
|
// Calculate the index first
|
||||||
// Otherwise, insert the value
|
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||||
uint64_t flagValue = 0;
|
|
||||||
|
|
||||||
flagValue |= shiftedValue;
|
const auto shiftedValue = 1ULL << flagId % 64;
|
||||||
|
|
||||||
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
auto it = m_PlayerFlags.find(flagIndex);
|
||||||
|
|
||||||
|
// Check if flag index exists
|
||||||
|
if (it != m_PlayerFlags.end()) {
|
||||||
|
// Update the value
|
||||||
|
if (value) {
|
||||||
|
it->second |= shiftedValue;
|
||||||
|
} else {
|
||||||
|
it->second &= ~shiftedValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value) {
|
||||||
|
// Otherwise, insert the value
|
||||||
|
uint64_t flagValue = 0;
|
||||||
|
|
||||||
|
flagValue |= shiftedValue;
|
||||||
|
|
||||||
|
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the client that a flag has changed server-side
|
// Notify the client that a flag has changed server-side
|
||||||
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
|
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Character::GetPlayerFlag(const uint32_t flagId) const {
|
bool Character::GetPlayerFlag(const uint32_t flagId) const {
|
||||||
// Calculate the index first
|
using enum ePlayerFlag;
|
||||||
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
|
||||||
|
|
||||||
const auto shiftedValue = 1ULL << flagId % 64;
|
bool toReturn = false; //by def, return false.
|
||||||
|
|
||||||
auto it = m_PlayerFlags.find(flagIndex);
|
// TODO make actual session flag checker using flags table in database.
|
||||||
if (it != m_PlayerFlags.end()) {
|
if (flagId == EQUPPED_TRIAL_FACTION_GEAR || flagId == IS_NEWS_SCREEN_VISIBLE) {
|
||||||
// Don't set the data if we don't have to
|
toReturn = m_SessionFlags.contains(flagId);
|
||||||
return (it->second & shiftedValue) != 0;
|
} else {
|
||||||
|
// Calculate the index first
|
||||||
|
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||||
|
|
||||||
|
const auto shiftedValue = 1ULL << flagId % 64;
|
||||||
|
|
||||||
|
auto it = m_PlayerFlags.find(flagIndex);
|
||||||
|
if (it != m_PlayerFlags.end()) {
|
||||||
|
// Don't set the data if we don't have to
|
||||||
|
toReturn = (it->second & shiftedValue) != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; //by def, return false.
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::SetRetroactiveFlags() {
|
void Character::SetRetroactiveFlags() {
|
||||||
|
|||||||
@@ -620,6 +620,12 @@ private:
|
|||||||
*/
|
*/
|
||||||
uint64_t m_LastLogin{};
|
uint64_t m_LastLogin{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags only set for the duration of a session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
std::set<uint32_t> m_SessionFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The gameplay flags this character has (not just true values)
|
* The gameplay flags this character has (not just true values)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -379,6 +386,9 @@ void Entity::Initialize() {
|
|||||||
if (m_Character) {
|
if (m_Character) {
|
||||||
comp->LoadFromXml(m_Character->GetXMLDoc());
|
comp->LoadFromXml(m_Character->GetXMLDoc());
|
||||||
} else {
|
} else {
|
||||||
|
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||||
|
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
||||||
|
|
||||||
if (componentID > 0) {
|
if (componentID > 0) {
|
||||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
||||||
|
|
||||||
@@ -413,9 +423,6 @@ void Entity::Initialize() {
|
|||||||
comp->SetMinCoins(currencyValues[0].minvalue);
|
comp->SetMinCoins(currencyValues[0].minvalue);
|
||||||
comp->SetMaxCoins(currencyValues[0].maxvalue);
|
comp->SetMaxCoins(currencyValues[0].maxvalue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
|
||||||
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
comp->SetHealth(1);
|
comp->SetHealth(1);
|
||||||
@@ -768,6 +775,12 @@ void Entity::Initialize() {
|
|||||||
// Hacky way to trigger these when the object has had a chance to get constructed
|
// Hacky way to trigger these when the object has had a chance to get constructed
|
||||||
AddCallbackTimer(0, [this]() {
|
AddCallbackTimer(0, [this]() {
|
||||||
this->GetScript()->OnStartup(this);
|
this->GetScript()->OnStartup(this);
|
||||||
|
if (this->m_ParentEntity) {
|
||||||
|
GameMessages::ChildLoaded childLoaded;
|
||||||
|
childLoaded.childID = this->m_ObjectID;
|
||||||
|
childLoaded.templateID = this->GetLOT();
|
||||||
|
this->m_ParentEntity->OnChildLoaded(childLoaded);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!m_Character && Game::entityManager->GetGhostingEnabled()) {
|
if (!m_Character && Game::entityManager->GetGhostingEnabled()) {
|
||||||
@@ -847,6 +860,9 @@ void Entity::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, co
|
|||||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||||
if (!destroyableComponent) return;
|
if (!destroyableComponent) return;
|
||||||
destroyableComponent->Subscribe(scriptObjId, scriptToAdd);
|
destroyableComponent->Subscribe(scriptObjId, scriptToAdd);
|
||||||
|
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||||
|
LOG("Subscribing to PlayerResurrectionFinished");
|
||||||
|
m_Subscriptions[scriptObjId][notificationName] = scriptToAdd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,6 +871,9 @@ void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationNa
|
|||||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||||
if (!destroyableComponent) return;
|
if (!destroyableComponent) return;
|
||||||
destroyableComponent->Unsubscribe(scriptObjId);
|
destroyableComponent->Unsubscribe(scriptObjId);
|
||||||
|
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||||
|
LOG("Unsubscribing from PlayerResurrectionFinished");
|
||||||
|
m_Subscriptions[scriptObjId].erase(notificationName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1271,6 +1290,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 +1354,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 +1372,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
|
||||||
@@ -1479,6 +1505,27 @@ void Entity::OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16s
|
|||||||
GetScript()->OnChoiceBoxResponse(this, sender, button, buttonIdentifier, identifier);
|
GetScript()->OnChoiceBoxResponse(this, sender, button, buttonIdentifier, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Entity::OnActivityNotify(GameMessages::ActivityNotify& notify) {
|
||||||
|
GetScript()->OnActivityNotify(this, notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entity::OnShootingGalleryFire(GameMessages::ShootingGalleryFire& fire) {
|
||||||
|
GetScript()->OnShootingGalleryFire(*this, fire);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entity::OnChildLoaded(GameMessages::ChildLoaded& childLoaded) {
|
||||||
|
GetScript()->OnChildLoaded(*this, childLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entity::NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg) {
|
||||||
|
for (const auto& [id, scriptList] : m_Subscriptions) {
|
||||||
|
auto it = scriptList.find("PlayerResurrectionFinished");
|
||||||
|
if (it == scriptList.end()) continue;
|
||||||
|
|
||||||
|
it->second->NotifyPlayerResurrectionFinished(*this, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
|
void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
|
||||||
GetScript()->OnRequestActivityExit(sender, player, canceled);
|
GetScript()->OnRequestActivityExit(sender, player, canceled);
|
||||||
}
|
}
|
||||||
@@ -1518,7 +1565,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
|
|||||||
|
|
||||||
m_DieCallbacks.clear();
|
m_DieCallbacks.clear();
|
||||||
|
|
||||||
//OMAI WA MOU, SHINDERIU
|
//お前はもう死んでいる
|
||||||
|
|
||||||
GetScript()->OnDie(this, murderer);
|
GetScript()->OnDie(this, murderer);
|
||||||
|
|
||||||
@@ -2153,7 +2200,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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,13 @@
|
|||||||
#include "eKillType.h"
|
#include "eKillType.h"
|
||||||
#include "Observable.h"
|
#include "Observable.h"
|
||||||
|
|
||||||
|
namespace GameMessages {
|
||||||
|
struct ActivityNotify;
|
||||||
|
struct ShootingGalleryFire;
|
||||||
|
struct ChildLoaded;
|
||||||
|
struct PlayerResurrectionFinished;
|
||||||
|
};
|
||||||
|
|
||||||
namespace Loot {
|
namespace Loot {
|
||||||
class Info;
|
class Info;
|
||||||
};
|
};
|
||||||
@@ -107,6 +114,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
|
||||||
*/
|
*/
|
||||||
@@ -205,6 +217,10 @@ public:
|
|||||||
void OnZonePropertyModelRemoved(Entity* player);
|
void OnZonePropertyModelRemoved(Entity* player);
|
||||||
void OnZonePropertyModelRemovedWhileEquipped(Entity* player);
|
void OnZonePropertyModelRemovedWhileEquipped(Entity* player);
|
||||||
void OnZonePropertyModelRotated(Entity* player);
|
void OnZonePropertyModelRotated(Entity* player);
|
||||||
|
void OnActivityNotify(GameMessages::ActivityNotify& notify);
|
||||||
|
void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify);
|
||||||
|
void OnChildLoaded(GameMessages::ChildLoaded& childLoaded);
|
||||||
|
void NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg);
|
||||||
|
|
||||||
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
|
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
|
||||||
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
|
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
|
||||||
@@ -358,6 +374,9 @@ protected:
|
|||||||
* Collision
|
* Collision
|
||||||
*/
|
*/
|
||||||
std::vector<LWOOBJID> m_TargetsInPhantom;
|
std::vector<LWOOBJID> m_TargetsInPhantom;
|
||||||
|
|
||||||
|
// objectID of receiver and map of notification name to script
|
||||||
|
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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));
|
||||||
// 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;
|
||||||
switch (leaderboardType) {
|
|
||||||
case Type::Racing:
|
int32_t index = 0;
|
||||||
case Type::MonumentRace:
|
// for friends and top, we dont need to find this players index.
|
||||||
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC";
|
if (infoType == Leaderboard::InfoType::MyStanding || infoType == Leaderboard::InfoType::Friends) {
|
||||||
case Type::Survival:
|
for (; index < leaderboard.size(); index++) {
|
||||||
return Game::config->GetValue("classic_survival_scoring") == "1" ?
|
if (leaderboard[index].charId == relatedPlayer) break;
|
||||||
"secondaryScore DESC, primaryScore DESC, tertiaryScore DESC" :
|
}
|
||||||
"primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
|
|
||||||
case Type::SurvivalNS:
|
|
||||||
return "primaryScore DESC, secondaryScore ASC, tertiaryScore DESC";
|
|
||||||
case Type::ShootingGallery:
|
|
||||||
case Type::FootRace:
|
|
||||||
case Type::UnusedLeaderboard4:
|
|
||||||
case Type::Donations:
|
|
||||||
case Type::None:
|
|
||||||
default:
|
|
||||||
return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) {
|
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
|
||||||
resultStart++;
|
// Filter the leaderboard to only include entries from the last week
|
||||||
resultEnd++;
|
const auto currentTime = std::chrono::system_clock::now();
|
||||||
// We need everything except 1 column so i'm selecting * from leaderboard
|
auto epochTime = currentTime.time_since_epoch().count();
|
||||||
const std::string queryBase =
|
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
|
||||||
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 =
|
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
|
||||||
R"QUERY(
|
for (const auto& entry : leaderboard) {
|
||||||
AND (
|
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
|
||||||
character_id IN (
|
weeklyLeaderboard.push_back(entry);
|
||||||
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.
|
return weeklyLeaderboard;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id");
|
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
|
||||||
// Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow
|
auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer);
|
||||||
constexpr uint16_t STRING_LENGTH = 4096;
|
std::vector<ILeaderboard::Entry> friendsLeaderboard;
|
||||||
std::unique_ptr<char[]> lookupBuffer = std::make_unique<char[]>(STRING_LENGTH);
|
for (const auto& entry : leaderboard) {
|
||||||
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd);
|
const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
|
||||||
DluAssert(res != -1);
|
return entry.charId == data.friendID;
|
||||||
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);
|
if (res != friendOfPlayer.cend() || entry.charId == relatedPlayer) {
|
||||||
query->setInt(1, this->gameID);
|
friendsLeaderboard.push_back(entry);
|
||||||
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);
|
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) {
|
||||||
|
case Type::SurvivalNS:
|
||||||
|
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:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Type::FootRace:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Type::Donations:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Type::None:
|
||||||
|
[[fallthrough]];
|
||||||
|
default:
|
||||||
|
leaderboardRes = Database::Get()->GetDescendingLeaderboard(gameID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
|
||||||
|
|
||||||
|
QueryToLdf(*this, processedLeaderboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -79,7 +43,7 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Resets the leaderboard state and frees its allocated memory
|
* @brief Resets the leaderboard state and frees its allocated memory
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
#include "CDPhysicsComponentTable.h"
|
#include "CDPhysicsComponentTable.h"
|
||||||
#include "dNavMesh.h"
|
#include "dNavMesh.h"
|
||||||
|
|
||||||
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) {
|
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) : Component(parent) {
|
||||||
m_Target = LWOOBJID_EMPTY;
|
m_Target = LWOOBJID_EMPTY;
|
||||||
m_DirtyStateOrTarget = true;
|
m_DirtyStateOrTarget = true;
|
||||||
m_State = AiState::spawn;
|
m_State = AiState::spawn;
|
||||||
@@ -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"
|
||||||
@@ -68,9 +69,10 @@ InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
|||||||
auto slot = 0u;
|
auto slot = 0u;
|
||||||
|
|
||||||
for (const auto& item : items) {
|
for (const auto& item : items) {
|
||||||
if (!item.equip || !Inventory::IsValidItem(item.itemid)) {
|
if (!Inventory::IsValidItem(item.itemid)) continue;
|
||||||
continue;
|
AddItem(item.itemid, item.count);
|
||||||
}
|
|
||||||
|
if (!item.equip) continue;
|
||||||
|
|
||||||
const LWOOBJID id = ObjectIDManager::GenerateObjectID();
|
const LWOOBJID id = ObjectIDManager::GenerateObjectID();
|
||||||
|
|
||||||
@@ -829,6 +831,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 +1167,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:
|
||||||
|
|||||||
@@ -265,6 +265,7 @@ void MovementAIComponent::PullToPoint(const NiPoint3& point) {
|
|||||||
|
|
||||||
void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
|
void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
|
||||||
if (path.empty()) return;
|
if (path.empty()) return;
|
||||||
|
while (!m_CurrentPath.empty()) m_CurrentPath.pop();
|
||||||
std::for_each(path.rbegin(), path.rend() - 1, [this](const PathWaypoint& point) {
|
std::for_each(path.rbegin(), path.rend() - 1, [this](const PathWaypoint& point) {
|
||||||
this->m_CurrentPath.push(point);
|
this->m_CurrentPath.push(point);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
|
|||||||
|
|
||||||
GameMessages::SendRegisterPetDBID(m_Tamer, petSubKey, tamer->GetSystemAddress());
|
GameMessages::SendRegisterPetDBID(m_Tamer, petSubKey, tamer->GetSystemAddress());
|
||||||
|
|
||||||
inventoryComponent->AddItem(m_Parent->GetLOT(), 1, eLootSourceType::ACTIVITY, eInventoryType::MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey);
|
inventoryComponent->AddItem(m_Parent->GetLOT(), 1, eLootSourceType::INVENTORY, eInventoryType::MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey);
|
||||||
auto* item = inventoryComponent->FindItemBySubKey(petSubKey, MODELS);
|
auto* item = inventoryComponent->FindItemBySubKey(petSubKey, MODELS);
|
||||||
|
|
||||||
if (item == nullptr) {
|
if (item == nullptr) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -73,7 +84,12 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
|
|||||||
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
|
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
|
||||||
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
|
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
|
||||||
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
|
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
|
||||||
} else {
|
// Leaving these out for now since they cause more issues than they solve in racing tracks without proper OBB checks.
|
||||||
|
} /* else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") {
|
||||||
|
toReturn = new dpEntity(m_Parent->GetObjectID(), 112.416870f, 50.363434f, 87.679268f);
|
||||||
|
} else if (info->physicsAsset == "env\\GFTrack_DeathVolume2_RoadGaps.hkx") {
|
||||||
|
toReturn = new dpEntity(m_Parent->GetObjectID(), 48.386536f, 50.363434f, 259.361755f);
|
||||||
|
} */ else {
|
||||||
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
|
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
|
||||||
|
|
||||||
//add fallback cube:
|
//add fallback cube:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -65,14 +65,7 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
|
|||||||
|
|
||||||
outBitStream.Write(false);
|
outBitStream.Write(false);
|
||||||
}
|
}
|
||||||
// If build state is completed and we've already serialized once in the completed state,
|
|
||||||
// don't serializing this component anymore as this will cause the build to jump again.
|
|
||||||
// If state changes, serialization will begin again.
|
|
||||||
if (!m_StateDirty && m_State == eQuickBuildState::COMPLETED) {
|
|
||||||
outBitStream.Write0();
|
|
||||||
outBitStream.Write0();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// BEGIN Scripted Activity
|
// BEGIN Scripted Activity
|
||||||
outBitStream.Write1();
|
outBitStream.Write1();
|
||||||
|
|
||||||
@@ -90,36 +83,27 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
|
|||||||
}
|
}
|
||||||
// END Scripted Activity
|
// END Scripted Activity
|
||||||
|
|
||||||
outBitStream.Write1();
|
outBitStream.Write(m_StateDirty || bIsInitialUpdate);
|
||||||
|
if (m_StateDirty || bIsInitialUpdate) {
|
||||||
|
outBitStream.Write(m_State);
|
||||||
|
|
||||||
outBitStream.Write(m_State);
|
outBitStream.Write(m_ShowResetEffect);
|
||||||
|
outBitStream.Write(m_Activator != nullptr);
|
||||||
|
|
||||||
outBitStream.Write(m_ShowResetEffect);
|
outBitStream.Write(m_Timer);
|
||||||
outBitStream.Write(m_Activator != nullptr);
|
outBitStream.Write(m_TimerIncomplete);
|
||||||
|
|
||||||
outBitStream.Write(m_Timer);
|
if (bIsInitialUpdate) {
|
||||||
outBitStream.Write(m_TimerIncomplete);
|
outBitStream.Write(false); // IsChoiceBuild
|
||||||
|
outBitStream.Write(m_ActivatorPosition);
|
||||||
if (bIsInitialUpdate) {
|
outBitStream.Write(m_RepositionPlayer);
|
||||||
outBitStream.Write(false);
|
}
|
||||||
outBitStream.Write(m_ActivatorPosition);
|
m_StateDirty = false;
|
||||||
outBitStream.Write(m_RepositionPlayer);
|
|
||||||
}
|
}
|
||||||
m_StateDirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickBuildComponent::Update(float deltaTime) {
|
void QuickBuildComponent::Update(float deltaTime) {
|
||||||
m_Activator = GetActivator();
|
SetActivator(GetActivator());
|
||||||
|
|
||||||
// Serialize the quickbuild every so often, fixes the odd bug where the quickbuild is not buildable
|
|
||||||
/*if (m_SoftTimer > 0.0f) {
|
|
||||||
m_SoftTimer -= deltaTime;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_SoftTimer = 5.0f;
|
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
switch (m_State) {
|
switch (m_State) {
|
||||||
case eQuickBuildState::OPEN: {
|
case eQuickBuildState::OPEN: {
|
||||||
@@ -130,12 +114,12 @@ void QuickBuildComponent::Update(float deltaTime) {
|
|||||||
const bool isSmashGroup = spawner != nullptr ? spawner->GetIsSpawnSmashGroup() : false;
|
const bool isSmashGroup = spawner != nullptr ? spawner->GetIsSpawnSmashGroup() : false;
|
||||||
|
|
||||||
if (isSmashGroup) {
|
if (isSmashGroup) {
|
||||||
m_TimerIncomplete += deltaTime;
|
ModifyIncompleteTimer(deltaTime);
|
||||||
|
|
||||||
// For reset times < 0 this has to be handled manually
|
// For reset times < 0 this has to be handled manually
|
||||||
if (m_TimeBeforeSmash > 0) {
|
if (m_TimeBeforeSmash > 0) {
|
||||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
|
||||||
m_ShowResetEffect = true;
|
SetShowResetEffect(true);
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
@@ -153,21 +137,19 @@ void QuickBuildComponent::Update(float deltaTime) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eQuickBuildState::COMPLETED: {
|
case eQuickBuildState::COMPLETED: {
|
||||||
m_Timer += deltaTime;
|
ModifyTimer(deltaTime);
|
||||||
|
|
||||||
// For reset times < 0 this has to be handled manually
|
// For reset times < 0 this has to be handled manually
|
||||||
if (m_ResetTime > 0) {
|
if (m_ResetTime > 0) {
|
||||||
if (m_Timer >= m_ResetTime - 4.0f) {
|
if (m_Timer >= m_ResetTime - 4.0f && !m_ShowResetEffect) {
|
||||||
if (!m_ShowResetEffect) {
|
SetShowResetEffect(true);
|
||||||
m_ShowResetEffect = true;
|
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Timer >= m_ResetTime) {
|
if (m_Timer >= m_ResetTime) {
|
||||||
|
|
||||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
|
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 7.0f, false, true);
|
||||||
|
|
||||||
ResetQuickBuild(false);
|
ResetQuickBuild(false);
|
||||||
}
|
}
|
||||||
@@ -185,9 +167,9 @@ void QuickBuildComponent::Update(float deltaTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_TimeBeforeDrain -= deltaTime;
|
m_TimeBeforeDrain -= deltaTime;
|
||||||
m_Timer += deltaTime;
|
ModifyTimer(deltaTime);
|
||||||
m_TimerIncomplete = 0;
|
SetIncompleteTimer(0.0f);
|
||||||
m_ShowResetEffect = false;
|
SetShowResetEffect(false);
|
||||||
|
|
||||||
if (m_TimeBeforeDrain <= 0.0f) {
|
if (m_TimeBeforeDrain <= 0.0f) {
|
||||||
m_TimeBeforeDrain = m_CompleteTime / static_cast<float>(m_TakeImagination);
|
m_TimeBeforeDrain = m_CompleteTime / static_cast<float>(m_TakeImagination);
|
||||||
@@ -215,12 +197,12 @@ void QuickBuildComponent::Update(float deltaTime) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eQuickBuildState::INCOMPLETE: {
|
case eQuickBuildState::INCOMPLETE: {
|
||||||
m_TimerIncomplete += deltaTime;
|
ModifyIncompleteTimer(deltaTime);
|
||||||
|
|
||||||
// For reset times < 0 this has to be handled manually
|
// For reset times < 0 this has to be handled manually
|
||||||
if (m_TimeBeforeSmash > 0) {
|
if (m_TimeBeforeSmash > 0) {
|
||||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
|
||||||
m_ShowResetEffect = true;
|
SetShowResetEffect(true);
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
@@ -260,7 +242,7 @@ void QuickBuildComponent::SpawnActivator() {
|
|||||||
info.spawnerID = m_Parent->GetObjectID();
|
info.spawnerID = m_Parent->GetObjectID();
|
||||||
info.pos = m_ActivatorPosition == NiPoint3Constant::ZERO ? m_Parent->GetPosition() : m_ActivatorPosition;
|
info.pos = m_ActivatorPosition == NiPoint3Constant::ZERO ? m_Parent->GetPosition() : m_ActivatorPosition;
|
||||||
|
|
||||||
m_Activator = Game::entityManager->CreateEntity(info, nullptr, m_Parent);
|
SetActivator(Game::entityManager->CreateEntity(info, nullptr, m_Parent));
|
||||||
if (m_Activator) {
|
if (m_Activator) {
|
||||||
m_ActivatorId = m_Activator->GetObjectID();
|
m_ActivatorId = m_Activator->GetObjectID();
|
||||||
Game::entityManager->ConstructEntity(m_Activator);
|
Game::entityManager->ConstructEntity(m_Activator);
|
||||||
@@ -277,7 +259,7 @@ void QuickBuildComponent::DespawnActivator() {
|
|||||||
|
|
||||||
m_Activator->ScheduleKillAfterUpdate();
|
m_Activator->ScheduleKillAfterUpdate();
|
||||||
|
|
||||||
m_Activator = nullptr;
|
SetActivator(nullptr);
|
||||||
|
|
||||||
m_ActivatorId = LWOOBJID_EMPTY;
|
m_ActivatorId = LWOOBJID_EMPTY;
|
||||||
}
|
}
|
||||||
@@ -405,8 +387,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) {
|
|||||||
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::BUILDING, user->GetObjectID());
|
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::BUILDING, user->GetObjectID());
|
||||||
GameMessages::SendEnableQuickBuild(m_Parent, true, false, false, eQuickBuildFailReason::NOT_GIVEN, 0.0f, user->GetObjectID());
|
GameMessages::SendEnableQuickBuild(m_Parent, true, false, false, eQuickBuildFailReason::NOT_GIVEN, 0.0f, user->GetObjectID());
|
||||||
|
|
||||||
m_State = eQuickBuildState::BUILDING;
|
SetState(eQuickBuildState::BUILDING);
|
||||||
m_StateDirty = true;
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
|
|
||||||
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
|
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
|
||||||
@@ -444,9 +425,8 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
|
|||||||
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||||
|
|
||||||
|
|
||||||
m_State = eQuickBuildState::COMPLETED;
|
SetState(eQuickBuildState::COMPLETED);
|
||||||
m_StateDirty = true;
|
SetTimer(0.0f);
|
||||||
m_Timer = 0.0f;
|
|
||||||
m_DrainedImagination = 0;
|
m_DrainedImagination = 0;
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
@@ -526,11 +506,10 @@ void QuickBuildComponent::ResetQuickBuild(const bool failed) {
|
|||||||
|
|
||||||
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::RESETTING, LWOOBJID_EMPTY);
|
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::RESETTING, LWOOBJID_EMPTY);
|
||||||
|
|
||||||
m_State = eQuickBuildState::RESETTING;
|
SetState(eQuickBuildState::RESETTING);
|
||||||
m_StateDirty = true;
|
SetTimer(0.0f);
|
||||||
m_Timer = 0.0f;
|
SetIncompleteTimer(0.0f);
|
||||||
m_TimerIncomplete = 0.0f;
|
SetShowResetEffect(false);
|
||||||
m_ShowResetEffect = false;
|
|
||||||
m_DrainedImagination = 0;
|
m_DrainedImagination = 0;
|
||||||
|
|
||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
@@ -563,8 +542,7 @@ void QuickBuildComponent::CancelQuickBuild(Entity* const entity, const eQuickBui
|
|||||||
GameMessages::SendTerminateInteraction(m_Parent->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
GameMessages::SendTerminateInteraction(m_Parent->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||||
|
|
||||||
// Now update the component itself
|
// Now update the component itself
|
||||||
m_State = eQuickBuildState::INCOMPLETE;
|
SetState(eQuickBuildState::INCOMPLETE);
|
||||||
m_StateDirty = true;
|
|
||||||
|
|
||||||
// Notify scripts and possible subscribers
|
// Notify scripts and possible subscribers
|
||||||
m_Parent->GetScript()->OnQuickBuildNotifyState(m_Parent, m_State);
|
m_Parent->GetScript()->OnQuickBuildNotifyState(m_Parent, m_State);
|
||||||
|
|||||||
@@ -218,6 +218,48 @@ public:
|
|||||||
* @param skipChecks whether or not to skip the check for the quickbuild not being completed
|
* @param skipChecks whether or not to skip the check for the quickbuild not being completed
|
||||||
*/
|
*/
|
||||||
void CancelQuickBuild(Entity* const builder, const eQuickBuildFailReason failReason, const bool skipChecks = false);
|
void CancelQuickBuild(Entity* const builder, const eQuickBuildFailReason failReason, const bool skipChecks = false);
|
||||||
|
|
||||||
|
void SetState(const eQuickBuildState state) {
|
||||||
|
if (m_State == state) return;
|
||||||
|
m_State = state;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetShowResetEffect(const bool value) {
|
||||||
|
if (m_ShowResetEffect == value) return;
|
||||||
|
m_ShowResetEffect = value;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetActivator(Entity* const activator) {
|
||||||
|
if (m_Activator == activator) return;
|
||||||
|
m_Activator = activator;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTimer(const float value) {
|
||||||
|
if (m_Timer == value) return;
|
||||||
|
m_Timer = value;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModifyTimer(const float value) {
|
||||||
|
if (value == 0.0f) return;
|
||||||
|
m_Timer += value;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetIncompleteTimer(const float value) {
|
||||||
|
if (m_TimerIncomplete == value) return;
|
||||||
|
m_TimerIncomplete = value;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModifyIncompleteTimer(const float value) {
|
||||||
|
if (value == 0.0f) return;
|
||||||
|
m_TimerIncomplete += value;
|
||||||
|
m_StateDirty = true;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Whether or not the quickbuild state has been changed since we last serialized it.
|
* Whether or not the quickbuild state has been changed since we last serialized it.
|
||||||
|
|||||||
@@ -35,7 +35,8 @@
|
|||||||
RacingControlComponent::RacingControlComponent(Entity* parent)
|
RacingControlComponent::RacingControlComponent(Entity* parent)
|
||||||
: Component(parent) {
|
: Component(parent) {
|
||||||
m_PathName = u"MainPath";
|
m_PathName = u"MainPath";
|
||||||
m_RemainingLaps = 3;
|
m_NumberOfLaps = 3;
|
||||||
|
m_RemainingLaps = m_NumberOfLaps;
|
||||||
m_LeadingPlayer = LWOOBJID_EMPTY;
|
m_LeadingPlayer = LWOOBJID_EMPTY;
|
||||||
m_RaceBestTime = 0;
|
m_RaceBestTime = 0;
|
||||||
m_RaceBestLap = 0;
|
m_RaceBestLap = 0;
|
||||||
@@ -284,7 +285,7 @@ void RacingControlComponent::OnRacingClientReady(Entity* player) {
|
|||||||
Game::entityManager->SerializeEntity(m_Parent);
|
Game::entityManager->SerializeEntity(m_Parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RacingControlComponent::OnRequestDie(Entity* player) {
|
void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& deathType) {
|
||||||
// Sent by the client when they collide with something which should smash
|
// Sent by the client when they collide with something which should smash
|
||||||
// them.
|
// them.
|
||||||
|
|
||||||
@@ -300,8 +301,9 @@ void RacingControlComponent::OnRequestDie(Entity* player) {
|
|||||||
|
|
||||||
if (!racingPlayer.noSmashOnReload) {
|
if (!racingPlayer.noSmashOnReload) {
|
||||||
racingPlayer.smashedTimes++;
|
racingPlayer.smashedTimes++;
|
||||||
|
LOG("Death type %s", GeneralUtils::UTF16ToWTF8(deathType).c_str());
|
||||||
GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true,
|
GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true,
|
||||||
eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0);
|
eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0);
|
||||||
|
|
||||||
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
|
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
|
||||||
uint32_t respawnImagination = 0;
|
uint32_t respawnImagination = 0;
|
||||||
@@ -658,23 +660,9 @@ void RacingControlComponent::Update(float deltaTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn imagination pickups
|
GameMessages::ZoneLoadedInfo zoneLoadInfo{};
|
||||||
auto* minSpawner = Game::zoneManager->GetSpawnersByName(
|
zoneLoadInfo.maxPlayers = m_LoadedPlayers;
|
||||||
"ImaginationSpawn_Min")[0];
|
m_Parent->GetScript()->OnZoneLoadedInfo(m_Parent, zoneLoadInfo);
|
||||||
auto* medSpawner = Game::zoneManager->GetSpawnersByName(
|
|
||||||
"ImaginationSpawn_Med")[0];
|
|
||||||
auto* maxSpawner = Game::zoneManager->GetSpawnersByName(
|
|
||||||
"ImaginationSpawn_Max")[0];
|
|
||||||
|
|
||||||
minSpawner->Activate();
|
|
||||||
|
|
||||||
if (m_LoadedPlayers > 2) {
|
|
||||||
medSpawner->Activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LoadedPlayers > 4) {
|
|
||||||
maxSpawner->Activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset players to their start location, without smashing them
|
// Reset players to their start location, without smashing them
|
||||||
for (auto& player : m_RacingPlayers) {
|
for (auto& player : m_RacingPlayers) {
|
||||||
@@ -764,7 +752,7 @@ void RacingControlComponent::Update(float deltaTime) {
|
|||||||
// new checkpoint
|
// new checkpoint
|
||||||
uint32_t respawnIndex = 0;
|
uint32_t respawnIndex = 0;
|
||||||
for (const auto& waypoint : path->pathWaypoints) {
|
for (const auto& waypoint : path->pathWaypoints) {
|
||||||
if (player.lap == 3) {
|
if (player.lap == m_NumberOfLaps) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +823,7 @@ void RacingControlComponent::Update(float deltaTime) {
|
|||||||
// Progress lap time tasks
|
// Progress lap time tasks
|
||||||
missionComponent->Progress(eMissionTaskType::RACING, lapTime.count(), static_cast<LWOOBJID>(eRacingTaskParam::LAP_TIME));
|
missionComponent->Progress(eMissionTaskType::RACING, lapTime.count(), static_cast<LWOOBJID>(eRacingTaskParam::LAP_TIME));
|
||||||
|
|
||||||
if (player.lap == 3) {
|
if (player.lap == m_NumberOfLaps) {
|
||||||
m_Finished++;
|
m_Finished++;
|
||||||
player.finished = m_Finished;
|
player.finished = m_Finished;
|
||||||
|
|
||||||
@@ -882,3 +870,20 @@ void RacingControlComponent::Update(float deltaTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RacingControlComponent::MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg) {
|
||||||
|
for (const auto& dataUnique : msg.racingSettings) {
|
||||||
|
if (!dataUnique) continue;
|
||||||
|
const auto* const data = dataUnique.get();
|
||||||
|
if (data->GetKey() == u"Race_PathName" && data->GetValueType() == LDF_TYPE_UTF_16) {
|
||||||
|
m_PathName = static_cast<const LDFData<std::u16string>*>(data)->GetValue();
|
||||||
|
} else if (data->GetKey() == u"activityID" && data->GetValueType() == LDF_TYPE_S32) {
|
||||||
|
m_ActivityID = static_cast<const LDFData<int32_t>*>(data)->GetValue();
|
||||||
|
} else if (data->GetKey() == u"Number_of_Laps" && data->GetValueType() == LDF_TYPE_S32) {
|
||||||
|
m_NumberOfLaps = static_cast<const LDFData<int32_t>*>(data)->GetValue();
|
||||||
|
m_RemainingLaps = m_NumberOfLaps;
|
||||||
|
} else if (data->GetKey() == u"Minimum_Players_for_Group_Achievements" && data->GetValueType() == LDF_TYPE_S32) {
|
||||||
|
m_MinimumPlayersForGroupAchievements = static_cast<const LDFData<int32_t>*>(data)->GetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Invoked when the client says it should be smashed.
|
* Invoked when the client says it should be smashed.
|
||||||
*/
|
*/
|
||||||
void OnRequestDie(Entity* player);
|
void OnRequestDie(Entity* player, const std::u16string& deathType = u"");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the player has finished respawning.
|
* Invoked when the player has finished respawning.
|
||||||
@@ -152,6 +152,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
RacingPlayerInfo* GetPlayerData(LWOOBJID playerID);
|
RacingPlayerInfo* GetPlayerData(LWOOBJID playerID);
|
||||||
|
|
||||||
|
void MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,11 +163,13 @@ private:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The paths that are followed for the camera scenes
|
* The paths that are followed for the camera scenes
|
||||||
|
* Configurable in the ConfigureRacingControl msg with the key `Race_PathName`.
|
||||||
*/
|
*/
|
||||||
std::u16string m_PathName;
|
std::u16string m_PathName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the activity for participating in this race
|
* The ID of the activity for participating in this race
|
||||||
|
* Configurable in the ConfigureRacingControl msg with the key `activityID`.
|
||||||
*/
|
*/
|
||||||
uint32_t m_ActivityID;
|
uint32_t m_ActivityID;
|
||||||
|
|
||||||
@@ -245,5 +249,20 @@ private:
|
|||||||
* Value for message box response to know if we are exiting the race via the activity dialogue
|
* Value for message box response to know if we are exiting the race via the activity dialogue
|
||||||
*/
|
*/
|
||||||
const int32_t m_ActivityExitConfirm = 1;
|
const int32_t m_ActivityExitConfirm = 1;
|
||||||
|
|
||||||
bool m_AllPlayersReady = false;
|
bool m_AllPlayersReady = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of laps in this race. Configurable in the ConfigureRacingControl msg
|
||||||
|
* with the key `Number_of_Laps`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int32_t m_NumberOfLaps{ 3 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The minimum number of players required to progress group achievements.
|
||||||
|
* Configurable with the ConfigureRacingControl msg with the key `Minimum_Players_for_Group_Achievements`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int32_t m_MinimumPlayersForGroupAchievements{ 2 };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user