mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-20 13:44:21 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1891955e2 | ||
|
|
7456d6b5c1 | ||
|
|
308412f46e | ||
|
|
56504d9447 | ||
|
|
ce9d4e823c | ||
|
|
0f17e1de3b | ||
|
|
c898356eba | ||
|
|
79bb48d3bc | ||
|
|
c0d055e66e | ||
|
|
7937951f7f | ||
|
|
0101933f5c | ||
|
|
90db1ac699 | ||
|
|
9f8d300340 | ||
|
|
1e9b18fa9d | ||
|
|
e5b8e5c6b7 | ||
|
|
707880b5fc | ||
|
|
90607bdd5c | ||
|
|
bb8f569354 | ||
|
|
93076dc36d | ||
|
|
a307f0601a | ||
|
|
045e097b13 | ||
|
|
ca0da9d3bf | ||
|
|
1d2de705fb | ||
|
|
a156a8fcba | ||
|
|
f6c9a27a2b | ||
|
|
8e09ffd6e8 | ||
|
|
0c1808686c | ||
|
|
4d6a624da2 | ||
|
|
4ab09cf1aa | ||
|
|
4ef9f43266 | ||
|
|
f3a5add038 | ||
|
|
f5d33a773a | ||
|
|
67bbe4c1f0 | ||
|
|
482ff82656 | ||
|
|
8061f512aa |
33
.github/workflows/build-and-push-docker.yml
vendored
33
.github/workflows/build-and-push-docker.yml
vendored
@@ -1,14 +1,14 @@
|
||||
name: CI
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- main
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- main
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -20,15 +20,21 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -36,21 +42,32 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
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={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
id: push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
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
|
||||
|
||||
78
.github/workflows/build-and-test.yml
vendored
78
.github/workflows/build-and-test.yml
vendored
@@ -3,6 +3,8 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
@@ -10,38 +12,51 @@ jobs:
|
||||
build-and-test:
|
||||
name: Build & Test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
continue-on-error: ${{ github.event_name == 'pull_request' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-2022, ubuntu-22.04, macos-15-intel ]
|
||||
include:
|
||||
- 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:
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Add msbuild to PATH (Windows only)
|
||||
if: ${{ matrix.os == 'windows-2022' }}
|
||||
uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330
|
||||
if: ${{ matrix.os == 'windows-2025' }}
|
||||
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3
|
||||
with:
|
||||
vs-version: '[17,18)'
|
||||
vs-version: '[18,19)'
|
||||
msbuild-architecture: x64
|
||||
- name: Install libssl and switch to XCode 15.2 (Mac Only)
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
run: |
|
||||
brew install openssl@3
|
||||
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
- name: Get CMake 3.x
|
||||
uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3
|
||||
with:
|
||||
cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
|
||||
cmakeVersion: "latest"
|
||||
- name: cmake
|
||||
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38
|
||||
uses: lukka/run-cmake@5d55ea7949e25f69f0ecb516d8d572297e03a956 # v10.9
|
||||
with:
|
||||
workflowPreset: "ci-${{matrix.os}}"
|
||||
workflowPreset: "${{ matrix.debug_preset }}"
|
||||
|
||||
- name: Extract Linux debug symbols
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: |
|
||||
find build -type f -name '*Server' | while read bin; do
|
||||
objcopy --only-keep-debug "$bin" "${bin}.debug"
|
||||
objcopy --strip-debug --add-gnu-debuglink="${bin}.debug" "$bin"
|
||||
done
|
||||
|
||||
- name: artifacts
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-${{matrix.os}}
|
||||
name: build-${{matrix.artifact}}
|
||||
path: |
|
||||
build/*/*Server*
|
||||
build/*/*.ini
|
||||
@@ -52,5 +67,30 @@ jobs:
|
||||
build/*/navmeshes/
|
||||
build/*/migrations/
|
||||
build/*/*.dcf
|
||||
!build/*/*.pdb
|
||||
!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
|
||||
|
||||
93
.github/workflows/canary.yml
vendored
Normal file
93
.github/workflows/canary.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
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 }}
|
||||
38
.github/workflows/pr-title-check.yml
vendored
Normal file
38
.github/workflows/pr-title-check.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
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
|
||||
70
.github/workflows/release.yml
vendored
Normal file
70
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
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
2
.gitignore
vendored
@@ -126,3 +126,5 @@ docker-compose.override.yml
|
||||
# CMake scripts
|
||||
!cmake/*
|
||||
!cmake/toolchains/*
|
||||
.mcp.json
|
||||
.claude/
|
||||
|
||||
@@ -16,10 +16,6 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"inherits": "default",
|
||||
"displayName": "[Multi] Windows (MSVC)",
|
||||
"description": "Set architecture to 64-bit (b/c RakNet)",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"generator": "Visual Studio 18 2026",
|
||||
"binaryDir": "${sourceDir}/build/msvc",
|
||||
"architecture": {
|
||||
"value": "x64"
|
||||
@@ -162,6 +162,13 @@
|
||||
"rhs": "Darwin"
|
||||
},
|
||||
"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": [
|
||||
@@ -255,7 +262,7 @@
|
||||
{
|
||||
"name": "macos-relwithdebinfo",
|
||||
"inherits": "default",
|
||||
"configurePreset": "macos",
|
||||
"configurePreset": "macos-relwithdebinfo",
|
||||
"displayName": "[RelWithDebInfo] MacOS",
|
||||
"description": "This preset is used to build in release mode with debug info on MacOS",
|
||||
"configuration": "RelWithDebInfo"
|
||||
@@ -374,7 +381,7 @@
|
||||
{
|
||||
"name": "macos-relwithdebinfo",
|
||||
"inherits": "default",
|
||||
"configurePreset": "macos",
|
||||
"configurePreset": "macos-relwithdebinfo",
|
||||
"displayName": "[RelWithDebInfo] MacOS",
|
||||
"description": "Runs all tests on a MacOS configuration",
|
||||
"configuration": "RelWithDebInfo"
|
||||
@@ -603,7 +610,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos"
|
||||
"name": "macos-relwithdebinfo"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
|
||||
35
cliff.toml
Normal file
35
cliff.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[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"
|
||||
@@ -80,7 +80,7 @@ else() # Build from source
|
||||
-DWITH_EXTERNAL_ZLIB=ON
|
||||
-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}
|
||||
-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\ -Wno-inconsistent-missing-override\ -include\ cstdint)
|
||||
else()
|
||||
set(MARIADB_EXTRA_CMAKE_ARGS
|
||||
-DCMAKE_C_FLAGS=-w # disable zlib warnings
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "MessageType/Chat.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "Game.h"
|
||||
#include "dConfig.h"
|
||||
#include "Logger.h"
|
||||
#include "eObjectBits.h"
|
||||
|
||||
@@ -72,8 +73,8 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int32_t MAX_IGNORES = 32;
|
||||
if (receiver.ignoredPlayers.size() > MAX_IGNORES) {
|
||||
const int32_t MAX_IGNORES = Game::config->GetValue("max_ignores", 32);
|
||||
if (receiver.ignoredPlayers.size() >= MAX_IGNORES) {
|
||||
LOG_DEBUG("Player %llu has too many ignores", playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -435,6 +435,11 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(channel);
|
||||
inStream.Read(size);
|
||||
if (size > MAX_MESSAGE_LENGTH) {
|
||||
LOG("Received a probably spoofed chat message, ignoring msg");
|
||||
return;
|
||||
}
|
||||
|
||||
inStream.IgnoreBytes(77);
|
||||
|
||||
LUWString message(size);
|
||||
@@ -479,6 +484,11 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
|
||||
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
|
||||
|
||||
inStream.Read(size);
|
||||
if (size > MAX_MESSAGE_LENGTH) {
|
||||
LOG("Received a probably spoofed chat message, ignoring msg");
|
||||
return;
|
||||
}
|
||||
|
||||
inStream.IgnoreBytes(77);
|
||||
|
||||
inStream.Read(LUReceiverName);
|
||||
|
||||
@@ -105,15 +105,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||
auto* team = TeamContainer::GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
}
|
||||
TeamContainer::RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
|
||||
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
|
||||
@@ -176,6 +168,7 @@ LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
|
||||
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) {
|
||||
return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Amf3.h"
|
||||
#include "StringifiedEnum.h"
|
||||
|
||||
/**
|
||||
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
|
||||
@@ -53,7 +54,7 @@ std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream)
|
||||
case eAmf::VectorObject:
|
||||
[[fallthrough]];
|
||||
case eAmf::Dictionary:
|
||||
throw marker;
|
||||
throw std::invalid_argument(StringifiedEnum::ToString(marker).data());
|
||||
default:
|
||||
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
|
||||
}
|
||||
@@ -88,6 +89,11 @@ 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
|
||||
length = length >> 1;
|
||||
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);
|
||||
inStream.Read(&value[0], length);
|
||||
// Empty strings are never sent by reference
|
||||
@@ -117,6 +123,12 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
|
||||
if (key.size() == 0) break;
|
||||
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
|
||||
for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
|
||||
arrayValue->Insert(i, Read(inStream));
|
||||
|
||||
@@ -52,8 +52,7 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
|
||||
|
||||
if (actualUncompressedSize != -1) {
|
||||
uint32_t previousSize = completeUncompressedModel.size();
|
||||
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()));
|
||||
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
|
||||
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()), actualUncompressedSize);
|
||||
} else {
|
||||
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
|
||||
break;
|
||||
|
||||
@@ -49,11 +49,10 @@ if (UNIX)
|
||||
elseif (WIN32)
|
||||
include(FetchContent)
|
||||
|
||||
# TODO Keep an eye on the zlib repository for an update to disable testing. Don't forget to update CMakePresets
|
||||
FetchContent_Declare(
|
||||
zlib
|
||||
URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip
|
||||
URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1
|
||||
URL https://github.com/madler/zlib/archive/refs/tags/v1.3.2.zip
|
||||
URL_HASH MD5=adbba6eef8960c3412818b2e241f46dc
|
||||
GIT_PROGRESS TRUE
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
@@ -62,12 +61,12 @@ elseif (WIN32)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||
# Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future
|
||||
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
|
||||
# Disable zlib tests
|
||||
set(ZLIB_BUILD_TESTING OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(zlib)
|
||||
|
||||
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 ()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
|
||||
@@ -308,8 +308,9 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
|
||||
for (const auto& t : std::filesystem::directory_iterator(folder)) {
|
||||
if (t.is_directory() || t.is_symlink()) continue;
|
||||
auto filename = t.path().filename().string();
|
||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
filenames.emplace(index, std::move(filename));
|
||||
// Ensure the file has a name in the format of xxxxxxxx_anything_goes_here.sql
|
||||
const auto migrationNumber = TryParse<uint32_t>(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
if (migrationNumber.has_value()) filenames.emplace(migrationNumber.value(), std::move(filename));
|
||||
}
|
||||
|
||||
// Now sort the map by the oldest migration.
|
||||
|
||||
@@ -205,6 +205,12 @@ namespace GeneralUtils {
|
||||
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>
|
||||
requires(!Numeric<T>)
|
||||
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
|
||||
@@ -237,6 +243,12 @@ namespace GeneralUtils {
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -258,6 +270,11 @@ namespace GeneralUtils {
|
||||
return z ? std::make_optional<T>(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The TryParse overload for handling NiPoint3 by passing a span of three strings
|
||||
* @param str The string vector representing the X, Y, and Z coordinates
|
||||
@@ -268,6 +285,11 @@ namespace GeneralUtils {
|
||||
return (str.size() == 3) ? TryParse<T>(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>
|
||||
std::u16string to_u16string(const T value) {
|
||||
return GeneralUtils::ASCIIToUTF16(std::to_string(value));
|
||||
|
||||
@@ -10,163 +10,151 @@
|
||||
#include <string_view>
|
||||
#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
|
||||
LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
|
||||
std::unique_ptr<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)
|
||||
if (format.empty() || format.length() <= 2) return nullptr;
|
||||
auto equalsPosition = format.find('=');
|
||||
// You can have an empty key, just make sure the type and value might exist
|
||||
if (equalsPosition == std::string::npos || equalsPosition == (format.size() - 1)) return nullptr;
|
||||
if (!format.empty() && format.length() > 2) {
|
||||
auto equalsPosition = format.find('=');
|
||||
// You can have an empty key, just make sure the type and value might exist
|
||||
if (equalsPosition != std::string::npos && equalsPosition != (format.size() - 1)) {
|
||||
|
||||
std::pair<LDFKey, LDFTypeAndValue> keyValue;
|
||||
keyValue.first = format.substr(0, equalsPosition);
|
||||
keyValue.second = format.substr(equalsPosition + 1, format.size());
|
||||
const std::string_view keyValue = format.substr(0, equalsPosition);
|
||||
const std::string_view typeAndValue = format.substr(equalsPosition + 1, format.size());
|
||||
|
||||
std::u16string key = GeneralUtils::ASCIIToUTF16(keyValue.first);
|
||||
const auto key = GeneralUtils::ASCIIToUTF16(keyValue);
|
||||
|
||||
auto colonPosition = keyValue.second.find(':');
|
||||
const auto colonPosition = typeAndValue.find(':');
|
||||
|
||||
// If : is the first thing after an =, then this is an invalid LDF since
|
||||
// we dont have a type to use.
|
||||
if (colonPosition == std::string::npos || colonPosition == 0) return nullptr;
|
||||
// If : is the first thing after an =, then this is an invalid LDF since
|
||||
// we dont have a type to use.
|
||||
if (colonPosition != std::string::npos && colonPosition != 0) {
|
||||
const std::string_view ldfType = typeAndValue.substr(0, colonPosition);
|
||||
const std::string_view ldfValue = typeAndValue.substr(colonPosition + 1, typeAndValue.size());
|
||||
|
||||
std::pair<LDFType, LDFValue> ldfTypeAndValue;
|
||||
ldfTypeAndValue.first = keyValue.second.substr(0, colonPosition);
|
||||
ldfTypeAndValue.second = keyValue.second.substr(colonPosition + 1, keyValue.second.size());
|
||||
// Only allow empty values for string values.
|
||||
if (!ldfValue.empty() || (ldfType == "0" /* UTF-16 */ || ldfType == "13" /* UTF-8 */)) {
|
||||
const eLDFType type = GeneralUtils::TryParse<eLDFType>(ldfType, LDF_TYPE_UNKNOWN);
|
||||
switch (type) {
|
||||
case LDF_TYPE_UTF_16: {
|
||||
std::u16string data = GeneralUtils::UTF8ToUTF16(ldfValue);
|
||||
toReturn.reset(new LDFData<std::u16string>(key, data));
|
||||
break;
|
||||
}
|
||||
|
||||
// Only allow empty values for string values.
|
||||
if (ldfTypeAndValue.second.size() == 0 && !(ldfTypeAndValue.first == "0" || ldfTypeAndValue.first == "13")) return nullptr;
|
||||
case LDF_TYPE_S32: {
|
||||
const auto data = GeneralUtils::TryParse<int32_t>(ldfValue);
|
||||
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());
|
||||
}
|
||||
|
||||
eLDFType type;
|
||||
char* storage;
|
||||
try {
|
||||
type = static_cast<eLDFType>(strtol(ldfTypeAndValue.first.data(), &storage, 10));
|
||||
} catch (std::exception) {
|
||||
LOG("Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data());
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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_FLOAT: {
|
||||
const auto data = GeneralUtils::TryParse<float>(ldfValue);
|
||||
if (data) {
|
||||
toReturn.reset(new LDFData<float>(key, data.value()));
|
||||
} else {
|
||||
LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfValue.data(), format.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());
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (parsed) toReturn.reset(new LDFData<uint32_t>(key, data));
|
||||
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_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();
|
||||
}
|
||||
}
|
||||
|
||||
case LDF_TYPE_U32:
|
||||
{
|
||||
uint32_t data;
|
||||
if (parsed) toReturn.reset(new LDFData<bool>(key, data));
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#ifndef __LDFFORMAT__H__
|
||||
#define __LDFFORMAT__H__
|
||||
#ifndef LDFFORMAT_H
|
||||
#define LDFFORMAT_H
|
||||
|
||||
// Custom Classes
|
||||
#include "dCommonVars.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
// C++
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
@@ -46,17 +47,17 @@ public:
|
||||
|
||||
virtual std::string GetValueAsString() const = 0;
|
||||
|
||||
virtual LDFBaseData* Copy() const = 0;
|
||||
virtual std::unique_ptr<LDFBaseData> Copy() const = 0;
|
||||
|
||||
/**
|
||||
* Given an input string, return the data as a LDF key.
|
||||
*/
|
||||
static LDFBaseData* DataFromString(const std::string_view& format);
|
||||
static std::unique_ptr<LDFBaseData> DataFromString(const std::string_view& format);
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class LDFData: public LDFBaseData {
|
||||
class LDFData : public LDFBaseData {
|
||||
private:
|
||||
std::u16string key;
|
||||
T value;
|
||||
@@ -164,8 +165,8 @@ public:
|
||||
return this->GetValueString();
|
||||
}
|
||||
|
||||
LDFBaseData* Copy() const override {
|
||||
return new LDFData<T>(key, value);
|
||||
std::unique_ptr<LDFBaseData> Copy() const override {
|
||||
return std::make_unique<LDFData<T>>(key, value);
|
||||
}
|
||||
|
||||
inline static const T Default = {};
|
||||
@@ -226,4 +227,89 @@ template<> inline std::string LDFData<LWOOBJID>::GetValueString() const { return
|
||||
|
||||
template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; }
|
||||
|
||||
#endif //!__LDFFORMAT__H__
|
||||
struct LwoNameValue {
|
||||
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
|
||||
|
||||
@@ -53,16 +53,21 @@ Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
try {
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
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;
|
||||
|
||||
@@ -1,105 +1,77 @@
|
||||
#include "Metrics.hpp"
|
||||
#include "Metrics.h"
|
||||
|
||||
#include "StringifiedEnum.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
std::unordered_map<MetricVariable, Metric*> Metrics::m_Metrics = {};
|
||||
std::vector<MetricVariable> Metrics::m_Variables = {
|
||||
MetricVariable::GameLoop,
|
||||
MetricVariable::PacketHandling,
|
||||
MetricVariable::UpdateEntities,
|
||||
MetricVariable::UpdateSpawners,
|
||||
MetricVariable::Physics,
|
||||
MetricVariable::UpdateReplica,
|
||||
MetricVariable::Ghosting,
|
||||
MetricVariable::CPUTime,
|
||||
MetricVariable::Sleep,
|
||||
MetricVariable::Frame,
|
||||
};
|
||||
namespace {
|
||||
std::unordered_map<MetricVariable, Metric> g_Metrics = {};
|
||||
std::vector<MetricVariable> g_Variables = {
|
||||
MetricVariable::GameLoop,
|
||||
MetricVariable::PacketHandling,
|
||||
MetricVariable::UpdateEntities,
|
||||
MetricVariable::UpdateSpawners,
|
||||
MetricVariable::Physics,
|
||||
MetricVariable::UpdateReplica,
|
||||
MetricVariable::Ghosting,
|
||||
MetricVariable::CPUTime,
|
||||
MetricVariable::Sleep,
|
||||
MetricVariable::Frame,
|
||||
};
|
||||
}
|
||||
|
||||
void Metrics::AddMeasurement(MetricVariable variable, int64_t value) {
|
||||
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;
|
||||
}
|
||||
auto& metric = g_Metrics[variable];
|
||||
|
||||
AddMeasurement(metric, value);
|
||||
}
|
||||
|
||||
void Metrics::AddMeasurement(Metric* metric, int64_t value) {
|
||||
const auto index = metric->measurementIndex;
|
||||
void Metrics::AddMeasurement(Metric& metric, int64_t value) {
|
||||
const auto index = metric.measurementIndex;
|
||||
|
||||
metric->measurements[index] = value;
|
||||
metric.measurements[index] = value;
|
||||
|
||||
if (metric->max == -1 || value > metric->max) {
|
||||
metric->max = value;
|
||||
} else if (metric->min == -1 || metric->min > value) {
|
||||
metric->min = value;
|
||||
if (metric.max == -1 || value > metric.max) {
|
||||
metric.max = value;
|
||||
} else if (metric.min == -1 || metric.min > value) {
|
||||
metric.min = value;
|
||||
}
|
||||
|
||||
if (metric->measurementSize < MAX_MEASURMENT_POINTS) {
|
||||
metric->measurementSize++;
|
||||
if (metric.measurementSize < MAX_MEASURMENT_POINTS) {
|
||||
metric.measurementSize++;
|
||||
}
|
||||
|
||||
metric->measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS;
|
||||
metric.measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS;
|
||||
}
|
||||
|
||||
const Metric* Metrics::GetMetric(MetricVariable variable) {
|
||||
const auto& iter = m_Metrics.find(variable);
|
||||
|
||||
if (iter == m_Metrics.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Metric* metric = iter->second;
|
||||
const Metric& Metrics::GetMetric(MetricVariable variable) {
|
||||
auto& metric = g_Metrics[variable];
|
||||
|
||||
int64_t average = 0;
|
||||
|
||||
for (size_t i = 0; i < metric->measurementSize; i++) {
|
||||
average += metric->measurements[i];
|
||||
for (size_t i = 0; i < metric.measurementSize; i++) {
|
||||
average += metric.measurements[i];
|
||||
}
|
||||
|
||||
average /= metric->measurementSize;
|
||||
average /= metric.measurementSize;
|
||||
|
||||
metric->average = average;
|
||||
metric.average = average;
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
void Metrics::StartMeasurement(MetricVariable variable) {
|
||||
const auto& iter = m_Metrics.find(variable);
|
||||
auto& metric = g_Metrics[variable];
|
||||
|
||||
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();
|
||||
metric.activeMeasurement = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Metrics::EndMeasurement(MetricVariable variable) {
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
const auto& iter = m_Metrics.find(variable);
|
||||
auto& metric = g_Metrics[variable];
|
||||
|
||||
if (iter == m_Metrics.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Metric* metric = iter->second;
|
||||
|
||||
const auto elapsed = end - metric->activeMeasurement;
|
||||
const auto elapsed = end - metric.activeMeasurement;
|
||||
|
||||
const auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
|
||||
|
||||
@@ -110,44 +82,12 @@ float Metrics::ToMiliseconds(int64_t nanoseconds) {
|
||||
return static_cast<float>(nanoseconds) / 1e6;
|
||||
}
|
||||
|
||||
std::string Metrics::MetricVariableToString(MetricVariable 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::string_view Metrics::MetricVariableToString(MetricVariable variable) {
|
||||
return StringifiedEnum::ToString(variable);
|
||||
}
|
||||
|
||||
const std::vector<MetricVariable>& Metrics::GetAllMetrics() {
|
||||
return m_Variables;
|
||||
}
|
||||
|
||||
void Metrics::Clear() {
|
||||
for (const auto& pair : m_Metrics) {
|
||||
delete pair.second;
|
||||
}
|
||||
|
||||
m_Metrics.clear();
|
||||
return g_Variables;
|
||||
}
|
||||
|
||||
/* RSS Memory utilities
|
||||
|
||||
48
dCommon/Metrics.h
Normal file
48
dCommon/Metrics.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#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();
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
#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;
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __NIPOINT3_H__
|
||||
#define __NIPOINT3_H__
|
||||
|
||||
#ifndef GLM_ENABLE_EXPERIMENTAL
|
||||
# define GLM_ENABLE_EXPERIMENTAL
|
||||
#endif
|
||||
/*!
|
||||
\file NiPoint3.hpp
|
||||
\brief Defines a point in space in XYZ coordinates
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef NIQUATERNION_H
|
||||
#define NIQUATERNION_H
|
||||
|
||||
#ifndef GLM_ENABLE_EXPERIMENTAL
|
||||
# define GLM_ENABLE_EXPERIMENTAL
|
||||
#endif
|
||||
// Custom Classes
|
||||
#include "NiPoint3.h"
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ Sd0::Sd0(std::istream& buffer) {
|
||||
uint32_t bufferSize = buffer.tellg();
|
||||
buffer.seekg(0, std::ios::beg);
|
||||
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);
|
||||
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
|
||||
if (!buffer.read(dataStart, bufferSize)) {
|
||||
@@ -71,6 +77,12 @@ Sd0::Sd0(std::istream& buffer) {
|
||||
|
||||
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);
|
||||
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
|
||||
if (!buffer.read(dataStart, chunkSize)) {
|
||||
@@ -95,6 +107,11 @@ void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
|
||||
startOffset, numToCopy,
|
||||
compressedChunk.data(), compressedChunk.size());
|
||||
|
||||
if (compressedSize == -1) {
|
||||
LOG("Failed to compress chunk, aborting");
|
||||
break;
|
||||
}
|
||||
|
||||
auto& chunk = m_Chunks.emplace_back();
|
||||
bool firstBuffer = m_Chunks.size() == 1;
|
||||
auto dataOffset = GetDataOffset(firstBuffer);
|
||||
@@ -119,6 +136,12 @@ std::string Sd0::GetAsStringUncompressed() const {
|
||||
auto dataOffset = GetDataOffset(first);
|
||||
first = false;
|
||||
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();
|
||||
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
|
||||
@@ -128,6 +151,13 @@ std::string Sd0::GetAsStringUncompressed() const {
|
||||
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
|
||||
error);
|
||||
|
||||
if (uncompressedSize == -1) {
|
||||
LOG("Failed to decompress chunk, aborting");
|
||||
toReturn = "";
|
||||
totalSize = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
totalSize += uncompressedSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "zlib.h"
|
||||
|
||||
namespace ZCompression {
|
||||
int32_t GetMaxCompressedLength(int32_t nLenSrc) {
|
||||
int32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
|
||||
uint32_t GetMaxCompressedLength(uint32_t nLenSrc) {
|
||||
uint32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
|
||||
return (nLenSrc + 6 + (n16kBlocks * 5));
|
||||
}
|
||||
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst) {
|
||||
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst) {
|
||||
z_stream zInfo = { 0 };
|
||||
zInfo.total_in = zInfo.avail_in = nLenSrc;
|
||||
zInfo.total_out = zInfo.avail_out = nLenDst;
|
||||
@@ -27,7 +27,7 @@ namespace ZCompression {
|
||||
return(nRet);
|
||||
}
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) {
|
||||
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr) {
|
||||
// Get the size of the decompressed data
|
||||
z_stream zInfo = { 0 };
|
||||
zInfo.total_in = zInfo.avail_in = nLenSrc;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZCompression {
|
||||
int32_t GetMaxCompressedLength(int32_t nLenSrc);
|
||||
uint32_t GetMaxCompressedLength(uint32_t nLenSrc);
|
||||
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
|
||||
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst);
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
|
||||
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "zlib.h"
|
||||
|
||||
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) {
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
@@ -25,7 +25,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / "..");
|
||||
@@ -34,7 +34,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / ".." / "..");
|
||||
@@ -54,15 +54,15 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
switch (m_AssetBundleType) {
|
||||
case eAssetBundleType::Packed: {
|
||||
this->LoadPackIndex();
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::None:
|
||||
[[fallthrough]];
|
||||
case eAssetBundleType::Unpacked: {
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::Packed: {
|
||||
this->LoadPackIndex();
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::None:
|
||||
[[fallthrough]];
|
||||
case eAssetBundleType::Unpacked: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ bool AssetManager::HasFile(std::string fixedName) const {
|
||||
std::replace(fixedName.begin(), fixedName.end(), '\\', '/');
|
||||
if (std::filesystem::exists(m_ResPath / fixedName)) return true;
|
||||
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false;
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked || !m_PackIndex) return false;
|
||||
|
||||
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
|
||||
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
|
||||
@@ -145,8 +145,12 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
|
||||
}
|
||||
|
||||
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
|
||||
const bool success = pack.ReadFileFromPack(crc, data, len);
|
||||
|
||||
bool success = false;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ bool Pack::HasFile(const uint32_t crc) 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
|
||||
|
||||
PackRecord pkRecord{};
|
||||
@@ -65,16 +66,21 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
|
||||
bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0;
|
||||
auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize;
|
||||
|
||||
FILE* file;
|
||||
FILE* file = nullptr;
|
||||
#ifdef _WIN32
|
||||
fopen_s(&file, m_FilePath.string().c_str(), "rb");
|
||||
fopen_s(&file, pathStr.c_str(), "rb");
|
||||
#elif __APPLE__
|
||||
// macOS has 64bit file IO by default
|
||||
file = fopen(m_FilePath.string().c_str(), "rb");
|
||||
file = fopen(pathStr.c_str(), "rb");
|
||||
#else
|
||||
file = fopen64(m_FilePath.string().c_str(), "rb");
|
||||
file = fopen64(pathStr.c_str(), "rb");
|
||||
#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);
|
||||
|
||||
if (!isCompressed) {
|
||||
@@ -102,14 +108,18 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
|
||||
int32_t readInData = fread(&size, sizeof(uint32_t), 1, file);
|
||||
pos += 4; // Move pointer position 4 to the right
|
||||
|
||||
char* chunk = static_cast<char*>(malloc(size));
|
||||
int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file);
|
||||
std::unique_ptr<char[]> chunk(new char[size]);
|
||||
int32_t readInData2 = fread(chunk.get(), sizeof(int8_t), size, file);
|
||||
pos += size; // Move pointer position the amount of bytes read to the right
|
||||
|
||||
int32_t err;
|
||||
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, 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);
|
||||
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;
|
||||
|
||||
@@ -84,3 +84,7 @@ void dConfig::ProcessLine(const std::string& line) {
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
class dConfig {
|
||||
public:
|
||||
dConfig(const std::string& filepath);
|
||||
@@ -22,6 +24,14 @@ public:
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -43,3 +53,9 @@ private:
|
||||
std::vector<std::function<void()>> m_ConfigHandlers;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
// 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.
|
||||
#define FRAMES_TO_MS(x) 1000 / x
|
||||
#define MS_TO_FRAMES(x) 1000 / x
|
||||
#define FRAMES_TO_MS(x) (1000 / (x))
|
||||
#define MS_TO_FRAMES(x) (1000 / (x))
|
||||
|
||||
//=========== FRAME TIMINGS ===========
|
||||
constexpr uint32_t highFramerate = 60;
|
||||
@@ -58,6 +58,7 @@ constexpr LWOCLONEID LWOCLONEID_INVALID = -1; //!< Invalid LWOCLONEID
|
||||
constexpr LWOINSTANCEID LWOINSTANCEID_INVALID = -1; //!< Invalid LWOINSTANCEID
|
||||
constexpr LWOMAPID LWOMAPID_INVALID = -1; //!< Invalid LWOMAPID
|
||||
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;
|
||||
|
||||
@@ -110,18 +111,6 @@ private:
|
||||
|
||||
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 {
|
||||
public:
|
||||
bool isOnline = false;
|
||||
|
||||
@@ -20,7 +20,8 @@ enum class eCharacterVersion : uint32_t {
|
||||
NJ_JAYMISSIONS,
|
||||
NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories
|
||||
PET_IDS, // Fixes pet ids in player inventories
|
||||
UP_TO_DATE, // will become INVENTORY_PERSISTENT_IDS
|
||||
INVENTORY_PERSISTENT_IDS, // Fixes racing meta missions
|
||||
UP_TO_DATE, // will become RACING_META_MISSIONS
|
||||
};
|
||||
|
||||
#endif //!__ECHARACTERVERSION__H__
|
||||
|
||||
@@ -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
|
||||
if (amountSplit.size() == 2) {
|
||||
currencies.insert({
|
||||
std::stoull(amountSplit[0]),
|
||||
std::stoi(amountSplit[1])
|
||||
GeneralUtils::TryParse<LOT>(amountSplit[0], LOT_NULL),
|
||||
GeneralUtils::TryParse<uint32_t>(amountSplit[1], 0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,13 +93,14 @@ std::vector<CDMissions> CDMissionsTable::Query(std::function<bool(CDMissions)> p
|
||||
}
|
||||
|
||||
const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const {
|
||||
const CDMissions* toReturn = &Default;
|
||||
for (const auto& entry : GetEntries()) {
|
||||
if (entry.id == missionID) {
|
||||
return const_cast<CDMissions*>(&entry);
|
||||
toReturn = &entry;
|
||||
}
|
||||
}
|
||||
|
||||
return &Default;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const {
|
||||
|
||||
@@ -66,6 +66,7 @@ public:
|
||||
// Queries the table with a custom "where" clause
|
||||
std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate);
|
||||
|
||||
// Cannot be null.
|
||||
const CDMissions* GetPtrByMissionID(uint32_t missionID) const;
|
||||
|
||||
const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const;
|
||||
|
||||
@@ -38,3 +38,11 @@ std::vector<CDObjectSkills> CDObjectSkillsTable::Query(std::function<bool(CDObje
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
#include "CDTable.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct CDObjectSkills {
|
||||
uint32_t objectTemplate; //!< The LOT of the item
|
||||
uint32_t skillID; //!< The Skill ID of the object
|
||||
uint32_t castOnType; //!< ???
|
||||
uint32_t AICombatWeight; //!< ???
|
||||
int32_t AICombatWeight; //!< ???
|
||||
};
|
||||
|
||||
class CDObjectSkillsTable : public CDTable<CDObjectSkillsTable, std::vector<CDObjectSkills>> {
|
||||
@@ -17,5 +18,6 @@ public:
|
||||
void LoadValuesFromDatabase();
|
||||
// Queries the table with a custom "where" clause
|
||||
std::vector<CDObjectSkills> Query(std::function<bool(CDObjectSkills)> predicate);
|
||||
std::vector<CDObjectSkills> Get(const LOT lot) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ const CDObjects& CDObjectsTable::GetByID(const uint32_t lot) {
|
||||
entry.name = tableData.getStringField("name", "");
|
||||
UNUSED(entry.placeable = tableData.getIntField("placeable", -1));
|
||||
entry.type = tableData.getStringField("type", "");
|
||||
UNUSED(ntry.description = tableData.getStringField(4, ""));
|
||||
UNUSED(entry.description = tableData.getStringField(4, ""));
|
||||
UNUSED(entry.localize = tableData.getIntField("localize", -1));
|
||||
UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1));
|
||||
UNUSED(entry.displayName = tableData.getStringField("displayName", ""));
|
||||
|
||||
@@ -56,7 +56,7 @@ CDRailActivatorComponent CDRailActivatorComponentTable::GetEntryByID(int32_t id)
|
||||
std::pair<uint32_t, std::u16string> CDRailActivatorComponentTable::EffectPairFromString(std::string& str) {
|
||||
const auto split = GeneralUtils::SplitString(str, ':');
|
||||
if (split.size() == 2) {
|
||||
return { std::stoi(split.at(0)), GeneralUtils::ASCIIToUTF16(split.at(1)) };
|
||||
return { GeneralUtils::TryParse(split.at(0), 0), GeneralUtils::ASCIIToUTF16(split.at(1)) };
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
};
|
||||
|
||||
struct PropertyEntranceResult {
|
||||
// This is the number of entries that are in the query IF it were ran without a limit.
|
||||
int32_t totalEntriesMatchingQuery{};
|
||||
// The entries that match the query. This should only contain up to 12 entries.
|
||||
std::vector<IProperty::Info> entries;
|
||||
@@ -48,7 +49,7 @@ public:
|
||||
// Get the properties for the given property lookup params.
|
||||
// This is expected to return a result set of up to 12 properties
|
||||
// so as not to transfer too much data at once.
|
||||
virtual std::optional<IProperty::PropertyEntranceResult> GetProperties(const PropertyLookup& params) = 0;
|
||||
virtual IProperty::PropertyEntranceResult GetProperties(const PropertyLookup& params) = 0;
|
||||
|
||||
// Update the property moderation info for the given property id.
|
||||
virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0;
|
||||
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID characterId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
|
||||
|
||||
@@ -48,7 +48,7 @@ std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId
|
||||
}
|
||||
|
||||
std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
|
||||
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
|
||||
if (!res->next()) {
|
||||
return std::nullopt;
|
||||
@@ -57,6 +57,7 @@ std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
|
||||
MailInfo toReturn;
|
||||
toReturn.itemLOT = res->getInt("attachment_lot");
|
||||
toReturn.itemCount = res->getInt("attachment_count");
|
||||
toReturn.receiverId = res->getUInt64("receiver_id");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(PreparedStmtResultSet& result) {
|
||||
return info;
|
||||
}
|
||||
|
||||
std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||
std::optional<IProperty::PropertyEntranceResult> result;
|
||||
IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||
IProperty::PropertyEntranceResult result;
|
||||
std::string query;
|
||||
PreparedStmtResultSet properties;
|
||||
|
||||
@@ -73,8 +73,7 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
||||
params.playerId
|
||||
);
|
||||
if (count->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||
result.totalEntriesMatchingQuery = count->getUInt("count");
|
||||
}
|
||||
} else {
|
||||
if (params.sortChoice == SORT_TYPE_REPUTATION) {
|
||||
@@ -127,14 +126,12 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
|
||||
params.playerSort
|
||||
);
|
||||
if (count->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count->getUInt("count");
|
||||
result.totalEntriesMatchingQuery = count->getUInt("count");
|
||||
}
|
||||
}
|
||||
|
||||
while (properties->next()) {
|
||||
if (!result) result = IProperty::PropertyEntranceResult();
|
||||
result->entries.push_back(ReadPropertyInfo(properties));
|
||||
result.entries.push_back(ReadPropertyInfo(properties));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID characterId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
|
||||
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
|
||||
@@ -170,91 +170,91 @@ private:
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) {
|
||||
LOG("%s", param.data());
|
||||
LOG_DEBUG("%s", param.data());
|
||||
stmt.bind(index, param.data());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) {
|
||||
LOG("%s", param);
|
||||
LOG_DEBUG("%s", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) {
|
||||
LOG("%s", param.c_str());
|
||||
LOG_DEBUG("%s", param.c_str());
|
||||
stmt.bind(index, param.c_str());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) {
|
||||
LOG("%u", param);
|
||||
LOG_DEBUG("%u", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) {
|
||||
LOG("%d", param);
|
||||
LOG_DEBUG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) {
|
||||
LOG("%u", param);
|
||||
LOG_DEBUG("%u", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) {
|
||||
LOG("%d", param);
|
||||
LOG_DEBUG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) {
|
||||
LOG("%u", param);
|
||||
LOG_DEBUG("%u", param);
|
||||
stmt.bind(index, static_cast<int32_t>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) {
|
||||
LOG("%d", param);
|
||||
LOG_DEBUG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) {
|
||||
LOG("%llu", param);
|
||||
LOG_DEBUG("%llu", param);
|
||||
stmt.bind(index, static_cast<sqlite_int64>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) {
|
||||
LOG("%llu", param);
|
||||
LOG_DEBUG("%llu", param);
|
||||
stmt.bind(index, static_cast<sqlite_int64>(param));
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const float param) {
|
||||
LOG("%f", param);
|
||||
LOG_DEBUG("%f", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const double param) {
|
||||
LOG("%f", param);
|
||||
LOG_DEBUG("%f", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) {
|
||||
LOG("%d", param);
|
||||
LOG_DEBUG("%d", param);
|
||||
stmt.bind(index, param);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) {
|
||||
LOG("Blob");
|
||||
LOG_DEBUG("Blob");
|
||||
// This is the one time you will ever see me use const_cast.
|
||||
std::stringstream stream;
|
||||
stream << param->rdbuf();
|
||||
@@ -264,10 +264,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* p
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
|
||||
if (param) {
|
||||
LOG("%d", param.value());
|
||||
LOG_DEBUG("%d", param.value());
|
||||
stmt.bind(index, static_cast<int>(param.value()));
|
||||
} else {
|
||||
LOG("Null");
|
||||
LOG_DEBUG("Null");
|
||||
stmt.bindNull(index);
|
||||
}
|
||||
}
|
||||
@@ -275,10 +275,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<u
|
||||
template<>
|
||||
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<LWOOBJID> param) {
|
||||
if (param) {
|
||||
LOG("%d", param.value());
|
||||
LOG_DEBUG("%d", param.value());
|
||||
stmt.bind(index, static_cast<sqlite_int64>(param.value()));
|
||||
} else {
|
||||
LOG("Null");
|
||||
LOG_DEBUG("Null");
|
||||
stmt.bindNull(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const LWOOBJID characterI
|
||||
}
|
||||
|
||||
std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
|
||||
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId);
|
||||
|
||||
if (res.eof()) {
|
||||
return std::nullopt;
|
||||
@@ -56,6 +56,7 @@ std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
|
||||
MailInfo toReturn;
|
||||
toReturn.itemLOT = res.getIntField("attachment_lot");
|
||||
toReturn.itemCount = res.getIntField("attachment_count");
|
||||
toReturn.receiverId = res.getInt64Field("receiver_id");
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(CppSQLite3Query& propertyEntry) {
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||
std::optional<IProperty::PropertyEntranceResult> result;
|
||||
IProperty::PropertyEntranceResult SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
|
||||
IProperty::PropertyEntranceResult result;
|
||||
std::string query;
|
||||
std::pair<CppSQLite3Statement, CppSQLite3Query> propertiesRes;
|
||||
|
||||
@@ -73,8 +73,7 @@ std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(c
|
||||
params.playerId
|
||||
);
|
||||
if (!count.eof()) {
|
||||
result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count.getIntField("count");
|
||||
result.totalEntriesMatchingQuery = count.getIntField("count");
|
||||
}
|
||||
} else {
|
||||
if (params.sortChoice == SORT_TYPE_REPUTATION) {
|
||||
@@ -127,15 +126,13 @@ std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(c
|
||||
params.playerSort
|
||||
);
|
||||
if (!count.eof()) {
|
||||
result = IProperty::PropertyEntranceResult();
|
||||
result->totalEntriesMatchingQuery = count.getIntField("count");
|
||||
result.totalEntriesMatchingQuery = count.getIntField("count");
|
||||
}
|
||||
}
|
||||
|
||||
auto& [_, properties] = propertiesRes;
|
||||
if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult();
|
||||
while (!properties.eof()) {
|
||||
result->entries.push_back(ReadPropertyInfo(properties));
|
||||
result.entries.push_back(ReadPropertyInfo(properties));
|
||||
properties.nextRow();
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class TestSQLDatabase : public GameDatabase {
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID behaviorId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
|
||||
IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
|
||||
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; };
|
||||
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override { return {}; };
|
||||
|
||||
@@ -198,11 +198,12 @@ Entity::~Entity() {
|
||||
}
|
||||
|
||||
void Entity::Initialize() {
|
||||
RegisterMsg<GameMessages::RequestServerObjectInfo>(this, &Entity::MsgRequestServerObjectInfo);
|
||||
RegisterMsg<GameMessages::DropClientLoot>(this, &Entity::MsgDropClientLoot);
|
||||
RegisterMsg<GameMessages::GetFactionTokenType>(this, &Entity::MsgGetFactionTokenType);
|
||||
RegisterMsg<GameMessages::PickupItem>(this, &Entity::MsgPickupItem);
|
||||
RegisterMsg<GameMessages::ChildRemoved>(this, &Entity::MsgChildRemoved);
|
||||
RegisterMsg(&Entity::MsgRequestServerObjectInfo);
|
||||
RegisterMsg(&Entity::MsgDropClientLoot);
|
||||
RegisterMsg(&Entity::MsgGetFactionTokenType);
|
||||
RegisterMsg(&Entity::MsgPickupItem);
|
||||
RegisterMsg(&Entity::MsgChildRemoved);
|
||||
RegisterMsg(&Entity::MsgGetFlag);
|
||||
/**
|
||||
* Setup trigger
|
||||
*/
|
||||
@@ -426,7 +427,7 @@ void Entity::Initialize() {
|
||||
comp->SetMaxArmor(destCompData[0].armor);
|
||||
comp->SetDeathBehavior(destCompData[0].death_behavior);
|
||||
|
||||
comp->SetIsSmashable(destCompData[0].isSmashable);
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() || destCompData[0].isSmashable);
|
||||
|
||||
comp->SetLootMatrixID(destCompData[0].LootMatrixIndex);
|
||||
comp->SetCurrencyIndex(destCompData[0].CurrencyIndex);
|
||||
@@ -948,13 +949,13 @@ void Entity::SetGMLevel(eGameMasterLevel value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Entity::WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const {
|
||||
void Entity::WriteLDFData(const LwoNameValue& ldf, RakNet::BitStream& outBitStream) const {
|
||||
RakNet::BitStream settingStream;
|
||||
int32_t numberOfValidKeys = ldf.size();
|
||||
int32_t numberOfValidKeys = ldf.values.size();
|
||||
|
||||
// Writing keys value pairs the client does not expect to receive or interpret will result in undefined behavior,
|
||||
// so we need to filter out any keys that are not valid and fix the number of valid keys to be correct.
|
||||
for (LDFBaseData* data : ldf) {
|
||||
for (const auto& data : ldf.values | std::views::values) {
|
||||
if (data && data->GetValueType() != eLDFType::LDF_TYPE_UNKNOWN) {
|
||||
data->WriteToPacket(settingStream);
|
||||
} else {
|
||||
@@ -986,16 +987,16 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke
|
||||
const auto& syncLDF = GetVar<std::vector<std::u16string>>(u"syncLDF");
|
||||
|
||||
// Only sync for models.
|
||||
if (!m_Settings.empty() && (GetComponent<ModelComponent>() && !GetComponent<PetComponent>())) {
|
||||
if (!m_Settings.values.empty() && (GetComponent<ModelComponent>() && !GetComponent<PetComponent>())) {
|
||||
outBitStream.Write1(); // Has ldf data
|
||||
WriteLDFData(m_Settings, outBitStream);
|
||||
} else if (!syncLDF.empty()) {
|
||||
// Find all the ldf data we need to write
|
||||
std::vector<LDFBaseData*> ldfData;
|
||||
ldfData.reserve(m_Settings.size());
|
||||
LwoNameValue ldfData;
|
||||
|
||||
for (const auto& data : syncLDF) {
|
||||
ldfData.push_back(GetVarData(data));
|
||||
const auto* toInsert = GetVarData(data);
|
||||
if (toInsert) ldfData.values.insert_or_assign(data, toInsert->Copy());
|
||||
}
|
||||
|
||||
outBitStream.Write1(); // Has ldf data
|
||||
@@ -1612,26 +1613,10 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
|
||||
else Game::entityManager->DestroyEntity(this);
|
||||
}
|
||||
|
||||
const auto& grpNameQBShowBricks = GetVar<std::string>(u"grpNameQBShowBricks");
|
||||
|
||||
const auto& grpNameQBShowBricks = GetVarAsString(u"grpNameQBShowBricks");
|
||||
if (!grpNameQBShowBricks.empty()) {
|
||||
auto spawners = Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks);
|
||||
|
||||
Spawner* spawner = nullptr;
|
||||
|
||||
if (!spawners.empty()) {
|
||||
spawner = spawners[0];
|
||||
} else {
|
||||
spawners = Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks);
|
||||
|
||||
if (!spawners.empty()) {
|
||||
spawner = spawners[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (spawner != nullptr) {
|
||||
spawner->Spawn();
|
||||
}
|
||||
for (auto* const spawner : Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks)) if (spawner) spawner->Spawn();
|
||||
for (auto* const spawner : Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks)) if (spawner) spawner->Spawn();
|
||||
}
|
||||
|
||||
// Track a player being smashed
|
||||
@@ -2045,13 +2030,7 @@ void Entity::SetI64(const std::u16string& name, const int64_t value) {
|
||||
}
|
||||
|
||||
bool Entity::HasVar(const std::u16string& name) const {
|
||||
for (auto* data : m_Settings) {
|
||||
if (data->GetKey() == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return m_Settings.values.contains(name);
|
||||
}
|
||||
|
||||
uint16_t Entity::GetNetworkId() const {
|
||||
@@ -2083,24 +2062,13 @@ void Entity::SendNetworkVar(const std::string& data, const SystemAddress& sysAdd
|
||||
GameMessages::SendSetNetworkScriptVar(this, sysAddr, data);
|
||||
}
|
||||
|
||||
LDFBaseData* Entity::GetVarData(const std::u16string& name) const {
|
||||
for (auto* data : m_Settings) {
|
||||
if (data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->GetKey() != name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
const LDFBaseData* const Entity::GetVarData(const std::u16string& name) const {
|
||||
const auto itr = m_Settings.values.find(name);
|
||||
return itr != m_Settings.values.cend() ? itr->second.get() : nullptr;
|
||||
}
|
||||
|
||||
std::string Entity::GetVarAsString(const std::u16string& name) const {
|
||||
auto* data = GetVarData(name);
|
||||
const auto* const data = GetVarData(name);
|
||||
return data ? data->GetValueAsString() : "";
|
||||
}
|
||||
|
||||
@@ -2251,8 +2219,7 @@ void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameM
|
||||
m_MsgHandlers.emplace(msgId, handler);
|
||||
}
|
||||
|
||||
bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
||||
auto& requestInfo = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
|
||||
bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& requestInfo) {
|
||||
AMFArrayValue response;
|
||||
response.Insert("visible", true);
|
||||
response.Insert("objectID", std::to_string(m_ObjectID));
|
||||
@@ -2277,7 +2244,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
||||
}
|
||||
|
||||
auto& configData = objectInfo.PushDebug("Config Data");
|
||||
for (const auto config : m_Settings) {
|
||||
for (const auto& config : m_Settings.values | std::views::values) {
|
||||
configData.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString();
|
||||
}
|
||||
|
||||
@@ -2288,9 +2255,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Entity::MsgDropClientLoot(GameMessages::GameMsg& msg) {
|
||||
auto& dropLootMsg = static_cast<GameMessages::DropClientLoot&>(msg);
|
||||
|
||||
bool Entity::MsgDropClientLoot(GameMessages::DropClientLoot& dropLootMsg) {
|
||||
if (dropLootMsg.item != LOT_NULL && dropLootMsg.item != 0) {
|
||||
Loot::Info info{
|
||||
.id = dropLootMsg.lootID,
|
||||
@@ -2307,13 +2272,11 @@ bool Entity::MsgDropClientLoot(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Entity::MsgGetFlag(GameMessages::GameMsg& msg) {
|
||||
auto& flagMsg = static_cast<GameMessages::GetFlag&>(msg);
|
||||
bool Entity::MsgGetFlag(GameMessages::GetFlag& flagMsg) {
|
||||
if (m_Character) flagMsg.flag = m_Character->GetPlayerFlag(flagMsg.flagID);
|
||||
return true;
|
||||
}
|
||||
bool Entity::MsgGetFactionTokenType(GameMessages::GameMsg& msg) {
|
||||
auto& tokenMsg = static_cast<GameMessages::GetFactionTokenType&>(msg);
|
||||
bool Entity::MsgGetFactionTokenType(GameMessages::GetFactionTokenType& tokenMsg) {
|
||||
GameMessages::GetFlag getFlagMsg{};
|
||||
|
||||
getFlagMsg.flagID = ePlayerFlag::ASSEMBLY_FACTION;
|
||||
@@ -2336,8 +2299,7 @@ bool Entity::MsgGetFactionTokenType(GameMessages::GameMsg& msg) {
|
||||
return tokenMsg.tokenType != LOT_NULL;
|
||||
}
|
||||
|
||||
bool Entity::MsgPickupItem(GameMessages::GameMsg& msg) {
|
||||
auto& pickupItemMsg = static_cast<GameMessages::PickupItem&>(msg);
|
||||
bool Entity::MsgPickupItem(GameMessages::PickupItem& pickupItemMsg) {
|
||||
if (GetObjectID() == pickupItemMsg.lootOwnerID) {
|
||||
PickupItem(pickupItemMsg.lootID);
|
||||
} else {
|
||||
@@ -2358,7 +2320,7 @@ bool Entity::MsgPickupItem(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Entity::MsgChildRemoved(GameMessages::GameMsg& msg) {
|
||||
GetScript()->OnChildRemoved(*this, static_cast<GameMessages::ChildRemoved&>(msg));
|
||||
bool Entity::MsgChildRemoved(GameMessages::ChildRemoved& msg) {
|
||||
GetScript()->OnChildRemoved(*this, msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
176
dGame/Entity.h
176
dGame/Entity.h
@@ -20,6 +20,12 @@ namespace GameMessages {
|
||||
struct ShootingGalleryFire;
|
||||
struct ChildLoaded;
|
||||
struct PlayerResurrectionFinished;
|
||||
struct RequestServerObjectInfo;
|
||||
struct DropClientLoot;
|
||||
struct GetFlag;
|
||||
struct GetFactionTokenType;
|
||||
struct PickupItem;
|
||||
struct ChildRemoved;
|
||||
};
|
||||
|
||||
namespace MessageType {
|
||||
@@ -91,9 +97,9 @@ public:
|
||||
|
||||
LWOOBJID GetSpawnerID() const { return m_SpawnerID; }
|
||||
|
||||
const std::vector<LDFBaseData*>& GetSettings() const { return m_Settings; }
|
||||
const LwoNameValue& GetSettings() const { return m_Settings; }
|
||||
|
||||
const std::vector<LDFBaseData*>& GetNetworkSettings() const { return m_NetworkSettings; }
|
||||
const LwoNameValue& GetNetworkSettings() const { return m_NetworkSettings; }
|
||||
|
||||
bool GetIsDead() const;
|
||||
|
||||
@@ -106,6 +112,7 @@ public:
|
||||
|
||||
uint16_t GetNetworkId() const;
|
||||
|
||||
// Cannot return nullptr.
|
||||
Entity* GetOwner() const;
|
||||
|
||||
const NiPoint3& GetDefaultPosition() const;
|
||||
@@ -175,12 +182,12 @@ public:
|
||||
|
||||
void AddComponent(eReplicaComponentType componentId, Component* component);
|
||||
|
||||
bool MsgRequestServerObjectInfo(GameMessages::GameMsg& msg);
|
||||
bool MsgDropClientLoot(GameMessages::GameMsg& msg);
|
||||
bool MsgGetFlag(GameMessages::GameMsg& msg);
|
||||
bool MsgGetFactionTokenType(GameMessages::GameMsg& msg);
|
||||
bool MsgPickupItem(GameMessages::GameMsg& msg);
|
||||
bool MsgChildRemoved(GameMessages::GameMsg& msg);
|
||||
bool MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& msg);
|
||||
bool MsgDropClientLoot(GameMessages::DropClientLoot& msg);
|
||||
bool MsgGetFlag(GameMessages::GetFlag& msg);
|
||||
bool MsgGetFactionTokenType(GameMessages::GetFactionTokenType& msg);
|
||||
bool MsgPickupItem(GameMessages::PickupItem& msg);
|
||||
bool MsgChildRemoved(GameMessages::ChildRemoved& msg);
|
||||
|
||||
// This is expceted to never return nullptr, an assert checks this.
|
||||
CppScripts::Script* const GetScript() const;
|
||||
@@ -305,6 +312,12 @@ public:
|
||||
template<typename T>
|
||||
void SetNetworkVar(const std::u16string& name, std::vector<T> value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
template<typename T>
|
||||
void SetNetworkVar(const std::string& name, T value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
template<typename T>
|
||||
LwoNameValue::ValueType::iterator InsertNetworkVar(const std::u16string& name, T value);
|
||||
|
||||
template<typename T>
|
||||
T GetNetworkVar(const std::u16string& name);
|
||||
|
||||
@@ -317,11 +330,6 @@ public:
|
||||
template<typename ComponentType, typename... VaArgs>
|
||||
ComponentType* AddComponent(VaArgs... args);
|
||||
|
||||
/**
|
||||
* Get the LDF data.
|
||||
*/
|
||||
LDFBaseData* GetVarData(const std::u16string& name) const;
|
||||
|
||||
/**
|
||||
* Get the LDF value and convert it to a string.
|
||||
*/
|
||||
@@ -343,14 +351,19 @@ public:
|
||||
|
||||
bool HandleMsg(GameMessages::GameMsg& msg) const;
|
||||
|
||||
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
|
||||
RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void RegisterMsg(auto* self, const auto handler) {
|
||||
T msg;
|
||||
RegisterMsg(msg.msgId, self, handler);
|
||||
// Provided a function that has a derived GameMessage as its only argument and returns a boolean,
|
||||
// this will register it as a handler for that message type. Casting is done automatically to the type
|
||||
// of the message in the first argument. This object is expected to exist as long as the handler can be called.
|
||||
template<typename DerivedGameMsg>
|
||||
inline void RegisterMsg(bool (Entity::* handler)(DerivedGameMsg&)) {
|
||||
static_assert(std::is_base_of_v<GameMessages::GameMsg, DerivedGameMsg>, "DerivedGameMsg must inherit from GameMsg");
|
||||
const auto boundFunction = std::bind(handler, this, std::placeholders::_1);
|
||||
// This is the actual function that will be registered, which casts the base GameMsg to the derived type
|
||||
const auto castWrapper = [boundFunction](GameMessages::GameMsg& msg) {
|
||||
return boundFunction(static_cast<DerivedGameMsg&>(msg));
|
||||
};
|
||||
DerivedGameMsg msg;
|
||||
RegisterMsg(msg.msgId, castWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,13 +372,20 @@ public:
|
||||
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
|
||||
|
||||
private:
|
||||
void WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const;
|
||||
|
||||
/**
|
||||
* Get the LDF data.
|
||||
*/
|
||||
const LDFBaseData* const GetVarData(const std::u16string& name) const;
|
||||
template<typename T>
|
||||
LwoNameValue::ValueType::iterator InsertLnvData(LwoNameValue& lnv, const std::u16string& key, T value);
|
||||
void WriteLDFData(const LwoNameValue& ldf, RakNet::BitStream& outBitStream) const;
|
||||
LWOOBJID m_ObjectID;
|
||||
|
||||
LOT m_TemplateID;
|
||||
|
||||
std::vector<LDFBaseData*> m_Settings;
|
||||
std::vector<LDFBaseData*> m_NetworkSettings;
|
||||
LwoNameValue m_Settings;
|
||||
LwoNameValue m_NetworkSettings;
|
||||
|
||||
NiPoint3 m_DefaultPosition;
|
||||
NiQuaternion m_DefaultRotation = QuatUtils::IDENTITY;
|
||||
@@ -447,13 +467,13 @@ T* Entity::GetComponent() const {
|
||||
|
||||
template<typename T>
|
||||
const T& Entity::GetVar(const std::u16string& name) const {
|
||||
auto* data = GetVarData(name);
|
||||
const auto* const data = GetVarData(name);
|
||||
|
||||
if (data == nullptr) {
|
||||
return LDFData<T>::Default;
|
||||
}
|
||||
|
||||
auto* typed = dynamic_cast<LDFData<T>*>(data);
|
||||
auto* typed = dynamic_cast<const LDFData<T>* const>(data);
|
||||
|
||||
if (typed == nullptr) {
|
||||
return LDFData<T>::Default;
|
||||
@@ -471,52 +491,44 @@ T Entity::GetVarAs(const std::u16string& name) const {
|
||||
|
||||
template<typename T>
|
||||
void Entity::SetVar(const std::u16string& name, T value) {
|
||||
auto* data = GetVarData(name);
|
||||
InsertLnvData<T>(m_Settings, name, value);
|
||||
}
|
||||
|
||||
if (data == nullptr) {
|
||||
auto* data = new LDFData<T>(name, value);
|
||||
|
||||
m_Settings.push_back(data);
|
||||
|
||||
return;
|
||||
template<typename T>
|
||||
LwoNameValue::ValueType::iterator Entity::InsertLnvData(LwoNameValue& lnv, const std::u16string& key, T value) {
|
||||
auto itr = lnv.values.find(key);
|
||||
if (itr != lnv.values.end()) {
|
||||
auto* lnvCast = dynamic_cast<LDFData<T>*>(itr->second.get());
|
||||
if (!lnvCast) {
|
||||
// Is of different type
|
||||
itr->second = std::make_unique<LDFData<T>>(key, value);
|
||||
} else {
|
||||
// Is the same type and exists
|
||||
lnvCast->SetValue(value);
|
||||
}
|
||||
} else {
|
||||
// Doesn't exist
|
||||
itr = lnv.values.insert_or_assign(key, std::make_unique<LDFData<T>>(key, value)).first;
|
||||
}
|
||||
|
||||
auto* typed = dynamic_cast<LDFData<T>*>(data);
|
||||
return itr;
|
||||
}
|
||||
|
||||
if (typed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
typed->SetValue(value);
|
||||
template<typename T>
|
||||
LwoNameValue::ValueType::iterator Entity::InsertNetworkVar(const std::u16string& name, T value) {
|
||||
return InsertLnvData<T>(m_NetworkSettings, name, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Entity::SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr) {
|
||||
LDFData<T>* newData = nullptr;
|
||||
const auto itr = InsertNetworkVar<T>(name, value);
|
||||
|
||||
for (auto* data : m_NetworkSettings) {
|
||||
if (data->GetKey() != name)
|
||||
continue;
|
||||
SendNetworkVar(itr->second->GetString(), sysAddr);
|
||||
}
|
||||
|
||||
newData = dynamic_cast<LDFData<T>*>(data);
|
||||
if (newData != nullptr) {
|
||||
newData->SetValue(value);
|
||||
} else { // If we're changing types
|
||||
m_NetworkSettings.erase(
|
||||
std::remove(m_NetworkSettings.begin(), m_NetworkSettings.end(), data), m_NetworkSettings.end()
|
||||
);
|
||||
delete data;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (newData == nullptr) {
|
||||
newData = new LDFData<T>(name, value);
|
||||
}
|
||||
|
||||
m_NetworkSettings.push_back(newData);
|
||||
SendNetworkVar(newData->GetString(true), sysAddr);
|
||||
template<typename T>
|
||||
void Entity::SetNetworkVar(const std::string& name, T value, const SystemAddress& sysAddr) {
|
||||
SetNetworkVar(GeneralUtils::UTF8ToUTF16(name), value, sysAddr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -525,28 +537,11 @@ void Entity::SetNetworkVar(const std::u16string& name, std::vector<T> values, co
|
||||
auto index = 1;
|
||||
|
||||
for (const auto& value : values) {
|
||||
LDFData<T>* newData = nullptr;
|
||||
const auto& indexedName = name + u"." + GeneralUtils::to_u16string(index);
|
||||
|
||||
for (auto* data : m_NetworkSettings) {
|
||||
if (data->GetKey() != indexedName)
|
||||
continue;
|
||||
|
||||
newData = dynamic_cast<LDFData<T>*>(data);
|
||||
newData->SetValue(value);
|
||||
break;
|
||||
}
|
||||
|
||||
if (newData == nullptr) {
|
||||
newData = new LDFData<T>(indexedName, value);
|
||||
}
|
||||
|
||||
m_NetworkSettings.push_back(newData);
|
||||
|
||||
if (index == values.size()) {
|
||||
updates << newData->GetString(true);
|
||||
} else {
|
||||
updates << newData->GetString(true) << "\n";
|
||||
const auto itr = InsertNetworkVar<T>(indexedName, value);
|
||||
updates << itr->second->GetString();
|
||||
if (index != values.size()) {
|
||||
updates << "\n";
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -557,18 +552,15 @@ void Entity::SetNetworkVar(const std::u16string& name, std::vector<T> values, co
|
||||
|
||||
template<typename T>
|
||||
T Entity::GetNetworkVar(const std::u16string& name) {
|
||||
for (auto* data : m_NetworkSettings) {
|
||||
if (data == nullptr || data->GetKey() != name)
|
||||
continue;
|
||||
T toReturn = LDFData<T>::Default;
|
||||
|
||||
auto* typed = dynamic_cast<LDFData<T>*>(data);
|
||||
if (typed == nullptr)
|
||||
continue;
|
||||
|
||||
return typed->GetValue();
|
||||
const auto itr = m_NetworkSettings.values.find(name);
|
||||
if (itr != m_NetworkSettings.values.cend()) {
|
||||
auto* cast = dynamic_cast<LDFData<T>*>(itr->second.get());
|
||||
if (cast) toReturn = cast->GetValue();
|
||||
}
|
||||
|
||||
return LDFData<T>::Default;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "SkillComponent.h"
|
||||
#include "SwitchComponent.h"
|
||||
#include "UserManager.h"
|
||||
#include "Metrics.hpp"
|
||||
#include "dZoneManager.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "Game.h"
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
#include "DluAssert.h"
|
||||
|
||||
#include "CDActivitiesTable.h"
|
||||
#include "Metrics.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace LeaderboardManager {
|
||||
std::map<GameID, Leaderboard::Type> leaderboardCache;
|
||||
@@ -38,10 +39,10 @@ Leaderboard::~Leaderboard() {
|
||||
}
|
||||
|
||||
void Leaderboard::Clear() {
|
||||
for (auto& entry : entries) for (auto ldfData : entry) delete ldfData;
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) {
|
||||
inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::unique_ptr<LDFBaseData>& data) {
|
||||
leaderboard << "\nResult[0].Row[" << index << "]." << data->GetString();
|
||||
}
|
||||
|
||||
@@ -58,8 +59,8 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const {
|
||||
|
||||
int32_t rowNumber = 0;
|
||||
for (auto& entry : entries) {
|
||||
for (auto* data : entry) {
|
||||
WriteLeaderboardRow(leaderboard, rowNumber, data);
|
||||
for (const auto& data : entry.values | std::views::values) {
|
||||
if (data) WriteLeaderboardRow(leaderboard, rowNumber, data);
|
||||
}
|
||||
rowNumber++;
|
||||
}
|
||||
@@ -84,57 +85,56 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>
|
||||
for (const auto& leaderboardEntry : leaderboardEntries) {
|
||||
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
|
||||
auto& entry = leaderboard.PushBackEntry();
|
||||
entry.reserve(MAX_NUM_DATA_PER_ROW);
|
||||
entry.push_back(new LDFData<uint64_t>(u"CharacterID", leaderboardEntry.charId));
|
||||
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp));
|
||||
entry.push_back(new LDFData<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed));
|
||||
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)));
|
||||
entry.push_back(new LDFData<uint64_t>(u"RowNumber", leaderboardEntry.ranking));
|
||||
entry.Insert<uint64_t>(u"CharacterID", leaderboardEntry.charId);
|
||||
entry.Insert<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp);
|
||||
entry.Insert<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed);
|
||||
entry.Insert<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name));
|
||||
entry.Insert<uint64_t>(u"RowNumber", leaderboardEntry.ranking);
|
||||
switch (leaderboard.GetLeaderboardType()) {
|
||||
case ShootingGallery:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Score", leaderboardEntry.primaryScore);
|
||||
// Score:1
|
||||
entry.push_back(new LDFData<int32_t>(u"Streak", leaderboardEntry.secondaryScore));
|
||||
entry.Insert<int32_t>(u"Streak", leaderboardEntry.secondaryScore);
|
||||
// Streak:1
|
||||
entry.push_back(new LDFData<float>(u"HitPercentage", leaderboardEntry.tertiaryScore));
|
||||
entry.Insert<float>(u"HitPercentage", leaderboardEntry.tertiaryScore);
|
||||
// HitPercentage:3 between 0 and 1
|
||||
break;
|
||||
case Racing:
|
||||
entry.push_back(new LDFData<float>(u"BestTime", leaderboardEntry.primaryScore));
|
||||
entry.Insert<float>(u"BestTime", leaderboardEntry.primaryScore);
|
||||
// BestLapTime:3
|
||||
entry.push_back(new LDFData<float>(u"BestLapTime", leaderboardEntry.secondaryScore));
|
||||
entry.Insert<float>(u"BestLapTime", leaderboardEntry.secondaryScore);
|
||||
// BestTime:3
|
||||
entry.push_back(new LDFData<int32_t>(u"License", 1));
|
||||
entry.Insert<int32_t>(u"License", 1);
|
||||
// License:1 - 1 if player has completed mission 637 and 0 otherwise
|
||||
entry.push_back(new LDFData<int32_t>(u"NumWins", leaderboardEntry.numWins));
|
||||
entry.Insert<int32_t>(u"NumWins", leaderboardEntry.numWins);
|
||||
// NumWins:1
|
||||
break;
|
||||
case UnusedLeaderboard4:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Points", leaderboardEntry.primaryScore);
|
||||
// Points:1
|
||||
break;
|
||||
case MonumentRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Time", leaderboardEntry.primaryScore);
|
||||
// Time:1(?)
|
||||
break;
|
||||
case FootRace:
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Time", leaderboardEntry.primaryScore);
|
||||
// Time:1
|
||||
break;
|
||||
case Survival:
|
||||
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Points", leaderboardEntry.primaryScore);
|
||||
// Points:1
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
|
||||
entry.Insert<int32_t>(u"Time", leaderboardEntry.secondaryScore);
|
||||
// Time:1
|
||||
break;
|
||||
case SurvivalNS:
|
||||
entry.push_back(new LDFData<int32_t>(u"Wave", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Wave", leaderboardEntry.primaryScore);
|
||||
// Wave:1
|
||||
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
|
||||
entry.Insert<int32_t>(u"Time", leaderboardEntry.secondaryScore);
|
||||
// Time:1
|
||||
break;
|
||||
case Donations:
|
||||
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
|
||||
entry.Insert<int32_t>(u"Score", leaderboardEntry.primaryScore);
|
||||
// Score:1
|
||||
break;
|
||||
case None:
|
||||
|
||||
@@ -70,8 +70,7 @@ public:
|
||||
|
||||
|
||||
private:
|
||||
using LeaderboardEntry = std::vector<LDFBaseData*>;
|
||||
using LeaderboardEntries = std::vector<LeaderboardEntry>;
|
||||
using LeaderboardEntries = std::vector<LwoNameValue>;
|
||||
|
||||
LeaderboardEntries entries;
|
||||
LWOOBJID relatedPlayer;
|
||||
@@ -81,7 +80,7 @@ private:
|
||||
bool weekly;
|
||||
uint32_t numResults;
|
||||
public:
|
||||
LeaderboardEntry& PushBackEntry() {
|
||||
LwoNameValue& PushBackEntry() {
|
||||
return entries.emplace_back();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#include "CharacterComponent.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "eMissionTaskType.h"
|
||||
#include <ranges>
|
||||
|
||||
namespace {
|
||||
std::unique_ptr<Trade> g_EmptyTrade;
|
||||
}
|
||||
|
||||
TradingManager* TradingManager::m_Address = nullptr;
|
||||
|
||||
@@ -233,55 +238,38 @@ void Trade::SendUpdateToOther(LWOOBJID participant) {
|
||||
GameMessages::SendServerTradeUpdate(other->GetObjectID(), coins, items, other->GetSystemAddress());
|
||||
}
|
||||
|
||||
TradingManager::TradingManager() {
|
||||
}
|
||||
|
||||
TradingManager::~TradingManager() {
|
||||
for (const auto& pair : trades) {
|
||||
delete pair.second;
|
||||
}
|
||||
|
||||
trades.clear();
|
||||
}
|
||||
|
||||
Trade* TradingManager::GetTrade(LWOOBJID tradeId) const {
|
||||
const std::unique_ptr<Trade>& TradingManager::GetTrade(LWOOBJID tradeId) const {
|
||||
const auto& pair = trades.find(tradeId);
|
||||
|
||||
if (pair == trades.end()) return nullptr;
|
||||
if (pair == trades.end()) return g_EmptyTrade;
|
||||
|
||||
return pair->second;
|
||||
}
|
||||
|
||||
Trade* TradingManager::GetPlayerTrade(LWOOBJID playerId) const {
|
||||
for (const auto& pair : trades) {
|
||||
if (pair.second->IsParticipant(playerId)) {
|
||||
return pair.second;
|
||||
const std::unique_ptr<Trade>& TradingManager::GetPlayerTrade(LWOOBJID playerId) const {
|
||||
for (const auto& trade : trades | std::views::values) {
|
||||
if (trade->IsParticipant(playerId)) {
|
||||
return trade;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return g_EmptyTrade;
|
||||
}
|
||||
|
||||
void TradingManager::CancelTrade(const LWOOBJID canceller, LWOOBJID tradeId, const bool sendCancelMessage) {
|
||||
auto* trade = GetTrade(tradeId);
|
||||
const auto& trade = GetTrade(tradeId);
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
if (sendCancelMessage) trade->Cancel(canceller);
|
||||
|
||||
delete trade;
|
||||
|
||||
trades.erase(tradeId);
|
||||
}
|
||||
|
||||
Trade* TradingManager::NewTrade(LWOOBJID participantA, LWOOBJID participantB) {
|
||||
void TradingManager::NewTrade(LWOOBJID participantA, LWOOBJID participantB) {
|
||||
const LWOOBJID tradeId = ObjectIDManager::GenerateObjectID();
|
||||
|
||||
auto* trade = new Trade(tradeId, participantA, participantB);
|
||||
|
||||
trades[tradeId] = trade;
|
||||
trades.insert_or_assign(tradeId, std::make_unique<Trade>(tradeId, participantA, participantB));
|
||||
|
||||
LOG("Created new trade between (%llu) <-> (%llu)", participantA, participantB);
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
struct TradeItem
|
||||
{
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
struct TradeItem {
|
||||
LWOOBJID itemId;
|
||||
LOT itemLot;
|
||||
uint32_t itemCount;
|
||||
};
|
||||
|
||||
class Trade
|
||||
{
|
||||
class Trade {
|
||||
public:
|
||||
explicit Trade(LWOOBJID tradeId, LWOOBJID participantA, LWOOBJID participantB);
|
||||
~Trade();
|
||||
@@ -50,8 +51,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class TradingManager
|
||||
{
|
||||
class TradingManager {
|
||||
public:
|
||||
static TradingManager* Instance() {
|
||||
if (!m_Address) {
|
||||
@@ -61,16 +61,13 @@ public:
|
||||
return m_Address;
|
||||
}
|
||||
|
||||
explicit TradingManager();
|
||||
~TradingManager();
|
||||
|
||||
Trade* GetTrade(LWOOBJID tradeId) const;
|
||||
Trade* GetPlayerTrade(LWOOBJID playerId) const;
|
||||
const std::unique_ptr<Trade>& GetTrade(LWOOBJID tradeId) const;
|
||||
const std::unique_ptr<Trade>& GetPlayerTrade(LWOOBJID playerId) const;
|
||||
void CancelTrade(const LWOOBJID canceller, LWOOBJID tradeId, const bool sendCancelMessage = true);
|
||||
Trade* NewTrade(LWOOBJID participantA, LWOOBJID participantB);
|
||||
void NewTrade(LWOOBJID participantA, LWOOBJID participantB);
|
||||
|
||||
private:
|
||||
static TradingManager* m_Address; //For singleton method
|
||||
|
||||
std::unordered_map<LWOOBJID, Trade*> trades;
|
||||
std::unordered_map<LWOOBJID, std::unique_ptr<Trade>> trades;
|
||||
};
|
||||
|
||||
@@ -30,6 +30,21 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
return;
|
||||
}
|
||||
|
||||
// So a player can't send an arbitrary behaviorID in a modified client and cast any behavior on any air behavior
|
||||
Behavior* toSync = nullptr;
|
||||
if (m_GroundAction->GetBehaviorID() == behaviorId) {
|
||||
toSync = m_GroundAction;
|
||||
} else if (m_HitAction->GetBehaviorID() == behaviorId) {
|
||||
toSync = m_HitAction;
|
||||
} else if (m_HitActionEnemy->GetBehaviorID() == behaviorId) {
|
||||
toSync = m_HitActionEnemy;
|
||||
} else if (m_TimeoutAction->GetBehaviorID() == behaviorId) {
|
||||
toSync = m_TimeoutAction;
|
||||
} else {
|
||||
LOG("Invalid Air Movement Behavior sync for behaviorID %i on behavior %i", behaviorId, m_behaviorId);
|
||||
return;
|
||||
}
|
||||
|
||||
LWOOBJID target{};
|
||||
|
||||
if (!bitStream.Read(target)) {
|
||||
@@ -37,15 +52,17 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
return;
|
||||
}
|
||||
|
||||
auto* behavior = CreateBehavior(behaviorId);
|
||||
|
||||
if (Game::entityManager->GetEntity(target) != nullptr) {
|
||||
branch.target = target;
|
||||
}
|
||||
|
||||
behavior->Handle(context, bitStream, branch);
|
||||
toSync->Handle(context, bitStream, branch);
|
||||
}
|
||||
|
||||
void AirMovementBehavior::Load() {
|
||||
this->m_Timeout = (GetFloat("timeout_ms") / 1000.0f);
|
||||
m_Timeout = (GetFloat("timeout_ms") / 1000.0f);
|
||||
m_GroundAction = GetAction("ground_action");
|
||||
m_HitAction = GetAction("hit_action");
|
||||
m_HitActionEnemy = GetAction("hit_action_enemy");
|
||||
m_TimeoutAction = GetAction("timeout_action");
|
||||
}
|
||||
|
||||
@@ -15,4 +15,9 @@ public:
|
||||
void Load() override;
|
||||
private:
|
||||
float m_Timeout;
|
||||
|
||||
Behavior* m_GroundAction{};
|
||||
Behavior* m_HitAction{};
|
||||
Behavior* m_HitActionEnemy{};
|
||||
Behavior* m_TimeoutAction{};
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream& b
|
||||
LWOOBJID target{};
|
||||
if (!bitStream.Read(target)) {
|
||||
LOG("failed to read in target %i from bitStream, aborting target Handle!", i);
|
||||
continue;
|
||||
};
|
||||
targets.push_back(target);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
|
||||
}
|
||||
|
||||
if (isBlocked) {
|
||||
destroyableComponent->SetAttacksToBlock(std::min(destroyableComponent->GetAttacksToBlock() - 1, 0U));
|
||||
destroyableComponent->SetAttacksToBlock(std::max<int32_t>(static_cast<int32_t>(destroyableComponent->GetAttacksToBlock() - 1), 0));
|
||||
Game::entityManager->SerializeEntity(targetEntity);
|
||||
this->m_OnFailBlocked->Handle(context, bitStream, branch);
|
||||
return;
|
||||
@@ -103,9 +103,10 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
|
||||
uint64_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
|
||||
|
||||
// A value that's too large may be a cheating attempt, so we set it to MIN
|
||||
// Can't overflow here either because should we somehow get to a 64 bit number it'll be clamped to a sane value.
|
||||
if (totalDamageDealt > this->m_MaxDamage) {
|
||||
totalDamageDealt = this->m_MinDamage;
|
||||
}
|
||||
|
||||
@@ -48,15 +48,13 @@ void BlockBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branc
|
||||
return;
|
||||
}
|
||||
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
auto* const destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock);
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
return;
|
||||
if (destroyableComponent) {
|
||||
// ??? what is going on here?
|
||||
destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock);
|
||||
destroyableComponent->SetAttacksToBlock(0);
|
||||
}
|
||||
|
||||
destroyableComponent->SetAttacksToBlock(0);
|
||||
}
|
||||
|
||||
void BlockBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {
|
||||
|
||||
@@ -11,6 +11,11 @@ void ChainBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStrea
|
||||
return;
|
||||
}
|
||||
|
||||
if (chainIndex == 0) {
|
||||
LOG("Received invalid chain index of 0 for behavior %i.", m_behaviorId);
|
||||
return;
|
||||
}
|
||||
|
||||
chainIndex--;
|
||||
|
||||
if (chainIndex < this->m_behaviors.size()) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_stream, const BehaviorBranchContext branch) {
|
||||
auto* entity = Game::entityManager->GetEntity(branch.target);
|
||||
if (!entity) return;
|
||||
|
||||
GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID);
|
||||
|
||||
@@ -21,6 +22,7 @@ void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_st
|
||||
|
||||
void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) {
|
||||
auto* entity = Game::entityManager->GetEntity(branch.target);
|
||||
if (!entity) return;
|
||||
|
||||
GameMessages::SendSetJetPackMode(entity, false);
|
||||
|
||||
|
||||
@@ -5,20 +5,40 @@
|
||||
|
||||
void NpcCombatSkillBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bit_stream, BehaviorBranchContext branch) {
|
||||
context->skillTime = this->m_npcSkillTime;
|
||||
const auto* const targetEntity = Game::entityManager->GetEntity(branch.target);
|
||||
const auto* const sourceEntity = Game::entityManager->GetEntity(context->caster);
|
||||
|
||||
for (auto* behavior : this->m_behaviors) {
|
||||
behavior->Calculate(context, bit_stream, branch);
|
||||
bool cast = true;
|
||||
// Check that the target is within the cast range
|
||||
if (targetEntity && sourceEntity && this->m_maxRange != 0.0f) {
|
||||
const auto targetPos = targetEntity->GetPosition();
|
||||
const auto sourcePos = sourceEntity->GetPosition();
|
||||
const auto distance = NiPoint3::DistanceSquared(targetPos, sourcePos);
|
||||
cast = distance >= this->m_minRange && distance <= this->m_maxRange;
|
||||
}
|
||||
|
||||
if (cast) {
|
||||
for (auto* behavior : this->m_behaviors) {
|
||||
behavior->Calculate(context, bit_stream, branch);
|
||||
}
|
||||
} else {
|
||||
// We failed to find a valid target, do not continue the behavior
|
||||
context->foundTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NpcCombatSkillBehavior::Load() {
|
||||
this->m_npcSkillTime = GetFloat("npc skill time");
|
||||
this->m_minRange = GetFloat("min range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements
|
||||
this->m_minRange *= this->m_minRange;
|
||||
this->m_maxRange = GetFloat("max range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements
|
||||
this->m_maxRange *= this->m_maxRange;
|
||||
|
||||
const auto parameters = GetParameterNames();
|
||||
|
||||
for (const auto& parameter : parameters) {
|
||||
if (parameter.first.rfind("behavior", 0) == 0) {
|
||||
auto* action = GetAction(parameter.second);
|
||||
for (const auto& [parameter, value] : parameters) {
|
||||
if (parameter.rfind("behavior", 0) == 0) {
|
||||
auto* action = GetAction(value);
|
||||
|
||||
this->m_behaviors.push_back(action);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ public:
|
||||
|
||||
float m_npcSkillTime;
|
||||
|
||||
float m_maxRange{};
|
||||
float m_minRange{};
|
||||
|
||||
/*
|
||||
* Inherited
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,11 @@ void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream&
|
||||
return;
|
||||
};
|
||||
|
||||
if (m_behaviors.empty()) {
|
||||
LOG("No behaviors were loaded for %i, aborting call.", m_behaviorId);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t trigger = 0;
|
||||
|
||||
for (unsigned int i = 0; i < this->m_behaviors.size(); i++) {
|
||||
|
||||
@@ -119,9 +119,8 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
|
||||
const auto targetPos = validTarget->GetPosition();
|
||||
|
||||
// make sure we aren't too high or low in comparison to the targer
|
||||
const auto heightDifference = std::abs(reference.y - targetPos.y);
|
||||
if (targetPos.y > reference.y && heightDifference > this->m_upperBound || targetPos.y < reference.y && heightDifference > this->m_lowerBound)
|
||||
// make sure we aren't too high or low in comparison to the target
|
||||
if (targetPos.y > (reference.y + m_upperBound) || targetPos.y < (reference.y + m_lowerBound))
|
||||
continue;
|
||||
|
||||
const auto forward = QuatUtils::Forward(self->GetRotation());
|
||||
@@ -208,9 +207,13 @@ void TacArcBehavior::Load() {
|
||||
GetFloat("offset_y", 0.0f),
|
||||
GetFloat("offset_z", 0.0f)
|
||||
);
|
||||
// https://explorer.lu/skills/behaviors/6212/6203 HACK: i cant figure out why the dragon fire wall doesnt work with the offset, probably has to be fixed with the near/far height parameters
|
||||
if (m_behaviorId == 6203) {
|
||||
this->m_offset = NiPoint3Constant::ZERO;
|
||||
}
|
||||
this->m_method = GetInt("method", 1);
|
||||
this->m_upperBound = std::abs(GetFloat("upper_bound", 4.4f));
|
||||
this->m_lowerBound = std::abs(GetFloat("lower_bound", 0.4f));
|
||||
this->m_upperBound = GetFloat("upper_bound", 4.4f);
|
||||
this->m_lowerBound = GetFloat("lower_bound", 0.4f) - 5.0f; // Makes it so players and objects can still be targetted when slightly below the caster. FIXME: use bounding spheres at some point
|
||||
this->m_usePickedTarget = GetBoolean("use_picked_target", false);
|
||||
this->m_useTargetPostion = GetBoolean("use_target_position", false);
|
||||
this->m_checkEnv = GetBoolean("check_env", false);
|
||||
|
||||
@@ -25,7 +25,7 @@ void VerifyBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
|
||||
const auto distance = Vector3::DistanceSquared(self->GetPosition(), entity->GetPosition());
|
||||
|
||||
if (distance > this->m_range * this->m_range) {
|
||||
if (distance > this->m_range) {
|
||||
success = false;
|
||||
}
|
||||
} else if (this->m_blockCheck) {
|
||||
@@ -57,4 +57,5 @@ void VerifyBehavior::Load() {
|
||||
this->m_action = GetAction("action");
|
||||
|
||||
this->m_range = GetFloat("range");
|
||||
this->m_range = this->m_range * this->m_range * 0.9f; // Range checks are slightly smaller than the actual range to account for client/server discrepancies
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "eMatchUpdate.h"
|
||||
#include "ServiceType.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "ObjectIDManager.h"
|
||||
|
||||
#include "CDCurrencyTableTable.h"
|
||||
#include "CDActivityRewardsTable.h"
|
||||
@@ -29,10 +30,14 @@
|
||||
#include "LeaderboardManager.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "Amf3.h"
|
||||
#include <ranges>
|
||||
|
||||
namespace {
|
||||
const ActivityInstance g_EmptyInstance{ nullptr, CDActivities{} };
|
||||
}
|
||||
|
||||
ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Component(parent, componentID) {
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &ActivityComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&ActivityComponent::OnGetObjectReportInfo);
|
||||
/*
|
||||
* This is precisely what the client does functionally
|
||||
* Use the component id as the default activity id and load its data from the database
|
||||
@@ -72,9 +77,9 @@ void ActivityComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIniti
|
||||
if (m_DirtyActivityInfo) {
|
||||
outBitStream.Write<uint32_t>(m_ActivityPlayers.size());
|
||||
if (!m_ActivityPlayers.empty()) {
|
||||
for (const auto& activityPlayer : m_ActivityPlayers) {
|
||||
outBitStream.Write<LWOOBJID>(activityPlayer->playerID);
|
||||
for (const auto& activityValue : activityPlayer->values) {
|
||||
for (const auto& [playerID, values] : m_ActivityPlayers) {
|
||||
outBitStream.Write<LWOOBJID>(playerID);
|
||||
for (const auto& activityValue : values) {
|
||||
outBitStream.Write<float_t>(activityValue);
|
||||
}
|
||||
}
|
||||
@@ -112,78 +117,81 @@ void ActivityComponent::PlayerJoin(Entity* player) {
|
||||
if (HasLobby()) {
|
||||
PlayerJoinLobby(player);
|
||||
} else if (!IsPlayedBy(player)) {
|
||||
auto* instance = NewInstance();
|
||||
instance->AddParticipant(player);
|
||||
NewInstance().AddParticipant(player);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityComponent::PlayerJoinLobby(Entity* player) {
|
||||
if (!m_Parent->HasComponent(eReplicaComponentType::QUICK_BUILD))
|
||||
GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby
|
||||
LobbyPlayer* newLobbyPlayer = new LobbyPlayer();
|
||||
newLobbyPlayer->entityID = player->GetObjectID();
|
||||
Lobby* playerLobby = nullptr;
|
||||
LobbyPlayer newLobbyPlayer{};
|
||||
newLobbyPlayer.entityID = player->GetObjectID();
|
||||
LWOOBJID playerLobbyID = LWOOBJID_EMPTY;
|
||||
|
||||
auto* character = player->GetCharacter();
|
||||
if (character != nullptr)
|
||||
character->SetLastNonInstanceZoneID(Game::zoneManager->GetZone()->GetWorldID());
|
||||
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) {
|
||||
for (auto& [lobbyID, lobby] : m_Queue) {
|
||||
if (lobby.players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() < m_ActivityInfo.maxTeams) {
|
||||
// If an empty slot in an existing lobby is found
|
||||
lobby->players.push_back(newLobbyPlayer);
|
||||
playerLobby = lobby;
|
||||
lobby.players.push_back(newLobbyPlayer);
|
||||
playerLobbyID = lobbyID;
|
||||
|
||||
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
|
||||
std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName();
|
||||
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
||||
auto* entity = joinedPlayer->GetEntity();
|
||||
LDFData<LWOOBJID> playerLDF("player", player->GetObjectID());
|
||||
LDFData<std::string> playerName("playerName", player->GetCharacter()->GetName());
|
||||
std::string matchUpdateJoined = playerLDF.GetString() + "\n" + playerName.GetString();
|
||||
for (const auto& joinedPlayer : lobby.players) {
|
||||
auto* const entity = joinedPlayer.GetEntity();
|
||||
|
||||
if (entity == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName();
|
||||
LDFData<LWOOBJID> entityLDF("player", entity->GetObjectID());
|
||||
LDFData<std::string> entityName("playerName", entity->GetCharacter()->GetName());
|
||||
std::string matchUpdate = entityLDF.GetString() + "\n" + entityName.GetString();
|
||||
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED);
|
||||
PlayerReady(entity, joinedPlayer->ready);
|
||||
PlayerReady(entity, joinedPlayer.ready);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playerLobby) {
|
||||
if (playerLobbyID == LWOOBJID_EMPTY) {
|
||||
// If all lobbies are full
|
||||
playerLobby = new Lobby();
|
||||
playerLobby->players.push_back(newLobbyPlayer);
|
||||
playerLobby->timer = m_ActivityInfo.waitTime / 1000;
|
||||
m_Queue.push_back(playerLobby);
|
||||
playerLobbyID = ObjectIDManager::GenerateObjectID();
|
||||
auto& newLobby = m_Queue[playerLobbyID];
|
||||
newLobby.players.push_back(newLobbyPlayer);
|
||||
newLobby.timer = m_ActivityInfo.waitTime / 1000;
|
||||
}
|
||||
const auto& lobby = m_Queue[playerLobbyID];
|
||||
|
||||
if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) {
|
||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
|
||||
// Update the joining player on the match timer
|
||||
std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer);
|
||||
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
||||
LDFData<float> matchTimer("time", lobby.timer);
|
||||
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimer.GetString(), eMatchUpdate::PHASE_WAIT_READY);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
||||
|
||||
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
for (int i = 0; i < lobby->players.size(); ++i) {
|
||||
if (lobby->players[i]->entityID == playerID) {
|
||||
std::string matchUpdateLeft = "player=9:" + std::to_string(playerID);
|
||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||
auto* entity = lobbyPlayer->GetEntity();
|
||||
for (auto& lobby : m_Queue | std::views::values) {
|
||||
for (int i = 0; i < lobby.players.size(); i++) {
|
||||
const auto& player = lobby.players[i];
|
||||
if (player.entityID == playerID) {
|
||||
LDFData<LWOOBJID> matchUpdateLeft("player", playerID);
|
||||
for (const auto& lobbyPlayer : lobby.players) {
|
||||
auto* const entity = lobbyPlayer.GetEntity();
|
||||
if (entity == nullptr)
|
||||
continue;
|
||||
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft.GetString(), eMatchUpdate::PLAYER_REMOVED);
|
||||
}
|
||||
|
||||
delete lobby->players[i];
|
||||
lobby->players[i] = nullptr;
|
||||
lobby->players.erase(lobby->players.begin() + i);
|
||||
lobby.players.erase(lobby.players.begin() + i);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -192,85 +200,79 @@ void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
||||
}
|
||||
|
||||
void ActivityComponent::Update(float deltaTime) {
|
||||
std::vector<Lobby*> lobbiesToRemove{};
|
||||
std::vector<LWOOBJID> lobbiesToRemove{};
|
||||
// Ticks all the lobbies, not applicable for non-instance activities
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
for (LobbyPlayer* player : lobby->players) {
|
||||
auto* entity = player->GetEntity();
|
||||
for (auto& [lobbyID, lobby] : m_Queue) {
|
||||
for (const auto& player : lobby.players) {
|
||||
const auto* const entity = player.GetEntity();
|
||||
if (entity == nullptr) {
|
||||
PlayerLeave(player->entityID);
|
||||
PlayerLeave(player.entityID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (lobby->players.empty()) {
|
||||
lobbiesToRemove.push_back(lobby);
|
||||
if (lobby.players.empty()) {
|
||||
lobbiesToRemove.push_back(lobbyID);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the match time for all players
|
||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize
|
||||
|| m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) {
|
||||
if (lobby->timer == m_ActivityInfo.waitTime / 1000) {
|
||||
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
||||
auto* entity = joinedPlayer->GetEntity();
|
||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize
|
||||
|| m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
|
||||
if (lobby.timer == m_ActivityInfo.waitTime / 1000) {
|
||||
for (const auto& joinedPlayer : lobby.players) {
|
||||
auto* const entity = joinedPlayer.GetEntity();
|
||||
|
||||
if (entity == nullptr)
|
||||
continue;
|
||||
|
||||
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
||||
LDFData<float> matchTimerUpdate("time", lobby.timer);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_READY);
|
||||
}
|
||||
}
|
||||
|
||||
lobby->timer -= deltaTime;
|
||||
lobby.timer -= deltaTime;
|
||||
}
|
||||
|
||||
bool lobbyReady = true;
|
||||
for (LobbyPlayer* player : lobby->players) {
|
||||
if (player->ready) continue;
|
||||
for (const auto& player : lobby.players) {
|
||||
if (player.ready) continue;
|
||||
lobbyReady = false;
|
||||
}
|
||||
|
||||
// If everyone's ready, jump the timer
|
||||
if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) {
|
||||
lobby->timer = m_ActivityInfo.startDelay / 1000;
|
||||
if (lobbyReady && lobby.timer > m_ActivityInfo.startDelay / 1000) {
|
||||
lobby.timer = m_ActivityInfo.startDelay / 1000;
|
||||
|
||||
// Update players in lobby on switch to start delay
|
||||
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
||||
for (LobbyPlayer* player : lobby->players) {
|
||||
auto* entity = player->GetEntity();
|
||||
LDFData<float> matchTimerUpdate("time", lobby.timer);
|
||||
for (const auto& player : lobby.players) {
|
||||
auto* const entity = player.GetEntity();
|
||||
|
||||
if (entity == nullptr)
|
||||
continue;
|
||||
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_START);
|
||||
}
|
||||
}
|
||||
|
||||
// The timer has elapsed, start the instance
|
||||
if (lobby->timer <= 0.0f) {
|
||||
if (lobby.timer <= 0.0f) {
|
||||
LOG("Setting up instance.");
|
||||
ActivityInstance* instance = NewInstance();
|
||||
LoadPlayersIntoInstance(instance, lobby->players);
|
||||
instance->StartZone();
|
||||
lobbiesToRemove.push_back(lobby);
|
||||
auto& instance = NewInstance();
|
||||
LoadPlayersIntoInstance(instance, lobby.players);
|
||||
instance.StartZone();
|
||||
lobbiesToRemove.push_back(lobbyID);
|
||||
}
|
||||
}
|
||||
|
||||
while (!lobbiesToRemove.empty()) {
|
||||
RemoveLobby(lobbiesToRemove.front());
|
||||
lobbiesToRemove.erase(lobbiesToRemove.begin());
|
||||
for (const auto id : lobbiesToRemove) {
|
||||
RemoveLobby(id);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityComponent::RemoveLobby(Lobby* lobby) {
|
||||
for (int i = 0; i < m_Queue.size(); ++i) {
|
||||
if (m_Queue[i] == lobby) {
|
||||
m_Queue.erase(m_Queue.begin() + i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
void ActivityComponent::RemoveLobby(const LWOOBJID lobbyID) {
|
||||
if (m_Queue.contains(lobbyID)) m_Queue.erase(lobbyID);
|
||||
}
|
||||
|
||||
bool ActivityComponent::HasLobby() const {
|
||||
@@ -279,9 +281,9 @@ bool ActivityComponent::HasLobby() const {
|
||||
}
|
||||
|
||||
bool ActivityComponent::PlayerIsInQueue(Entity* player) {
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||
if (player->GetObjectID() == lobbyPlayer->entityID) return true;
|
||||
for (const auto& lobby : m_Queue | std::views::values) {
|
||||
for (const auto& lobbyPlayer : lobby.players) {
|
||||
if (player->GetObjectID() == lobbyPlayer.entityID) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,8 +291,8 @@ bool ActivityComponent::PlayerIsInQueue(Entity* player) {
|
||||
}
|
||||
|
||||
bool ActivityComponent::IsPlayedBy(Entity* player) const {
|
||||
for (const auto* instance : this->m_Instances) {
|
||||
for (const auto* instancePlayer : instance->GetParticipants()) {
|
||||
for (const auto& instance : m_Instances) {
|
||||
for (const auto* instancePlayer : instance.GetParticipants()) {
|
||||
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID())
|
||||
return true;
|
||||
}
|
||||
@@ -300,8 +302,8 @@ bool ActivityComponent::IsPlayedBy(Entity* player) const {
|
||||
}
|
||||
|
||||
bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
|
||||
for (const auto* instance : this->m_Instances) {
|
||||
for (const auto* instancePlayer : instance->GetParticipants()) {
|
||||
for (const auto& instance : m_Instances) {
|
||||
for (const auto* instancePlayer : instance.GetParticipants()) {
|
||||
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID)
|
||||
return true;
|
||||
}
|
||||
@@ -330,136 +332,95 @@ bool ActivityComponent::TakeCost(Entity* player) const {
|
||||
}
|
||||
|
||||
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||
if (lobbyPlayer->entityID == player->GetObjectID()) {
|
||||
for (auto& lobby : m_Queue | std::views::values) {
|
||||
for (auto& lobbyPlayer : lobby.players) {
|
||||
if (lobbyPlayer.entityID == player->GetObjectID()) {
|
||||
|
||||
lobbyPlayer->ready = bReady;
|
||||
lobbyPlayer.ready = bReady;
|
||||
|
||||
// Update players in lobby on player being ready
|
||||
std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID());
|
||||
LDFData<LWOOBJID> matchReadyUpdate("player", player->GetObjectID());
|
||||
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
|
||||
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
|
||||
for (LobbyPlayer* otherPlayer : lobby->players) {
|
||||
auto* entity = otherPlayer->GetEntity();
|
||||
for (const auto& otherPlayer : lobby.players) {
|
||||
auto* const entity = otherPlayer.GetEntity();
|
||||
if (entity == nullptr)
|
||||
continue;
|
||||
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus);
|
||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate.GetString(), readyStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActivityInstance* ActivityComponent::NewInstance() {
|
||||
auto* instance = new ActivityInstance(m_Parent, m_ActivityInfo);
|
||||
m_Instances.push_back(instance);
|
||||
return instance;
|
||||
ActivityInstance& ActivityComponent::NewInstance() {
|
||||
m_Instances.push_back(ActivityInstance(m_Parent, m_ActivityInfo));
|
||||
return m_Instances.back();
|
||||
}
|
||||
|
||||
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
|
||||
for (LobbyPlayer* player : lobby) {
|
||||
auto* entity = player->GetEntity();
|
||||
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const {
|
||||
for (const auto& player : lobby) {
|
||||
auto* const entity = player.GetEntity();
|
||||
if (entity == nullptr || !CheckCost(entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
instance->AddParticipant(entity);
|
||||
instance.AddParticipant(entity);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ActivityInstance*>& ActivityComponent::GetInstances() const {
|
||||
return m_Instances;
|
||||
}
|
||||
|
||||
ActivityInstance* ActivityComponent::GetInstance(const LWOOBJID playerID) {
|
||||
for (const auto* instance : GetInstances()) {
|
||||
for (const auto* participant : instance->GetParticipants()) {
|
||||
const ActivityInstance& ActivityComponent::GetInstance(const LWOOBJID playerID) const {
|
||||
for (const auto& instance : m_Instances) {
|
||||
for (const auto* participant : instance.GetParticipants()) {
|
||||
if (participant->GetObjectID() == playerID)
|
||||
return const_cast<ActivityInstance*>(instance);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return g_EmptyInstance;
|
||||
}
|
||||
|
||||
void ActivityComponent::ClearInstances() {
|
||||
for (ActivityInstance* instance : m_Instances) {
|
||||
delete instance;
|
||||
}
|
||||
m_Instances.clear();
|
||||
}
|
||||
|
||||
ActivityPlayer* ActivityComponent::GetActivityPlayerData(LWOOBJID playerID) {
|
||||
for (auto* activityData : m_ActivityPlayers) {
|
||||
if (activityData->playerID == playerID) {
|
||||
return activityData;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
bool ActivityComponent::PlayerHasActivityData(LWOOBJID playerID) const {
|
||||
return m_ActivityPlayers.contains(playerID);
|
||||
}
|
||||
|
||||
void ActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) {
|
||||
for (size_t i = 0; i < m_ActivityPlayers.size(); i++) {
|
||||
if (m_ActivityPlayers[i]->playerID == playerID) {
|
||||
delete m_ActivityPlayers[i];
|
||||
m_ActivityPlayers[i] = nullptr;
|
||||
|
||||
m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i);
|
||||
m_DirtyActivityInfo = true;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActivityPlayer* ActivityComponent::AddActivityPlayerData(LWOOBJID playerID) {
|
||||
auto* data = GetActivityPlayerData(playerID);
|
||||
if (data != nullptr)
|
||||
return data;
|
||||
|
||||
m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} });
|
||||
m_ActivityPlayers.erase(playerID);
|
||||
m_DirtyActivityInfo = true;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
return GetActivityPlayerData(playerID);
|
||||
}
|
||||
|
||||
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) {
|
||||
auto value = -1.0f;
|
||||
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) const {
|
||||
float value = -1.0f;
|
||||
|
||||
auto* data = GetActivityPlayerData(playerID);
|
||||
if (data != nullptr) {
|
||||
value = data->values[std::min(index, static_cast<uint32_t>(9))];
|
||||
const auto& data = m_ActivityPlayers.find(playerID);
|
||||
if (data != m_ActivityPlayers.cend()) {
|
||||
value = data->second[std::min(index, static_cast<uint32_t>(9))];
|
||||
}
|
||||
|
||||
LOG_DEBUG("Player %llu has score %f at index %i", playerID, value, index);
|
||||
return value;
|
||||
}
|
||||
|
||||
void ActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) {
|
||||
auto* data = AddActivityPlayerData(playerID);
|
||||
if (data != nullptr) {
|
||||
data->values[std::min(index, static_cast<uint32_t>(9))] = value;
|
||||
}
|
||||
auto& data = m_ActivityPlayers[playerID];
|
||||
data[std::min(index, static_cast<uint32_t>(9))] = value;
|
||||
LOG_DEBUG("%llu index %i has score of %f", playerID, index, value);
|
||||
m_DirtyActivityInfo = true;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
void ActivityComponent::PlayerRemove(LWOOBJID playerID) {
|
||||
for (auto* instance : GetInstances()) {
|
||||
auto participants = instance->GetParticipants();
|
||||
for (int i = 0; i < m_Instances.size(); i++) {
|
||||
auto& instance = m_Instances[i];
|
||||
auto participants = instance.GetParticipants();
|
||||
for (const auto* participant : participants) {
|
||||
if (participant != nullptr && participant->GetObjectID() == playerID) {
|
||||
instance->RemoveParticipant(participant);
|
||||
instance.RemoveParticipant(participant);
|
||||
RemoveActivityPlayerData(playerID);
|
||||
|
||||
// If the instance is empty after the delete of the participant, delete the instance too
|
||||
if (instance->GetParticipants().empty()) {
|
||||
m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance));
|
||||
delete instance;
|
||||
if (instance.GetParticipants().empty()) {
|
||||
m_Instances.erase(m_Instances.begin() + i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -590,22 +551,19 @@ Entity* LobbyPlayer::GetEntity() const {
|
||||
return Game::entityManager->GetEntity(entityID);
|
||||
}
|
||||
|
||||
bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
|
||||
bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& activityInfo = reportInfo.info->PushDebug("Activity");
|
||||
|
||||
auto& instances = activityInfo.PushDebug("Instances: " + std::to_string(m_Instances.size()));
|
||||
size_t i = 0;
|
||||
for (const auto& activityInstance : m_Instances) {
|
||||
if (!activityInstance) continue;
|
||||
auto& instance = instances.PushDebug("Instance " + std::to_string(i++));
|
||||
instance.PushDebug<AMFIntValue>("Score") = activityInstance->GetScore();
|
||||
instance.PushDebug<AMFIntValue>("Next Zone Clone ID") = activityInstance->GetNextZoneCloneID();
|
||||
instance.PushDebug<AMFIntValue>("Score") = activityInstance.GetScore();
|
||||
instance.PushDebug<AMFIntValue>("Next Zone Clone ID") = activityInstance.GetNextZoneCloneID();
|
||||
|
||||
{
|
||||
auto& activityInfo = instance.PushDebug("Activity Info");
|
||||
const auto& instanceActInfo = activityInstance->GetActivityInfo();
|
||||
const auto& instanceActInfo = activityInstance.GetActivityInfo();
|
||||
activityInfo.PushDebug<AMFIntValue>("ActivityID") = instanceActInfo.ActivityID;
|
||||
activityInfo.PushDebug<AMFIntValue>("locStatus") = instanceActInfo.locStatus;
|
||||
activityInfo.PushDebug<AMFIntValue>("instanceMapID") = instanceActInfo.instanceMapID;
|
||||
@@ -628,7 +586,7 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
}
|
||||
|
||||
auto& participants = instance.PushDebug("Participants");
|
||||
for (const auto* participant : activityInstance->GetParticipants()) {
|
||||
for (const auto* participant : activityInstance.GetParticipants()) {
|
||||
if (!participant) continue;
|
||||
auto* character = participant->GetCharacter();
|
||||
if (!character) continue;
|
||||
@@ -638,38 +596,36 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
|
||||
auto& queue = activityInfo.PushDebug("Queue");
|
||||
i = 0;
|
||||
for (const auto& lobbyQueue : m_Queue) {
|
||||
for (const auto& lobbyQueue : m_Queue | std::views::values) {
|
||||
auto& lobby = queue.PushDebug("Lobby " + std::to_string(i++));
|
||||
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue->timer;
|
||||
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue.timer;
|
||||
|
||||
auto& players = lobby.PushDebug("Players");
|
||||
for (const auto* player : lobbyQueue->players) {
|
||||
if (!player) continue;
|
||||
auto* playerEntity = player->GetEntity();
|
||||
for (const auto& player : lobbyQueue.players) {
|
||||
const auto* const playerEntity = player.GetEntity();
|
||||
if (!playerEntity) continue;
|
||||
auto* character = playerEntity->GetCharacter();
|
||||
if (!character) continue;
|
||||
|
||||
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player->ready ? "Ready" : "Not Ready";
|
||||
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player.ready ? "Ready" : "Not Ready";
|
||||
}
|
||||
}
|
||||
|
||||
auto& activityPlayers = activityInfo.PushDebug("Activity Players");
|
||||
for (const auto* activityPlayer : m_ActivityPlayers) {
|
||||
if (!activityPlayer) continue;
|
||||
auto* const activityPlayerEntity = Game::entityManager->GetEntity(activityPlayer->playerID);
|
||||
for (const auto& [playerID, playerScores] : m_ActivityPlayers) {
|
||||
auto* const activityPlayerEntity = Game::entityManager->GetEntity(playerID);
|
||||
if (!activityPlayerEntity) continue;
|
||||
auto* character = activityPlayerEntity->GetCharacter();
|
||||
if (!character) continue;
|
||||
|
||||
auto& playerData = activityPlayers.PushDebug(std::to_string(activityPlayer->playerID) + " " + character->GetName());
|
||||
auto& playerData = activityPlayers.PushDebug(std::to_string(playerID) + " " + character->GetName());
|
||||
|
||||
auto& scores = playerData.PushDebug("Scores");
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
scores.PushDebug<AMFDoubleValue>(std::to_string(i)) = activityPlayer->values[i];
|
||||
scores.PushDebug<AMFDoubleValue>(std::to_string(i)) = playerScores[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
activityInfo.PushDebug<AMFIntValue>("ActivityID") = m_ActivityID;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
#include "CDActivitiesTable.h"
|
||||
#include <array>
|
||||
|
||||
namespace GameMessages {
|
||||
class GameMsg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an instance of an activity, having participants and score
|
||||
*/
|
||||
/**
|
||||
* Represents an instance of an activity, having participants and score
|
||||
*/
|
||||
class ActivityInstance {
|
||||
public:
|
||||
ActivityInstance(Entity* parent, CDActivities activityInfo) { m_Parent = parent; m_ActivityInfo = activityInfo; };
|
||||
@@ -104,7 +105,7 @@ struct LobbyPlayer {
|
||||
/**
|
||||
* The ID of the entity that is in the lobby
|
||||
*/
|
||||
LWOOBJID entityID;
|
||||
LWOOBJID entityID = LWOOBJID_EMPTY;
|
||||
|
||||
/**
|
||||
* Whether or not the entity is ready
|
||||
@@ -126,12 +127,12 @@ struct Lobby {
|
||||
/**
|
||||
* The lobby of players
|
||||
*/
|
||||
std::vector<LobbyPlayer*> players;
|
||||
std::vector<LobbyPlayer> players;
|
||||
|
||||
/**
|
||||
* The timer that determines when the activity should start
|
||||
*/
|
||||
float timer;
|
||||
float timer{};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -142,12 +143,12 @@ struct ActivityPlayer {
|
||||
/**
|
||||
* The entity that the score is tracked for
|
||||
*/
|
||||
LWOOBJID playerID;
|
||||
LWOOBJID playerID{};
|
||||
|
||||
/**
|
||||
* The list of score for this entity
|
||||
*/
|
||||
float values[10];
|
||||
float values[10]{};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -194,13 +195,13 @@ public:
|
||||
* @param instance the instance to load the players into
|
||||
* @param lobby the players to load into the instance
|
||||
*/
|
||||
void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const;
|
||||
void LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const;
|
||||
|
||||
/**
|
||||
* Removes a lobby from the activity manager
|
||||
* @param lobby the lobby to remove
|
||||
*/
|
||||
void RemoveLobby(Lobby* lobby);
|
||||
void RemoveLobby(const LWOOBJID lobbyID);
|
||||
|
||||
/**
|
||||
* Marks a player as (un)ready in a lobby
|
||||
@@ -246,7 +247,7 @@ public:
|
||||
*/
|
||||
bool IsPlayedBy(LWOOBJID playerID) const;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Checks if the entity has enough cost to play this activity
|
||||
* @param player the entity to check
|
||||
* @return true if the entity has enough cost to play this activity, false otherwise
|
||||
@@ -271,20 +272,14 @@ public:
|
||||
* Creates a new instance for this activity
|
||||
* @return a new instance for this activity
|
||||
*/
|
||||
ActivityInstance* NewInstance();
|
||||
|
||||
/**
|
||||
* Returns all the currently active instances of this activity
|
||||
* @return all the currently active instances of this activity
|
||||
*/
|
||||
const std::vector<ActivityInstance*>& GetInstances() const;
|
||||
ActivityInstance& NewInstance();
|
||||
|
||||
/**
|
||||
* Returns the instance that some entity is currently playing in
|
||||
* @param playerID the entity to check for
|
||||
* @return if any, the instance that the entity is currently in
|
||||
*/
|
||||
ActivityInstance* GetInstance(const LWOOBJID playerID);
|
||||
const ActivityInstance& GetInstance(const LWOOBJID playerID) const;
|
||||
|
||||
/**
|
||||
* @brief Reloads the config settings for this component
|
||||
@@ -292,23 +287,12 @@ public:
|
||||
*/
|
||||
void ReloadConfig();
|
||||
|
||||
/**
|
||||
* Removes all the instances
|
||||
*/
|
||||
void ClearInstances();
|
||||
|
||||
/**
|
||||
* Returns all the score for the players that are currently playing this activity
|
||||
* @return
|
||||
*/
|
||||
std::vector<ActivityPlayer*> GetActivityPlayers() { return m_ActivityPlayers; };
|
||||
|
||||
/**
|
||||
* Returns activity data for a specific entity (e.g. score and such).
|
||||
* @param playerID the entity to get data for
|
||||
* @return the activity data (score) for the passed player in this activity, if it exists
|
||||
*/
|
||||
ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID);
|
||||
bool PlayerHasActivityData(LWOOBJID playerID) const;
|
||||
|
||||
/**
|
||||
* Sets some score value for an entity
|
||||
@@ -324,7 +308,7 @@ public:
|
||||
* @param index the index to get score for
|
||||
* @return activity score for the passed parameters
|
||||
*/
|
||||
float_t GetActivityValue(LWOOBJID playerID, uint32_t index);
|
||||
float_t GetActivityValue(LWOOBJID playerID, uint32_t index) const;
|
||||
|
||||
/**
|
||||
* Removes activity score tracking for some entity
|
||||
@@ -332,13 +316,6 @@ public:
|
||||
*/
|
||||
void RemoveActivityPlayerData(LWOOBJID playerID);
|
||||
|
||||
/**
|
||||
* Adds activity score tracking for some entity
|
||||
* @param playerID the entity to add the activity score for
|
||||
* @return the created entry
|
||||
*/
|
||||
ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID);
|
||||
|
||||
/**
|
||||
* Sets the mapID that this activity points to
|
||||
* @param mapID the map ID to set
|
||||
@@ -346,8 +323,7 @@ public:
|
||||
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
|
||||
|
||||
private:
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& msg);
|
||||
/**
|
||||
* The database information for this activity
|
||||
*/
|
||||
@@ -356,17 +332,17 @@ private:
|
||||
/**
|
||||
* All the active instances of this activity
|
||||
*/
|
||||
std::vector<ActivityInstance*> m_Instances;
|
||||
std::vector<ActivityInstance> m_Instances;
|
||||
|
||||
/**
|
||||
* The current lobbies for this activity
|
||||
*/
|
||||
std::vector<Lobby*> m_Queue;
|
||||
std::map<LWOOBJID, Lobby> m_Queue;
|
||||
|
||||
/**
|
||||
* All the activity score for the players in this activity
|
||||
*/
|
||||
std::vector<ActivityPlayer*> m_ActivityPlayers;
|
||||
std::map<LWOOBJID, std::array<float, 10>> m_ActivityPlayers;
|
||||
|
||||
/**
|
||||
* The activity id
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
#include "CDClientDatabase.h"
|
||||
#include "CDClientManager.h"
|
||||
#include "CDObjectSkillsTable.h"
|
||||
#include "CDSkillBehaviorTable.h"
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -23,17 +25,13 @@
|
||||
#include "SkillComponent.h"
|
||||
#include "QuickBuildComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Metrics.hpp"
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDPhysicsComponentTable.h"
|
||||
#include "dNavMesh.h"
|
||||
#include "Amf3.h"
|
||||
|
||||
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
{
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &BaseCombatAIComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
RegisterMsg(&BaseCombatAIComponent::MsgGetObjectReportInfo);
|
||||
m_Target = LWOOBJID_EMPTY;
|
||||
m_DirtyStateOrTarget = true;
|
||||
m_State = AiState::spawn;
|
||||
@@ -47,7 +45,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
|
||||
|
||||
//Grab the aggro information from BaseCombatAI:
|
||||
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
|
||||
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius, minRoundLength, maxRoundLength, combatRoundLength FROM BaseCombatAIComponent WHERE id = ?;");
|
||||
componentQuery.bind(1, static_cast<int>(componentID));
|
||||
|
||||
auto componentResult = componentQuery.execQuery();
|
||||
@@ -67,44 +65,37 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
|
||||
|
||||
if (!componentResult.fieldIsNull("hardTetherRadius"))
|
||||
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
|
||||
|
||||
m_MinRoundLength = componentResult.getFloatField("minRoundLength");
|
||||
m_MaxRoundLength = componentResult.getFloatField("maxRoundLength");
|
||||
m_CombatRoundLength = componentResult.getFloatField("combatRoundLength");
|
||||
}
|
||||
|
||||
componentResult.finalize();
|
||||
|
||||
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
|
||||
// radii if it is greater than the one in the database.
|
||||
if (m_Parent) {
|
||||
auto aggroRadius = m_Parent->GetVar<float>(u"aggroRadius");
|
||||
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
|
||||
auto tetherRadius = m_Parent->GetVar<float>(u"tetherRadius");
|
||||
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
|
||||
}
|
||||
m_AggroRadius = m_Parent->HasVar(u"aggroRadius") ? m_Parent->GetVar<float>(u"aggroRadius") : m_AggroRadius;
|
||||
m_HardTetherRadius = m_Parent->HasVar(u"tetherRadius") ? m_Parent->GetVar<float>(u"tetherRadius") : m_HardTetherRadius;
|
||||
|
||||
/*
|
||||
* Find skills
|
||||
*/
|
||||
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
|
||||
skillQuery.bind(1, static_cast<int>(parent->GetLOT()));
|
||||
for (const auto objectSkill : CDClientManager::GetTable<CDObjectSkillsTable>()->Get(parent->GetLOT())) {
|
||||
const auto skillBehavior = CDClientManager::GetTable<CDSkillBehaviorTable>()->GetSkillByID(objectSkill.skillID);
|
||||
if (skillBehavior.skillID == objectSkill.skillID) {
|
||||
const auto skillId = skillBehavior.skillID;
|
||||
|
||||
auto result = skillQuery.execQuery();
|
||||
const auto abilityCooldown = skillBehavior.cooldown;
|
||||
|
||||
while (!result.eof()) {
|
||||
const auto skillId = static_cast<uint32_t>(result.getIntField("skillID"));
|
||||
const auto behaviorId = skillBehavior.behaviorID;
|
||||
|
||||
const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown"));
|
||||
const auto combatWeight = objectSkill.AICombatWeight;
|
||||
|
||||
const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
AiSkillEntry entry = { .skillId = skillId, .cooldown = 0.0f, .abilityCooldown = abilityCooldown, .behavior = behavior, .combatWeight = combatWeight };
|
||||
|
||||
std::stringstream behaviorQuery;
|
||||
|
||||
AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior };
|
||||
|
||||
m_SkillEntries.push_back(entry);
|
||||
|
||||
result.nextRow();
|
||||
m_SkillEntries.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
Stun(1.0f);
|
||||
@@ -214,8 +205,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
}
|
||||
|
||||
if (stunnedThisFrame) {
|
||||
m_MovementAI->Stop();
|
||||
if (!m_MovementAI->IsPaused()) m_MovementAI->Pause();
|
||||
|
||||
// in this case we just become unstunned so check if we paused and resume if we did
|
||||
if (!m_Stunned && m_MovementAI->IsPaused()) m_MovementAI->Resume();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -250,10 +243,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
|
||||
void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
bool hasSkillToCast = false;
|
||||
int32_t maxSkillWeights = 0;
|
||||
for (auto& entry : m_SkillEntries) {
|
||||
if (entry.cooldown > 0.0f) {
|
||||
entry.cooldown -= deltaTime;
|
||||
} else {
|
||||
maxSkillWeights += entry.combatWeight;
|
||||
hasSkillToCast = true;
|
||||
}
|
||||
}
|
||||
@@ -321,12 +316,14 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
SetAiState(AiState::aggro);
|
||||
} else {
|
||||
SetAiState(AiState::idle);
|
||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
}
|
||||
|
||||
if (!hasSkillToCast) return;
|
||||
|
||||
if (m_Target == LWOOBJID_EMPTY) {
|
||||
SetAiState(AiState::idle);
|
||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -337,14 +334,23 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
LookAt(target->GetPosition());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < m_SkillEntries.size(); ++i) {
|
||||
auto entry = m_SkillEntries.at(i);
|
||||
// Roll to find which skill we'll try to cast
|
||||
auto randomizedWeight = GeneralUtils::GenerateRandomNumber<int32_t>(0, maxSkillWeights);
|
||||
|
||||
if (entry.cooldown > 0) {
|
||||
for (auto& entry : m_SkillEntries) {
|
||||
// Skill isn't cooled off yet
|
||||
if (entry.cooldown > 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY);
|
||||
randomizedWeight -= entry.combatWeight;
|
||||
|
||||
// if the weight is still greater than 0 continue to the next rolled skill
|
||||
if (randomizedWeight > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget());
|
||||
|
||||
if (result.success) {
|
||||
if (m_MovementAI != nullptr) {
|
||||
@@ -359,8 +365,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
|
||||
entry.cooldown = entry.abilityCooldown + m_SkillTime;
|
||||
|
||||
m_SkillEntries[i] = entry;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -481,6 +485,7 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
|
||||
|
||||
for (auto id : m_Parent->GetTargetsInPhantom()) {
|
||||
auto* other = Game::entityManager->GetEntity(id);
|
||||
if (!other) continue;
|
||||
|
||||
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());
|
||||
|
||||
@@ -621,6 +626,11 @@ void BaseCombatAIComponent::Wander() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a path to follow we should almost certainly do that instead of wandering.
|
||||
if (m_MovementAI->HasPath()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_MovementAI->SetHaltDistance(0);
|
||||
|
||||
const auto& info = m_MovementAI->GetInfo();
|
||||
@@ -749,8 +759,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
|
||||
m_TetherSpeed = value;
|
||||
}
|
||||
|
||||
void BaseCombatAIComponent::Stun(const float time) {
|
||||
if (m_StunImmune || m_StunTime > time) {
|
||||
void BaseCombatAIComponent::Stun(const float time, const bool force) {
|
||||
if (!force && (m_StunImmune || m_StunTime > time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -845,10 +855,9 @@ void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float valu
|
||||
m_Target = LWOOBJID_EMPTY;
|
||||
}
|
||||
|
||||
bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
using enum AiState;
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.info->PushDebug("Base Combat AI");
|
||||
auto& cmptType = reportInfo.info->PushDebug("Base Combat AI");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
auto& targetInfo = cmptType.PushDebug("Current Target Info");
|
||||
targetInfo.PushDebug<AMFStringValue>("Current Target ID") = std::to_string(m_Target);
|
||||
@@ -866,12 +875,12 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
||||
std::string curState;
|
||||
switch (m_State) {
|
||||
case idle: curState = "Idling"; break;
|
||||
case aggro: curState = "Aggroed"; break;
|
||||
case tether: curState = "Returning to Tether"; break;
|
||||
case spawn: curState = "Spawn"; break;
|
||||
case dead: curState = "Dead"; break;
|
||||
default: curState = "Unknown or Undefined"; break;
|
||||
case idle: curState = "Idling"; break;
|
||||
case aggro: curState = "Aggroed"; break;
|
||||
case tether: curState = "Returning to Tether"; break;
|
||||
case spawn: curState = "Spawn"; break;
|
||||
case dead: curState = "Dead"; break;
|
||||
default: curState = "Unknown or Undefined"; break;
|
||||
}
|
||||
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
||||
|
||||
@@ -909,8 +918,16 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
}
|
||||
|
||||
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
|
||||
for (const auto& [id, threat] : m_ThreatEntries) {
|
||||
for (const auto& [id, threat] : m_RemovedThreatList) {
|
||||
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
|
||||
}
|
||||
auto& skillInfo = cmptType.PushDebug("Skill Info");
|
||||
for (const auto& skill : m_SkillEntries) {
|
||||
auto& skillDebug = skillInfo.PushDebug("Skill ID " + std::to_string(skill.skillId));
|
||||
skillDebug.PushDebug<AMFDoubleValue>("Cooldown") = skill.cooldown;
|
||||
skillDebug.PushDebug<AMFDoubleValue>("Ability Cooldown") = skill.abilityCooldown;
|
||||
skillDebug.PushDebug<AMFIntValue>("AI Combat Weight") = skill.combatWeight;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -33,13 +33,15 @@ enum class AiState : uint32_t {
|
||||
*/
|
||||
struct AiSkillEntry
|
||||
{
|
||||
uint32_t skillId;
|
||||
uint32_t skillId{};
|
||||
|
||||
float cooldown;
|
||||
float cooldown{};
|
||||
|
||||
float abilityCooldown;
|
||||
float abilityCooldown{};
|
||||
|
||||
Behavior* behavior;
|
||||
Behavior* behavior{};
|
||||
|
||||
int32_t combatWeight{};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -181,8 +183,9 @@ public:
|
||||
/**
|
||||
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
|
||||
* @param time the time to stun the entity, if stunnable
|
||||
* @param force whether or not to force the stun and ignore checks
|
||||
*/
|
||||
void Stun(float time);
|
||||
void Stun(float time, const bool force = false);
|
||||
|
||||
/**
|
||||
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
|
||||
@@ -234,7 +237,9 @@ public:
|
||||
// Ignore a threat for a certain amount of time
|
||||
void IgnoreThreat(const LWOOBJID target, const float time);
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; }
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -394,9 +399,17 @@ private:
|
||||
*/
|
||||
bool m_DirtyStateOrTarget = false;
|
||||
|
||||
// Min amount of time to remain as in combat after casting a skill
|
||||
float m_MinRoundLength = 0.0f;
|
||||
|
||||
// max amount of time to remain as in combat after casting a skill
|
||||
float m_MaxRoundLength = 0.0f;
|
||||
|
||||
// The amount of time the entity will be forced to tether for
|
||||
float m_ForcedTetherTime = 0.0f;
|
||||
|
||||
float m_CombatRoundLength = 0.0f;
|
||||
|
||||
// The amount of time a removed threat will be ignored for.
|
||||
std::map<LWOOBJID, float> m_RemovedThreatList;
|
||||
|
||||
|
||||
@@ -31,10 +31,7 @@ BouncerComponent::BouncerComponent(Entity* parent, const int32_t componentID) :
|
||||
LookupPetSwitch();
|
||||
}
|
||||
|
||||
{
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &BouncerComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
RegisterMsg(&BouncerComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
|
||||
BouncerComponent::~BouncerComponent() {
|
||||
@@ -113,9 +110,8 @@ void BouncerComponent::LookupPetSwitch() {
|
||||
}
|
||||
}
|
||||
|
||||
bool BouncerComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.info->PushDebug("Bouncer");
|
||||
bool BouncerComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& cmptType = reportInfo.info->PushDebug("Bouncer");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
auto& destPos = cmptType.PushDebug("Destination Position");
|
||||
if (m_Destination != NiPoint3Constant::ZERO) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
*/
|
||||
void LookupPetSwitch();
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -450,19 +450,10 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
|
||||
param.value = result.getFloatField("NumberValue");
|
||||
param.effectId = result.getIntField("EffectID");
|
||||
|
||||
if (!result.fieldIsNull("StringValue")) {
|
||||
std::istringstream stream(result.getStringField("StringValue"));
|
||||
std::string token;
|
||||
|
||||
while (std::getline(stream, token, ',')) {
|
||||
try {
|
||||
const auto value = std::stof(token);
|
||||
|
||||
param.values.push_back(value);
|
||||
} catch (std::invalid_argument& exception) {
|
||||
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
|
||||
}
|
||||
}
|
||||
for (const auto& str : GeneralUtils::SplitString(result.getStringField("StringValue"), ',')) {
|
||||
if (str.empty()) continue;
|
||||
const auto value = GeneralUtils::TryParse<float>(str);
|
||||
if (value) param.values.push_back(value.value());
|
||||
}
|
||||
|
||||
parameters.push_back(param);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "WorldPackets.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include <ctime>
|
||||
#include <ranges>
|
||||
|
||||
CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) {
|
||||
m_Character = character;
|
||||
@@ -49,11 +50,10 @@ CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID
|
||||
m_LastUpdateTimestamp = std::time(nullptr);
|
||||
m_SystemAddress = systemAddress;
|
||||
|
||||
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &CharacterComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&CharacterComponent::OnGetObjectReportInfo);
|
||||
}
|
||||
|
||||
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
|
||||
auto& cmptType = reportInfo.info->PushDebug("Character");
|
||||
|
||||
@@ -492,7 +492,7 @@ Item* CharacterComponent::RocketEquip(Entity* player) {
|
||||
if (!rocket) return rocket;
|
||||
|
||||
// build and define the rocket config
|
||||
for (LDFBaseData* data : rocket->GetConfig()) {
|
||||
for (const auto& data : rocket->GetConfig().values | std::views::values) {
|
||||
if (data->GetKey() == u"assemblyPartLOTs") {
|
||||
std::string newRocketStr = data->GetValueAsString() + ";";
|
||||
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");
|
||||
@@ -798,8 +798,14 @@ std::string CharacterComponent::StatisticsToString() const {
|
||||
return result.str();
|
||||
}
|
||||
|
||||
uint64_t CharacterComponent::GetStatisticFromSplit(std::vector<std::string> split, uint32_t index) {
|
||||
return split.size() > index ? std::stoull(split.at(index)) : 0;
|
||||
uint64_t CharacterComponent::GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index) {
|
||||
uint64_t toReturn = 0;
|
||||
if (index < split.size()) {
|
||||
const auto parsed = GeneralUtils::TryParse<uint64_t>(split[index]);
|
||||
if (parsed) toReturn = *parsed;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {
|
||||
|
||||
@@ -17,6 +17,10 @@ enum class eGameActivity : uint32_t;
|
||||
|
||||
class Item;
|
||||
|
||||
namespace GameMessages {
|
||||
struct GetObjectReportInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The statistics that can be achieved per zone
|
||||
*/
|
||||
@@ -331,7 +335,7 @@ public:
|
||||
void LoadVisitedLevelsXml(const tinyxml2::XMLElement& doc);
|
||||
private:
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
/**
|
||||
* The map of active venture vision effects
|
||||
@@ -446,7 +450,7 @@ private:
|
||||
* @param index the statistics ID in the string
|
||||
* @return the integer value of this statistic, parsed from the string
|
||||
*/
|
||||
static uint64_t GetStatisticFromSplit(std::vector<std::string> split, uint32_t index);
|
||||
static uint64_t GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index);
|
||||
|
||||
/**
|
||||
* Gets all the statistics for a certain map, if it doesn't exist, it creates empty stats
|
||||
@@ -522,6 +526,7 @@ private:
|
||||
|
||||
/**
|
||||
* Total amount of meters traveled by this character
|
||||
* Should be a double and then truncated so decimals can be tracked
|
||||
*/
|
||||
uint64_t m_MetersTraveled;
|
||||
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
|
||||
CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) :
|
||||
Component(parentEntity, componentID), m_CollectibleId(collectibleId) {
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &CollectibleComponent::MsgGetObjectReportInfo);
|
||||
RegisterMsg(&CollectibleComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
|
||||
void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
|
||||
outBitStream.Write(GetCollectibleId());
|
||||
}
|
||||
|
||||
bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) {
|
||||
auto& cmptType = reportMsg.info->PushDebug("Collectible");
|
||||
auto collectibleID = static_cast<uint32_t>(m_CollectibleId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
int16_t GetCollectibleId() const { return m_CollectibleId; }
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) override;
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
private:
|
||||
int16_t m_CollectibleId = 0;
|
||||
};
|
||||
|
||||
@@ -55,17 +55,18 @@ public:
|
||||
virtual void LoadFromXml(const tinyxml2::XMLDocument& doc) {}
|
||||
|
||||
virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {}
|
||||
|
||||
protected:
|
||||
template<typename GameObjClass, typename DerivedMsg>
|
||||
inline void RegisterMsg(bool (GameObjClass::*handler)(DerivedMsg&)) {
|
||||
static_assert(std::is_base_of_v<GameMessages::GameMsg, DerivedMsg>, "DerivedMsg must inherit from GameMsg");
|
||||
static_assert(std::is_base_of_v<Component, GameObjClass>, "GameObjClass must inherit from Component");
|
||||
const auto handlerBound = std::bind(handler, static_cast<GameObjClass*>(this), std::placeholders::_1);
|
||||
const auto castWrapper = [handlerBound](GameMessages::GameMsg& msg) {
|
||||
return handlerBound(static_cast<DerivedMsg&>(msg));
|
||||
};
|
||||
|
||||
inline void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
|
||||
m_Parent->RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void RegisterMsg(auto* self, const auto handler) {
|
||||
T msg;
|
||||
RegisterMsg(msg.msgId, self, handler);
|
||||
DerivedMsg msg;
|
||||
m_Parent->RegisterMsg(msg.msgId, castWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "Amf3.h"
|
||||
|
||||
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, const int32_t componentID) : PhysicsComponent(entity, componentID) {
|
||||
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &ControllablePhysicsComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&ControllablePhysicsComponent::OnGetObjectReportInfo);
|
||||
|
||||
m_Velocity = {};
|
||||
m_AngularVelocity = {};
|
||||
@@ -359,9 +359,8 @@ void ControllablePhysicsComponent::SetStunImmunity(
|
||||
);
|
||||
}
|
||||
|
||||
bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(msg);
|
||||
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
|
||||
auto& info = reportInfo.subCategory->PushDebug("Controllable Info");
|
||||
|
||||
auto& vel = info.PushDebug("Velocity");
|
||||
|
||||
@@ -284,7 +284,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
/**
|
||||
* The entity that owns this component
|
||||
*/
|
||||
|
||||
@@ -48,7 +48,6 @@ Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
|
||||
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
|
||||
|
||||
DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
using namespace GameMessages;
|
||||
m_iArmor = 0;
|
||||
m_fMaxArmor = 0.0f;
|
||||
m_iImagination = 0;
|
||||
@@ -86,9 +85,9 @@ DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t compone
|
||||
|
||||
m_DamageCooldownTimer = 0.0f;
|
||||
|
||||
RegisterMsg<GetObjectReportInfo>(this, &DestroyableComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg<GameMessages::SetFaction>(this, &DestroyableComponent::OnSetFaction);
|
||||
RegisterMsg<GameMessages::IsDead>(this, &DestroyableComponent::OnIsDead);
|
||||
RegisterMsg(&DestroyableComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&DestroyableComponent::OnSetFaction);
|
||||
RegisterMsg(&DestroyableComponent::OnIsDead);
|
||||
}
|
||||
|
||||
DestroyableComponent::~DestroyableComponent() {
|
||||
@@ -794,7 +793,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
|
||||
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
|
||||
for (Entity* scriptEntity : scriptedActs) {
|
||||
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
|
||||
if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
|
||||
scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_Parent);
|
||||
}
|
||||
}
|
||||
@@ -882,9 +881,9 @@ void DestroyableComponent::FixStats() {
|
||||
int32_t currentImagination = destroyableComponent->GetImagination();
|
||||
|
||||
// Unequip all items
|
||||
auto equipped = inventoryComponent->GetEquippedItems();
|
||||
const auto equipped = inventoryComponent->GetEquippedItems();
|
||||
|
||||
for (auto& equippedItem : equipped) {
|
||||
for (const auto& equippedItem : equipped) {
|
||||
// Get the item with the item ID
|
||||
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);
|
||||
|
||||
@@ -925,7 +924,7 @@ void DestroyableComponent::FixStats() {
|
||||
buffComponent->ReApplyBuffs();
|
||||
|
||||
// Requip all items
|
||||
for (auto& equippedItem : equipped) {
|
||||
for (const auto& equippedItem : equipped) {
|
||||
// Get the item with the item ID
|
||||
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);
|
||||
|
||||
@@ -965,6 +964,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
|
||||
if (m_Parent->IsPlayer()) {
|
||||
//remove hardcore_lose_uscore_on_death_percent from the player's uscore:
|
||||
auto* character = m_Parent->GetComponent<CharacterComponent>();
|
||||
if (!character) return;
|
||||
|
||||
auto uscore = character->GetUScore();
|
||||
|
||||
auto uscoreToLose = static_cast<uint64_t>(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f));
|
||||
@@ -1061,8 +1062,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& destroyableInfo = reportInfo.info->PushDebug("Destroyable");
|
||||
destroyableInfo.PushDebug<AMFIntValue>("DestructibleComponent DB Table Template ID") = m_ComponentID;
|
||||
|
||||
@@ -1184,16 +1184,14 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DestroyableComponent::OnSetFaction(GameMessages::GameMsg& msg) {
|
||||
auto& modifyFaction = static_cast<GameMessages::SetFaction&>(msg);
|
||||
bool DestroyableComponent::OnSetFaction(GameMessages::SetFaction& setFaction) {
|
||||
m_DirtyHealth = true;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
SetFaction(modifyFaction.factionID, modifyFaction.bIgnoreChecks);
|
||||
SetFaction(setFaction.factionID, setFaction.bIgnoreChecks);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DestroyableComponent::OnIsDead(GameMessages::GameMsg& msg) {
|
||||
auto& isDeadMsg = static_cast<GameMessages::IsDead&>(msg);
|
||||
isDeadMsg.bDead = m_IsDead || (GetHealth() == 0 && GetArmor() == 0);
|
||||
bool DestroyableComponent::OnIsDead(GameMessages::IsDead& isDead) {
|
||||
isDead.bDead = m_IsDead || (GetHealth() == 0 && GetArmor() == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace GameMessages {
|
||||
struct GetObjectReportInfo;
|
||||
struct SetFaction;
|
||||
struct IsDead;
|
||||
};
|
||||
|
||||
namespace CppScripts {
|
||||
@@ -470,9 +472,9 @@ public:
|
||||
// handle hardcode mode drops
|
||||
void DoHardcoreModeDrops(const LWOOBJID source);
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnSetFaction(GameMessages::GameMsg& msg);
|
||||
bool OnIsDead(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
bool OnSetFaction(GameMessages::SetFaction& setFaction);
|
||||
bool OnIsDead(GameMessages::IsDead& isDead);
|
||||
|
||||
void SetIsDead(const bool value) { m_IsDead = value; }
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Comp
|
||||
m_GhostOverridePoint = NiPoint3Constant::ZERO;
|
||||
m_GhostOverride = false;
|
||||
|
||||
RegisterMsg<GameMessages::ToggleGMInvis>(this, &GhostComponent::OnToggleGMInvis);
|
||||
RegisterMsg<GameMessages::GetGMInvis>(this, &GhostComponent::OnGetGMInvis);
|
||||
RegisterMsg<GameMessages::GetObjectReportInfo>(this, &GhostComponent::MsgGetObjectReportInfo);
|
||||
RegisterMsg(&GhostComponent::OnToggleGMInvis);
|
||||
RegisterMsg(&GhostComponent::OnGetGMInvis);
|
||||
RegisterMsg(&GhostComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
|
||||
GhostComponent::~GhostComponent() {
|
||||
@@ -29,26 +29,6 @@ GhostComponent::~GhostComponent() {
|
||||
}
|
||||
}
|
||||
|
||||
void GhostComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
|
||||
auto* objElement = doc.FirstChildElement("obj");
|
||||
if (!objElement) return;
|
||||
auto* ghstElement = objElement->FirstChildElement("ghst");
|
||||
if (!ghstElement) return;
|
||||
m_IsGMInvisible = ghstElement->BoolAttribute("i");
|
||||
}
|
||||
|
||||
void GhostComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
|
||||
auto* objElement = doc.FirstChildElement("obj");
|
||||
if (!objElement) return;
|
||||
auto* ghstElement = objElement->FirstChildElement("ghst");
|
||||
if (ghstElement) objElement->DeleteChild(ghstElement);
|
||||
// Only save if GM invisible
|
||||
const auto* const user = UserManager::Instance()->GetUser(m_Parent->GetSystemAddress());
|
||||
if (!m_IsGMInvisible || !user || user->GetMaxGMLevel() < eGameMasterLevel::FORUM_MODERATOR) return;
|
||||
ghstElement = objElement->InsertNewChildElement("ghst");
|
||||
if (ghstElement) ghstElement->SetAttribute("i", m_IsGMInvisible);
|
||||
}
|
||||
|
||||
void GhostComponent::SetGhostReferencePoint(const NiPoint3& value) {
|
||||
m_GhostReferencePoint = value;
|
||||
}
|
||||
@@ -88,15 +68,17 @@ void GhostComponent::GhostEntity(LWOOBJID id) {
|
||||
m_ObservedEntities.erase(id);
|
||||
}
|
||||
|
||||
bool GhostComponent::OnToggleGMInvis(GameMessages::GameMsg& msg) {
|
||||
// TODO: disabled for now while bugs are fixed
|
||||
return false;
|
||||
auto& gmInvisMsg = static_cast<GameMessages::ToggleGMInvis&>(msg);
|
||||
bool GhostComponent::OnToggleGMInvis(GameMessages::ToggleGMInvis& gmInvisMsg) {
|
||||
gmInvisMsg.bStateOut = !m_IsGMInvisible;
|
||||
m_IsGMInvisible = !m_IsGMInvisible;
|
||||
LOG_DEBUG("GM Invisibility toggled to: %s", m_IsGMInvisible ? "true" : "false");
|
||||
gmInvisMsg.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
auto* thisUser = UserManager::Instance()->GetUser(m_Parent->GetSystemAddress());
|
||||
if (!thisUser) {
|
||||
LOG("Unable to find user for entity %llu when toggling GM invisibility!", m_Parent->GetObjectID());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& player : PlayerManager::GetAllPlayers()) {
|
||||
if (!player || player->GetObjectID() == m_Parent->GetObjectID()) continue;
|
||||
auto* toUser = UserManager::Instance()->GetUser(player->GetSystemAddress());
|
||||
@@ -105,7 +87,7 @@ bool GhostComponent::OnToggleGMInvis(GameMessages::GameMsg& msg) {
|
||||
Game::entityManager->DestructEntity(m_Parent, player->GetSystemAddress());
|
||||
}
|
||||
} else {
|
||||
if (toUser->GetMaxGMLevel() >= thisUser->GetMaxGMLevel()) {
|
||||
if (toUser->GetMaxGMLevel() < thisUser->GetMaxGMLevel()) {
|
||||
Game::entityManager->ConstructEntity(m_Parent, player->GetSystemAddress());
|
||||
auto* controllableComp = m_Parent->GetComponent<ControllablePhysicsComponent>();
|
||||
controllableComp->SetDirtyPosition(true);
|
||||
@@ -117,18 +99,13 @@ bool GhostComponent::OnToggleGMInvis(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GhostComponent::OnGetGMInvis(GameMessages::GameMsg& msg) {
|
||||
bool GhostComponent::OnGetGMInvis(GameMessages::GetGMInvis& gmInvisMsg) {
|
||||
LOG_DEBUG("GM Invisibility requested: %s", m_IsGMInvisible ? "true" : "false");
|
||||
auto& gmInvisMsg = static_cast<GameMessages::GetGMInvis&>(msg);
|
||||
// TODO: disabled for now while bugs are fixed
|
||||
// gmInvisMsg.bGMInvis = m_IsGMInvisible;
|
||||
// return gmInvisMsg.bGMInvis;
|
||||
gmInvisMsg.bGMInvis = false;
|
||||
return false;
|
||||
gmInvisMsg.bGMInvis = m_IsGMInvisible;
|
||||
return gmInvisMsg.bGMInvis;
|
||||
}
|
||||
|
||||
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) {
|
||||
auto& cmptType = reportMsg.info->PushDebug("Ghost");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = false;
|
||||
|
||||
@@ -16,8 +16,6 @@ public:
|
||||
static inline const eReplicaComponentType ComponentType = eReplicaComponentType::GHOST;
|
||||
GhostComponent(Entity* parent, const int32_t componentID);
|
||||
~GhostComponent() override;
|
||||
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
|
||||
void UpdateXml(tinyxml2::XMLDocument& doc) override;
|
||||
|
||||
void SetGhostOverride(bool value) { m_GhostOverride = value; };
|
||||
|
||||
@@ -45,11 +43,11 @@ public:
|
||||
|
||||
void GhostEntity(const LWOOBJID id);
|
||||
|
||||
bool OnToggleGMInvis(GameMessages::GameMsg& msg);
|
||||
bool OnToggleGMInvis(GameMessages::ToggleGMInvis& msg);
|
||||
|
||||
bool OnGetGMInvis(GameMessages::GameMsg& msg);
|
||||
bool OnGetGMInvis(GameMessages::GetGMInvis& msg);
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& msg);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Amf3.h"
|
||||
|
||||
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) {
|
||||
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &HavokVehiclePhysicsComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&HavokVehiclePhysicsComponent::OnGetObjectReportInfo);
|
||||
|
||||
m_Velocity = NiPoint3Constant::ZERO;
|
||||
m_AngularVelocity = NiPoint3Constant::ZERO;
|
||||
@@ -102,9 +102,8 @@ void HavokVehiclePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo
|
||||
outBitStream.Write0();
|
||||
}
|
||||
|
||||
bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(msg);
|
||||
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
|
||||
if (!reportInfo.subCategory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
void SetRemoteInputInfo(const RemoteInputInfo&);
|
||||
|
||||
private:
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
NiPoint3 m_Velocity;
|
||||
NiPoint3 m_AngularVelocity;
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
#include <ranges>
|
||||
|
||||
InventoryComponent::InventoryComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &InventoryComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&InventoryComponent::OnGetObjectReportInfo);
|
||||
this->m_Dirty = true;
|
||||
this->m_Equipped = {};
|
||||
this->m_Pushed = {};
|
||||
@@ -174,7 +173,7 @@ void InventoryComponent::AddItem(
|
||||
const uint32_t count,
|
||||
eLootSourceType lootSourceType,
|
||||
eInventoryType inventoryType,
|
||||
const std::vector<LDFBaseData*>& config,
|
||||
const LwoNameValue& config,
|
||||
const LWOOBJID parent,
|
||||
const bool showFlyingLoot,
|
||||
bool isModMoveAndEquip,
|
||||
@@ -205,7 +204,7 @@ void InventoryComponent::AddItem(
|
||||
|
||||
auto* inventory = GetInventory(inventoryType);
|
||||
|
||||
if (!config.empty() || bound) {
|
||||
if (!config.values.empty() || bound) {
|
||||
const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot();
|
||||
|
||||
if (slot == -1) {
|
||||
@@ -357,7 +356,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
|
||||
const auto subkey = item->GetSubKey();
|
||||
|
||||
if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
|
||||
if (subkey == LWOOBJID_EMPTY && item->GetConfig().values.empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
|
||||
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
|
||||
|
||||
while (left > 0) {
|
||||
@@ -380,11 +379,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
isModMoveAndEquip = false;
|
||||
}
|
||||
} else {
|
||||
std::vector<LDFBaseData*> config;
|
||||
|
||||
for (auto* const data : item->GetConfig()) {
|
||||
config.push_back(data->Copy());
|
||||
}
|
||||
const auto config = item->GetConfig();
|
||||
|
||||
const auto delta = std::min<uint32_t>(item->GetCount(), count);
|
||||
|
||||
@@ -745,18 +740,17 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
|
||||
|
||||
outBitStream.Write0();
|
||||
|
||||
bool flag = !item.config.empty();
|
||||
bool flag = !item.config.values.empty();
|
||||
outBitStream.Write(flag);
|
||||
if (flag) {
|
||||
RakNet::BitStream ldfStream;
|
||||
ldfStream.Write<int32_t>(item.config.size()); // Key count
|
||||
for (LDFBaseData* data : item.config) {
|
||||
ldfStream.Write<int32_t>(item.config.values.size()); // Key count
|
||||
for (const auto& data : item.config.values | std::views::values) {
|
||||
if (data->GetKey() == u"assemblyPartLOTs") {
|
||||
std::string newRocketStr = data->GetValueAsString() + ";";
|
||||
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");
|
||||
LDFData<std::u16string>* ldf_data = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr));
|
||||
ldf_data->WriteToPacket(ldfStream);
|
||||
delete ldf_data;
|
||||
LDFData<std::u16string> ldf_data(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr));
|
||||
ldf_data.WriteToPacket(ldfStream);
|
||||
} else {
|
||||
data->WriteToPacket(ldfStream);
|
||||
}
|
||||
@@ -783,7 +777,7 @@ void InventoryComponent::Update(float deltaTime) {
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent) {
|
||||
void InventoryComponent::UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent) {
|
||||
const auto index = m_Equipped.find(location);
|
||||
|
||||
if (index != m_Equipped.end()) {
|
||||
@@ -967,8 +961,9 @@ void InventoryComponent::EquipScripts(Item* equippedItem) {
|
||||
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
|
||||
if (!itemScript) {
|
||||
LOG("null script?");
|
||||
} else {
|
||||
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
|
||||
}
|
||||
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,8 +977,9 @@ void InventoryComponent::UnequipScripts(Item* unequippedItem) {
|
||||
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
|
||||
if (!itemScript) {
|
||||
LOG("null script?");
|
||||
} else {
|
||||
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
|
||||
}
|
||||
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1079,7 +1075,7 @@ void InventoryComponent::PushEquippedItems() {
|
||||
}
|
||||
|
||||
void InventoryComponent::PopEquippedItems() {
|
||||
auto current = m_Equipped;
|
||||
const auto current = m_Equipped;
|
||||
|
||||
for (const auto& pair : current) {
|
||||
auto* const item = FindItemById(pair.second.id);
|
||||
@@ -1634,7 +1630,7 @@ void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) {
|
||||
DatabasePet databasePet;
|
||||
databasePet.lot = lot;
|
||||
databasePet.moderationState = moderationStatus;
|
||||
databasePet.name = std::string(name);
|
||||
databasePet.name = name ? name : "";
|
||||
|
||||
SetDatabasePet(id, databasePet);
|
||||
|
||||
@@ -1847,9 +1843,8 @@ std::string DebugInvToString(const eInventoryType inv, bool verbose) {
|
||||
return "";
|
||||
}
|
||||
|
||||
bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& report = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmpt = report.info->PushDebug("Inventory");
|
||||
bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& cmpt = reportInfo.info->PushDebug("Inventory");
|
||||
cmpt.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
uint32_t numItems = 0;
|
||||
for (auto* inventory : m_Inventories | std::views::values) numItems += inventory->GetItems().size();
|
||||
@@ -1859,7 +1854,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
for (const auto& [id, inventoryMut] : m_Inventories) {
|
||||
if (!inventoryMut) continue;
|
||||
const auto* const inventory = inventoryMut;
|
||||
auto& curInv = itemsInBags.PushDebug(DebugInvToString(id, report.bVerbose) + " - " + std::to_string(id));
|
||||
auto& curInv = itemsInBags.PushDebug(DebugInvToString(id, reportInfo.bVerbose) + " - " + std::to_string(id));
|
||||
for (uint32_t i = 0; i < inventory->GetSize(); i++) {
|
||||
const auto* const item = inventory->FindItemBySlot(i);
|
||||
if (!item) continue;
|
||||
@@ -1876,7 +1871,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
slot.PushDebug<AMFBoolValue>("Bind on equip") = item->GetInfo().isBOE;
|
||||
slot.PushDebug<AMFBoolValue>("Is currently bound") = item->GetBound();
|
||||
auto& extra = slot.PushDebug("Extra Info");
|
||||
for (const auto* const setting : item->GetConfig()) {
|
||||
for (const auto& setting : item->GetConfig().values | std::views::values) {
|
||||
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
|
||||
}
|
||||
}
|
||||
@@ -1892,7 +1887,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
equipSlot.PushDebug<AMFIntValue>("Slot") = info.slot;
|
||||
equipSlot.PushDebug<AMFIntValue>("Count") = info.count;
|
||||
auto& extra = equipSlot.PushDebug("Extra Info");
|
||||
for (const auto* const setting : info.config) {
|
||||
for (const auto& setting : info.config.values | std::views::values) {
|
||||
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ typedef std::map<std::string, EquippedItem> EquipmentMap;
|
||||
|
||||
enum class eItemType : int32_t;
|
||||
|
||||
namespace GameMessages {
|
||||
struct GetObjectReportInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the inventory of entity, including the items they possess and have equipped. An entity can have inventories
|
||||
* of different types, each type representing a different group of items, see `eInventoryType` for a list of
|
||||
@@ -130,7 +134,7 @@ public:
|
||||
uint32_t count,
|
||||
eLootSourceType lootSourceType = eLootSourceType::NONE,
|
||||
eInventoryType inventoryType = INVALID,
|
||||
const std::vector<LDFBaseData*>& config = {},
|
||||
const LwoNameValue& config = {},
|
||||
LWOOBJID parent = LWOOBJID_EMPTY,
|
||||
bool showFlyingLoot = true,
|
||||
bool isModMoveAndEquip = false,
|
||||
@@ -209,7 +213,7 @@ public:
|
||||
* @param item the item to place
|
||||
* @param keepCurrent stores the item in an additional temp slot if there's already an item equipped
|
||||
*/
|
||||
void UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent = false);
|
||||
void UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent = false);
|
||||
|
||||
/**
|
||||
* Removes a slot from the inventory
|
||||
@@ -411,7 +415,7 @@ public:
|
||||
// Used to migrate a character version, no need to call outside of that context
|
||||
void RegenerateItemIDs();
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
void NextLUPExhibit();
|
||||
private:
|
||||
float m_UpdateTimer = 0.0f;
|
||||
std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 };
|
||||
const std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 };
|
||||
uint8_t m_LUPExhibitIndex = 0;
|
||||
bool m_DirtyLUPExhibit = true;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright 2019
|
||||
*/
|
||||
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
@@ -27,12 +28,11 @@ std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> MissionComponent:
|
||||
|
||||
//! Initializer
|
||||
MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
using namespace GameMessages;
|
||||
m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue();
|
||||
|
||||
RegisterMsg<GetObjectReportInfo>(this, &MissionComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg<GameMessages::GetMissionState>(this, &MissionComponent::OnGetMissionState);
|
||||
RegisterMsg<GameMessages::MissionNeedsLot>(this, &MissionComponent::OnMissionNeedsLot);
|
||||
RegisterMsg(&MissionComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&MissionComponent::OnGetMissionState);
|
||||
RegisterMsg(&MissionComponent::OnMissionNeedsLot);
|
||||
}
|
||||
|
||||
//! Destructor
|
||||
@@ -143,13 +143,22 @@ void MissionComponent::RemoveMission(uint32_t missionId) {
|
||||
|
||||
void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count, bool ignoreAchievements) {
|
||||
LOG("Progressing missions %s %i %llu %s %s", StringifiedEnum::ToString(type).data(), value, associate, targets.c_str(), ignoreAchievements ? "(ignoring achievements)" : "");
|
||||
std::vector<uint32_t> acceptedAchievements;
|
||||
if (count > 0 && !ignoreAchievements) {
|
||||
acceptedAchievements = LookForAchievements(type, value, true, associate, targets, count);
|
||||
|
||||
// If we are already iterating m_Missions, defer this call to avoid iterator invalidation
|
||||
// from re-entrant insertions (e.g. a completing achievement triggering LookForAchievements).
|
||||
if (m_IsProgressing) {
|
||||
m_PendingProgress.push_back({ type, value, associate, targets, count, ignoreAchievements });
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> acceptedAchievements;
|
||||
if (count > 0 && !ignoreAchievements) {
|
||||
acceptedAchievements = LookForAchievements(type, value, associate, targets, count);
|
||||
}
|
||||
|
||||
m_IsProgressing = true;
|
||||
for (const auto& [id, mission] : m_Missions) {
|
||||
if (!mission || std::find(acceptedAchievements.begin(), acceptedAchievements.end(), mission->GetMissionId()) != acceptedAchievements.end()) continue;
|
||||
if (!mission) continue;
|
||||
|
||||
if (mission->IsAchievement() && ignoreAchievements) continue;
|
||||
|
||||
@@ -157,6 +166,18 @@ void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID a
|
||||
|
||||
mission->Progress(type, value, associate, targets, count);
|
||||
}
|
||||
m_IsProgressing = false;
|
||||
|
||||
// Drain any Progress() calls that were deferred during the loop above.
|
||||
// Each call here may itself defer further calls, which are drained recursively
|
||||
// before returning, so the while loop only needs one pass in practice.
|
||||
while (!m_PendingProgress.empty()) {
|
||||
auto pending = std::move(m_PendingProgress);
|
||||
m_PendingProgress.clear();
|
||||
for (const auto& p : pending) {
|
||||
Progress(p.type, p.value, p.associate, p.targets, p.count, p.ignoreAchievements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MissionComponent::ForceProgress(const uint32_t missionId, const uint32_t taskId, const int32_t value, const bool acceptMission) {
|
||||
@@ -279,10 +300,7 @@ bool MissionComponent::GetMissionInfo(uint32_t missionId, CDMissions& result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MISSION_NEW_METHOD
|
||||
|
||||
const std::vector<uint32_t> MissionComponent::LookForAchievements(eMissionTaskType type, int32_t value, bool progress, LWOOBJID associate, const std::string& targets, int32_t count) {
|
||||
#ifdef MISSION_NEW_METHOD
|
||||
const std::vector<uint32_t> MissionComponent::LookForAchievements(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count) {
|
||||
// Query for achievments, using the cache
|
||||
const auto& result = QueryAchievements(type, value, targets);
|
||||
|
||||
@@ -309,85 +327,9 @@ const std::vector<uint32_t> MissionComponent::LookForAchievements(eMissionTaskTy
|
||||
instance->Accept();
|
||||
|
||||
acceptedAchievements.push_back(missionID);
|
||||
|
||||
if (progress) {
|
||||
// Progress mission to bring it up to speed
|
||||
instance->Progress(type, value, associate, targets, count);
|
||||
}
|
||||
}
|
||||
|
||||
return acceptedAchievements;
|
||||
#else
|
||||
auto* missionTasksTable = CDClientManager::GetTable<CDMissionTasksTable>();
|
||||
auto* missionsTable = CDClientManager::GetTable<CDMissionsTable>();
|
||||
|
||||
auto tasks = missionTasksTable->Query([=](const CDMissionTasks& entry) {
|
||||
return entry.taskType == static_cast<unsigned>(type);
|
||||
});
|
||||
|
||||
std::vector<uint32_t> acceptedAchievements;
|
||||
|
||||
for (const auto& task : tasks) {
|
||||
if (GetMission(task.id) != nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto missionEntries = missionsTable->Query([=](const CDMissions& entry) {
|
||||
return entry.id == static_cast<int>(task.id) && !entry.isMission;
|
||||
});
|
||||
|
||||
if (missionEntries.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto mission = missionEntries[0];
|
||||
|
||||
if (mission.isMission || !MissionPrerequisites::CanAccept(mission.id, m_Missions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task.target != value && task.targetGroup != targets) {
|
||||
auto stream = std::istringstream(task.targetGroup);
|
||||
std::string token;
|
||||
|
||||
auto found = false;
|
||||
|
||||
while (std::getline(stream, token, ',')) {
|
||||
try {
|
||||
const auto target = std::stoul(token);
|
||||
|
||||
found = target == value;
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
} catch (std::invalid_argument& exception) {
|
||||
LOG("Failed to parse target (%s): (%s)!", token.c_str(), exception.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto* instance = new Mission(this, mission.id);
|
||||
|
||||
m_Missions.insert_or_assign(mission.id, instance);
|
||||
|
||||
if (instance->IsMission()) instance->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
|
||||
|
||||
instance->Accept();
|
||||
|
||||
acceptedAchievements.push_back(mission.id);
|
||||
|
||||
if (progress) {
|
||||
instance->Progress(type, value, associate, targets, count);
|
||||
}
|
||||
}
|
||||
|
||||
return acceptedAchievements;
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::vector<uint32_t>& MissionComponent::QueryAchievements(eMissionTaskType type, int32_t value, const std::string targets) {
|
||||
@@ -496,7 +438,7 @@ bool MissionComponent::RequiresItem(const LOT lot) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto required = LookForAchievements(eMissionTaskType::GATHER, lot, false);
|
||||
const auto required = LookForAchievements(eMissionTaskType::GATHER, lot);
|
||||
|
||||
return !required.empty();
|
||||
}
|
||||
@@ -507,45 +449,48 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
|
||||
|
||||
if (mis == nullptr) return;
|
||||
|
||||
auto* cur = mis->FirstChildElement("cur");
|
||||
auto* done = mis->FirstChildElement("done");
|
||||
|
||||
auto* doneM = done->FirstChildElement();
|
||||
|
||||
while (doneM) {
|
||||
int missionId;
|
||||
|
||||
doneM->QueryAttribute("id", &missionId);
|
||||
|
||||
auto* mission = new Mission(this, missionId);
|
||||
|
||||
mission->LoadFromXmlDone(*doneM);
|
||||
|
||||
doneM = doneM->NextSiblingElement();
|
||||
|
||||
m_Missions.insert_or_assign(missionId, mission);
|
||||
}
|
||||
|
||||
auto* currentM = cur->FirstChildElement();
|
||||
|
||||
uint32_t missionOrder{};
|
||||
while (currentM) {
|
||||
int missionId;
|
||||
|
||||
currentM->QueryAttribute("id", &missionId);
|
||||
|
||||
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
|
||||
|
||||
mission->LoadFromXmlCur(*currentM);
|
||||
|
||||
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
|
||||
mission->SetUniqueMissionOrderID(missionOrder);
|
||||
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
|
||||
if (done) {
|
||||
auto* doneM = done->FirstChildElement();
|
||||
|
||||
while (doneM) {
|
||||
int missionId;
|
||||
|
||||
doneM->QueryAttribute("id", &missionId);
|
||||
|
||||
auto* mission = new Mission(this, missionId);
|
||||
|
||||
mission->LoadFromXmlDone(*doneM);
|
||||
|
||||
doneM = doneM->NextSiblingElement();
|
||||
|
||||
m_Missions.insert_or_assign(missionId, mission);
|
||||
}
|
||||
}
|
||||
|
||||
auto* cur = mis->FirstChildElement("cur");
|
||||
if (cur) {
|
||||
auto* currentM = cur->FirstChildElement();
|
||||
|
||||
currentM = currentM->NextSiblingElement();
|
||||
uint32_t missionOrder{};
|
||||
while (currentM) {
|
||||
int missionId;
|
||||
|
||||
m_Missions.insert_or_assign(missionId, mission);
|
||||
currentM->QueryAttribute("id", &missionId);
|
||||
|
||||
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
|
||||
|
||||
mission->LoadFromXmlCur(*currentM);
|
||||
|
||||
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
|
||||
mission->SetUniqueMissionOrderID(missionOrder);
|
||||
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
|
||||
}
|
||||
|
||||
currentM = currentM->NextSiblingElement();
|
||||
|
||||
m_Missions.insert_or_assign(missionId, mission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,9 +651,8 @@ void PushMissions(const std::map<uint32_t, Mission*>& missions, AMFArrayValue& V
|
||||
}
|
||||
}
|
||||
|
||||
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& missionInfo = reportMsg.info->PushDebug("Mission (Laggy)");
|
||||
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& missionInfo = reportInfo.info->PushDebug("Mission (Laggy)");
|
||||
missionInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
// Sort the missions so they are easier to parse and present to the end user
|
||||
std::map<uint32_t, Mission*> achievements;
|
||||
@@ -724,26 +668,51 @@ bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
// None of these should be empty, but if they are dont print the field
|
||||
if (!achievements.empty() || !missions.empty()) {
|
||||
auto& incompleteMissions = missionInfo.PushDebug("Incomplete Missions");
|
||||
PushMissions(achievements, incompleteMissions, reportMsg.bVerbose);
|
||||
PushMissions(missions, incompleteMissions, reportMsg.bVerbose);
|
||||
PushMissions(achievements, incompleteMissions, reportInfo.bVerbose);
|
||||
PushMissions(missions, incompleteMissions, reportInfo.bVerbose);
|
||||
}
|
||||
|
||||
if (!doneMissions.empty()) {
|
||||
auto& completeMissions = missionInfo.PushDebug("Completed Missions");
|
||||
PushMissions(doneMissions, completeMissions, reportMsg.bVerbose);
|
||||
PushMissions(doneMissions, completeMissions, reportInfo.bVerbose);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MissionComponent::OnGetMissionState(GameMessages::GameMsg& msg) {
|
||||
auto misState = static_cast<GameMessages::GetMissionState&>(msg);
|
||||
misState.missionState = GetMissionState(misState.missionID);
|
||||
bool MissionComponent::OnGetMissionState(GameMessages::GetMissionState& getMissionState) {
|
||||
getMissionState.missionState = GetMissionState(getMissionState.missionID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MissionComponent::OnMissionNeedsLot(GameMessages::GameMsg& msg) {
|
||||
const auto& needMsg = static_cast<GameMessages::MissionNeedsLot&>(msg);
|
||||
return RequiresItem(needMsg.item);
|
||||
bool MissionComponent::OnMissionNeedsLot(GameMessages::MissionNeedsLot& missionNeedsLot) {
|
||||
return RequiresItem(missionNeedsLot.item);
|
||||
}
|
||||
|
||||
void MissionComponent::FixRacingMetaMissions() {
|
||||
for (auto* const mission : m_Missions | std::views::values) {
|
||||
if (!mission || mission->IsComplete()) continue;
|
||||
|
||||
for (auto* const task : mission->GetTasks()) {
|
||||
if (!task) continue;
|
||||
|
||||
// has to be a racing meta mission and have a taskparam1 of 4
|
||||
if (task->GetType() != eMissionTaskType::RACING || !task->GetClientInfo().taskParam1.starts_with("4")) continue;
|
||||
|
||||
// Each target is racing mission that needs to be completed.
|
||||
// If its completed, progress the meta task by 1.
|
||||
uint32_t progress = 0;
|
||||
for (const auto& target : task->GetAllTargets()) {
|
||||
if (target == 0) continue;
|
||||
auto* racingMission = GetMission(target);
|
||||
if (racingMission && racingMission->IsComplete()) {
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
task->SetProgress(progress);
|
||||
}
|
||||
// in case the mission is actually complete, give them the rewards
|
||||
mission->CheckCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +134,12 @@ public:
|
||||
* Checks if there's any achievements we might be able to accept for the given parameters
|
||||
* @param type the task type for tasks in the achievement that we wish to progress
|
||||
* @param value the value to progress by
|
||||
* @param progress if we can accept the mission, this will apply the progression
|
||||
* @param associate optional associate related to mission progression
|
||||
* @param targets optional multiple targets related to mission progression
|
||||
* @param count the number of values to progress by (differs by task type)
|
||||
* @return true if a achievement was accepted, false otherwise
|
||||
*/
|
||||
const std::vector<uint32_t> LookForAchievements(eMissionTaskType type, int32_t value, bool progress = true, LWOOBJID associate = LWOOBJID_EMPTY, const std::string& targets = "", int32_t count = 1);
|
||||
const std::vector<uint32_t> LookForAchievements(eMissionTaskType type, int32_t value, LWOOBJID associate = LWOOBJID_EMPTY, const std::string& targets = "", int32_t count = 1);
|
||||
|
||||
/**
|
||||
* Checks if there's a mission active that requires the collection of the specified LOT
|
||||
@@ -170,10 +169,12 @@ public:
|
||||
bool HasMission(uint32_t missionId);
|
||||
|
||||
void ResetMission(const int32_t missionId);
|
||||
|
||||
void FixRacingMetaMissions();
|
||||
private:
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnGetMissionState(GameMessages::GameMsg& msg);
|
||||
bool OnMissionNeedsLot(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
bool OnGetMissionState(GameMessages::GetMissionState& getMissionState);
|
||||
bool OnMissionNeedsLot(GameMessages::MissionNeedsLot& missionNeedsLot);
|
||||
/**
|
||||
* All the missions owned by this entity, mapped by mission ID
|
||||
*/
|
||||
@@ -206,6 +207,25 @@ private:
|
||||
* In live this value started at 745.
|
||||
*/
|
||||
uint32_t m_LastUsedMissionOrderUID = 746U;
|
||||
|
||||
/**
|
||||
* Holds arguments for a Progress() call that arrived re-entrantly while m_Missions was
|
||||
* being iterated, so it can be replayed after the active loop finishes.
|
||||
*/
|
||||
struct PendingProgress {
|
||||
eMissionTaskType type{};
|
||||
int32_t value{};
|
||||
LWOOBJID associate{};
|
||||
std::string targets;
|
||||
int32_t count{};
|
||||
bool ignoreAchievements{};
|
||||
};
|
||||
|
||||
/// True while the m_Missions range-for loop in Progress() is executing.
|
||||
bool m_IsProgressing = false;
|
||||
|
||||
/// Re-entrant Progress() calls deferred here for replay after the active loop finishes.
|
||||
std::vector<PendingProgress> m_PendingProgress;
|
||||
};
|
||||
|
||||
#endif // MISSIONCOMPONENT_H
|
||||
|
||||
@@ -17,20 +17,18 @@
|
||||
#include "DluAssert.h"
|
||||
|
||||
ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
using namespace GameMessages;
|
||||
m_OriginalPosition = m_Parent->GetDefaultPosition();
|
||||
m_OriginalRotation = m_Parent->GetDefaultRotation();
|
||||
m_IsPaused = false;
|
||||
m_NumListeningInteract = 0;
|
||||
|
||||
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
|
||||
RegisterMsg<RequestUse>(this, &ModelComponent::OnRequestUse);
|
||||
RegisterMsg<ResetModelToDefaults>(this, &ModelComponent::OnResetModelToDefaults);
|
||||
RegisterMsg<GetObjectReportInfo>(this, &ModelComponent::OnGetObjectReportInfo);
|
||||
RegisterMsg(&ModelComponent::OnRequestUse);
|
||||
RegisterMsg(&ModelComponent::OnResetModelToDefaults);
|
||||
RegisterMsg(&ModelComponent::OnGetObjectReportInfo);
|
||||
}
|
||||
|
||||
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
|
||||
auto& reset = static_cast<GameMessages::ResetModelToDefaults&>(msg);
|
||||
bool ModelComponent::OnResetModelToDefaults(GameMessages::ResetModelToDefaults& reset) {
|
||||
if (reset.bResetBehaviors) for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
|
||||
|
||||
if (reset.bUnSmash) {
|
||||
@@ -61,10 +59,9 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelComponent::OnRequestUse(GameMessages::GameMsg& msg) {
|
||||
bool ModelComponent::OnRequestUse(GameMessages::RequestUse& requestUse) {
|
||||
bool toReturn = false;
|
||||
if (!m_IsPaused) {
|
||||
auto& requestUse = static_cast<GameMessages::RequestUse&>(msg);
|
||||
for (auto& behavior : m_Behaviors) behavior.HandleMsg(requestUse);
|
||||
toReturn = true;
|
||||
}
|
||||
@@ -189,8 +186,7 @@ void ModelComponent::AddBehavior(AddMessage& msg) {
|
||||
// Check if this behavior is able to be found via lot (if so, its a loot behavior).
|
||||
insertedBehavior.SetIsLoot(inventoryComponent->FindItemByLot(msg.GetBehaviorId(), eInventoryType::BEHAVIORS));
|
||||
}
|
||||
auto* const missionComponent = playerEntity->GetComponent<MissionComponent>();
|
||||
if (missionComponent) missionComponent->Progress(eMissionTaskType::ADD_BEHAVIOR, 0);
|
||||
ProgressAddBehaviorMission(*playerEntity);
|
||||
}
|
||||
|
||||
auto* const simplePhysComponent = m_Parent->GetComponent<SimplePhysicsComponent>();
|
||||
@@ -200,6 +196,11 @@ void ModelComponent::AddBehavior(AddMessage& msg) {
|
||||
}
|
||||
}
|
||||
|
||||
void ModelComponent::ProgressAddBehaviorMission(Entity& playerEntity) {
|
||||
auto* const missionComponent = playerEntity.GetComponent<MissionComponent>();
|
||||
if (missionComponent) missionComponent->Progress(eMissionTaskType::ADD_BEHAVIOR, 0);
|
||||
}
|
||||
|
||||
std::string ModelComponent::SaveBehavior(const PropertyBehavior& behavior) const {
|
||||
tinyxml2::XMLDocument doc;
|
||||
auto* root = doc.NewElement("Behavior");
|
||||
@@ -220,8 +221,8 @@ void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keep
|
||||
auto* const inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent && !behavior.GetIsLoot()) {
|
||||
// config is owned by the item
|
||||
std::vector<LDFBaseData*> config;
|
||||
config.push_back(new LDFData<std::string>(u"userModelName", behavior.GetName()));
|
||||
LwoNameValue config;
|
||||
config.Insert(u"userModelName", behavior.GetName());
|
||||
inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId());
|
||||
}
|
||||
}
|
||||
@@ -347,10 +348,9 @@ void ModelComponent::RemoveAttack() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
if (!reportMsg.info) return false;
|
||||
auto& cmptInfo = reportMsg.info->PushDebug("Model Behaviors (Mutable)");
|
||||
bool ModelComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
if (!reportInfo.info) return false;
|
||||
auto& cmptInfo = reportInfo.info->PushDebug("Model Behaviors (Mutable)");
|
||||
cmptInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
|
||||
cmptInfo.PushDebug<AMFStringValue>("Name") = "Objects_" + std::to_string(m_Parent->GetLOT()) + "_name";
|
||||
|
||||
@@ -32,9 +32,9 @@ public:
|
||||
void LoadBehaviors();
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
bool OnRequestUse(GameMessages::GameMsg& msg);
|
||||
bool OnResetModelToDefaults(GameMessages::GameMsg& msg);
|
||||
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
bool OnRequestUse(GameMessages::RequestUse& requestUse);
|
||||
bool OnResetModelToDefaults(GameMessages::ResetModelToDefaults& resetModelToDefaults);
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
|
||||
@@ -98,6 +98,8 @@ public:
|
||||
return msg.GetNeedsNewBehaviorID();
|
||||
};
|
||||
|
||||
void ProgressAddBehaviorMission(Entity& playerEntity);
|
||||
|
||||
void AddBehavior(AddMessage& msg);
|
||||
|
||||
void RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem);
|
||||
|
||||
@@ -16,8 +16,15 @@
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "QuickBuildComponent.h"
|
||||
#include "CDPhysicsComponentTable.h"
|
||||
#include "Amf3.h"
|
||||
|
||||
#include "dNavMesh.h"
|
||||
#include "eWaypointCommandType.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "SkillComponent.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "RenderComponent.h"
|
||||
#include "InventoryComponent.h"
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@@ -30,8 +37,6 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
|
||||
m_Info = info;
|
||||
m_AtFinalWaypoint = true;
|
||||
|
||||
m_BaseCombatAI = nullptr;
|
||||
|
||||
m_BaseCombatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
//Try and fix the insane values:
|
||||
@@ -57,7 +62,9 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
|
||||
m_SavedVelocity = NiPoint3Constant::ZERO;
|
||||
m_IsBounced = false;
|
||||
|
||||
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
||||
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
|
||||
|
||||
SetPath(m_Parent->GetVarAsString(u"attached_path"));
|
||||
}
|
||||
|
||||
void MovementAIComponent::SetPath(const std::string pathName) {
|
||||
@@ -122,7 +129,11 @@ void MovementAIComponent::Update(const float deltaTime) {
|
||||
|
||||
m_TimeTravelled += deltaTime;
|
||||
|
||||
SetPosition(ApproximateLocation());
|
||||
const auto approxPos = ApproximateLocation();
|
||||
SetPosition(approxPos);
|
||||
// Set the AIs new home based on where our current waypoint is IF we're idle, that way we can return to this
|
||||
// when resuming the pathing after losing aggro while moving the aggro hitbox with us
|
||||
if (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle) m_BaseCombatAI->SetStartingPosition(approxPos);
|
||||
|
||||
if (m_TimeTravelled < m_TimeToTravel) return;
|
||||
m_TimeTravelled = 0.0f;
|
||||
@@ -157,32 +168,34 @@ void MovementAIComponent::Update(const float deltaTime) {
|
||||
SetRotation(QuatUtils::LookAt(source, m_NextWaypoint));
|
||||
}
|
||||
} else {
|
||||
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
||||
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
||||
if (m_CurrentPath.empty()) {
|
||||
if (m_Path) {
|
||||
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
||||
SetPath(m_Path->pathWaypoints);
|
||||
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
|
||||
m_IsBounced = !m_IsBounced;
|
||||
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
|
||||
if (m_IsBounced) std::ranges::reverse(waypoints);
|
||||
SetPath(waypoints);
|
||||
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||
// Only try to renew or continue the path if we're in the idle or spawn state and we actually have a combatAI component
|
||||
if (!m_BaseCombatAI || (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle)) {
|
||||
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
||||
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
||||
RunWaypointCommands(waypointNum);
|
||||
if (m_CurrentPath.empty()) {
|
||||
if (m_Path) {
|
||||
if (m_Path->pathBehavior == PathBehavior::Loop) {
|
||||
SetPath(m_Path->pathWaypoints);
|
||||
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
|
||||
m_IsBounced = !m_IsBounced;
|
||||
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
|
||||
if (m_IsBounced) std::ranges::reverse(waypoints);
|
||||
SetPath(waypoints);
|
||||
} else if (m_Path->pathBehavior == PathBehavior::Once) {
|
||||
// In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again.
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||
SetDestination(m_CurrentPath.top().position);
|
||||
SetDestination(m_CurrentPath.top().position);
|
||||
|
||||
m_CurrentPath.pop();
|
||||
m_CurrentPath.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,8 +222,7 @@ NiPoint3 MovementAIComponent::GetCurrentWaypoint() const {
|
||||
|
||||
NiPoint3 MovementAIComponent::ApproximateLocation() const {
|
||||
auto source = m_SourcePosition;
|
||||
|
||||
if (AtFinalWaypoint()) return source;
|
||||
if (AtFinalWaypoint()) return m_Parent->GetPosition();
|
||||
|
||||
auto destination = m_NextWaypoint;
|
||||
|
||||
@@ -420,5 +432,128 @@ NiPoint3 MovementAIComponent::GetDestination() const {
|
||||
void MovementAIComponent::SetMaxSpeed(const float value) {
|
||||
if (value == m_MaxSpeed) return;
|
||||
m_MaxSpeed = value;
|
||||
m_Acceleration = value / 5;
|
||||
m_Acceleration = value / 5.0f;
|
||||
}
|
||||
|
||||
void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) {
|
||||
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
|
||||
|
||||
if (!m_Path || waypointNum >= m_Path->pathWaypoints.size()) return;
|
||||
const auto& commands = m_Path->pathWaypoints[waypointNum].commands;
|
||||
for (const auto& [command, data] : commands) {
|
||||
LOG_DEBUG("%s %s %s", StringifiedEnum::ToString(command).data(), m_Path->pathName.c_str(), data.c_str());
|
||||
const auto dataSplit = GeneralUtils::SplitString(data, ',');
|
||||
switch (command) {
|
||||
case eWaypointCommandType::INVALID: break;
|
||||
case eWaypointCommandType::BOUNCE: break;
|
||||
case eWaypointCommandType::STOP: Pause(); break;
|
||||
case eWaypointCommandType::GROUP_EMOTE: break;
|
||||
case eWaypointCommandType::SET_VARIABLE: break; // Empty in the client
|
||||
case eWaypointCommandType::CAST_SKILL: {
|
||||
const auto skill = GeneralUtils::TryParse<uint32_t>(data);
|
||||
if (skill) {
|
||||
auto* const skillComponent = m_Parent->GetComponent<SkillComponent>();
|
||||
if (skillComponent) skillComponent->CastSkill(skill.value());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eWaypointCommandType::EQUIP_INVENTORY: {
|
||||
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent) {
|
||||
// items should always exist
|
||||
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
||||
if (item) inventoryComponent->EquipItem(item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eWaypointCommandType::UNEQUIP_INVENTORY: {
|
||||
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent) {
|
||||
// items should always exist
|
||||
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
|
||||
if (item) inventoryComponent->UnEquipItem(item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eWaypointCommandType::DELAY: {
|
||||
// Pause(GeneralUtils::TryParse<float>(data).value_or(0.0f));
|
||||
break;
|
||||
}
|
||||
case eWaypointCommandType::EMOTE: {
|
||||
// m_Delay = RenderComponent::GetAnimationTime(m_Parent, data);
|
||||
// const auto emoteID = GeneralUtils::TryParse<uint32_t>(data);
|
||||
// if (emoteID) GameMessages::SendPlayEmote(m_Parent->GetObjectID(), emoteID.value(), LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
break;
|
||||
}
|
||||
case eWaypointCommandType::TELEPORT: break;
|
||||
case eWaypointCommandType::PATH_SPEED: m_BaseSpeed = GetBaseSpeed(m_Parent->GetLOT()) * GeneralUtils::TryParse<float>(data).value_or(1.0f); break;
|
||||
case eWaypointCommandType::REMOVE_NPC: break;
|
||||
case eWaypointCommandType::CHANGE_WAYPOINT: SetPath(dataSplit[0]); break;
|
||||
case eWaypointCommandType::DELETE_SELF: break;
|
||||
case eWaypointCommandType::KILL_SELF: m_Parent->Smash(); break;
|
||||
case eWaypointCommandType::SPAWN_OBJECT: break;
|
||||
case eWaypointCommandType::PLAY_SOUND: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
|
||||
auto& movementInfo = reportInfo.info->PushDebug("MovementAI");
|
||||
if (m_Path) {
|
||||
movementInfo.PushDebug<AMFStringValue>("Path") = m_Path->pathName;
|
||||
}
|
||||
|
||||
auto& movementAiInfo = movementInfo.PushDebug("Movement AI Info");
|
||||
movementAiInfo.PushDebug<AMFStringValue>("Movement Type") = m_Info.movementType;
|
||||
movementAiInfo.PushDebug<AMFDoubleValue>("Wander Radius") = m_Info.wanderRadius;
|
||||
movementAiInfo.PushDebug<AMFDoubleValue>("Wander Speed") = m_Info.wanderSpeed;
|
||||
movementAiInfo.PushDebug<AMFDoubleValue>("Wander Chance") = m_Info.wanderChance;
|
||||
movementAiInfo.PushDebug<AMFDoubleValue>("Wander Delay Min") = m_Info.wanderDelayMin;
|
||||
movementAiInfo.PushDebug<AMFDoubleValue>("Wander Delay Max") = m_Info.wanderDelayMax;
|
||||
|
||||
auto& speedInfo = movementInfo.PushDebug("Speed Info");
|
||||
speedInfo.PushDebug<AMFDoubleValue>("Base Speed") = m_BaseSpeed;
|
||||
speedInfo.PushDebug<AMFDoubleValue>("Max Speed") = m_MaxSpeed;
|
||||
speedInfo.PushDebug<AMFDoubleValue>("Current Speed") = m_CurrentSpeed;
|
||||
speedInfo.PushDebug<AMFDoubleValue>("Acceleration") = m_Acceleration;
|
||||
|
||||
movementInfo.PushDebug<AMFDoubleValue>("Halt Distance") = m_HaltDistance;
|
||||
movementInfo.PushDebug<AMFDoubleValue>("Time To Travel") = m_TimeToTravel;
|
||||
movementInfo.PushDebug<AMFDoubleValue>("Time Travelled") = m_TimeTravelled;
|
||||
movementInfo.PushDebug<AMFBoolValue>("Lock Rotation") = m_LockRotation;
|
||||
movementInfo.PushDebug<AMFBoolValue>("Paused") = m_Paused;
|
||||
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
|
||||
movementInfo.PushDebug<AMFBoolValue>("At Final Waypoint") = m_AtFinalWaypoint;
|
||||
|
||||
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("Y") = m_PullPoint.y;
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("Z") = m_PullPoint.z;
|
||||
|
||||
// movementInfo.PushDebug<AMFDoubleValue>("Delay") = m_Delay;
|
||||
|
||||
auto& waypoints = movementInfo.PushDebug("Interpolated Waypoints");
|
||||
int i = 0;
|
||||
for (const auto& point : m_InterpolatedWaypoints) {
|
||||
auto& waypoint = waypoints.PushDebug("Waypoint " + std::to_string(++i));
|
||||
waypoint.PushDebug<AMFDoubleValue>("X") = point.x;
|
||||
waypoint.PushDebug<AMFDoubleValue>("Y") = point.y;
|
||||
waypoint.PushDebug<AMFDoubleValue>("Z") = point.z;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
auto& currentPath = movementInfo.PushDebug("Current Path");
|
||||
auto pathCopy = m_CurrentPath; // Copy to avoid modifying the original stack
|
||||
while (!pathCopy.empty()) {
|
||||
const auto& waypoint = pathCopy.top();
|
||||
auto& pathWaypoint = currentPath.PushDebug("Waypoint " + std::to_string(++i));
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("X") = waypoint.position.x;
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("Y") = waypoint.position.y;
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("Z") = waypoint.position.z;
|
||||
|
||||
pathCopy.pop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -211,8 +211,17 @@ public:
|
||||
|
||||
bool IsPaused() const { return m_Paused; }
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
bool HasPath() const { return m_Path != nullptr; }
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Runs the commands on a waypoint if a path exists
|
||||
*/
|
||||
void RunWaypointCommands(uint32_t waypointNum);
|
||||
|
||||
/**
|
||||
* Sets the current position of the entity
|
||||
* @param value the position to set
|
||||
|
||||
@@ -17,7 +17,10 @@ MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int
|
||||
MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {}
|
||||
|
||||
void MultiZoneEntranceComponent::OnUse(Entity* originator) {
|
||||
auto* rocket = originator->GetComponent<CharacterComponent>()->RocketEquip(originator);
|
||||
auto* const characterComponent = originator->GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
|
||||
auto* rocket = characterComponent->RocketEquip(originator);
|
||||
if (!rocket) return;
|
||||
|
||||
// the LUP world menu is just the property menu, the client knows how to handle it
|
||||
@@ -26,7 +29,7 @@ void MultiZoneEntranceComponent::OnUse(Entity* originator) {
|
||||
|
||||
void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) {
|
||||
auto* rocketLaunchpadControlComponent = m_Parent->GetComponent<RocketLaunchpadControlComponent>();
|
||||
if (!rocketLaunchpadControlComponent) return;
|
||||
if (!rocketLaunchpadControlComponent || index >= m_LUPWorlds.size()) return;
|
||||
|
||||
rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0);
|
||||
}
|
||||
|
||||
@@ -922,7 +922,9 @@ void PetComponent::Deactivate() {
|
||||
}
|
||||
|
||||
void PetComponent::Release() {
|
||||
auto* inventoryComponent = GetOwner()->GetComponent<InventoryComponent>();
|
||||
auto* const owner = GetOwner();
|
||||
if (!owner) return;
|
||||
auto* const inventoryComponent = owner->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent == nullptr) {
|
||||
return;
|
||||
@@ -932,9 +934,9 @@ void PetComponent::Release() {
|
||||
|
||||
inventoryComponent->RemoveDatabasePet(m_DatabaseId);
|
||||
|
||||
auto* item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
|
||||
auto* const item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
|
||||
|
||||
item->SetCount(0, false, false);
|
||||
if (item) item->SetCount(0, false, false);
|
||||
}
|
||||
|
||||
void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, const int32_t commandType, const int32_t typeId, const bool overrideObey) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user