Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ec37c4fd78 Address feedback: SQLite recommendation, GM terminology, testing capabilities, and file endings
Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com>
2025-09-03 04:48:45 +00:00
copilot-swe-agent[bot]
42327a1b8e Add comprehensive .github/copilot-instructions.md with validated commands and timing
Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com>
2025-09-02 21:46:58 +00:00
copilot-swe-agent[bot]
578799464e Initial plan 2025-09-02 21:19:01 +00:00
455 changed files with 4788 additions and 9009 deletions

View File

@@ -1,29 +1,154 @@
# GitHub Copilot Instructions # Darkflame Universe Server Development Instructions
* c++20 standard, please use the latest features except NO modules. Darkflame Universe (DLU) is a LEGO Universe server emulator written in C++ with a multi-server architecture (AuthServer, ChatServer, MasterServer, WorldServer). Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
* use `.contains` for searching in associative containers
* use const as much as possible. If it can be const, it should be made const ## Working Effectively
* DO NOT USE const_cast EVER.
* use `cstdint` bitwidth types ALWAYS for integral types. ### Bootstrap, Build, and Test - REQUIRED STEPS
* NEVER use std::wstring. If wide strings are necessary, use std::u16string with conversion utilties in GeneralUtils.h. Execute these commands in order for ANY development work. NEVER CANCEL builds - they take time but work reliably:
* Functions are ALWAYS PascalCase.
* local variables are camelCase ```bash
* NEVER use snake case # 1. Install system dependencies (Ubuntu/Debian)
* indentation is TABS, not SPACES. sudo apt update && sudo apt install -y build-essential gcc zlib1g-dev libssl-dev openssl mariadb-server cmake
* TABS are 4 spaces by default
* Use trailing braces ALWAYS # 2. Initialize git submodules (CRITICAL - project won't build without this)
* global variables are prefixed with `g_` git submodule update --init --recursive
* if global variables or functions are needed, they should be located in an anonymous namespace
* Use `GeneralUtils::TryParse` for ANY parsing of strings to integrals. # 3. Build using the provided script
* Use brace initialization when possible. ./build.sh -j2
* ALWAYS default initialize variables. ```
* Pointers should be avoided unless necessary. Use references when the pointer has been checked and should not be null - **Build time: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds (12+ minutes).**
* headers should be as compact as possible. Do NOT include extra data that isnt needed. - **Uses CMake 3.25-3.31 (confirmed working with 3.31.6)**
* Remember to include logs (LOG macro uses printf style logging) while putting verbose logs under LOG_DEBUG. - **Requires g++11+ (confirmed working with g++ 13.3.0)**
* NEVER USE `RakNet::BitStream::ReadBit`
* NEVER assume pointers are good, always check if they are null. Once a pointer is checked and is known to be non-null, further accesses no longer need checking ### Alternative Build Methods
* Be wary of TOCTOU. Prevent all possible issues relating to TOCTOU. ```bash
* new memory allocations should never be used unless absolutely necessary. # Using CMake presets (CI-style)
* new for reconstruction of objects is allowed cmake --workflow --preset ci-ubuntu-22.04
* Prefer following the format of the file over correct formatting. Consistency over correctness.
* When using auto, ALWAYS put a * for pointers. # Manual CMake (for custom configurations)
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE="Release" ..
cmake --build . --config Release -j2
```
- **Same timing: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds.**
### Run Tests
```bash
cd build
ctest --output-on-failure
```
- **Test time: <4 seconds. Set timeout to 30+ seconds.**
- **91 tests run, all should pass**
- **Tests are built automatically when ENABLE_TESTING=1 in CMakeVariables.txt**
### Database Setup (for runtime testing)
```bash
# Start MariaDB
sudo systemctl start mysql
# Create test database and user
sudo mysql -e "CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass'; GRANT ALL ON *.* TO 'testuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; CREATE DATABASE testdarkflame;"
```
## Validation
### Build Validation
- **ALWAYS run the bootstrapping steps first** before making any code changes
- **ALWAYS build and test your changes** before considering them complete
- Build output should include all server binaries: AuthServer, ChatServer, MasterServer, WorldServer
- Build directory contains required files: `*.ini` configs, `navmeshes/`, `migrations/`, `vanity/`, `blocklist.dcf`, `libmariadbcpp.so`
### Runtime Validation
The servers can be started for basic validation:
```bash
cd build
./MasterServer
```
- **Server will start but complain about missing client files (this is expected)**
- **Database connections work with proper configuration in sharedconfig.ini**
- **For full server testing, LEGO Universe client files are required (not available in this repository)**
### Code Validation
**ALWAYS validate your changes by**:
1. Building successfully with no new compilation errors
2. Running the test suite and confirming all tests pass
3. Starting MasterServer to verify basic functionality
4. **Use .editorconfig** - code style uses tabs (width=4), Unix line endings, trailing whitespace removal
## Common Tasks
### Project Structure
```
/home/runner/work/DarkflameServer/DarkflameServer/
├── dAuthServer/ # Authentication server code
├── dChatServer/ # Chat server code
├── dMasterServer/ # Master server (main coordinator)
├── dWorldServer/ # World/game server code
├── dCommon/ # Shared common utilities
├── dDatabase/ # Database abstraction layer
├── dGame/ # Core game logic, components, behaviors
├── dScripts/ # Game scripts (NPCs, quests, etc.)
├── dNet/ # Network utilities
├── dPhysics/ # Physics integration
├── tests/ # Unit tests (GoogleTest)
├── migrations/ # Database schema migrations
├── thirdparty/ # External dependencies
├── build.sh # Main build script
├── CMakeVariables.txt # Build configuration variables
└── CMakePresets.json # CMake preset configurations
```
### Key Files to Know
- **CMakeVariables.txt**: Build configuration (testing enabled, MariaDB jobs, etc.)
- **build/sharedconfig.ini**: Database connection, client location, server settings
- **build/masterconfig.ini**: Master server port and startup configuration
- **CONTRIBUTING.md**: Code style guidelines and commit message format
- **docs/Commands.md**: Complete list of in-game server commands
### Build Configuration
Located in `CMakeVariables.txt`:
- `ENABLE_TESTING=1` - Unit tests enabled (keep enabled)
- `MARIADB_CONNECTOR_COMPILE_JOBS=1` - Parallel compilation jobs for MariaDB connector
- `CDCLIENT_CACHE_ALL=0` - Database caching strategy
### Common Commands Reference
```bash
# Build from clean state
rm -rf build && ./build.sh -j2
# Run specific test
cd build && ctest -R "TestName" --output-on-failure
# Check which servers were built
cd build && ls -la *Server
# View build configuration
cat CMakeVariables.txt
# Check git submodules status
git submodule status
```
### Important Notes
- **Client files are NOT included** - this is only the server emulator
- **Database can use SQLite or MariaDB** - SQLite recommended for development since it's lighter and doesn't require an external service
- **Multi-server architecture** requires all 4 servers to run a complete setup
- **Network ports**: Auth (1001), Chat (2005), Master (2000), World (3000+)
- **Development uses Debug builds**, production uses Release builds
- **GM level 0** = normal player, **GM level 8-9** = admin privileges
## Troubleshooting
- **"Asset bundle not found"**: Expected without LEGO Universe client files
- **"Submodule errors"**: Run `git submodule update --init --recursive`
- **"CMake version errors"**: Requires CMake 3.25-3.31
- **"MariaDB connection errors"**: Check database setup and sharedconfig.ini
- **"Permission denied on port"**: Run `sudo setcap 'cap_net_bind_service=+ep' AuthServer` for ports <1024
### CI Information
- **GitHub Actions** runs builds on Windows, Ubuntu, and macOS
- **Build matrix** tests multiple configurations via CMake presets
- **All tests must pass** for CI to succeed
- **Build artifacts** are automatically generated and uploaded
**Remember: This is a complex game server requiring LEGO Universe client files for full functionality, but the server has the capability to mock everything that's needed to test without the client since cdclient can be mocked and the database can be mocked as well.**

View File

@@ -1,14 +1,14 @@
name: Docker name: CI
on: on:
push: push:
branches: branches:
- main - "main"
tags: tags:
- "v*.*.*" - "v*.*.*"
pull_request: pull_request:
branches: branches:
- main - "main"
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
@@ -20,21 +20,15 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
attestations: write
id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Log in to the Container registry - name: Log in to the Container registry
if: github.event_name != 'pull_request' uses: docker/login-action@v3
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -42,32 +36,21 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: | tags: |
type=ref,event=pr type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=raw,value=canary,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
- name: Build and push Docker image - name: Build and push Docker image
id: push uses: docker/build-push-action@v5
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Sign Docker image
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View File

@@ -3,8 +3,6 @@ name: CI
on: on:
push: push:
branches: [ main ] branches: [ main ]
tags:
- "v*.*.*"
pull_request: pull_request:
branches: [ main ] branches: [ main ]
@@ -12,51 +10,38 @@ jobs:
build-and-test: build-and-test:
name: Build & Test (${{ matrix.os }}) name: Build & Test (${{ matrix.os }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
continue-on-error: ${{ github.event_name == 'pull_request' }} continue-on-error: true
strategy: strategy:
matrix: matrix:
include: os: [ windows-2022, ubuntu-22.04, macos-13 ]
- os: windows-2025
artifact: windows
debug_preset: windows-msvc-relwithdebinfo
- os: ubuntu-24.04
artifact: linux
debug_preset: linux-gnu-relwithdebinfo
- os: macos-15-intel
artifact: macos
debug_preset: macos-relwithdebinfo
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
with: with:
submodules: true submodules: true
- name: Add msbuild to PATH (Windows only) - name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2025' }} if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3 uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330
with: with:
vs-version: '[18,19)' vs-version: '[17,18)'
msbuild-architecture: x64 msbuild-architecture: x64
- name: Get CMake - name: Install libssl and switch to XCode 15.2 (Mac Only)
uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 if: ${{ matrix.os == 'macos-13' }}
with:
cmakeVersion: "latest"
- name: cmake
uses: lukka/run-cmake@5d55ea7949e25f69f0ecb516d8d572297e03a956 # v10.9
with:
workflowPreset: "${{ matrix.debug_preset }}"
- name: Extract Linux debug symbols
if: matrix.os == 'ubuntu-24.04'
run: | run: |
find build -type f -name '*Server' | while read bin; do brew install openssl@3
objcopy --only-keep-debug "$bin" "${bin}.debug" sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
objcopy --strip-debug --add-gnu-debuglink="${bin}.debug" "$bin" - name: Get CMake 3.x
done uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
- name: artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: build-${{matrix.artifact}} cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
- name: cmake
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38
with:
workflowPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: build-${{matrix.os}}
path: | path: |
build/*/*Server* build/*/*Server*
build/*/*.ini build/*/*.ini
@@ -67,30 +52,5 @@ jobs:
build/*/navmeshes/ build/*/navmeshes/
build/*/migrations/ build/*/migrations/
build/*/*.dcf build/*/*.dcf
!build/*/*.pdb
!build/*/d*/ !build/*/d*/
!build/*/*.dSYM/
!build/**/*.debug
- name: debug symbols (Windows)
if: matrix.os == 'windows-2025'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: debug-${{matrix.artifact}}
path: |
build/*/*.pdb
build/*/d*/
retention-days: 30
- name: debug symbols (Linux)
if: matrix.os == 'ubuntu-24.04'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: debug-${{matrix.artifact}}
path: build/**/*.debug
retention-days: 30
- name: debug symbols (macOS)
if: matrix.os == 'macos-15-intel'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: debug-${{matrix.artifact}}
path: build/**/*.dSYM/
retention-days: 30

View File

@@ -1,93 +0,0 @@
name: Canary
on:
workflow_run:
workflows: ["CI"]
branches: [main]
types: [completed]
permissions:
contents: write
actions: read
jobs:
canary-release:
name: Publish Canary Release
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha }}
- name: Get last release tag
id: last_tag
run: |
tag=$(git describe --tags --abbrev=0 --match "v*.*.*" 2>/dev/null || echo "none")
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: Generate changelog since last release tag
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
id: cliff
with:
config: cliff.toml
args: --unreleased --strip header
env:
OUTPUT: CHANGES.md
GITHUB_REPO: ${{ github.repository }}
- name: Prepend header to changelog
run: |
last="${{ steps.last_tag.outputs.tag }}"
sha="${{ github.event.workflow_run.head_sha }}"
short="${sha:0:7}"
if [ "$last" != "none" ]; then
header="Changes since **$last** ([full diff](https://github.com/${{ github.repository }}/compare/${last}...${sha}))\n\n"
else
header="Changes up to \`${short}\`\n\n"
fi
printf "%b" "$header" | cat - CHANGES.md > CHANGES.tmp && mv CHANGES.tmp CHANGES.md
- name: Download artifacts from CI run
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
with:
run_id: ${{ github.event.workflow_run.id }}
path: artifacts/
- name: Package artifacts
run: |
declare -A platform_map=(
["build-windows"]="darkflame-universe-windows"
["build-linux"]="darkflame-universe-linux"
["build-macos"]="darkflame-universe-macos"
)
cd artifacts
for dir in build-*/; do
name="${dir%/}"
out="${platform_map[$name]:-$name}"
zip -r "../${out}.zip" "$dir"
done
cd ..
ls -lh *.zip
- name: Delete existing canary release
run: gh release delete canary --yes --cleanup-tag 2>/dev/null || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create canary pre-release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
tag: canary
name: "Canary ${{ steps.last_tag.outputs.tag }}+${{ github.event.workflow_run.head_sha }}"
bodyFile: CHANGES.md
artifacts: "*.zip"
artifactContentType: application/zip
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: true
draft: false
allowUpdates: true
removeArtifacts: true
commit: ${{ github.event.workflow_run.head_sha }}

View File

@@ -1,38 +0,0 @@
name: PR Title Check
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
permissions:
pull-requests: write
statuses: write
jobs:
check-title:
name: Conventional Commit Title
runs-on: ubuntu-latest
steps:
- name: Check PR title follows Conventional Commits
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
perf
refactor
docs
chore
test
ci
revert
requireScope: false
subjectPattern: ^.+$
subjectPatternError: |
The PR title "{title}" must have a description after the type/scope prefix.
Example: "feat: add new login flow" or "fix(auth): handle null token"
wip: true
validateSingleCommit: false

View File

@@ -1,70 +0,0 @@
name: Release
on:
workflow_run:
workflows: ["CI"]
types: [completed]
permissions:
contents: write
actions: read
jobs:
release:
name: Create Release
# Only run when CI completed successfully on a tag push (not a PR branch named like a version)
if: |
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha }}
- name: Generate changelog
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
id: cliff
with:
config: cliff.toml
args: --latest --strip header
env:
OUTPUT: CHANGES.md
GITHUB_REPO: ${{ github.repository }}
- name: Download artifacts from CI run
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
with:
run_id: ${{ github.event.workflow_run.id }}
path: artifacts/
- name: Package artifacts
run: |
declare -A platform_map=(
["build-windows"]="darkflame-universe-windows"
["build-linux"]="darkflame-universe-linux"
["build-macos"]="darkflame-universe-macos"
)
cd artifacts
for dir in build-*/; do
name="${dir%/}"
out="${platform_map[$name]:-$name}"
zip -r "../${out}.zip" "$dir"
done
cd ..
ls -lh *.zip
- name: Create GitHub Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
tag: ${{ github.event.workflow_run.head_branch }}
name: ${{ github.event.workflow_run.head_branch }}
bodyFile: CHANGES.md
artifacts: "*.zip"
artifactContentType: application/zip
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
draft: false

2
.gitignore vendored
View File

