mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-17 12:04:27 -06:00
Compare commits
57 Commits
add-sqlite
...
ub-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dbaf6a2bc | ||
|
|
4860410f6f | ||
|
|
0b261e934f | ||
|
|
1b9f7e44c7 | ||
|
|
08a168de88 | ||
|
|
701fc8061b | ||
|
|
0c948a8df6 | ||
|
|
6ed6efa921 | ||
|
|
dcc9e023a6 | ||
|
|
8509ec8856 | ||
|
|
18295017c1 | ||
|
|
e8f011b830 | ||
|
|
a787673baf | ||
|
|
e869c0ad03 | ||
|
|
b2af3fa9d4 | ||
|
|
2560bb00da | ||
|
|
6699081080 | ||
|
|
1ae21c423f | ||
|
|
0ae9eb4a96 | ||
|
|
fced6d753a | ||
|
|
15dc5feeb5 | ||
|
|
a60865cd19 | ||
|
|
77b42daca1 | ||
|
|
ba364800fe | ||
|
|
e1c20192f7 | ||
|
|
0f8c5b436d | ||
| 53242ad5d5 | |||
|
|
a8919c8c14 | ||
|
|
5ff121612e | ||
| 34618607c3 | |||
|
|
02b76adb7a | ||
|
|
3beb414b55 | ||
|
|
1dadeeb36f | ||
|
|
aa7c3b9061 | ||
|
|
1644d9448d | ||
|
|
8b56b0b7ba | ||
|
|
4a1c289fb1 | ||
|
|
32a1e5ece5 | ||
|
|
7fcbb9507b | ||
|
|
730533c690 | ||
|
|
129d9fd0b9 | ||
|
|
4922e2edb1 | ||
|
|
c05c2543a9 | ||
|
|
5599bd946b | ||
|
|
a568e66e41 | ||
|
|
66fa3ff4ba | ||
|
|
6fa719c679 | ||
|
|
7740bbbaab | ||
|
|
8eb3488812 | ||
|
|
ac4fd02a6c | ||
|
|
b799f8967c | ||
|
|
12387ba07d | ||
|
|
051a0ba05e | ||
|
|
5aa8b1395b | ||
|
|
3a0e37ee8a | ||
|
|
0a06f309f6 | ||
|
|
30d4076808 |
10
.env.example
10
.env.example
@@ -3,12 +3,20 @@ CLIENT_PATH=./client
|
||||
# Updates NET_VERSION in CMakeVariables.txt
|
||||
NET_VERSION=171022
|
||||
# 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=
|
||||
# Should be the externally facing IP of your server host
|
||||
EXTERNAL_IP=localhost
|
||||
|
||||
# The database type that will be used.
|
||||
# Acceptable values are `sqlite`, `mysql`, `mariadb`, `maria`.
|
||||
# Case insensitive.
|
||||
DATABASE_TYPE=mariadb
|
||||
SQLITE_DATABASE_PATH=resServer/dlu.sqlite
|
||||
|
||||
# Database values
|
||||
# Be careful with special characters here. It is more safe to use normal characters and/or numbers.
|
||||
MARIADB_USER=darkflame
|
||||
MARIADB_PASSWORD=
|
||||
MARIADB_DATABASE=darkflame
|
||||
SKIP_ACCOUNT_CREATION=1
|
||||
|
||||
1
.github/workflows/build-and-test.yml
vendored
1
.github/workflows/build-and-test.yml
vendored
@@ -43,6 +43,7 @@ jobs:
|
||||
build/*/*.ini
|
||||
build/*/*.so
|
||||
build/*/*.dll
|
||||
build/*/*.dylib
|
||||
build/*/vanity/
|
||||
build/*/navmeshes/
|
||||
build/*/migrations/
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,11 +2,11 @@ temp/
|
||||
cmake-build-debug/
|
||||
RelWithDebInfo/
|
||||
docker/configs
|
||||
valgrind-out.txt
|
||||
|
||||
# Third party libraries
|
||||
thirdparty/mysql/
|
||||
thirdparty/mysql_linux/
|
||||
CMakeVariables.txt
|
||||
|
||||
# Build folders
|
||||
build/
|
||||
@@ -95,6 +95,7 @@ ipch/
|
||||
|
||||
# Exceptions:
|
||||
CMakeSettings.json
|
||||
CMakeUserPresets.json
|
||||
*.vcxproj
|
||||
*.filters
|
||||
*.cmake
|
||||
|
||||
@@ -4,6 +4,18 @@ project(Darkflame
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
# Sanitizer flags - TODO: Make CMake preset before finalizing PR
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
add_compile_options("-fsanitize=undefined")
|
||||
add_link_options("-fsanitize=undefined")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_link_options("-static-libsan")
|
||||
else()
|
||||
add_link_options("-static-libasan")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# check if the path to the source directory contains a space
|
||||
if("${CMAKE_SOURCE_DIR}" MATCHES " ")
|
||||
message(FATAL_ERROR "The server cannot build in the path (" ${CMAKE_SOURCE_DIR} ") because it contains a space. Please move the server to a path without spaces.")
|
||||
@@ -17,7 +29,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN OFF) # Set C and C++ symbol visibility to hide inlined functions
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
# Read variables from file
|
||||
@@ -66,6 +78,7 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
# Disabled no-register
|
||||
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
|
||||
if(UNIX)
|
||||
add_link_options("-Wl,-rpath,$ORIGIN/")
|
||||
add_compile_options("-fPIC")
|
||||
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
|
||||
|
||||
@@ -174,16 +187,18 @@ foreach(resource_file ${RESOURCE_FILES})
|
||||
list(GET line_split 0 variable_name)
|
||||
|
||||
if(NOT ${parsed_current_file_contents} MATCHES ${variable_name})
|
||||
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
|
||||
set(line_to_add ${line_to_add} ${line})
|
||||
# For backwards compatibility with older setup versions, dont add this option.
|
||||
if(NOT ${variable_name} MATCHES "database_type")
|
||||
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
|
||||
set(line_to_add ${line_to_add} ${line})
|
||||
|
||||
foreach(line_to_append ${line_to_add})
|
||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append})
|
||||
endforeach()
|
||||
foreach(line_to_append ${line_to_add})
|
||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append})
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n")
|
||||
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(line_to_add "")
|
||||
else()
|
||||
set(line_to_add ${line_to_add} ${line})
|
||||
@@ -213,21 +228,8 @@ foreach(file ${VANITY_FILES})
|
||||
endforeach()
|
||||
|
||||
# Move our migrations for MasterServer to run
|
||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/)
|
||||
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
|
||||
|
||||
foreach(file ${SQL_FILES})
|
||||
get_filename_component(file ${file} NAME)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file})
|
||||
endforeach()
|
||||
|
||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/cdserver/)
|
||||
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/cdserver/*.sql)
|
||||
|
||||
foreach(file ${SQL_FILES})
|
||||
get_filename_component(file ${file} NAME)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file})
|
||||
endforeach()
|
||||
file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/migrations)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/migrations DESTINATION ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
|
||||
if (APPLE)
|
||||
@@ -272,7 +274,7 @@ if(MSVC)
|
||||
# add_compile_options("/W4")
|
||||
# Want to enable warnings eventually, but WAY too much noise right now
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
add_compile_options("-Wuninitialized" "-Wold-style-cast")
|
||||
add_compile_options("-Wuninitialized" "-Wold-style-cast" "-Wstrict-aliasing=01")
|
||||
else()
|
||||
message(WARNING "Unknown compiler: '${CMAKE_CXX_COMPILER_ID}' - No warning flags enabled.")
|
||||
endif()
|
||||
@@ -312,7 +314,7 @@ add_subdirectory(dPhysics)
|
||||
add_subdirectory(dServer)
|
||||
|
||||
# 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
|
||||
if(UNIX)
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"displayName": "Default configure step",
|
||||
"description": "Use 'build' dir and Unix makefiles",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"environment": {
|
||||
"DLU_CONFIG_DIR": "${sourceDir}/build"
|
||||
},
|
||||
"generator": "Unix Makefiles"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PROJECT_VERSION_MAJOR=2
|
||||
PROJECT_VERSION_MINOR=3
|
||||
PROJECT_VERSION_MAJOR=3
|
||||
PROJECT_VERSION_MINOR=0
|
||||
PROJECT_VERSION_PATCH=0
|
||||
|
||||
# Debugging
|
||||
|
||||
39
README.md
39
README.md
@@ -13,21 +13,33 @@ Darkflame Universe is licensed under AGPLv3, please read [LICENSE](LICENSE). Som
|
||||
* You must disclose any changes you make to the code when you distribute it
|
||||
* Hosting a server for others counts as distribution
|
||||
|
||||
## Disclaimers
|
||||
### Setup difficulty
|
||||
Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous.
|
||||
|
||||
### Hosting a server
|
||||
We do not recommend hosting public servers. Darkflame Universe is intended for small scale deployment, for example within a group of friends. It has not been tested for large scale deployment which comes with additional security risks.
|
||||
|
||||
### Supply of resource files
|
||||
Darkflame Universe is a server emulator and does not distribute any LEGO® Universe files. A separate game client is required to setup this server emulator and play the game, which we cannot supply. Users are strongly suggested to refer to the safe checksums listed [here](#verifying-your-client-files) to see if a client will work.
|
||||
|
||||
## Step by step walkthrough for a single-player server
|
||||
If you would like a setup for a single player server only on a Windows machine, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README.
|
||||
## Setting up a single player server
|
||||
* If you don't know what WSL is, skip this warning.
|
||||
Warning: WSL version 1 does NOT support using sqlite as a database due to how it handles filesystem synchronization.
|
||||
You must use Version 2 if you must run the server under WSL. Not doing so will result in save data loss.
|
||||
* Single player installs now no longer require building the server from source or installing development tools.
|
||||
* Download the [latest windows release](https://github.com/DarkflameUniverse/DarkflameServer/releases) (or whichever release you need) and extract the files into a folder inside your client. Note that this setup is expecting that when double clicking the folder that you put in the same folder as `legouniverse.exe`, the file `MasterServer.exe` is in there.
|
||||
* You should be able to see the folder with the server files in the same folder as `legouniverse.exe`.
|
||||
* Go into the server files folder and open `sharedconfig.ini`. Find the line that says `client_location` and put `..` after it so the line reads `client_location=..`.
|
||||
* To run the server, double-click `MasterServer.exe`.
|
||||
* You will be asked to create an account the first time you run the server. After you have created the account, the server will shutdown and need to be restarted.
|
||||
* To connect to the server, either delete the file `boot.cfg` which is found in your LEGO Universe client, rename the file `boot.cfg` to something else or follow the steps [here](#allowing-a-user-to-connect-to-your-server) if you wish to keep the file.
|
||||
* When shutting down the server, it is highly recommended to click the `MasterServer.exe` window and hold `ctrl` while pressing `c` to stop the server.
|
||||
* We are working on a way to make it so when you close the game, the server stops automatically alongside when you open the game, the server starts automatically.
|
||||
|
||||
## Steps to setup server
|
||||
<font size="32">**If you are not planning on hosting a server for others, working in the codebase or wanting to use MariaDB for a database, you can stop reading here.**</font>
|
||||
|
||||
If you would like to use a MariaDB as a database instead of the default of sqlite, follow the steps [here](#database-setup).
|
||||
|
||||
# Steps to setup a development environment
|
||||
* [Clone this repository](#clone-the-repository)
|
||||
* [Setting up a development environment](#setting-up-a-development-environment)
|
||||
* [Install dependencies](#install-dependencies)
|
||||
* [Database setup](#database-setup)
|
||||
* [Build the server](#build-the-server)
|
||||
@@ -39,6 +51,13 @@ If you would like a setup for a single player server only on a Windows machine,
|
||||
* [User Guide](#user-guide)
|
||||
* [Docker](#docker)
|
||||
|
||||
## Disclaimers
|
||||
### Setup difficulty
|
||||
Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous.
|
||||
|
||||
## Step by step walkthrough for building a single-player Windows server from source
|
||||
If you would like a setup for a single player server only on a Windows machine built from source, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README.
|
||||
|
||||
## Clone the repository
|
||||
If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win)
|
||||
|
||||
@@ -266,8 +285,8 @@ systemctl stop darkflame.service
|
||||
journalctl -xeu darkflame.service
|
||||
```
|
||||
|
||||
### First admin user
|
||||
Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users!
|
||||
### First user or adding more users.
|
||||
The first time you run `MasterServer`, you will be prompted to create an account. To create more accounts from the command line, `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users!
|
||||
|
||||
### Account management tool (Nexus Dashboard)
|
||||
**If you are just using this server for yourself, you can skip setting up Nexus Dashboard**
|
||||
@@ -371,7 +390,7 @@ at once. For that:
|
||||
- Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env`
|
||||
- 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.
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Try and find a clang-16 install, falling back to a generic clang install otherwise
|
||||
find_program(CLANG_C_COMPILER clang-16 | clang REQUIRED)
|
||||
find_program(CLANG_CXX_COMPILER clang++-16 | clang++ REQUIRED)
|
||||
find_program(CLANG_CXX_LINKER lld REQUIRED)
|
||||
|
||||
# Debug messages
|
||||
message(DEBUG "CLANG_C_COMPILER = ${CLANG_C_COMPILER}")
|
||||
|
||||
@@ -60,7 +60,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
try {
|
||||
Database::Connect();
|
||||
} catch (sql::SQLException& ex) {
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||
Database::Destroy("AuthServer");
|
||||
delete Game::server;
|
||||
|
||||
@@ -81,7 +81,7 @@ int main(int argc, char** argv) {
|
||||
//Connect to the MySQL Database
|
||||
try {
|
||||
Database::Connect();
|
||||
} catch (sql::SQLException& ex) {
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef __AMF3__H__
|
||||
#define __AMF3__H__
|
||||
#ifndef AMF3_H
|
||||
#define AMF3_H
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -74,28 +75,15 @@ template <> [[nodiscard]] constexpr eAmf AMFValue<double>::GetValueType() const
|
||||
template <typename ValueType>
|
||||
[[nodiscard]] constexpr eAmf AMFValue<ValueType>::GetValueType() const noexcept { return eAmf::Undefined; }
|
||||
|
||||
// As a string this is much easier to write and read from a BitStream.
|
||||
template <>
|
||||
class AMFValue<const char*> : public AMFBaseValue {
|
||||
public:
|
||||
AMFValue() = default;
|
||||
AMFValue(const char* value) { m_Data = value; }
|
||||
virtual ~AMFValue() override = default;
|
||||
|
||||
[[nodiscard]] constexpr eAmf GetValueType() const noexcept override { return eAmf::String; }
|
||||
|
||||
[[nodiscard]] const std::string& GetValue() const { return m_Data; }
|
||||
void SetValue(const std::string& value) { m_Data = value; }
|
||||
protected:
|
||||
std::string m_Data;
|
||||
};
|
||||
|
||||
using AMFNullValue = AMFValue<std::nullptr_t>;
|
||||
using AMFBoolValue = AMFValue<bool>;
|
||||
using AMFIntValue = AMFValue<int32_t>;
|
||||
using AMFStringValue = AMFValue<std::string>;
|
||||
using AMFDoubleValue = AMFValue<double>;
|
||||
|
||||
// Template deduction guide to ensure string literals deduce
|
||||
AMFValue(const char*) -> AMFValue<std::string>; // AMFStringValue
|
||||
|
||||
/**
|
||||
* The AMFArrayValue object holds 2 types of lists:
|
||||
* An associative list where a key maps to a value
|
||||
@@ -106,7 +94,7 @@ using AMFDoubleValue = AMFValue<double>;
|
||||
*/
|
||||
class AMFArrayValue : public AMFBaseValue {
|
||||
using AMFAssociative =
|
||||
std::unordered_map<std::string, std::unique_ptr<AMFBaseValue>, GeneralUtils::transparent_string_hash, std::equal_to<>>;
|
||||
std::unordered_map<std::string, std::unique_ptr<AMFBaseValue>, GeneralUtils::transparent_string_hash, std::equal_to<void>>;
|
||||
|
||||
using AMFDense = std::vector<std::unique_ptr<AMFBaseValue>>;
|
||||
|
||||
@@ -137,17 +125,20 @@ public:
|
||||
* @return The inserted element if the type matched,
|
||||
* or nullptr if a key existed and was not the same type
|
||||
*/
|
||||
template <typename ValueType>
|
||||
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const std::string_view key, const ValueType value) {
|
||||
template <typename T>
|
||||
[[maybe_unused]] auto Insert(const std::string_view key, const T value) -> std::pair<decltype(AMFValue(value))*, bool> {
|
||||
// This ensures the deduced type matches the AMFValue constructor
|
||||
using AMFValueType = decltype(AMFValue(value));
|
||||
|
||||
const auto element = m_Associative.find(key);
|
||||
AMFValue<ValueType>* val = nullptr;
|
||||
AMFValueType* val = nullptr;
|
||||
bool found = true;
|
||||
if (element == m_Associative.cend()) {
|
||||
auto newVal = std::make_unique<AMFValue<ValueType>>(value);
|
||||
auto newVal = std::make_unique<AMFValueType>(value);
|
||||
val = newVal.get();
|
||||
m_Associative.emplace(key, std::move(newVal));
|
||||
} else {
|
||||
val = dynamic_cast<AMFValue<ValueType>*>(element->second.get());
|
||||
val = dynamic_cast<AMFValueType*>(element->second.get());
|
||||
found = false;
|
||||
}
|
||||
return std::make_pair(val, found);
|
||||
@@ -190,15 +181,18 @@ public:
|
||||
* @return The inserted element, or nullptr if the type did not match
|
||||
* what was at the index.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const size_t index, const ValueType value) {
|
||||
template <typename T>
|
||||
[[maybe_unused]] auto Insert(const size_t index, const T value) -> std::pair<decltype(AMFValue(value))*, bool> {
|
||||
// This ensures the deduced type matches the AMFValue constructor
|
||||
using AMFValueType = decltype(AMFValue(value));
|
||||
|
||||
bool inserted = false;
|
||||
if (index >= m_Dense.size()) {
|
||||
m_Dense.resize(index + 1);
|
||||
m_Dense.at(index) = std::make_unique<AMFValue<ValueType>>(value);
|
||||
m_Dense.at(index) = std::make_unique<AMFValueType>(value);
|
||||
inserted = true;
|
||||
}
|
||||
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(m_Dense.at(index).get()), inserted);
|
||||
return std::make_pair(dynamic_cast<AMFValueType*>(m_Dense.at(index).get()), inserted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,8 +239,8 @@ public:
|
||||
*
|
||||
* @return The inserted pointer, or nullptr should the key already be in use.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
[[maybe_unused]] inline AMFValue<ValueType>* Push(const ValueType value) {
|
||||
template <typename T>
|
||||
[[maybe_unused]] inline auto Push(const T value) -> decltype(AMFValue(value))* {
|
||||
return Insert(m_Dense.size(), value).first;
|
||||
}
|
||||
|
||||
@@ -356,4 +350,4 @@ private:
|
||||
AMFDense m_Dense;
|
||||
};
|
||||
|
||||
#endif //!__AMF3__H__
|
||||
#endif //!AMF3_H
|
||||
|
||||
@@ -10,40 +10,54 @@ void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
|
||||
this->Write(type);
|
||||
switch (type) {
|
||||
case eAmf::Integer: {
|
||||
this->Write<AMFIntValue&>(*static_cast<AMFIntValue*>(&value));
|
||||
this->Write<AMFIntValue&>(static_cast<AMFIntValue&>(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case eAmf::Double: {
|
||||
this->Write<AMFDoubleValue&>(*static_cast<AMFDoubleValue*>(&value));
|
||||
this->Write<AMFDoubleValue&>(static_cast<AMFDoubleValue&>(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case eAmf::String: {
|
||||
this->Write<AMFStringValue&>(*static_cast<AMFStringValue*>(&value));
|
||||
this->Write<AMFStringValue&>(static_cast<AMFStringValue&>(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case eAmf::Array: {
|
||||
this->Write<AMFArrayValue&>(*static_cast<AMFArrayValue*>(&value));
|
||||
this->Write<AMFArrayValue&>(static_cast<AMFArrayValue&>(value));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG("Encountered unwritable AMFType %i!", type);
|
||||
[[fallthrough]];
|
||||
}
|
||||
case eAmf::Undefined:
|
||||
[[fallthrough]];
|
||||
case eAmf::Null:
|
||||
[[fallthrough]];
|
||||
case eAmf::False:
|
||||
[[fallthrough]];
|
||||
case eAmf::True:
|
||||
[[fallthrough]];
|
||||
case eAmf::Date:
|
||||
[[fallthrough]];
|
||||
case eAmf::Object:
|
||||
[[fallthrough]];
|
||||
case eAmf::XML:
|
||||
[[fallthrough]];
|
||||
case eAmf::XMLDoc:
|
||||
[[fallthrough]];
|
||||
case eAmf::ByteArray:
|
||||
[[fallthrough]];
|
||||
case eAmf::VectorInt:
|
||||
[[fallthrough]];
|
||||
case eAmf::VectorUInt:
|
||||
[[fallthrough]];
|
||||
case eAmf::VectorDouble:
|
||||
[[fallthrough]];
|
||||
case eAmf::VectorObject:
|
||||
[[fallthrough]];
|
||||
case eAmf::Dictionary:
|
||||
break;
|
||||
}
|
||||
@@ -145,8 +159,7 @@ void RakNet::BitStream::Write<AMFIntValue&>(AMFIntValue& value) {
|
||||
// Writes an AMFDoubleValue to BitStream
|
||||
template<>
|
||||
void RakNet::BitStream::Write<AMFDoubleValue&>(AMFDoubleValue& value) {
|
||||
double d = value.GetValue();
|
||||
WriteAMFU64(*this, *reinterpret_cast<uint64_t*>(&d));
|
||||
WriteAMFU64(*this, std::bit_cast<uint64_t>(value.GetValue()));
|
||||
}
|
||||
|
||||
// Writes an AMFStringValue to BitStream
|
||||
|
||||
@@ -2,16 +2,25 @@
|
||||
#include <string>
|
||||
|
||||
//For reading null-terminated strings
|
||||
std::string BinaryIO::ReadString(std::istream& instream) {
|
||||
std::string toReturn;
|
||||
char buffer;
|
||||
template<typename StringType>
|
||||
StringType ReadString(std::istream& instream) {
|
||||
StringType toReturn{};
|
||||
typename StringType::value_type buffer{};
|
||||
|
||||
BinaryIO::BinaryRead(instream, buffer);
|
||||
|
||||
while (buffer != 0x00) {
|
||||
toReturn += buffer;
|
||||
BinaryRead(instream, buffer);
|
||||
BinaryIO::BinaryRead(instream, buffer);
|
||||
}
|
||||
|
||||
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::u8string ReadU8String(std::istream& instream);
|
||||
|
||||
inline bool DoesFileExist(const std::string& name) {
|
||||
std::ifstream f(name.c_str());
|
||||
return f.good();
|
||||
|
||||
@@ -123,7 +123,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
|
||||
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
|
||||
LOG("Updated model %i to sd0", model.id);
|
||||
updatedModels++;
|
||||
} catch (sql::SQLException exception) {
|
||||
} catch (std::exception& exception) {
|
||||
LOG("Failed to update model %i. This model should be inspected manually to see why."
|
||||
"The database error is %s", model.id, exception.what());
|
||||
}
|
||||
@@ -146,7 +146,7 @@ void WriteSd0Magic(char* input, uint32_t chunkSize) {
|
||||
input[2] = '0';
|
||||
input[3] = 0x01;
|
||||
input[4] = 0xFF;
|
||||
*reinterpret_cast<uint32_t*>(input + 5) = chunkSize; // Write the integer to the character array
|
||||
std::memcpy(&input[5], &chunkSize, sizeof(uint32_t)); // Write the integer to the character array
|
||||
}
|
||||
|
||||
bool CheckSd0Magic(std::istream& streamToCheck) {
|
||||
|
||||
@@ -37,7 +37,6 @@ target_include_directories(dCommon
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
|
||||
@@ -65,13 +65,14 @@ int64_t FdbToSqlite::Convert::ReadInt64(std::istream& cdClientBuffer) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// cdclient is encoded in latin1
|
||||
std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) {
|
||||
int32_t prevPosition = SeekPointer(cdClientBuffer);
|
||||
|
||||
auto readString = BinaryIO::ReadString(cdClientBuffer);
|
||||
const auto readString = BinaryIO::ReadU8String(cdClientBuffer);
|
||||
|
||||
cdClientBuffer.seekg(prevPosition);
|
||||
return readString;
|
||||
return GeneralUtils::Latin1ToUTF8(readString);
|
||||
}
|
||||
|
||||
int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) {
|
||||
|
||||
@@ -53,9 +53,9 @@ bool static _IsSuffixChar(const uint8_t c) {
|
||||
bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
|
||||
const size_t rem = slice.length();
|
||||
if (slice.empty()) return false;
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front());
|
||||
const char* const bytes = slice.data();
|
||||
if (rem > 0) {
|
||||
const uint8_t first = bytes[0];
|
||||
const uint8_t first = static_cast<uint8_t>(bytes[0]);
|
||||
if (first < 0x80) { // 1 byte character
|
||||
out = static_cast<uint32_t>(first & 0x7F);
|
||||
slice.remove_prefix(1);
|
||||
@@ -64,7 +64,7 @@ bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out
|
||||
// middle byte, not valid at start, fall through
|
||||
} else if (first < 0xE0) { // two byte character
|
||||
if (rem > 1) {
|
||||
const uint8_t second = bytes[1];
|
||||
const uint8_t second = static_cast<uint8_t>(bytes[1]);
|
||||
if (_IsSuffixChar(second)) {
|
||||
out = (static_cast<uint32_t>(first & 0x1F) << 6)
|
||||
+ static_cast<uint32_t>(second & 0x3F);
|
||||
@@ -74,8 +74,8 @@ bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out
|
||||
}
|
||||
} else if (first < 0xF0) { // three byte character
|
||||
if (rem > 2) {
|
||||
const uint8_t second = bytes[1];
|
||||
const uint8_t third = bytes[2];
|
||||
const uint8_t second = static_cast<uint8_t>(bytes[1]);
|
||||
const uint8_t third = static_cast<uint8_t>(bytes[2]);
|
||||
if (_IsSuffixChar(second) && _IsSuffixChar(third)) {
|
||||
out = (static_cast<uint32_t>(first & 0x0F) << 12)
|
||||
+ (static_cast<uint32_t>(second & 0x3F) << 6)
|
||||
@@ -86,9 +86,9 @@ bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out
|
||||
}
|
||||
} else if (first < 0xF8) { // four byte character
|
||||
if (rem > 3) {
|
||||
const uint8_t second = bytes[1];
|
||||
const uint8_t third = bytes[2];
|
||||
const uint8_t fourth = bytes[3];
|
||||
const uint8_t second = static_cast<uint8_t>(bytes[1]);
|
||||
const uint8_t third = static_cast<uint8_t>(bytes[2]);
|
||||
const uint8_t fourth = static_cast<uint8_t>(bytes[3]);
|
||||
if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) {
|
||||
out = (static_cast<uint32_t>(first & 0x07) << 18)
|
||||
+ (static_cast<uint32_t>(second & 0x3F) << 12)
|
||||
@@ -167,6 +167,15 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const s
|
||||
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
|
||||
//! 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) {
|
||||
@@ -175,9 +184,9 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const si
|
||||
ret.reserve(newSize);
|
||||
|
||||
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) {
|
||||
const char16_t next = string[i + 1];
|
||||
const auto next = string[i + 1];
|
||||
if (IsTrailSurrogate(next)) {
|
||||
i += 1;
|
||||
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) {
|
||||
// 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)) {
|
||||
auto filename = t.path().filename().string();
|
||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
filenames.emplace(index, std::move(filename));
|
||||
if (t.is_directory() || t.is_symlink()) continue;
|
||||
auto filename = t.path().filename().string();
|
||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
filenames.emplace(index, std::move(filename));
|
||||
}
|
||||
|
||||
// Now sort the map by the oldest migration.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// C++
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
@@ -51,6 +52,14 @@ namespace GeneralUtils {
|
||||
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
|
||||
/*!
|
||||
\param string The string to convert
|
||||
@@ -137,7 +146,7 @@ namespace GeneralUtils {
|
||||
template <typename... Bases>
|
||||
struct overload : Bases... {
|
||||
using is_transparent = void;
|
||||
using Bases::operator() ... ;
|
||||
using Bases::operator()...;
|
||||
};
|
||||
|
||||
struct char_pointer_hash {
|
||||
@@ -152,6 +161,26 @@ namespace GeneralUtils {
|
||||
char_pointer_hash
|
||||
>;
|
||||
|
||||
/**
|
||||
* A convenience wrapper around std::memcpy that creates a new object
|
||||
* from the value representation of the provided bytes.Use when
|
||||
* std::bit_cast is not applicable.
|
||||
* @warning All restrictions of std::memcpy still apply. Accessing
|
||||
* outside of the source object is undefined behavior.
|
||||
* @param from The source of the value representation
|
||||
* @returns A new object with the given value representation
|
||||
*/
|
||||
template <typename To, typename From>
|
||||
[[nodiscard]]
|
||||
inline To FromBitsUnchecked(const From* from) noexcept
|
||||
requires (std::is_trivially_copyable_v<To>
|
||||
&& std::is_trivially_copyable_v<From>)
|
||||
{
|
||||
To to{};
|
||||
std::memcpy(&to, from, sizeof(To));
|
||||
return to;
|
||||
}
|
||||
|
||||
// Concept constraining to enum types
|
||||
template <typename T>
|
||||
concept Enum = std::is_enum_v<T>;
|
||||
|
||||
@@ -54,6 +54,7 @@ struct AssetStream : std::istream {
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
// NEED TO FIX THIS
|
||||
return reinterpret_cast<AssetMemoryBuffer*>(rdbuf())->m_Success;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -957,6 +957,7 @@ namespace MessageType {
|
||||
MODIFY_PLAYER_ZONE_STATISTIC = 1046,
|
||||
APPLY_EXTERNAL_FORCE = 1049,
|
||||
GET_APPLIED_EXTERNAL_FORCE = 1050,
|
||||
ACTIVITY_NOTIFY = 1051,
|
||||
ITEM_EQUIPPED = 1052,
|
||||
ACTIVITY_STATE_CHANGE_REQUEST = 1053,
|
||||
OVERRIDE_FRICTION = 1054,
|
||||
@@ -1253,6 +1254,7 @@ namespace MessageType {
|
||||
VEHICLE_NOTIFY_HIT_EXPLODER = 1385,
|
||||
CHECK_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1386,
|
||||
REQUEST_NEAREST_ROCKET_LAUNCH_PRE_CONDITIONS = 1387,
|
||||
CONFIGURE_RACING_CONTROL = 1388,
|
||||
CONFIGURE_RACING_CONTROL_CLIENT = 1389,
|
||||
NOTIFY_RACING_CLIENT = 1390,
|
||||
RACING_PLAYER_HACK_CAR = 1391,
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
namespace MessageType {
|
||||
enum class World : uint32_t {
|
||||
VALIDATION = 1, // Session info
|
||||
INVALID = 0,
|
||||
VALIDATION, // Session info
|
||||
CHARACTER_LIST_REQUEST,
|
||||
CHARACTER_CREATE_REQUEST,
|
||||
LOGIN_REQUEST, // Character selected
|
||||
|
||||
@@ -2,6 +2,12 @@ add_subdirectory(CDClientDatabase)
|
||||
add_subdirectory(GameDatabase)
|
||||
|
||||
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_link_libraries(dDatabase
|
||||
PUBLIC dDatabaseCDClient dDatabaseGame)
|
||||
|
||||
@@ -8,6 +8,12 @@ foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES})
|
||||
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(SQLite)
|
||||
|
||||
foreach(file ${DDATABSE_DATABSES_SQLITE_SOURCES})
|
||||
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "SQLite/${file}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(TestSQL)
|
||||
|
||||
foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES})
|
||||
@@ -16,13 +22,14 @@ endforeach()
|
||||
|
||||
add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES})
|
||||
target_include_directories(dDatabaseGame PUBLIC "."
|
||||
"ITables" PRIVATE "MySQL" "TestSQL"
|
||||
"ITables" PRIVATE "MySQL" "SQLite" "TestSQL"
|
||||
"${PROJECT_SOURCE_DIR}/dCommon"
|
||||
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
|
||||
)
|
||||
|
||||
target_link_libraries(dDatabaseGame
|
||||
PUBLIC MariaDB::ConnCpp
|
||||
INTERFACE dCommon)
|
||||
INTERFACE dCommon
|
||||
PRIVATE sqlite3 MariaDB::ConnCpp)
|
||||
|
||||
# Glob together all headers that need to be precompiled
|
||||
file(
|
||||
|
||||
@@ -2,22 +2,46 @@
|
||||
#include "Game.h"
|
||||
#include "dConfig.h"
|
||||
#include "Logger.h"
|
||||
#include "MySQLDatabase.h"
|
||||
#include "DluAssert.h"
|
||||
|
||||
#include "SQLiteDatabase.h"
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#pragma warning (disable:4251) //Disables SQL warnings
|
||||
|
||||
namespace {
|
||||
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() {
|
||||
if (database) {
|
||||
LOG("Tried to connect to database when it's already connected!");
|
||||
return;
|
||||
}
|
||||
|
||||
database = new MySQLDatabase();
|
||||
const auto databaseType = GetMigrationFolder();
|
||||
|
||||
if (databaseType == "sqlite") database = new SQLiteDatabase();
|
||||
else if (databaseType == "mysql") database = new MySQLDatabase();
|
||||
else {
|
||||
LOG("Invalid database type specified in config, using MySQL");
|
||||
database = new MySQLDatabase();
|
||||
}
|
||||
|
||||
database->Connect();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <conncpp.hpp>
|
||||
|
||||
#include "GameDatabase.h"
|
||||
|
||||
@@ -13,4 +12,6 @@ namespace Database {
|
||||
// Used for assigning a test database as the handler for database logic.
|
||||
// Do not use in production code.
|
||||
void _setDatabase(GameDatabase* const db);
|
||||
|
||||
std::string GetMigrationFolder();
|
||||
};
|
||||
|
||||
@@ -24,14 +24,10 @@
|
||||
#include "IIgnoreList.h"
|
||||
#include "IAccountsRewardCodes.h"
|
||||
#include "IBehaviors.h"
|
||||
|
||||
namespace sql {
|
||||
class Statement;
|
||||
class PreparedStatement;
|
||||
};
|
||||
#include "IUgcModularBuild.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
|
||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
|
||||
#else
|
||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) x
|
||||
#endif // _DEBUG
|
||||
@@ -42,14 +38,13 @@ class GameDatabase :
|
||||
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
||||
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
||||
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
||||
public IBehaviors {
|
||||
public IBehaviors, public IUgcModularBuild {
|
||||
public:
|
||||
virtual ~GameDatabase() = default;
|
||||
// TODO: These should be made private.
|
||||
virtual void Connect() = 0;
|
||||
virtual void Destroy(std::string source = "") = 0;
|
||||
virtual void ExecuteCustomQuery(const std::string_view query) = 0;
|
||||
virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0;
|
||||
virtual void Commit() = 0;
|
||||
virtual bool GetAutoCommit() = 0;
|
||||
virtual void SetAutoCommit(bool value) = 0;
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
|
||||
// Update the GameMaster level of an account.
|
||||
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
|
||||
|
||||
virtual uint32_t GetAccountCount() = 0;
|
||||
};
|
||||
|
||||
#endif //!__IACCOUNTS__H__
|
||||
|
||||
@@ -3,12 +3,45 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ILeaderboard {
|
||||
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.
|
||||
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__
|
||||
|
||||
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() {
|
||||
LOG("Using MySQL database");
|
||||
driver = sql::mariadb::get_driver_instance();
|
||||
|
||||
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
|
||||
@@ -67,7 +68,7 @@ void MySQLDatabase::ExecuteCustomQuery(const std::string_view query) {
|
||||
|
||||
sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) {
|
||||
if (!con) {
|
||||
Connect();
|
||||
Database::Get()->Connect();
|
||||
LOG("Trying to reconnect to MySQL");
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& quer
|
||||
|
||||
con = nullptr;
|
||||
|
||||
Connect();
|
||||
Database::Get()->Connect();
|
||||
LOG("Trying to reconnect to MySQL from invalid or closed connection");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "GameDatabase.h"
|
||||
|
||||
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
|
||||
// bind a parameter to a type that isn't defined.
|
||||
@@ -29,7 +30,6 @@ public:
|
||||
void Connect() override;
|
||||
void Destroy(std::string source = "") override;
|
||||
|
||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
|
||||
void Commit() override;
|
||||
bool GetAutoCommit() override;
|
||||
void SetAutoCommit(bool value) override;
|
||||
@@ -113,6 +113,19 @@ public:
|
||||
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;
|
||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
|
||||
uint32_t GetAccountCount() override;
|
||||
private:
|
||||
|
||||
// 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) {
|
||||
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"
|
||||
"Servers.cpp"
|
||||
"Ugc.cpp"
|
||||
"UgcModularBuild.cpp"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
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);
|
||||
|
||||
@@ -9,3 +13,79 @@ std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityI
|
||||
|
||||
return donation_total->getUInt("donation_total");
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> ProcessQuery(UniqueResultSet& rows) {
|
||||
std::vector<ILeaderboard::Entry> entries;
|
||||
entries.reserve(rows->rowsCount());
|
||||
|
||||
while (rows->next()) {
|
||||
auto& entry = entries.emplace_back();
|
||||
|
||||
entry.charId = rows->getUInt("character_id");
|
||||
entry.lastPlayedTimestamp = rows->getUInt("lp_unix");
|
||||
entry.primaryScore = rows->getFloat("primaryScore");
|
||||
entry.secondaryScore = rows->getFloat("secondaryScore");
|
||||
entry.tertiaryScore = rows->getFloat("tertiaryScore");
|
||||
entry.numWins = rows->getUInt("numWins");
|
||||
entry.numTimesPlayed = rows->getUInt("timesPlayed");
|
||||
entry.name = rows->getString("char_name");
|
||||
// entry.ranking is never set because its calculated in leaderboard in code.
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> MySQLDatabase::GetDescendingLeaderboard(const uint32_t activityId) {
|
||||
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;", activityId);
|
||||
return ProcessQuery(leaderboard);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAscendingLeaderboard(const uint32_t activityId) {
|
||||
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore ASC, secondaryscore ASC, tertiaryScore ASC, last_played ASC;", activityId);
|
||||
return ProcessQuery(leaderboard);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAgsLeaderboard(const uint32_t activityId) {
|
||||
auto query = Game::config->GetValue("classic_survival_scoring") != "1" ?
|
||||
"SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;" :
|
||||
"SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY secondaryscore DESC, primaryscore DESC, tertiaryScore DESC, last_played ASC;";
|
||||
auto leaderboard = ExecuteSelect(query, activityId);
|
||||
return ProcessQuery(leaderboard);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> MySQLDatabase::GetNsLeaderboard(const uint32_t activityId) {
|
||||
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore ASC, tertiaryScore DESC, last_played ASC;", activityId);
|
||||
return ProcessQuery(leaderboard);
|
||||
}
|
||||
|
||||
void MySQLDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||
ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;",
|
||||
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||
ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;",
|
||||
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||
}
|
||||
|
||||
void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
|
||||
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
}
|
||||
|
||||
std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
|
||||
std::optional<ILeaderboard::Score> toReturn = std::nullopt;
|
||||
auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
if (res->next()) {
|
||||
toReturn = ILeaderboard::Score{
|
||||
.primaryScore = res->getFloat("primaryScore"),
|
||||
.secondaryScore = res->getFloat("secondaryScore"),
|
||||
.tertiaryScore = res->getFloat("tertiaryScore")
|
||||
};
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void MySQLDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) {
|
||||
ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
}
|
||||
|
||||
9
dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp
Normal file
9
dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
void MySQLDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) {
|
||||
ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId);
|
||||
}
|
||||
|
||||
void MySQLDatabase::DeleteUgcBuild(const LWOOBJID bigId) {
|
||||
ExecuteDelete("DELETE FROM ugc_modular_build WHERE ugc_id = ?;", bigId);
|
||||
}
|
||||
11
dDatabase/GameDatabase/SQLite/CMakeLists.txt
Normal file
11
dDatabase/GameDatabase/SQLite/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
SET(DDATABSE_DATABSES_SQLITE_SOURCES
|
||||
"SQLiteDatabase.cpp"
|
||||
)
|
||||
|
||||
add_subdirectory(Tables)
|
||||
|
||||
foreach(file ${DDATABASES_DATABASES_SQLITE_TABLES_SOURCES})
|
||||
set(DDATABSE_DATABSES_SQLITE_SOURCES ${DDATABSE_DATABSES_SQLITE_SOURCES} "Tables/${file}")
|
||||
endforeach()
|
||||
|
||||
set(DDATABSE_DATABSES_SQLITE_SOURCES ${DDATABSE_DATABSES_SQLITE_SOURCES} PARENT_SCOPE)
|
||||
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 SetMasterIp(const std::string_view ip, const uint32_t port) override;
|
||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||
void InsertDefaultPersistentId() override;
|
||||
void UpdatePersistentId(const uint32_t id) override;
|
||||
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
|
||||
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
|
||||
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
|
||||
void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
|
||||
void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
|
||||
std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
|
||||
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
|
||||
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
|
||||
void AddBehavior(const IBehaviors::Info& info) override;
|
||||
std::string GetBehavior(const int32_t behaviorId) override;
|
||||
void RemoveBehavior(const int32_t characterId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override;
|
||||
void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
|
||||
void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
|
||||
std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override;
|
||||
void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override;
|
||||
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override;
|
||||
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
|
||||
void DeleteUgcBuild(const LWOOBJID bigId) override;
|
||||
uint32_t GetAccountCount() override;
|
||||
private:
|
||||
CppSQLite3Statement CreatePreppedStmt(const std::string& query);
|
||||
|
||||
// Generic query functions that can be used for any query.
|
||||
// Return type may be different depending on the query, so it is up to the caller to check the return type.
|
||||
// The first argument is the query string, and the rest are the parameters to bind to the query.
|
||||
// The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope
|
||||
template<typename... Args>
|
||||
inline std::pair<CppSQLite3Statement, CppSQLite3Query> ExecuteSelect(const std::string& query, Args&&... args) {
|
||||
std::pair<CppSQLite3Statement, CppSQLite3Query> toReturn;
|
||||
toReturn.first = CreatePreppedStmt(query);
|
||||
SetParams(toReturn.first, std::forward<Args>(args)...);
|
||||
DLU_SQL_TRY_CATCH_RETHROW(toReturn.second = toReturn.first.execQuery());
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void ExecuteDelete(const std::string& query, Args&&... args) {
|
||||
auto preppedStmt = CreatePreppedStmt(query);
|
||||
SetParams(preppedStmt, std::forward<Args>(args)...);
|
||||
DLU_SQL_TRY_CATCH_RETHROW(preppedStmt.execDML());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline int32_t ExecuteUpdate(const std::string& query, Args&&... args) {
|
||||
auto preppedStmt = CreatePreppedStmt(query);
|
||||
SetParams(preppedStmt, std::forward<Args>(args)...);
|
||||
DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt.execDML());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline int ExecuteInsert(const std::string& query, Args&&... args) {
|
||||
auto preppedStmt = CreatePreppedStmt(query);
|
||||
SetParams(preppedStmt, std::forward<Args>(args)...);
|
||||
DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt.execDML());
|
||||
}
|
||||
};
|
||||
|
||||
// Below are each of the definitions of SetParam for each supported type.
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) {
|
||||
LOG("%s", param.data());
|
||||
stmt.bind(index, param.data());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) {
|
||||
LOG("%s", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) {
|
||||
LOG("%s", param.c_str());
|
||||
stmt.bind(index, param.c_str());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) {
|
||||
LOG("%u", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) {
|
||||
LOG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) {
|
||||
LOG("%u", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) {
|
||||
LOG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) {
|
||||
LOG("%u", param);
|
||||
stmt.bind(index, static_cast<int32_t>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) {
|
||||
LOG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) {
|
||||
LOG("%llu", param);
|
||||
stmt.bind(index, static_cast<sqlite_int64>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) {
|
||||
LOG("%llu", param);
|
||||
stmt.bind(index, static_cast<sqlite_int64>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const float param) {
|
||||
LOG("%f", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const double param) {
|
||||
LOG("%f", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) {
|
||||
LOG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) {
|
||||
LOG("Blob");
|
||||
// This is the one time you will ever see me use const_cast.
|
||||
std::stringstream stream;
|
||||
stream << param->rdbuf();
|
||||
stmt.bind(index, reinterpret_cast<const unsigned char*>(stream.str().c_str()), stream.str().size());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
|
||||
if (param) {
|
||||
LOG("%d", param.value());
|
||||
stmt.bind(index, static_cast<int>(param.value()));
|
||||
} else {
|
||||
LOG("Null");
|
||||
stmt.bindNull(index);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //!SQLITEDATABASE_H
|
||||
49
dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp
Normal file
49
dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "Database.h"
|
||||
|
||||
std::optional<IAccounts::Info> SQLiteDatabase::GetAccountInfo(const std::string_view username) {
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM accounts WHERE name = ? LIMIT 1", username);
|
||||
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IAccounts::Info toReturn;
|
||||
toReturn.id = result.getIntField("id");
|
||||
toReturn.maxGmLevel = static_cast<eGameMasterLevel>(result.getIntField("gm_level"));
|
||||
toReturn.bcryptPassword = result.getStringField("password");
|
||||
toReturn.banned = result.getIntField("banned");
|
||||
toReturn.locked = result.getIntField("locked");
|
||||
toReturn.playKeyId = result.getIntField("play_key_id");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) {
|
||||
ExecuteUpdate("UPDATE accounts SET mute_expire = ? WHERE id = ?;", timeToUnmute, accountId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) {
|
||||
ExecuteUpdate("UPDATE accounts SET banned = ? WHERE id = ?;", banned, accountId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) {
|
||||
ExecuteUpdate("UPDATE accounts SET password = ? WHERE id = ?;", bcryptpassword, accountId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) {
|
||||
ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast<int32_t>(eGameMasterLevel::OPERATOR));
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
|
||||
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
|
||||
}
|
||||
|
||||
uint32_t SQLiteDatabase::GetAccountCount() {
|
||||
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
|
||||
if (res.eof()) return 0;
|
||||
|
||||
return res.getIntField("count");
|
||||
}
|
||||
17
dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp
Normal file
17
dDatabase/GameDatabase/SQLite/Tables/AccountsRewardCodes.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) {
|
||||
ExecuteInsert("INSERT OR IGNORE INTO accounts_rewardcodes (account_id, rewardcode) VALUES (?, ?);", account_id, reward_code);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> SQLiteDatabase::GetRewardCodesByAccountID(const uint32_t account_id) {
|
||||
auto [_, result] = ExecuteSelect("SELECT rewardcode FROM accounts_rewardcodes WHERE account_id = ?;", account_id);
|
||||
|
||||
std::vector<uint32_t> toReturn;
|
||||
while (!result.eof()) {
|
||||
toReturn.push_back(result.getIntField("rewardcode"));
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
6
dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp
Normal file
6
dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) {
|
||||
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
|
||||
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
|
||||
}
|
||||
19
dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp
Normal file
19
dDatabase/GameDatabase/SQLite/Tables/Behaviors.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "IBehaviors.h"
|
||||
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::AddBehavior(const IBehaviors::Info& info) {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON CONFLICT(behavior_id) DO UPDATE SET behavior_info = ?",
|
||||
info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo
|
||||
);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveBehavior(const int32_t behaviorId) {
|
||||
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
}
|
||||
|
||||
std::string SQLiteDatabase::GetBehavior(const int32_t behaviorId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
return !result.eof() ? result.getStringField("behavior_info") : "";
|
||||
}
|
||||
6
dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp
Normal file
6
dDatabase/GameDatabase/SQLite/Tables/BugReports.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertNewBugReport(const IBugReports::Info& info) {
|
||||
ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)",
|
||||
info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId);
|
||||
}
|
||||
26
dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt
Normal file
26
dDatabase/GameDatabase/SQLite/Tables/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES
|
||||
"Accounts.cpp"
|
||||
"AccountsRewardCodes.cpp"
|
||||
"ActivityLog.cpp"
|
||||
"Behaviors.cpp"
|
||||
"BugReports.cpp"
|
||||
"CharInfo.cpp"
|
||||
"CharXml.cpp"
|
||||
"CommandLog.cpp"
|
||||
"Friends.cpp"
|
||||
"IgnoreList.cpp"
|
||||
"Leaderboard.cpp"
|
||||
"Mail.cpp"
|
||||
"MigrationHistory.cpp"
|
||||
"ObjectIdTracker.cpp"
|
||||
"PetNames.cpp"
|
||||
"PlayerCheatDetections.cpp"
|
||||
"PlayKeys.cpp"
|
||||
"Property.cpp"
|
||||
"PropertyContents.cpp"
|
||||
"Servers.cpp"
|
||||
"Ugc.cpp"
|
||||
"UgcModularBuild.cpp"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
79
dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp
Normal file
79
dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::vector<std::string> SQLiteDatabase::GetApprovedCharacterNames() {
|
||||
auto [_, result] = ExecuteSelect("SELECT name FROM charinfo;");
|
||||
|
||||
std::vector<std::string> toReturn;
|
||||
|
||||
while (!result.eof()) {
|
||||
toReturn.push_back(result.getStringField("name"));
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<ICharInfo::Info> CharInfoFromQueryResult(CppSQLite3Query stmt) {
|
||||
if (stmt.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ICharInfo::Info toReturn;
|
||||
|
||||
toReturn.id = stmt.getIntField("id");
|
||||
toReturn.name = stmt.getStringField("name");
|
||||
toReturn.pendingName = stmt.getStringField("pending_name");
|
||||
toReturn.needsRename = stmt.getIntField("needs_rename");
|
||||
toReturn.cloneId = stmt.getInt64Field("prop_clone_id");
|
||||
toReturn.accountId = stmt.getIntField("account_id");
|
||||
toReturn.permissionMap = static_cast<ePermissionMap>(stmt.getIntField("permission_map"));
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<ICharInfo::Info> SQLiteDatabase::GetCharacterInfo(const uint32_t charId) {
|
||||
return CharInfoFromQueryResult(
|
||||
ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId).second
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<ICharInfo::Info> SQLiteDatabase::GetCharacterInfo(const std::string_view name) {
|
||||
return CharInfoFromQueryResult(
|
||||
ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name).second
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> SQLiteDatabase::GetAccountCharacterIds(const uint32_t accountId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId);
|
||||
|
||||
std::vector<uint32_t> toReturn;
|
||||
while (!result.eof()) {
|
||||
toReturn.push_back(result.getIntField("id"));
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewCharacter(const ICharInfo::Info info) {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `charinfo`(`id`, `account_id`, `name`, `pending_name`, `needs_rename`, `last_login`, `prop_clone_id`) VALUES (?,?,?,?,?,?,(SELECT IFNULL(MAX(`prop_clone_id`), 0) + 1 FROM `charinfo`))",
|
||||
info.id,
|
||||
info.accountId,
|
||||
info.name,
|
||||
info.pendingName,
|
||||
false,
|
||||
static_cast<uint32_t>(time(NULL)));
|
||||
}
|
||||
|
||||
void SQLiteDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) {
|
||||
ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast<uint32_t>(time(NULL)), characterId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) {
|
||||
ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast<uint32_t>(time(NULL)), characterId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
|
||||
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), characterId);
|
||||
}
|
||||
19
dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp
Normal file
19
dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::string SQLiteDatabase::GetCharacterXml(const uint32_t charId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId);
|
||||
|
||||
if (result.eof()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return result.getStringField("xml_data");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) {
|
||||
ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) {
|
||||
ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml);
|
||||
}
|
||||
5
dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp
Normal file
5
dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) {
|
||||
ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command);
|
||||
}
|
||||
73
dDatabase/GameDatabase/SQLite/Tables/Friends.cpp
Normal file
73
dDatabase/GameDatabase/SQLite/Tables/Friends.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::vector<FriendData> SQLiteDatabase::GetFriendsList(const uint32_t charId) {
|
||||
auto [_, friendsList] = ExecuteSelect(
|
||||
R"QUERY(
|
||||
SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM
|
||||
(
|
||||
SELECT CASE
|
||||
WHEN player_id = ? THEN friend_id
|
||||
WHEN friend_id = ? THEN player_id
|
||||
END AS requested_player, best_friend FROM friends
|
||||
) AS fr
|
||||
JOIN charinfo AS ci ON ci.id = fr.requested_player
|
||||
WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?;
|
||||
)QUERY", charId, charId, charId);
|
||||
|
||||
std::vector<FriendData> toReturn;
|
||||
|
||||
while (!friendsList.eof()) {
|
||||
FriendData fd;
|
||||
fd.friendID = friendsList.getIntField("player");
|
||||
fd.isBestFriend = friendsList.getIntField("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
|
||||
fd.friendName = friendsList.getStringField("name");
|
||||
|
||||
toReturn.push_back(fd);
|
||||
friendsList.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<IFriends::BestFriendStatus> SQLiteDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;",
|
||||
playerCharacterId,
|
||||
friendCharacterId,
|
||||
friendCharacterId,
|
||||
playerCharacterId
|
||||
);
|
||||
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IFriends::BestFriendStatus toReturn;
|
||||
toReturn.playerCharacterId = result.getIntField("player_id");
|
||||
toReturn.friendCharacterId = result.getIntField("friend_id");
|
||||
toReturn.bestFriendStatus = result.getIntField("best_friend");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) {
|
||||
ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);",
|
||||
bestFriendStatus,
|
||||
playerCharacterId,
|
||||
friendCharacterId,
|
||||
friendCharacterId,
|
||||
playerCharacterId
|
||||
);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
|
||||
ExecuteInsert("INSERT OR IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
|
||||
ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);",
|
||||
playerCharacterId,
|
||||
friendCharacterId,
|
||||
friendCharacterId,
|
||||
playerCharacterId
|
||||
);
|
||||
}
|
||||
22
dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp
Normal file
22
dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::vector<IIgnoreList::Info> SQLiteDatabase::GetIgnoreList(const uint32_t playerId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId);
|
||||
|
||||
std::vector<IIgnoreList::Info> ignoreList;
|
||||
|
||||
while (!result.eof()) {
|
||||
ignoreList.push_back(IIgnoreList::Info{ result.getStringField("name"), static_cast<uint32_t>(result.getIntField("ignore_id")) });
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return ignoreList;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
|
||||
ExecuteInsert("INSERT OR IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
|
||||
ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId);
|
||||
}
|
||||
91
dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp
Normal file
91
dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
std::optional<uint32_t> SQLiteDatabase::GetDonationTotal(const uint32_t activityId) {
|
||||
auto [_, donation_total] = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId);
|
||||
|
||||
if (donation_total.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return donation_total.getIntField("donation_total");
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> ProcessQuery(CppSQLite3Query& rows) {
|
||||
std::vector<ILeaderboard::Entry> entries;
|
||||
|
||||
while (!rows.eof()) {
|
||||
auto& entry = entries.emplace_back();
|
||||
|
||||
entry.charId = rows.getIntField("character_id");
|
||||
entry.lastPlayedTimestamp = rows.getIntField("lp_unix");
|
||||
entry.primaryScore = rows.getFloatField("primaryScore");
|
||||
entry.secondaryScore = rows.getFloatField("secondaryScore");
|
||||
entry.tertiaryScore = rows.getFloatField("tertiaryScore");
|
||||
entry.numWins = rows.getIntField("numWins");
|
||||
entry.numTimesPlayed = rows.getIntField("timesPlayed");
|
||||
entry.name = rows.getStringField("char_name");
|
||||
// entry.ranking is never set because its calculated in leaderboard in code.
|
||||
rows.nextRow();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> SQLiteDatabase::GetDescendingLeaderboard(const uint32_t activityId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;", activityId);
|
||||
return ProcessQuery(result);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> SQLiteDatabase::GetAscendingLeaderboard(const uint32_t activityId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore ASC, secondaryscore ASC, tertiaryScore ASC, last_played ASC;", activityId);
|
||||
return ProcessQuery(result);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> SQLiteDatabase::GetAgsLeaderboard(const uint32_t activityId) {
|
||||
auto query = Game::config->GetValue("classic_survival_scoring") != "1" ?
|
||||
"SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;" :
|
||||
"SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY secondaryscore DESC, primaryscore DESC, tertiaryScore DESC, last_played ASC;";
|
||||
auto [_, result] = ExecuteSelect(query, activityId);
|
||||
return ProcessQuery(result);
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> SQLiteDatabase::GetNsLeaderboard(const uint32_t activityId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT *, CAST(strftime('%s', last_played) as INT) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore ASC, tertiaryScore DESC, last_played ASC;", activityId);
|
||||
return ProcessQuery(result);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||
ExecuteInsert("INSERT INTO leaderboard (primaryScore, secondaryScore, tertiaryScore, character_id, game_id, last_played) VALUES (?,?,?,?,?,CURRENT_TIMESTAMP) ;",
|
||||
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
|
||||
ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;",
|
||||
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
|
||||
}
|
||||
|
||||
std::optional<ILeaderboard::Score> SQLiteDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
|
||||
std::optional<ILeaderboard::Score> toReturn = std::nullopt;
|
||||
auto [_, res] = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
if (!res.eof()) {
|
||||
toReturn = ILeaderboard::Score{
|
||||
.primaryScore = static_cast<float>(res.getFloatField("primaryScore")),
|
||||
.secondaryScore = static_cast<float>(res.getFloatField("secondaryScore")),
|
||||
.tertiaryScore = static_cast<float>(res.getFloatField("tertiaryScore"))
|
||||
};
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) {
|
||||
ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
|
||||
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId);
|
||||
}
|
||||
83
dDatabase/GameDatabase/SQLite/Tables/Mail.cpp
Normal file
83
dDatabase/GameDatabase/SQLite/Tables/Mail.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `mail` "
|
||||
"(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)"
|
||||
" VALUES (?,?,?,?,?,?,?,?,?,?,?,0)",
|
||||
mail.senderId,
|
||||
mail.senderUsername,
|
||||
mail.receiverId,
|
||||
mail.recipient,
|
||||
static_cast<uint32_t>(time(NULL)),
|
||||
mail.subject,
|
||||
mail.body,
|
||||
mail.itemID,
|
||||
mail.itemLOT,
|
||||
0,
|
||||
mail.itemCount);
|
||||
}
|
||||
|
||||
std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
|
||||
auto [_, res] = ExecuteSelect(
|
||||
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
|
||||
" FROM mail WHERE receiver_id=? limit ?;",
|
||||
characterId, numberOfMail);
|
||||
|
||||
std::vector<IMail::MailInfo> toReturn;
|
||||
|
||||
while (!res.eof()) {
|
||||
IMail::MailInfo mail;
|
||||
mail.id = res.getInt64Field("id");
|
||||
mail.subject = res.getStringField("subject");
|
||||
mail.body = res.getStringField("body");
|
||||
mail.senderUsername = res.getStringField("sender_name");
|
||||
mail.itemID = res.getIntField("attachment_id");
|
||||
mail.itemLOT = res.getIntField("attachment_lot");
|
||||
mail.itemSubkey = res.getIntField("attachment_subkey");
|
||||
mail.itemCount = res.getIntField("attachment_count");
|
||||
mail.timeSent = res.getInt64Field("time_sent");
|
||||
mail.wasRead = res.getIntField("was_read");
|
||||
|
||||
toReturn.push_back(std::move(mail));
|
||||
res.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<IMail::MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
|
||||
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
|
||||
if (res.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IMail::MailInfo toReturn;
|
||||
toReturn.itemLOT = res.getIntField("attachment_lot");
|
||||
toReturn.itemCount = res.getIntField("attachment_count");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
uint32_t SQLiteDatabase::GetUnreadMailCount(const uint32_t characterId) {
|
||||
auto [_, res] = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId);
|
||||
|
||||
if (res.eof()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return res.getIntField("number_unread");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::MarkMailRead(const uint64_t mailId) {
|
||||
ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=?;", mailId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::ClaimMailItem(const uint64_t mailId) {
|
||||
ExecuteUpdate("UPDATE mail SET attachment_lot=0 WHERE id=?;", mailId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::DeleteMail(const uint64_t mailId) {
|
||||
ExecuteDelete("DELETE FROM mail WHERE id=?;", mailId);
|
||||
}
|
||||
13
dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp
Normal file
13
dDatabase/GameDatabase/SQLite/Tables/MigrationHistory.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::CreateMigrationHistoryTable() {
|
||||
ExecuteInsert("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);");
|
||||
}
|
||||
|
||||
bool SQLiteDatabase::IsMigrationRun(const std::string_view str) {
|
||||
return !ExecuteSelect("SELECT name FROM migration_history WHERE name = ?;", str).second.eof();
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertMigration(const std::string_view str) {
|
||||
ExecuteInsert("INSERT INTO migration_history (name) VALUES (?);", str);
|
||||
}
|
||||
17
dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp
Normal file
17
dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::optional<uint32_t> SQLiteDatabase::GetCurrentPersistentId() {
|
||||
auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker");
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result.getIntField("last_object_id");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertDefaultPersistentId() {
|
||||
ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdatePersistentId(const uint32_t newId) {
|
||||
ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId);
|
||||
}
|
||||
26
dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp
Normal file
26
dDatabase/GameDatabase/SQLite/Tables/PetNames.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `pet_names` (`id`, `pet_name`, `approved`) VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(id) DO UPDATE SET pet_name = ?, approved = ?;",
|
||||
petId,
|
||||
info.petName,
|
||||
info.approvalStatus,
|
||||
info.petName,
|
||||
info.approvalStatus);
|
||||
}
|
||||
|
||||
std::optional<IPetNames::Info> SQLiteDatabase::GetPetNameInfo(const LWOOBJID& petId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT pet_name, approved FROM pet_names WHERE id = ? LIMIT 1;", petId);
|
||||
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IPetNames::Info toReturn;
|
||||
toReturn.petName = result.getStringField("pet_name");
|
||||
toReturn.approvalStatus = result.getIntField("approved");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
11
dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp
Normal file
11
dDatabase/GameDatabase/SQLite/Tables/PlayKeys.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::optional<bool> SQLiteDatabase::IsPlaykeyActive(const int32_t playkeyId) {
|
||||
auto [_, keyCheckRes] = ExecuteSelect("SELECT active FROM `play_keys` WHERE id=?", playkeyId);
|
||||
|
||||
if (keyCheckRes.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return keyCheckRes.getIntField("active");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& info) {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)",
|
||||
info.userId, info.username, info.extraMessage, info.systemAddress);
|
||||
}
|
||||
195
dDatabase/GameDatabase/SQLite/Tables/Property.cpp
Normal file
195
dDatabase/GameDatabase/SQLite/Tables/Property.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
#include "ePropertySortType.h"
|
||||
|
||||
std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||
std::optional<IProperty::PropertyEntranceResult> result;
|
||||
std::string query;
|
||||
std::pair<CppSQLite3Statement, CppSQLite3Query> propertiesRes;
|
||||
|
||||
if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) {
|
||||
query = R"QUERY(
|
||||
FROM properties as p
|
||||
JOIN charinfo as ci
|
||||
ON ci.prop_clone_id = p.clone_id
|
||||
where p.zone_id = ?
|
||||
AND (
|
||||
p.description LIKE ?
|
||||
OR p.name LIKE ?
|
||||
OR ci.name LIKE ?
|
||||
)
|
||||
AND p.privacy_option >= ?
|
||||
AND p.owner_id IN (
|
||||
SELECT fr.requested_player AS player FROM (
|
||||
SELECT CASE
|
||||
WHEN player_id = ? THEN friend_id
|
||||
WHEN friend_id = ? THEN player_id
|
||||
END AS requested_player FROM friends
|
||||
) AS fr
|
||||
JOIN charinfo AS ci ON ci.id = fr.requested_player
|
||||
WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?
|
||||
) ORDER BY ci.name ASC
|
||||
)QUERY";
|
||||
const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;";
|
||||
propertiesRes = ExecuteSelect(
|
||||
completeQuery,
|
||||
params.mapId,
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
params.playerSort,
|
||||
params.playerId,
|
||||
params.playerId,
|
||||
params.playerId,
|
||||
params.numResults,
|
||||
params.startIndex
|
||||
);
|
||||
const auto countQuery = "SELECT COUNT(*) as count" + query + ";";
|
||||
auto [_, count] = ExecuteSelect(
|
||||
countQuery,
|
||||
params.mapId,
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
params.playerSort,
|
||||
params.playerId,
|
||||
params.playerId,
|
||||
params.playerId
|
||||
);
|
||||
if (!count.eof()) {
|
||||
result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count.getIntField("count");
|
||||
}
|
||||
} else {
|
||||
if (params.sortChoice == SORT_TYPE_REPUTATION) {
|
||||
query = R"QUERY(
|
||||
FROM properties as p
|
||||
JOIN charinfo as ci
|
||||
ON ci.prop_clone_id = p.clone_id
|
||||
where p.zone_id = ?
|
||||
AND (
|
||||
p.description LIKE ?
|
||||
OR p.name LIKE ?
|
||||
OR ci.name LIKE ?
|
||||
)
|
||||
AND p.privacy_option >= ?
|
||||
ORDER BY p.reputation DESC, p.last_updated DESC
|
||||
)QUERY";
|
||||
} else {
|
||||
query = R"QUERY(
|
||||
FROM properties as p
|
||||
JOIN charinfo as ci
|
||||
ON ci.prop_clone_id = p.clone_id
|
||||
where p.zone_id = ?
|
||||
AND (
|
||||
p.description LIKE ?
|
||||
OR p.name LIKE ?
|
||||
OR ci.name LIKE ?
|
||||
)
|
||||
AND p.privacy_option >= ?
|
||||
ORDER BY p.last_updated DESC
|
||||
)QUERY";
|
||||
}
|
||||
const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;";
|
||||
propertiesRes = ExecuteSelect(
|
||||
completeQuery,
|
||||
params.mapId,
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
params.playerSort,
|
||||
params.numResults,
|
||||
params.startIndex
|
||||
);
|
||||
const auto countQuery = "SELECT COUNT(*) as count" + query + ";";
|
||||
auto [_, count] = ExecuteSelect(
|
||||
countQuery,
|
||||
params.mapId,
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
"%" + params.searchString + "%",
|
||||
params.playerSort
|
||||
);
|
||||
if (!count.eof()) {
|
||||
result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count.getIntField("count");
|
||||
}
|
||||
}
|
||||
|
||||
auto& [_, properties] = propertiesRes;
|
||||
if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult();
|
||||
while (!properties.eof()) {
|
||||
auto& entry = result->entries.emplace_back();
|
||||
entry.id = properties.getInt64Field("id");
|
||||
entry.ownerId = properties.getInt64Field("owner_id");
|
||||
entry.cloneId = properties.getInt64Field("clone_id");
|
||||
entry.name = properties.getStringField("name");
|
||||
entry.description = properties.getStringField("description");
|
||||
entry.privacyOption = properties.getIntField("privacy_option");
|
||||
entry.rejectionReason = properties.getStringField("rejection_reason");
|
||||
entry.lastUpdatedTime = properties.getIntField("last_updated");
|
||||
entry.claimedTime = properties.getIntField("time_claimed");
|
||||
entry.reputation = properties.getIntField("reputation");
|
||||
entry.modApproved = properties.getIntField("mod_approved");
|
||||
entry.performanceCost = properties.getFloatField("performance_cost");
|
||||
properties.nextRow();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<IProperty::Info> SQLiteDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) {
|
||||
auto [_, propertyEntry] = ExecuteSelect(
|
||||
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost "
|
||||
"FROM properties WHERE zone_id = ? AND clone_id = ?;", mapId, cloneId);
|
||||
|
||||
if (propertyEntry.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IProperty::Info toReturn;
|
||||
toReturn.id = propertyEntry.getInt64Field("id");
|
||||
toReturn.ownerId = propertyEntry.getInt64Field("owner_id");
|
||||
toReturn.cloneId = propertyEntry.getInt64Field("clone_id");
|
||||
toReturn.name = propertyEntry.getStringField("name");
|
||||
toReturn.description = propertyEntry.getStringField("description");
|
||||
toReturn.privacyOption = propertyEntry.getIntField("privacy_option");
|
||||
toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason");
|
||||
toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated");
|
||||
toReturn.claimedTime = propertyEntry.getIntField("time_claimed");
|
||||
toReturn.reputation = propertyEntry.getIntField("reputation");
|
||||
toReturn.modApproved = propertyEntry.getIntField("mod_approved");
|
||||
toReturn.performanceCost = propertyEntry.getFloatField("performance_cost");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET privacy_option = ?, rejection_reason = ?, mod_approved = ? WHERE id = ?;",
|
||||
info.privacyOption,
|
||||
info.rejectionReason,
|
||||
info.modApproved,
|
||||
info.id);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
|
||||
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ?;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
|
||||
auto insertion = ExecuteInsert(
|
||||
"INSERT INTO properties"
|
||||
" (id, owner_id, template_id, clone_id, name, description, zone_id, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, performance_cost)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, 0, CAST(strftime('%s', 'now') as INT), CAST(strftime('%s', 'now') as INT), '', 0, 0.0)",
|
||||
info.id,
|
||||
info.ownerId,
|
||||
templateId,
|
||||
zoneId.GetCloneID(),
|
||||
info.name,
|
||||
info.description,
|
||||
zoneId.GetMapID()
|
||||
);
|
||||
}
|
||||
65
dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp
Normal file
65
dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::vector<IPropertyContents::Model> SQLiteDatabase::GetPropertyModels(const LWOOBJID& propertyId) {
|
||||
auto [_, result] = ExecuteSelect(
|
||||
"SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, "
|
||||
"behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 "
|
||||
"FROM properties_contents WHERE property_id = ?;", propertyId);
|
||||
|
||||
std::vector<IPropertyContents::Model> toReturn;
|
||||
while (!result.eof()) {
|
||||
IPropertyContents::Model model;
|
||||
model.id = result.getInt64Field("id");
|
||||
model.lot = static_cast<LOT>(result.getIntField("lot"));
|
||||
model.position.x = result.getFloatField("x");
|
||||
model.position.y = result.getFloatField("y");
|
||||
model.position.z = result.getFloatField("z");
|
||||
model.rotation.w = result.getFloatField("rw");
|
||||
model.rotation.x = result.getFloatField("rx");
|
||||
model.rotation.y = result.getFloatField("ry");
|
||||
model.rotation.z = result.getFloatField("rz");
|
||||
model.ugcId = result.getInt64Field("ugc_id");
|
||||
model.behaviors[0] = result.getIntField("behavior_1");
|
||||
model.behaviors[1] = result.getIntField("behavior_2");
|
||||
model.behaviors[2] = result.getIntField("behavior_3");
|
||||
model.behaviors[3] = result.getIntField("behavior_4");
|
||||
model.behaviors[4] = result.getIntField("behavior_5");
|
||||
|
||||
toReturn.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) {
|
||||
try {
|
||||
ExecuteInsert(
|
||||
"INSERT INTO properties_contents"
|
||||
"(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5)"
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18
|
||||
model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast<uint32_t>(model.lot),
|
||||
model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w,
|
||||
name, "", // Model description. TODO implement this.
|
||||
model.behaviors[0], // behavior 1
|
||||
model.behaviors[1], // behavior 2
|
||||
model.behaviors[2], // behavior 3
|
||||
model.behaviors[3], // behavior 4
|
||||
model.behaviors[4] // behavior 5
|
||||
);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Error inserting new property model: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
ExecuteUpdate(
|
||||
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
|
||||
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
|
||||
position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w,
|
||||
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
|
||||
}
|
||||
23
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
23
dDatabase/GameDatabase/SQLite/Tables/Servers.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
||||
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
|
||||
// since it would be two queries anyways.
|
||||
ExecuteDelete("DELETE FROM servers;");
|
||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
|
||||
}
|
||||
|
||||
std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
|
||||
auto [_, result] = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
|
||||
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MasterInfo toReturn;
|
||||
|
||||
toReturn.ip = result.getStringField("ip");
|
||||
toReturn.port = result.getIntField("port");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
72
dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp
Normal file
72
dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) {
|
||||
auto [_, result] = ExecuteSelect(
|
||||
"SELECT lxfml, u.id FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;",
|
||||
propertyId);
|
||||
|
||||
std::vector<IUgc::Model> toReturn;
|
||||
|
||||
while (!result.eof()) {
|
||||
IUgc::Model model;
|
||||
|
||||
int blobSize{};
|
||||
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
|
||||
model.id = result.getInt64Field("id");
|
||||
toReturn.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
|
||||
auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||
|
||||
std::vector<IUgc::Model> models;
|
||||
while (!result.eof()) {
|
||||
IUgc::Model model;
|
||||
model.id = result.getInt64Field("id");
|
||||
|
||||
int blobSize{};
|
||||
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
|
||||
models.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveUnreferencedUgcModels() {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewUgcModel(
|
||||
std::istringstream& sd0Data, // cant be const sad
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) {
|
||||
const std::istream stream(sd0Data.rdbuf());
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)",
|
||||
blueprintId,
|
||||
accountId,
|
||||
characterId,
|
||||
0,
|
||||
&stream,
|
||||
false,
|
||||
"weedeater.lxfml"
|
||||
);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId);
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
const std::istream stream(lxfml.rdbuf());
|
||||
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
|
||||
}
|
||||
9
dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp
Normal file
9
dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) {
|
||||
ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::DeleteUgcBuild(const LWOOBJID bigId) {
|
||||
ExecuteDelete("DELETE FROM ugc_modular_build WHERE ugc_id = ?;", bigId);
|
||||
}
|
||||
@@ -8,10 +8,6 @@ void TestSQLDatabase::Destroy(std::string source) {
|
||||
|
||||
}
|
||||
|
||||
sql::PreparedStatement* TestSQLDatabase::CreatePreppedStmt(const std::string& query) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TestSQLDatabase::Commit() {
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void Connect() override;
|
||||
void Destroy(std::string source = "") override;
|
||||
|
||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
|
||||
void Commit() override;
|
||||
bool GetAutoCommit() override;
|
||||
void SetAutoCommit(bool value) override;
|
||||
@@ -91,6 +90,18 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void RemoveBehavior(const int32_t behaviorId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
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
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
#include <fstream>
|
||||
|
||||
Migration LoadMigration(std::string path) {
|
||||
Migration LoadMigration(std::string folder, std::string path) {
|
||||
Migration migration{};
|
||||
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path);
|
||||
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / folder / path);
|
||||
|
||||
if (file.is_open()) {
|
||||
std::string line;
|
||||
@@ -34,10 +34,19 @@ Migration LoadMigration(std::string path) {
|
||||
void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->CreateMigrationHistoryTable();
|
||||
|
||||
sql::SQLString finalSQL = "";
|
||||
// has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again.
|
||||
|
||||
const auto migrationFolder = Database::GetMigrationFolder();
|
||||
if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") {
|
||||
LOG("Running migration: 17_migration_for_migrations.sql");
|
||||
Database::Get()->ExecuteCustomQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 5) WHERE `name` LIKE \"dlu%\";");
|
||||
Database::Get()->InsertMigration("17_migration_for_migrations.sql");
|
||||
}
|
||||
|
||||
std::string finalSQL = "";
|
||||
bool runSd0Migrations = false;
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) {
|
||||
auto migration = LoadMigration("dlu/" + entry);
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
||||
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
||||
|
||||
if (migration.data.empty()) {
|
||||
continue;
|
||||
@@ -46,7 +55,7 @@ void MigrationRunner::RunMigrations() {
|
||||
if (Database::Get()->IsMigrationRun(migration.name)) continue;
|
||||
|
||||
LOG("Running migration: %s", migration.name.c_str());
|
||||
if (migration.name == "dlu/5_brick_model_sd0.sql") {
|
||||
if (migration.name == "5_brick_model_sd0.sql") {
|
||||
runSd0Migrations = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data.c_str());
|
||||
@@ -61,12 +70,12 @@ void MigrationRunner::RunMigrations() {
|
||||
}
|
||||
|
||||
if (!finalSQL.empty()) {
|
||||
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';');
|
||||
auto migration = GeneralUtils::SplitString(finalSQL, ';');
|
||||
for (auto& query : migration) {
|
||||
try {
|
||||
if (query.empty()) continue;
|
||||
Database::Get()->ExecuteCustomQuery(query.c_str());
|
||||
} catch (sql::SQLException& e) {
|
||||
Database::Get()->ExecuteCustomQuery(query);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Encountered error running migration: %s", e.what());
|
||||
}
|
||||
}
|
||||
@@ -86,10 +95,14 @@ void MigrationRunner::RunSQLiteMigrations() {
|
||||
cdstmt.execQuery().finalize();
|
||||
cdstmt.finalize();
|
||||
|
||||
Database::Get()->CreateMigrationHistoryTable();
|
||||
if (CDClientDatabase::ExecuteQuery("select * from migration_history where name = \"7_migration_for_migrations.sql\";").eof()) {
|
||||
LOG("Running migration: 7_migration_for_migrations.sql");
|
||||
CDClientDatabase::ExecuteQuery("UPDATE `migration_history` SET `name` = SUBSTR(`name`, 10) WHERE `name` LIKE \"cdserver%\";");
|
||||
CDClientDatabase::ExecuteQuery("INSERT INTO migration_history (name) VALUES (\"7_migration_for_migrations.sql\");");
|
||||
}
|
||||
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) {
|
||||
auto migration = LoadMigration("cdserver/" + entry);
|
||||
auto migration = LoadMigration("cdserver/", entry);
|
||||
|
||||
if (migration.data.empty()) continue;
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ target_include_directories(dGameBase PUBLIC "." "dEntity"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
||||
# dPhysics
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
#include "ItemComponent.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "AchievementVendorComponent.h"
|
||||
#include "VanityUtilities.h"
|
||||
|
||||
// Table includes
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
@@ -96,6 +97,8 @@
|
||||
#include "CDSkillBehaviorTable.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
|
||||
|
||||
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
|
||||
@@ -285,8 +288,9 @@ void Entity::Initialize() {
|
||||
AddComponent<PropertyEntranceComponent>(propertyEntranceComponentID);
|
||||
}
|
||||
|
||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS) > 0) {
|
||||
auto* controllablePhysics = AddComponent<ControllablePhysicsComponent>();
|
||||
const int32_t controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS);
|
||||
if (controllablePhysicsComponentID > 0) {
|
||||
auto* controllablePhysics = AddComponent<ControllablePhysicsComponent>(controllablePhysicsComponentID);
|
||||
|
||||
if (m_Character) {
|
||||
controllablePhysics->LoadFromXml(m_Character->GetXMLDoc());
|
||||
@@ -329,16 +333,19 @@ void Entity::Initialize() {
|
||||
AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID);
|
||||
}
|
||||
|
||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) > 0) {
|
||||
AddComponent<RigidbodyPhantomPhysicsComponent>();
|
||||
const int32_t rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS);
|
||||
if (rigidBodyPhantomPhysicsComponentID > 0) {
|
||||
AddComponent<RigidbodyPhantomPhysicsComponent>(rigidBodyPhantomPhysicsComponentID);
|
||||
}
|
||||
|
||||
if (markedAsPhantom || compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS) > 0) {
|
||||
AddComponent<PhantomPhysicsComponent>()->SetPhysicsEffectActive(false);
|
||||
const int32_t phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS);
|
||||
if (markedAsPhantom || phantomPhysicsComponentID > 0) {
|
||||
AddComponent<PhantomPhysicsComponent>(phantomPhysicsComponentID)->SetPhysicsEffectActive(false);
|
||||
}
|
||||
|
||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) > 0) {
|
||||
auto* havokVehiclePhysicsComponent = AddComponent<HavokVehiclePhysicsComponent>();
|
||||
const int32_t havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS);
|
||||
if (havokVehiclePhysicsComponentID > 0) {
|
||||
auto* havokVehiclePhysicsComponent = AddComponent<HavokVehiclePhysicsComponent>(havokVehiclePhysicsComponentID);
|
||||
havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition);
|
||||
havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation);
|
||||
}
|
||||
@@ -1271,6 +1278,7 @@ void Entity::Update(const float deltaTime) {
|
||||
auto timerName = timer.GetName();
|
||||
m_Timers.erase(m_Timers.begin() + timerPosition);
|
||||
GetScript()->OnTimerDone(this, timerName);
|
||||
VanityUtilities::OnTimerDone(this, timerName);
|
||||
|
||||
TriggerEvent(eTriggerEventType::TIMER_DONE, this);
|
||||
} else {
|
||||
@@ -1334,6 +1342,7 @@ void Entity::OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxN
|
||||
if (!other) return;
|
||||
|
||||
GetScript()->OnProximityUpdate(this, other, proxName, status);
|
||||
VanityUtilities::OnProximityUpdate(this, other, proxName, status);
|
||||
|
||||
RocketLaunchpadControlComponent* rocketComp = GetComponent<RocketLaunchpadControlComponent>();
|
||||
if (!rocketComp) return;
|
||||
@@ -1351,6 +1360,11 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
|
||||
callback(other);
|
||||
}
|
||||
|
||||
SwitchComponent* switchComp = GetComponent<SwitchComponent>();
|
||||
if (switchComp) {
|
||||
switchComp->OnUse(other);
|
||||
}
|
||||
|
||||
TriggerEvent(eTriggerEventType::ENTER, other);
|
||||
|
||||
// POI system
|
||||
@@ -1479,6 +1493,14 @@ void Entity::OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16s
|
||||
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::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
|
||||
GetScript()->OnRequestActivityExit(sender, player, canceled);
|
||||
}
|
||||
@@ -2153,7 +2175,19 @@ void Entity::SetRespawnPos(const NiPoint3& position) {
|
||||
auto* characterComponent = GetComponent<CharacterComponent>();
|
||||
if (characterComponent) characterComponent->SetRespawnPos(position);
|
||||
}
|
||||
|
||||
void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
||||
auto* characterComponent = GetComponent<CharacterComponent>();
|
||||
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,11 @@
|
||||
#include "eKillType.h"
|
||||
#include "Observable.h"
|
||||
|
||||
namespace GameMessages {
|
||||
struct ActivityNotify;
|
||||
struct ShootingGalleryFire;
|
||||
};
|
||||
|
||||
namespace Loot {
|
||||
class Info;
|
||||
};
|
||||
@@ -107,6 +112,11 @@ public:
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -205,6 +215,8 @@ public:
|
||||
void OnZonePropertyModelRemoved(Entity* player);
|
||||
void OnZonePropertyModelRemovedWhileEquipped(Entity* player);
|
||||
void OnZonePropertyModelRotated(Entity* player);
|
||||
void OnActivityNotify(GameMessages::ActivityNotify& notify);
|
||||
void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify);
|
||||
|
||||
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);
|
||||
|
||||
@@ -505,6 +505,7 @@ void EntityManager::UpdateGhosting(Entity* player) {
|
||||
|
||||
if (collectionId != 0) {
|
||||
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
|
||||
|
||||
if (missionComponent->HasCollectible(collectionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "LeaderboardManager.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
@@ -72,197 +73,191 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write0();
|
||||
}
|
||||
|
||||
void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
|
||||
Clear();
|
||||
if (rows->rowsCount() == 0) return;
|
||||
// Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
|
||||
// to send it to a client.
|
||||
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());
|
||||
while (rows->next()) {
|
||||
for (const auto& leaderboardEntry : leaderboardEntries) {
|
||||
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
|
||||
this->entries.push_back(std::vector<LDFBaseData*>());
|
||||
auto& entry = this->entries.back();
|
||||
auto& entry = leaderboard.PushBackEntry();
|
||||
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"LastPlayed", rows->getUInt64("lastPlayed")));
|
||||
entry.push_back(new LDFData<int32_t>(u"NumPlayed", rows->getInt("timesPlayed")));
|
||||
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(rows->getString("name").c_str())));
|
||||
entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking")));
|
||||
switch (leaderboardType) {
|
||||
case Type::ShootingGallery:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
|
||||
entry.push_back(new LDFData<uint64_t>(u"CharacterID", leaderboardEntry.charId));
|
||||
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp));
|
||||
entry.push_back(new LDFData<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed));
|
||||
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)));
|
||||
entry.push_back(new LDFData<uint64_t>(u"RowNumber", leaderboardEntry.ranking));
|
||||
switch (leaderboard.GetLeaderboardType()) {
|
||||
case ShootingGallery:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||
// 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
|
||||
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
|
||||
break;
|
||||
case Type::Racing:
|
||||
entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("primaryScore")));
|
||||
case Racing:
|
||||
entry.push_back(new LDFData<float>(u"BestTime", leaderboardEntry.primaryScore));
|
||||
// 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
|
||||
entry.push_back(new LDFData<int32_t>(u"License", 1));
|
||||
// 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
|
||||
break;
|
||||
case Type::UnusedLeaderboard4:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
|
||||
case UnusedLeaderboard4:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||
// Points:1
|
||||
break;
|
||||
case Type::MonumentRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
|
||||
case MonumentRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||
// Time:1(?)
|
||||
break;
|
||||
case Type::FootRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
|
||||
case FootRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||
// Time:1
|
||||
break;
|
||||
case Type::Survival:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
|
||||
case Survival:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||
// 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
|
||||
break;
|
||||
case Type::SurvivalNS:
|
||||
entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("primaryScore")));
|
||||
case SurvivalNS:
|
||||
entry.push_back(new LDFData<int32_t>(u"Wave", leaderboardEntry.primaryScore));
|
||||
// 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
|
||||
break;
|
||||
case Type::Donations:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
|
||||
case Donations:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||
// Score:1
|
||||
break;
|
||||
case Type::None:
|
||||
// This type is included here simply to resolve a compiler warning on mac about unused enum types
|
||||
break;
|
||||
case None:
|
||||
[[fallthrough]];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
|
||||
// Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better
|
||||
switch (leaderboardType) {
|
||||
case Type::Racing:
|
||||
case Type::MonumentRace:
|
||||
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC";
|
||||
case Type::Survival:
|
||||
return Game::config->GetValue("classic_survival_scoring") == "1" ?
|
||||
"secondaryScore DESC, primaryScore DESC, tertiaryScore DESC" :
|
||||
"primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
|
||||
case Type::SurvivalNS:
|
||||
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";
|
||||
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
|
||||
std::vector<ILeaderboard::Entry> toReturn;
|
||||
|
||||
int32_t index = 0;
|
||||
// for friends and top, we dont need to find this players index.
|
||||
if (infoType == Leaderboard::InfoType::MyStanding || infoType == Leaderboard::InfoType::Friends) {
|
||||
for (; index < leaderboard.size(); index++) {
|
||||
if (leaderboard[index].charId == relatedPlayer) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderboard.size() < 10) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.end());
|
||||
index = 0;
|
||||
} else if (index < 10) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.begin() + 10); // get the top 10 since we are in the top 10
|
||||
index = 0;
|
||||
} else if (index > leaderboard.size() - 10) {
|
||||
toReturn.assign(leaderboard.end() - 10, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
|
||||
index = leaderboard.size() - 10;
|
||||
} else {
|
||||
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
|
||||
index -= 5;
|
||||
}
|
||||
|
||||
int32_t i = index;
|
||||
for (auto& entry : toReturn) {
|
||||
entry.ranking = ++i;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) {
|
||||
resultStart++;
|
||||
resultEnd++;
|
||||
// We need everything except 1 column so i'm selecting * from leaderboard
|
||||
const std::string queryBase =
|
||||
R"QUERY(
|
||||
WITH leaderboardsRanked AS (
|
||||
SELECT leaderboard.*, charinfo.name,
|
||||
RANK() OVER
|
||||
(
|
||||
ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC
|
||||
) AS ranking
|
||||
FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id
|
||||
WHERE game_id = ? %s
|
||||
),
|
||||
myStanding AS (
|
||||
SELECT
|
||||
ranking as myRank
|
||||
FROM leaderboardsRanked
|
||||
WHERE id = ?
|
||||
),
|
||||
lowestRanking AS (
|
||||
SELECT MAX(ranking) AS lowestRank
|
||||
FROM leaderboardsRanked
|
||||
)
|
||||
SELECT leaderboardsRanked.*, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking
|
||||
WHERE leaderboardsRanked.ranking
|
||||
BETWEEN
|
||||
LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), CAST(lowestRanking.lowestRank AS SIGNED) - 9)
|
||||
AND
|
||||
LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank)
|
||||
ORDER BY ranking ASC;
|
||||
)QUERY";
|
||||
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
|
||||
// Filter the leaderboard to only include entries from the last week
|
||||
const auto currentTime = std::chrono::system_clock::now();
|
||||
auto epochTime = currentTime.time_since_epoch().count();
|
||||
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
|
||||
|
||||
std::string friendsFilter =
|
||||
R"QUERY(
|
||||
AND (
|
||||
character_id IN (
|
||||
SELECT fr.requested_player FROM (
|
||||
SELECT CASE
|
||||
WHEN player_id = ? THEN friend_id
|
||||
WHEN friend_id = ? THEN player_id
|
||||
END AS requested_player
|
||||
FROM friends
|
||||
) AS fr
|
||||
JOIN charinfo AS ci
|
||||
ON ci.id = fr.requested_player
|
||||
WHERE fr.requested_player IS NOT NULL
|
||||
)
|
||||
OR character_id = ?
|
||||
)
|
||||
)QUERY";
|
||||
|
||||
std::string weeklyFilter = " AND UNIX_TIMESTAMP(last_played) BETWEEN UNIX_TIMESTAMP(date_sub(now(),INTERVAL 1 WEEK)) AND UNIX_TIMESTAMP(now()) ";
|
||||
|
||||
std::string filter;
|
||||
// Setup our filter based on the query type
|
||||
if (this->infoType == InfoType::Friends) filter += friendsFilter;
|
||||
if (this->weekly) filter += weeklyFilter;
|
||||
const auto orderBase = GetOrdering(this->leaderboardType);
|
||||
|
||||
// For top query, we want to just rank all scores, but for all others we need the scores around a specific player
|
||||
std::string baseLookup;
|
||||
if (this->infoType == InfoType::Top) {
|
||||
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " ORDER BY ";
|
||||
baseLookup += orderBase.data();
|
||||
} else {
|
||||
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " AND character_id = ";
|
||||
baseLookup += std::to_string(static_cast<uint32_t>(this->relatedPlayer));
|
||||
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
|
||||
for (const auto& entry : leaderboard) {
|
||||
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
|
||||
weeklyLeaderboard.push_back(entry);
|
||||
}
|
||||
}
|
||||
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");
|
||||
|
||||
// Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow
|
||||
constexpr uint16_t STRING_LENGTH = 4096;
|
||||
std::unique_ptr<char[]> lookupBuffer = std::make_unique<char[]>(STRING_LENGTH);
|
||||
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd);
|
||||
DluAssert(res != -1);
|
||||
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt(lookupBuffer.get()));
|
||||
LOG_DEBUG("Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId);
|
||||
query->setInt(1, this->gameID);
|
||||
if (this->infoType == InfoType::Friends) {
|
||||
query->setInt(2, this->relatedPlayer);
|
||||
query->setInt(3, this->relatedPlayer);
|
||||
query->setInt(4, this->relatedPlayer);
|
||||
query->setInt(5, relatedPlayerLeaderboardId);
|
||||
} else {
|
||||
query->setInt(2, relatedPlayerLeaderboardId);
|
||||
std::vector<ILeaderboard::Entry> FilterFriends(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer) {
|
||||
// Filter the leaderboard to only include friends of the player
|
||||
auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer);
|
||||
std::vector<ILeaderboard::Entry> friendsLeaderboard;
|
||||
for (const auto& entry : leaderboard) {
|
||||
const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
|
||||
return entry.charId == data.friendID;
|
||||
});
|
||||
if (res != friendOfPlayer.cend() || entry.charId == relatedPlayer) {
|
||||
friendsLeaderboard.push_back(entry);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -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) {
|
||||
const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId);
|
||||
|
||||
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"));
|
||||
query->setInt(1, playerID);
|
||||
query->setInt(2, activityId);
|
||||
std::unique_ptr<sql::ResultSet> myScoreResult(query->executeQuery());
|
||||
const auto oldScore = Database::Get()->GetPlayerScore(playerID, activityId);
|
||||
|
||||
std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;");
|
||||
Score newScore(primaryScore, secondaryScore, tertiaryScore);
|
||||
if (myScoreResult->next()) {
|
||||
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;
|
||||
}
|
||||
ILeaderboard::Score newScore{ .primaryScore = primaryScore, .secondaryScore = secondaryScore, .tertiaryScore = tertiaryScore };
|
||||
if (oldScore.has_value()) {
|
||||
bool lowerScoreBetter = leaderboardType == Leaderboard::Type::Racing || leaderboardType == Leaderboard::Type::MonumentRace;
|
||||
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
|
||||
// Nimbus station has a weird leaderboard where we need a custom scoring system
|
||||
if (leaderboardType == Leaderboard::Type::SurvivalNS) {
|
||||
newHighScore = newScore.GetPrimaryScore() > oldScore.GetPrimaryScore() ||
|
||||
(newScore.GetPrimaryScore() == oldScore.GetPrimaryScore() && newScore.GetSecondaryScore() < oldScore.GetSecondaryScore());
|
||||
newHighScore = newScore.primaryScore > oldScore->primaryScore ||
|
||||
(newScore.primaryScore == oldScore->primaryScore && newScore.secondaryScore < oldScore->secondaryScore);
|
||||
} else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") {
|
||||
Score oldScoreFlipped(oldScore.GetSecondaryScore(), oldScore.GetPrimaryScore());
|
||||
Score newScoreFlipped(newScore.GetSecondaryScore(), newScore.GetPrimaryScore());
|
||||
ILeaderboard::Score oldScoreFlipped{oldScore->secondaryScore, oldScore->primaryScore, oldScore->tertiaryScore};
|
||||
ILeaderboard::Score newScoreFlipped{newScore.secondaryScore, newScore.primaryScore, newScore.tertiaryScore};
|
||||
newHighScore = newScoreFlipped > oldScoreFlipped;
|
||||
}
|
||||
|
||||
if (newHighScore) {
|
||||
saveQuery = FormatInsert(leaderboardType, newScore, true);
|
||||
Database::Get()->UpdateScore(playerID, activityId, newScore);
|
||||
} else {
|
||||
Database::Get()->IncrementTimesPlayed(playerID, activityId);
|
||||
}
|
||||
} 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
|
||||
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 = ?;"));
|
||||
winUpdate->setInt(1, playerID);
|
||||
winUpdate->setInt(2, activityId);
|
||||
winUpdate->execute();
|
||||
Database::Get()->IncrementNumWins(playerID, activityId);
|
||||
}
|
||||
}
|
||||
|
||||
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.SetupLeaderboard(weekly, resultStart, resultEnd);
|
||||
leaderboard.SetupLeaderboard(weekly);
|
||||
leaderboard.Send(targetID);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,46 +9,10 @@
|
||||
#include "dCommonVars.h"
|
||||
#include "LDFFormat.h"
|
||||
|
||||
namespace sql {
|
||||
class ResultSet;
|
||||
};
|
||||
|
||||
namespace RakNet {
|
||||
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;
|
||||
|
||||
class Leaderboard {
|
||||
@@ -79,7 +43,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Resets the leaderboard state and frees its allocated memory
|
||||
*
|
||||
*
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
@@ -96,20 +60,16 @@ public:
|
||||
* @param resultStart The index to start 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.
|
||||
*/
|
||||
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 LeaderboardEntries = std::vector<LeaderboardEntry>;
|
||||
|
||||
@@ -119,10 +79,18 @@ private:
|
||||
InfoType infoType;
|
||||
Leaderboard::Type leaderboardType;
|
||||
bool weekly;
|
||||
public:
|
||||
LeaderboardEntry& PushBackEntry() {
|
||||
return entries.emplace_back();
|
||||
}
|
||||
|
||||
Type GetLeaderboardType() const {
|
||||
return leaderboardType;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@@ -27,7 +28,7 @@
|
||||
#include "CDPhysicsComponentTable.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_DirtyStateOrTarget = true;
|
||||
m_State = AiState::spawn;
|
||||
@@ -37,6 +38,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
|
||||
m_Disabled = false;
|
||||
m_SkillEntries = {};
|
||||
m_SoftTimer = 5.0f;
|
||||
m_ForcedTetherTime = 0.0f;
|
||||
|
||||
//Grab the aggro information from BaseCombatAI:
|
||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||
@@ -170,6 +172,17 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
|
||||
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) {
|
||||
@@ -287,40 +300,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
}
|
||||
|
||||
if (!m_TetherEffectActive && m_OutOfCombat && (m_OutOfCombatTime -= deltaTime) <= 0) {
|
||||
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);
|
||||
}
|
||||
TetherLogic();
|
||||
|
||||
m_OutOfCombat = false;
|
||||
m_OutOfCombatTime = 0.0f;
|
||||
@@ -499,7 +479,7 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -626,6 +606,7 @@ const NiPoint3& BaseCombatAIComponent::GetStartPosition() const {
|
||||
|
||||
void BaseCombatAIComponent::ClearThreat() {
|
||||
m_ThreatEntries.clear();
|
||||
m_Target = LWOOBJID_EMPTY;
|
||||
|
||||
m_DirtyThreat = true;
|
||||
}
|
||||
@@ -806,3 +787,55 @@ void BaseCombatAIComponent::Wake() {
|
||||
m_dpEntity->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();
|
||||
|
||||
// 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:
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// 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
|
||||
* @return whether this entity is a mech
|
||||
|
||||
@@ -7,7 +7,6 @@ set(DGAME_DCOMPONENTS_SOURCES
|
||||
"BuildBorderComponent.cpp"
|
||||
"CharacterComponent.cpp"
|
||||
"CollectibleComponent.cpp"
|
||||
"Component.cpp"
|
||||
"ControllablePhysicsComponent.cpp"
|
||||
"DestroyableComponent.cpp"
|
||||
"DonationVendorComponent.cpp"
|
||||
@@ -65,7 +64,6 @@ target_include_directories(dComponents PUBLIC "."
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
|
||||
# dPhysics (via dpWorld.h)
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include"
|
||||
|
||||
@@ -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
|
||||
|
||||
#include "tinyxml2.h"
|
||||
namespace tinyxml2 {
|
||||
class XMLDocument;
|
||||
}
|
||||
|
||||
namespace RakNet {
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
class Entity;
|
||||
|
||||
@@ -9,40 +15,40 @@ class Entity;
|
||||
*/
|
||||
class Component {
|
||||
public:
|
||||
Component(Entity* parent);
|
||||
virtual ~Component();
|
||||
Component(Entity* parent) : m_Parent{ parent } {}
|
||||
virtual ~Component() = default;
|
||||
|
||||
/**
|
||||
* Gets 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
|
||||
* @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
|
||||
* @param originator
|
||||
*/
|
||||
virtual void OnUse(Entity* originator);
|
||||
virtual void OnUse(Entity* originator) {}
|
||||
|
||||
/**
|
||||
* Save data from this componennt to character XML
|
||||
* @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
|
||||
* @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:
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "LevelProgressionComponent.h"
|
||||
#include "eStateChangeType.h"
|
||||
|
||||
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : PhysicsComponent(entity) {
|
||||
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) {
|
||||
m_Velocity = {};
|
||||
m_AngularVelocity = {};
|
||||
m_InJetpackMode = false;
|
||||
|
||||
@@ -23,7 +23,7 @@ class ControllablePhysicsComponent : public PhysicsComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
|
||||
|
||||
ControllablePhysicsComponent(Entity* entity);
|
||||
ControllablePhysicsComponent(Entity* entity, int32_t componentId);
|
||||
~ControllablePhysicsComponent() override;
|
||||
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "HavokVehiclePhysicsComponent.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_AngularVelocity = NiPoint3Constant::ZERO;
|
||||
m_IsOnGround = true;
|
||||
|
||||
@@ -13,7 +13,7 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent {
|
||||
public:
|
||||
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;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "eStateChangeType.h"
|
||||
#include "eUseItemResponse.h"
|
||||
#include "Mail.h"
|
||||
#include "ProximityMonitorComponent.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDInventoryComponentTable.h"
|
||||
@@ -68,9 +69,10 @@ InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
||||
auto slot = 0u;
|
||||
|
||||
for (const auto& item : items) {
|
||||
if (!item.equip || !Inventory::IsValidItem(item.itemid)) {
|
||||
continue;
|
||||
}
|
||||
if (!Inventory::IsValidItem(item.itemid)) continue;
|
||||
AddItem(item.itemid, item.count);
|
||||
|
||||
if (!item.equip) continue;
|
||||
|
||||
const LWOOBJID id = ObjectIDManager::GenerateObjectID();
|
||||
|
||||
@@ -829,6 +831,30 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1141,6 +1167,25 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
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) {
|
||||
const auto info = Inventory::FindItemComponent(lot);
|
||||
|
||||
|
||||
@@ -404,6 +404,8 @@ public:
|
||||
void UpdateGroup(const GroupUpdate& groupUpdate);
|
||||
void RemoveGroup(const std::string& groupId);
|
||||
|
||||
void FixInvisibleItems();
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -265,6 +265,7 @@ void MovementAIComponent::PullToPoint(const NiPoint3& point) {
|
||||
|
||||
void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
|
||||
if (path.empty()) return;
|
||||
while (!m_CurrentPath.empty()) m_CurrentPath.pop();
|
||||
std::for_each(path.rbegin(), path.rend() - 1, [this](const PathWaypoint& point) {
|
||||
this->m_CurrentPath.push(point);
|
||||
});
|
||||
|
||||
@@ -524,7 +524,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
|
||||
|
||||
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);
|
||||
|
||||
if (item == nullptr) {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "dpShapeBox.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_Rotation = m_Parent->GetDefaultRotation();
|
||||
m_Scale = m_Parent->GetDefaultScale();
|
||||
|
||||
@@ -30,7 +30,7 @@ class PhantomPhysicsComponent final : public PhysicsComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS;
|
||||
|
||||
PhantomPhysicsComponent(Entity* parent);
|
||||
PhantomPhysicsComponent(Entity* parent, int32_t componentId);
|
||||
~PhantomPhysicsComponent() override;
|
||||
void Update(float deltaTime) override;
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
|
||||
@@ -14,10 +14,21 @@
|
||||
|
||||
#include "EntityInfo.h"
|
||||
|
||||
PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) {
|
||||
PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) {
|
||||
m_Position = NiPoint3Constant::ZERO;
|
||||
m_Rotation = NiQuaternionConstant::IDENTITY;
|
||||
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) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class dpEntity;
|
||||
|
||||
class PhysicsComponent : public Component {
|
||||
public:
|
||||
PhysicsComponent(Entity* parent);
|
||||
PhysicsComponent(Entity* parent, int32_t componentId);
|
||||
virtual ~PhysicsComponent() = default;
|
||||
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
@@ -25,6 +25,9 @@ public:
|
||||
|
||||
const NiQuaternion& GetRotation() const { return m_Rotation; }
|
||||
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:
|
||||
dpEntity* CreatePhysicsEntity(eReplicaComponentType type);
|
||||
|
||||
@@ -37,6 +40,8 @@ protected:
|
||||
NiQuaternion m_Rotation;
|
||||
|
||||
bool m_DirtyPosition;
|
||||
|
||||
int32_t m_CollisionGroup{};
|
||||
};
|
||||
|
||||
#endif //!__PHYSICSCOMPONENT__H__
|
||||
|
||||
@@ -38,7 +38,7 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std::
|
||||
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);
|
||||
|
||||
if (iter == m_ProximitiesData.cend()) {
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
* @param name the proximity name to retrieve physics objects for
|
||||
* @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
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
RacingControlComponent::RacingControlComponent(Entity* parent)
|
||||
: Component(parent) {
|
||||
m_PathName = u"MainPath";
|
||||
m_RemainingLaps = 3;
|
||||
m_NumberOfLaps = 3;
|
||||
m_RemainingLaps = m_NumberOfLaps;
|
||||
m_LeadingPlayer = LWOOBJID_EMPTY;
|
||||
m_RaceBestTime = 0;
|
||||
m_RaceBestLap = 0;
|
||||
@@ -658,23 +659,9 @@ void RacingControlComponent::Update(float deltaTime) {
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn imagination pickups
|
||||
auto* minSpawner = Game::zoneManager->GetSpawnersByName(
|
||||
"ImaginationSpawn_Min")[0];
|
||||
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();
|
||||
}
|
||||
GameMessages::ZoneLoadedInfo zoneLoadInfo{};
|
||||
zoneLoadInfo.maxPlayers = m_LoadedPlayers;
|
||||
m_Parent->GetScript()->OnZoneLoadedInfo(m_Parent, zoneLoadInfo);
|
||||
|
||||
// Reset players to their start location, without smashing them
|
||||
for (auto& player : m_RacingPlayers) {
|
||||
@@ -764,7 +751,7 @@ void RacingControlComponent::Update(float deltaTime) {
|
||||
// new checkpoint
|
||||
uint32_t respawnIndex = 0;
|
||||
for (const auto& waypoint : path->pathWaypoints) {
|
||||
if (player.lap == 3) {
|
||||
if (player.lap == m_NumberOfLaps) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -835,7 +822,7 @@ void RacingControlComponent::Update(float deltaTime) {
|
||||
// Progress lap time tasks
|
||||
missionComponent->Progress(eMissionTaskType::RACING, lapTime.count(), static_cast<LWOOBJID>(eRacingTaskParam::LAP_TIME));
|
||||
|
||||
if (player.lap == 3) {
|
||||
if (player.lap == m_NumberOfLaps) {
|
||||
m_Finished++;
|
||||
player.finished = m_Finished;
|
||||
|
||||
@@ -882,3 +869,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,8 @@ public:
|
||||
*/
|
||||
RacingPlayerInfo* GetPlayerData(LWOOBJID playerID);
|
||||
|
||||
void MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
@@ -161,11 +163,13 @@ private:
|
||||
|
||||
/**
|
||||
* The paths that are followed for the camera scenes
|
||||
* Configurable in the ConfigureRacingControl msg with the key `Race_PathName`.
|
||||
*/
|
||||
std::u16string m_PathName;
|
||||
|
||||
/**
|
||||
* The ID of the activity for participating in this race
|
||||
* Configurable in the ConfigureRacingControl msg with the key `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
|
||||
*/
|
||||
const int32_t m_ActivityExitConfirm = 1;
|
||||
|
||||
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"EntityInfo.h"
|
||||
|
||||
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) {
|
||||
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
|
||||
m_Position = m_Parent->GetDefaultPosition();
|
||||
m_Rotation = m_Parent->GetDefaultRotation();
|
||||
m_Scale = m_Parent->GetDefaultScale();
|
||||
|
||||
@@ -21,7 +21,7 @@ class RigidbodyPhantomPhysicsComponent : public PhysicsComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS;
|
||||
|
||||
RigidbodyPhantomPhysicsComponent(Entity* parent);
|
||||
RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId);
|
||||
|
||||
void Update(const float deltaTime) override;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, uint32_t componentID) : PhysicsComponent(parent) {
|
||||
SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, int32_t componentID) : PhysicsComponent(parent, componentID) {
|
||||
m_Position = m_Parent->GetDefaultPosition();
|
||||
m_Rotation = m_Parent->GetDefaultRotation();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class SimplePhysicsComponent : public PhysicsComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS;
|
||||
|
||||
SimplePhysicsComponent(Entity* parent, uint32_t componentID);
|
||||
SimplePhysicsComponent(Entity* parent, int32_t componentID);
|
||||
~SimplePhysicsComponent() override;
|
||||
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
|
||||
@@ -123,6 +123,11 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
|
||||
behavior->Handle(sync_entry.context, bitStream, branch);
|
||||
|
||||
this->m_managedProjectiles.erase(this->m_managedProjectiles.begin() + index);
|
||||
|
||||
GameMessages::ActivityNotify notify;
|
||||
notify.notification.push_back( std::make_unique<LDFData<int32_t>>(u"shot_done", sync_entry.skillId));
|
||||
|
||||
m_Parent->OnActivityNotify(notify);
|
||||
}
|
||||
|
||||
void SkillComponent::RegisterPlayerProjectile(const LWOOBJID projectileId, BehaviorContext* context, const BehaviorBranchContext& branch, const LOT lot) {
|
||||
@@ -132,6 +137,7 @@ void SkillComponent::RegisterPlayerProjectile(const LWOOBJID projectileId, Behav
|
||||
entry.branchContext = branch;
|
||||
entry.lot = lot;
|
||||
entry.id = projectileId;
|
||||
entry.skillId = context->skillID;
|
||||
|
||||
this->m_managedProjectiles.push_back(entry);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ struct ProjectileSyncEntry {
|
||||
|
||||
BehaviorBranchContext branchContext{ 0, 0 };
|
||||
|
||||
int32_t skillId{ 0 };
|
||||
|
||||
explicit ProjectileSyncEntry();
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ void SwitchComponent::EntityEnter(Entity* entity) {
|
||||
RenderComponent::PlayAnimation(m_Parent, u"engaged");
|
||||
m_PetBouncer->SetPetBouncerEnabled(true);
|
||||
} else {
|
||||
GameMessages::SendKnockback(entity->GetObjectID(), m_Parent->GetObjectID(), m_Parent->GetObjectID(), 0.0f, NiPoint3(0.0f, 17.0f, 0.0f));
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,18 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
break;
|
||||
}
|
||||
|
||||
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
|
||||
case MessageType::Game::SELECT_SKILL: {
|
||||
auto var = entity->GetVar<bool>(u"dlu_first_time_load");
|
||||
if (var) {
|
||||
entity->SetVar<bool>(u"dlu_first_time_load", false);
|
||||
InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) inventoryComponent->FixInvisibleItems();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageType::Game::PLAYER_LOADED: {
|
||||
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
|
||||
entity->SetPlayerReadyForUpdates();
|
||||
@@ -691,6 +703,12 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS:
|
||||
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::SHOOTING_GALLERY_FIRE: {
|
||||
GameMessages::ShootingGalleryFire fire{};
|
||||
fire.Deserialize(inStream);
|
||||
fire.Handle(*entity, sysAddr);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user