mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-05-28 01:32:24 +00:00
Compare commits
142 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
34665f6f5c | ||
![]() |
32487dcd5f | ||
![]() |
891b176b4f | ||
![]() |
e42df5b02e | ||
![]() |
61921cfb62 | ||
![]() |
91f6b2bf81 | ||
![]() |
01917841cb | ||
![]() |
e18c504ee4 | ||
![]() |
b6f7b4c092 | ||
![]() |
522299c9ec | ||
![]() |
0e551429d3 | ||
![]() |
c77e9ce33a | ||
![]() |
3ebc6709db | ||
![]() |
841b754b01 | ||
![]() |
62c3f489fe | ||
![]() |
5ccb8357fd | ||
![]() |
4bacb8a2ee | ||
![]() |
89678c4a05 | ||
![]() |
4f97ecc073 | ||
![]() |
c9e4cde68d | ||
![]() |
0a12672889 | ||
![]() |
00a69909f8 | ||
![]() |
7c8ca1c1cb | ||
![]() |
4930fb93b3 | ||
![]() |
b31f9670d1 | ||
![]() |
1cc1782b35 | ||
![]() |
55d409eb82 | ||
![]() |
65f3c33ca5 | ||
![]() |
93fa4e268f | ||
![]() |
1fb1da101c | ||
![]() |
6f94043b33 | ||
![]() |
5785764a95 | ||
![]() |
fa53fa7935 | ||
![]() |
6b0f3a66e9 | ||
![]() |
f5c212fb86 | ||
![]() |
3d595ce4ac | ||
![]() |
99f6cf2d92 | ||
![]() |
bc0f3d9163 | ||
![]() |
20d5a9b6d8 | ||
![]() |
c490d45fe0 | ||
![]() |
aa49aaae76 | ||
![]() |
f78baee534 | ||
![]() |
347fc46f01 | ||
![]() |
d104559cc4 | ||
![]() |
b702843011 | ||
![]() |
14d7dec6a8 | ||
![]() |
6eaf0a153e | ||
![]() |
78e52904e5 | ||
![]() |
b388b03251 | ||
![]() |
ae37641635 | ||
![]() |
566791e647 | ||
![]() |
306d959a83 | ||
![]() |
a07d54e513 | ||
![]() |
e4c2eecbc7 | ||
![]() |
b01b3cc38d | ||
![]() |
b7c579fb84 | ||
![]() |
7b1d6948c3 | ||
![]() |
1b3cdc6d9c | ||
![]() |
d860552776 | ||
![]() |
b0d993de33 | ||
![]() |
dc602a94eb | ||
![]() |
3faf9eea45 | ||
![]() |
9542216650 | ||
![]() |
5b8fe2cba0 | ||
![]() |
f4b55915bc | ||
![]() |
a0913ff23d | ||
![]() |
b738504812 | ||
![]() |
c968dc9028 | ||
![]() |
2209a4432f | ||
![]() |
c855a6b9cf | ||
![]() |
8abc545bd1 | ||
![]() |
136133dde2 | ||
![]() |
6cd1310460 | ||
![]() |
23551d4ed8 | ||
![]() |
7599a2e81e | ||
![]() |
72ae55981b | ||
![]() |
c31bf3fad4 | ||
![]() |
0aa1be01d2 | ||
![]() |
d8172e2126 | ||
![]() |
88376c233c | ||
![]() |
f3b4143698 | ||
![]() |
55a1209c75 | ||
![]() |
a6c6d892cf | ||
![]() |
f7228a6495 | ||
![]() |
fb32534ae3 | ||
![]() |
c8fcb3788d | ||
![]() |
7a99e8ee6b | ||
![]() |
a5e8fd86ac | ||
![]() |
210bc48149 | ||
![]() |
e757086465 | ||
![]() |
915e9f75d1 | ||
![]() |
e9ee3e21cf | ||
![]() |
deddf0f256 | ||
![]() |
ca38139ff5 | ||
![]() |
41a001e242 | ||
![]() |
4e57b4aa7e | ||
![]() |
86b419735b | ||
![]() |
631980af3a | ||
![]() |
9e16e01b8d | ||
![]() |
6e66c5c362 | ||
![]() |
ee590c49c1 | ||
![]() |
7aaa69e42d | ||
![]() |
181bb0ce14 | ||
![]() |
2b325165aa | ||
![]() |
94d53fa77c | ||
![]() |
846ba894a4 | ||
![]() |
2ce2f4e363 | ||
![]() |
fd1ce75380 | ||
![]() |
3578076eca | ||
![]() |
7d06d012b5 | ||
![]() |
126701b5fe | ||
![]() |
2d08ec641c | ||
![]() |
9387a8e3d1 | ||
![]() |
e86f4e011b | ||
![]() |
070bec697c | ||
![]() |
9936bb0d00 | ||
![]() |
beffad42ea | ||
![]() |
3ecbd1013b | ||
![]() |
ff4546c027 | ||
![]() |
71baa5ce90 | ||
![]() |
5ccd15a7d8 | ||
![]() |
35bcaf6e95 | ||
![]() |
900c9b6abe | ||
![]() |
94e7cfc211 | ||
![]() |
021db0ecd1 | ||
![]() |
0b261e934f | ||
![]() |
1b9f7e44c7 | ||
![]() |
08a168de88 | ||
![]() |
0c948a8df6 | ||
![]() |
6ed6efa921 | ||
![]() |
dcc9e023a6 | ||
![]() |
8509ec8856 | ||
![]() |
18295017c1 | ||
![]() |
e8f011b830 | ||
![]() |
a787673baf | ||
![]() |
e869c0ad03 | ||
![]() |
b2af3fa9d4 | ||
![]() |
2560bb00da | ||
![]() |
1ae21c423f | ||
![]() |
0ae9eb4a96 | ||
![]() |
fced6d753a | ||
![]() |
15dc5feeb5 |
12
.github/workflows/build-and-test.yml
vendored
12
.github/workflows/build-and-test.yml
vendored
@ -16,12 +16,12 @@ jobs:
|
||||
os: [ windows-2022, ubuntu-22.04, macos-13 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Add msbuild to PATH (Windows only)
|
||||
if: ${{ matrix.os == 'windows-2022' }}
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330
|
||||
with:
|
||||
vs-version: '[17,18)'
|
||||
msbuild-architecture: x64
|
||||
@ -30,12 +30,16 @@ jobs:
|
||||
run: |
|
||||
brew install openssl@3
|
||||
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
- name: Get CMake 3.x
|
||||
uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
|
||||
with:
|
||||
cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
|
||||
- name: cmake
|
||||
uses: lukka/run-cmake@v10
|
||||
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38
|
||||
with:
|
||||
workflowPreset: "ci-${{matrix.os}}"
|
||||
- name: artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: build-${{matrix.os}}
|
||||
path: |
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
||||
[submodule "thirdparty/cpp-httplib"]
|
||||
path = thirdparty/cpp-httplib
|
||||
url = https://github.com/yhirose/cpp-httplib
|
||||
[submodule "thirdparty/tinyxml2"]
|
||||
path = thirdparty/tinyxml2
|
||||
url = https://github.com/leethomason/tinyxml2
|
||||
|
@ -235,6 +235,8 @@ include_directories(
|
||||
|
||||
"dNet"
|
||||
|
||||
"dWeb"
|
||||
|
||||
"tests"
|
||||
"tests/dCommonTests"
|
||||
"tests/dGameTests"
|
||||
@ -247,8 +249,9 @@ include_directories(
|
||||
"thirdparty/recastnavigation"
|
||||
"thirdparty/SQLite"
|
||||
"thirdparty/cpplinq"
|
||||
"thirdparty/cpp-httplib"
|
||||
"thirdparty/MD5"
|
||||
"thirdparty/nlohmann"
|
||||
"thirdparty/mongoose"
|
||||
)
|
||||
|
||||
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
|
||||
@ -300,6 +303,7 @@ add_subdirectory(dZoneManager)
|
||||
add_subdirectory(dNavigation)
|
||||
add_subdirectory(dPhysics)
|
||||
add_subdirectory(dServer)
|
||||
add_subdirectory(dWeb)
|
||||
|
||||
# Create a list of common libraries shared between all binaries
|
||||
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
|
||||
|
@ -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
|
||||
|
25
Dockerfile
25
Dockerfile
@ -11,7 +11,12 @@ COPY --chmod=0500 ./build.sh /app/
|
||||
|
||||
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
|
||||
|
||||
RUN ./build.sh
|
||||
RUN --mount=type=cache,target=/app/build,id=build-cache \
|
||||
mkdir -p /app/build /tmp/persisted-build && \
|
||||
cd /app/build && \
|
||||
cmake .. && \
|
||||
make -j$(nproc --ignore 1) && \
|
||||
cp -r /app/build/* /tmp/persisted-build/
|
||||
|
||||
FROM debian:12 as runtime
|
||||
|
||||
@ -23,23 +28,23 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Grab libraries and load them
|
||||
COPY --from=build /app/build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
|
||||
COPY --from=build /tmp/persisted-build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
|
||||
RUN ldconfig
|
||||
|
||||
# Server bins
|
||||
COPY --from=build /app/build/*Server /app/
|
||||
COPY --from=build /tmp/persisted-build/*Server /app/
|
||||
|
||||
# Necessary suplimentary files
|
||||
COPY --from=build /app/build/*.ini /app/configs/
|
||||
COPY --from=build /app/build/vanity/*.* /app/vanity/
|
||||
COPY --from=build /app/build/navmeshes /app/navmeshes
|
||||
COPY --from=build /app/build/migrations /app/migrations
|
||||
COPY --from=build /app/build/*.dcf /app/
|
||||
COPY --from=build /tmp/persisted-build/*.ini /app/configs/
|
||||
COPY --from=build /tmp/persisted-build/vanity/*.* /app/vanity/
|
||||
COPY --from=build /tmp/persisted-build/navmeshes /app/navmeshes
|
||||
COPY --from=build /tmp/persisted-build/migrations /app/migrations
|
||||
COPY --from=build /tmp/persisted-build/*.dcf /app/
|
||||
|
||||
# backup of config and vanity files to copy to the host incase
|
||||
# of a mount clobbering the copy from above
|
||||
COPY --from=build /app/build/*.ini /app/default-configs/
|
||||
COPY --from=build /app/build/vanity/*.* /app/default-vanity/
|
||||
COPY --from=build /tmp/persisted-build/*.ini /app/default-configs/
|
||||
COPY --from=build /tmp/persisted-build/vanity/*.* /app/default-vanity/
|
||||
|
||||
# needed as the container runs with the root user
|
||||
# and therefore sudo doesn't exist
|
||||
|
73
README.md
73
README.md
@ -24,13 +24,16 @@ Darkflame Universe is a server emulator and does not distribute any LEGO® Unive
|
||||
Warning: WSL version 1 does NOT support using sqlite as a database due to how it handles filesystem synchronization.
|
||||
You must use Version 2 if you must run the server under WSL. Not doing so will result in save data loss.
|
||||
* Single player installs now no longer require building the server from source or installing development tools.
|
||||
* Download the [latest release](https://github.com/DarkflameUniverse/DarkflameServer/releases) and extract the files into a folder inside your client.
|
||||
* You should be able to see the folder with the server executables in the same folder as `legouniverse.exe`.
|
||||
* Open `sharedconfig.ini` and find the line that says `client_location` and put `..` after it so the line reads `client_location=..`.
|
||||
* 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.
|
||||
* 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 saves automatically alongside when you open the game, the server starts automatically.
|
||||
* We are working on a way to make it so when you close the game, the server stops automatically alongside when you open the game, the server starts automatically.
|
||||
* If you are not setting a server up on mac, you can ignore this note
|
||||
* Note: you'll need to allow through System Preferences `AuthServer`, `ChatServer`, `MasterServer`, `WorldServer` and `libmariadbcpp.dylib` to run. The initial pop-up will block it due to the binaries being unsigned, after allowing them to run the servers will run as normal.
|
||||
|
||||
<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>
|
||||
|
||||
@ -67,9 +70,15 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
|
||||
|
||||
## Install dependencies
|
||||
|
||||
### Required compiler versions
|
||||
- g++11 or greater
|
||||
- MSVC unchecked
|
||||
- clang unchecked
|
||||
- appleclang unchecked
|
||||
|
||||
### Windows packages
|
||||
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
|
||||
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
|
||||
You'll also need to download and install [CMake](https://cmake.org/download/) (<font size="4">**version 3.25**</font> up to <font size="4">**version 3.31**</font>!).
|
||||
|
||||
### MacOS packages
|
||||
Ensure you have [brew](https://brew.sh) installed.
|
||||
@ -91,7 +100,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve
|
||||
```
|
||||
|
||||
#### Required CMake version
|
||||
This project uses <font size="4">**CMake version 3.25**</font> or higher and as such you will need to ensure you have this version installed.
|
||||
This project uses <font size="4">**CMake version 3.25**</font> up to <font size="4">**version 3.31**</font> and as such you will need to ensure you have this version installed.
|
||||
You can check your CMake version by using the following command in a terminal.
|
||||
```bash
|
||||
cmake --version
|
||||
@ -201,6 +210,7 @@ If you would like to build the server faster, append `-j<number>` where number i
|
||||
### Notes
|
||||
Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building:
|
||||
* If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory.
|
||||
* By default it should be set to the correct directory.
|
||||
* If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023.
|
||||
|
||||
## Configuring your server
|
||||
@ -223,28 +233,41 @@ Navigate to `build/sharedconfig.ini` and fill in the following fields:
|
||||
* `chatconfig.ini` contains a port option.
|
||||
* `masterconfig.ini` contains options related to permissions you want to run your servers with.
|
||||
* `sharedconfig.ini` contains several options that are shared across all servers
|
||||
* `worldconfig.ini` contains several options to turn on QOL improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off.
|
||||
* `worldconfig.ini` contains several options to turn on Quality of Life improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off.
|
||||
|
||||
## Verify your setup
|
||||
Your build directory should now look like this:
|
||||
* AuthServer
|
||||
* ChatServer
|
||||
* MasterServer
|
||||
* WorldServer
|
||||
* authconfig.ini
|
||||
* chatconfig.ini
|
||||
* masterconfig.ini
|
||||
Your build directory should contain at a minimum all of the following files.
|
||||
All listed files are required for a server to start.
|
||||
`ini` files can be located at the environment variable `DLU_CONFIG_DIR` and do not need to be located in this directory.
|
||||
(windows will have .exe at the end of the executables):
|
||||
* sharedconfig.ini
|
||||
* AuthServer(.exe)
|
||||
* authconfig.ini
|
||||
* ChatServer(.exe)
|
||||
* chatconfig.ini
|
||||
* MasterServer(.exe)
|
||||
* masterconfig.ini
|
||||
* WorldServer(.exe)
|
||||
* worldconfig.ini
|
||||
* ...
|
||||
* blocklist.dcf
|
||||
* migrations
|
||||
* vanity
|
||||
* navmeshes
|
||||
* 1 of the following lists based on platform
|
||||
* windows
|
||||
* libmariadb.dll
|
||||
* mariadbcpp.dll
|
||||
* zlib.dll
|
||||
* MacOS
|
||||
* libmariadbcpp.dylib
|
||||
* *nix
|
||||
* libmariadbcpp.so
|
||||
|
||||
## Running the server
|
||||
If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you either have to give the `AuthServer` binary network permissions or run it under sudo.
|
||||
To give `AuthServer` network permissions and not require sudo, run the following command
|
||||
If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you have to give the `AuthServer` binary network permissions by running the following command:
|
||||
```bash
|
||||
sudo setcap 'cap_net_bind_service=+ep' AuthServer
|
||||
```
|
||||
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
|
||||
|
||||
### Linux Service
|
||||
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
|
||||
@ -307,8 +330,14 @@ To connect to a server follow these steps:
|
||||
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
|
||||
* Next locate the line `UGCUSE3DSERVICES=7:`
|
||||
* Ensure the number after the 7 is a `0`
|
||||
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
|
||||
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
|
||||
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
|
||||
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78
|
||||
```cfg
|
||||
AUTHSERVERIP=0:12.34.56.78,
|
||||
UGCUSE3DSERVICES=7:0
|
||||
```
|
||||
|
||||
## Updating your server
|
||||
To update your server to the latest version navigate to your cloned directory
|
||||
@ -325,6 +354,10 @@ Now follow the [build](#build-the-server) section for your system and your serve
|
||||
## In-game commands
|
||||
* A list of all in-game commands can be found [here](./docs/Commands.md).
|
||||
|
||||
## Chat Web API
|
||||
* The Chat server has an API that can be enabled via `chatconfig.ini`.
|
||||
* You can view the OpenAPI doc for the API here [here](./docs/ChatWebAPI.yaml).
|
||||
|
||||
## Verifying your client files
|
||||
|
||||
### LEGO® Universe 1.10.64
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "Game.h"
|
||||
#include "Server.h"
|
||||
|
||||
|
||||
namespace Game {
|
||||
Logger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
@ -71,12 +70,15 @@ int main(int argc, char** argv) {
|
||||
//Find out the master's IP:
|
||||
std::string masterIP;
|
||||
uint32_t masterPort = 1500;
|
||||
std::string masterPassword;
|
||||
|
||||
auto masterInfo = Database::Get()->GetMasterInfo();
|
||||
if (masterInfo) {
|
||||
masterIP = masterInfo->ip;
|
||||
masterPort = masterInfo->port;
|
||||
masterPassword = masterInfo->password;
|
||||
}
|
||||
|
||||
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
|
||||
|
||||
Game::randomEngine = std::mt19937(time(0));
|
||||
@ -90,7 +92,7 @@ int main(int argc, char** argv) {
|
||||
const auto externalIPString = Game::config->GetValue("external_ip");
|
||||
if (!externalIPString.empty()) ourIP = externalIPString;
|
||||
|
||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal);
|
||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal, masterPassword);
|
||||
|
||||
//Run it until server gets a kill message from Master:
|
||||
auto t = std::chrono::high_resolution_clock::now();
|
||||
|
@ -1,7 +1,4 @@
|
||||
add_executable(AuthServer "AuthServer.cpp")
|
||||
|
||||
target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer)
|
||||
|
||||
target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer)
|
||||
|
||||
add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
|
||||
|
@ -105,7 +105,7 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowLis
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
|
||||
std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
|
||||
if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true.
|
||||
if (message.empty()) return { };
|
||||
if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } };
|
||||
@ -114,7 +114,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
|
||||
std::string segment;
|
||||
std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)");
|
||||
|
||||
std::vector<std::pair<uint8_t, uint8_t>> listOfBadSegments = std::vector<std::pair<uint8_t, uint8_t>>();
|
||||
std::set<std::pair<uint8_t, uint8_t>> listOfBadSegments;
|
||||
|
||||
uint32_t position = 0;
|
||||
|
||||
@ -127,17 +127,17 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
|
||||
size_t hash = CalculateHash(segment);
|
||||
|
||||
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) {
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) {
|
||||
m_UserUnapprovedWordCache.push_back(hash);
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) {
|
||||
m_UserUnapprovedWordCache.push_back(hash);
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
position += originalSegment.length() + 1;
|
||||
|
@ -24,7 +24,7 @@ public:
|
||||
void ReadWordlistPlaintext(const std::string& filepath, bool allowList);
|
||||
bool ReadWordlistDCF(const std::string& filepath, bool allowList);
|
||||
void ExportWordlistToDCF(const std::string& filepath, bool allowList);
|
||||
std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
|
||||
std::set<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
|
||||
|
||||
private:
|
||||
bool m_DontGenerateDCF;
|
||||
|
@ -1,16 +1,19 @@
|
||||
set(DCHATSERVER_SOURCES
|
||||
"ChatIgnoreList.cpp"
|
||||
"ChatPacketHandler.cpp"
|
||||
"ChatJSONUtils.cpp"
|
||||
"ChatWeb.cpp"
|
||||
"PlayerContainer.cpp"
|
||||
"TeamContainer.cpp"
|
||||
)
|
||||
|
||||
add_executable(ChatServer "ChatServer.cpp")
|
||||
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter")
|
||||
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter" "${PROJECT_SOURCE_DIR}/dWeb")
|
||||
add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
|
||||
|
||||
add_library(dChatServer ${DCHATSERVER_SOURCES})
|
||||
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
|
||||
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter")
|
||||
|
||||
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer)
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
||||
// not allowing teams, rejecting DMs, friends requets etc.
|
||||
// The only thing not auto-handled is instance activities force joining the team on the server.
|
||||
|
||||
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const MessageType::Client type) {
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receivingPlayer);
|
||||
|
||||
//portion that will get routed:
|
||||
@ -48,9 +48,9 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
|
||||
}
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::GET_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::GET_IGNORE_LIST_RESPONSE);
|
||||
|
||||
bitStream.Write<uint8_t>(false); // Probably is Is Free Trial, but we don't care about that
|
||||
bitStream.Write<uint8_t>(false); // Is Free Trial, but we don't care about that
|
||||
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
|
||||
|
||||
bitStream.Write<uint16_t>(receiver.ignoredPlayers.size());
|
||||
@ -86,7 +86,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
|
||||
std::string toIgnoreStr = toIgnoreName.GetAsString();
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::ADD_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::ADD_IGNORE_RESPONSE);
|
||||
|
||||
// Check if the player exists
|
||||
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
|
||||
@ -161,7 +161,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
|
||||
receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end());
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::REMOVE_IGNORE_RESPONSE);
|
||||
|
||||
bitStream.Write<int8_t>(0);
|
||||
LUWString playerNameSend(removedIgnoreStr, 33);
|
||||
|
@ -5,17 +5,16 @@ struct Packet;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief The ignore list allows players to ignore someone silently. Requests will generally be blocked by the client, but they should also be checked
|
||||
* on the server as well so the sender can get a generic error code in response.
|
||||
*
|
||||
*/
|
||||
namespace ChatIgnoreList {
|
||||
void GetIgnoreList(Packet* packet);
|
||||
void AddIgnore(Packet* packet);
|
||||
void RemoveIgnore(Packet* packet);
|
||||
|
||||
enum class Response : uint8_t {
|
||||
ADD_IGNORE = 32,
|
||||
REMOVE_IGNORE = 33,
|
||||
GET_IGNORE = 34,
|
||||
};
|
||||
|
||||
enum class AddResponse : uint8_t {
|
||||
SUCCESS,
|
||||
ALREADY_IGNORED,
|
||||
|
49
dChatServer/ChatJSONUtils.cpp
Normal file
49
dChatServer/ChatJSONUtils.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "ChatJSONUtils.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void to_json(json& data, const PlayerData& playerData) {
|
||||
data["id"] = playerData.playerID;
|
||||
data["name"] = playerData.playerName;
|
||||
data["gm_level"] = playerData.gmLevel;
|
||||
data["muted"] = playerData.GetIsMuted();
|
||||
|
||||
auto& zoneID = data["zone_id"];
|
||||
zoneID["map_id"] = playerData.zoneID.GetMapID();
|
||||
zoneID["instance_id"] = playerData.zoneID.GetInstanceID();
|
||||
zoneID["clone_id"] = playerData.zoneID.GetCloneID();
|
||||
}
|
||||
|
||||
void to_json(json& data, const PlayerContainer& playerContainer) {
|
||||
data = json::array();
|
||||
for (auto& playerData : playerContainer.GetAllPlayers()) {
|
||||
if (playerData.first == LWOOBJID_EMPTY) continue;
|
||||
data.push_back(playerData.second);
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& data, const TeamData& teamData) {
|
||||
data["id"] = teamData.teamID;
|
||||
data["loot_flag"] = teamData.lootFlag;
|
||||
data["local"] = teamData.local;
|
||||
|
||||
auto& leader = Game::playerContainer.GetPlayerData(teamData.leaderID);
|
||||
data["leader"] = leader.playerName;
|
||||
|
||||
auto& members = data["members"];
|
||||
for (auto& member : teamData.memberIDs) {
|
||||
auto& playerData = Game::playerContainer.GetPlayerData(member);
|
||||
|
||||
if (!playerData) continue;
|
||||
members.push_back(playerData);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) {
|
||||
for (auto& teamData : TeamContainer::GetTeams()) {
|
||||
if (!teamData) continue;
|
||||
data.push_back(*teamData);
|
||||
}
|
||||
}
|
18
dChatServer/ChatJSONUtils.h
Normal file
18
dChatServer/ChatJSONUtils.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef __CHATJSONUTILS_H__
|
||||
#define __CHATJSONUTILS_H__
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
#include "PlayerContainer.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
/* Remember, to_json needs to be in the same namespace as the class its located in */
|
||||
|
||||
void to_json(nlohmann::json& data, const PlayerData& playerData);
|
||||
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
|
||||
void to_json(nlohmann::json& data, const TeamData& teamData);
|
||||
|
||||
namespace TeamContainer {
|
||||
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
|
||||
};
|
||||
|
||||
#endif // !__CHATJSONUTILS_H__
|
@ -19,6 +19,7 @@
|
||||
#include "StringifiedEnum.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
//Get from the packet which player we want to do something with:
|
||||
@ -49,7 +50,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
fd.zoneID = fr.zoneID;
|
||||
|
||||
//Since this friend is online, we need to update them on the fact that we've just logged in:
|
||||
SendFriendUpdate(fr, player, 1, fd.isBestFriend);
|
||||
if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend);
|
||||
} else {
|
||||
fd.isOnline = false;
|
||||
fd.zoneID = LWOZONEID();
|
||||
@ -73,7 +74,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
data.Serialize(bitStream);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = player.sysAddr;
|
||||
SystemAddress sysAddr = player.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -103,7 +104,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
return;
|
||||
};
|
||||
|
||||
auto& requestee = Game::playerContainer.GetPlayerDataMutable(playerName);
|
||||
// Intentional copy
|
||||
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
|
||||
|
||||
// Check if player is online first
|
||||
if (isBestFriendRequest && !requestee) {
|
||||
@ -121,7 +123,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
requesteeFriendData.isOnline = false;
|
||||
requesteeFriendData.zoneID = requestor.zoneID;
|
||||
requestee.friends.push_back(requesteeFriendData);
|
||||
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -140,7 +142,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
|
||||
// Prevent GM friend spam
|
||||
// If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process
|
||||
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN ) {
|
||||
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) {
|
||||
SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN);
|
||||
return;
|
||||
}
|
||||
@ -188,24 +190,29 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
|
||||
// Sent the best friend update here if the value is 3
|
||||
if (bestFriendStatus == 3U) {
|
||||
requestee.countOfBestFriends += 1;
|
||||
requestor.countOfBestFriends += 1;
|
||||
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
|
||||
for (auto& friendData : requestor.friends) {
|
||||
if (friendData.friendID == requestee.playerID) {
|
||||
friendData.isBestFriend = true;
|
||||
}
|
||||
}
|
||||
for (auto& friendData : requestee.friends) {
|
||||
if (friendData.friendID == requestor.playerID) {
|
||||
friendData.isBestFriend = true;
|
||||
requestor.countOfBestFriends += 1;
|
||||
|
||||
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName);
|
||||
if (toModify) {
|
||||
for (auto& friendData : toModify.friends) {
|
||||
if (friendData.friendID == requestor.playerID) {
|
||||
friendData.isBestFriend = true;
|
||||
}
|
||||
}
|
||||
toModify.countOfBestFriends += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
|
||||
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
|
||||
}
|
||||
} else {
|
||||
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
|
||||
@ -378,7 +385,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
|
||||
bitStream.Write(player.zoneID.GetCloneID());
|
||||
bitStream.Write(request.playerName);
|
||||
|
||||
SystemAddress sysAddr = sender.sysAddr;
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -400,8 +407,8 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
|
||||
bitStream.Write(Game::playerContainer.GetSimCount());
|
||||
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
|
||||
bitStream.Write<uint8_t>(request.displayZoneData);
|
||||
if (request.displayZoneData || request.displayIndividualPlayers){
|
||||
for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
|
||||
if (request.displayZoneData || request.displayIndividualPlayers) {
|
||||
for (auto& [playerID, playerData] : Game::playerContainer.GetAllPlayers()) {
|
||||
if (!playerData) continue;
|
||||
bitStream.Write<uint8_t>(0); // structure packing
|
||||
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
|
||||
@ -412,7 +419,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
|
||||
}
|
||||
}
|
||||
}
|
||||
SystemAddress sysAddr = sender.sysAddr;
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -441,7 +448,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
|
||||
|
||||
switch (channel) {
|
||||
case eChatChannel::TEAM: {
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
auto* team = TeamContainer::GetTeam(playerID);
|
||||
if (team == nullptr) return;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
@ -513,6 +520,28 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
|
||||
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
|
||||
ChatPackets::AchievementNotify notify{};
|
||||
notify.Deserialize(bitstream);
|
||||
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
|
||||
if (!playerData) return;
|
||||
|
||||
for (const auto& myFriend : playerData.friends) {
|
||||
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
|
||||
if (friendData) {
|
||||
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
|
||||
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
|
||||
|
||||
RakNet::BitStream worldStream;
|
||||
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
worldStream.Write(friendData.playerID);
|
||||
notify.WriteHeader(worldStream);
|
||||
notify.Serialize(worldStream);
|
||||
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
@ -531,387 +560,7 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
|
||||
bitStream.Write(responseCode);
|
||||
bitStream.Write(message);
|
||||
|
||||
SystemAddress sysAddr = routeTo.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
|
||||
void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID;
|
||||
LUWString invitedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(invitedPlayer);
|
||||
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!player) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team == nullptr) {
|
||||
team = Game::playerContainer.CreateTeam(playerID);
|
||||
}
|
||||
|
||||
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
|
||||
|
||||
if (!other) return;
|
||||
|
||||
if (Game::playerContainer.GetTeam(other.playerID) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() > 3) {
|
||||
// no more teams greater than 4
|
||||
|
||||
LOG("Someone tried to invite a 5th player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
SendTeamInvite(other, player);
|
||||
|
||||
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
|
||||
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
|
||||
|
||||
if (declined) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(leaderID);
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for leader (%llu)", leaderID);
|
||||
|
||||
team = Game::playerContainer.GetTeam(playerID);
|
||||
}
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for player (%llu)", playerID);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::playerContainer.AddMember(team, playerID);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
LOG("(%llu) leaving team", playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamKick(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString kickedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(kickedPlayer);
|
||||
|
||||
|
||||
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
|
||||
|
||||
LWOOBJID kickedId = LWOOBJID_EMPTY;
|
||||
|
||||
if (kicked) {
|
||||
kickedId = kicked.playerID;
|
||||
} else {
|
||||
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
|
||||
}
|
||||
|
||||
if (kickedId == LWOOBJID_EMPTY) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID || team->leaderID == kickedId) return;
|
||||
|
||||
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString promotedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(promotedPlayer);
|
||||
|
||||
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
|
||||
|
||||
if (!promoted) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
Game::playerContainer.PromoteMember(team, promoted.playerID);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
team->lootFlag = option;
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
const auto& data = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (team != nullptr && data) {
|
||||
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
|
||||
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1 && !team->local) {
|
||||
Game::playerContainer.DisbandTeam(team);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, team->leaderID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (memberId == playerID) continue;
|
||||
|
||||
const auto memberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
|
||||
}
|
||||
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
}
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = routeTo.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -953,7 +602,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
|
||||
bitStream.Write<uint8_t>(isBestFriend); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
|
||||
SystemAddress sysAddr = friendData.sysAddr;
|
||||
SystemAddress sysAddr = friendData.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -975,7 +624,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -988,7 +637,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
|
||||
bitStream.Write(responseCode);
|
||||
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
|
||||
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
// Then write the player name
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
// Then if this is an acceptance code, write the following extra info.
|
||||
@ -998,7 +647,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
|
||||
bitStream.Write(isBestFriendRequest); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
}
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@ -1012,6 +661,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
|
||||
bitStream.Write<uint8_t>(isSuccessful); //isOnline
|
||||
bitStream.Write(LUWString(personToRemove));
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
@ -35,13 +35,13 @@ enum class eChatChannel : uint8_t {
|
||||
|
||||
|
||||
enum class eChatMessageResponseCode : uint8_t {
|
||||
SENT = 0,
|
||||
NOTONLINE,
|
||||
GENERALERROR,
|
||||
RECEIVEDNEWWHISPER,
|
||||
NOTFRIENDS,
|
||||
SENDERFREETRIAL,
|
||||
RECEIVERFREETRIAL,
|
||||
SENT = 0,
|
||||
NOTONLINE,
|
||||
GENERALERROR,
|
||||
RECEIVEDNEWWHISPER,
|
||||
NOTFRIENDS,
|
||||
SENDERFREETRIAL,
|
||||
RECEIVERFREETRIAL,
|
||||
};
|
||||
|
||||
namespace ChatPacketHandler {
|
||||
@ -52,30 +52,14 @@ namespace ChatPacketHandler {
|
||||
void HandleGMLevelUpdate(Packet* packet);
|
||||
void HandleWho(Packet* packet);
|
||||
void HandleShowAll(Packet* packet);
|
||||
|
||||
void HandleChatMessage(Packet* packet);
|
||||
void HandlePrivateChatMessage(Packet* packet);
|
||||
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
|
||||
|
||||
void HandleTeamInvite(Packet* packet);
|
||||
void HandleTeamInviteResponse(Packet* packet);
|
||||
void HandleTeamLeave(Packet* packet);
|
||||
void HandleTeamKick(Packet* packet);
|
||||
void HandleTeamPromote(Packet* packet);
|
||||
void HandleTeamLootOption(Packet* packet);
|
||||
void HandleTeamStatusRequest(Packet* packet);
|
||||
|
||||
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
|
||||
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
|
||||
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
|
||||
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
|
||||
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
|
||||
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
|
||||
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
|
||||
|
||||
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
|
||||
void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend);
|
||||
|
||||
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
|
||||
void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
|
||||
void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "MessageType/World.h"
|
||||
#include "ChatIgnoreList.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Server.h"
|
||||
@ -28,6 +29,8 @@
|
||||
#include "RakNetDefines.h"
|
||||
#include "MessageIdentifiers.h"
|
||||
|
||||
#include "ChatWeb.h"
|
||||
|
||||
namespace Game {
|
||||
Logger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
@ -74,7 +77,8 @@ int main(int argc, char** argv) {
|
||||
Game::assetManager = new AssetManager(clientPath);
|
||||
} catch (std::runtime_error& ex) {
|
||||
LOG("Got an error while setting up assets: %s", ex.what());
|
||||
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -84,18 +88,33 @@ int main(int argc, char** argv) {
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Got an error while connecting to the database: %s", ex.what());
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// setup the chat api web server
|
||||
const uint32_t web_server_port = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("web_server_port")).value_or(2005);
|
||||
if (Game::config->GetValue("web_server_enabled") == "1" && !Game::web.Startup("localhost", web_server_port)) {
|
||||
// if we want the web server and it fails to start, exit
|
||||
LOG("Failed to start web server, shutting down.");
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (Game::web.IsEnabled()) ChatWeb::RegisterRoutes();
|
||||
|
||||
//Find out the master's IP:
|
||||
std::string masterIP;
|
||||
uint32_t masterPort = 1000;
|
||||
std::string masterPassword;
|
||||
auto masterInfo = Database::Get()->GetMasterInfo();
|
||||
if (masterInfo) {
|
||||
masterIP = masterInfo->ip;
|
||||
masterPort = masterInfo->port;
|
||||
masterPassword = masterInfo->password;
|
||||
}
|
||||
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
|
||||
std::string ourIP = "localhost";
|
||||
@ -104,7 +123,7 @@ int main(int argc, char** argv) {
|
||||
const auto externalIPString = Game::config->GetValue("external_ip");
|
||||
if (!externalIPString.empty()) ourIP = externalIPString;
|
||||
|
||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal);
|
||||
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword);
|
||||
|
||||
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
|
||||
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
|
||||
@ -122,6 +141,8 @@ int main(int argc, char** argv) {
|
||||
uint32_t framesSinceMasterDisconnect = 0;
|
||||
uint32_t framesSinceLastSQLPing = 0;
|
||||
|
||||
auto lastTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
Game::logger->Flush(); // once immediately before main loop
|
||||
while (!Game::ShouldShutdown()) {
|
||||
//Check if we're still connected to master:
|
||||
@ -132,7 +153,11 @@ int main(int argc, char** argv) {
|
||||
break; //Exit our loop, shut down.
|
||||
} else framesSinceMasterDisconnect = 0;
|
||||
|
||||
//In world we'd update our other systems here.
|
||||
const auto currentTime = std::chrono::high_resolution_clock::now();
|
||||
const float deltaTime = std::chrono::duration<float>(currentTime - lastTime).count();
|
||||
lastTime = currentTime;
|
||||
|
||||
Game::playerContainer.Update(deltaTime);
|
||||
|
||||
//Check for packets here:
|
||||
Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed.
|
||||
@ -143,6 +168,9 @@ int main(int argc, char** argv) {
|
||||
packet = nullptr;
|
||||
}
|
||||
|
||||
// Check and handle web requests:
|
||||
if (Game::web.IsEnabled()) Game::web.ReceiveRequests();
|
||||
|
||||
//Push our log every 30s:
|
||||
if (framesSinceLastFlush >= logFlushTime) {
|
||||
Game::logger->Flush();
|
||||
@ -168,7 +196,8 @@ int main(int argc, char** argv) {
|
||||
t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps"
|
||||
std::this_thread::sleep_until(t);
|
||||
}
|
||||
|
||||
Game::playerContainer.Shutdown();
|
||||
TeamContainer::Shutdown();
|
||||
//Delete our objects here:
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
@ -196,151 +225,156 @@ void HandlePacket(Packet* packet) {
|
||||
if (connection != eConnectionType::CHAT) return;
|
||||
inStream.Read(chatMessageID);
|
||||
|
||||
// Our packing byte wasnt there? Probably a false packet
|
||||
if (inStream.GetNumberOfUnreadBits() < 8) return;
|
||||
inStream.IgnoreBytes(1);
|
||||
|
||||
switch (chatMessageID) {
|
||||
case MessageType::Chat::GM_MUTE:
|
||||
Game::playerContainer.MuteUpdate(packet);
|
||||
break;
|
||||
case MessageType::Chat::GM_MUTE:
|
||||
Game::playerContainer.MuteUpdate(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::CREATE_TEAM:
|
||||
Game::playerContainer.CreateTeamServer(packet);
|
||||
break;
|
||||
case MessageType::Chat::CREATE_TEAM:
|
||||
TeamContainer::CreateTeamServer(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::GET_FRIENDS_LIST:
|
||||
ChatPacketHandler::HandleFriendlistRequest(packet);
|
||||
break;
|
||||
case MessageType::Chat::GET_FRIENDS_LIST:
|
||||
ChatPacketHandler::HandleFriendlistRequest(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::GET_IGNORE_LIST:
|
||||
ChatIgnoreList::GetIgnoreList(packet);
|
||||
break;
|
||||
case MessageType::Chat::GET_IGNORE_LIST:
|
||||
ChatIgnoreList::GetIgnoreList(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::ADD_IGNORE:
|
||||
ChatIgnoreList::AddIgnore(packet);
|
||||
break;
|
||||
case MessageType::Chat::ADD_IGNORE:
|
||||
ChatIgnoreList::AddIgnore(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::REMOVE_IGNORE:
|
||||
ChatIgnoreList::RemoveIgnore(packet);
|
||||
break;
|
||||
case MessageType::Chat::REMOVE_IGNORE:
|
||||
ChatIgnoreList::RemoveIgnore(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_GET_STATUS:
|
||||
ChatPacketHandler::HandleTeamStatusRequest(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_GET_STATUS:
|
||||
TeamContainer::HandleTeamStatusRequest(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::ADD_FRIEND_REQUEST:
|
||||
//this involves someone sending the initial request, the response is below, response as in from the other player.
|
||||
//We basically just check to see if this player is online or not and route the packet.
|
||||
ChatPacketHandler::HandleFriendRequest(packet);
|
||||
break;
|
||||
case MessageType::Chat::ADD_FRIEND_REQUEST:
|
||||
//this involves someone sending the initial request, the response is below, response as in from the other player.
|
||||
//We basically just check to see if this player is online or not and route the packet.
|
||||
ChatPacketHandler::HandleFriendRequest(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::ADD_FRIEND_RESPONSE:
|
||||
//This isn't the response a server sent, rather it is a player's response to a received request.
|
||||
//Here, we'll actually have to add them to eachother's friend lists depending on the response code.
|
||||
ChatPacketHandler::HandleFriendResponse(packet);
|
||||
break;
|
||||
case MessageType::Chat::ADD_FRIEND_RESPONSE:
|
||||
//This isn't the response a server sent, rather it is a player's response to a received request.
|
||||
//Here, we'll actually have to add them to eachother's friend lists depending on the response code.
|
||||
ChatPacketHandler::HandleFriendResponse(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::REMOVE_FRIEND:
|
||||
ChatPacketHandler::HandleRemoveFriend(packet);
|
||||
break;
|
||||
case MessageType::Chat::REMOVE_FRIEND:
|
||||
ChatPacketHandler::HandleRemoveFriend(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::GENERAL_CHAT_MESSAGE:
|
||||
ChatPacketHandler::HandleChatMessage(packet);
|
||||
break;
|
||||
case MessageType::Chat::GENERAL_CHAT_MESSAGE:
|
||||
ChatPacketHandler::HandleChatMessage(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::PRIVATE_CHAT_MESSAGE:
|
||||
//This message is supposed to be echo'd to both the sender and the receiver
|
||||
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
|
||||
ChatPacketHandler::HandlePrivateChatMessage(packet);
|
||||
break;
|
||||
case MessageType::Chat::PRIVATE_CHAT_MESSAGE:
|
||||
//This message is supposed to be echo'd to both the sender and the receiver
|
||||
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
|
||||
ChatPacketHandler::HandlePrivateChatMessage(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_INVITE:
|
||||
ChatPacketHandler::HandleTeamInvite(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_INVITE:
|
||||
TeamContainer::HandleTeamInvite(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_INVITE_RESPONSE:
|
||||
ChatPacketHandler::HandleTeamInviteResponse(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_INVITE_RESPONSE:
|
||||
TeamContainer::HandleTeamInviteResponse(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_LEAVE:
|
||||
ChatPacketHandler::HandleTeamLeave(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_LEAVE:
|
||||
TeamContainer::HandleTeamLeave(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_SET_LEADER:
|
||||
ChatPacketHandler::HandleTeamPromote(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_SET_LEADER:
|
||||
TeamContainer::HandleTeamPromote(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_KICK:
|
||||
ChatPacketHandler::HandleTeamKick(packet);
|
||||
break;
|
||||
case MessageType::Chat::TEAM_KICK:
|
||||
TeamContainer::HandleTeamKick(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_SET_LOOT:
|
||||
ChatPacketHandler::HandleTeamLootOption(packet);
|
||||
break;
|
||||
case MessageType::Chat::GMLEVEL_UPDATE:
|
||||
ChatPacketHandler::HandleGMLevelUpdate(packet);
|
||||
break;
|
||||
case MessageType::Chat::LOGIN_SESSION_NOTIFY:
|
||||
Game::playerContainer.InsertPlayer(packet);
|
||||
break;
|
||||
case MessageType::Chat::GM_ANNOUNCE:{
|
||||
// we just forward this packet to every connected server
|
||||
inStream.ResetReadPointer();
|
||||
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
|
||||
}
|
||||
break;
|
||||
case MessageType::Chat::UNEXPECTED_DISCONNECT:
|
||||
Game::playerContainer.RemovePlayer(packet);
|
||||
break;
|
||||
case MessageType::Chat::WHO:
|
||||
ChatPacketHandler::HandleWho(packet);
|
||||
break;
|
||||
case MessageType::Chat::SHOW_ALL:
|
||||
ChatPacketHandler::HandleShowAll(packet);
|
||||
break;
|
||||
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
|
||||
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
|
||||
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
|
||||
case MessageType::Chat::WORLD_PARCEL_RESPONSE:
|
||||
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK:
|
||||
case MessageType::Chat::GUILD_CREATE:
|
||||
case MessageType::Chat::GUILD_INVITE:
|
||||
case MessageType::Chat::GUILD_INVITE_RESPONSE:
|
||||
case MessageType::Chat::GUILD_LEAVE:
|
||||
case MessageType::Chat::GUILD_KICK:
|
||||
case MessageType::Chat::GUILD_GET_STATUS:
|
||||
case MessageType::Chat::GUILD_GET_ALL:
|
||||
case MessageType::Chat::BLUEPRINT_MODERATED:
|
||||
case MessageType::Chat::BLUEPRINT_MODEL_READY:
|
||||
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL:
|
||||
case MessageType::Chat::PROPERTY_MODERATION_CHANGED:
|
||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED:
|
||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT:
|
||||
case MessageType::Chat::MAIL:
|
||||
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST:
|
||||
case MessageType::Chat::REPUTATION_UPDATE:
|
||||
case MessageType::Chat::SEND_CANNED_TEXT:
|
||||
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST:
|
||||
case MessageType::Chat::CSR_REQUEST:
|
||||
case MessageType::Chat::CSR_REPLY:
|
||||
case MessageType::Chat::GM_KICK:
|
||||
case MessageType::Chat::WORLD_ROUTE_PACKET:
|
||||
case MessageType::Chat::GET_ZONE_POPULATIONS:
|
||||
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE:
|
||||
case MessageType::Chat::MATCH_REQUEST:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
|
||||
case MessageType::Chat::UGCC_REQUEST:
|
||||
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
|
||||
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
||||
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
|
||||
case MessageType::Chat::PLAYER_READY:
|
||||
case MessageType::Chat::GET_DONATION_TOTAL:
|
||||
case MessageType::Chat::UPDATE_DONATION:
|
||||
case MessageType::Chat::PRG_CSR_COMMAND:
|
||||
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD:
|
||||
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS:
|
||||
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
|
||||
break;
|
||||
default:
|
||||
LOG("Unknown CHAT Message id: %i", chatMessageID);
|
||||
case MessageType::Chat::TEAM_SET_LOOT:
|
||||
TeamContainer::HandleTeamLootOption(packet);
|
||||
break;
|
||||
case MessageType::Chat::GMLEVEL_UPDATE:
|
||||
ChatPacketHandler::HandleGMLevelUpdate(packet);
|
||||
break;
|
||||
case MessageType::Chat::LOGIN_SESSION_NOTIFY:
|
||||
Game::playerContainer.InsertPlayer(packet);
|
||||
break;
|
||||
case MessageType::Chat::GM_ANNOUNCE:
|
||||
// we just forward this packet to every connected server
|
||||
inStream.ResetReadPointer();
|
||||
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
|
||||
break;
|
||||
case MessageType::Chat::UNEXPECTED_DISCONNECT:
|
||||
Game::playerContainer.ScheduleRemovePlayer(packet);
|
||||
break;
|
||||
case MessageType::Chat::WHO:
|
||||
ChatPacketHandler::HandleWho(packet);
|
||||
break;
|
||||
case MessageType::Chat::SHOW_ALL:
|
||||
ChatPacketHandler::HandleShowAll(packet);
|
||||
break;
|
||||
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
||||
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
|
||||
break;
|
||||
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
|
||||
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
|
||||
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
|
||||
case MessageType::Chat::WORLD_PARCEL_RESPONSE:
|
||||
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK:
|
||||
case MessageType::Chat::GUILD_CREATE:
|
||||
case MessageType::Chat::GUILD_INVITE:
|
||||
case MessageType::Chat::GUILD_INVITE_RESPONSE:
|
||||
case MessageType::Chat::GUILD_LEAVE:
|
||||
case MessageType::Chat::GUILD_KICK:
|
||||
case MessageType::Chat::GUILD_GET_STATUS:
|
||||
case MessageType::Chat::GUILD_GET_ALL:
|
||||
case MessageType::Chat::BLUEPRINT_MODERATED:
|
||||
case MessageType::Chat::BLUEPRINT_MODEL_READY:
|
||||
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL:
|
||||
case MessageType::Chat::PROPERTY_MODERATION_CHANGED:
|
||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED:
|
||||
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT:
|
||||
case MessageType::Chat::MAIL:
|
||||
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST:
|
||||
case MessageType::Chat::REPUTATION_UPDATE:
|
||||
case MessageType::Chat::SEND_CANNED_TEXT:
|
||||
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST:
|
||||
case MessageType::Chat::CSR_REQUEST:
|
||||
case MessageType::Chat::CSR_REPLY:
|
||||
case MessageType::Chat::GM_KICK:
|
||||
case MessageType::Chat::WORLD_ROUTE_PACKET:
|
||||
case MessageType::Chat::GET_ZONE_POPULATIONS:
|
||||
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE:
|
||||
case MessageType::Chat::MATCH_REQUEST:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE:
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
|
||||
case MessageType::Chat::UGCC_REQUEST:
|
||||
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
|
||||
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
|
||||
case MessageType::Chat::PLAYER_READY:
|
||||
case MessageType::Chat::GET_DONATION_TOTAL:
|
||||
case MessageType::Chat::UPDATE_DONATION:
|
||||
case MessageType::Chat::PRG_CSR_COMMAND:
|
||||
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD:
|
||||
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS:
|
||||
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
|
||||
break;
|
||||
default:
|
||||
LOG("Unknown CHAT Message id: %i", chatMessageID);
|
||||
}
|
||||
}
|
||||
|
133
dChatServer/ChatWeb.cpp
Normal file
133
dChatServer/ChatWeb.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
#include "ChatWeb.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
#include "json.hpp"
|
||||
#include "dCommonVars.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "dServer.h"
|
||||
#include "dConfig.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "eHTTPMethod.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "ChatPackets.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "Database.h"
|
||||
#include "ChatJSONUtils.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "dChatFilter.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = Game::playerContainer;
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = TeamContainer::GetTeamContainer();
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
|
||||
auto data = GeneralUtils::TryParse<json>(body);
|
||||
if (!data) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& good_data = data.value();
|
||||
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
|
||||
if (!check.empty()) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = check;
|
||||
} else {
|
||||
|
||||
ChatPackets::Announcement announcement;
|
||||
announcement.title = good_data["title"];
|
||||
announcement.message = good_data["message"];
|
||||
announcement.Broadcast();
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = "{\"status\":\"Announcement Sent\"}";
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWSChat(mg_connection* connection, json data) {
|
||||
auto check = JSONUtils::CheckRequiredData(data, { "user", "message", "gmlevel", "zone" });
|
||||
if (!check.empty()) {
|
||||
LOG_DEBUG("Received invalid websocket message: %s", check.c_str());
|
||||
} else {
|
||||
const auto user = data["user"].get<std::string>();
|
||||
const auto message = data["message"].get<std::string>();
|
||||
const auto gmlevel = GeneralUtils::TryParse<eGameMasterLevel>(data["gmlevel"].get<std::string>()).value_or(eGameMasterLevel::CIVILIAN);
|
||||
const auto zone = data["zone"].get<uint32_t>();
|
||||
|
||||
const auto filter_check = Game::chatFilter->IsSentenceOkay(message, gmlevel);
|
||||
if (!filter_check.empty()) {
|
||||
LOG_DEBUG("Chat message \"%s\" from %s was not allowed", message.c_str(), user.c_str());
|
||||
data["error"] = "Chat message blocked by filter";
|
||||
data["filtered"] = json::array();
|
||||
for (const auto& [start, len] : filter_check) {
|
||||
data["filtered"].push_back(message.substr(start, len));
|
||||
}
|
||||
mg_ws_send(connection, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT);
|
||||
return;
|
||||
}
|
||||
LOG("%s: %s", user.c_str(), message.c_str());
|
||||
|
||||
// TODO: Implement chat message handling from websocket message
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace ChatWeb {
|
||||
void RegisterRoutes() {
|
||||
|
||||
// REST API v1 routes
|
||||
|
||||
std::string v1_route = "/api/v1/";
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "players",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleHTTPPlayersRequest
|
||||
});
|
||||
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "teams",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleHTTPTeamsRequest
|
||||
});
|
||||
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "announce",
|
||||
.method = eHTTPMethod::POST,
|
||||
.handle = HandleHTTPAnnounceRequest
|
||||
});
|
||||
|
||||
// WebSocket Events Handlers
|
||||
|
||||
// Game::web.RegisterWSEvent({
|
||||
// .name = "chat",
|
||||
// .handle = HandleWSChat
|
||||
// });
|
||||
|
||||
// WebSocket subscriptions
|
||||
|
||||
Game::web.RegisterWSSubscription("player");
|
||||
}
|
||||
|
||||
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType) {
|
||||
json data;
|
||||
data["player_data"] = player;
|
||||
data["update_type"] = magic_enum::enum_name(activityType);
|
||||
Game::web.SendWSMessage("player", data);
|
||||
}
|
||||
}
|
||||
|
19
dChatServer/ChatWeb.h
Normal file
19
dChatServer/ChatWeb.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef __CHATWEB_H__
|
||||
#define __CHATWEB_H__
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "Web.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "IActivityLog.h"
|
||||
#include "ChatPacketHandler.h"
|
||||
|
||||
namespace ChatWeb {
|
||||
void RegisterRoutes();
|
||||
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType);
|
||||
};
|
||||
|
||||
|
||||
#endif // __CHATWEB_H__
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include "ChatPackets.h"
|
||||
#include "dConfig.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "ChatWeb.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
void PlayerContainer::Initialize() {
|
||||
m_MaxNumberOfBestFriends =
|
||||
@ -32,7 +34,10 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto isLogin = !m_Players.contains(playerId);
|
||||
auto& data = m_Players[playerId];
|
||||
data = PlayerData();
|
||||
data.isLogin = isLogin;
|
||||
data.playerID = playerId;
|
||||
|
||||
uint32_t len;
|
||||
@ -49,21 +54,41 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
|
||||
if (!inStream.Read(data.zoneID)) return;
|
||||
if (!inStream.Read(data.muteExpire)) return;
|
||||
if (!inStream.Read(data.gmLevel)) return;
|
||||
data.sysAddr = packet->systemAddress;
|
||||
data.worldServerSysAddr = packet->systemAddress;
|
||||
|
||||
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
m_PlayerCount++;
|
||||
|
||||
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
|
||||
ChatWeb::SendWSPlayerUpdate(data, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone);
|
||||
|
||||
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
|
||||
Database::Get()->UpdateActivityLog(data.playerID, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone, data.zoneID.GetMapID());
|
||||
m_PlayersToRemove.erase(playerId);
|
||||
}
|
||||
|
||||
void PlayerContainer::RemovePlayer(Packet* packet) {
|
||||
void PlayerContainer::ScheduleRemovePlayer(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID;
|
||||
LWOOBJID playerID{ LWOOBJID_EMPTY };
|
||||
inStream.Read(playerID);
|
||||
constexpr float updatePlayerOnLogoutTime = 20.0f;
|
||||
if (playerID != LWOOBJID_EMPTY) m_PlayersToRemove.insert_or_assign(playerID, updatePlayerOnLogoutTime);
|
||||
}
|
||||
|
||||
void PlayerContainer::Update(const float deltaTime) {
|
||||
for (auto it = m_PlayersToRemove.begin(); it != m_PlayersToRemove.end();) {
|
||||
auto& [id, time] = *it;
|
||||
time -= deltaTime;
|
||||
|
||||
if (time <= 0.0f) {
|
||||
RemovePlayer(id);
|
||||
it = m_PlayersToRemove.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||
//Before they get kicked, we need to also send a message to their friends saying that they disconnected.
|
||||
const auto& player = GetPlayerData(playerID);
|
||||
|
||||
@ -77,7 +102,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
|
||||
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend);
|
||||
}
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
auto* team = TeamContainer::GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
|
||||
@ -87,10 +112,12 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
|
||||
|
||||
m_PlayerCount--;
|
||||
LOG("Removed user: %llu", playerID);
|
||||
m_Players.erase(playerID);
|
||||
@ -118,40 +145,6 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
|
||||
BroadcastMuteUpdate(playerID, expire);
|
||||
}
|
||||
|
||||
void PlayerContainer::CreateTeamServer(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
size_t membersSize = 0;
|
||||
inStream.Read(membersSize);
|
||||
|
||||
if (membersSize >= 4) {
|
||||
LOG("Tried to create a team with more than 4 players");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
members.reserve(membersSize);
|
||||
|
||||
for (size_t i = 0; i < membersSize; i++) {
|
||||
LWOOBJID member;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
|
||||
LWOZONEID zoneId;
|
||||
|
||||
inStream.Read(zoneId);
|
||||
|
||||
auto* team = CreateLocalTeam(members);
|
||||
|
||||
if (team != nullptr) {
|
||||
team->zoneId = zoneId;
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE);
|
||||
@ -162,221 +155,6 @@ void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
|
||||
if (members.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TeamData* newTeam = nullptr;
|
||||
|
||||
for (const auto member : members) {
|
||||
auto* team = GetTeam(member);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, member, false, false, true);
|
||||
}
|
||||
|
||||
if (newTeam == nullptr) {
|
||||
newTeam = CreateTeam(member, true);
|
||||
} else {
|
||||
AddMember(newTeam, member);
|
||||
}
|
||||
}
|
||||
|
||||
newTeam->lootFlag = 1;
|
||||
|
||||
TeamStatusUpdate(newTeam);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
|
||||
auto* team = new TeamData();
|
||||
|
||||
team->teamID = ++m_TeamIDCounter;
|
||||
team->leaderID = leader;
|
||||
team->local = local;
|
||||
|
||||
mTeams.push_back(team);
|
||||
|
||||
AddMember(team, leader);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
|
||||
for (auto* team : mTeams) {
|
||||
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
|
||||
if (team->memberIDs.size() >= 4) {
|
||||
LOG("Tried to add player to team that already had 4 players");
|
||||
const auto& player = GetPlayerData(playerID);
|
||||
if (!player) return;
|
||||
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index != team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.push_back(playerID);
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
const auto& member = GetPlayerData(playerID);
|
||||
|
||||
if (!leader || !member) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
|
||||
|
||||
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, leader.playerID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (otherMember == member) continue;
|
||||
|
||||
const auto otherMemberName = GetName(memberId);
|
||||
|
||||
ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) {
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index == team->memberIDs.end()) return;
|
||||
|
||||
const auto& member = GetPlayerData(playerID);
|
||||
|
||||
if (member && !silent) {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
const auto memberName = GetName(playerID);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
if (silent && memberId == playerID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName);
|
||||
}
|
||||
|
||||
team->memberIDs.erase(index);
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
if (team->memberIDs.size() <= 1) {
|
||||
DisbandTeam(team);
|
||||
} else {
|
||||
if (playerID == team->leaderID) {
|
||||
PromoteMember(team, team->memberIDs[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
|
||||
team->leaderID = newLeader;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::DisbandTeam(TeamData* team) {
|
||||
const auto index = std::find(mTeams.begin(), mTeams.end(), team);
|
||||
|
||||
if (index == mTeams.end()) return;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
|
||||
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, true);
|
||||
|
||||
mTeams.erase(index);
|
||||
|
||||
delete team;
|
||||
}
|
||||
|
||||
void PlayerContainer::TeamStatusUpdate(TeamData* team) {
|
||||
const auto index = std::find(mTeams.begin(), mTeams.end(), team);
|
||||
|
||||
if (index == mTeams.end()) return;
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
|
||||
if (!leader) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
|
||||
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(team->teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(team->lootFlag);
|
||||
bitStream.Write<char>(team->memberIDs.size());
|
||||
for (const auto memberID : team->memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
|
||||
const auto iter = m_Names.find(playerID);
|
||||
|
||||
@ -417,3 +195,12 @@ const PlayerData& PlayerContainer::GetPlayerData(const LWOOBJID& playerID) {
|
||||
const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) {
|
||||
return GetPlayerDataMutable(playerName);
|
||||
}
|
||||
|
||||
void PlayerContainer::Shutdown() {
|
||||
m_Players.erase(LWOOBJID_EMPTY);
|
||||
while (!m_Players.empty()) {
|
||||
const auto& [id, playerData] = *m_Players.begin();
|
||||
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
|
||||
m_Players.erase(m_Players.begin());
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
enum class eGameMasterLevel : uint8_t;
|
||||
|
||||
struct TeamData;
|
||||
|
||||
struct IgnoreData {
|
||||
IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {}
|
||||
inline bool operator==(const std::string& other) const noexcept {
|
||||
@ -36,7 +38,7 @@ struct PlayerData {
|
||||
return muteExpire == 1 || muteExpire > time(NULL);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr{};
|
||||
SystemAddress worldServerSysAddr{};
|
||||
LWOZONEID zoneID{};
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
time_t muteExpire = 0;
|
||||
@ -46,8 +48,10 @@ struct PlayerData {
|
||||
std::vector<IgnoreData> ignoredPlayers;
|
||||
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
|
||||
bool isFTP = false;
|
||||
bool isLogin = false;
|
||||
};
|
||||
|
||||
|
||||
struct TeamData {
|
||||
TeamData();
|
||||
LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use
|
||||
@ -62,38 +66,31 @@ class PlayerContainer {
|
||||
public:
|
||||
void Initialize();
|
||||
void InsertPlayer(Packet* packet);
|
||||
void RemovePlayer(Packet* packet);
|
||||
void ScheduleRemovePlayer(Packet* packet);
|
||||
void RemovePlayer(const LWOOBJID playerID);
|
||||
void MuteUpdate(Packet* packet);
|
||||
void CreateTeamServer(Packet* packet);
|
||||
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
|
||||
void Shutdown();
|
||||
|
||||
const PlayerData& GetPlayerData(const LWOOBJID& playerID);
|
||||
const PlayerData& GetPlayerData(const std::string& playerName);
|
||||
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
|
||||
PlayerData& GetPlayerDataMutable(const std::string& playerName);
|
||||
uint32_t GetPlayerCount() { return m_PlayerCount; };
|
||||
uint32_t GetSimCount() { return m_SimCount; };
|
||||
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; };
|
||||
|
||||
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
|
||||
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
|
||||
TeamData* GetTeam(LWOOBJID playerID);
|
||||
void AddMember(TeamData* team, LWOOBJID playerID);
|
||||
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
|
||||
void PromoteMember(TeamData* team, LWOOBJID newLeader);
|
||||
void DisbandTeam(TeamData* team);
|
||||
void TeamStatusUpdate(TeamData* team);
|
||||
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
|
||||
std::u16string GetName(LWOOBJID playerID);
|
||||
LWOOBJID GetId(const std::u16string& playerName);
|
||||
void Update(const float deltaTime);
|
||||
|
||||
uint32_t GetPlayerCount() { return m_PlayerCount; };
|
||||
uint32_t GetSimCount() { return m_SimCount; };
|
||||
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() const { return m_Players; };
|
||||
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
|
||||
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
|
||||
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
|
||||
|
||||
private:
|
||||
LWOOBJID m_TeamIDCounter = 0;
|
||||
std::map<LWOOBJID, PlayerData> m_Players;
|
||||
std::vector<TeamData*> mTeams;
|
||||
std::unordered_map<LWOOBJID, std::u16string> m_Names;
|
||||
std::map<LWOOBJID, float> m_PlayersToRemove;
|
||||
uint32_t m_MaxNumberOfBestFriends = 5;
|
||||
uint32_t m_MaxNumberOfFriends = 50;
|
||||
uint32_t m_PlayerCount = 0;
|
||||
|
669
dChatServer/TeamContainer.cpp
Normal file
669
dChatServer/TeamContainer.cpp
Normal file
@ -0,0 +1,669 @@
|
||||
#include "TeamContainer.h"
|
||||
|
||||
#include "ChatPackets.h"
|
||||
|
||||
#include "MessageType/Chat.h"
|
||||
#include "MessageType/Game.h"
|
||||
|
||||
#include "ChatPacketHandler.h"
|
||||
#include "PlayerContainer.h"
|
||||
|
||||
namespace {
|
||||
TeamContainer::Data g_TeamContainer{};
|
||||
LWOOBJID g_TeamIDCounter = 0;
|
||||
}
|
||||
|
||||
const TeamContainer::Data& TeamContainer::GetTeamContainer() {
|
||||
return g_TeamContainer;
|
||||
}
|
||||
|
||||
std::vector<TeamData*>& TeamContainer::GetTeamsMut() {
|
||||
return g_TeamContainer.mTeams;
|
||||
}
|
||||
|
||||
const std::vector<TeamData*>& TeamContainer::GetTeams() {
|
||||
return GetTeamsMut();
|
||||
}
|
||||
|
||||
void TeamContainer::Shutdown() {
|
||||
for (auto* team : g_TeamContainer.mTeams) if (team) delete team;
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamInvite(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID;
|
||||
LUWString invitedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(invitedPlayer);
|
||||
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!player) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team == nullptr) {
|
||||
team = CreateTeam(playerID);
|
||||
}
|
||||
|
||||
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
|
||||
|
||||
if (!other) return;
|
||||
|
||||
if (GetTeam(other.playerID) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() > 3) {
|
||||
// no more teams greater than 4
|
||||
|
||||
LOG("Someone tried to invite a 5th player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
SendTeamInvite(other, player);
|
||||
|
||||
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
|
||||
|
||||
bool failed = false;
|
||||
for (const auto& ignore : other.ignoredPlayers) {
|
||||
if (ignore.playerId == player.playerID) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ChatPackets::TeamInviteInitialResponse response{};
|
||||
response.inviteFailedToSend = failed;
|
||||
response.playerName = invitedPlayer.string;
|
||||
ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr);
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamInviteResponse(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
|
||||
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
|
||||
|
||||
if (declined) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* team = GetTeam(leaderID);
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for leader (%llu)", leaderID);
|
||||
|
||||
team = GetTeam(playerID);
|
||||
}
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for player (%llu)", playerID);
|
||||
return;
|
||||
}
|
||||
|
||||
AddMember(team, playerID);
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamLeave(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
LOG("(%llu) leaving team", playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamKick(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString kickedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(kickedPlayer);
|
||||
|
||||
|
||||
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
|
||||
|
||||
LWOOBJID kickedId = LWOOBJID_EMPTY;
|
||||
|
||||
if (kicked) {
|
||||
kickedId = kicked.playerID;
|
||||
} else {
|
||||
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
|
||||
}
|
||||
|
||||
if (kickedId == LWOOBJID_EMPTY) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID || team->leaderID == kickedId) return;
|
||||
|
||||
RemoveMember(team, kickedId, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamPromote(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString promotedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(promotedPlayer);
|
||||
|
||||
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
|
||||
|
||||
if (!promoted) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
PromoteMember(team, promoted.playerID);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamLootOption(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
team->lootFlag = option;
|
||||
|
||||
TeamStatusUpdate(team);
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamStatusRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
const auto& data = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (team != nullptr && data) {
|
||||
LOG_DEBUG("Player %llu is requesting team status", playerID);
|
||||
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
|
||||
RemoveMember(team, playerID, false, false, false, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1 && !team->local) {
|
||||
DisbandTeam(team, LWOOBJID_EMPTY, u"");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamSetLeader(data, team->leaderID);
|
||||
} else {
|
||||
SendTeamSetLeader(data, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
TeamStatusUpdate(team);
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (memberId == playerID) continue;
|
||||
|
||||
const auto memberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
if (otherMember) {
|
||||
SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
|
||||
}
|
||||
SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::CreateTeamServer(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
size_t membersSize = 0;
|
||||
inStream.Read(membersSize);
|
||||
|
||||
if (membersSize >= 4) {
|
||||
LOG("Tried to create a team with more than 4 players");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
members.reserve(membersSize);
|
||||
|
||||
for (size_t i = 0; i < membersSize; i++) {
|
||||
LWOOBJID member;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
|
||||
LWOZONEID zoneId;
|
||||
|
||||
inStream.Read(zoneId);
|
||||
|
||||
auto* team = CreateLocalTeam(members);
|
||||
|
||||
if (team != nullptr) {
|
||||
team->zoneId = zoneId;
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
|
||||
if (members.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TeamData* newTeam = nullptr;
|
||||
|
||||
for (const auto member : members) {
|
||||
auto* team = GetTeam(member);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, member, false, false, true);
|
||||
}
|
||||
|
||||
if (newTeam == nullptr) {
|
||||
newTeam = CreateTeam(member, true);
|
||||
} else {
|
||||
AddMember(newTeam, member);
|
||||
}
|
||||
}
|
||||
|
||||
newTeam->lootFlag = 1;
|
||||
|
||||
TeamStatusUpdate(newTeam);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::CreateTeam(LWOOBJID leader, bool local) {
|
||||
auto* team = new TeamData();
|
||||
|
||||
team->teamID = ++g_TeamIDCounter;
|
||||
team->leaderID = leader;
|
||||
team->local = local;
|
||||
|
||||
GetTeamsMut().push_back(team);
|
||||
|
||||
AddMember(team, leader);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::GetTeam(LWOOBJID playerID) {
|
||||
for (auto* team : GetTeams()) {
|
||||
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TeamContainer::AddMember(TeamData* team, LWOOBJID playerID) {
|
||||
if (team->memberIDs.size() >= 4) {
|
||||
LOG("Tried to add player to team that already had 4 players");
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
if (!player) return;
|
||||
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index != team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.push_back(playerID);
|
||||
|
||||
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
|
||||
const auto& member = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!leader || !member) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
|
||||
|
||||
SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamSetLeader(member, leader.playerID);
|
||||
} else {
|
||||
SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (otherMember == member) continue;
|
||||
|
||||
const auto otherMemberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
|
||||
if (otherMember) {
|
||||
SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
|
||||
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
|
||||
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
|
||||
|
||||
if (index == team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.erase(index);
|
||||
|
||||
const auto& member = Game::playerContainer.GetPlayerData(causingPlayerID);
|
||||
|
||||
const auto causingMemberName = Game::playerContainer.GetName(causingPlayerID);
|
||||
|
||||
if (member && !silent) {
|
||||
SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1) {
|
||||
DisbandTeam(team, causingPlayerID, causingMemberName);
|
||||
} else /* team has enough members to be a team still */ {
|
||||
team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID;
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
if (silent && memberId == causingPlayerID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
|
||||
team->leaderID = newLeader;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamSetLeader(otherMember, newLeader);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
|
||||
const auto index = std::ranges::find(GetTeams(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
|
||||
SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, true);
|
||||
|
||||
GetTeamsMut().erase(index);
|
||||
|
||||
delete team;
|
||||
}
|
||||
|
||||
void TeamContainer::TeamStatusUpdate(TeamData* team) {
|
||||
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
|
||||
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
|
||||
|
||||
if (!leader) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
|
||||
void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(team->teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(team->lootFlag);
|
||||
bitStream.Write<char>(team->memberIDs.size());
|
||||
for (const auto memberID : team->memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
59
dChatServer/TeamContainer.h
Normal file
59
dChatServer/TeamContainer.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef TEAMCONTAINER_H
|
||||
#define TEAMCONTAINER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
|
||||
struct Packet;
|
||||
struct PlayerData;
|
||||
struct TeamData;
|
||||
|
||||
namespace TeamContainer {
|
||||
struct Data {
|
||||
std::vector<TeamData*> mTeams;
|
||||
};
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void HandleTeamInvite(Packet* packet);
|
||||
void HandleTeamInviteResponse(Packet* packet);
|
||||
void HandleTeamLeave(Packet* packet);
|
||||
void HandleTeamKick(Packet* packet);
|
||||
void HandleTeamPromote(Packet* packet);
|
||||
void HandleTeamLootOption(Packet* packet);
|
||||
void HandleTeamStatusRequest(Packet* packet);
|
||||
|
||||
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
|
||||
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
|
||||
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
|
||||
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
|
||||
|
||||
/* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */
|
||||
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
|
||||
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
|
||||
|
||||
void CreateTeamServer(Packet* packet);
|
||||
|
||||
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
|
||||
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
|
||||
TeamData* GetTeam(LWOOBJID playerID);
|
||||
void AddMember(TeamData* team, LWOOBJID playerID);
|
||||
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
|
||||
void PromoteMember(TeamData* team, LWOOBJID newLeader);
|
||||
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
|
||||
void TeamStatusUpdate(TeamData* team);
|
||||
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
|
||||
|
||||
const TeamContainer::Data& GetTeamContainer();
|
||||
std::vector<TeamData*>& GetTeamsMut();
|
||||
const std::vector<TeamData*>& GetTeams();
|
||||
};
|
||||
|
||||
#endif //!TEAMCONTAINER_H
|
@ -5,6 +5,7 @@
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@ -39,6 +40,7 @@ public:
|
||||
// AMFValue template class instantiations
|
||||
template <typename ValueType>
|
||||
class AMFValue : public AMFBaseValue {
|
||||
static_assert(!std::is_same_v<ValueType, std::string_view>, "AMFValue cannot be instantiated with std::string_view");
|
||||
public:
|
||||
AMFValue() = default;
|
||||
AMFValue(const ValueType value) : m_Data{ value } {}
|
||||
@ -51,6 +53,15 @@ public:
|
||||
|
||||
void SetValue(const ValueType value) { m_Data = value; }
|
||||
|
||||
AMFValue<ValueType>& operator=(const AMFValue<ValueType>& other) {
|
||||
return operator=(other.m_Data);
|
||||
}
|
||||
|
||||
AMFValue<ValueType>& operator=(const ValueType& other) {
|
||||
m_Data = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
ValueType m_Data;
|
||||
};
|
||||
@ -210,13 +221,17 @@ public:
|
||||
* @param key The key to associate with the value
|
||||
* @param value The value to insert
|
||||
*/
|
||||
void Insert(const std::string_view key, std::unique_ptr<AMFBaseValue> value) {
|
||||
template<typename AmfType>
|
||||
AmfType& Insert(const std::string_view key, std::unique_ptr<AmfType> value) {
|
||||
const auto element = m_Associative.find(key);
|
||||
auto& toReturn = *value;
|
||||
if (element != m_Associative.cend() && element->second) {
|
||||
element->second = std::move(value);
|
||||
} else {
|
||||
m_Associative.emplace(key, std::move(value));
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,11 +243,15 @@ public:
|
||||
* @param key The key to associate with the value
|
||||
* @param value The value to insert
|
||||
*/
|
||||
void Insert(const size_t index, std::unique_ptr<AMFBaseValue> value) {
|
||||
template<typename AmfType>
|
||||
AmfType& Insert(const size_t index, std::unique_ptr<AmfType> value) {
|
||||
auto& toReturn = *value;
|
||||
if (index >= m_Dense.size()) {
|
||||
m_Dense.resize(index + 1);
|
||||
}
|
||||
|
||||
m_Dense.at(index) = std::move(value);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,10 +276,10 @@ public:
|
||||
*
|
||||
* @param key The key to remove from the associative portion
|
||||
*/
|
||||
void Remove(const std::string& key, const bool deleteValue = true) {
|
||||
void Remove(const std::string& key) {
|
||||
const AMFAssociative::const_iterator it = m_Associative.find(key);
|
||||
if (it != m_Associative.cend()) {
|
||||
if (deleteValue) m_Associative.erase(it);
|
||||
m_Associative.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,6 +362,18 @@ public:
|
||||
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
m_Associative.clear();
|
||||
m_Dense.clear();
|
||||
}
|
||||
|
||||
template<typename AmfType = AMFArrayValue>
|
||||
AmfType& PushDebug(const std::string_view name) {
|
||||
auto* value = PushArray();
|
||||
value->Insert("name", name.data());
|
||||
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The associative portion. These values are key'd with strings to an AMFValue.
|
||||
|
@ -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();
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "Sd0.h"
|
||||
#include "ZCompression.h"
|
||||
#include "Logger.h"
|
||||
|
||||
@ -44,10 +45,10 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
|
||||
}
|
||||
|
||||
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
|
||||
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[ZCompression::MAX_SD0_CHUNK_SIZE]);
|
||||
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE]);
|
||||
int32_t err{};
|
||||
int32_t actualUncompressedSize = ZCompression::Decompress(
|
||||
compressedChunk.get(), chunkSize, uncompressedChunk.get(), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
compressedChunk.get(), chunkSize, uncompressedChunk.get(), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
|
||||
if (actualUncompressedSize != -1) {
|
||||
uint32_t previousSize = completeUncompressedModel.size();
|
||||
@ -117,7 +118,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
|
||||
}
|
||||
|
||||
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
|
||||
std::istringstream outputStringStream(outputString);
|
||||
std::stringstream outputStringStream(outputString);
|
||||
|
||||
try {
|
||||
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
|
||||
|
@ -16,6 +16,10 @@ set(DCOMMON_SOURCES
|
||||
"BrickByBrickFix.cpp"
|
||||
"BinaryPathFinder.cpp"
|
||||
"FdbToSqlite.cpp"
|
||||
"JSONUtils.cpp"
|
||||
"TinyXmlUtils.cpp"
|
||||
"Sd0.cpp"
|
||||
"Lxfml.cpp"
|
||||
)
|
||||
|
||||
# Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible.
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define DluAssert(expression) assert(expression)
|
||||
# define DluAssert(expression) do { assert(expression) } while(0)
|
||||
#else
|
||||
# define DluAssert(expression)
|
||||
#endif
|
||||
|
@ -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) {
|
||||
|
@ -7,6 +7,10 @@
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
template <typename T>
|
||||
static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) {
|
||||
if (size == SIZE_MAX || size > string.size()) {
|
||||
@ -167,6 +171,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 +188,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 +304,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.
|
||||
@ -317,6 +331,17 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
|
||||
return sortedFiles;
|
||||
}
|
||||
|
||||
template<>
|
||||
[[nodiscard]] std::optional<json> GeneralUtils::TryParse(std::string_view str) {
|
||||
try {
|
||||
return json::parse(str);
|
||||
} catch (const std::exception& e) {
|
||||
return std::nullopt;
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
|
||||
|
||||
// MacOS floating-point parse function specializations
|
||||
|
@ -51,6 +51,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
|
||||
@ -193,6 +201,10 @@ namespace GeneralUtils {
|
||||
return isParsed ? static_cast<T>(result) : std::optional<T>{};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!Numeric<T>)
|
||||
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
|
||||
|
||||
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
|
||||
|
||||
// MacOS floating-point parse helper function specializations
|
||||
|
17
dCommon/JSONUtils.cpp
Normal file
17
dCommon/JSONUtils.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "JSONUtils.h"
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
|
||||
json check;
|
||||
check["error"] = json::array();
|
||||
for (const auto& required : requiredData) {
|
||||
if (!data.contains(required)) {
|
||||
check["error"].push_back("Missing Parameter: " + required);
|
||||
} else if (data[required] == "") {
|
||||
check["error"].push_back("Empty Parameter: " + required);
|
||||
}
|
||||
}
|
||||
return check["error"].empty() ? "" : check.dump();
|
||||
}
|
11
dCommon/JSONUtils.h
Normal file
11
dCommon/JSONUtils.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef _JSONUTILS_H_
|
||||
#define _JSONUTILS_H_
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
|
||||
namespace JSONUtils {
|
||||
// check required fields in json data
|
||||
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
|
||||
}
|
||||
|
||||
#endif // _JSONUTILS_H_
|
@ -29,8 +29,8 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
|
||||
// they will not be valid constexpr and will be evaluated at runtime instead of compile time!
|
||||
// The full string is still stored in the binary, however the offset of the filename in the absolute paths
|
||||
// is used in the instruction instead of the start of the absolute path.
|
||||
#define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
|
||||
#define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
|
||||
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(str_, message, ##__VA_ARGS__); } while(0)
|
||||
#define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0)
|
||||
|
||||
// Writer class for writing data to files.
|
||||
class Writer {
|
||||
|
115
dCommon/Lxfml.cpp
Normal file
115
dCommon/Lxfml.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include "Lxfml.h"
|
||||
|
||||
#include "GeneralUtils.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "TinyXmlUtils.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
|
||||
Result toReturn;
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto err = doc.Parse(data.data());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
TinyXmlUtils::DocumentReader reader(doc);
|
||||
std::map<std::string/* refID */, std::string> transformations;
|
||||
|
||||
auto lxfml = reader["LXFML"];
|
||||
if (!lxfml) {
|
||||
LOG("Failed to find LXFML element.");
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// First get all the positions of bricks
|
||||
for (const auto& brick : lxfml["Bricks"]) {
|
||||
const auto* part = brick.FirstChildElement("Part");
|
||||
if (part) {
|
||||
const auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) transformations[refID] = transformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These points are well out of bounds for an actual player
|
||||
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
|
||||
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
|
||||
|
||||
// Calculate the lowest and highest points on the entire model
|
||||
for (const auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
}
|
||||
|
||||
auto delta = (highest - lowest) / 2.0f;
|
||||
auto newRootPos = lowest + delta;
|
||||
|
||||
// Clamp the Y to the lowest point on the model
|
||||
newRootPos.y = lowest.y;
|
||||
|
||||
// Adjust all positions to account for the new origin
|
||||
for (auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
|
||||
std::stringstream stream;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
stream << split[i];
|
||||
stream << ',';
|
||||
}
|
||||
stream << x << ',' << y << ',' << z;
|
||||
transformation = stream.str();
|
||||
}
|
||||
|
||||
// Finally write the new transformation back into the lxfml
|
||||
for (auto& brick : lxfml["Bricks"]) {
|
||||
auto* part = brick.FirstChildElement("Part");
|
||||
if (part) {
|
||||
auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) {
|
||||
bone->SetAttribute("transformation", transformations[refID].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLPrinter printer;
|
||||
doc.Print(&printer);
|
||||
|
||||
toReturn.lxfml = printer.CStr();
|
||||
toReturn.center = newRootPos;
|
||||
return toReturn;
|
||||
}
|
23
dCommon/Lxfml.h
Normal file
23
dCommon/Lxfml.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef LXFML_H
|
||||
#define LXFML_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "NiPoint3.h"
|
||||
|
||||
namespace Lxfml {
|
||||
struct Result {
|
||||
std::string lxfml;
|
||||
NiPoint3 center;
|
||||
};
|
||||
|
||||
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
|
||||
// Returns a struct of its new center and the updated LXFML containing these edits.
|
||||
[[nodiscard]] Result NormalizePosition(const std::string_view data);
|
||||
};
|
||||
|
||||
#endif //!LXFML_H
|
150
dCommon/Sd0.cpp
Normal file
150
dCommon/Sd0.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
#include "Sd0.h"
|
||||
|
||||
#include <array>
|
||||
#include <ranges>
|
||||
|
||||
#include "BinaryIO.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#include "ZCompression.h"
|
||||
|
||||
// Insert header if on first buffer
|
||||
void WriteHeader(Sd0::BinaryBuffer& chunk) {
|
||||
chunk.push_back(Sd0::SD0_HEADER[0]);
|
||||
chunk.push_back(Sd0::SD0_HEADER[1]);
|
||||
chunk.push_back(Sd0::SD0_HEADER[2]);
|
||||
chunk.push_back(Sd0::SD0_HEADER[3]);
|
||||
chunk.push_back(Sd0::SD0_HEADER[4]);
|
||||
}
|
||||
|
||||
// Write the size of the buffer to a chunk
|
||||
void WriteSize(Sd0::BinaryBuffer& chunk, uint32_t chunkSize) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char toPush = chunkSize & 0xff;
|
||||
chunkSize = chunkSize >> 8;
|
||||
chunk.push_back(toPush);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GetDataOffset(bool firstBuffer) {
|
||||
return firstBuffer ? 9 : 4;
|
||||
}
|
||||
|
||||
Sd0::Sd0(std::istream& buffer) {
|
||||
char header[5]{};
|
||||
|
||||
// Check if this is an sd0 buffer. It's possible we may be handed a zlib buffer directly due to old code so check for that too.
|
||||
if (!BinaryIO::BinaryRead(buffer, header) || memcmp(header, SD0_HEADER, sizeof(header)) != 0) {
|
||||
LOG("Failed to read SD0 header %i %i %i %i %i %i %i", buffer.good(), buffer.tellg(), header[0], header[1], header[2], header[3], header[4]);
|
||||
LOG_DEBUG("This may be a zlib buffer directly? Trying again assuming its a zlib buffer.");
|
||||
auto& firstChunk = m_Chunks.emplace_back();
|
||||
WriteHeader(firstChunk);
|
||||
buffer.seekg(0, std::ios::end);
|
||||
uint32_t bufferSize = buffer.tellg();
|
||||
buffer.seekg(0, std::ios::beg);
|
||||
WriteSize(firstChunk, bufferSize);
|
||||
firstChunk.resize(firstChunk.size() + bufferSize);
|
||||
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
|
||||
if (!buffer.read(dataStart, bufferSize)) {
|
||||
m_Chunks.pop_back();
|
||||
LOG("Failed to read %u bytes from chunk %i", bufferSize, m_Chunks.size() - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
while (buffer && buffer.peek() != std::istream::traits_type::eof()) {
|
||||
uint32_t chunkSize{};
|
||||
if (!BinaryIO::BinaryRead(buffer, chunkSize)) {
|
||||
LOG("Failed to read chunk size from stream %lld %zu", buffer.tellg(), m_Chunks.size());
|
||||
break;
|
||||
}
|
||||
auto& chunk = m_Chunks.emplace_back();
|
||||
bool firstBuffer = m_Chunks.size() == 1;
|
||||
auto dataOffset = GetDataOffset(firstBuffer);
|
||||
|
||||
// Insert header if on first buffer
|
||||
if (firstBuffer) {
|
||||
WriteHeader(chunk);
|
||||
}
|
||||
|
||||
WriteSize(chunk, chunkSize);
|
||||
|
||||
chunk.resize(chunkSize + dataOffset);
|
||||
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
|
||||
if (!buffer.read(dataStart, chunkSize)) {
|
||||
m_Chunks.pop_back();
|
||||
LOG("Failed to read %u bytes from chunk %i", chunkSize, m_Chunks.size() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
|
||||
const auto originalBufferSize = bufferSize;
|
||||
if (bufferSize == 0) return;
|
||||
|
||||
m_Chunks.clear();
|
||||
while (bufferSize > 0) {
|
||||
const auto numToCopy = std::min(MAX_UNCOMPRESSED_CHUNK_SIZE, bufferSize);
|
||||
const auto* startOffset = data + originalBufferSize - bufferSize;
|
||||
bufferSize -= numToCopy;
|
||||
std::array<uint8_t, MAX_UNCOMPRESSED_CHUNK_SIZE> compressedChunk;
|
||||
const auto compressedSize = ZCompression::Compress(
|
||||
startOffset, numToCopy,
|
||||
compressedChunk.data(), compressedChunk.size());
|
||||
|
||||
auto& chunk = m_Chunks.emplace_back();
|
||||
bool firstBuffer = m_Chunks.size() == 1;
|
||||
auto dataOffset = GetDataOffset(firstBuffer);
|
||||
|
||||
if (firstBuffer) {
|
||||
WriteHeader(chunk);
|
||||
}
|
||||
|
||||
WriteSize(chunk, compressedSize);
|
||||
|
||||
chunk.resize(compressedSize + dataOffset);
|
||||
memcpy(chunk.data() + dataOffset, compressedChunk.data(), compressedSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string Sd0::GetAsStringUncompressed() const {
|
||||
std::string toReturn;
|
||||
bool first = true;
|
||||
uint32_t totalSize{};
|
||||
for (const auto& chunk : m_Chunks) {
|
||||
auto dataOffset = GetDataOffset(first);
|
||||
first = false;
|
||||
const auto chunkSize = chunk.size();
|
||||
|
||||
auto oldSize = toReturn.size();
|
||||
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
|
||||
int32_t error{};
|
||||
const auto uncompressedSize = ZCompression::Decompress(
|
||||
chunk.data() + dataOffset, chunkSize - dataOffset,
|
||||
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
|
||||
error);
|
||||
|
||||
totalSize += uncompressedSize;
|
||||
}
|
||||
|
||||
toReturn.resize(totalSize);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::stringstream Sd0::GetAsStream() const {
|
||||
std::stringstream toReturn;
|
||||
|
||||
for (const auto& chunk : m_Chunks) {
|
||||
toReturn.write(reinterpret_cast<const char*>(chunk.data()), chunk.size());
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
const std::vector<Sd0::BinaryBuffer>& Sd0::GetAsVector() const {
|
||||
return m_Chunks;
|
||||
}
|
42
dCommon/Sd0.h
Normal file
42
dCommon/Sd0.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef SD0_H
|
||||
#define SD0_H
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
// Sd0 is comprised of multiple zlib compressed buffers stored in a row.
|
||||
// The format starts with a SD0 header (see SD0_HEADER) followed by the size of a zlib buffer, and then the zlib buffer itself.
|
||||
// This repeats until end of file
|
||||
class Sd0 {
|
||||
public:
|
||||
using BinaryBuffer = std::vector<uint8_t>;
|
||||
|
||||
static inline const char* SD0_HEADER = "sd0\x01\xff";
|
||||
|
||||
/**
|
||||
* @brief Max size of an inflated sd0 zlib chunk
|
||||
*/
|
||||
static constexpr inline size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 1024 * 256;
|
||||
|
||||
// Read the input buffer into an internal chunk stream to be used later
|
||||
Sd0(std::istream& buffer);
|
||||
|
||||
// Uncompresses the entire Sd0 buffer and returns it as a string
|
||||
[[nodiscard]] std::string GetAsStringUncompressed() const;
|
||||
|
||||
// Gets the Sd0 buffer as a stream in its raw compressed form
|
||||
[[nodiscard]] std::stringstream GetAsStream() const;
|
||||
|
||||
// Gets the Sd0 buffer as a vector in its raw compressed form
|
||||
[[nodiscard]] const std::vector<BinaryBuffer>& GetAsVector() const;
|
||||
|
||||
// Compress data into a Sd0 buffer
|
||||
void FromData(const uint8_t* data, size_t bufferSize);
|
||||
private:
|
||||
std::vector<BinaryBuffer> m_Chunks{};
|
||||
};
|
||||
|
||||
#endif //!SD0_H
|
37
dCommon/TinyXmlUtils.cpp
Normal file
37
dCommon/TinyXmlUtils.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "TinyXmlUtils.h"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
using namespace TinyXmlUtils;
|
||||
|
||||
Element DocumentReader::operator[](const std::string_view elem) const {
|
||||
return Element(m_Doc.FirstChildElement(elem.empty() ? nullptr : elem.data()), elem);
|
||||
}
|
||||
|
||||
Element::Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem) :
|
||||
m_IteratedName{ elem },
|
||||
m_Elem{ xmlElem } {
|
||||
}
|
||||
|
||||
Element Element::operator[](const std::string_view elem) const {
|
||||
const auto* usedElem = elem.empty() ? nullptr : elem.data();
|
||||
auto* toReturn = m_Elem ? m_Elem->FirstChildElement(usedElem) : nullptr;
|
||||
return Element(toReturn, m_IteratedName);
|
||||
}
|
||||
|
||||
ElementIterator Element::begin() {
|
||||
return ElementIterator(m_Elem ? m_Elem->FirstChildElement() : nullptr);
|
||||
}
|
||||
|
||||
ElementIterator Element::end() {
|
||||
return ElementIterator(nullptr);
|
||||
}
|
||||
|
||||
ElementIterator::ElementIterator(tinyxml2::XMLElement* elem) :
|
||||
m_CurElem{ elem } {
|
||||
}
|
||||
|
||||
ElementIterator& ElementIterator::operator++() {
|
||||
if (m_CurElem) m_CurElem = m_CurElem->NextSiblingElement();
|
||||
return *this;
|
||||
}
|
66
dCommon/TinyXmlUtils.h
Normal file
66
dCommon/TinyXmlUtils.h
Normal file
@ -0,0 +1,66 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef TINYXMLUTILS_H
|
||||
#define TINYXMLUTILS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "DluAssert.h"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
namespace TinyXmlUtils {
|
||||
// See cstdlib for iterator technicalities
|
||||
struct ElementIterator {
|
||||
ElementIterator(tinyxml2::XMLElement* elem);
|
||||
|
||||
ElementIterator& operator++();
|
||||
[[nodiscard]] tinyxml2::XMLElement* operator->() { DluAssert(m_CurElem); return m_CurElem; }
|
||||
[[nodiscard]] tinyxml2::XMLElement& operator*() { DluAssert(m_CurElem); return *m_CurElem; }
|
||||
|
||||
bool operator==(const ElementIterator& other) const { return other.m_CurElem == m_CurElem; }
|
||||
|
||||
private:
|
||||
tinyxml2::XMLElement* m_CurElem{ nullptr };
|
||||
};
|
||||
|
||||
// Wrapper class to act as an iterator over xml elements.
|
||||
// All the normal rules that apply to Iterators in the std library apply here.
|
||||
class Element {
|
||||
public:
|
||||
Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem);
|
||||
|
||||
// The first child element of this element.
|
||||
[[nodiscard]] ElementIterator begin();
|
||||
|
||||
// Always returns an ElementIterator which points to nullptr.
|
||||
// TinyXml2 return NULL when you've reached the last child element so
|
||||
// you can't do any funny one past end logic here.
|
||||
[[nodiscard]] ElementIterator end();
|
||||
|
||||
// Get a child element
|
||||
[[nodiscard]] Element operator[](const std::string_view elem) const;
|
||||
[[nodiscard]] Element operator[](const char* elem) const { return operator[](std::string_view(elem)); };
|
||||
|
||||
// Whether or not data exists for this element
|
||||
operator bool() const { return m_Elem != nullptr; }
|
||||
|
||||
[[nodiscard]] const tinyxml2::XMLElement* operator->() const { return m_Elem; }
|
||||
private:
|
||||
const char* GetElementName() const { return m_IteratedName.empty() ? nullptr : m_IteratedName.c_str(); }
|
||||
const std::string m_IteratedName;
|
||||
tinyxml2::XMLElement* m_Elem;
|
||||
};
|
||||
|
||||
class DocumentReader {
|
||||
public:
|
||||
DocumentReader(tinyxml2::XMLDocument& doc) : m_Doc{ doc } {}
|
||||
|
||||
[[nodiscard]] Element operator[](const std::string_view elem) const;
|
||||
private:
|
||||
tinyxml2::XMLDocument& m_Doc;
|
||||
};
|
||||
};
|
||||
|
||||
#endif //!TINYXMLUTILS_H
|
@ -8,11 +8,5 @@ namespace ZCompression {
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
|
||||
|
||||
/**
|
||||
* @brief Max size of an inflated sd0 zlib chunk
|
||||
*
|
||||
*/
|
||||
constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,9 @@
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
|
||||
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
|
||||
|
||||
AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory.");
|
||||
@ -18,12 +21,20 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
|
||||
m_RootPath = m_Path;
|
||||
m_ResPath = (m_Path / "client" / "res");
|
||||
} else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) {
|
||||
} else if (std::filesystem::exists(m_Path / "res" / "pack")) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / "..");
|
||||
m_ResPath = (m_Path / "res");
|
||||
} else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
|
||||
} else if (std::filesystem::exists(m_Path / "pack")) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / ".." / "..");
|
||||
@ -48,6 +59,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::None:
|
||||
[[fallthrough]];
|
||||
case eAssetBundleType::Unpacked: {
|
||||
break;
|
||||
}
|
||||
@ -55,19 +67,10 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
void AssetManager::LoadPackIndex() {
|
||||
m_PackIndex = new PackIndex(m_RootPath);
|
||||
m_PackIndex = PackIndex(m_RootPath);
|
||||
}
|
||||
|
||||
std::filesystem::path AssetManager::GetResPath() {
|
||||
return m_ResPath;
|
||||
}
|
||||
|
||||
eAssetBundleType AssetManager::GetAssetBundleType() {
|
||||
return m_AssetBundleType;
|
||||
}
|
||||
|
||||
bool AssetManager::HasFile(const char* name) {
|
||||
auto fixedName = std::string(name);
|
||||
bool AssetManager::HasFile(std::string fixedName) const {
|
||||
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
|
||||
|
||||
// Special case for unpacked client have BrickModels in upper case
|
||||
@ -81,8 +84,7 @@ bool AssetManager::HasFile(const char* name) {
|
||||
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
|
||||
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
|
||||
|
||||
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
|
||||
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
|
||||
const auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
|
||||
|
||||
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
|
||||
if (item.m_Crc == crc) {
|
||||
@ -93,8 +95,7 @@ bool AssetManager::HasFile(const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
|
||||
auto fixedName = std::string(name);
|
||||
bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) const {
|
||||
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
|
||||
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes
|
||||
|
||||
@ -129,8 +130,7 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
|
||||
fixedName = "client\\res\\" + fixedName;
|
||||
}
|
||||
int32_t packIndex = -1;
|
||||
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
|
||||
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
|
||||
auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
|
||||
|
||||
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
|
||||
if (item.m_Crc == crc) {
|
||||
@ -144,15 +144,13 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto packs = this->m_PackIndex->GetPacks();
|
||||
auto* pack = packs.at(packIndex);
|
||||
|
||||
bool success = pack->ReadFileFromPack(crc, data, len);
|
||||
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
|
||||
const bool success = pack.ReadFileFromPack(crc, data, len);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
AssetStream AssetManager::GetFile(const char* name) {
|
||||
AssetStream AssetManager::GetFile(const char* name) const {
|
||||
char* buf; uint32_t len;
|
||||
|
||||
bool success = this->GetFile(name, &buf, &len);
|
||||
@ -160,23 +158,15 @@ AssetStream AssetManager::GetFile(const char* name) {
|
||||
return AssetStream(buf, len, success);
|
||||
}
|
||||
|
||||
uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
|
||||
size_t i, j;
|
||||
uint32_t crc, msb;
|
||||
|
||||
crc = base;
|
||||
for (i = 0; i < l; i++) {
|
||||
uint32_t AssetManager::crc32b(uint32_t crc, const std::string_view message) {
|
||||
for (const auto byte : message) {
|
||||
// xor next byte to upper bits of crc
|
||||
crc ^= (static_cast<unsigned int>(message[i]) << 24);
|
||||
for (j = 0; j < 8; j++) { // Do eight times.
|
||||
msb = crc >> 31;
|
||||
crc ^= (static_cast<uint32_t>(std::bit_cast<uint8_t>(byte)) << 24);
|
||||
for (size_t _ = 0; _ < 8; _++) { // Do eight times.
|
||||
const uint32_t msb = crc >> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (0 - msb) & 0x04C11DB7;
|
||||
}
|
||||
}
|
||||
return crc; // don't complement crc on output
|
||||
}
|
||||
|
||||
AssetManager::~AssetManager() {
|
||||
delete m_PackIndex;
|
||||
}
|
||||
|
@ -61,23 +61,32 @@ struct AssetStream : std::istream {
|
||||
class AssetManager {
|
||||
public:
|
||||
AssetManager(const std::filesystem::path& path);
|
||||
~AssetManager();
|
||||
|
||||
std::filesystem::path GetResPath();
|
||||
eAssetBundleType GetAssetBundleType();
|
||||
[[nodiscard]]
|
||||
const std::filesystem::path& GetResPath() const {
|
||||
return m_ResPath;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eAssetBundleType GetAssetBundleType() const {
|
||||
return m_AssetBundleType;
|
||||
}
|
||||
|
||||
bool HasFile(const char* name);
|
||||
bool GetFile(const char* name, char** data, uint32_t* len);
|
||||
AssetStream GetFile(const char* name);
|
||||
[[nodiscard]]
|
||||
bool HasFile(std::string name) const;
|
||||
|
||||
[[nodiscard]]
|
||||
bool GetFile(std::string name, char** data, uint32_t* len) const;
|
||||
|
||||
[[nodiscard]]
|
||||
AssetStream GetFile(const char* name) const;
|
||||
|
||||
private:
|
||||
void LoadPackIndex();
|
||||
|
||||
// Modified crc algorithm (mpeg2)
|
||||
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2
|
||||
inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l);
|
||||
|
||||
bool m_SuccessfullyLoaded;
|
||||
static inline uint32_t crc32b(uint32_t crc, std::string_view message);
|
||||
|
||||
std::filesystem::path m_Path;
|
||||
std::filesystem::path m_RootPath;
|
||||
@ -85,5 +94,5 @@ private:
|
||||
|
||||
eAssetBundleType m_AssetBundleType = eAssetBundleType::None;
|
||||
|
||||
PackIndex* m_PackIndex;
|
||||
std::optional<PackIndex> m_PackIndex;
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Pack.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
#include "Sd0.h"
|
||||
#include "ZCompression.h"
|
||||
|
||||
Pack::Pack(const std::filesystem::path& filePath) {
|
||||
@ -21,19 +22,20 @@ Pack::Pack(const std::filesystem::path& filePath) {
|
||||
|
||||
m_FileStream.seekg(recordCountPos, std::ios::beg);
|
||||
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount);
|
||||
uint32_t recordCount = 0;
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCount);
|
||||
|
||||
for (int i = 0; i < m_RecordCount; i++) {
|
||||
m_Records.reserve(recordCount);
|
||||
std::generate_n(std::back_inserter(m_Records), recordCount, [&] {
|
||||
PackRecord record;
|
||||
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
|
||||
|
||||
m_Records.push_back(record);
|
||||
}
|
||||
return record;
|
||||
});
|
||||
|
||||
m_FileStream.close();
|
||||
}
|
||||
|
||||
bool Pack::HasFile(uint32_t crc) {
|
||||
bool Pack::HasFile(const uint32_t crc) const {
|
||||
for (const auto& record : m_Records) {
|
||||
if (record.m_Crc == crc) {
|
||||
return true;
|
||||
@ -43,7 +45,7 @@ bool Pack::HasFile(uint32_t crc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
|
||||
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
|
||||
// Time for some wacky C file reading for speed reasons
|
||||
|
||||
PackRecord pkRecord{};
|
||||
@ -105,7 +107,7 @@ bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
|
||||
pos += size; // Move pointer position the amount of bytes read to the right
|
||||
|
||||
int32_t err;
|
||||
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
|
||||
free(chunk);
|
||||
}
|
||||
|
@ -24,16 +24,17 @@ struct PackRecord {
|
||||
class Pack {
|
||||
public:
|
||||
Pack(const std::filesystem::path& filePath);
|
||||
~Pack() = default;
|
||||
|
||||
bool HasFile(uint32_t crc);
|
||||
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len);
|
||||
[[nodiscard]]
|
||||
bool HasFile(uint32_t crc) const;
|
||||
|
||||
[[nodiscard]]
|
||||
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) const;
|
||||
private:
|
||||
std::ifstream m_FileStream;
|
||||
std::filesystem::path m_FilePath;
|
||||
|
||||
char m_Version[7];
|
||||
|
||||
uint32_t m_RecordCount;
|
||||
std::vector<PackRecord> m_Records;
|
||||
};
|
||||
|
@ -6,38 +6,32 @@
|
||||
PackIndex::PackIndex(const std::filesystem::path& filePath) {
|
||||
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
|
||||
|
||||
uint32_t packPathCount = 0;
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packPathCount);
|
||||
|
||||
m_PackPaths.resize(m_PackPathCount);
|
||||
m_PackPaths.resize(packPathCount);
|
||||
for (auto& item : m_PackPaths) {
|
||||
BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String);
|
||||
}
|
||||
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);
|
||||
uint32_t packFileIndexCount = 0;
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packFileIndexCount);
|
||||
|
||||
for (int i = 0; i < m_PackFileIndexCount; i++) {
|
||||
m_PackFileIndices.reserve(packFileIndexCount);
|
||||
std::generate_n(std::back_inserter(m_PackFileIndices), packFileIndexCount, [&] {
|
||||
PackFileIndex packFileIndex;
|
||||
BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex);
|
||||
|
||||
m_PackFileIndices.push_back(packFileIndex);
|
||||
}
|
||||
return packFileIndex;
|
||||
});
|
||||
|
||||
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
|
||||
|
||||
m_Packs.reserve(m_PackPaths.size());
|
||||
for (auto& item : m_PackPaths) {
|
||||
std::replace(item.begin(), item.end(), '\\', '/');
|
||||
|
||||
auto* pack = new Pack(filePath / item);
|
||||
|
||||
m_Packs.push_back(pack);
|
||||
m_Packs.emplace_back(filePath / item);
|
||||
}
|
||||
|
||||
m_FileStream.close();
|
||||
}
|
||||
|
||||
PackIndex::~PackIndex() {
|
||||
for (const auto* item : m_Packs) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
@ -21,20 +21,23 @@ struct PackFileIndex {
|
||||
class PackIndex {
|
||||
public:
|
||||
PackIndex(const std::filesystem::path& filePath);
|
||||
~PackIndex();
|
||||
|
||||
const std::vector<std::string>& GetPackPaths() { return m_PackPaths; }
|
||||
const std::vector<PackFileIndex>& GetPackFileIndices() { return m_PackFileIndices; }
|
||||
const std::vector<Pack*>& GetPacks() { return m_Packs; }
|
||||
[[nodiscard]]
|
||||
const std::vector<std::string>& GetPackPaths() const { return m_PackPaths; }
|
||||
|
||||
[[nodiscard]]
|
||||
const std::vector<PackFileIndex>& GetPackFileIndices() const { return m_PackFileIndices; }
|
||||
|
||||
[[nodiscard]]
|
||||
const std::vector<Pack>& GetPacks() const { return m_Packs; }
|
||||
private:
|
||||
std::ifstream m_FileStream;
|
||||
|
||||
uint32_t m_Version;
|
||||
|
||||
uint32_t m_PackPathCount;
|
||||
std::vector<std::string> m_PackPaths;
|
||||
uint32_t m_PackFileIndexCount;
|
||||
|
||||
std::vector<PackFileIndex> m_PackFileIndices;
|
||||
|
||||
std::vector<Pack*> m_Packs;
|
||||
std::vector<Pack> m_Packs;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -3,13 +3,14 @@
|
||||
#ifndef __DCOMMONVARS__H__
|
||||
#define __DCOMMONVARS__H__
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "BitStream.h"
|
||||
#include "eConnectionType.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "eConnectionType.h"
|
||||
|
||||
#pragma warning (disable:4251) //Disables SQL warnings
|
||||
|
||||
@ -98,6 +99,8 @@ public:
|
||||
constexpr LWOZONEID() noexcept = default;
|
||||
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
|
||||
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
|
||||
constexpr bool operator==(const LWOZONEID&) const = default;
|
||||
constexpr auto operator<=>(const LWOZONEID&) const = default;
|
||||
|
||||
private:
|
||||
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...
|
||||
|
@ -16,7 +16,9 @@ enum class eCharacterVersion : uint32_t {
|
||||
VAULT_SIZE,
|
||||
// Fixes speed base value in level component
|
||||
SPEED_BASE,
|
||||
UP_TO_DATE, // will become NJ_JAYMISSIONS
|
||||
// Fixes nexus force explorer missions
|
||||
NJ_JAYMISSIONS,
|
||||
UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER
|
||||
};
|
||||
|
||||
#endif //!__ECHARACTERVERSION__H__
|
||||
|
@ -7,7 +7,8 @@ enum class eConnectionType : uint16_t {
|
||||
CHAT,
|
||||
WORLD = 4,
|
||||
CLIENT,
|
||||
MASTER
|
||||
MASTER,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
#endif //!__ECONNECTIONTYPE__H__
|
||||
|
28
dCommon/dEnums/eHTTPMethod.h
Normal file
28
dCommon/dEnums/eHTTPMethod.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef __EHTTPMETHODS__H__
|
||||
#define __EHTTPMETHODS__H__
|
||||
|
||||
#include "dPlatforms.h"
|
||||
|
||||
#ifdef DARKFLAME_PLATFORM_WIN32
|
||||
#pragma push_macro("DELETE")
|
||||
#undef DELETE
|
||||
#endif
|
||||
|
||||
enum class eHTTPMethod {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
HEAD,
|
||||
CONNECT,
|
||||
OPTIONS,
|
||||
TRACE,
|
||||
PATCH,
|
||||
INVALID
|
||||
};
|
||||
|
||||
#ifdef DARKFLAME_PLATFORM_WIN32
|
||||
#pragma pop_macro("DELETE")
|
||||
#endif
|
||||
|
||||
#endif // __EHTTPMETHODS__H__
|
72
dCommon/dEnums/eHTTPStatusCode.h
Normal file
72
dCommon/dEnums/eHTTPStatusCode.h
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef __EHTTPSTATUSCODE__H__
|
||||
#define __EHTTPSTATUSCODE__H__
|
||||
|
||||
#include <cstdint>
|
||||
// verbose list of http codes
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
enum class eHTTPStatusCode : uint32_t {
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
EARLY_HINTS = 103,
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
MULTI_STATUS = 207,
|
||||
ALREADY_REPORTED = 208,
|
||||
IM_USED = 226,
|
||||
MULTIPLE_CHOICES = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
FOUND = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
USE_PROXY = 305,
|
||||
SWITCH_PROXY = 306,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
PAYLOAD_TOO_LARGE = 413,
|
||||
URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
I_AM_A_TEAPOT = 418,
|
||||
MISDIRECTED_REQUEST = 421,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
LOCKED = 423,
|
||||
FAILED_DEPENDENCY = 424,
|
||||
UPGRADE_REQUIRED = 426,
|
||||
PRECONDITION_REQUIRED = 428,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
VARIANT_ALSO_NEGOTIATES = 506,
|
||||
INSUFFICIENT_STORAGE = 507,
|
||||
LOOP_DETECTED = 508,
|
||||
NOT_EXTENDED = 510,
|
||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||
};
|
||||
|
||||
#endif // !__EHTTPSTATUSCODE__H__
|
@ -28,7 +28,8 @@ enum eInventoryType : uint32_t {
|
||||
DONATION,
|
||||
VAULT_MODELS,
|
||||
ITEM_SETS, //internal, technically this is BankBehaviors.
|
||||
INVALID // made up, for internal use!!!, Technically this called the ALL inventory.
|
||||
INVALID, // made up, for internal use!!!, Technically this called the ALL inventory.
|
||||
ALL, // Use this to search all inventories instead of a specific one.
|
||||
};
|
||||
|
||||
class InventoryType {
|
||||
|
@ -102,7 +102,6 @@ DEFINE_TABLE_STORAGE(CDScriptComponentTable);
|
||||
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
|
||||
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
|
||||
DEFINE_TABLE_STORAGE(CDVendorComponentTable);
|
||||
DEFINE_TABLE_STORAGE(CDZoneTableTable);
|
||||
|
||||
void CDClientManager::LoadValuesFromDatabase() {
|
||||
if (!CDClientDatabase::isConnected) {
|
||||
@ -149,7 +148,7 @@ void CDClientManager::LoadValuesFromDatabase() {
|
||||
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
|
||||
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
|
||||
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
|
||||
CDZoneTableTable::Instance().LoadValuesFromDatabase();
|
||||
CDZoneTableTable::LoadValuesFromDatabase();
|
||||
}
|
||||
|
||||
void CDClientManager::LoadValuesFromDefaults() {
|
||||
|
@ -0,0 +1,38 @@
|
||||
#include "CDPlayerFlagsTable.h"
|
||||
|
||||
#include "CDClientDatabase.h"
|
||||
|
||||
namespace CDPlayerFlagsTable {
|
||||
Table entries;
|
||||
|
||||
void ReadEntry(CppSQLite3Query& table) {
|
||||
Entry entry;
|
||||
entry.sessionOnly = table.getIntField("SessionOnly") == 1;
|
||||
entry.onlySetByServer = table.getIntField("OnlySetByServer") == 1;
|
||||
entry.sessionZoneOnly = table.getIntField("SessionZoneOnly") == 1;
|
||||
entries[table.getIntField("id")] = entry;
|
||||
}
|
||||
|
||||
void LoadValuesFromDatabase() {
|
||||
auto table = CDClientDatabase::ExecuteQuery("SELECT * FROM PlayerFlags;");
|
||||
|
||||
if (!table.eof()) {
|
||||
do {
|
||||
ReadEntry(table);
|
||||
} while (!table.nextRow());
|
||||
}
|
||||
}
|
||||
|
||||
const std::optional<Entry> GetEntry(const FlagId flagId) {
|
||||
if (!entries.contains(flagId)) {
|
||||
auto table = CDClientDatabase::CreatePreppedStmt("SELECT * FROM PlayerFlags WHERE id = ?;");
|
||||
table.bind(1, static_cast<int>(flagId));
|
||||
auto result = table.execQuery();
|
||||
if (!result.eof()) {
|
||||
ReadEntry(result);
|
||||
}
|
||||
}
|
||||
|
||||
return entries[flagId];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
#ifndef CDPLAYERFLAGSTABLE_H
|
||||
#define CDPLAYERFLAGSTABLE_H
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
namespace CDPlayerFlagsTable {
|
||||
struct Entry {
|
||||
bool sessionOnly{};
|
||||
bool onlySetByServer{};
|
||||
bool sessionZoneOnly{};
|
||||
};
|
||||
|
||||
using FlagId = uint32_t;
|
||||
using Table = std::map<FlagId, std::optional<Entry>>;
|
||||
|
||||
void LoadValuesFromDatabase();
|
||||
const std::optional<Entry> GetEntry(const FlagId flagId);
|
||||
};
|
||||
|
||||
#endif //!CDPLAYERFLAGSTABLE_H
|
@ -1,67 +1,53 @@
|
||||
#include "CDZoneTableTable.h"
|
||||
|
||||
void CDZoneTableTable::LoadValuesFromDatabase() {
|
||||
namespace CDZoneTableTable {
|
||||
Table entries;
|
||||
|
||||
// First, get the size of the table
|
||||
uint32_t size = 0;
|
||||
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ZoneTable");
|
||||
while (!tableSize.eof()) {
|
||||
size = tableSize.getIntField(0, 0);
|
||||
void LoadValuesFromDatabase() {
|
||||
// Get the data from the database
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
|
||||
while (!tableData.eof()) {
|
||||
CDZoneTable entry;
|
||||
entry.zoneID = tableData.getIntField("zoneID", -1);
|
||||
entry.locStatus = tableData.getIntField("locStatus", -1);
|
||||
entry.zoneName = tableData.getStringField("zoneName", "");
|
||||
entry.scriptID = tableData.getIntField("scriptID", -1);
|
||||
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
|
||||
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
|
||||
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
|
||||
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
|
||||
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
|
||||
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
|
||||
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
|
||||
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
|
||||
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
|
||||
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
|
||||
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
|
||||
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
|
||||
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
|
||||
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
|
||||
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
|
||||
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
|
||||
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
|
||||
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
|
||||
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
|
||||
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
|
||||
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
|
||||
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
|
||||
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
|
||||
|
||||
tableSize.nextRow();
|
||||
entries[entry.zoneID] = entry;
|
||||
tableData.nextRow();
|
||||
}
|
||||
}
|
||||
|
||||
tableSize.finalize();
|
||||
//! Queries the table with a zoneID to find.
|
||||
const CDZoneTable* Query(uint32_t zoneID) {
|
||||
const auto& iter = entries.find(zoneID);
|
||||
if (iter != entries.end()) {
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
// Now get the data
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
|
||||
auto& entries = GetEntriesMutable();
|
||||
while (!tableData.eof()) {
|
||||
CDZoneTable entry;
|
||||
entry.zoneID = tableData.getIntField("zoneID", -1);
|
||||
entry.locStatus = tableData.getIntField("locStatus", -1);
|
||||
entry.zoneName = tableData.getStringField("zoneName", "");
|
||||
entry.scriptID = tableData.getIntField("scriptID", -1);
|
||||
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
|
||||
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
|
||||
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
|
||||
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
|
||||
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
|
||||
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
|
||||
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
|
||||
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
|
||||
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
|
||||
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
|
||||
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
|
||||
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
|
||||
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
|
||||
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
|
||||
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
|
||||
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
|
||||
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
|
||||
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
|
||||
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
|
||||
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
|
||||
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
|
||||
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
|
||||
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
|
||||
|
||||
entries.insert(std::make_pair(entry.zoneID, entry));
|
||||
tableData.nextRow();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tableData.finalize();
|
||||
}
|
||||
|
||||
//! Queries the table with a zoneID to find.
|
||||
const CDZoneTable* CDZoneTableTable::Query(uint32_t zoneID) {
|
||||
auto& m_Entries = GetEntries();
|
||||
const auto& iter = m_Entries.find(zoneID);
|
||||
|
||||
if (iter != m_Entries.end()) {
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,8 @@ struct CDZoneTable {
|
||||
bool mountsAllowed; //!< Whether or not mounts are allowed
|
||||
};
|
||||
|
||||
class CDZoneTableTable : public CDTable<CDZoneTableTable, std::map<uint32_t, CDZoneTable>> {
|
||||
public:
|
||||
namespace CDZoneTableTable {
|
||||
using Table = std::map<uint32_t, CDZoneTable>;
|
||||
void LoadValuesFromDatabase();
|
||||
|
||||
// Queries the table with a zoneID to find.
|
||||
|
@ -25,6 +25,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
|
||||
"CDObjectsTable.cpp"
|
||||
"CDPetComponentTable.cpp"
|
||||
"CDPackageComponentTable.cpp"
|
||||
"CDPlayerFlagsTable.cpp"
|
||||
"CDPhysicsComponentTable.cpp"
|
||||
"CDPropertyEntranceComponentTable.cpp"
|
||||
"CDPropertyTemplateTable.cpp"
|
||||
|
@ -1,7 +1,7 @@
|
||||
add_subdirectory(CDClientDatabase)
|
||||
add_subdirectory(GameDatabase)
|
||||
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp")
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp")
|
||||
|
||||
add_custom_target(conncpp_dylib
|
||||
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
|
||||
|
@ -8,6 +8,7 @@
|
||||
enum class eActivityType : uint32_t {
|
||||
PlayerLoggedIn,
|
||||
PlayerLoggedOut,
|
||||
PlayerChangedZone
|
||||
};
|
||||
|
||||
class IActivityLog {
|
||||
|
@ -44,6 +44,8 @@ public:
|
||||
|
||||
// Updates the given character ids last login to be right now.
|
||||
virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0;
|
||||
|
||||
virtual bool IsNameInUse(const std::string_view name) = 0;
|
||||
};
|
||||
|
||||
#endif //!__ICHARINFO__H__
|
||||
|
@ -14,7 +14,7 @@ public:
|
||||
uint32_t lastPlayedTimestamp{};
|
||||
float primaryScore{};
|
||||
float secondaryScore{};
|
||||
uint32_t tertiaryScore{};
|
||||
float tertiaryScore{};
|
||||
uint32_t numWins{};
|
||||
uint32_t numTimesPlayed{};
|
||||
uint32_t ranking{};
|
||||
|
@ -8,27 +8,10 @@
|
||||
#include "dCommonVars.h"
|
||||
#include "NiQuaternion.h"
|
||||
#include "NiPoint3.h"
|
||||
|
||||
#include "MailInfo.h"
|
||||
|
||||
class IMail {
|
||||
public:
|
||||
struct MailInfo {
|
||||
std::string senderUsername;
|
||||
std::string recipient;
|
||||
std::string subject;
|
||||
std::string body;
|
||||
uint64_t id{};
|
||||
uint32_t senderId{};
|
||||
uint32_t receiverId{};
|
||||
uint64_t timeSent{};
|
||||
bool wasRead{};
|
||||
struct {
|
||||
LWOOBJID itemID{};
|
||||
int32_t itemCount{};
|
||||
LOT itemLOT{};
|
||||
LWOOBJID itemSubkey{};
|
||||
};
|
||||
};
|
||||
|
||||
// Insert a new mail into the database.
|
||||
virtual void InsertNewMail(const MailInfo& mail) = 0;
|
||||
|
||||
|
@ -53,6 +53,9 @@ public:
|
||||
// Update the property details for the given property id.
|
||||
virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0;
|
||||
|
||||
// Update the last updated time for the given property id.
|
||||
virtual void UpdateLastSave(const IProperty::Info& info) = 0;
|
||||
|
||||
// Update the property performance cost for the given property id.
|
||||
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;
|
||||
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
|
||||
// Inserts a new UGC model into the database.
|
||||
virtual void InsertNewUgcModel(
|
||||
std::istringstream& sd0Data,
|
||||
std::stringstream& sd0Data,
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) = 0;
|
||||
@ -34,9 +34,17 @@ public:
|
||||
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
|
||||
|
||||
// Update the model position and rotation for the given property id.
|
||||
virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0;
|
||||
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0;
|
||||
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<int32_t, 5> behaviorIDs) {
|
||||
std::array<std::pair<int32_t, std::string>, 5> behaviors;
|
||||
for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i];
|
||||
UpdateModel(modelID, position, rotation, behaviors);
|
||||
}
|
||||
|
||||
// Remove the model for the given property id.
|
||||
virtual void RemoveModel(const LWOOBJID& modelId) = 0;
|
||||
|
||||
// Gets a model by ID
|
||||
virtual Model GetModel(const LWOOBJID modelID) = 0;
|
||||
};
|
||||
#endif //!__IPROPERTIESCONTENTS__H__
|
||||
|
@ -9,10 +9,11 @@ public:
|
||||
struct MasterInfo {
|
||||
std::string ip;
|
||||
uint32_t port{};
|
||||
std::string password{};
|
||||
};
|
||||
|
||||
// Set the master server ip and port.
|
||||
virtual void SetMasterIp(const std::string_view ip, const uint32_t port) = 0;
|
||||
virtual void SetMasterInfo(const MasterInfo& info) = 0;
|
||||
|
||||
// Get the master server info.
|
||||
virtual std::optional<MasterInfo> GetMasterInfo() = 0;
|
||||
|
@ -12,6 +12,7 @@ public:
|
||||
struct Model {
|
||||
std::stringstream lxfmlData;
|
||||
LWOOBJID id{};
|
||||
LWOOBJID modelID{};
|
||||
};
|
||||
|
||||
// Gets all UGC models for the given property id.
|
||||
@ -27,6 +28,6 @@ public:
|
||||
virtual void DeleteUgcModelData(const LWOOBJID& modelId) = 0;
|
||||
|
||||
// Inserts a new UGC model into the database.
|
||||
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) = 0;
|
||||
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0;
|
||||
};
|
||||
#endif //!__IUGC__H__
|
||||
|
@ -48,7 +48,7 @@ public:
|
||||
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;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -70,23 +70,24 @@ public:
|
||||
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 UpdateLastSave(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 UpdateModel(const LWOOBJID& modelID, 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 InsertNewMail(const MailInfo& mail) override;
|
||||
void InsertNewUgcModel(
|
||||
std::istringstream& sd0Data,
|
||||
std::stringstream& 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;
|
||||
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<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;
|
||||
@ -96,7 +97,7 @@ public:
|
||||
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;
|
||||
void SetMasterInfo(const IServers::MasterInfo& info) override;
|
||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||
void InsertDefaultPersistentId() override;
|
||||
void UpdatePersistentId(const uint32_t id) override;
|
||||
@ -124,8 +125,10 @@ public:
|
||||
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;
|
||||
bool IsNameInUse(const std::string_view name) override;
|
||||
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
|
||||
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
|
||||
private:
|
||||
|
||||
// Generic query functions that can be used for any query.
|
||||
|
@ -76,3 +76,9 @@ void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const st
|
||||
void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
|
||||
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast<uint32_t>(time(NULL)), characterId);
|
||||
}
|
||||
|
||||
bool MySQLDatabase::IsNameInUse(const std::string_view name) {
|
||||
auto result = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
|
||||
|
||||
return result->next();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
|
||||
void MySQLDatabase::InsertNewMail(const 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`)"
|
||||
@ -18,17 +19,17 @@ void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
mail.itemCount);
|
||||
}
|
||||
|
||||
std::vector<IMail::MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
|
||||
std::vector<MailInfo> MySQLDatabase::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;
|
||||
std::vector<MailInfo> toReturn;
|
||||
toReturn.reserve(res->rowsCount());
|
||||
|
||||
while (res->next()) {
|
||||
IMail::MailInfo mail;
|
||||
MailInfo mail;
|
||||
mail.id = res->getUInt64("id");
|
||||
mail.subject = res->getString("subject").c_str();
|
||||
mail.body = res->getString("body").c_str();
|
||||
@ -46,14 +47,14 @@ std::vector<IMail::MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t char
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<IMail::MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
|
||||
std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
|
||||
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
|
||||
if (!res->next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
IMail::MailInfo toReturn;
|
||||
MailInfo toReturn;
|
||||
toReturn.itemLOT = res->getInt("attachment_lot");
|
||||
toReturn.itemCount = res->getInt("attachment_count");
|
||||
|
||||
|
@ -56,6 +56,7 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
||||
params.playerId
|
||||
);
|
||||
if (count->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||
}
|
||||
} else {
|
||||
@ -109,11 +110,13 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
||||
params.playerSort
|
||||
);
|
||||
if (count->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||
}
|
||||
}
|
||||
|
||||
while (properties->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
auto& entry = result->entries.emplace_back();
|
||||
entry.id = properties->getUInt64("id");
|
||||
entry.ownerId = properties->getUInt64("owner_id");
|
||||
@ -170,6 +173,10 @@ void MySQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ? LIMIT 1;", info.name, info.description, info.id);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, info.id);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
|
||||
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ? LIMIT 1;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
|
||||
}
|
||||
|
@ -52,14 +52,39 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
|
||||
}
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, 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);
|
||||
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
|
||||
}
|
||||
|
||||
void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
|
||||
}
|
||||
|
||||
IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) {
|
||||
auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
|
||||
|
||||
IPropertyContents::Model model{};
|
||||
while (result->next()) {
|
||||
model.id = result->getUInt64("id");
|
||||
model.lot = static_cast<LOT>(result->getUInt("lot"));
|
||||
model.position.x = result->getFloat("x");
|
||||
model.position.y = result->getFloat("y");
|
||||
model.position.z = result->getFloat("z");
|
||||
model.rotation.w = result->getFloat("rw");
|
||||
model.rotation.x = result->getFloat("rx");
|
||||
model.rotation.y = result->getFloat("ry");
|
||||
model.rotation.z = result->getFloat("rz");
|
||||
model.ugcId = result->getUInt64("ugc_id");
|
||||
model.behaviors[0] = result->getInt("behavior_1");
|
||||
model.behaviors[1] = result->getInt("behavior_2");
|
||||
model.behaviors[2] = result->getInt("behavior_3");
|
||||
model.behaviors[3] = result->getInt("behavior_4");
|
||||
model.behaviors[4] = result->getInt("behavior_5");
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
void MySQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
||||
void MySQLDatabase::SetMasterInfo(const MasterInfo& info) {
|
||||
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
|
||||
// since it would be two queries anyways.
|
||||
ExecuteDelete("TRUNCATE TABLE servers;");
|
||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
|
||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`, `master_password`) VALUES ('master', ?, ?, 0, 171022, ?)", info.ip, info.port, info.password);
|
||||
}
|
||||
|
||||
std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
|
||||
auto result = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
|
||||
auto result = ExecuteSelect("SELECT ip, port, master_password FROM servers WHERE name='master' LIMIT 1;");
|
||||
|
||||
if (!result->next()) {
|
||||
return std::nullopt;
|
||||
@ -18,6 +18,7 @@ std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
|
||||
|
||||
toReturn.ip = result->getString("ip").c_str();
|
||||
toReturn.port = result->getInt("port");
|
||||
toReturn.password = result->getString("master_password").c_str();
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
std::vector<IUgc::Model> MySQLDatabase::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;",
|
||||
"SELECT lxfml, u.id as ugcID, pc.id as modelID 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;
|
||||
@ -13,7 +13,8 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
|
||||
// blob is owned by the query, so we need to do a deep copy :/
|
||||
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
|
||||
model.lxfmlData << blob->rdbuf();
|
||||
model.id = result->getUInt64("id");
|
||||
model.id = result->getUInt64("ugcID");
|
||||
model.modelID = result->getUInt64("modelID");
|
||||
toReturn.push_back(std::move(model));
|
||||
}
|
||||
|
||||
@ -21,13 +22,14 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
|
||||
}
|
||||
|
||||
std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
|
||||
auto result = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||
auto result = ExecuteSelect("SELECT u.id AS ugcID, lxfml, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON pc.ugc_id = u.id WHERE pc.lot = 14 AND pc.ugc_id IS NOT NULL;");
|
||||
|
||||
std::vector<IUgc::Model> models;
|
||||
models.reserve(result->rowsCount());
|
||||
while (result->next()) {
|
||||
IUgc::Model model;
|
||||
model.id = result->getInt64("id");
|
||||
model.id = result->getInt64("ugcID");
|
||||
model.modelID = result->getUInt64("modelID");
|
||||
|
||||
// blob is owned by the query, so we need to do a deep copy :/
|
||||
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
|
||||
@ -43,7 +45,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertNewUgcModel(
|
||||
std::istringstream& sd0Data, // cant be const sad
|
||||
std:: stringstream& sd0Data, // cant be const sad
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) {
|
||||
@ -65,7 +67,7 @@ void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
const std::istream stream(lxfml.rdbuf());
|
||||
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "dConfig.h"
|
||||
#include "Logger.h"
|
||||
#include "dPlatforms.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
|
||||
// Static Variables
|
||||
|
||||
@ -17,7 +18,14 @@ namespace {
|
||||
void SQLiteDatabase::Connect() {
|
||||
LOG("Using SQLite database");
|
||||
con = new CppSQLite3DB();
|
||||
con->open(Game::config->GetValue("sqlite_database_path").c_str());
|
||||
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.
|
||||
|
@ -46,7 +46,7 @@ public:
|
||||
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;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -68,23 +68,24 @@ public:
|
||||
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 UpdateLastSave(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 UpdateModel(const LWOOBJID& modelID, 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 InsertNewMail(const MailInfo& mail) override;
|
||||
void InsertNewUgcModel(
|
||||
std::istringstream& sd0Data,
|
||||
std::stringstream& 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;
|
||||
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<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;
|
||||
@ -94,7 +95,7 @@ public:
|
||||
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;
|
||||
void SetMasterInfo(const IServers::MasterInfo& info) override;
|
||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||
void InsertDefaultPersistentId() override;
|
||||
void UpdatePersistentId(const uint32_t id) override;
|
||||
@ -123,6 +124,8 @@ public:
|
||||
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;
|
||||
bool IsNameInUse(const std::string_view name) override;
|
||||
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
|
||||
private:
|
||||
CppSQLite3Statement CreatePreppedStmt(const std::string& query);
|
||||
|
||||
|
@ -77,3 +77,9 @@ void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const s
|
||||
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
|
||||
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), characterId);
|
||||
}
|
||||
|
||||
bool SQLiteDatabase::IsNameInUse(const std::string_view name) {
|
||||
auto [_, result] = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
|
||||
|
||||
return !result.eof();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
void SQLiteDatabase::InsertNewMail(const 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`)"
|
||||
@ -18,16 +18,16 @@ void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
mail.itemCount);
|
||||
}
|
||||
|
||||
std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
|
||||
std::vector<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;
|
||||
std::vector<MailInfo> toReturn;
|
||||
|
||||
while (!res.eof()) {
|
||||
IMail::MailInfo mail;
|
||||
MailInfo mail;
|
||||
mail.id = res.getInt64Field("id");
|
||||
mail.subject = res.getStringField("subject");
|
||||
mail.body = res.getStringField("body");
|
||||
@ -46,14 +46,14 @@ std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t cha
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<IMail::MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
|
||||
std::optional<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;
|
||||
MailInfo toReturn;
|
||||
toReturn.itemLOT = res.getIntField("attachment_lot");
|
||||
toReturn.itemCount = res.getIntField("attachment_count");
|
||||
|
||||
|
@ -175,6 +175,10 @@ void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, 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());
|
||||
}
|
||||
|
@ -52,14 +52,41 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, 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);
|
||||
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
|
||||
}
|
||||
|
||||
IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) {
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
|
||||
|
||||
IPropertyContents::Model model{};
|
||||
if (!result.eof()) {
|
||||
do {
|
||||
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");
|
||||
} while (result.nextRow());
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
void SQLiteDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
||||
void SQLiteDatabase::SetMasterInfo(const MasterInfo& info) {
|
||||
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
|
||||
// since it would be two queries anyways.
|
||||
ExecuteDelete("DELETE FROM servers;");
|
||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
|
||||
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`, `master_password`) VALUES ('master', ?, ?, 0, 171022, ?)", info.ip, info.port, info.password);
|
||||
}
|
||||
|
||||
std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
|
||||
auto [_, result] = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
|
||||
auto [_, result] = ExecuteSelect("SELECT ip, port, master_password FROM servers WHERE name='master' LIMIT 1;");
|
||||
|
||||
if (result.eof()) {
|
||||
return std::nullopt;
|
||||
@ -18,6 +18,7 @@ std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
|
||||
|
||||
toReturn.ip = result.getStringField("ip");
|
||||
toReturn.port = result.getIntField("port");
|
||||
toReturn.password = result.getStringField("master_password");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
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;",
|
||||
"SELECT lxfml, u.id AS ugcID, pc.id AS modelID 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;
|
||||
@ -13,7 +13,8 @@ std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
|
||||
int blobSize{};
|
||||
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
|
||||
model.id = result.getInt64Field("id");
|
||||
model.id = result.getInt64Field("ugcID");
|
||||
model.modelID = result.getInt64Field("modelID");
|
||||
toReturn.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
}
|
||||
@ -22,12 +23,13 @@ std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
|
||||
}
|
||||
|
||||
std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
|
||||
auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||
auto [_, result] = ExecuteSelect("SELECT u.id AS ugcID, pc.id AS modelID, lxfml FROM ugc AS u JOIN properties_contents AS pc ON pc.id = u.id;");
|
||||
|
||||
std::vector<IUgc::Model> models;
|
||||
while (!result.eof()) {
|
||||
IUgc::Model model;
|
||||
model.id = result.getInt64Field("id");
|
||||
model.id = result.getInt64Field("ugcID");
|
||||
model.modelID = result.getInt64Field("modelID");
|
||||
|
||||
int blobSize{};
|
||||
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||
@ -44,7 +46,7 @@ void SQLiteDatabase::RemoveUnreferencedUgcModels() {
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewUgcModel(
|
||||
std::istringstream& sd0Data, // cant be const sad
|
||||
std::stringstream& sd0Data, // cant be const sad
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) {
|
||||
@ -66,7 +68,7 @@ void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
const std::istream stream(lxfml.rdbuf());
|
||||
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
|
||||
}
|
||||
|
||||
@ -148,6 +148,10 @@ void TestSQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
|
||||
|
||||
}
|
||||
@ -164,7 +168,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
|
||||
}
|
||||
|
||||
@ -184,19 +188,19 @@ void TestSQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& i
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
|
||||
void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
|
||||
|
||||
}
|
||||
|
||||
std::vector<IMail::MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
|
||||
std::vector<MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<IMail::MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
|
||||
std::optional<MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -236,7 +240,7 @@ void TestSQLDatabase::InsertNewAccount(const std::string_view username, const st
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
|
||||
void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class TestSQLDatabase : public GameDatabase {
|
||||
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;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -47,23 +47,24 @@ class TestSQLDatabase : public GameDatabase {
|
||||
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 UpdateLastSave(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 UpdateModel(const LWOOBJID& modelID, 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 InsertNewMail(const MailInfo& mail) override;
|
||||
void InsertNewUgcModel(
|
||||
std::istringstream& sd0Data,
|
||||
std::stringstream& 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;
|
||||
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<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;
|
||||
@ -73,7 +74,7 @@ class TestSQLDatabase : public GameDatabase {
|
||||
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;
|
||||
void SetMasterInfo(const IServers::MasterInfo& info) override;
|
||||
std::optional<uint32_t> GetCurrentPersistentId() override;
|
||||
void InsertDefaultPersistentId() override;
|
||||
void UpdatePersistentId(const uint32_t id) override;
|
||||
@ -102,6 +103,9 @@ class TestSQLDatabase : public GameDatabase {
|
||||
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; };
|
||||
|
||||
bool IsNameInUse(const std::string_view name) override { return false; };
|
||||
IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; }
|
||||
};
|
||||
|
||||
#endif //!TESTSQLDATABASE_H
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "GeneralUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "ModelNormalizeMigration.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
@ -35,7 +36,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->CreateMigrationHistoryTable();
|
||||
|
||||
// has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again.
|
||||
|
||||
|
||||
const auto migrationFolder = Database::GetMigrationFolder();
|
||||
if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") {
|
||||
LOG("Running migration: 17_migration_for_migrations.sql");
|
||||
@ -45,6 +46,7 @@ void MigrationRunner::RunMigrations() {
|
||||
|
||||
std::string finalSQL = "";
|
||||
bool runSd0Migrations = false;
|
||||
bool runNormalizeMigrations = false;
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
||||
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
||||
|
||||
@ -57,6 +59,8 @@ void MigrationRunner::RunMigrations() {
|
||||
LOG("Running migration: %s", migration.name.c_str());
|
||||
if (migration.name == "5_brick_model_sd0.sql") {
|
||||
runSd0Migrations = true;
|
||||
} else if (migration.name.ends_with("_normalize_model_positions.sql")) {
|
||||
runNormalizeMigrations = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data.c_str());
|
||||
}
|
||||
@ -64,7 +68,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->InsertMigration(migration.name);
|
||||
}
|
||||
|
||||
if (finalSQL.empty() && !runSd0Migrations) {
|
||||
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations) {
|
||||
LOG("Server database is up to date.");
|
||||
return;
|
||||
}
|
||||
@ -88,6 +92,10 @@ void MigrationRunner::RunMigrations() {
|
||||
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
|
||||
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
|
||||
}
|
||||
|
||||
if (runNormalizeMigrations) {
|
||||
ModelNormalizeMigration::Run();
|
||||
}
|
||||
}
|
||||
|
||||
void MigrationRunner::RunSQLiteMigrations() {
|
||||
|
30
dDatabase/ModelNormalizeMigration.cpp
Normal file
30
dDatabase/ModelNormalizeMigration.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "ModelNormalizeMigration.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "Lxfml.h"
|
||||
#include "Sd0.h"
|
||||
|
||||
void ModelNormalizeMigration::Run() {
|
||||
const auto oldCommit = Database::Get()->GetAutoCommit();
|
||||
Database::Get()->SetAutoCommit(false);
|
||||
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
|
||||
const auto model = Database::Get()->GetModel(modelID);
|
||||
// only BBB models (lot 14) and models with a position of NiPoint3::ZERO need to have their position fixed.
|
||||
if (model.position != NiPoint3Constant::ZERO || model.lot != 14) continue;
|
||||
|
||||
Sd0 sd0(lxfmlData);
|
||||
const auto asStr = sd0.GetAsStringUncompressed();
|
||||
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
|
||||
if (newCenter == NiPoint3Constant::ZERO) {
|
||||
LOG("Failed to update model %llu due to failure reading xml.");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
|
||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
||||
auto asStream = sd0.GetAsStream();
|
||||
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
|
||||
Database::Get()->UpdateUgcModelData(id, asStream);
|
||||
}
|
||||
Database::Get()->SetAutoCommit(oldCommit);
|
||||
}
|
11
dDatabase/ModelNormalizeMigration.h
Normal file
11
dDatabase/ModelNormalizeMigration.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef MODELNORMALIZEMIGRATION_H
|
||||
#define MODELNORMALIZEMIGRATION_H
|
||||
|
||||
namespace ModelNormalizeMigration {
|
||||
void Run();
|
||||
};
|
||||
|
||||
#endif //!MODELNORMALIZEMIGRATION_H
|
@ -21,6 +21,7 @@
|
||||
#include "eObjectBits.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "ePlayerFlag.h"
|
||||
#include "CDPlayerFlagsTable.h"
|
||||
|
||||
Character::Character(uint32_t id, User* parentUser) {
|
||||
//First load the name, etc:
|
||||
@ -203,6 +204,7 @@ void Character::DoQuickXMLDataParse() {
|
||||
while (currentChild) {
|
||||
const auto* temp = currentChild->Attribute("v");
|
||||
const auto* id = currentChild->Attribute("id");
|
||||
const auto* si = currentChild->Attribute("si");
|
||||
if (temp && id) {
|
||||
uint32_t index = 0;
|
||||
uint64_t value = 0;
|
||||
@ -211,6 +213,9 @@ void Character::DoQuickXMLDataParse() {
|
||||
value = std::stoull(temp);
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(index, value));
|
||||
} else if (si) {
|
||||
auto value = GeneralUtils::TryParse<uint32_t>(si);
|
||||
if (value) m_SessionFlags.insert(value.value());
|
||||
}
|
||||
currentChild = currentChild->NextSiblingElement();
|
||||
}
|
||||
@ -231,6 +236,12 @@ void Character::SetBuildMode(bool buildMode) {
|
||||
}
|
||||
|
||||
void Character::SaveXMLToDatabase() {
|
||||
// Check that we can actually _save_ before saving
|
||||
if (!m_OurEntity) {
|
||||
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
//For metrics, we'll record the time it took to save:
|
||||
auto start = std::chrono::system_clock::now();
|
||||
|
||||
@ -277,33 +288,19 @@ void Character::SaveXMLToDatabase() {
|
||||
}
|
||||
|
||||
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
|
||||
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) {
|
||||
auto* f = m_Doc.NewElement("f");
|
||||
f->SetAttribute("id", flag.first);
|
||||
|
||||
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
|
||||
//Only signed 64-bits ints would work.
|
||||
std::string v = std::to_string(flag.second);
|
||||
f->SetAttribute("v", v.c_str());
|
||||
|
||||
flags->LinkEndChild(f);
|
||||
for (const auto& [index, flagBucket] : m_PlayerFlags) {
|
||||
auto* f = flags->InsertNewChildElement("f");
|
||||
f->SetAttribute("id", index);
|
||||
f->SetAttribute("v", flagBucket);
|
||||
}
|
||||
|
||||
// Prevents the news feed from showing up on world transfers
|
||||
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) {
|
||||
auto* s = m_Doc.NewElement("s");
|
||||
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
|
||||
flags->LinkEndChild(s);
|
||||
for (const auto& sessionFlag : m_SessionFlags) {
|
||||
auto* s = flags->InsertNewChildElement("s");
|
||||
s->SetAttribute("si", sessionFlag);
|
||||
}
|
||||
|
||||
SaveXmlRespawnCheckpoints();
|
||||
|
||||
//Call upon the entity to update our xmlDoc:
|
||||
if (!m_OurEntity) {
|
||||
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
m_OurEntity->UpdateXMLDoc(m_Doc);
|
||||
|
||||
WriteToDatabase();
|
||||
@ -323,8 +320,8 @@ void Character::SetIsNewLogin() {
|
||||
while (currentChild) {
|
||||
auto* nextChild = currentChild->NextSiblingElement();
|
||||
if (currentChild->Attribute("si")) {
|
||||
LOG("Removed session flag (%s) from character %i:%s, saving character to database", currentChild->Attribute("si"), GetID(), GetName().c_str());
|
||||
flags->DeleteChild(currentChild);
|
||||
LOG("Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
|
||||
WriteToDatabase();
|
||||
}
|
||||
currentChild = nextChild;
|
||||
@ -357,49 +354,64 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the index first
|
||||
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
|
||||
// Check if flag index exists
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Update the value
|
||||
if (value) {
|
||||
it->second |= shiftedValue;
|
||||
} else {
|
||||
it->second &= ~shiftedValue;
|
||||
}
|
||||
if (flagEntry && flagEntry->sessionOnly) {
|
||||
if (value) m_SessionFlags.insert(flagId);
|
||||
else m_SessionFlags.erase(flagId);
|
||||
} else {
|
||||
if (value) {
|
||||
// Otherwise, insert the value
|
||||
uint64_t flagValue = 0;
|
||||
// Calculate the index first
|
||||
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
|
||||
flagValue |= shiftedValue;
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
|
||||
// Check if flag index exists
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Update the value
|
||||
if (value) {
|
||||
it->second |= shiftedValue;
|
||||
} else {
|
||||
it->second &= ~shiftedValue;
|
||||
}
|
||||
} else {
|
||||
if (value) {
|
||||
// Otherwise, insert the value
|
||||
uint64_t flagValue = 0;
|
||||
|
||||
flagValue |= shiftedValue;
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the client that a flag has changed server-side
|
||||
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
|
||||
}
|
||||
|
||||
bool Character::GetPlayerFlag(const uint32_t flagId) const {
|
||||
// Calculate the index first
|
||||
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
using enum ePlayerFlag;
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
bool toReturn = false; //by def, return false.
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Don't set the data if we don't have to
|
||||
return (it->second & shiftedValue) != 0;
|
||||
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
|
||||
if (flagEntry && flagEntry->sessionOnly) {
|
||||
toReturn = m_SessionFlags.contains(flagId);
|
||||
} else {
|
||||
// Calculate the index first
|
||||
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Don't set the data if we don't have to
|
||||
toReturn = (it->second & shiftedValue) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false; //by def, return false.
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void Character::SetRetroactiveFlags() {
|
||||
|
@ -620,6 +620,12 @@ private:
|
||||
*/
|
||||
uint64_t m_LastLogin{};
|
||||
|
||||
/**
|
||||
* Flags only set for the duration of a session
|
||||
*
|
||||
*/
|
||||
std::set<uint32_t> m_SessionFlags;
|
||||
|
||||
/**
|
||||
* The gameplay flags this character has (not just true values)
|
||||
*/
|
||||
|
@ -386,6 +386,9 @@ void Entity::Initialize() {
|
||||
if (m_Character) {
|
||||
comp->LoadFromXml(m_Character->GetXMLDoc());
|
||||
} else {
|
||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
||||
|
||||
if (componentID > 0) {
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
||||
|
||||
@ -420,9 +423,6 @@ void Entity::Initialize() {
|
||||
comp->SetMinCoins(currencyValues[0].minvalue);
|
||||
comp->SetMaxCoins(currencyValues[0].maxvalue);
|
||||
}
|
||||
|
||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
||||
}
|
||||
} else {
|
||||
comp->SetHealth(1);
|
||||
@ -545,9 +545,8 @@ void Entity::Initialize() {
|
||||
|
||||
// ZoneControl script
|
||||
if (m_TemplateID == 2365) {
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
const auto zoneID = Game::zoneManager->GetZoneID();
|
||||
const CDZoneTable* zoneData = zoneTable->Query(zoneID.GetMapID());
|
||||
const CDZoneTable* zoneData = CDZoneTableTable::Query(zoneID.GetMapID());
|
||||
|
||||
if (zoneData != nullptr) {
|
||||
int zoneScriptID = zoneData->scriptID;
|
||||
@ -775,6 +774,12 @@ void Entity::Initialize() {
|
||||
// Hacky way to trigger these when the object has had a chance to get constructed
|
||||
AddCallbackTimer(0, [this]() {
|
||||
this->GetScript()->OnStartup(this);
|
||||
if (this->m_ParentEntity) {
|
||||
GameMessages::ChildLoaded childLoaded;
|
||||
childLoaded.childID = this->m_ObjectID;
|
||||
childLoaded.templateID = this->GetLOT();
|
||||
this->m_ParentEntity->OnChildLoaded(childLoaded);
|
||||
}
|
||||
});
|
||||
|
||||
if (!m_Character && Game::entityManager->GetGhostingEnabled()) {
|
||||
@ -854,6 +859,9 @@ void Entity::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, co
|
||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||
if (!destroyableComponent) return;
|
||||
destroyableComponent->Subscribe(scriptObjId, scriptToAdd);
|
||||
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||
LOG("Subscribing to PlayerResurrectionFinished");
|
||||
m_Subscriptions[scriptObjId][notificationName] = scriptToAdd;
|
||||
}
|
||||
}
|
||||
|
||||
@ -862,6 +870,9 @@ void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationNa
|
||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||
if (!destroyableComponent) return;
|
||||
destroyableComponent->Unsubscribe(scriptObjId);
|
||||
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||
LOG("Unsubscribing from PlayerResurrectionFinished");
|
||||
m_Subscriptions[scriptObjId].erase(notificationName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1493,6 +1504,27 @@ 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::OnChildLoaded(GameMessages::ChildLoaded& childLoaded) {
|
||||
GetScript()->OnChildLoaded(*this, childLoaded);
|
||||
}
|
||||
|
||||
void Entity::NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg) {
|
||||
for (const auto& [id, scriptList] : m_Subscriptions) {
|
||||
auto it = scriptList.find("PlayerResurrectionFinished");
|
||||
if (it == scriptList.end()) continue;
|
||||
|
||||
it->second->NotifyPlayerResurrectionFinished(*this, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
|
||||
GetScript()->OnRequestActivityExit(sender, player, canceled);
|
||||
}
|
||||
@ -1532,7 +1564,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
|
||||
|
||||
m_DieCallbacks.clear();
|
||||
|
||||
//OMAI WA MOU, SHINDERIU
|
||||
//お前はもう死んでいる
|
||||
|
||||
GetScript()->OnDie(this, murderer);
|
||||
|
||||
@ -2175,7 +2207,7 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
||||
|
||||
int32_t Entity::GetCollisionGroup() const {
|
||||
for (const auto* component : m_Components | std::views::values) {
|
||||
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
|
||||
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
|
||||
if (compToCheck) {
|
||||
return compToCheck->GetCollisionGroup();
|
||||
}
|
||||
@ -2183,3 +2215,17 @@ int32_t Entity::GetCollisionGroup() const {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Entity::HandleMsg(GameMessages::GameMsg& msg) const {
|
||||
bool handled = false;
|
||||
const auto [beg, end] = m_MsgHandlers.equal_range(msg.msgId);
|
||||
for (auto it = beg; it != end; ++it) {
|
||||
if (it->second) handled |= it->second(msg);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler) {
|
||||
m_MsgHandlers.emplace(msgId, handler);
|
||||
}
|
||||
|
@ -13,6 +13,18 @@
|
||||
#include "eKillType.h"
|
||||
#include "Observable.h"
|
||||
|
||||
namespace GameMessages {
|
||||
struct GameMsg;
|
||||
struct ActivityNotify;
|
||||
struct ShootingGalleryFire;
|
||||
struct ChildLoaded;
|
||||
struct PlayerResurrectionFinished;
|
||||
};
|
||||
|
||||
namespace MessageType {
|
||||
enum class Game : uint16_t;
|
||||
}
|
||||
|
||||
namespace Loot {
|
||||
class Info;
|
||||
};
|
||||
@ -210,6 +222,10 @@ public:
|
||||
void OnZonePropertyModelRemoved(Entity* player);
|
||||
void OnZonePropertyModelRemovedWhileEquipped(Entity* player);
|
||||
void OnZonePropertyModelRotated(Entity* player);
|
||||
void OnActivityNotify(GameMessages::ActivityNotify& notify);
|
||||
void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify);
|
||||
void OnChildLoaded(GameMessages::ChildLoaded& childLoaded);
|
||||
void NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg);
|
||||
|
||||
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
|
||||
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
|
||||
@ -305,6 +321,10 @@ public:
|
||||
// Scale will only be communicated to the client when the construction packet is sent
|
||||
void SetScale(const float scale) { m_Scale = scale; };
|
||||
|
||||
void RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler);
|
||||
|
||||
bool HandleMsg(GameMessages::GameMsg& msg) const;
|
||||
|
||||
/**
|
||||
* @brief The observable for player entity position updates.
|
||||
*/
|
||||
@ -363,6 +383,11 @@ protected:
|
||||
* Collision
|
||||
*/
|
||||
std::vector<LWOOBJID> m_TargetsInPhantom;
|
||||
|
||||
// objectID of receiver and map of notification name to script
|
||||
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
|
||||
|
||||
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -99,7 +99,7 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
|
||||
}
|
||||
|
||||
// Exclude the zone control object from any flags
|
||||
if (!controller && info.lot != 14) {
|
||||
if (!controller) {
|
||||
|
||||
// The client flags means the client should render the entity
|
||||
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
||||
|
@ -24,12 +24,13 @@ namespace LeaderboardManager {
|
||||
std::map<GameID, Leaderboard::Type> leaderboardCache;
|
||||
}
|
||||
|
||||
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) {
|
||||
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type leaderboardType) {
|
||||
this->gameID = gameID;
|
||||
this->weekly = weekly;
|
||||
this->infoType = infoType;
|
||||
this->leaderboardType = leaderboardType;
|
||||
this->relatedPlayer = relatedPlayer;
|
||||
this->numResults = numResults;
|
||||
}
|
||||
|
||||
Leaderboard::~Leaderboard() {
|
||||
@ -95,7 +96,7 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>
|
||||
// Score:1
|
||||
entry.push_back(new LDFData<int32_t>(u"Streak", leaderboardEntry.secondaryScore));
|
||||
// Streak:1
|
||||
entry.push_back(new LDFData<float>(u"HitPercentage", (leaderboardEntry.tertiaryScore / 100.0f)));
|
||||
entry.push_back(new LDFData<float>(u"HitPercentage", leaderboardEntry.tertiaryScore));
|
||||
// HitPercentage:3 between 0 and 1
|
||||
break;
|
||||
case Racing:
|
||||
@ -144,7 +145,7 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
|
||||
std::vector<ILeaderboard::Entry> FilterToNumResults(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType, const uint32_t numResults) {
|
||||
std::vector<ILeaderboard::Entry> toReturn;
|
||||
|
||||
int32_t index = 0;
|
||||
@ -155,18 +156,19 @@ std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entr
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderboard.size() < 10) {
|
||||
if (leaderboard.size() < numResults) {
|
||||
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
|
||||
} else if (index < numResults) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.begin() + numResults); // 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 if (index > leaderboard.size() - numResults) {
|
||||
toReturn.assign(leaderboard.end() - numResults, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
|
||||
index = leaderboard.size() - numResults;
|
||||
} else {
|
||||
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
|
||||
index -= 5;
|
||||
auto half = numResults / 2;
|
||||
toReturn.assign(leaderboard.begin() + index - half, leaderboard.begin() + index + half); // get the 5 above and below
|
||||
index -= half;
|
||||
}
|
||||
|
||||
int32_t i = index;
|
||||
@ -178,14 +180,16 @@ std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entr
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
|
||||
using namespace std::chrono;
|
||||
// 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.
|
||||
const auto epochTime = system_clock::now();
|
||||
constexpr auto oneWeek = weeks(1);
|
||||
|
||||
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
|
||||
for (const auto& entry : leaderboard) {
|
||||
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
|
||||
const sys_time<seconds> asSysTime(seconds(entry.lastPlayedTimestamp));
|
||||
const auto timeDiff = epochTime - asSysTime;
|
||||
if (timeDiff < oneWeek) {
|
||||
weeklyLeaderboard.push_back(entry);
|
||||
}
|
||||
}
|
||||
@ -199,9 +203,9 @@ std::vector<ILeaderboard::Entry> FilterFriends(const std::vector<ILeaderboard::E
|
||||
std::vector<ILeaderboard::Entry> friendsLeaderboard;
|
||||
for (const auto& entry : leaderboard) {
|
||||
const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
|
||||
return entry.charId == data.friendID || entry.charId == relatedPlayer;
|
||||
return entry.charId == data.friendID;
|
||||
});
|
||||
if (res != friendOfPlayer.cend()) {
|
||||
if (res != friendOfPlayer.cend() || entry.charId == relatedPlayer) {
|
||||
friendsLeaderboard.push_back(entry);
|
||||
}
|
||||
}
|
||||
@ -213,14 +217,15 @@ std::vector<ILeaderboard::Entry> ProcessLeaderboard(
|
||||
const std::vector<ILeaderboard::Entry>& leaderboard,
|
||||
const bool weekly,
|
||||
const Leaderboard::InfoType infoType,
|
||||
const uint32_t relatedPlayer) {
|
||||
const uint32_t relatedPlayer,
|
||||
const uint32_t numResults) {
|
||||
std::vector<ILeaderboard::Entry> toReturn;
|
||||
|
||||
if (infoType == Leaderboard::InfoType::Friends) {
|
||||
const auto friendsLeaderboard = FilterFriends(leaderboard, relatedPlayer);
|
||||
toReturn = FilterTo10(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType);
|
||||
toReturn = FilterToNumResults(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType, numResults);
|
||||
} else {
|
||||
toReturn = FilterTo10(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType);
|
||||
toReturn = FilterToNumResults(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType, numResults);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
@ -255,7 +260,7 @@ void Leaderboard::SetupLeaderboard(bool weekly) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
|
||||
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer, numResults);
|
||||
|
||||
QueryToLdf(*this, processedLeaderboard);
|
||||
}
|
||||
@ -301,8 +306,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults) {
|
||||
Leaderboard leaderboard(gameID, infoType, weekly, playerID, numResults, GetLeaderboardType(gameID));
|
||||
leaderboard.SetupLeaderboard(weekly);
|
||||
leaderboard.Send(targetID);
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public:
|
||||
None
|
||||
};
|
||||
Leaderboard() = delete;
|
||||
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None);
|
||||
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type = None);
|
||||
|
||||
~Leaderboard();
|
||||
|
||||
@ -79,6 +79,7 @@ private:
|
||||
InfoType infoType;
|
||||
Leaderboard::Type leaderboardType;
|
||||
bool weekly;
|
||||
uint32_t numResults;
|
||||
public:
|
||||
LeaderboardEntry& PushBackEntry() {
|
||||
return entries.emplace_back();
|
||||
@ -90,7 +91,7 @@ public:
|
||||
};
|
||||
|
||||
namespace LeaderboardManager {
|
||||
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
|
||||
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults);
|
||||
|
||||
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "MessageType/Chat.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "CharacterComponent.h"
|
||||
|
||||
UserManager* UserManager::m_Address = nullptr;
|
||||
|
||||
@ -305,13 +306,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle);
|
||||
LOT pantsLOT = FindCharPantsID(pantsColor);
|
||||
|
||||
if (!name.empty() && Database::Get()->GetCharacterInfo(name)) {
|
||||
if (!name.empty() && Database::Get()->IsNameInUse(name)) {
|
||||
LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str());
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Database::Get()->GetCharacterInfo(predefinedName)) {
|
||||
if (Database::Get()->IsNameInUse(predefinedName)) {
|
||||
LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str());
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE);
|
||||
return;
|
||||
@ -324,7 +325,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
}
|
||||
|
||||
//Now that the name is ok, we can get an objectID from Master:
|
||||
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) mutable {
|
||||
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) {
|
||||
if (Database::Get()->GetCharacterInfo(objectID)) {
|
||||
LOG("Character object id unavailable, check object_id_tracker!");
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
|
||||
@ -340,7 +341,10 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
|
||||
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
|
||||
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\"></char>";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
|
||||
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
|
||||
|
||||
xml << "</char>";
|
||||
|
||||
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
|
||||
|
||||
@ -369,13 +373,14 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
|
||||
// If predefined name is invalid, change it to be their object id
|
||||
// that way more than one player can create characters if the predefined name files are not provided
|
||||
if (predefinedName == "INVALID") {
|
||||
auto assignedPredefinedName = predefinedName;
|
||||
if (assignedPredefinedName == "INVALID") {
|
||||
std::stringstream nameObjID;
|
||||
nameObjID << "minifig" << objectID;
|
||||
predefinedName = nameObjID.str();
|
||||
assignedPredefinedName = nameObjID.str();
|
||||
}
|
||||
|
||||
std::string_view nameToAssign = !name.empty() && nameOk ? name : predefinedName;
|
||||
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
|
||||
std::string pendingName = !name.empty() && !nameOk ? name : "";
|
||||
|
||||
ICharInfo::Info info;
|
||||
@ -521,6 +526,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
|
||||
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||
if (character) {
|
||||
auto* entity = character->GetEntity();
|
||||
if (entity) {
|
||||
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
if (characterComponent) {
|
||||
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
||||
}
|
||||
}
|
||||
character->SetZoneID(zoneID);
|
||||
character->SetZoneInstance(zoneInstance);
|
||||
character->SetZoneClone(zoneClone);
|
||||
|
@ -20,7 +20,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
|
||||
|
||||
//Handle player damage cooldown
|
||||
if (entity->IsPlayer() && !this->m_DontApplyImmune) {
|
||||
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
|
||||
const float immunityTime = Game::zoneManager->GetWorldConfig().globalImmunityTime;
|
||||
destroyableComponent->SetDamageCooldownTimer(immunityTime);
|
||||
}
|
||||
}
|
||||
@ -214,7 +214,7 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
|
||||
//Handle player damage cooldown
|
||||
if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) {
|
||||
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig()->globalImmunityTime);
|
||||
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig().globalImmunityTime);
|
||||
}
|
||||
|
||||
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user