@@ -126,5 +126,3 @@ docker-compose.override.yml
# CMake scripts # CMake scripts
!cmake/* !cmake/*
!cmake/toolchains/* !cmake/toolchains/*
.mcp.json
.claude/

View File

@@ -15,12 +15,10 @@ set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
set(FETCHCONTENT_QUIET FALSE) # GLM takes a long time to clone, this will at least show _something_ while its downloading
# Read variables from file # Read variables from file
FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables) FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables)
@@ -68,11 +66,7 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
# Disabled no-register # Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas. # Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
if(UNIX) if(UNIX)
if(APPLE) add_link_options("-Wl,-rpath,$ORIGIN/")
add_link_options("-Wl,-rpath,@loader_path/")
else()
add_link_options("-Wl,-rpath,$ORIGIN/")
endif()
add_compile_options("-fPIC") add_compile_options("-fPIC")
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0) add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
@@ -312,7 +306,7 @@ add_subdirectory(dServer)
add_subdirectory(dWeb) add_subdirectory(dWeb)
# Create a list of common libraries shared between all binaries # Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES glm::glm "dCommon" "dDatabase" "dNet" "raknet" "magic_enum") set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
# Add platform specific common libraries # Add platform specific common libraries
if(UNIX) if(UNIX)

View File

@@ -49,7 +49,7 @@
"inherits": "default", "inherits": "default",
"displayName": "[Multi] Windows (MSVC)", "displayName": "[Multi] Windows (MSVC)",
"description": "Set architecture to 64-bit (b/c RakNet)", "description": "Set architecture to 64-bit (b/c RakNet)",
"generator": "Visual Studio 18 2026", "generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build/msvc", "binaryDir": "${sourceDir}/build/msvc",
"architecture": { "architecture": {
"value": "x64" "value": "x64"
@@ -162,13 +162,6 @@
"rhs": "Darwin" "rhs": "Darwin"
}, },
"binaryDir": "${sourceDir}/build/macos" "binaryDir": "${sourceDir}/build/macos"
},
{
"name": "macos-relwithdebinfo",
"inherits": ["macos", "relwithdebinfo-config"],
"displayName": "[RelWithDebInfo] MacOS",
"description": "Create a RelWithDebInfo build for MacOS",
"binaryDir": "${sourceDir}/build/macos-relwithdebinfo"
} }
], ],
"buildPresets": [ "buildPresets": [
@@ -262,7 +255,7 @@
{ {
"name": "macos-relwithdebinfo", "name": "macos-relwithdebinfo",
"inherits": "default", "inherits": "default",
"configurePreset": "macos-relwithdebinfo", "configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS", "displayName": "[RelWithDebInfo] MacOS",
"description": "This preset is used to build in release mode with debug info on MacOS", "description": "This preset is used to build in release mode with debug info on MacOS",
"configuration": "RelWithDebInfo" "configuration": "RelWithDebInfo"
@@ -381,7 +374,7 @@
{ {
"name": "macos-relwithdebinfo", "name": "macos-relwithdebinfo",
"inherits": "default", "inherits": "default",
"configurePreset": "macos-relwithdebinfo", "configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS", "displayName": "[RelWithDebInfo] MacOS",
"description": "Runs all tests on a MacOS configuration", "description": "Runs all tests on a MacOS configuration",
"configuration": "RelWithDebInfo" "configuration": "RelWithDebInfo"
@@ -610,7 +603,7 @@
"steps": [ "steps": [
{ {
"type": "configure", "type": "configure",
"name": "macos-relwithdebinfo" "name": "macos"
}, },
{ {
"type": "build", "type": "build",
@@ -623,7 +616,7 @@
] ]
}, },
{ {
"name": "ci-macos-15-intel", "name": "ci-macos-13",
"displayName": "[Release] MacOS", "displayName": "[Release] MacOS",
"description": "CI workflow preset for MacOS", "description": "CI workflow preset for MacOS",
"steps": [ "steps": [

View File

@@ -16,9 +16,7 @@ RUN --mount=type=cache,target=/app/build,id=build-cache \
cd /app/build && \ cd /app/build && \
cmake .. && \ cmake .. && \
make -j$(nproc --ignore 1) && \ make -j$(nproc --ignore 1) && \
cp -r /app/build/* /tmp/persisted-build/ && \ cp -r /app/build/* /tmp/persisted-build/
mkdir -p /tmp/persisted-build/mariadbcpp && \
cp /app/build/thirdparty/mariadb-connector-cpp/src/mariadb_connector_cpp-build/libmariadbcpp.so /tmp/persisted-build/mariadbcpp/
FROM debian:12 as runtime FROM debian:12 as runtime

View File

@@ -1,35 +0,0 @@
[changelog]
header = ""
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/commit/{{ commit.id }}))\
{% if commit.github.username %} by [@{{ commit.github.username }}](https://github.com/{{ commit.github.username }}){% endif %}
{% endfor %}
{% endfor %}
"""
footer = ""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactoring" },
{ message = "^docs", group = "Documentation" },
{ message = "^chore", group = "Chores" },
{ message = "^test", group = "Testing" },
{ message = "^ci", group = "CI/CD" },
{ message = "^revert", group = "Reverts" },
]
filter_commits = false
tag_pattern = "v[0-9].*"
skip_tags = "canary"
ignore_tags = ""
topo_order = false
sort_commits = "oldest"

View File

@@ -6,8 +6,6 @@ FetchContent_Declare(
googletest googletest
GIT_REPOSITORY https://github.com/google/googletest.git GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1 GIT_TAG release-1.12.1
GIT_PROGRESS TRUE
GIT_SHALLOW 1
) )
# For Windows: Prevent overriding the parent project's compiler/linker settings # For Windows: Prevent overriding the parent project's compiler/linker settings

View File

@@ -10,15 +10,14 @@ if(WIN32 AND NOT MARIADB_BUILD_SOURCE)
file(MAKE_DIRECTORY "${MARIADB_MSI_DIR}") file(MAKE_DIRECTORY "${MARIADB_MSI_DIR}")
file(MAKE_DIRECTORY "${MARIADB_CONNECTOR_DIR}") file(MAKE_DIRECTORY "${MARIADB_CONNECTOR_DIR}")
# These values track the published Windows MSI packages used by the prebuilt path. # These values need to be updated whenever a new minor release replaces an old one
# Keep the Connector/C++ package version aligned with the checked out submodule tag when possible. # Go to https://mariadb.com/downloads/connectors/ to find the up-to-date URL parts
# Go to https://mariadb.com/downloads/connectors/ to find the up-to-date URL parts. set(MARIADB_CONNECTOR_C_VERSION "3.2.7")
set(MARIADB_CONNECTOR_C_VERSION "3.4.8") set(MARIADB_CONNECTOR_C_BUCKET "2319651")
set(MARIADB_CONNECTOR_C_BUCKET "4516894") set(MARIADB_CONNECTOR_C_MD5 "f8636d733f1d093af9d4f22f3239f885")
set(MARIADB_CONNECTOR_C_MD5 "50f6fc0c77b8d3bacbeac0126e179861") set(MARIADB_CONNECTOR_CPP_VERSION "1.0.2")
set(MARIADB_CONNECTOR_CPP_VERSION "1.1.7") set(MARIADB_CONNECTOR_CPP_BUCKET "2531525")
set(MARIADB_CONNECTOR_CPP_BUCKET "4464908") set(MARIADB_CONNECTOR_CPP_MD5 "3034bbd6ca00a0125345f9fd1a178401")
set(MARIADB_CONNECTOR_CPP_MD5 "08644a7ff084b5933325cadb904796e5")
set(MARIADB_CONNECTOR_C_MSI "mariadb-connector-c-${MARIADB_CONNECTOR_C_VERSION}-win64.msi") set(MARIADB_CONNECTOR_C_MSI "mariadb-connector-c-${MARIADB_CONNECTOR_C_VERSION}-win64.msi")
set(MARIADB_CONNECTOR_CPP_MSI "mariadb-connector-cpp-${MARIADB_CONNECTOR_CPP_VERSION}-win64.msi") set(MARIADB_CONNECTOR_CPP_MSI "mariadb-connector-cpp-${MARIADB_CONNECTOR_CPP_VERSION}-win64.msi")
@@ -80,39 +79,23 @@ else() # Build from source
-DWITH_EXTERNAL_ZLIB=ON -DWITH_EXTERNAL_ZLIB=ON
-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}
-DCMAKE_C_FLAGS=-w # disable zlib warnings -DCMAKE_C_FLAGS=-w # disable zlib warnings
-DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0\ -Wno-inconsistent-missing-override\ -include\ cstdint) -DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0)
else() else()
set(MARIADB_EXTRA_CMAKE_ARGS set(MARIADB_EXTRA_CMAKE_ARGS
-DCMAKE_C_FLAGS=-w # disable zlib warnings -DCMAKE_C_FLAGS=-w # disable zlib warnings
-DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0\ -include\ cstdint) -DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0)
endif() endif()
set(MARIADBCPP_BUILD_DIR "${PROJECT_BINARY_DIR}/thirdparty/mariadb-connector-cpp/src/mariadb_connector_cpp-build")
set(MARIADBCPP_INSTALL_DIR ${PROJECT_BINARY_DIR}/prefix) set(MARIADBCPP_INSTALL_DIR ${PROJECT_BINARY_DIR}/prefix)
set(MARIADBCPP_LIBRARY_DIR ${PROJECT_BINARY_DIR}/mariadbcpp)
set(MARIADBCPP_PLUGIN_DIR ${MARIADBCPP_LIBRARY_DIR}/plugin)
set(MARIADBCPP_SOURCE_DIR ${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp) set(MARIADBCPP_SOURCE_DIR ${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp)
set(MARIADB_INCLUDE_DIR "${MARIADBCPP_SOURCE_DIR}/include") set(MARIADB_INCLUDE_DIR "${MARIADBCPP_SOURCE_DIR}/include")
if(WIN32)
set(MARIADBCPP_LIBRARY_DIR ${PROJECT_BINARY_DIR}/mariadbcpp)
set(MARIADBCPP_PLUGIN_DIR ${MARIADBCPP_LIBRARY_DIR}/plugin)
set(MARIADB_INSTALL_COMMAND)
else()
set(MARIADBCPP_LIBRARY_DIR ${MARIADBCPP_BUILD_DIR})
set(MARIADBCPP_PLUGIN_DIR ${MARIADBCPP_BUILD_DIR}/libmariadb)
set(MARIADB_INSTALL_COMMAND INSTALL_COMMAND ${CMAKE_COMMAND} -E true)
endif()
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set(MARIADB_POLICY_VERSION_ARG -DCMAKE_POLICY_VERSION_MINIMUM=3.5)
endif()
ExternalProject_Add(mariadb_connector_cpp ExternalProject_Add(mariadb_connector_cpp
PREFIX "${PROJECT_BINARY_DIR}/thirdparty/mariadb-connector-cpp" PREFIX "${PROJECT_BINARY_DIR}/thirdparty/mariadb-connector-cpp"
SOURCE_DIR ${MARIADBCPP_SOURCE_DIR} SOURCE_DIR ${MARIADBCPP_SOURCE_DIR}
BINARY_DIR ${MARIADBCPP_BUILD_DIR}
INSTALL_DIR ${MARIADBCPP_INSTALL_DIR} INSTALL_DIR ${MARIADBCPP_INSTALL_DIR}
CMAKE_ARGS -Wno-dev CMAKE_ARGS -Wno-dev
${MARIADB_POLICY_VERSION_ARG}
-DWITH_UNIT_TESTS=OFF -DWITH_UNIT_TESTS=OFF
-DMARIADB_LINK_DYNAMIC=OFF -DMARIADB_LINK_DYNAMIC=OFF
-DCMAKE_BUILD_RPATH_USE_ORIGIN=${CMAKE_BUILD_RPATH_USE_ORIGIN} -DCMAKE_BUILD_RPATH_USE_ORIGIN=${CMAKE_BUILD_RPATH_USE_ORIGIN}
@@ -120,7 +103,6 @@ else() # Build from source
-DINSTALL_LIBDIR=${MARIADBCPP_LIBRARY_DIR} -DINSTALL_LIBDIR=${MARIADBCPP_LIBRARY_DIR}
-DINSTALL_PLUGINDIR=${MARIADBCPP_PLUGIN_DIR} -DINSTALL_PLUGINDIR=${MARIADBCPP_PLUGIN_DIR}
${MARIADB_EXTRA_CMAKE_ARGS} ${MARIADB_EXTRA_CMAKE_ARGS}
${MARIADB_INSTALL_COMMAND}
BUILD_ALWAYS true BUILD_ALWAYS true
) )

View File

@@ -52,7 +52,6 @@ int main(int argc, char** argv) {
//Create all the objects we need to run our service: //Create all the objects we need to run our service:
Server::SetupLogger("AuthServer"); Server::SetupLogger("AuthServer");
if (!Game::logger) return EXIT_FAILURE; if (!Game::logger) return EXIT_FAILURE;
Game::config->LogSettings();
LOG("Starting Auth server..."); LOG("Starting Auth server...");
LOG("Version: %s", PROJECT_VERSION); LOG("Version: %s", PROJECT_VERSION);

View File

@@ -1,4 +1,4 @@
set(DCHATFILTER_SOURCES "dChatFilter.cpp") set(DCHATFILTER_SOURCES "dChatFilter.cpp")
add_library(dChatFilter STATIC ${DCHATFILTER_SOURCES}) add_library(dChatFilter STATIC ${DCHATFILTER_SOURCES})
target_link_libraries(dChatFilter dDatabase glm::glm) target_link_libraries(dChatFilter dDatabase)

View File

@@ -14,6 +14,6 @@ add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}
add_library(dChatServer ${DCHATSERVER_SOURCES}) add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter") target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter glm::glm) target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb) target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)

View File

@@ -3,7 +3,6 @@
#include "MessageType/Chat.h" #include "MessageType/Chat.h"
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "Game.h" #include "Game.h"
#include "dConfig.h"
#include "Logger.h" #include "Logger.h"
#include "eObjectBits.h" #include "eObjectBits.h"
@@ -35,7 +34,7 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
if (!receiver.ignoredPlayers.empty()) { if (!receiver.ignoredPlayers.empty()) {
LOG_DEBUG("Player %llu already has an ignore list, but is requesting it again.", playerId); LOG_DEBUG("Player %llu already has an ignore list, but is requesting it again.", playerId);
} else { } else {
auto ignoreList = Database::Get()->GetIgnoreList(playerId); auto ignoreList = Database::Get()->GetIgnoreList(static_cast<uint32_t>(playerId));
if (ignoreList.empty()) { if (ignoreList.empty()) {
LOG_DEBUG("Player %llu has no ignores", playerId); LOG_DEBUG("Player %llu has no ignores", playerId);
return; return;
@@ -44,6 +43,7 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
for (auto& ignoredPlayer : ignoreList) { for (auto& ignoredPlayer : ignoreList) {
receiver.ignoredPlayers.emplace_back(ignoredPlayer.name, ignoredPlayer.id); receiver.ignoredPlayers.emplace_back(ignoredPlayer.name, ignoredPlayer.id);
GeneralUtils::SetBit(receiver.ignoredPlayers.back().playerId, eObjectBits::CHARACTER); GeneralUtils::SetBit(receiver.ignoredPlayers.back().playerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(receiver.ignoredPlayers.back().playerId, eObjectBits::PERSISTENT);
} }
} }
@@ -73,8 +73,8 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
return; return;
} }
const int32_t MAX_IGNORES = Game::config->GetValue("max_ignores", 32); constexpr int32_t MAX_IGNORES = 32;
if (receiver.ignoredPlayers.size() >= MAX_IGNORES) { if (receiver.ignoredPlayers.size() > MAX_IGNORES) {
LOG_DEBUG("Player %llu has too many ignores", playerId); LOG_DEBUG("Player %llu has too many ignores", playerId);
return; return;
} }
@@ -114,8 +114,9 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
} }
if (ignoredPlayerId != LWOOBJID_EMPTY) { if (ignoredPlayerId != LWOOBJID_EMPTY) {
Database::Get()->AddIgnore(playerId, ignoredPlayerId); Database::Get()->AddIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(ignoredPlayerId));
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::CHARACTER); GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::PERSISTENT);
receiver.ignoredPlayers.emplace_back(toIgnoreStr, ignoredPlayerId); receiver.ignoredPlayers.emplace_back(toIgnoreStr, ignoredPlayerId);
LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str()); LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str());
@@ -156,7 +157,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
return; return;
} }
Database::Get()->RemoveIgnore(playerId, toRemove->playerId); Database::Get()->RemoveIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(toRemove->playerId));
receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end()); receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end());
CBITSTREAM; CBITSTREAM;

View File

@@ -35,6 +35,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
FriendData fd; FriendData fd;
fd.isFTP = false; // not a thing in DLU fd.isFTP = false; // not a thing in DLU
fd.friendID = friendData.friendID; fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
@@ -160,7 +161,9 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// Set the bits // Set the bits
GeneralUtils::SetBit(queryPlayerID, eObjectBits::CHARACTER); GeneralUtils::SetBit(queryPlayerID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(queryPlayerID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(queryFriendID, eObjectBits::CHARACTER); GeneralUtils::SetBit(queryFriendID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(queryFriendID, eObjectBits::PERSISTENT);
// Since this player can either be the friend of someone else or be friends with someone else // Since this player can either be the friend of someone else or be friends with someone else
// their column in the database determines what bit gets set. When the value hits 3, they // their column in the database determines what bit gets set. When the value hits 3, they
@@ -315,6 +318,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
} }
// Convert friendID to LWOOBJID // Convert friendID to LWOOBJID
GeneralUtils::SetBit(friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER); GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER);
Database::Get()->RemoveFriend(playerID, friendID); Database::Get()->RemoveFriend(playerID, friendID);
@@ -435,11 +439,6 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
inStream.IgnoreBytes(4); inStream.IgnoreBytes(4);
inStream.Read(channel); inStream.Read(channel);
inStream.Read(size); inStream.Read(size);
if (size > MAX_MESSAGE_LENGTH) {
LOG("Received a probably spoofed chat message, ignoring msg");
return;
}
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
LUWString message(size); LUWString message(size);
@@ -484,11 +483,6 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!"); if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
inStream.Read(size); inStream.Read(size);
if (size > MAX_MESSAGE_LENGTH) {
LOG("Received a probably spoofed chat message, ignoring msg");
return;
}
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
inStream.Read(LUReceiverName); inStream.Read(LUReceiverName);

View File

@@ -59,7 +59,6 @@ int main(int argc, char** argv) {
//Create all the objects we need to run our service: //Create all the objects we need to run our service:
Server::SetupLogger("ChatServer"); Server::SetupLogger("ChatServer");
if (!Game::logger) return EXIT_FAILURE; if (!Game::logger) return EXIT_FAILURE;
Game::config->LogSettings();
//Read our config: //Read our config:
@@ -202,11 +201,8 @@ int main(int argc, char** argv) {
//Delete our objects here: //Delete our objects here:
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server; delete Game::server;
Game::server = nullptr;
delete Game::logger; delete Game::logger;
Game::logger = nullptr;
delete Game::config; delete Game::config;
Game::config = nullptr;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -176,7 +176,6 @@ LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
return toReturn; return toReturn;
} }
// TODO Make this a pointer again or do something to make it so you cant edit the LWOOBJID_EMPTY entry? it should be ignored in all cases anyways though...
PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) { PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) {
return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY]; return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY];
} }

View File

@@ -477,7 +477,7 @@ TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
} }
} }
newTeam->lootFlag = 0; newTeam->lootFlag = 1;
TeamStatusUpdate(newTeam); TeamStatusUpdate(newTeam);

View File

@@ -3,7 +3,6 @@
#include <stdexcept> #include <stdexcept>
#include "Amf3.h" #include "Amf3.h"
#include "StringifiedEnum.h"
/** /**
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf * AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
@@ -54,7 +53,7 @@ std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream)
case eAmf::VectorObject: case eAmf::VectorObject:
[[fallthrough]]; [[fallthrough]];
case eAmf::Dictionary: case eAmf::Dictionary:
throw std::invalid_argument(StringifiedEnum::ToString(marker).data()); throw marker;
default: default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker))); throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
} }
@@ -89,11 +88,6 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
// Right shift by 1 bit to get index if reference or size of next string if value // Right shift by 1 bit to get index if reference or size of next string if value
length = length >> 1; length = length >> 1;
if (isReference) { if (isReference) {
constexpr int32_t maxStringSize = 1024 * 1024;
if (length > maxStringSize) {
LOG("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
throw std::invalid_argument("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
}
std::string value(length, 0); std::string value(length, 0);
inStream.Read(&value[0], length); inStream.Read(&value[0], length);
// Empty strings are never sent by reference // Empty strings are never sent by reference
@@ -123,12 +117,6 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
if (key.size() == 0) break; if (key.size() == 0) break;
arrayValue->Insert(key, Read(inStream)); arrayValue->Insert(key, Read(inStream));
} }
constexpr int32_t maxArraySize = 10'000;
if (sizeOfDenseArray > maxArraySize) {
LOG("Someone sent 10,000 dense array entries, probably a bad packet.");
throw std::invalid_argument("Someone sent 10,000 dense array entries, probably a bad packet.");
}
// Finally read dense portion // Finally read dense portion
for (uint32_t i = 0; i < sizeOfDenseArray; i++) { for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
arrayValue->Insert(i, Read(inStream)); arrayValue->Insert(i, Read(inStream));

View File

@@ -374,21 +374,6 @@ public:
return value->Insert<AmfType>("value", std::make_unique<AmfType>()); return value->Insert<AmfType>("value", std::make_unique<AmfType>());
} }
AMFArrayValue& PushDebug(const NiPoint3& point) {
PushDebug<AMFDoubleValue>("X") = point.x;
PushDebug<AMFDoubleValue>("Y") = point.y;
PushDebug<AMFDoubleValue>("Z") = point.z;
return *this;
}
AMFArrayValue& PushDebug(const NiQuaternion& rot) {
PushDebug<AMFDoubleValue>("W") = rot.w;
PushDebug<AMFDoubleValue>("X") = rot.x;
PushDebug<AMFDoubleValue>("Y") = rot.y;
PushDebug<AMFDoubleValue>("Z") = rot.z;
return *this;
}
private: private:
/** /**
* The associative portion. These values are key'd with strings to an AMFValue. * The associative portion. These values are key'd with strings to an AMFValue.

View File

@@ -52,7 +52,8 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
if (actualUncompressedSize != -1) { if (actualUncompressedSize != -1) {
uint32_t previousSize = completeUncompressedModel.size(); uint32_t previousSize = completeUncompressedModel.size();
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()), actualUncompressedSize); completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()));
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
} else { } else {
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err); LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
break; break;

View File

@@ -49,24 +49,23 @@ if (UNIX)
elseif (WIN32) elseif (WIN32)
include(FetchContent) include(FetchContent)
# TODO Keep an eye on the zlib repository for an update to disable testing. Don't forget to update CMakePresets
FetchContent_Declare( FetchContent_Declare(
zlib zlib
URL https://github.com/madler/zlib/archive/refs/tags/v1.3.2.zip URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip
URL_HASH MD5=adbba6eef8960c3412818b2e241f46dc URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1
GIT_PROGRESS TRUE
GIT_SHALLOW 1
) )
# Disable warning about no project version. # Disable warning about no project version.
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
# Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future # Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE) set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
# Disable zlib tests
set(ZLIB_BUILD_TESTING OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(zlib) FetchContent_MakeAvailable(zlib)
set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
set_target_properties(zlib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIRS}")
add_library(ZLIB::ZLIB ALIAS zlib)
else () else ()
message( message(
FATAL_ERROR FATAL_ERROR
@@ -75,6 +74,5 @@ else ()
endif () endif ()
target_link_libraries(dCommon target_link_libraries(dCommon
PUBLIC glm::glm
PRIVATE ZLIB::ZLIB bcrypt tinyxml2 PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase) INTERFACE dDatabase)

View File

@@ -308,9 +308,8 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
for (const auto& t : std::filesystem::directory_iterator(folder)) { for (const auto& t : std::filesystem::directory_iterator(folder)) {
if (t.is_directory() || t.is_symlink()) continue; if (t.is_directory() || t.is_symlink()) continue;
auto filename = t.path().filename().string(); auto filename = t.path().filename().string();
// Ensure the file has a name in the format of xxxxxxxx_anything_goes_here.sql const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
const auto migrationNumber = TryParse<uint32_t>(GeneralUtils::SplitString(filename, '_').at(0)); filenames.emplace(index, std::move(filename));
if (migrationNumber.has_value()) filenames.emplace(migrationNumber.value(), std::move(filename));
} }
// Now sort the map by the oldest migration. // Now sort the map by the oldest migration.

View File

@@ -3,7 +3,7 @@
// C++ // C++
#include <charconv> #include <charconv>
#include <cstdint> #include <cstdint>
#include <cmath> #include <cmath>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
#include <optional> #include <optional>
@@ -19,9 +19,6 @@
#include "dPlatforms.h" #include "dPlatforms.h"
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
#include "DluAssert.h"
#include <glm/ext/vector_float3.hpp>
enum eInventoryType : uint32_t; enum eInventoryType : uint32_t;
enum class eObjectBits : size_t; enum class eObjectBits : size_t;
@@ -205,12 +202,6 @@ namespace GeneralUtils {
return isParsed ? static_cast<T>(result) : std::optional<T>{}; return isParsed ? static_cast<T>(result) : std::optional<T>{};
} }
// A version of TryParse that will return `errorVal` if `str` failed to parse.
template <Numeric T>
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
return TryParse<T>(str).value_or(errorVal);
}
template<typename T> template<typename T>
requires(!Numeric<T>) requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str); [[nodiscard]] std::optional<T> TryParse(std::string_view str);
@@ -243,12 +234,6 @@ namespace GeneralUtils {
return std::nullopt; return std::nullopt;
} }
// A version of TryParse that will return `errorVal` if `str` failed to parse.
template <std::floating_point T>
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
return TryParse<T>(str).value_or(errorVal);
}
#endif #endif
/** /**
@@ -259,7 +244,7 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/ */
template <typename T> template <typename T>
[[nodiscard]] std::optional<T> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) { [[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
const auto x = TryParse<float>(strX); const auto x = TryParse<float>(strX);
if (!x) return std::nullopt; if (!x) return std::nullopt;
@@ -267,12 +252,7 @@ namespace GeneralUtils {
if (!y) return std::nullopt; if (!y) return std::nullopt;
const auto z = TryParse<float>(strZ); const auto z = TryParse<float>(strZ);
return z ? std::make_optional<T>(x.value(), y.value(), z.value()) : std::nullopt; return z ? std::make_optional<NiPoint3>(x.value(), y.value(), z.value()) : std::nullopt;
}
// Alternative overload of TryParse with a default value
[[nodiscard]] inline NiPoint3 TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ, const NiPoint3 errorVal) {
return TryParse<NiPoint3>(strX, strY, strZ).value_or(errorVal);
} }
/** /**
@@ -281,13 +261,8 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/ */
template <typename T> template <typename T>
[[nodiscard]] std::optional<T> TryParse(const std::span<const std::string> str) { [[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) {
return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt; return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt;
}
// Alternative overload of TryParse with a default value
[[nodiscard]] inline NiPoint3 TryParse(const std::span<const std::string> str, const NiPoint3 errorVal) {
return TryParse<NiPoint3>(str).value_or(errorVal);
} }
template <typename T> template <typename T>
@@ -328,7 +303,7 @@ namespace GeneralUtils {
template<typename Container> template<typename Container>
inline Container::value_type GetRandomElement(const Container& container) { inline Container::value_type GetRandomElement(const Container& container) {
DluAssert(!container.empty()); DluAssert(!container.empty());
return container[GenerateRandomNumber<typename Container::size_type>(0, container.size() - 1)]; return container[GenerateRandomNumber<typename Container::value_type>(0, container.size() - 1)];
} }
/** /**

View File

@@ -10,151 +10,163 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
using LDFKey = std::string_view;
using LDFTypeAndValue = std::string_view;
using LDFType = std::string_view;
using LDFValue = std::string_view;
//! Returns a pointer to a LDFData value based on string format //! Returns a pointer to a LDFData value based on string format
std::unique_ptr<LDFBaseData> LDFBaseData::DataFromString(const std::string_view& format) { LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
std::unique_ptr<LDFBaseData> toReturn;
// A valid LDF must be at least 3 characters long (=0:) is the shortest valid LDF (empty UTF-16 key with no initial value) // A valid LDF must be at least 3 characters long (=0:) is the shortest valid LDF (empty UTF-16 key with no initial value)
if (!format.empty() && format.length() > 2) { if (format.empty() || format.length() <= 2) return nullptr;
auto equalsPosition = format.find('='); auto equalsPosition = format.find('=');
// You can have an empty key, just make sure the type and value might exist // You can have an empty key, just make sure the type and value might exist
if (equalsPosition != std::string::npos && equalsPosition != (format.size() - 1)) { if (equalsPosition == std::string::npos || equalsPosition == (format.size() - 1)) return nullptr;
const std::string_view keyValue = format.substr(0, equalsPosition); std::pair<LDFKey, LDFTypeAndValue> keyValue;
const std::string_view typeAndValue = format.substr(equalsPosition + 1, format.size()); keyValue.first = format.substr(0, equalsPosition);
keyValue.second = format.substr(equalsPosition + 1, format.size());
const auto key = GeneralUtils::ASCIIToUTF16(keyValue); std::u16string key = GeneralUtils::ASCIIToUTF16(keyValue.first);
const auto colonPosition = typeAndValue.find(':'); auto colonPosition = keyValue.second.find(':');
// If : is the first thing after an =, then this is an invalid LDF since // If : is the first thing after an =, then this is an invalid LDF since
// we dont have a type to use. // we dont have a type to use.
if (colonPosition != std::string::npos && colonPosition != 0) { if (colonPosition == std::string::npos || colonPosition == 0) return nullptr;
const std::string_view ldfType = typeAndValue.substr(0, colonPosition);
const std::string_view ldfValue = typeAndValue.substr(colonPosition + 1, typeAndValue.size());
// Only allow empty values for string values. std::pair<LDFType, LDFValue> ldfTypeAndValue;
if (!ldfValue.empty() || (ldfType == "0" /* UTF-16 */ || ldfType == "13" /* UTF-8 */)) { ldfTypeAndValue.first = keyValue.second.substr(0, colonPosition);
const eLDFType type = GeneralUtils::TryParse<eLDFType>(ldfType, LDF_TYPE_UNKNOWN); ldfTypeAndValue.second = keyValue.second.substr(colonPosition + 1, keyValue.second.size());
switch (type) {
case LDF_TYPE_UTF_16: {
std::u16string data = GeneralUtils::UTF8ToUTF16(ldfValue);
toReturn.reset(new LDFData<std::u16string>(key, data));
break;
}
case LDF_TYPE_S32: { // Only allow empty values for string values.
const auto data = GeneralUtils::TryParse<int32_t>(ldfValue); if (ldfTypeAndValue.second.size() == 0 && !(ldfTypeAndValue.first == "0" || ldfTypeAndValue.first == "13")) return nullptr;
if (data) {
toReturn.reset(new LDFData<int32_t>(key, data.value()));
} else {
LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfValue.data(), format.data());
}
break; eLDFType type;
} char* storage;
try {
case LDF_TYPE_FLOAT: { type = static_cast<eLDFType>(strtol(ldfTypeAndValue.first.data(), &storage, 10));
const auto data = GeneralUtils::TryParse<float>(ldfValue); } catch (std::exception) {
if (data) { LOG("Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data());
toReturn.reset(new LDFData<float>(key, data.value())); return nullptr;
} else {
LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfValue.data(), format.data());
}
break;
}
case LDF_TYPE_DOUBLE: {
const auto data = GeneralUtils::TryParse<double>(ldfValue);
if (data) {
toReturn.reset(new LDFData<double>(key, data.value()));
} else {
LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfValue.data(), format.data());
}
break;
}
case LDF_TYPE_U32:
{
uint32_t data;
bool parsed = true;
// Have to do this really weird parsing to allow for copy ellision
if (ldfValue == "true") {
data = 1;
} else if (ldfValue == "false") {
data = 0;
} else {
const auto dataOptional = GeneralUtils::TryParse<uint32_t>(ldfValue);
if (!dataOptional) {
LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfValue.data(), format.data());
parsed = false;
} else {
data = dataOptional.value();
}
}
if (parsed) toReturn.reset(new LDFData<uint32_t>(key, data));
break;
}
case LDF_TYPE_BOOLEAN: {
bool data;
bool parsed = true;
// Have to do this really weird parsing to allow for copy ellision
if (ldfValue == "true") {
data = true;
} else if (ldfValue == "false") {
data = false;
} else {
const auto dataOptional = GeneralUtils::TryParse<bool>(ldfValue);
if (!dataOptional) {
LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfValue.data(), format.data());
parsed = false;
} else {
data = dataOptional.value();
}
}
if (parsed) toReturn.reset(new LDFData<bool>(key, data));
break;
}
case LDF_TYPE_U64: {
const auto data = GeneralUtils::TryParse<uint64_t>(ldfValue);
if (data) {
toReturn.reset(new LDFData<uint64_t>(key, data.value()));
} else {
LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfValue.data(), format.data());
}
break;
}
case LDF_TYPE_OBJID: {
const auto data = GeneralUtils::TryParse<LWOOBJID>(ldfValue);
if (data) {
toReturn.reset(new LDFData<LWOOBJID>(key, data.value()));
} else {
LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfValue.data(), format.data());
}
break;
}
case LDF_TYPE_UTF_8: {
toReturn.reset(new LDFData<std::string>(key, ldfValue.data()));
break;
}
case LDF_TYPE_UNKNOWN:
[[fallthrough]];
default: {
LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfValue.data(), format.data());
break;
}
}
}
}
}
} }
return toReturn; LDFBaseData* returnValue = nullptr;
switch (type) {
case LDF_TYPE_UTF_16: {
std::u16string data = GeneralUtils::UTF8ToUTF16(ldfTypeAndValue.second);
returnValue = new LDFData<std::u16string>(key, data);
break;
}
case LDF_TYPE_S32: {
const auto data = GeneralUtils::TryParse<int32_t>(ldfTypeAndValue.second);
if (!data) {
LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<int32_t>(key, data.value());
break;
}
case LDF_TYPE_FLOAT: {
const auto data = GeneralUtils::TryParse<float>(ldfTypeAndValue.second);
if (!data) {
LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<float>(key, data.value());
break;
}
case LDF_TYPE_DOUBLE: {
const auto data = GeneralUtils::TryParse<double>(ldfTypeAndValue.second);
if (!data) {
LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<double>(key, data.value());
break;
}
case LDF_TYPE_U32:
{
uint32_t data;
if (ldfTypeAndValue.second == "true") {
data = 1;
} else if (ldfTypeAndValue.second == "false") {
data = 0;
} else {
const auto dataOptional = GeneralUtils::TryParse<uint32_t>(ldfTypeAndValue.second);
if (!dataOptional) {
LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
data = dataOptional.value();
}
returnValue = new LDFData<uint32_t>(key, data);
break;
}
case LDF_TYPE_BOOLEAN: {
bool data;
if (ldfTypeAndValue.second == "true") {
data = true;
} else if (ldfTypeAndValue.second == "false") {
data = false;
} else {
const auto dataOptional = GeneralUtils::TryParse<bool>(ldfTypeAndValue.second);
if (!dataOptional) {
LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
data = dataOptional.value();
}
returnValue = new LDFData<bool>(key, data);
break;
}
case LDF_TYPE_U64: {
const auto data = GeneralUtils::TryParse<uint64_t>(ldfTypeAndValue.second);
if (!data) {
LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<uint64_t>(key, data.value());
break;
}
case LDF_TYPE_OBJID: {
const auto data = GeneralUtils::TryParse<LWOOBJID>(ldfTypeAndValue.second);
if (!data) {
LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<LWOOBJID>(key, data.value());
break;
}
case LDF_TYPE_UTF_8: {
std::string data = ldfTypeAndValue.second.data();
returnValue = new LDFData<std::string>(key, data);
break;
}
case LDF_TYPE_UNKNOWN: {
LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
break;
}
default: {
LOG("Warning: Attempted to process invalid LDF type (%d) from string (%s)", type, format.data());
break;
}
}
return returnValue;
} }

View File

@@ -1,12 +1,11 @@
#ifndef LDFFORMAT_H #ifndef __LDFFORMAT__H__
#define LDFFORMAT_H #define __LDFFORMAT__H__
// Custom Classes // Custom Classes
#include "dCommonVars.h" #include "dCommonVars.h"
#include "GeneralUtils.h" #include "GeneralUtils.h"
// C++ // C++
#include <map>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <sstream> #include <sstream>
@@ -47,17 +46,17 @@ public:
virtual std::string GetValueAsString() const = 0; virtual std::string GetValueAsString() const = 0;
virtual std::unique_ptr<LDFBaseData> Copy() const = 0; virtual LDFBaseData* Copy() const = 0;
/** /**
* Given an input string, return the data as a LDF key. * Given an input string, return the data as a LDF key.
*/ */
static std::unique_ptr<LDFBaseData> DataFromString(const std::string_view& format); static LDFBaseData* DataFromString(const std::string_view& format);
}; };
template<typename T> template<typename T>
class LDFData : public LDFBaseData { class LDFData: public LDFBaseData {
private: private:
std::u16string key; std::u16string key;
T value; T value;
@@ -165,8 +164,8 @@ public:
return this->GetValueString(); return this->GetValueString();
} }
std::unique_ptr<LDFBaseData> Copy() const override { LDFBaseData* Copy() const override {
return std::make_unique<LDFData<T>>(key, value); return new LDFData<T>(key, value);
} }
inline static const T Default = {}; inline static const T Default = {};
@@ -227,89 +226,4 @@ template<> inline std::string LDFData<LWOOBJID>::GetValueString() const { return
template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; } template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; }
struct LwoNameValue { #endif //!__LDFFORMAT__H__
using LDFPtr = std::unique_ptr<LDFBaseData>;
using ValueType = std::map<std::u16string, LDFPtr>;
LwoNameValue& operator=(const LwoNameValue& other) {
this->values = other.Copy();
return *this;
}
template<typename T>
void Insert(const std::u16string& key, const T& value) {
this->values.insert_or_assign(key, std::unique_ptr(std::make_unique<LDFData<T>>(key, value)));
}
void Insert(const std::u16string& key, const char* value) {
this->Insert<std::string>(key, value);
}
void Insert(const std::u16string& key, const char16_t* value) {
this->Insert<std::u16string>(key, value);
}
template<typename T>
void Insert(const std::string& key, const T& value) {
this->Insert<T>(GeneralUtils::UTF8ToUTF16(key), value);
}
void Insert(const std::string& key, const char* value) {
this->Insert<std::string>(GeneralUtils::UTF8ToUTF16(key), value);
}
void Insert(const std::string& key, const char16_t* value) {
this->Insert<std::u16string>(GeneralUtils::UTF8ToUTF16(key), value);
}
const LDFPtr& ParseInsert(const std::string& data) {
LDFPtr toInsert(LDFBaseData::DataFromString(data));
return toInsert ?
this->values.insert_or_assign(toInsert->GetKey(), std::move(toInsert)).first->second :
this->values.insert_or_assign(u"FAILED_TO_PARSE_" + GeneralUtils::UTF8ToUTF16(data), std::make_unique<LDFData<std::string>>("", "")).first->second;
}
const LDFPtr& ParseInsert(const std::u16string& data) {
return this->ParseInsert(GeneralUtils::UTF16ToWTF8(data));
}
ValueType::const_iterator begin() const {
return this->values.cbegin();
}
ValueType::const_iterator end() const {
return this->values.cend();
}
void Erase(const std::u16string& key) {
this->values.erase(key);
}
void Erase(const std::string& key) {
this->Erase(GeneralUtils::ASCIIToUTF16(key));
}
ValueType::iterator find(const ValueType::key_type& key) {
return this->values.find(key);
}
ValueType::const_iterator find(const ValueType::key_type& key) const {
return this->values.find(key);
}
LwoNameValue() = default;
LwoNameValue(const LwoNameValue& other) {
this->values = other.Copy();
}
ValueType values;
private:
ValueType Copy() const {
ValueType copy;
for (const auto& [key, value] : this->values) copy.insert_or_assign(key, value->Copy());
return copy;
}
};
#endif //!LDFFORMAT_H

View File

@@ -96,17 +96,3 @@ bool Logger::GetLogToConsole() const {
} }
return toReturn; return toReturn;
} }
FuncEntry::FuncEntry(const char* funcName, const char* fileName, const uint32_t line) {
m_FuncName = funcName;
if (!m_FuncName) m_FuncName = "Unknown";
m_Line = line;
m_FileName = fileName;
LOG("--> %s::%s:%i", m_FileName, m_FuncName, m_Line);
}
FuncEntry::~FuncEntry() {
if (!m_FuncName || !m_FileName) return;
LOG("<-- %s::%s:%i", m_FileName, m_FuncName, m_Line);
}

View File

@@ -32,19 +32,6 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(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) #define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0)
// Place this right at the start of a function. Will log a message when called and then once you leave the function.
#define LOG_ENTRY auto str_ = GetFileNameFromAbsolutePath(__FILE__); FuncEntry funcEntry_(__FUNCTION__, str_, __LINE__)
class FuncEntry {
public:
FuncEntry(const char* funcName, const char* fileName, const uint32_t line);
~FuncEntry();
private:
const char* m_FuncName = nullptr;
const char* m_FileName = nullptr;
uint32_t m_Line = 0;
};
// Writer class for writing data to files. // Writer class for writing data to files.
class Writer { class Writer {
public: public:

View File

@@ -5,43 +5,13 @@
#include "TinyXmlUtils.h" #include "TinyXmlUtils.h"
#include <ranges> #include <ranges>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <sstream>
namespace {
// The base LXFML xml file to use when creating new models.
std::string g_base = R"(<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<LXFML versionMajor="5" versionMinor="0">
<Meta>
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
<Brand name="LEGOUniverse"/>
<BrickSet version="457"/>
</Meta>
<Bricks>
</Bricks>
<RigidSystems>
</RigidSystems>
<GroupSystems>
<GroupSystem>
</GroupSystem>
</GroupSystems>
</LXFML>)";
}
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) { Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
Result toReturn; Result toReturn;
// Handle empty or invalid input
if (data.empty()) {
return toReturn;
}
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
// Use length-based parsing to avoid expensive string copy const auto err = doc.Parse(data.data());
const auto err = doc.Parse(data.data(), data.size());
if (err != tinyxml2::XML_SUCCESS) { if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn; return toReturn;
} }
@@ -50,6 +20,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
auto lxfml = reader["LXFML"]; auto lxfml = reader["LXFML"];
if (!lxfml) { if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn; return toReturn;
} }
@@ -78,19 +49,16 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
// Calculate the lowest and highest points on the entire model // Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) { for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ','); auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) continue; if (split.size() < 12) {
LOG("Not enough in the split?");
auto xOpt = GeneralUtils::TryParse<float>(split[9]); continue;
auto yOpt = GeneralUtils::TryParse<float>(split[10]); }
auto zOpt = GeneralUtils::TryParse<float>(split[11]);
auto x = GeneralUtils::TryParse<float>(split[9]).value();
if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) continue; auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
auto x = xOpt.value(); if (x < lowest.x) lowest.x = x;
auto y = yOpt.value(); if (y < lowest.y) lowest.y = y;
auto z = zOpt.value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z; if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x; if (highest.x < x) highest.x = x;
@@ -119,19 +87,13 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
for (auto& transformation : transformations | std::views::values) { for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ','); auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) { if (split.size() < 12) {
LOG("Not enough in the split?");
continue; continue;
} }
auto xOpt = GeneralUtils::TryParse<float>(split[9]); auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x + curPosition.x;
auto yOpt = GeneralUtils::TryParse<float>(split[10]); auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y + curPosition.y;
auto zOpt = GeneralUtils::TryParse<float>(split[11]); auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z + curPosition.z;
if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) {
continue;
}
auto x = xOpt.value() - newRootPos.x + curPosition.x;
auto y = yOpt.value() - newRootPos.y + curPosition.y;
auto z = zOpt.value() - newRootPos.z + curPosition.z;
std::stringstream stream; std::stringstream stream;
for (int i = 0; i < 9; i++) { for (int i = 0; i < 9; i++) {
stream << split[i]; stream << split[i];
@@ -166,345 +128,3 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
toReturn.center = newRootPos; toReturn.center = newRootPos;
return toReturn; return toReturn;
} }
// Deep-clone an XMLElement (attributes, text, and child elements) into a target document
// with maximum depth protection to prevent infinite loops
static tinyxml2::XMLElement* CloneElementDeep(const tinyxml2::XMLElement* src, tinyxml2::XMLDocument& dstDoc, int maxDepth = 100) {
if (!src || maxDepth <= 0) return nullptr;
auto* dst = dstDoc.NewElement(src->Name());
// copy attributes
for (const tinyxml2::XMLAttribute* attr = src->FirstAttribute(); attr; attr = attr->Next()) {
dst->SetAttribute(attr->Name(), attr->Value());
}
// copy children (elements and text)
for (const tinyxml2::XMLNode* child = src->FirstChild(); child; child = child->NextSibling()) {
if (const tinyxml2::XMLElement* childElem = child->ToElement()) {
// Recursively clone child elements with decremented depth
auto* clonedChild = CloneElementDeep(childElem, dstDoc, maxDepth - 1);
if (clonedChild) dst->InsertEndChild(clonedChild);
} else if (const tinyxml2::XMLText* txt = child->ToText()) {
auto* n = dstDoc.NewText(txt->Value());
dst->InsertEndChild(n);
} else if (const tinyxml2::XMLComment* c = child->ToComment()) {
auto* n = dstDoc.NewComment(c->Value());
dst->InsertEndChild(n);
}
}
return dst;
}
std::vector<Lxfml::Result> Lxfml::Split(const std::string_view data, const NiPoint3& curPosition) {
std::vector<Result> results;
// Handle empty or invalid input
if (data.empty()) {
return results;
}
// Prevent processing extremely large inputs that could cause hangs
if (data.size() > 10000000) { // 10MB limit
return results;
}
tinyxml2::XMLDocument doc;
// Use length-based parsing to avoid expensive string copy
const auto err = doc.Parse(data.data(), data.size());
if (err != tinyxml2::XML_SUCCESS) {
return results;
}
auto* lxfml = doc.FirstChildElement("LXFML");
if (!lxfml) {
return results;
}
// Build maps: partRef -> Part element, partRef -> Brick element, boneRef -> partRef, brickRef -> Brick element
std::unordered_map<std::string, tinyxml2::XMLElement*> partRefToPart;
std::unordered_map<std::string, tinyxml2::XMLElement*> partRefToBrick;
std::unordered_map<std::string, std::string> boneRefToPartRef;
std::unordered_map<std::string, tinyxml2::XMLElement*> brickByRef;
auto* bricksParent = lxfml->FirstChildElement("Bricks");
if (bricksParent) {
for (auto* brick = bricksParent->FirstChildElement("Brick"); brick; brick = brick->NextSiblingElement("Brick")) {
const char* brickRef = brick->Attribute("refID");
if (brickRef) brickByRef.emplace(std::string(brickRef), brick);
for (auto* part = brick->FirstChildElement("Part"); part; part = part->NextSiblingElement("Part")) {
const char* partRef = part->Attribute("refID");
if (partRef) {
partRefToPart.emplace(std::string(partRef), part);
partRefToBrick.emplace(std::string(partRef), brick);
}
auto* bone = part->FirstChildElement("Bone");
if (bone) {
const char* boneRef = bone->Attribute("refID");
if (boneRef) boneRefToPartRef.emplace(std::string(boneRef), partRef ? std::string(partRef) : std::string());
}
}
}
}
// Collect RigidSystem elements
std::vector<tinyxml2::XMLElement*> rigidSystems;
auto* rigidSystemsParent = lxfml->FirstChildElement("RigidSystems");
if (rigidSystemsParent) {
for (auto* rs = rigidSystemsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) {
rigidSystems.push_back(rs);
}
}
// Collect top-level groups (immediate children of GroupSystem)
std::vector<tinyxml2::XMLElement*> groupRoots;
auto* groupSystemsParent = lxfml->FirstChildElement("GroupSystems");
if (groupSystemsParent) {
for (auto* gs = groupSystemsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) {
for (auto* group = gs->FirstChildElement("Group"); group; group = group->NextSiblingElement("Group")) {
groupRoots.push_back(group);
}
}
}
// Track used bricks and rigidsystems
std::unordered_set<std::string> usedBrickRefs;
std::unordered_set<tinyxml2::XMLElement*> usedRigidSystems;
// Track used groups to avoid processing them twice
std::unordered_set<tinyxml2::XMLElement*> usedGroups;
// Helper to create output document from sets of brick refs and rigidsystem pointers
auto makeOutput = [&](const std::unordered_set<std::string>& bricksToInclude, const std::vector<tinyxml2::XMLElement*>& rigidSystemsToInclude, const std::vector<tinyxml2::XMLElement*>& groupsToInclude = {}) {
tinyxml2::XMLDocument outDoc;
outDoc.Parse(g_base.c_str());
auto* outRoot = outDoc.FirstChildElement("LXFML");
auto* outBricks = outRoot->FirstChildElement("Bricks");
auto* outRigidSystems = outRoot->FirstChildElement("RigidSystems");
auto* outGroupSystems = outRoot->FirstChildElement("GroupSystems");
// clone and insert bricks
for (const auto& bref : bricksToInclude) {
auto it = brickByRef.find(bref);
if (it == brickByRef.end()) continue;
tinyxml2::XMLElement* cloned = CloneElementDeep(it->second, outDoc);
if (cloned) outBricks->InsertEndChild(cloned);
}
// clone and insert rigidsystems
for (auto* rsPtr : rigidSystemsToInclude) {
tinyxml2::XMLElement* cloned = CloneElementDeep(rsPtr, outDoc);
if (cloned) outRigidSystems->InsertEndChild(cloned);
}
// clone and insert group(s) if requested
if (outGroupSystems && !groupsToInclude.empty()) {
// clear default children
while (outGroupSystems->FirstChild()) outGroupSystems->DeleteChild(outGroupSystems->FirstChild());
// create a GroupSystem element and append requested groups
auto* newGS = outDoc.NewElement("GroupSystem");
for (auto* gptr : groupsToInclude) {
tinyxml2::XMLElement* clonedG = CloneElementDeep(gptr, outDoc);
if (clonedG) newGS->InsertEndChild(clonedG);
}
outGroupSystems->InsertEndChild(newGS);
}
// Print to string
tinyxml2::XMLPrinter printer;
outDoc.Print(&printer);
// Normalize position and compute center using existing helper
std::string xmlString = printer.CStr();
if (xmlString.size() > 5000000) { // 5MB limit for normalization
Result emptyResult;
emptyResult.lxfml = xmlString;
return emptyResult;
}
auto normalized = NormalizePosition(xmlString, curPosition);
return normalized;
};
// 1) Process groups (each top-level Group becomes one output; nested groups are included)
for (auto* groupRoot : groupRoots) {
// Skip if this group was already processed as part of another group
if (usedGroups.find(groupRoot) != usedGroups.end()) continue;
// Helper to collect all partRefs in a group's subtree
std::function<void(const tinyxml2::XMLElement*, std::unordered_set<std::string>&)> collectParts = [&](const tinyxml2::XMLElement* g, std::unordered_set<std::string>& partRefs) {
if (!g) return;
const char* partAttr = g->Attribute("partRefs");
if (partAttr) {
for (auto& tok : GeneralUtils::SplitString(partAttr, ',')) partRefs.insert(tok);
}
for (auto* child = g->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectParts(child, partRefs);
};
// Collect all groups that need to be merged into this output
std::vector<tinyxml2::XMLElement*> groupsToInclude{ groupRoot };
usedGroups.insert(groupRoot);
// Build initial sets of bricks and boneRefs from the starting group
std::unordered_set<std::string> partRefs;
collectParts(groupRoot, partRefs);
std::unordered_set<std::string> bricksIncluded;
std::unordered_set<std::string> boneRefsIncluded;
for (const auto& pref : partRefs) {
auto pit = partRefToBrick.find(pref);
if (pit != partRefToBrick.end()) {
const char* bref = pit->second->Attribute("refID");
if (bref) bricksIncluded.insert(std::string(bref));
}
auto partIt = partRefToPart.find(pref);
if (partIt != partRefToPart.end()) {
auto* bone = partIt->second->FirstChildElement("Bone");
if (bone) {
const char* bref = bone->Attribute("refID");
if (bref) boneRefsIncluded.insert(std::string(bref));
}
}
}
// Iteratively include any RigidSystems that reference any boneRefsIncluded
// and check if those rigid systems' bricks span other groups
bool changed = true;
std::vector<tinyxml2::XMLElement*> rigidSystemsToInclude;
int maxIterations = 1000; // Safety limit to prevent infinite loops
int iteration = 0;
while (changed && iteration < maxIterations) {
changed = false;
iteration++;
// First, expand rigid systems based on current boneRefsIncluded
for (auto* rs : rigidSystems) {
if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue;
// parse boneRefs of this rigid system (from its <Rigid> children)
bool intersects = false;
std::vector<std::string> rsBoneRefs;
for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) {
const char* battr = rigid->Attribute("boneRefs");
if (!battr) continue;
for (auto& tok : GeneralUtils::SplitString(battr, ',')) {
rsBoneRefs.push_back(tok);
if (boneRefsIncluded.find(tok) != boneRefsIncluded.end()) intersects = true;
}
}
if (!intersects) continue;
// include this rigid system and all boneRefs it references
usedRigidSystems.insert(rs);
rigidSystemsToInclude.push_back(rs);
for (const auto& br : rsBoneRefs) {
boneRefsIncluded.insert(br);
auto bpIt = boneRefToPartRef.find(br);
if (bpIt != boneRefToPartRef.end()) {
auto partRef = bpIt->second;
auto pbIt = partRefToBrick.find(partRef);
if (pbIt != partRefToBrick.end()) {
const char* bref = pbIt->second->Attribute("refID");
if (bref && bricksIncluded.insert(std::string(bref)).second) changed = true;
}
}
}
}
// Second, check if the newly included bricks span any other groups
// If so, merge those groups into the current output
for (auto* otherGroup : groupRoots) {
if (usedGroups.find(otherGroup) != usedGroups.end()) continue;
// Collect partRefs from this other group
std::unordered_set<std::string> otherPartRefs;
collectParts(otherGroup, otherPartRefs);
// Check if any of these partRefs correspond to bricks we've already included
bool spansOtherGroup = false;
for (const auto& pref : otherPartRefs) {
auto pit = partRefToBrick.find(pref);
if (pit != partRefToBrick.end()) {
const char* bref = pit->second->Attribute("refID");
if (bref && bricksIncluded.find(std::string(bref)) != bricksIncluded.end()) {
spansOtherGroup = true;
break;
}
}
}
if (spansOtherGroup) {
// Merge this group into the current output
usedGroups.insert(otherGroup);
groupsToInclude.push_back(otherGroup);
changed = true;
// Add all partRefs, boneRefs, and bricks from this group
for (const auto& pref : otherPartRefs) {
auto pit = partRefToBrick.find(pref);
if (pit != partRefToBrick.end()) {
const char* bref = pit->second->Attribute("refID");
if (bref) bricksIncluded.insert(std::string(bref));
}
auto partIt = partRefToPart.find(pref);
if (partIt != partRefToPart.end()) {
auto* bone = partIt->second->FirstChildElement("Bone");
if (bone) {
const char* bref = bone->Attribute("refID");
if (bref) boneRefsIncluded.insert(std::string(bref));
}
}
}
}
}
}
if (iteration >= maxIterations) {
// Iteration limit reached, stop processing to prevent infinite loops
// The file is likely malformed, so just skip further processing
return results;
}
// include bricks from bricksIncluded into used set
for (const auto& b : bricksIncluded) usedBrickRefs.insert(b);
// make output doc and push result (include all merged groups' XML)
auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsToInclude);
results.push_back(normalized);
}
// 2) Process remaining RigidSystems (each becomes its own file)
for (auto* rs : rigidSystems) {
if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue;
std::unordered_set<std::string> bricksIncluded;
// collect boneRefs referenced by this rigid system
for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) {
const char* battr = rigid->Attribute("boneRefs");
if (!battr) continue;
for (auto& tok : GeneralUtils::SplitString(battr, ',')) {
auto bpIt = boneRefToPartRef.find(tok);
if (bpIt != boneRefToPartRef.end()) {
auto partRef = bpIt->second;
auto pbIt = partRefToBrick.find(partRef);
if (pbIt != partRefToBrick.end()) {
const char* bref = pbIt->second->Attribute("refID");
if (bref) bricksIncluded.insert(std::string(bref));
}
}
}
}
// mark used
for (const auto& b : bricksIncluded) usedBrickRefs.insert(b);
usedRigidSystems.insert(rs);
std::vector<tinyxml2::XMLElement*> rsVec{ rs };
auto normalized = makeOutput(bricksIncluded, rsVec);
results.push_back(normalized);
}
// 3) Any remaining bricks not included become their own files
for (const auto& [bref, brickPtr] : brickByRef) {
if (usedBrickRefs.find(bref) != usedBrickRefs.end()) continue;
std::unordered_set<std::string> bricksIncluded{ bref };
auto normalized = makeOutput(bricksIncluded, {});
results.push_back(normalized);
usedBrickRefs.insert(bref);
}
return results;
}

View File

@@ -6,7 +6,6 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector>
#include "NiPoint3.h" #include "NiPoint3.h"
@@ -19,7 +18,6 @@ namespace Lxfml {
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0. // 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. // Returns a struct of its new center and the updated LXFML containing these edits.
[[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO); [[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
[[nodiscard]] std::vector<Result> Split(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
// these are only for the migrations due to a bug in one of the implementations. // these are only for the migrations due to a bug in one of the implementations.
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data); [[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);

View File

@@ -53,21 +53,16 @@ Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data)
continue; continue;
} }
try { auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto x = GeneralUtils::TryParse<float>(split[9]).value(); auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value(); auto z = GeneralUtils::TryParse<float>(split[11]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value(); if (x < lowest.x) lowest.x = x;
if (x < lowest.x) lowest.x = x; if (y < lowest.y) lowest.y = y;
if (y < lowest.y) lowest.y = y; if (z < lowest.z) lowest.z = z;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x; if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y; if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z; if (highest.z < z) highest.z = z;
} catch (std::exception& e) {
LOG("Failed to parse a split value of either (%s), (%s), or (%s).", split[9].c_str(), split[10].c_str(), split[11].c_str());
return toReturn; // Early return since we failed to parse this lxfml.
}
} }
auto delta = (highest - lowest) / 2.0f; auto delta = (highest - lowest) / 2.0f;

View File

@@ -1,77 +1,105 @@
#include "Metrics.h" #include "Metrics.hpp"
#include "StringifiedEnum.h"
#include <chrono> #include <chrono>
namespace { std::unordered_map<MetricVariable, Metric*> Metrics::m_Metrics = {};
std::unordered_map<MetricVariable, Metric> g_Metrics = {}; std::vector<MetricVariable> Metrics::m_Variables = {
std::vector<MetricVariable> g_Variables = { MetricVariable::GameLoop,
MetricVariable::GameLoop, MetricVariable::PacketHandling,
MetricVariable::PacketHandling, MetricVariable::UpdateEntities,
MetricVariable::UpdateEntities, MetricVariable::UpdateSpawners,
MetricVariable::UpdateSpawners, MetricVariable::Physics,
MetricVariable::Physics, MetricVariable::UpdateReplica,
MetricVariable::UpdateReplica, MetricVariable::Ghosting,
MetricVariable::Ghosting, MetricVariable::CPUTime,
MetricVariable::CPUTime, MetricVariable::Sleep,
MetricVariable::Sleep, MetricVariable::Frame,
MetricVariable::Frame, };
};
}
void Metrics::AddMeasurement(MetricVariable variable, int64_t value) { void Metrics::AddMeasurement(MetricVariable variable, int64_t value) {
auto& metric = g_Metrics[variable]; const auto& iter = m_Metrics.find(variable);
Metric* metric;
if (iter == m_Metrics.end()) {
metric = new Metric();
m_Metrics[variable] = metric;
} else {
metric = iter->second;
}
AddMeasurement(metric, value); AddMeasurement(metric, value);
} }
void Metrics::AddMeasurement(Metric& metric, int64_t value) { void Metrics::AddMeasurement(Metric* metric, int64_t value) {
const auto index = metric.measurementIndex; const auto index = metric->measurementIndex;
metric.measurements[index] = value; metric->measurements[index] = value;
if (metric.max == -1 || value > metric.max) { if (metric->max == -1 || value > metric->max) {
metric.max = value; metric->max = value;
} else if (metric.min == -1 || metric.min > value) { } else if (metric->min == -1 || metric->min > value) {
metric.min = value; metric->min = value;
} }
if (metric.measurementSize < MAX_MEASURMENT_POINTS) { if (metric->measurementSize < MAX_MEASURMENT_POINTS) {
metric.measurementSize++; metric->measurementSize++;
} }
metric.measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS; metric->measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS;
} }
const Metric& Metrics::GetMetric(MetricVariable variable) { const Metric* Metrics::GetMetric(MetricVariable variable) {
auto& metric = g_Metrics[variable]; const auto& iter = m_Metrics.find(variable);
if (iter == m_Metrics.end()) {
return nullptr;
}
Metric* metric = iter->second;
int64_t average = 0; int64_t average = 0;
for (size_t i = 0; i < metric.measurementSize; i++) { for (size_t i = 0; i < metric->measurementSize; i++) {
average += metric.measurements[i]; average += metric->measurements[i];
} }
average /= metric.measurementSize; average /= metric->measurementSize;
metric.average = average; metric->average = average;
return metric; return metric;
} }
void Metrics::StartMeasurement(MetricVariable variable) { void Metrics::StartMeasurement(MetricVariable variable) {
auto& metric = g_Metrics[variable]; const auto& iter = m_Metrics.find(variable);
metric.activeMeasurement = std::chrono::high_resolution_clock::now(); Metric* metric;
if (iter == m_Metrics.end()) {
metric = new Metric();
m_Metrics[variable] = metric;
} else {
metric = iter->second;
}
metric->activeMeasurement = std::chrono::high_resolution_clock::now();
} }
void Metrics::EndMeasurement(MetricVariable variable) { void Metrics::EndMeasurement(MetricVariable variable) {
const auto end = std::chrono::high_resolution_clock::now(); const auto end = std::chrono::high_resolution_clock::now();
auto& metric = g_Metrics[variable]; const auto& iter = m_Metrics.find(variable);
const auto elapsed = end - metric.activeMeasurement; if (iter == m_Metrics.end()) {
return;
}
Metric* metric = iter->second;
const auto elapsed = end - metric->activeMeasurement;
const auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count(); const auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
@@ -82,12 +110,44 @@ float Metrics::ToMiliseconds(int64_t nanoseconds) {
return static_cast<float>(nanoseconds) / 1e6; return static_cast<float>(nanoseconds) / 1e6;
} }
const std::string_view Metrics::MetricVariableToString(MetricVariable variable) { std::string Metrics::MetricVariableToString(MetricVariable variable) {
return StringifiedEnum::ToString(variable); switch (variable) {
case MetricVariable::GameLoop:
return "GameLoop";
case MetricVariable::PacketHandling:
return "PacketHandling";
case MetricVariable::UpdateEntities:
return "UpdateEntities";
case MetricVariable::UpdateSpawners:
return "UpdateSpawners";
case MetricVariable::Physics:
return "Physics";
case MetricVariable::UpdateReplica:
return "UpdateReplica";
case MetricVariable::Sleep:
return "Sleep";
case MetricVariable::CPUTime:
return "CPUTime";
case MetricVariable::Frame:
return "Frame";
case MetricVariable::Ghosting:
return "Ghosting";
default:
return "Invalid";
}
} }
const std::vector<MetricVariable>& Metrics::GetAllMetrics() { const std::vector<MetricVariable>& Metrics::GetAllMetrics() {
return g_Variables; return m_Variables;
}
void Metrics::Clear() {
for (const auto& pair : m_Metrics) {
delete pair.second;
}
m_Metrics.clear();
} }
/* RSS Memory utilities /* RSS Memory utilities

View File

@@ -1,48 +0,0 @@
#pragma once
#include "dCommonVars.h"
#include <vector>
#include <map>
#include <string_view>
#include <unordered_map>
#include <chrono>
#define MAX_MEASURMENT_POINTS 1024
enum class MetricVariable : int32_t {
GameLoop,
PacketHandling,
UpdateEntities,
UpdateSpawners,
Physics,
UpdateReplica,
Ghosting,
CPUTime,
Sleep,
Frame,
};
struct Metric {
int64_t measurements[MAX_MEASURMENT_POINTS] = {};
size_t measurementIndex = 0;
size_t measurementSize = 0;
int64_t max = -1;
int64_t min = -1;
int64_t average = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> activeMeasurement;
};
namespace Metrics {
void AddMeasurement(MetricVariable variable, int64_t value);
void AddMeasurement(Metric& metric, int64_t value);
const Metric& GetMetric(MetricVariable variable);
void StartMeasurement(MetricVariable variable);
void EndMeasurement(MetricVariable variable);
float ToMiliseconds(int64_t nanoseconds);
const std::string_view MetricVariableToString(MetricVariable variable);
const std::vector<MetricVariable>& GetAllMetrics();
size_t GetPeakRSS();
size_t GetCurrentRSS();
size_t GetProcessID();
};

61
dCommon/Metrics.hpp Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include "dCommonVars.h"
#include <vector>
#include <map>
#include <unordered_map>
#include <chrono>
#define MAX_MEASURMENT_POINTS 1024
enum class MetricVariable : int32_t
{
GameLoop,
PacketHandling,
UpdateEntities,
UpdateSpawners,
Physics,
UpdateReplica,
Ghosting,
CPUTime,
Sleep,
Frame,
};
struct Metric
{
int64_t measurements[MAX_MEASURMENT_POINTS] = {};
size_t measurementIndex = 0;
size_t measurementSize = 0;
int64_t max = -1;
int64_t min = -1;
int64_t average = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> activeMeasurement;
};
class Metrics
{
public:
~Metrics();
static void AddMeasurement(MetricVariable variable, int64_t value);
static void AddMeasurement(Metric* metric, int64_t value);
static const Metric* GetMetric(MetricVariable variable);
static void StartMeasurement(MetricVariable variable);
static void EndMeasurement(MetricVariable variable);
static float ToMiliseconds(int64_t nanoseconds);
static std::string MetricVariableToString(MetricVariable variable);
static const std::vector<MetricVariable>& GetAllMetrics();
static size_t GetPeakRSS();
static size_t GetCurrentRSS();
static size_t GetProcessID();
static void Clear();
private:
Metrics();
static std::unordered_map<MetricVariable, Metric*> m_Metrics;
static std::vector<MetricVariable> m_Variables;
};

View File

@@ -1,21 +1,15 @@
#ifndef __NIPOINT3_H__ #ifndef __NIPOINT3_H__
#define __NIPOINT3_H__ #define __NIPOINT3_H__
#ifndef GLM_ENABLE_EXPERIMENTAL
# define GLM_ENABLE_EXPERIMENTAL
#endif
/*! /*!
\file NiPoint3.hpp \file NiPoint3.hpp
\brief Defines a point in space in XYZ coordinates \brief Defines a point in space in XYZ coordinates
*/ */
class NiPoint3; class NiPoint3;
class NiQuaternion;
typedef NiPoint3 Vector3; //!< The Vector3 class is technically the NiPoint3 class, but typedef'd for clarity in some cases typedef NiPoint3 Vector3; //!< The Vector3 class is technically the NiPoint3 class, but typedef'd for clarity in some cases
#include <glm/ext/vector_float3.hpp>
#include "NiQuaternion.h"
//! A custom class the defines a point in space //! A custom class the defines a point in space
class NiPoint3 { class NiPoint3 {
public: public:
@@ -27,12 +21,6 @@ public:
//! Initializer //! Initializer
constexpr NiPoint3() = default; constexpr NiPoint3() = default;
constexpr NiPoint3(const glm::vec3& vec) noexcept
: x{ vec.x }
, y{ vec.y }
, z{ vec.z } {
}
//! Initializer //! Initializer
/*! /*!
\param x The x coordinate \param x The x coordinate

View File

@@ -4,7 +4,6 @@
#endif #endif
#include "NiQuaternion.h" #include "NiQuaternion.h"
#include <glm/ext/quaternion_float.hpp>
// MARK: Getters / Setters // MARK: Getters / Setters

View File

@@ -3,18 +3,37 @@
// C++ // C++
#include <cmath> #include <cmath>
#include <glm/gtx/quaternion.hpp>
// MARK: Member Functions // MARK: Member Functions
Vector3 QuatUtils::Euler(const NiQuaternion& quat) { Vector3 NiQuaternion::GetEulerAngles() const {
return glm::eulerAngles(quat); Vector3 angles;
// roll (x-axis rotation)
const float sinr_cosp = 2 * (w * x + y * z);
const float cosr_cosp = 1 - 2 * (x * x + y * y);
angles.x = std::atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
const float sinp = 2 * (w * y - z * x);
if (std::abs(sinp) >= 1) {
angles.y = std::copysign(3.14 / 2, sinp); // use 90 degrees if out of range
} else {
angles.y = std::asin(sinp);
}
// yaw (z-axis rotation)
const float siny_cosp = 2 * (w * z + x * y);
const float cosy_cosp = 1 - 2 * (y * y + z * z);
angles.z = std::atan2(siny_cosp, cosy_cosp);
return angles;
} }
// MARK: Helper Functions // MARK: Helper Functions
//! Look from a specific point in space to another point in space (Y-locked) //! Look from a specific point in space to another point in space (Y-locked)
NiQuaternion QuatUtils::LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint) { NiQuaternion NiQuaternion::LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
//To make sure we don't orient around the X/Z axis: //To make sure we don't orient around the X/Z axis:
NiPoint3 source = sourcePoint; NiPoint3 source = sourcePoint;
NiPoint3 dest = destPoint; NiPoint3 dest = destPoint;
@@ -32,11 +51,11 @@ NiQuaternion QuatUtils::LookAt(const NiPoint3& sourcePoint, const NiPoint3& dest
NiPoint3 vecB = vecA.CrossProduct(posZ); NiPoint3 vecB = vecA.CrossProduct(posZ);
if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle; if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle;
return glm::angleAxis(rotAngle, glm::vec3{vecA.x, vecA.y, vecA.z}); return NiQuaternion::CreateFromAxisAngle(vecA, rotAngle);
} }
//! Look from a specific point in space to another point in space //! Look from a specific point in space to another point in space
NiQuaternion QuatUtils::LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint) { NiQuaternion NiQuaternion::LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
NiPoint3 forwardVector = NiPoint3(destPoint - sourcePoint).Unitize(); NiPoint3 forwardVector = NiPoint3(destPoint - sourcePoint).Unitize();
NiPoint3 posZ = NiPoint3Constant::UNIT_Z; NiPoint3 posZ = NiPoint3Constant::UNIT_Z;
@@ -48,26 +67,37 @@ NiQuaternion QuatUtils::LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoin
NiPoint3 vecB = vecA.CrossProduct(posZ); NiPoint3 vecB = vecA.CrossProduct(posZ);
if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle; if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle;
return glm::angleAxis(rotAngle, glm::vec3{vecA.x, vecA.y, vecA.z}); return NiQuaternion::CreateFromAxisAngle(vecA, rotAngle);
} }
//! Creates a Quaternion from a specific axis and angle relative to that axis //! Creates a Quaternion from a specific axis and angle relative to that axis
NiQuaternion QuatUtils::AxisAngle(const Vector3& axis, float angle) { NiQuaternion NiQuaternion::CreateFromAxisAngle(const Vector3& axis, float angle) {
return glm::angleAxis(angle, glm::vec3(axis.x, axis.y, axis.z)); float halfAngle = angle * 0.5f;
float s = static_cast<float>(sin(halfAngle));
NiQuaternion q;
q.x = axis.GetX() * s;
q.y = axis.GetY() * s;
q.z = axis.GetZ() * s;
q.w = static_cast<float>(cos(halfAngle));
return q;
} }
NiQuaternion QuatUtils::FromEuler(const NiPoint3& eulerAngles) { NiQuaternion NiQuaternion::FromEulerAngles(const NiPoint3& eulerAngles) {
return glm::quat(glm::vec3(eulerAngles.x, eulerAngles.y, eulerAngles.z)); // Abbreviations for the various angular functions
} float cy = cos(eulerAngles.z * 0.5);
float sy = sin(eulerAngles.z * 0.5);
float cp = cos(eulerAngles.y * 0.5);
float sp = sin(eulerAngles.y * 0.5);
float cr = cos(eulerAngles.x * 0.5);
float sr = sin(eulerAngles.x * 0.5);
Vector3 QuatUtils::Forward(const NiQuaternion& quat) { NiQuaternion q;
return quat * glm::vec3(0, 0, 1); q.w = cr * cp * cy + sr * sp * sy;
} q.x = sr * cp * cy - cr * sp * sy;
q.y = cr * sp * cy + sr * cp * sy;
q.z = cr * cp * sy - sr * sp * cy;
Vector3 QuatUtils::Up(const NiQuaternion& quat) { return q;
return quat * glm::vec3(0, 1, 0);
}
Vector3 QuatUtils::Right(const NiQuaternion& quat) {
return quat * glm::vec3(1, 0, 0);
} }

View File

@@ -1,29 +1,158 @@
#ifndef NIQUATERNION_H #ifndef __NIQUATERNION_H__
#define NIQUATERNION_H #define __NIQUATERNION_H__
#ifndef GLM_ENABLE_EXPERIMENTAL
# define GLM_ENABLE_EXPERIMENTAL
#endif
// Custom Classes // Custom Classes
#include "NiPoint3.h" #include "NiPoint3.h"
#define GLM_FORCE_QUAT_DATA_WXYZ /*!
\file NiQuaternion.hpp
\brief Defines a quaternion in space in WXYZ coordinates
*/
#include <glm/ext/quaternion_float.hpp> class NiQuaternion;
typedef NiQuaternion Quaternion; //!< A typedef for a shorthand version of NiQuaternion
using Quaternion = glm::quat; //! A class that defines a rotation in space
using NiQuaternion = Quaternion; class NiQuaternion {
public:
float w{ 1 }; //!< The w coordinate
float x{ 0 }; //!< The x coordinate
float y{ 0 }; //!< The y coordinate
float z{ 0 }; //!< The z coordinate
namespace QuatUtils {
constexpr NiQuaternion IDENTITY = glm::identity<NiQuaternion>(); //! The initializer
Vector3 Forward(const NiQuaternion& quat); constexpr NiQuaternion() = default;
Vector3 Up(const NiQuaternion& quat);
Vector3 Right(const NiQuaternion& quat); //! The initializer
NiQuaternion LookAt(const NiPoint3& from, const NiPoint3& to); /*!
NiQuaternion LookAtUnlocked(const NiPoint3& from, const NiPoint3& to); \param w The w coordinate
Vector3 Euler(const NiQuaternion& quat); \param x The x coordinate
NiQuaternion AxisAngle(const Vector3& axis, float angle); \param y The y coordinate
NiQuaternion FromEuler(const NiPoint3& eulerAngles); \param z The z coordinate
constexpr float PI_OVER_180 = glm::pi<float>() / 180.0f; */
constexpr NiQuaternion(const float w, const float x, const float y, const float z) noexcept
: w{ w }
, x{ x }
, y{ y }
, z{ z } {
}
// MARK: Setters / Getters
//! Gets the W coordinate
/*!
\return The w coordinate
*/
[[nodiscard]] constexpr float GetW() const noexcept;
//! Sets the W coordinate
/*!
\param w The w coordinate
*/
constexpr void SetW(const float w) noexcept;
//! Gets the X coordinate
/*!
\return The x coordinate
*/
[[nodiscard]] constexpr float GetX() const noexcept;
//! Sets the X coordinate
/*!
\param x The x coordinate
*/
constexpr void SetX(const float x) noexcept;
//! Gets the Y coordinate
/*!
\return The y coordinate
*/
[[nodiscard]] constexpr float GetY() const noexcept;
//! Sets the Y coordinate
/*!
\param y The y coordinate
*/
constexpr void SetY(const float y) noexcept;
//! Gets the Z coordinate
/*!
\return The z coordinate
*/
[[nodiscard]] constexpr float GetZ() const noexcept;
//! Sets the Z coordinate
/*!
\param z The z coordinate
*/
constexpr void SetZ(const float z) noexcept;
// MARK: Member Functions
//! Returns the forward vector from the quaternion
/*!
\return The forward vector of the quaternion
*/
[[nodiscard]] constexpr Vector3 GetForwardVector() const noexcept;
//! Returns the up vector from the quaternion
/*!
\return The up vector fo the quaternion
*/
[[nodiscard]] constexpr Vector3 GetUpVector() const noexcept;
//! Returns the right vector from the quaternion
/*!
\return The right vector of the quaternion
*/
[[nodiscard]] constexpr Vector3 GetRightVector() const noexcept;
[[nodiscard]] Vector3 GetEulerAngles() const;
// MARK: Operators
//! Operator to check for equality
constexpr bool operator==(const NiQuaternion& rot) const noexcept;
//! Operator to check for inequality
constexpr bool operator!=(const NiQuaternion& rot) const noexcept;
// MARK: Helper Functions
//! Look from a specific point in space to another point in space (Y-locked)
/*!
\param sourcePoint The source location
\param destPoint The destination location
\return The Quaternion with the rotation towards the destination
*/
[[nodiscard]] static NiQuaternion LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint);
//! Look from a specific point in space to another point in space
/*!
\param sourcePoint The source location
\param destPoint The destination location
\return The Quaternion with the rotation towards the destination
*/
[[nodiscard]] static NiQuaternion LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint);
//! Creates a Quaternion from a specific axis and angle relative to that axis
/*!
\param axis The axis that is used
\param angle The angle relative to this axis
\return A quaternion created from the axis and angle
*/
[[nodiscard]] static NiQuaternion CreateFromAxisAngle(const Vector3& axis, float angle);
[[nodiscard]] static NiQuaternion FromEulerAngles(const NiPoint3& eulerAngles);
}; };
#endif // !NIQUATERNION_H // Static Variables
namespace NiQuaternionConstant {
constexpr NiQuaternion IDENTITY(1, 0, 0, 0);
}
// Include constexpr and inline function definitions in a seperate file for readability
#include "NiQuaternion.inl"
#endif // !__NIQUATERNION_H__

75
dCommon/NiQuaternion.inl Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
#ifndef __NIQUATERNION_H__
#error "This should only be included inline in NiQuaternion.h: Do not include directly!"
#endif
// MARK: Setters / Getters
//! Gets the W coordinate
constexpr float NiQuaternion::GetW() const noexcept {
return this->w;
}
//! Sets the W coordinate
constexpr void NiQuaternion::SetW(const float w) noexcept {
this->w = w;
}
//! Gets the X coordinate
constexpr float NiQuaternion::GetX() const noexcept {
return this->x;
}
//! Sets the X coordinate
constexpr void NiQuaternion::SetX(const float x) noexcept {
this->x = x;
}
//! Gets the Y coordinate
constexpr float NiQuaternion::GetY() const noexcept {
return this->y;
}
//! Sets the Y coordinate
constexpr void NiQuaternion::SetY(const float y) noexcept {
this->y = y;
}
//! Gets the Z coordinate
constexpr float NiQuaternion::GetZ() const noexcept {
return this->z;
}
//! Sets the Z coordinate
constexpr void NiQuaternion::SetZ(const float z) noexcept {
this->z = z;
}
// MARK: Member Functions
//! Returns the forward vector from the quaternion
constexpr Vector3 NiQuaternion::GetForwardVector() const noexcept {
return Vector3(2 * (x * z + w * y), 2 * (y * z - w * x), 1 - 2 * (x * x + y * y));
}
//! Returns the up vector from the quaternion
constexpr Vector3 NiQuaternion::GetUpVector() const noexcept {
return Vector3(2 * (x * y - w * z), 1 - 2 * (x * x + z * z), 2 * (y * z + w * x));
}
//! Returns the right vector from the quaternion
constexpr Vector3 NiQuaternion::GetRightVector() const noexcept {
return Vector3(1 - 2 * (y * y + z * z), 2 * (x * y + w * z), 2 * (x * z - w * y));
}
// MARK: Operators
//! Operator to check for equality
constexpr bool NiQuaternion::operator==(const NiQuaternion& rot) const noexcept {
return rot.x == this->x && rot.y == this->y && rot.z == this->z && rot.w == this->w;
}
//! Operator to check for inequality
constexpr bool NiQuaternion::operator!=(const NiQuaternion& rot) const noexcept {
return !(*this == rot);
}

View File

@@ -24,7 +24,7 @@ struct LocalSpaceInfo {
struct PositionUpdate { struct PositionUpdate {
NiPoint3 position = NiPoint3Constant::ZERO; NiPoint3 position = NiPoint3Constant::ZERO;
NiQuaternion rotation = QuatUtils::IDENTITY; NiQuaternion rotation = NiQuaternionConstant::IDENTITY;
bool onGround = false; bool onGround = false;
bool onRail = false; bool onRail = false;
NiPoint3 velocity = NiPoint3Constant::ZERO; NiPoint3 velocity = NiPoint3Constant::ZERO;

View File

@@ -45,12 +45,6 @@ Sd0::Sd0(std::istream& buffer) {
uint32_t bufferSize = buffer.tellg(); uint32_t bufferSize = buffer.tellg();
buffer.seekg(0, std::ios::beg); buffer.seekg(0, std::ios::beg);
WriteSize(firstChunk, bufferSize); WriteSize(firstChunk, bufferSize);
// its expected that if we got here, we got an old sd0 buffer where we ignored the sd0 part
// that means this can be at most the compressed chunk limit.
if (bufferSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
LOG("Possible bad chunk size of %i specified, rejecting.", bufferSize);
return;
}
firstChunk.resize(firstChunk.size() + bufferSize); firstChunk.resize(firstChunk.size() + bufferSize);
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true)); auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
if (!buffer.read(dataStart, bufferSize)) { if (!buffer.read(dataStart, bufferSize)) {
@@ -77,12 +71,6 @@ Sd0::Sd0(std::istream& buffer) {
WriteSize(chunk, chunkSize); WriteSize(chunk, chunkSize);
// Assuming a good buffer that is large enough to take up 2 zlib buffers
// any buffer should be compressed enough to take up less size than its uncompressed counterpart
if (chunkSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
LOG("Possible bad chunk size of %i specified, rejecting.", chunkSize);
break;
}
chunk.resize(chunkSize + dataOffset); chunk.resize(chunkSize + dataOffset);
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset); auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
if (!buffer.read(dataStart, chunkSize)) { if (!buffer.read(dataStart, chunkSize)) {
@@ -107,11 +95,6 @@ void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
startOffset, numToCopy, startOffset, numToCopy,
compressedChunk.data(), compressedChunk.size()); compressedChunk.data(), compressedChunk.size());
if (compressedSize == -1) {
LOG("Failed to compress chunk, aborting");
break;
}
auto& chunk = m_Chunks.emplace_back(); auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1; bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer); auto dataOffset = GetDataOffset(firstBuffer);
@@ -136,12 +119,6 @@ std::string Sd0::GetAsStringUncompressed() const {
auto dataOffset = GetDataOffset(first); auto dataOffset = GetDataOffset(first);
first = false; first = false;
const auto chunkSize = chunk.size(); const auto chunkSize = chunk.size();
if (chunkSize <= static_cast<size_t>(dataOffset)) {
LOG("Bad chunkSize for data, aborting");
toReturn = "";
totalSize = 0;
break;
}
auto oldSize = toReturn.size(); auto oldSize = toReturn.size();
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE); toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
@@ -151,13 +128,6 @@ std::string Sd0::GetAsStringUncompressed() const {
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE, reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
error); error);
if (uncompressedSize == -1) {
LOG("Failed to decompress chunk, aborting");
toReturn = "";
totalSize = 0;
break;
}
totalSize += uncompressedSize; totalSize += uncompressedSize;
} }

View File

@@ -3,12 +3,12 @@
#include "zlib.h" #include "zlib.h"
namespace ZCompression { namespace ZCompression {
uint32_t GetMaxCompressedLength(uint32_t nLenSrc) { int32_t GetMaxCompressedLength(int32_t nLenSrc) {
uint32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block int32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
return (nLenSrc + 6 + (n16kBlocks * 5)); return (nLenSrc + 6 + (n16kBlocks * 5));
} }
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst) { int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst) {
z_stream zInfo = { 0 }; z_stream zInfo = { 0 };
zInfo.total_in = zInfo.avail_in = nLenSrc; zInfo.total_in = zInfo.avail_in = nLenSrc;
zInfo.total_out = zInfo.avail_out = nLenDst; zInfo.total_out = zInfo.avail_out = nLenDst;
@@ -27,7 +27,7 @@ namespace ZCompression {
return(nRet); return(nRet);
} }
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr) { int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) {
// Get the size of the decompressed data // Get the size of the decompressed data
z_stream zInfo = { 0 }; z_stream zInfo = { 0 };
zInfo.total_in = zInfo.avail_in = nLenSrc; zInfo.total_in = zInfo.avail_in = nLenSrc;

View File

@@ -3,10 +3,10 @@
#include <cstdint> #include <cstdint>
namespace ZCompression { namespace ZCompression {
uint32_t GetMaxCompressedLength(uint32_t nLenSrc); int32_t GetMaxCompressedLength(int32_t nLenSrc);
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst); int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr); int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
} }

View File

@@ -7,7 +7,7 @@
#include "zlib.h" #include "zlib.h"
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF; constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
constexpr auto NULL_TERMINATOR = std::string_view{ "\0\0\0", 4 }; constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
AssetManager::AssetManager(const std::filesystem::path& path) { AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) { if (!std::filesystem::is_directory(path)) {
@@ -25,7 +25,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::exists(m_Path / ".." / "versions")) { 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."); 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_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".."); m_RootPath = (m_Path / "..");
@@ -34,7 +34,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) { 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."); 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_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".." / ".."); m_RootPath = (m_Path / ".." / "..");
@@ -54,15 +54,15 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
} }
switch (m_AssetBundleType) { switch (m_AssetBundleType) {
case eAssetBundleType::Packed: { case eAssetBundleType::Packed: {
this->LoadPackIndex(); this->LoadPackIndex();
break; break;
} }
case eAssetBundleType::None: case eAssetBundleType::None:
[[fallthrough]]; [[fallthrough]];
case eAssetBundleType::Unpacked: { case eAssetBundleType::Unpacked: {
break; break;
} }
} }
} }
@@ -79,7 +79,7 @@ bool AssetManager::HasFile(std::string fixedName) const {
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); std::replace(fixedName.begin(), fixedName.end(), '\\', '/');
if (std::filesystem::exists(m_ResPath / fixedName)) return true; if (std::filesystem::exists(m_ResPath / fixedName)) return true;
if (this->m_AssetBundleType == eAssetBundleType::Unpacked || !m_PackIndex) return false; if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false;
std::replace(fixedName.begin(), fixedName.end(), '/', '\\'); std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName; if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
@@ -145,12 +145,8 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
} }
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex); const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
bool success = false; const bool success = pack.ReadFileFromPack(crc, data, len);
try {
success = pack.ReadFileFromPack(crc, data, len);
} catch (std::exception& e) {
LOG("Failed to read file %s from pack file", fixedName.c_str());
}
return success; return success;
} }

View File

@@ -81,9 +81,6 @@ public:
[[nodiscard]] [[nodiscard]]
AssetStream GetFile(const char* name) const; AssetStream GetFile(const char* name) const;
[[nodiscard]]
AssetStream GetFile(const std::string& name) const { return GetFile(name.c_str()); };
private: private:
void LoadPackIndex(); void LoadPackIndex();

View File

@@ -46,7 +46,6 @@ bool Pack::HasFile(const uint32_t crc) const {
} }
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const { bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
const auto pathStr = m_FilePath.string();
// Time for some wacky C file reading for speed reasons // Time for some wacky C file reading for speed reasons
PackRecord pkRecord{}; PackRecord pkRecord{};
@@ -66,21 +65,16 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0; bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0;
auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize; auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize;
FILE* file = nullptr; FILE* file;
#ifdef _WIN32 #ifdef _WIN32
fopen_s(&file, pathStr.c_str(), "rb"); fopen_s(&file, m_FilePath.string().c_str(), "rb");
#elif __APPLE__ #elif __APPLE__
// macOS has 64bit file IO by default // macOS has 64bit file IO by default
file = fopen(pathStr.c_str(), "rb"); file = fopen(m_FilePath.string().c_str(), "rb");
#else #else
file = fopen64(pathStr.c_str(), "rb"); file = fopen64(m_FilePath.string().c_str(), "rb");
#endif #endif
if (!file) {
LOG("No file found for path %s", pathStr.c_str());
throw std::runtime_error("Could not find file " + pathStr);
}
fseek(file, pos, SEEK_SET); fseek(file, pos, SEEK_SET);
if (!isCompressed) { if (!isCompressed) {
@@ -108,18 +102,14 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
int32_t readInData = fread(&size, sizeof(uint32_t), 1, file); int32_t readInData = fread(&size, sizeof(uint32_t), 1, file);
pos += 4; // Move pointer position 4 to the right pos += 4; // Move pointer position 4 to the right
std::unique_ptr<char[]> chunk(new char[size]); char* chunk = static_cast<char*>(malloc(size));
int32_t readInData2 = fread(chunk.get(), sizeof(int8_t), size, file); int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file);
pos += size; // Move pointer position the amount of bytes read to the right pos += size; // Move pointer position the amount of bytes read to the right
int32_t err; int32_t err;
const auto countToRead = ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk.get()), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
if (countToRead == -1) {
LOG("Error decompressing zlib data from file %s", pathStr.c_str());
throw std::runtime_error("Error decompressing zlib data from file " + pathStr);
}
currentReadPos += countToRead;
free(chunk);
} }
*data = decompressedData; *data = decompressedData;

View File

@@ -47,8 +47,6 @@ void dConfig::LoadConfig() {
void dConfig::ReloadConfig() { void dConfig::ReloadConfig() {
this->m_ConfigValues.clear(); this->m_ConfigValues.clear();
LoadConfig(); LoadConfig();
for (const auto& handler : m_ConfigHandlers) handler();
LogSettings();
} }
const std::string& dConfig::GetValue(std::string key) { const std::string& dConfig::GetValue(std::string key) {
@@ -60,18 +58,6 @@ const std::string& dConfig::GetValue(std::string key) {
return this->m_ConfigValues[key]; return this->m_ConfigValues[key];
} }
void dConfig::AddConfigHandler(std::function<void()> handler) {
m_ConfigHandlers.push_back(handler);
}
void dConfig::LogSettings() const {
LOG("Configuration settings:");
for (const auto& [key, value] : m_ConfigValues) {
const auto& valueLog = key.find("password") != std::string::npos ? "<HIDDEN>" : value;
LOG(" %s = %s", key.c_str(), valueLog.c_str());
}
}
void dConfig::ProcessLine(const std::string& line) { void dConfig::ProcessLine(const std::string& line) {
auto splitLoc = line.find('='); auto splitLoc = line.find('=');
auto key = line.substr(0, splitLoc); auto key = line.substr(0, splitLoc);
@@ -84,7 +70,3 @@ void dConfig::ProcessLine(const std::string& line) {
this->m_ConfigValues.insert(std::make_pair(key, value)); this->m_ConfigValues.insert(std::make_pair(key, value));
} }
std::string dConfig::GetValue(const std::string& key, const char* emptyValue) {
return GetValue(key, std::string(emptyValue));
};

View File

@@ -1,12 +1,8 @@
#pragma once #pragma once
#include <fstream> #include <fstream>
#include <functional>
#include <map> #include <map>
#include <string> #include <string>
#include "GeneralUtils.h"
class dConfig { class dConfig {
public: public:
dConfig(const std::string& filepath); dConfig(const std::string& filepath);
@@ -24,14 +20,6 @@ public:
*/ */
const std::string& GetValue(std::string key); const std::string& GetValue(std::string key);
// Gets a value from the config and returns the parsed value, or the default value should parsing have failed.
template<typename T>
T GetValue(const std::string& key, const T emptyValue = T()) {
return GeneralUtils::TryParse<T>(GetValue(key)).value_or(emptyValue);
}
std::string GetValue(const std::string& key, const char* emptyValue);
/** /**
* Loads the config from a file * Loads the config from a file
*/ */
@@ -41,21 +29,10 @@ public:
* Reloads the config file to reset values * Reloads the config file to reset values
*/ */
void ReloadConfig(); void ReloadConfig();
// Adds a function to be called when the config is (re)loaded
void AddConfigHandler(std::function<void()> handler);
void LogSettings() const;
private: private:
void ProcessLine(const std::string& line); void ProcessLine(const std::string& line);
private:
std::map<std::string, std::string> m_ConfigValues; std::map<std::string, std::string> m_ConfigValues;
std::vector<std::function<void()>> m_ConfigHandlers;
std::string m_ConfigFilePath; std::string m_ConfigFilePath;
}; };
template<>
inline std::string dConfig::GetValue(const std::string& key, const std::string emptyValue) {
const auto& value = GetValue(key);
return value.empty() ? emptyValue : value;
};

View File

@@ -3,7 +3,9 @@
namespace MessageType { namespace MessageType {
enum class Master : uint32_t { enum class Master : uint32_t {
REQUEST_ZONE_TRANSFER = 1, REQUEST_PERSISTENT_ID = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE, REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO, SERVER_INFO,
REQUEST_SESSION_KEY, REQUEST_SESSION_KEY,

View File

@@ -16,8 +16,8 @@
// These are the same define, but they mean two different things in different contexts // These are the same define, but they mean two different things in different contexts
// so a different define to distinguish what calculation is happening will help clarity. // so a different define to distinguish what calculation is happening will help clarity.
#define FRAMES_TO_MS(x) (1000 / (x)) #define FRAMES_TO_MS(x) 1000 / x
#define MS_TO_FRAMES(x) (1000 / (x)) #define MS_TO_FRAMES(x) 1000 / x
//=========== FRAME TIMINGS =========== //=========== FRAME TIMINGS ===========
constexpr uint32_t highFramerate = 60; constexpr uint32_t highFramerate = 60;
@@ -58,7 +58,6 @@ constexpr LWOCLONEID LWOCLONEID_INVALID = -1; //!< Invalid LWOCLONEID
constexpr LWOINSTANCEID LWOINSTANCEID_INVALID = -1; //!< Invalid LWOINSTANCEID constexpr LWOINSTANCEID LWOINSTANCEID_INVALID = -1; //!< Invalid LWOINSTANCEID
constexpr LWOMAPID LWOMAPID_INVALID = -1; //!< Invalid LWOMAPID constexpr LWOMAPID LWOMAPID_INVALID = -1; //!< Invalid LWOMAPID
constexpr uint64_t LWOZONEID_INVALID = 0; //!< Invalid LWOZONEID constexpr uint64_t LWOZONEID_INVALID = 0; //!< Invalid LWOZONEID
constexpr uint32_t MAX_MESSAGE_LENGTH = 0x500000; //!< Prevent exceptionally large msgs from being processed. Should always be used to check user provided inputs.
constexpr float PI = 3.14159f; constexpr float PI = 3.14159f;
@@ -111,6 +110,18 @@ private:
constexpr LWOSCENEID LWOSCENEID_INVALID = -1; constexpr LWOSCENEID LWOSCENEID_INVALID = -1;
struct LWONameValue {
uint32_t length = 0; //!< The length of the name
std::u16string name; //!< The name
LWONameValue() = default;
LWONameValue(const std::u16string& name) {
this->name = name;
this->length = static_cast<uint32_t>(name.length());
}
};
struct FriendData { struct FriendData {
public: public:
bool isOnline = false; bool isOnline = false;

View File

@@ -18,10 +18,7 @@ enum class eCharacterVersion : uint32_t {
SPEED_BASE, SPEED_BASE,
// Fixes nexus force explorer missions // Fixes nexus force explorer missions
NJ_JAYMISSIONS, NJ_JAYMISSIONS,
NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER
PET_IDS, // Fixes pet ids in player inventories
INVENTORY_PERSISTENT_IDS, // Fixes racing meta missions
UP_TO_DATE, // will become RACING_META_MISSIONS
}; };
#endif //!__ECHARACTERVERSION__H__ #endif //!__ECHARACTERVERSION__H__

View File

@@ -50,10 +50,7 @@ enum class eMissionState : int {
/** /**
* The mission has been completed before and has now been completed again. Used for daily missions. * The mission has been completed before and has now been completed again. Used for daily missions.
*/ */
COMPLETE_READY_TO_COMPLETE = 12, COMPLETE_READY_TO_COMPLETE = 12
// The mission is failed (don't know where this is used)
FAILED = 16,
}; };
#endif //!__MISSIONSTATE__H__ #endif //!__MISSIONSTATE__H__

View File

@@ -1,12 +1,13 @@
#ifndef EOBJECTBITS_H #ifndef __EOBJECTBITS__H__
#define EOBJECTBITS_H #define __EOBJECTBITS__H__
#include <cstdint> #include <cstdint>
enum class eObjectBits : size_t { enum class eObjectBits : size_t {
PERSISTENT = 32,
CLIENT = 46, CLIENT = 46,
SPAWNED = 58, SPAWNED = 58,
CHARACTER = 60 CHARACTER = 60
}; };
#endif //!EOBJECTBITS_H #endif //!__EOBJECTBITS__H__

View File

@@ -1,5 +1,6 @@
#include "CDActivitiesTable.h" #include "CDActivitiesTable.h"
void CDActivitiesTable::LoadValuesFromDatabase() { void CDActivitiesTable::LoadValuesFromDatabase() {
// First, get the size of the table // First, get the size of the table
uint32_t size = 0; uint32_t size = 0;
@@ -55,13 +56,3 @@ std::vector<CDActivities> CDActivitiesTable::Query(std::function<bool(CDActiviti
return data; return data;
} }
std::optional<const CDActivities> CDActivitiesTable::GetActivity(const uint32_t activityID) {
auto& entries = GetEntries();
for (const auto& entry : entries) {
if (entry.ActivityID == activityID) {
return entry;
}
}
return std::nullopt;
}

View File

@@ -2,7 +2,6 @@
// Custom Classes // Custom Classes
#include "CDTable.h" #include "CDTable.h"
#include <optional>
struct CDActivities { struct CDActivities {
uint32_t ActivityID; uint32_t ActivityID;
@@ -32,5 +31,4 @@ public:
// Queries the table with a custom "where" clause // Queries the table with a custom "where" clause
std::vector<CDActivities> Query(std::function<bool(CDActivities)> predicate); std::vector<CDActivities> Query(std::function<bool(CDActivities)> predicate);
std::optional<const CDActivities> GetActivity(const uint32_t activityID);
}; };

View File

@@ -154,8 +154,8 @@ std::map<LOT, uint32_t> CDItemComponentTable::ParseCraftingCurrencies(const CDIt
// Checking for 2 here, not sure what to do when there's more stuff than expected // Checking for 2 here, not sure what to do when there's more stuff than expected
if (amountSplit.size() == 2) { if (amountSplit.size() == 2) {
currencies.insert({ currencies.insert({
GeneralUtils::TryParse<LOT>(amountSplit[0], LOT_NULL), std::stoull(amountSplit[0]),
GeneralUtils::TryParse<uint32_t>(amountSplit[1], 0) std::stoi(amountSplit[1])
}); });
} }
} }

View File

@@ -93,14 +93,13 @@ std::vector<CDMissions> CDMissionsTable::Query(std::function<bool(CDMissions)> p
} }
const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const { const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const {
const CDMissions* toReturn = &Default;
for (const auto& entry : GetEntries()) { for (const auto& entry : GetEntries()) {
if (entry.id == missionID) { if (entry.id == missionID) {
toReturn = &entry; return const_cast<CDMissions*>(&entry);
} }
} }
return toReturn; return &Default;
} }
const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const { const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const {

View File

@@ -66,7 +66,6 @@ public:
// Queries the table with a custom "where" clause // Queries the table with a custom "where" clause
std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate); std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate);
// Cannot be null.
const CDMissions* GetPtrByMissionID(uint32_t missionID) const; const CDMissions* GetPtrByMissionID(uint32_t missionID) const;
const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const; const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const;

View File

@@ -38,11 +38,3 @@ std::vector<CDObjectSkills> CDObjectSkillsTable::Query(std::function<bool(CDObje
return data; return data;
} }
std::vector<CDObjectSkills> CDObjectSkillsTable::Get(const LOT lot) const {
std::vector<CDObjectSkills> toReturn;
for (const auto& entry : GetEntries()) {
if (entry.objectTemplate == lot) toReturn.push_back(entry);
}
return toReturn;
}

View File

@@ -4,13 +4,12 @@
#include "CDTable.h" #include "CDTable.h"
#include <cstdint> #include <cstdint>
#include <vector>
struct CDObjectSkills { struct CDObjectSkills {
uint32_t objectTemplate; //!< The LOT of the item uint32_t objectTemplate; //!< The LOT of the item
uint32_t skillID; //!< The Skill ID of the object uint32_t skillID; //!< The Skill ID of the object
uint32_t castOnType; //!< ??? uint32_t castOnType; //!< ???
int32_t AICombatWeight; //!< ??? uint32_t AICombatWeight; //!< ???
}; };
class CDObjectSkillsTable : public CDTable<CDObjectSkillsTable, std::vector<CDObjectSkills>> { class CDObjectSkillsTable : public CDTable<CDObjectSkillsTable, std::vector<CDObjectSkills>> {
@@ -18,6 +17,5 @@ public:
void LoadValuesFromDatabase(); void LoadValuesFromDatabase();
// Queries the table with a custom "where" clause // Queries the table with a custom "where" clause
std::vector<CDObjectSkills> Query(std::function<bool(CDObjectSkills)> predicate); std::vector<CDObjectSkills> Query(std::function<bool(CDObjectSkills)> predicate);
std::vector<CDObjectSkills> Get(const LOT lot) const;
}; };

View File

@@ -69,7 +69,7 @@ const CDObjects& CDObjectsTable::GetByID(const uint32_t lot) {
entry.name = tableData.getStringField("name", ""); entry.name = tableData.getStringField("name", "");
UNUSED(entry.placeable = tableData.getIntField("placeable", -1)); UNUSED(entry.placeable = tableData.getIntField("placeable", -1));
entry.type = tableData.getStringField("type", ""); entry.type = tableData.getStringField("type", "");
UNUSED(entry.description = tableData.getStringField(4, "")); UNUSED(ntry.description = tableData.getStringField(4, ""));
UNUSED(entry.localize = tableData.getIntField("localize", -1)); UNUSED(entry.localize = tableData.getIntField("localize", -1));
UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1)); UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1));
UNUSED(entry.displayName = tableData.getStringField("displayName", "")); UNUSED(entry.displayName = tableData.getStringField("displayName", ""));

View File

@@ -56,7 +56,7 @@ CDRailActivatorComponent CDRailActivatorComponentTable::GetEntryByID(int32_t id)
std::pair<uint32_t, std::u16string> CDRailActivatorComponentTable::EffectPairFromString(std::string& str) { std::pair<uint32_t, std::u16string> CDRailActivatorComponentTable::EffectPairFromString(std::string& str) {
const auto split = GeneralUtils::SplitString(str, ':'); const auto split = GeneralUtils::SplitString(str, ':');
if (split.size() == 2) { if (split.size() == 2) {
return { GeneralUtils::TryParse(split.at(0), 0), GeneralUtils::ASCIIToUTF16(split.at(1)) }; return { std::stoi(split.at(0)), GeneralUtils::ASCIIToUTF16(split.at(1)) };
} }
return {}; return {};

View File

@@ -15,7 +15,7 @@ target_include_directories(dDatabaseCDClient PUBLIC "."
"${PROJECT_SOURCE_DIR}/dCommon" "${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums" "${PROJECT_SOURCE_DIR}/dCommon/dEnums"
) )
target_link_libraries(dDatabaseCDClient PRIVATE sqlite3 glm::glm) target_link_libraries(dDatabaseCDClient PRIVATE sqlite3)
if (${CDCLIENT_CACHE_ALL}) if (${CDCLIENT_CACHE_ALL})
add_compile_definitions(dDatabaseCDClient PRIVATE CDCLIENT_CACHE_ALL=${CDCLIENT_CACHE_ALL}) add_compile_definitions(dDatabaseCDClient PRIVATE CDCLIENT_CACHE_ALL=${CDCLIENT_CACHE_ALL})

View File

@@ -10,5 +10,4 @@ add_dependencies(dDatabase conncpp_dylib)
target_include_directories(dDatabase PUBLIC ".") target_include_directories(dDatabase PUBLIC ".")
target_link_libraries(dDatabase target_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame PUBLIC dDatabaseCDClient dDatabaseGame)
PRIVATE glm::glm)

View File

@@ -29,7 +29,7 @@ target_include_directories(dDatabaseGame PUBLIC "."
target_link_libraries(dDatabaseGame target_link_libraries(dDatabaseGame
INTERFACE dCommon INTERFACE dCommon
PRIVATE sqlite3 MariaDB::ConnCpp glm::glm) PRIVATE sqlite3 MariaDB::ConnCpp)
# Glob together all headers that need to be precompiled # Glob together all headers that need to be precompiled
file( file(

View File

@@ -48,7 +48,7 @@ public:
virtual void Commit() = 0; virtual void Commit() = 0;
virtual bool GetAutoCommit() = 0; virtual bool GetAutoCommit() = 0;
virtual void SetAutoCommit(bool value) = 0; virtual void SetAutoCommit(bool value) = 0;
virtual void DeleteCharacter(const LWOOBJID characterId) = 0; virtual void DeleteCharacter(const uint32_t characterId) = 0;
}; };
#endif //!__GAMEDATABASE__H__ #endif //!__GAMEDATABASE__H__

View File

@@ -14,7 +14,6 @@ public:
std::string bcryptPassword; std::string bcryptPassword;
uint32_t id{}; uint32_t id{};
uint32_t playKeyId{}; uint32_t playKeyId{};
uint64_t muteExpire{};
bool banned{}; bool banned{};
bool locked{}; bool locked{};
eGameMasterLevel maxGmLevel{}; eGameMasterLevel maxGmLevel{};

View File

@@ -14,7 +14,7 @@ enum class eActivityType : uint32_t {
class IActivityLog { class IActivityLog {
public: public:
// Update the activity log for the given account. // Update the activity log for the given account.
virtual void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) = 0; virtual void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) = 0;
}; };
#endif //!__IACTIVITYLOG__H__ #endif //!__IACTIVITYLOG__H__

View File

@@ -9,7 +9,7 @@ class IBehaviors {
public: public:
struct Info { struct Info {
LWOOBJID behaviorId{}; LWOOBJID behaviorId{};
LWOOBJID characterId{}; uint32_t characterId{};
std::string behaviorInfo; std::string behaviorInfo;
}; };

View File

@@ -11,7 +11,7 @@ public:
std::string clientVersion; std::string clientVersion;
std::string otherPlayer; std::string otherPlayer;
std::string selection; std::string selection;
LWOOBJID characterId{}; uint32_t characterId{};
}; };
// Add a new bug report to the database. // Add a new bug report to the database.

View File

@@ -14,7 +14,7 @@ public:
struct Info { struct Info {
std::string name; std::string name;
std::string pendingName; std::string pendingName;
LWOOBJID id{}; uint32_t id{};
uint32_t accountId{}; uint32_t accountId{};
bool needsRename{}; bool needsRename{};
LWOCLONEID cloneId{}; LWOCLONEID cloneId{};
@@ -25,25 +25,25 @@ public:
virtual std::vector<std::string> GetApprovedCharacterNames() = 0; virtual std::vector<std::string> GetApprovedCharacterNames() = 0;
// Get the character info for the given character id. // Get the character info for the given character id.
virtual std::optional<ICharInfo::Info> GetCharacterInfo(const LWOOBJID charId) = 0; virtual std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) = 0;
// Get the character info for the given character name. // Get the character info for the given character name.
virtual std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view name) = 0; virtual std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view name) = 0;
// Get the character ids for the given account. // Get the character ids for the given account.
virtual std::vector<LWOOBJID> GetAccountCharacterIds(const LWOOBJID accountId) = 0; virtual std::vector<uint32_t> GetAccountCharacterIds(const uint32_t accountId) = 0;
// Insert a new character into the database. // Insert a new character into the database.
virtual void InsertNewCharacter(const ICharInfo::Info info) = 0; virtual void InsertNewCharacter(const ICharInfo::Info info) = 0;
// Set the name of the given character. // Set the name of the given character.
virtual void SetCharacterName(const LWOOBJID characterId, const std::string_view name) = 0; virtual void SetCharacterName(const uint32_t characterId, const std::string_view name) = 0;
// Set the pending name of the given character. // Set the pending name of the given character.
virtual void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) = 0; virtual void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) = 0;
// Updates the given character ids last login to be right now. // Updates the given character ids last login to be right now.
virtual void UpdateLastLoggedInCharacter(const LWOOBJID characterId) = 0; virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0;
virtual bool IsNameInUse(const std::string_view name) = 0; virtual bool IsNameInUse(const std::string_view name) = 0;
}; };

View File

@@ -8,13 +8,13 @@
class ICharXml { class ICharXml {
public: public:
// Get the character xml for the given character id. // Get the character xml for the given character id.
virtual std::string GetCharacterXml(const LWOOBJID charId) = 0; virtual std::string GetCharacterXml(const uint32_t charId) = 0;
// Update the character xml for the given character id. // Update the character xml for the given character id.
virtual void UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) = 0; virtual void UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) = 0;
// Insert the character xml for the given character id. // Insert the character xml for the given character id.
virtual void InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) = 0; virtual void InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) = 0;
}; };
#endif //!__ICHARXML__H__ #endif //!__ICHARXML__H__

View File

@@ -8,7 +8,7 @@ class ICommandLog {
public: public:
// Insert a new slash command log entry. // Insert a new slash command log entry.
virtual void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) = 0; virtual void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) = 0;
}; };
#endif //!__ICOMMANDLOG__H__ #endif //!__ICOMMANDLOG__H__

View File

@@ -8,25 +8,25 @@
class IFriends { class IFriends {
public: public:
struct BestFriendStatus { struct BestFriendStatus {
LWOOBJID playerCharacterId{}; uint32_t playerCharacterId{};
LWOOBJID friendCharacterId{}; uint32_t friendCharacterId{};
uint32_t bestFriendStatus{}; uint32_t bestFriendStatus{};
}; };
// Get the friends list for the given character id. // Get the friends list for the given character id.
virtual std::vector<FriendData> GetFriendsList(const LWOOBJID charId) = 0; virtual std::vector<FriendData> GetFriendsList(const uint32_t charId) = 0;
// Get the best friend status for the given player and friend character ids. // Get the best friend status for the given player and friend character ids.
virtual std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; virtual std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
// Set the best friend status for the given player and friend character ids. // Set the best friend status for the given player and friend character ids.
virtual void SetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId, const uint32_t bestFriendStatus) = 0; virtual void SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) = 0;
// Add a friend to the given character id. // Add a friend to the given character id.
virtual void AddFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; virtual void AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
// Remove a friend from the given character id. // Remove a friend from the given character id.
virtual void RemoveFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; virtual void RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
}; };
#endif //!__IFRIENDS__H__ #endif //!__IFRIENDS__H__

View File

@@ -9,12 +9,12 @@ class IIgnoreList {
public: public:
struct Info { struct Info {
std::string name; std::string name;
LWOOBJID id; uint32_t id;
}; };
virtual std::vector<Info> GetIgnoreList(const LWOOBJID playerId) = 0; virtual std::vector<Info> GetIgnoreList(const uint32_t playerId) = 0;
virtual void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) = 0; virtual void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0;
virtual void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) = 0; virtual void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0;
}; };
#endif //!__IIGNORELIST__H__ #endif //!__IIGNORELIST__H__

View File

@@ -5,13 +5,12 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include "dCommonVars.h"
class ILeaderboard { class ILeaderboard {
public: public:
struct Entry { struct Entry {
LWOOBJID charId{}; uint32_t charId{};
uint32_t lastPlayedTimestamp{}; uint32_t lastPlayedTimestamp{};
float primaryScore{}; float primaryScore{};
float secondaryScore{}; float secondaryScore{};
@@ -37,12 +36,12 @@ public:
virtual std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) = 0; virtual std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) = 0; virtual std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) = 0; virtual std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) = 0;
virtual std::optional<Score> GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) = 0; virtual std::optional<Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) = 0;
virtual void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) = 0; virtual void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) = 0; virtual void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) = 0; virtual void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) = 0;
virtual void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) = 0; virtual void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) = 0;
}; };
#endif //!__ILEADERBOARD__H__ #endif //!__ILEADERBOARD__H__

View File

@@ -16,13 +16,13 @@ public:
virtual void InsertNewMail(const MailInfo& mail) = 0; virtual void InsertNewMail(const MailInfo& mail) = 0;
// Get the mail for the given character id. // Get the mail for the given character id.
virtual std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) = 0; virtual std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) = 0;
// Get the mail for the given mail id. // Get the mail for the given mail id.
virtual std::optional<MailInfo> GetMail(const uint64_t mailId) = 0; virtual std::optional<MailInfo> GetMail(const uint64_t mailId) = 0;
// Get the number of unread mail for the given character id. // Get the number of unread mail for the given character id.
virtual uint32_t GetUnreadMailCount(const LWOOBJID characterId) = 0; virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0;
// Mark the given mail as read. // Mark the given mail as read.
virtual void MarkMailRead(const uint64_t mailId) = 0; virtual void MarkMailRead(const uint64_t mailId) = 0;

View File

@@ -6,19 +6,14 @@
class IObjectIdTracker { class IObjectIdTracker {
public: public:
// Only the first 48 bits of the ids are the id, the last 16 bits are reserved for flags.
struct Range {
uint64_t minID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags.
uint64_t maxID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags.
};
// Get the current persistent id. // Get the current persistent id.
virtual std::optional<uint64_t> GetCurrentPersistentId() = 0; virtual std::optional<uint32_t> GetCurrentPersistentId() = 0;
// Insert the default persistent id. // Insert the default persistent id.
virtual void InsertDefaultPersistentId() = 0; virtual void InsertDefaultPersistentId() = 0;
virtual Range GetPersistentIdRange() = 0; // Update the persistent id.
virtual void UpdatePersistentId(const uint32_t newId) = 0;
}; };
#endif //!__IOBJECTIDTRACKER__H__ #endif //!__IOBJECTIDTRACKER__H__

View File

@@ -13,7 +13,7 @@ public:
std::string description; std::string description;
std::string rejectionReason; std::string rejectionReason;
LWOOBJID id{}; LWOOBJID id{};
LWOOBJID ownerId{}; uint32_t ownerId{};
LWOCLONEID cloneId{}; LWOCLONEID cloneId{};
int32_t privacyOption{}; int32_t privacyOption{};
uint32_t modApproved{}; uint32_t modApproved{};
@@ -27,29 +27,25 @@ public:
uint32_t mapId{}; uint32_t mapId{};
std::string searchString; std::string searchString;
ePropertySortType sortChoice{}; ePropertySortType sortChoice{};
LWOOBJID playerId{}; uint32_t playerId{};
uint32_t numResults{}; uint32_t numResults{};
uint32_t startIndex{}; uint32_t startIndex{};
uint32_t playerSort{}; uint32_t playerSort{};
}; };
struct PropertyEntranceResult { struct PropertyEntranceResult {
// This is the number of entries that are in the query IF it were ran without a limit.
int32_t totalEntriesMatchingQuery{}; int32_t totalEntriesMatchingQuery{};
// The entries that match the query. This should only contain up to 12 entries. // The entries that match the query. This should only contain up to 12 entries.
std::vector<IProperty::Info> entries; std::vector<IProperty::Info> entries;
}; };
// Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) = 0;
// Get the property info for the given property id. // Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0;
// Get the properties for the given property lookup params. // Get the properties for the given property lookup params.
// This is expected to return a result set of up to 12 properties // This is expected to return a result set of up to 12 properties
// so as not to transfer too much data at once. // so as not to transfer too much data at once.
virtual IProperty::PropertyEntranceResult GetProperties(const PropertyLookup& params) = 0; virtual std::optional<IProperty::PropertyEntranceResult> GetProperties(const PropertyLookup& params) = 0;
// Update the property moderation info for the given property id. // Update the property moderation info for the given property id.
virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0; virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0;

View File

@@ -13,19 +13,19 @@ public:
} }
NiPoint3 position; NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY; NiQuaternion rotation;
LWOOBJID id{}; LWOOBJID id{};
LOT lot{}; LOT lot{};
LWOOBJID ugcId{}; uint32_t ugcId{};
std::array<LWOOBJID, 5> behaviors{}; std::array<LWOOBJID, 5> behaviors{};
}; };
// Inserts a new UGC model into the database. // Inserts a new UGC model into the database.
virtual void InsertNewUgcModel( virtual void InsertNewUgcModel(
std::stringstream& sd0Data, std::stringstream& sd0Data,
const uint64_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const LWOOBJID characterId) = 0; const uint32_t characterId) = 0;
// Get the property models for the given property id. // Get the property models for the given property id.
virtual std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) = 0; virtual std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) = 0;
@@ -45,6 +45,6 @@ public:
virtual void RemoveModel(const LWOOBJID& modelId) = 0; virtual void RemoveModel(const LWOOBJID& modelId) = 0;
// Gets a model by ID // Gets a model by ID
virtual std::optional<Model> GetModel(const LWOOBJID modelID) = 0; virtual Model GetModel(const LWOOBJID modelID) = 0;
}; };
#endif //!__IPROPERTIESCONTENTS__H__ #endif //!__IPROPERTIESCONTENTS__H__

View File

@@ -29,7 +29,5 @@ public:
// Inserts a new UGC model into the database. // Inserts a new UGC model into the database.
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0; virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0;
virtual std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) = 0;
}; };
#endif //!__IUGC__H__ #endif //!__IUGC__H__

View File

@@ -7,7 +7,7 @@
class IUgcModularBuild { class IUgcModularBuild {
public: public:
virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<LWOOBJID> characterId) = 0; virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) = 0;
virtual void DeleteUgcBuild(const LWOOBJID bigId) = 0; virtual void DeleteUgcBuild(const LWOOBJID bigId) = 0;
}; };

View File

@@ -100,7 +100,7 @@ void MySQLDatabase::SetAutoCommit(bool value) {
con->setAutoCommit(value); con->setAutoCommit(value);
} }
void MySQLDatabase::DeleteCharacter(const LWOOBJID characterId) { void MySQLDatabase::DeleteCharacter(const uint32_t characterId) {
ExecuteDelete("DELETE FROM charxml WHERE id=? LIMIT 1;", characterId); ExecuteDelete("DELETE FROM charxml WHERE id=? LIMIT 1;", characterId);
ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId); ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId); ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId);

View File

@@ -9,20 +9,6 @@
typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef; typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef;
typedef std::unique_ptr<sql::ResultSet> UniqueResultSet; typedef std::unique_ptr<sql::ResultSet> UniqueResultSet;
// This struct is used to keep the PreparedStatement alive alongside the ResultSet, since the ResultSet will be invalidated if the PreparedStatement is destroyed.
// Declaring the members in reverse order of usage to ensure the PreparedStatement is destroyed after the ResultSet. This is guaranteed by the C++ standard.
struct PreparedStmtResultSet {
std::unique_ptr<sql::PreparedStatement> m_stmt;
std::unique_ptr<sql::ResultSet> m_resultSet;
PreparedStmtResultSet(sql::PreparedStatement* stmt = nullptr, sql::ResultSet* resultSet = nullptr)
: m_stmt(stmt), m_resultSet(resultSet) {}
sql::ResultSet* operator->() const {
return m_resultSet.get();
}
};
// Purposefully no definition for this to provide linker errors in the case someone tries to // Purposefully no definition for this to provide linker errors in the case someone tries to
// bind a parameter to a type that isn't defined. // bind a parameter to a type that isn't defined.
template<typename ParamType> template<typename ParamType>
@@ -54,31 +40,31 @@ public:
std::vector<std::string> GetApprovedCharacterNames() override; std::vector<std::string> GetApprovedCharacterNames() override;
std::vector<FriendData> GetFriendsList(LWOOBJID charID) override; std::vector<FriendData> GetFriendsList(uint32_t charID) override;
std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) override; std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override;
void SetBestFriendStatus(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId, const uint32_t bestFriendStatus) override; void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override;
void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override; void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override; void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override; void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override; std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override; void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override; bool IsMigrationRun(const std::string_view str) override;
void InsertMigration(const std::string_view str) override; void InsertMigration(const std::string_view str) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const LWOOBJID charId) override; std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view charId) override; std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view charId) override;
std::string GetCharacterXml(const LWOOBJID accountId) override; std::string GetCharacterXml(const uint32_t accountId) override;
void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override; void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override; std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
void InsertNewCharacter(const ICharInfo::Info info) override; void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override; void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override;
std::vector<LWOOBJID> GetAccountCharacterIds(LWOOBJID accountId) override; std::vector<uint32_t> GetAccountCharacterIds(uint32_t accountId) override;
void DeleteCharacter(const LWOOBJID characterId) override; void DeleteCharacter(const uint32_t characterId) override;
void SetCharacterName(const LWOOBJID characterId, const std::string_view name) override; void SetCharacterName(const uint32_t characterId, const std::string_view name) override;
void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) override; void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override;
void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override; void UpdateLastLoggedInCharacter(const uint32_t characterId) override;
void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override;
std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override; std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
@@ -97,68 +83,63 @@ public:
void InsertNewMail(const MailInfo& mail) override; void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel( void InsertNewUgcModel(
std::stringstream& sd0Data, std::stringstream& sd0Data,
const uint64_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const LWOOBJID characterId) override; const uint32_t characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override; void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override;
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterInfo(const IServers::MasterInfo& info) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint64_t> GetCurrentPersistentId() override; std::optional<uint32_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override; std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override; std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override; std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
std::vector<IIgnoreList::Info> GetIgnoreList(const LWOOBJID playerId) override; std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override; void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const LWOOBJID behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override; void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override;
void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
std::optional<ILeaderboard::Score> GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) override; std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override;
void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) override; void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override;
void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) override; void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override;
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<LWOOBJID> characterId) override; void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override; void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override; uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override; bool IsNameInUse(const std::string_view name) override;
std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override; IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query); sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private: private:
// Generic query functions that can be used for any query. // Generic query functions that can be used for any query.
// Return type may be different depending on the query, so it is up to the caller to check the return type. // Return type may be different depending on the query, so it is up to the caller to check the return type.
// The first argument is the query string, and the rest are the parameters to bind to the query. // The first argument is the query string, and the rest are the parameters to bind to the query.
// The return type is a PreparedStmtResultSet which keeps the PreparedStatement alive alongside the ResultSet. // The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope
template<typename... Args> template<typename... Args>
inline PreparedStmtResultSet ExecuteSelect(const std::string& query, Args&&... args) { inline std::unique_ptr<sql::ResultSet> ExecuteSelect(const std::string& query, Args&&... args) {
PreparedStmtResultSet toReturn; std::unique_ptr<sql::PreparedStatement> preppedStmt(CreatePreppedStmt(query));
toReturn.m_stmt.reset(CreatePreppedStmt(query)); SetParams(preppedStmt, std::forward<Args>(args)...);
SetParams(toReturn.m_stmt, std::forward<Args>(args)...); DLU_SQL_TRY_CATCH_RETHROW(return std::unique_ptr<sql::ResultSet>(preppedStmt->executeQuery()));
DLU_SQL_TRY_CATCH_RETHROW(toReturn.m_resultSet.reset(toReturn.m_stmt->executeQuery()));
// Return the PreparedStmtResultSet, which now owns both the PreparedStatement and ResultSet via unique_ptr and will ensure they are properly cleaned up.
return toReturn;
} }
template<typename... Args> template<typename... Args>
@@ -187,91 +168,91 @@ private:
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) {
LOG_DEBUG("%s", param.data()); // LOG("%s", param.data());
stmt->setString(index, param.data()); stmt->setString(index, param.data());
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) {
LOG_DEBUG("%s", param); // LOG("%s", param);
stmt->setString(index, param); stmt->setString(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) {
LOG_DEBUG("%s", param.c_str()); // LOG("%s", param.c_str());
stmt->setString(index, param.c_str()); stmt->setString(index, param.c_str());
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) {
LOG_DEBUG("%u", param); // LOG("%u", param);
stmt->setByte(index, param); stmt->setByte(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) {
LOG_DEBUG("%d", param); // LOG("%d", param);
stmt->setByte(index, param); stmt->setByte(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) {
LOG_DEBUG("%u", param); // LOG("%u", param);
stmt->setShort(index, param); stmt->setShort(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) {
LOG_DEBUG("%d", param); // LOG("%d", param);
stmt->setShort(index, param); stmt->setShort(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) {
LOG_DEBUG("%u", param); // LOG("%u", param);
stmt->setUInt(index, param); stmt->setUInt(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) {
LOG_DEBUG("%d", param); // LOG("%d", param);
stmt->setInt(index, param); stmt->setInt(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) {
LOG_DEBUG("%llu", param); // LOG("%llu", param);
stmt->setInt64(index, param); stmt->setInt64(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) {
LOG_DEBUG("%llu", param); // LOG("%llu", param);
stmt->setUInt64(index, param); stmt->setUInt64(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) {
LOG_DEBUG("%f", param); // LOG("%f", param);
stmt->setFloat(index, param); stmt->setFloat(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) {
LOG_DEBUG("%f", param); // LOG("%f", param);
stmt->setDouble(index, param); stmt->setDouble(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) {
LOG_DEBUG("%s", param ? "true" : "false"); // LOG("%d", param);
stmt->setBoolean(index, param); stmt->setBoolean(index, param);
} }
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) {
LOG_DEBUG("Blob"); // LOG("Blob");
// This is the one time you will ever see me use const_cast. // This is the one time you will ever see me use const_cast.
stmt->setBlob(index, const_cast<std::istream*>(param)); stmt->setBlob(index, const_cast<std::istream*>(param));
} }
@@ -279,21 +260,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istr
template<> template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) { inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
if (param) { if (param) {
LOG_DEBUG("%d", param.value()); // LOG("%d", param.value());
stmt->setInt(index, param.value()); stmt->setInt(index, param.value());
} else { } else {
LOG_DEBUG("Null"); // LOG("Null");
stmt->setNull(index, sql::DataType::SQLNULL);
}
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional<LWOOBJID> param) {
if (param) {
LOG_DEBUG("%d", param.value());
stmt->setInt64(index, param.value());
} else {
LOG_DEBUG("Null");
stmt->setNull(index, sql::DataType::SQLNULL); stmt->setNull(index, sql::DataType::SQLNULL);
} }
} }

View File

@@ -3,7 +3,7 @@
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_view username) { std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_view username) {
auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level, mute_expire FROM accounts WHERE name = ? LIMIT 1;", username); auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level FROM accounts WHERE name = ? LIMIT 1;", username);
if (!result->next()) { if (!result->next()) {
return std::nullopt; return std::nullopt;
@@ -16,7 +16,6 @@ std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_v
toReturn.banned = result->getBoolean("banned"); toReturn.banned = result->getBoolean("banned");
toReturn.locked = result->getBoolean("locked"); toReturn.locked = result->getBoolean("locked");
toReturn.playKeyId = result->getUInt("play_key_id"); toReturn.playKeyId = result->getUInt("play_key_id");
toReturn.muteExpire = result->getUInt64("mute_expire");
return toReturn; return toReturn;
} }

View File

@@ -1,6 +1,6 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
void MySQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) { void MySQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) {
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);", ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId); characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
} }

View File

@@ -12,14 +12,14 @@ std::vector<std::string> MySQLDatabase::GetApprovedCharacterNames() {
return toReturn; return toReturn;
} }
std::optional<ICharInfo::Info> CharInfoFromQueryResult(PreparedStmtResultSet& stmt) { std::optional<ICharInfo::Info> CharInfoFromQueryResult(std::unique_ptr<sql::ResultSet> stmt) {
if (!stmt->next()) { if (!stmt->next()) {
return std::nullopt; return std::nullopt;
} }
ICharInfo::Info toReturn; ICharInfo::Info toReturn;
toReturn.id = stmt->getInt64("id"); toReturn.id = stmt->getUInt("id");
toReturn.name = stmt->getString("name").c_str(); toReturn.name = stmt->getString("name").c_str();
toReturn.pendingName = stmt->getString("pending_name").c_str(); toReturn.pendingName = stmt->getString("pending_name").c_str();
toReturn.needsRename = stmt->getBoolean("needs_rename"); toReturn.needsRename = stmt->getBoolean("needs_rename");
@@ -30,23 +30,25 @@ std::optional<ICharInfo::Info> CharInfoFromQueryResult(PreparedStmtResultSet& st
return toReturn; return toReturn;
} }
std::optional<ICharInfo::Info> MySQLDatabase::GetCharacterInfo(const LWOOBJID charId) { std::optional<ICharInfo::Info> MySQLDatabase::GetCharacterInfo(const uint32_t charId) {
auto result = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId); return CharInfoFromQueryResult(
return CharInfoFromQueryResult(result); ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId)
);
} }
std::optional<ICharInfo::Info> MySQLDatabase::GetCharacterInfo(const std::string_view name) { std::optional<ICharInfo::Info> MySQLDatabase::GetCharacterInfo(const std::string_view name) {
auto result = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name); return CharInfoFromQueryResult(
return CharInfoFromQueryResult(result); ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name)
);
} }
std::vector<LWOOBJID> MySQLDatabase::GetAccountCharacterIds(const LWOOBJID accountId) { std::vector<uint32_t> MySQLDatabase::GetAccountCharacterIds(const uint32_t accountId) {
auto result = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId); auto result = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId);
std::vector<LWOOBJID> toReturn; std::vector<uint32_t> toReturn;
toReturn.reserve(result->rowsCount()); toReturn.reserve(result->rowsCount());
while (result->next()) { while (result->next()) {
toReturn.push_back(result->getInt64("id")); toReturn.push_back(result->getUInt("id"));
} }
return toReturn; return toReturn;
@@ -63,15 +65,15 @@ void MySQLDatabase::InsertNewCharacter(const ICharInfo::Info info) {
static_cast<uint32_t>(time(NULL))); static_cast<uint32_t>(time(NULL)));
} }
void MySQLDatabase::SetCharacterName(const LWOOBJID characterId, const std::string_view name) { void MySQLDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) {
ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1;", name, static_cast<uint32_t>(time(NULL)), characterId); ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1;", name, static_cast<uint32_t>(time(NULL)), characterId);
} }
void MySQLDatabase::SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) { void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) {
ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1", name, static_cast<uint32_t>(time(NULL)), characterId); ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1", name, static_cast<uint32_t>(time(NULL)), characterId);
} }
void MySQLDatabase::UpdateLastLoggedInCharacter(const LWOOBJID characterId) { void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast<uint32_t>(time(NULL)), characterId); ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast<uint32_t>(time(NULL)), characterId);
} }

View File

@@ -1,6 +1,6 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
std::string MySQLDatabase::GetCharacterXml(const LWOOBJID charId) { std::string MySQLDatabase::GetCharacterXml(const uint32_t charId) {
auto result = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId); auto result = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId);
if (!result->next()) { if (!result->next()) {
@@ -10,10 +10,10 @@ std::string MySQLDatabase::GetCharacterXml(const LWOOBJID charId) {
return result->getString("xml_data").c_str(); return result->getString("xml_data").c_str();
} }
void MySQLDatabase::UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) { void MySQLDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) {
ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId); ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId);
} }
void MySQLDatabase::InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) { void MySQLDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) {
ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml); ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml);
} }

View File

@@ -1,5 +1,5 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
void MySQLDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) { void MySQLDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) {
ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command); ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command);
} }

View File

@@ -1,6 +1,6 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
std::vector<FriendData> MySQLDatabase::GetFriendsList(const LWOOBJID charId) { std::vector<FriendData> MySQLDatabase::GetFriendsList(const uint32_t charId) {
auto friendsList = ExecuteSelect( auto friendsList = ExecuteSelect(
R"QUERY( R"QUERY(
SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM
@@ -19,7 +19,7 @@ std::vector<FriendData> MySQLDatabase::GetFriendsList(const LWOOBJID charId) {
while (friendsList->next()) { while (friendsList->next()) {
FriendData fd; FriendData fd;
fd.friendID = friendsList->getUInt64("player"); fd.friendID = friendsList->getUInt("player");
fd.isBestFriend = friendsList->getInt("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs fd.isBestFriend = friendsList->getInt("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
fd.friendName = friendsList->getString("name").c_str(); fd.friendName = friendsList->getString("name").c_str();
@@ -29,7 +29,7 @@ std::vector<FriendData> MySQLDatabase::GetFriendsList(const LWOOBJID charId) {
return toReturn; return toReturn;
} }
std::optional<IFriends::BestFriendStatus> MySQLDatabase::GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { std::optional<IFriends::BestFriendStatus> MySQLDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
auto result = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", auto result = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;",
playerCharacterId, playerCharacterId,
friendCharacterId, friendCharacterId,
@@ -42,14 +42,14 @@ std::optional<IFriends::BestFriendStatus> MySQLDatabase::GetBestFriendStatus(con
} }
IFriends::BestFriendStatus toReturn; IFriends::BestFriendStatus toReturn;
toReturn.playerCharacterId = result->getUInt64("player_id"); toReturn.playerCharacterId = result->getUInt("player_id");
toReturn.friendCharacterId = result->getUInt64("friend_id"); toReturn.friendCharacterId = result->getUInt("friend_id");
toReturn.bestFriendStatus = result->getUInt("best_friend"); toReturn.bestFriendStatus = result->getUInt("best_friend");
return toReturn; return toReturn;
} }
void MySQLDatabase::SetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId, const uint32_t bestFriendStatus) { void MySQLDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) {
ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;",
bestFriendStatus, bestFriendStatus,
playerCharacterId, playerCharacterId,
@@ -59,11 +59,11 @@ void MySQLDatabase::SetBestFriendStatus(const LWOOBJID playerCharacterId, const
); );
} }
void MySQLDatabase::AddFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { void MySQLDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
ExecuteInsert("INSERT IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId); ExecuteInsert("INSERT IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId);
} }
void MySQLDatabase::RemoveFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { void MySQLDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;",
playerCharacterId, playerCharacterId,
friendCharacterId, friendCharacterId,

View File

@@ -1,22 +1,22 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
std::vector<IIgnoreList::Info> MySQLDatabase::GetIgnoreList(const LWOOBJID playerId) { std::vector<IIgnoreList::Info> MySQLDatabase::GetIgnoreList(const uint32_t playerId) {
auto result = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId); auto result = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId);
std::vector<IIgnoreList::Info> ignoreList; std::vector<IIgnoreList::Info> ignoreList;
ignoreList.reserve(result->rowsCount()); ignoreList.reserve(result->rowsCount());
while (result->next()) { while (result->next()) {
ignoreList.push_back(IIgnoreList::Info{ result->getString("name").c_str(), result->getInt64("ignore_id") }); ignoreList.push_back(IIgnoreList::Info{ result->getString("name").c_str(), result->getUInt("ignore_id") });
} }
return ignoreList; return ignoreList;
} }
void MySQLDatabase::AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { void MySQLDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
ExecuteInsert("INSERT IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId); ExecuteInsert("INSERT IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId);
} }
void MySQLDatabase::RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { void MySQLDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId); ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId);
} }

View File

@@ -14,14 +14,14 @@ std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityI
return donation_total->getUInt("donation_total"); return donation_total->getUInt("donation_total");
} }
std::vector<ILeaderboard::Entry> ProcessQuery(PreparedStmtResultSet& rows) { std::vector<ILeaderboard::Entry> ProcessQuery(UniqueResultSet& rows) {
std::vector<ILeaderboard::Entry> entries; std::vector<ILeaderboard::Entry> entries;
entries.reserve(rows->rowsCount()); entries.reserve(rows->rowsCount());
while (rows->next()) { while (rows->next()) {
auto& entry = entries.emplace_back(); auto& entry = entries.emplace_back();
entry.charId = rows->getUInt64("character_id"); entry.charId = rows->getUInt("character_id");
entry.lastPlayedTimestamp = rows->getUInt("lp_unix"); entry.lastPlayedTimestamp = rows->getUInt("lp_unix");
entry.primaryScore = rows->getFloat("primaryScore"); entry.primaryScore = rows->getFloat("primaryScore");
entry.secondaryScore = rows->getFloat("secondaryScore"); entry.secondaryScore = rows->getFloat("secondaryScore");
@@ -58,21 +58,21 @@ std::vector<ILeaderboard::Entry> MySQLDatabase::GetNsLeaderboard(const uint32_t
return ProcessQuery(leaderboard); return ProcessQuery(leaderboard);
} }
void MySQLDatabase::SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { void MySQLDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;", ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;",
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
} }
void MySQLDatabase::UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;",
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
} }
void MySQLDatabase::IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) { void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId); ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
} }
std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) { std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
std::optional<ILeaderboard::Score> toReturn = std::nullopt; std::optional<ILeaderboard::Score> toReturn = std::nullopt;
auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId); auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
if (res->next()) { if (res->next()) {
@@ -86,6 +86,6 @@ std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const LWOOBJID
return toReturn; return toReturn;
} }
void MySQLDatabase::IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) { void MySQLDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) {
ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId); ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
} }

View File

@@ -19,7 +19,7 @@ void MySQLDatabase::InsertNewMail(const MailInfo& mail) {
mail.itemCount); mail.itemCount);
} }
std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) { std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto res = ExecuteSelect( auto res = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;", " FROM mail WHERE receiver_id=? limit ?;",
@@ -48,7 +48,7 @@ std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId
} }
std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) { std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId); auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
if (!res->next()) { if (!res->next()) {
return std::nullopt; return std::nullopt;
@@ -57,12 +57,11 @@ std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
MailInfo toReturn; MailInfo toReturn;
toReturn.itemLOT = res->getInt("attachment_lot"); toReturn.itemLOT = res->getInt("attachment_lot");
toReturn.itemCount = res->getInt("attachment_count"); toReturn.itemCount = res->getInt("attachment_count");
toReturn.receiverId = res->getUInt64("receiver_id");
return toReturn; return toReturn;
} }
uint32_t MySQLDatabase::GetUnreadMailCount(const LWOOBJID characterId) { uint32_t MySQLDatabase::GetUnreadMailCount(const uint32_t characterId) {
auto res = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId); auto res = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId);
if (!res->next()) { if (!res->next()) {

View File

@@ -1,42 +1,17 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
std::optional<uint64_t> MySQLDatabase::GetCurrentPersistentId() { std::optional<uint32_t> MySQLDatabase::GetCurrentPersistentId() {
auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker");
if (!result->next()) { if (!result->next()) {
return std::nullopt; return std::nullopt;
} }
return result->getUInt64("last_object_id"); return result->getUInt("last_object_id");
} }
void MySQLDatabase::InsertDefaultPersistentId() { void MySQLDatabase::InsertDefaultPersistentId() {
ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);");
} }
IObjectIdTracker::Range MySQLDatabase::GetPersistentIdRange() { void MySQLDatabase::UpdatePersistentId(const uint32_t newId) {
IObjectIdTracker::Range range; ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId);
auto prevCommit = GetAutoCommit();
SetAutoCommit(false);
// THIS MUST ABSOLUTELY NOT FAIL. These IDs are expected to be unique. As such a transactional select is used to safely get a range
// of IDs that will never be used again. A separate feature could track unused IDs and recycle them, but that is not implemented.
ExecuteCustomQuery("START TRANSACTION;");
// 200 seems like a good range to reserve at once. Only way this would be noticable is if a player
// added hundreds of items at once.
// Doing the update first ensures that all other connections are blocked from accessing this table until we commit.
auto result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;");
// If no rows were updated, it means the table is empty, so we need to insert the default row first.
if (result == 0) {
InsertDefaultPersistentId();
result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;");
}
// At this point all connections are waiting on us to finish the transaction, so we can safely select the ID we just set.
auto selectRes = ExecuteSelect("SELECT last_object_id FROM object_id_tracker;");
selectRes->next();
range.maxID = selectRes->getUInt64("last_object_id");
range.minID = range.maxID - 199;
ExecuteCustomQuery("COMMIT;");
SetAutoCommit(prevCommit);
return range;
} }

View File

@@ -1,27 +1,10 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
#include "ePropertySortType.h" #include "ePropertySortType.h"
IProperty::Info ReadPropertyInfo(PreparedStmtResultSet& result) { std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
IProperty::Info info; std::optional<IProperty::PropertyEntranceResult> result;
info.id = result->getUInt64("id");
info.ownerId = result->getInt64("owner_id");
info.cloneId = result->getUInt64("clone_id");
info.name = result->getString("name").c_str();
info.description = result->getString("description").c_str();
info.privacyOption = result->getInt("privacy_option");
info.rejectionReason = result->getString("rejection_reason").c_str();
info.lastUpdatedTime = result->getUInt("last_updated");
info.claimedTime = result->getUInt("time_claimed");
info.reputation = result->getUInt("reputation");
info.modApproved = result->getUInt("mod_approved");
info.performanceCost = result->getFloat("performance_cost");
return info;
}
IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
IProperty::PropertyEntranceResult result;
std::string query; std::string query;
PreparedStmtResultSet properties; std::unique_ptr<sql::ResultSet> properties;
if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) { if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) {
query = R"QUERY( query = R"QUERY(
@@ -73,7 +56,8 @@ IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::
params.playerId params.playerId
); );
if (count->next()) { if (count->next()) {
result.totalEntriesMatchingQuery = count->getUInt("count"); if (!result) result = IProperty::PropertyEntranceResult();
result->totalEntriesMatchingQuery = count->getUInt("count");
} }
} else { } else {
if (params.sortChoice == SORT_TYPE_REPUTATION) { if (params.sortChoice == SORT_TYPE_REPUTATION) {
@@ -126,12 +110,26 @@ IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::
params.playerSort params.playerSort
); );
if (count->next()) { if (count->next()) {
result.totalEntriesMatchingQuery = count->getUInt("count"); if (!result) result = IProperty::PropertyEntranceResult();
result->totalEntriesMatchingQuery = count->getUInt("count");
} }
} }
while (properties->next()) { while (properties->next()) {
result.entries.push_back(ReadPropertyInfo(properties)); if (!result) result = IProperty::PropertyEntranceResult();
auto& entry = result->entries.emplace_back();
entry.id = properties->getUInt64("id");
entry.ownerId = properties->getUInt64("owner_id");
entry.cloneId = properties->getUInt64("clone_id");
entry.name = properties->getString("name").c_str();
entry.description = properties->getString("description").c_str();
entry.privacyOption = properties->getInt("privacy_option");
entry.rejectionReason = properties->getString("rejection_reason").c_str();
entry.lastUpdatedTime = properties->getUInt("last_updated");
entry.claimedTime = properties->getUInt("time_claimed");
entry.reputation = properties->getUInt("reputation");
entry.modApproved = properties->getUInt("mod_approved");
entry.performanceCost = properties->getFloat("performance_cost");
} }
return result; return result;
@@ -146,7 +144,21 @@ std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOMAPID map
return std::nullopt; return std::nullopt;
} }
return ReadPropertyInfo(propertyEntry); IProperty::Info toReturn;
toReturn.id = propertyEntry->getUInt64("id");
toReturn.ownerId = propertyEntry->getUInt64("owner_id");
toReturn.cloneId = propertyEntry->getUInt64("clone_id");
toReturn.name = propertyEntry->getString("name").c_str();
toReturn.description = propertyEntry->getString("description").c_str();
toReturn.privacyOption = propertyEntry->getInt("privacy_option");
toReturn.rejectionReason = propertyEntry->getString("rejection_reason").c_str();
toReturn.lastUpdatedTime = propertyEntry->getUInt("last_updated");
toReturn.claimedTime = propertyEntry->getUInt("time_claimed");
toReturn.reputation = propertyEntry->getUInt("reputation");
toReturn.modApproved = propertyEntry->getUInt("mod_approved");
toReturn.performanceCost = propertyEntry->getFloat("performance_cost");
return toReturn;
} }
void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) {
@@ -183,15 +195,3 @@ void MySQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_
zoneId.GetMapID() zoneId.GetMapID()
); );
} }
std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOOBJID id) {
auto propertyEntry = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties WHERE id = ?;", id);
if (!propertyEntry->next()) {
return std::nullopt;
}
return ReadPropertyInfo(propertyEntry);
}

View File

@@ -64,27 +64,26 @@ void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
} }
std::optional<IPropertyContents::Model> MySQLDatabase::GetModel(const LWOOBJID modelID) { IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) {
auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
std::optional<IPropertyContents::Model> model = std::nullopt; IPropertyContents::Model model{};
while (result->next()) { while (result->next()) {
model = IPropertyContents::Model{}; model.id = result->getUInt64("id");
model->id = result->getUInt64("id"); model.lot = static_cast<LOT>(result->getUInt("lot"));
model->lot = static_cast<LOT>(result->getUInt("lot")); model.position.x = result->getFloat("x");
model->position.x = result->getFloat("x"); model.position.y = result->getFloat("y");
model->position.y = result->getFloat("y"); model.position.z = result->getFloat("z");
model->position.z = result->getFloat("z"); model.rotation.w = result->getFloat("rw");
model->rotation.w = result->getFloat("rw"); model.rotation.x = result->getFloat("rx");
model->rotation.x = result->getFloat("rx"); model.rotation.y = result->getFloat("ry");
model->rotation.y = result->getFloat("ry"); model.rotation.z = result->getFloat("rz");
model->rotation.z = result->getFloat("rz"); model.ugcId = result->getUInt64("ugc_id");
model->ugcId = result->getUInt64("ugc_id"); model.behaviors[0] = result->getUInt64("behavior_1");
model->behaviors[0] = result->getUInt64("behavior_1"); model.behaviors[1] = result->getUInt64("behavior_2");
model->behaviors[1] = result->getUInt64("behavior_2"); model.behaviors[2] = result->getUInt64("behavior_3");
model->behaviors[2] = result->getUInt64("behavior_3"); model.behaviors[3] = result->getUInt64("behavior_4");
model->behaviors[3] = result->getUInt64("behavior_4"); model.behaviors[4] = result->getUInt64("behavior_5");
model->behaviors[4] = result->getUInt64("behavior_5");
} }
return model; return model;

View File

@@ -1,17 +1,5 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
IUgc::Model ReadModel(PreparedStmtResultSet& result) {
IUgc::Model model;
// 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("ugcID");
model.modelID = result->getUInt64("modelID");
return model;
}
std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) { std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) {
auto result = ExecuteSelect( auto result = ExecuteSelect(
"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;", "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;",
@@ -20,7 +8,14 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
std::vector<IUgc::Model> toReturn; std::vector<IUgc::Model> toReturn;
while (result->next()) { while (result->next()) {
toReturn.push_back(ReadModel(result)); IUgc::Model model;
// 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("ugcID");
model.modelID = result->getUInt64("modelID");
toReturn.push_back(std::move(model));
} }
return toReturn; return toReturn;
@@ -32,7 +27,14 @@ std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
std::vector<IUgc::Model> models; std::vector<IUgc::Model> models;
models.reserve(result->rowsCount()); models.reserve(result->rowsCount());
while (result->next()) { while (result->next()) {
models.push_back(ReadModel(result)); IUgc::Model model;
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"));
model.lxfmlData << blob->rdbuf();
models.push_back(std::move(model));
} }
return models; return models;
@@ -43,10 +45,10 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
} }
void MySQLDatabase::InsertNewUgcModel( void MySQLDatabase::InsertNewUgcModel(
std::stringstream& sd0Data, // cant be const sad std:: stringstream& sd0Data, // cant be const sad
const uint64_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const LWOOBJID characterId) { const uint32_t characterId) {
const std::istream stream(sd0Data.rdbuf()); const std::istream stream(sd0Data.rdbuf());
ExecuteInsert( ExecuteInsert(
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)", "INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)",
@@ -69,14 +71,3 @@ void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstrea
const std::istream stream(lxfml.rdbuf()); const std::istream stream(lxfml.rdbuf());
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
} }
std::optional<IUgc::Model> MySQLDatabase::GetUgcModel(const LWOOBJID ugcId) {
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 u.id = ?", ugcId);
std::optional<IUgc::Model> toReturn = std::nullopt;
if (result->next()) {
toReturn = ReadModel(result);
}
return toReturn;
}

Some files were not shown because too many files have changed in this diff Show More