mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-10-23 05:20:14 +00:00
Compare commits
14 Commits
575754f727
...
lua
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9af3b60d4d | ||
![]() |
d5fc36f877 | ||
aa3dacc84b | |||
a29de394ee | |||
ca299e0a5b | |||
794b881c4d | |||
e8659962a5 | |||
2090062c75 | |||
b25b229eb2 | |||
c2853d9271 | |||
cb85f7b7c9 | |||
80f0ff7479 | |||
2aa23a83de | |||
43aa4eaeb8 |
147
.github/workflows/check-builds.yaml
vendored
147
.github/workflows/check-builds.yaml
vendored
@@ -1,147 +0,0 @@
|
||||
name: Check Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- .github/workflows/check-builds.yaml
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: sudo apt install clang cmake snap -y && sudo snap install powershell --classic
|
||||
- name: Check compilation
|
||||
run: |
|
||||
$versions = "104", "728", "1013"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
Write-Output "Cleaning old output"
|
||||
Invoke-Expression "make clean"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Building version $version"
|
||||
Invoke-Expression "make -j8 PROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin"
|
||||
shell: pwsh
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
windows-build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
|
||||
shell: pwsh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Check compilation
|
||||
run: |
|
||||
$versions = "104", "728", "1013"
|
||||
$configurations = "Release"
|
||||
# "Debug" builds are disabled, since we don't really need them
|
||||
|
||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
|
||||
|
||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
|
||||
Invoke-Expression "vcpkg install sqlite3:x64-windows"
|
||||
Invoke-Expression "vcpkg integrate install"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
if (Test-Path -LiteralPath "build") {
|
||||
Remove-Item "build" -Recurse
|
||||
Write-Output "Deleted existing build folder"
|
||||
}
|
||||
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Generated build files for version $version"
|
||||
|
||||
foreach ($configuration in $configurations) {
|
||||
Write-Output "Building version $version $configuration"
|
||||
Invoke-Expression "msbuild build\OpenFusion.sln /maxcpucount:8 /p:BuildInParallel=true /p:CL_MPCount=8 /p:UseMultiToolTask=true /p:Configuration=$configuration"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
||||
Write-Output "Built version $version $configuration"
|
||||
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
|
||||
}
|
||||
}
|
||||
shell: pwsh
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
copy-artifacts:
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [windows-build, ubuntu-build]
|
||||
env:
|
||||
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
||||
ENDPOINT: ${{ secrets.ENDPOINT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- run: |
|
||||
GITDESC=$(git describe --tags)
|
||||
mkdir $GITDESC
|
||||
echo "ARTDIR=$GITDESC" >> $GITHUB_ENV
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ${{ env.ARTDIR }}
|
||||
- name: Upload artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt install zip -y
|
||||
cd $ARTDIR
|
||||
for build in *; do
|
||||
cd $build
|
||||
zip -r ../$build.zip *
|
||||
cd ..
|
||||
rm -r $build
|
||||
done
|
||||
cd ..
|
||||
umask 077
|
||||
printf %s "$BOT_SSH_KEY" > cdn_key
|
||||
scp -i cdn_key -o StrictHostKeyChecking=no -r $ARTDIR $ENDPOINT
|
38
.github/workflows/push-docker-image.yml
vendored
38
.github/workflows/push-docker-image.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Push Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push-docker-image:
|
||||
name: Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Retrieve major version
|
||||
uses: winterjung/split@v2
|
||||
id: split
|
||||
with:
|
||||
msg: ${{ github.ref_name }}
|
||||
separator: .
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
- name: Build and push the Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platforms }}
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.ref_name }},${{ secrets.DOCKERHUB_REPOSITORY }}:${{ steps.split.outputs._0 }},${{ secrets.DOCKERHUB_REPOSITORY }}:latest
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,5 +16,3 @@ build/
|
||||
version.h
|
||||
infer-out
|
||||
gmon.out
|
||||
*.bak
|
||||
|
||||
|
26
.vscode/launch.json
vendored
26
.vscode/launch.json
vendored
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug (Linux)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/fusion",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Debug (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Release (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
@@ -3,8 +3,7 @@ project(OpenFusion)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# OpenFusion supports multiple packet/struct versions
|
||||
# 104 is the default version to build which can be changed
|
||||
@@ -44,10 +43,8 @@ add_executable(openfusion ${SOURCES})
|
||||
|
||||
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||
|
||||
# find sqlite3 and use it
|
||||
find_package(SQLite3 REQUIRED)
|
||||
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
||||
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
||||
target_link_libraries(openfusion sqlite3)
|
||||
target_link_libraries(openfusion lua51)
|
||||
|
||||
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
|
||||
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
@@ -57,5 +54,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S
|
||||
# It's not something you should do, but it's there if you need it...
|
||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(openfusion PRIVATE pthread)
|
||||
target_link_libraries(openfusion pthread)
|
||||
endif()
|
||||
|
@@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra
|
||||
|
||||
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
||||
|
||||
### Dirty pull requests
|
||||
## Dirty pull requests
|
||||
|
||||
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||
These are generally either:
|
||||
@@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut
|
||||
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
||||
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
||||
|
||||
### The details
|
||||
## The details
|
||||
|
||||
A git commit is uniquely identified by its SHA1 hash.
|
||||
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
||||
@@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c
|
||||
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
||||
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
||||
|
||||
### The solution
|
||||
## The solution
|
||||
|
||||
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
||||
|
||||
@@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you
|
||||
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
||||
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
||||
|
||||
### Avoiding the problem
|
||||
## Avoiding the problem
|
||||
|
||||
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
||||
Create a work branch just for your changeset with `git checkout -b work`.
|
||||
@@ -81,34 +81,3 @@ That way you can always keep master in sync with upstream with `git pull --ff-on
|
||||
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
||||
|
||||
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
||||
|
||||
## Code guidelines
|
||||
|
||||
Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow:
|
||||
|
||||
### Match the styling
|
||||
|
||||
Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc.
|
||||
|
||||
### Prefer short-circuiting
|
||||
|
||||
To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement.
|
||||
|
||||
### Follow the include convention
|
||||
|
||||
This one matters a lot as it can cause cyclic dependencies and other code-breaking issues.
|
||||
|
||||
FOR HEADER FILES (.hpp):
|
||||
- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp
|
||||
- you may NOT include ANYTHING ELSE
|
||||
|
||||
FOR SOURCE FILES (.cpp):
|
||||
- you can #include whatever you want as long as the partner header is included first
|
||||
- anything that gets included by another include is fair game
|
||||
- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean.
|
||||
|
||||
The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues.
|
||||
|
||||
## When in doubt, ask
|
||||
|
||||
If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub.
|
||||
|
32
Dockerfile
32
Dockerfile
@@ -1,32 +0,0 @@
|
||||
# build
|
||||
FROM debian:stable-slim as build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get -y update && apt-get install -y \
|
||||
git \
|
||||
clang \
|
||||
make \
|
||||
libsqlite3-dev
|
||||
|
||||
COPY src ./src
|
||||
COPY vendor ./vendor
|
||||
COPY .git ./.git
|
||||
COPY Makefile CMakeLists.txt version.h.in ./
|
||||
|
||||
RUN make -j8
|
||||
|
||||
# prod
|
||||
FROM debian:stable-slim
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get -y update && apt-get install -y \
|
||||
libsqlite3-dev
|
||||
|
||||
COPY --from=build /usr/src/app/bin/fusion /bin/fusion
|
||||
COPY sql ./sql
|
||||
|
||||
CMD ["/bin/fusion"]
|
||||
|
||||
LABEL Name=openfusion Version=0.0.2
|
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 OpenFusion Contributors
|
||||
Copyright (c) 2020 Seth Stubbs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
38
Makefile
38
Makefile
@@ -4,9 +4,11 @@ CC=clang
|
||||
CXX=clang++
|
||||
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
|
||||
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
|
||||
CFLAGS=-Wall -Wno-unknown-pragmas -O2 -fPIE -D_FORTIFY_SOURCE=1 -fstack-protector #-g3 -fsanitize=address
|
||||
CXXFLAGS=$(CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||
LDFLAGS=-lpthread -lsqlite3 -pie -Wl,-z,relro -Wl,-z,now #-g3 -fsanitize=address
|
||||
CFLAGS=-O3 #-g3 -fsanitize=address
|
||||
LCFLAGS=`pkg-config --cflags luajit`
|
||||
LLIBFLAGS=`pkg-config --libs luajit`
|
||||
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor $(LCFLAGS) #-g3 -fsanitize=address
|
||||
LDFLAGS=-lpthread -lsqlite3 $(LLIBFLAGS) -lstdc++fs #-g3 -fsanitize=address
|
||||
# specifies the name of our exectuable
|
||||
SERVER=bin/fusion
|
||||
|
||||
@@ -17,9 +19,9 @@ PROTOCOL_VERSION?=104
|
||||
# Windows-specific
|
||||
WIN_CC=x86_64-w64-mingw32-gcc
|
||||
WIN_CXX=x86_64-w64-mingw32-g++
|
||||
WIN_CFLAGS=-O2 -D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas
|
||||
WIN_CXXFLAGS=$(WIN_CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3
|
||||
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
|
||||
WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor $(LCFLAGS) #-g3 -fsanitize=address
|
||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 $(LLIBFLAGS) -lstdc++fs #-g3 -fsanitize=address
|
||||
WIN_SERVER=bin/winfusion.exe
|
||||
|
||||
# C code; currently exclusively from vendored libraries
|
||||
@@ -43,14 +45,17 @@ CXXSRC=\
|
||||
src/servers/CNLoginServer.cpp\
|
||||
src/servers/CNShardServer.cpp\
|
||||
src/servers/Monitor.cpp\
|
||||
src/lua/LuaManager.cpp\
|
||||
src/lua/EventWrapper.cpp\
|
||||
src/lua/WorldWrapper.cpp\
|
||||
src/lua/EntityWrapper.cpp\
|
||||
src/lua/PlayerWrapper.cpp\
|
||||
src/lua/NPCWrapper.cpp\
|
||||
src/db/init.cpp\
|
||||
src/db/login.cpp\
|
||||
src/db/shard.cpp\
|
||||
src/db/player.cpp\
|
||||
src/db/email.cpp\
|
||||
src/sandbox/seccomp.cpp\
|
||||
src/sandbox/openbsd.cpp\
|
||||
src/Buffs.cpp\
|
||||
src/Chat.cpp\
|
||||
src/CustomCommands.cpp\
|
||||
src/Entities.cpp\
|
||||
@@ -89,13 +94,20 @@ CXXHDR=\
|
||||
src/servers/CNLoginServer.hpp\
|
||||
src/servers/CNShardServer.hpp\
|
||||
src/servers/Monitor.hpp\
|
||||
src/lua/LuaManager.hpp\
|
||||
src/lua/LuaWrapper.hpp\
|
||||
src/lua/EventWrapper.hpp\
|
||||
src/lua/WorldWrapper.hpp\
|
||||
src/lua/EntityWrapper.hpp\
|
||||
src/lua/PlayerWrapper.hpp\
|
||||
src/lua/NPCWrapper.hpp\
|
||||
src/db/Database.hpp\
|
||||
src/db/internal.hpp\
|
||||
src/sandbox/Sandbox.hpp\
|
||||
vendor/bcrypt/BCrypt.hpp\
|
||||
vendor/INIReader.hpp\
|
||||
vendor/JSON.hpp\
|
||||
src/Buffs.hpp\
|
||||
vendor/INIReader.hpp\
|
||||
vendor/JSON.hpp\
|
||||
src/Chat.hpp\
|
||||
src/CustomCommands.hpp\
|
||||
src/Entities.hpp\
|
||||
@@ -142,7 +154,7 @@ windows : CXXFLAGS=$(WIN_CXXFLAGS)
|
||||
windows : LDFLAGS=$(WIN_LDFLAGS)
|
||||
windows : SERVER=$(WIN_SERVER)
|
||||
|
||||
.SUFFIXES: .o .c .cpp .h .hpp
|
||||
.SUFFIX: .o .c .cpp .h .hpp
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) -o $@ $<
|
||||
@@ -151,7 +163,7 @@ windows : SERVER=$(WIN_SERVER)
|
||||
$(CXX) -c $(CXXFLAGS) -o $@ $<
|
||||
|
||||
# header timestamps are a prerequisite for OF object files
|
||||
$(CXXOBJ): $(HDR)
|
||||
$(CXXOBJ): $(CXXHDR)
|
||||
|
||||
$(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
|
||||
mkdir -p bin
|
||||
|
60
README.md
60
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/releases/latest"><img src="https://img.shields.io/github/v/release/OpenFusionProject/OpenFusion" alt="Current Release"></a>
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml"><img src="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml/badge.svg" alt="Workflow"></a>
|
||||
<a href="https://ci.appveyor.com/project/OpenFusionProject/openfusion"><img src="https://ci.appveyor.com/api/projects/status/github/OpenFusionProject/OpenFusion?svg=true" alt="AppVeyor"></a>
|
||||
<a href="https://discord.gg/DYavckB"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord" alt="Discord"></a>
|
||||
<a href="https://github.com/OpenFusionProject/OpenFusion/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/OpenFusionProject/OpenFusion" alt="License"></a>
|
||||
</p>
|
||||
@@ -12,31 +12,21 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
||||
## Usage
|
||||
|
||||
### Getting Started
|
||||
#### Method A: Installer (Easiest)
|
||||
1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5-Installer.exe) - choose to run the file.
|
||||
2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
||||
|
||||
#### Method B: Standalone .zip file
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5.zip).
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.3/OpenFusionClient-1.3.zip).
|
||||
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
||||
3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
||||
|
||||
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
|
||||
|
||||
### Hosting a server
|
||||
1. Grab `OpenFusionServer-1.5-Original.zip` or `OpenFusionServer-1.5-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5).
|
||||
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
||||
3. Add a new server to the client's list:
|
||||
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||
2. For Server IP, enter the IP address and port of the login server. If you're hosting and playing on the same PC, this would be `127.0.0.1:23000`.
|
||||
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
||||
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||
|
||||
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||
1. Grab `OpenFusionServer-1.3-original.zip` or `OpenFusionServer-1.3-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.3).
|
||||
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
||||
3. Add a new server to the client's list: the default port is 23000, so the full IP with default settings would be 127.0.0.1:23000.
|
||||
4. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||
|
||||
If you want, [compiled binaries (artifacts) for each new commit can be found on AppVeyor.](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
|
||||
|
||||
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
||||
|
||||
@@ -53,7 +43,10 @@ FusionFall consists of the following components:
|
||||
|
||||
The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser.
|
||||
|
||||
The browser/Electron client opens a web page with an `<embed>` tag of the appropriate MIME type, where the `src` param is the address of the game's `.unity3d` entrypoint. This triggers the browser to load an NPAPI plugin that handles said MIME type, in this case the Unity Web Player.
|
||||
The browser/Electron client opens a web page with an `<embed>` tag of MIME type `application/vnd.unity`, where the `src` param is the address of the game's `.unity3d` entrypoint.
|
||||
|
||||
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\%USERNAME%\AppData\LocalLow\Unity\WebPlayer`.
|
||||
The Web Player was previously copied there by `installUnity.bat`.
|
||||
|
||||
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
|
||||
This will potentially become relevant later, as people start experimenting and mixing and matching versions.
|
||||
@@ -62,7 +55,7 @@ The web player will execute the game code, which will request the following file
|
||||
|
||||
`/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
|
||||
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
|
||||
It instead loads the web pages locally using the `file://` schema, and fetches the game's assets from a standard web server.
|
||||
It loads the web pages locally using the `file://` schema, and fetches the game's assets from Turner's CDN (which is still hosting them to this day!).
|
||||
|
||||
`/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
|
||||
|
||||
@@ -79,7 +72,7 @@ This just works if you're all under the same LAN, but if you want to play over t
|
||||
|
||||
## Compiling
|
||||
|
||||
OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||
OpenFusion has one external dependency: SQLite. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||
|
||||
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.
|
||||
|
||||
@@ -98,13 +91,26 @@ If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTR
|
||||
## Gameplay
|
||||
|
||||
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
|
||||
While most features are implemented and the game is playable start to finish, there may be missing functionality or bugs present.
|
||||
The server is not yet complete, however, and some functionality is still missing.
|
||||
|
||||
Depending on the server configuration, you'll have access to certain commands.
|
||||
Because the server is still in development, ordinary players are allowed access to a few admin commands:
|
||||
|
||||
For the public servers: Original has item spawning, the ability to set player speed/jump height, and teleportation enabled (default account level 50).
|
||||
Meanwhile the Academy server is more meant for legitimate playthroughs (default account level 99).
|
||||

|
||||
|
||||
When hosting a local server, you will have access to all commands by default (account level 1).
|
||||
### Movement commands
|
||||
* A `/speed` of around 2400 or 3000 is nice.
|
||||
* A `/jump` of about 50 will send you soaring
|
||||
* [This map](res/dong_number_map.png) (credit to Danny O) is useful for `/warp` coordinates.
|
||||
* `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.).
|
||||
|
||||
For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
||||
### Item commands
|
||||
* `/itemN [type] [itemId] [amount]`
|
||||
(Refer to the [item list](https://docs.google.com/spreadsheets/d/1mpoJ9iTHl_xLI4wQ_9UvIDYNcsDYscdkyaGizs43TCg/))
|
||||
|
||||
### Nano commands
|
||||
* `/nano [id] (1-36)`
|
||||
* `/nano_equip [id] (1-36) [slot] (0-2)`
|
||||
* `/nano_unequip [slot] (0-2)`
|
||||
* `/nano_active [slot] (0-2)`
|
||||
|
||||
### A full list of commands can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
||||
|
93
appveyor.yml
Normal file
93
appveyor.yml
Normal file
@@ -0,0 +1,93 @@
|
||||
version: 'openfusion-{branch}-{build}'
|
||||
|
||||
build_cloud: GCE us-east1-b n2-standard-8
|
||||
skip_branch_with_pr: true
|
||||
|
||||
image:
|
||||
- GCP-Windows-VS2019
|
||||
- GCP-Linux-Ubuntu2004
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: GCP-Linux-Ubuntu2004
|
||||
install:
|
||||
- sh: sudo apt install libluajit-5.1-dev -y
|
||||
build_script:
|
||||
- ps: |
|
||||
$versions = "104", "728", "1013"
|
||||
|
||||
foreach ($version in $versions) {
|
||||
Write-Output "Cleaning old output"
|
||||
Invoke-Expression "make clean"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Building version $version"
|
||||
Invoke-Expression "make -j8 PROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "make failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin"
|
||||
artifacts:
|
||||
- path: bin
|
||||
name: ubuntu20_04-bin-x64
|
||||
type: zip
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: GCP-Windows-VS2019
|
||||
install:
|
||||
- cmd: vcpkg install sqlite3:x64-windows
|
||||
- cmd: vcpkg install luajit:x64-windows
|
||||
- cmd: vcpkg integrate install
|
||||
build_script:
|
||||
- ps: |
|
||||
$versions = "104", "728", "1013"
|
||||
$configurations = "Release"
|
||||
# "Debug" builds are disabled, since we don't really need them
|
||||
|
||||
# AppVeyor uses VS2019 Community
|
||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community"
|
||||
|
||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
|
||||
foreach ($version in $versions) {
|
||||
if (Test-Path -LiteralPath "build") {
|
||||
Remove-Item "build" -Recurse
|
||||
Write-Output "Deleted existing build folder"
|
||||
}
|
||||
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Write-Output "Generated build files for version $version"
|
||||
|
||||
foreach ($configuration in $configurations) {
|
||||
Write-Output "Building version $version $configuration"
|
||||
Invoke-Expression "msbuild build\OpenFusion.sln /maxcpucount:8 /p:BuildInParallel=true /p:CL_MPCount=8 /p:UseMultiToolTask=true /p:Configuration=$configuration"
|
||||
if ($LASTEXITCODE -ne "0") {
|
||||
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
||||
Write-Output "Built version $version $configuration"
|
||||
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
|
||||
}
|
||||
}
|
||||
artifacts:
|
||||
- path: bin
|
||||
name: windows-vs2019-bin-x64
|
||||
type: zip
|
@@ -5,18 +5,12 @@
|
||||
# 3 = print all packets
|
||||
verbosity=1
|
||||
|
||||
# sandbox the process on supported platforms
|
||||
sandbox=true
|
||||
|
||||
# Login Server configuration
|
||||
[login]
|
||||
# must be kept in sync with loginInfo.php
|
||||
port=23000
|
||||
# will all custom names be approved instantly?
|
||||
acceptallcustomnames=true
|
||||
# should attempts to log into non-existent accounts
|
||||
# automatically create them?
|
||||
autocreateaccounts=true
|
||||
# how often should everything be flushed to the database?
|
||||
# the default is 4 minutes
|
||||
dbsaveinterval=240
|
||||
@@ -66,9 +60,6 @@ motd=Welcome to OpenFusion!
|
||||
# location of the database
|
||||
#dbpath=database.db
|
||||
|
||||
# should there be a score cap for infected zone races?
|
||||
#izracescorecapped=true
|
||||
|
||||
# should tutorial flags be disabled off the bat?
|
||||
disablefirstuseflag=true
|
||||
|
||||
|
@@ -1,16 +0,0 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
openfusion:
|
||||
image: openfusion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
volumes:
|
||||
- ./config.ini:/usr/src/app/config.ini
|
||||
- ./database.db:/usr/src/app/database.db
|
||||
- ./tdata:/usr/src/app/tdata
|
||||
ports:
|
||||
- "23000:23000"
|
||||
- "23001:23001"
|
||||
- "8003:8003"
|
28
scripts/test.lua
Normal file
28
scripts/test.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
print("Hello wtf!")
|
||||
|
||||
function onJoin(plr)
|
||||
print(plr.type .. " " .. plr.name .. " joined from LUA!!")
|
||||
plr.onChat:listen(function(msg)
|
||||
print(plr.name .. " said : \'" .. msg .. "\'")
|
||||
|
||||
if msg == "kickme" then
|
||||
plr:kick()
|
||||
elseif msg == "hi" then
|
||||
print("hello " .. plr.name)
|
||||
elseif msg == "pet" then
|
||||
local dog = NPC.new(plr.x, plr.y, plr.z, 3054)
|
||||
while wait(2) and plr:exists() do
|
||||
dog:moveTo(plr.x + math.random(-500, 500), plr.y + math.random(-500, 500), plr.z)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
World.onPlayerAdded:listen(onJoin)
|
||||
|
||||
for i, plr in ipairs(World.players) do
|
||||
onJoin(plr)
|
||||
end
|
||||
|
||||
wait(2)
|
||||
print("Hello world ~2 seconds later! running protcol version " .. World.version)
|
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
It is recommended in the SQLite manual to turn off
|
||||
foreign keys when making schema changes that involve them
|
||||
*/
|
||||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
-- Change username column (Login) to be case-insensitive
|
||||
CREATE TABLE Temp (
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||
BanReason TEXT DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY(AccountID AUTOINCREMENT)
|
||||
);
|
||||
INSERT INTO Temp SELECT * FROM Accounts;
|
||||
DROP TABLE Accounts;
|
||||
ALTER TABLE Temp RENAME TO Accounts;
|
||||
-- Update DB Version
|
||||
UPDATE Meta SET Value = 4 WHERE Key = 'DatabaseVersion';
|
||||
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys=ON;
|
@@ -1,6 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Login TEXT NOT NULL UNIQUE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
|
1195
src/Abilities.cpp
1195
src/Abilities.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,112 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "Combat.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
#include "Player.hpp"
|
||||
typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
struct NanoPower {
|
||||
int16_t skillType;
|
||||
int32_t bitFlag;
|
||||
int16_t timeBuffID;
|
||||
PowerHandler handler;
|
||||
|
||||
const int COMBAT_TICKS_PER_DRAIN_PROC = 2;
|
||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||
NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {}
|
||||
|
||||
enum class SkillType {
|
||||
DAMAGE = 1,
|
||||
HEAL_HP = 2,
|
||||
KNOCKDOWN = 3, // uses DamageNDebuff
|
||||
SLEEP = 4, // uses DamageNDebuff
|
||||
SNARE = 5, // uses DamageNDebuff
|
||||
HEAL_STAMINA = 6,
|
||||
STAMINA_SELF = 7,
|
||||
STUN = 8, // uses DamageNDebuff
|
||||
WEAPONSLOW = 9,
|
||||
JUMP = 10,
|
||||
RUN = 11,
|
||||
STEALTH = 12,
|
||||
SWIM = 13,
|
||||
MINIMAPENEMY = 14,
|
||||
MINIMAPTRESURE = 15,
|
||||
PHOENIX = 16,
|
||||
PROTECTBATTERY = 17,
|
||||
PROTECTINFECTION = 18,
|
||||
REWARDBLOB = 19,
|
||||
REWARDCASH = 20,
|
||||
BATTERYDRAIN = 21,
|
||||
CORRUPTIONATTACK = 22,
|
||||
INFECTIONDAMAGE = 23,
|
||||
KNOCKBACK = 24,
|
||||
FREEDOM = 25,
|
||||
PHOENIX_GROUP = 26,
|
||||
RECALL = 27,
|
||||
RECALL_GROUP = 28,
|
||||
RETROROCKET_SELF = 29,
|
||||
BLOODSUCKING = 30,
|
||||
BOUNDINGBALL = 31,
|
||||
INVULNERABLE = 32,
|
||||
NANOSTIMPAK = 33,
|
||||
RETURNHOMEHEAL = 34,
|
||||
BUFFHEAL = 35,
|
||||
EXTRABANK = 36,
|
||||
CORRUPTIONATTACKWIN = 38,
|
||||
CORRUPTIONATTACKLOSE = 39,
|
||||
};
|
||||
void handle(CNSocket *sock, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) {
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
enum class SkillEffectTarget {
|
||||
POINT = 1,
|
||||
SELF = 2,
|
||||
CONE = 3,
|
||||
WEAPON = 4,
|
||||
AREA_SELF = 5,
|
||||
AREA_TARGET = 6
|
||||
};
|
||||
|
||||
enum class SkillTargetType {
|
||||
MOBS = 1,
|
||||
PLAYERS = 2,
|
||||
GROUP = 3
|
||||
};
|
||||
|
||||
enum class SkillDrainType {
|
||||
ACTIVE = 1,
|
||||
PASSIVE = 2
|
||||
};
|
||||
|
||||
struct SkillResult {
|
||||
size_t size;
|
||||
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||
SkillResult(size_t len, void* dat) {
|
||||
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||
size = len;
|
||||
memcpy(payload, dat, len);
|
||||
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
||||
}
|
||||
SkillResult() {
|
||||
size = 0;
|
||||
};
|
||||
|
||||
typedef void (*MobPowerHandler)(Mob*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
||||
|
||||
struct MobPower {
|
||||
int16_t skillType;
|
||||
int32_t bitFlag;
|
||||
int16_t timeBuffID;
|
||||
MobPowerHandler handler;
|
||||
|
||||
MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {}
|
||||
|
||||
void handle(Mob *mob, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) {
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
||||
}
|
||||
};
|
||||
|
||||
struct SkillData {
|
||||
SkillType skillType; // eST
|
||||
SkillEffectTarget effectTarget;
|
||||
int effectType; // always 1?
|
||||
SkillTargetType targetType;
|
||||
SkillDrainType drainType;
|
||||
int skillType;
|
||||
int targetType;
|
||||
int drainType;
|
||||
int effectArea;
|
||||
|
||||
int batteryUse[4];
|
||||
int durationTime[4];
|
||||
|
||||
int valueTypes[3];
|
||||
int values[3][4];
|
||||
int powerIntensity[4];
|
||||
};
|
||||
|
||||
namespace Abilities {
|
||||
namespace Nanos {
|
||||
extern std::vector<NanoPower> NanoPowers;
|
||||
extern std::map<int32_t, SkillData> SkillTable;
|
||||
|
||||
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||
void nanoUnbuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower);
|
||||
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags);
|
||||
|
||||
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||
int getCSTBFromST(SkillType skillType);
|
||||
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr);
|
||||
}
|
||||
|
||||
namespace Combat {
|
||||
extern std::vector<MobPower> MobPowers;
|
||||
}
|
||||
|
@@ -1,10 +1,15 @@
|
||||
#include "Buddies.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Buddies.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Buddies.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
using namespace Buddies;
|
||||
|
||||
@@ -30,7 +35,7 @@ static bool playerHasBuddyWithID(Player* plr, int buddyID) {
|
||||
#pragma endregion
|
||||
|
||||
// Refresh buddy list
|
||||
void Buddies::sendBuddyList(CNSocket* sock) {
|
||||
void Buddies::refreshBuddyList(CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
int buddyCnt = Database::getNumBuddies(plr);
|
||||
|
||||
@@ -277,6 +282,15 @@ static void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
|
||||
// Getting buddy state
|
||||
static void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
/*
|
||||
* If the buddy list wasn't synced a second time yet, sync it.
|
||||
* Not sure why we have to do it again for the client not to trip up.
|
||||
*/
|
||||
if (!plr->buddiesSynced) {
|
||||
refreshBuddyList(sock);
|
||||
plr->buddiesSynced = true;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp);
|
||||
|
||||
|
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "core/Core.hpp"
|
||||
|
||||
namespace Buddies {
|
||||
void init();
|
||||
void init();
|
||||
|
||||
// Buddy list
|
||||
void sendBuddyList(CNSocket* sock);
|
||||
// Buddy list
|
||||
void refreshBuddyList(CNSocket* sock);
|
||||
}
|
||||
|
198
src/Buffs.cpp
198
src/Buffs.cpp
@@ -1,198 +0,0 @@
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
using namespace Buffs;
|
||||
|
||||
void Buff::tick(time_t currTime) {
|
||||
auto it = stacks.begin();
|
||||
while(it != stacks.end()) {
|
||||
BuffStack& stack = *it;
|
||||
//if(onTick) onTick(self, this, currTime);
|
||||
|
||||
if(stack.durationTicks == 0) {
|
||||
BuffStack deadStack = stack;
|
||||
it = stacks.erase(it);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||
} else {
|
||||
if(stack.durationTicks > 0) stack.durationTicks--;
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::combatTick(time_t currTime) {
|
||||
if(onCombatTick) onCombatTick(self, this, currTime);
|
||||
}
|
||||
|
||||
void Buff::clear() {
|
||||
while(!stacks.empty()) {
|
||||
BuffStack stack = stacks.back();
|
||||
stacks.pop_back();
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack);
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::clear(BuffClass buffClass) {
|
||||
auto it = stacks.begin();
|
||||
while(it != stacks.end()) {
|
||||
BuffStack& stack = *it;
|
||||
if(stack.buffStackClass == buffClass) {
|
||||
BuffStack deadStack = stack;
|
||||
it = stacks.erase(it);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Buff::addStack(BuffStack* stack) {
|
||||
stacks.push_back(*stack);
|
||||
if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back());
|
||||
}
|
||||
|
||||
bool Buff::hasClass(BuffClass buffClass) {
|
||||
for(BuffStack& stack : stacks) {
|
||||
if(stack.buffStackClass == buffClass)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BuffClass Buff::maxClass() {
|
||||
BuffClass buffClass = BuffClass::NONE;
|
||||
for(BuffStack& stack : stacks) {
|
||||
if(stack.buffStackClass > buffClass)
|
||||
buffClass = stack.buffStackClass;
|
||||
}
|
||||
return buffClass;
|
||||
}
|
||||
|
||||
int Buff::getValue(BuffValueSelector selector) {
|
||||
if(isStale()) return 0;
|
||||
|
||||
int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value;
|
||||
for(BuffStack& stack : stacks) {
|
||||
switch(selector)
|
||||
{
|
||||
case BuffValueSelector::NET_TOTAL:
|
||||
value += stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MIN_VALUE:
|
||||
if(stack.value < value) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MAX_VALUE:
|
||||
if(stack.value > value) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MIN_MAGNITUDE:
|
||||
if(abs(stack.value) < abs(value)) value = stack.value;
|
||||
break;
|
||||
case BuffValueSelector::MAX_MAGNITUDE:
|
||||
default:
|
||||
if(abs(stack.value) > abs(value)) value = stack.value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
EntityRef Buff::getLastSource() {
|
||||
if(stacks.empty())
|
||||
return self;
|
||||
return stacks.back().source;
|
||||
}
|
||||
|
||||
bool Buff::isStale() {
|
||||
return stacks.empty();
|
||||
}
|
||||
|
||||
/* This will practically never do anything important, but it's here just in case */
|
||||
void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) {
|
||||
if(!onUpdate) onUpdate = fOnUpdate;
|
||||
if(!onCombatTick) onCombatTick = fOnCombatTick;
|
||||
}
|
||||
|
||||
#pragma region Handlers
|
||||
void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
|
||||
if(self.kind != EntityKind::PLAYER)
|
||||
return; // not implemented
|
||||
|
||||
Player* plr = (Player*)self.getEntity();
|
||||
if(plr == nullptr)
|
||||
return; // sanity check
|
||||
|
||||
if(status == ETBU_DEL && !buff->isStale())
|
||||
return; // no premature effect deletion
|
||||
|
||||
int cbf = plr->getCompositeCondition();
|
||||
sTimeBuff payload{};
|
||||
if(status == ETBU_ADD) {
|
||||
payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE);
|
||||
// we need to explicitly add the ECSB for this buff,
|
||||
// in case this is the first stack in and the entry
|
||||
// in the buff map doesn't yet exist
|
||||
if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
||||
pkt.eTBU = status; // eTimeBuffUpdate
|
||||
pkt.eTBT = (int)stack->buffStackClass;
|
||||
pkt.iConditionBitFlag = cbf;
|
||||
pkt.TimeBuff = payload;
|
||||
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
|
||||
void Buffs::timeBuffTick(EntityRef self, Buff* buff) {
|
||||
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not implemented
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt);
|
||||
pkt.eCT = combatant->getCharType();
|
||||
pkt.iID = combatant->getID();
|
||||
pkt.iTB_ID = buff->id;
|
||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
}
|
||||
|
||||
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not a combatant
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||
int32_t eCharType = combatant->getCharType();
|
||||
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
|
||||
pkt.iID = combatant->getID();
|
||||
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) {
|
||||
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||
return; // not implemented
|
||||
Entity* entity = self.getEntity();
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
int damage = combatant->getMaxHP() / 100 * mult;
|
||||
int dealt = combatant->takeDamage(buff->getLastSource(), damage);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||
pkt->iID = self.id;
|
||||
pkt->eCT = combatant->getCharType();
|
||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||
|
||||
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
drain->iDamage = dealt;
|
||||
drain->iHP = combatant->getCurrentHP();
|
||||
drain->eCT = pkt->eCT;
|
||||
drain->iID = pkt->iID;
|
||||
|
||||
NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
}
|
||||
#pragma endregion
|
@@ -1,93 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
/* forward declaration(s) */
|
||||
class Buff;
|
||||
template<class... Types>
|
||||
using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>;
|
||||
|
||||
#define CSB_FROM_ECSB(x) (1 << (x - 1))
|
||||
|
||||
enum class BuffClass {
|
||||
NONE = ETBT_NONE,
|
||||
NANO = ETBT_NANO,
|
||||
GROUP_NANO = ETBT_GROUPNANO,
|
||||
EGG = ETBT_SHINY,
|
||||
ENVIRONMENT = ETBT_LANDEFFECT,
|
||||
ITEM = ETBT_ITEM,
|
||||
CASH_ITEM = ETBT_CASHITEM
|
||||
};
|
||||
|
||||
enum class BuffValueSelector {
|
||||
MAX_VALUE,
|
||||
MIN_VALUE,
|
||||
MAX_MAGNITUDE,
|
||||
MIN_MAGNITUDE,
|
||||
NET_TOTAL
|
||||
};
|
||||
|
||||
struct BuffStack {
|
||||
int durationTicks;
|
||||
int value;
|
||||
EntityRef source;
|
||||
BuffClass buffStackClass;
|
||||
};
|
||||
|
||||
class Buff {
|
||||
private:
|
||||
EntityRef self;
|
||||
std::vector<BuffStack> stacks;
|
||||
|
||||
public:
|
||||
int id;
|
||||
/* called just after a stack is added or removed */
|
||||
BuffCallback<int, BuffStack*> onUpdate;
|
||||
/* called when the buff is combat-ticked */
|
||||
BuffCallback<time_t> onCombatTick;
|
||||
|
||||
void tick(time_t);
|
||||
void combatTick(time_t);
|
||||
void clear();
|
||||
void clear(BuffClass buffClass);
|
||||
void addStack(BuffStack* stack);
|
||||
|
||||
/*
|
||||
* Sometimes we need to determine if a buff
|
||||
* is covered by a certain class, ex: nano
|
||||
* vs. coco egg in the case of infection protection
|
||||
*/
|
||||
bool hasClass(BuffClass buffClass);
|
||||
BuffClass maxClass();
|
||||
|
||||
int getValue(BuffValueSelector selector);
|
||||
EntityRef getLastSource();
|
||||
|
||||
/*
|
||||
* In general, a Buff object won't exist
|
||||
* unless it has stacks. However, when
|
||||
* popping stacks during iteration (onExpire),
|
||||
* stacks will be empty for a brief moment
|
||||
* when the last stack is popped.
|
||||
*/
|
||||
bool isStale();
|
||||
|
||||
void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick);
|
||||
|
||||
Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack)
|
||||
: self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) {
|
||||
addStack(firstStack);
|
||||
}
|
||||
};
|
||||
|
||||
namespace Buffs {
|
||||
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||
void timeBuffTick(EntityRef self, Buff* buff);
|
||||
void timeBuffTimeout(EntityRef self);
|
||||
void tickDrain(EntityRef self, Buff* buff, int mult);
|
||||
}
|
@@ -1,13 +1,10 @@
|
||||
#include "BuiltinCommands.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
// helper function, not a packet handler
|
||||
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -72,41 +69,31 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// Handle serverside value-changes
|
||||
switch (setData->iSetValueType) {
|
||||
case CN_GM_SET_VALUE_TYPE__HP:
|
||||
response.iSetValue = plr->HP = setData->iSetValue;
|
||||
case 1:
|
||||
plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||
case 2:
|
||||
plr->batteryW = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryW;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||
case 3:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryN;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||
case 4:
|
||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||
response.iSetValue = plr->fusionmatter;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||
response.iSetValue = plr->money = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__SPEED:
|
||||
case CN_GM_SET_VALUE_TYPE__JUMP:
|
||||
response.iSetValue = setData->iSetValue;
|
||||
case 5:
|
||||
plr->money = setData->iSetValue;
|
||||
break;
|
||||
}
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iSetValue = setData->iSetValue;
|
||||
response.iSetValueType = setData->iSetValueType;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);
|
||||
@@ -260,17 +247,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
||||
uint64_t instance = plr->instanceID;
|
||||
const int unstickRange = 400;
|
||||
|
||||
switch ((eCN_GM_TeleportType)req->eTeleportType) {
|
||||
case eCN_GM_TeleportType::MyLocation:
|
||||
switch (req->eTeleportType) {
|
||||
case eCN_GM_TeleportMapType__MyLocation:
|
||||
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
||||
break;
|
||||
case eCN_GM_TeleportType::MapXYZ:
|
||||
case eCN_GM_TeleportMapType__MapXYZ:
|
||||
instance = req->iToMap;
|
||||
// fallthrough
|
||||
case eCN_GM_TeleportType::XYZ:
|
||||
case eCN_GM_TeleportMapType__XYZ:
|
||||
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
||||
break;
|
||||
case eCN_GM_TeleportType::SomeoneLocation:
|
||||
case eCN_GM_TeleportMapType__SomeoneLocation:
|
||||
// player to teleport to
|
||||
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
||||
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
||||
@@ -282,7 +269,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
||||
|
||||
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
||||
break;
|
||||
case eCN_GM_TeleportType::Unstick:
|
||||
case eCN_GM_TeleportMapType__Unstick:
|
||||
targetPlr = PlayerManager::getPlayer(targetSock);
|
||||
|
||||
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),
|
||||
@@ -296,56 +283,35 @@ static void itemGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr->accountLevel > 50) {
|
||||
// TODO: send fail packet
|
||||
// TODO: send fail packet
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp);
|
||||
if (itemreq->eIL == 2) {
|
||||
// Quest item, not a real item, handle this later, stubbed for now
|
||||
} else if (itemreq->eIL == 1 && itemreq->Item.iType >= 0 && itemreq->Item.iType <= 10) {
|
||||
|
||||
if (itemreq->eIL == 1) {
|
||||
|
||||
if (Items::ItemData.find(std::pair<int32_t, int32_t>(itemreq->Item.iID, itemreq->Item.iType)) == Items::ItemData.end()
|
||||
|| itemreq->Item.iType < 0 || itemreq->Item.iType > 10) {
|
||||
if (Items::ItemData.find(std::pair<int32_t, int32_t>(itemreq->Item.iID, itemreq->Item.iType)) == Items::ItemData.end()) {
|
||||
// invalid item
|
||||
std::cout << "[WARN] Item id " << itemreq->Item.iID << " with type " << itemreq->Item.iType << " is invalid (give item)" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp);
|
||||
|
||||
resp.eIL = itemreq->eIL;
|
||||
resp.iSlotNum = itemreq->iSlotNum;
|
||||
if (itemreq->Item.iType == 10) {
|
||||
// item is vehicle, set expiration date
|
||||
// set time limit: current time + 7days
|
||||
itemreq->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
}
|
||||
resp.Item = itemreq->Item;
|
||||
|
||||
plr->Inven[itemreq->iSlotNum] = itemreq->Item;
|
||||
} else if (itemreq->eIL == 2) {
|
||||
int id = itemreq->Item.iID;
|
||||
int slot = Missions::findQSlot(plr, id);
|
||||
|
||||
if (slot == -1) {
|
||||
std::cout << "[WARN] Player has no room for quest items" << std::endl;
|
||||
return;
|
||||
}
|
||||
if (id != 0)
|
||||
std::cout << "new qitem in slot " << slot << std::endl;
|
||||
|
||||
// update player
|
||||
if (id != 0) {
|
||||
plr->QInven[slot].iType = 8;
|
||||
plr->QInven[slot].iID = id;
|
||||
plr->QInven[slot].iOpt += itemreq->Item.iOpt;
|
||||
|
||||
// destroy the item if its 0
|
||||
if (plr->QInven[slot].iOpt == 0)
|
||||
memset(&plr->QInven[slot], 0, sizeof(sItemBase));
|
||||
}
|
||||
std::cout << "Item id " << id << " is in slot " << slot << " of count " << plr->QInven[slot].iOpt << std::endl;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
}
|
||||
|
||||
resp.eIL = itemreq->eIL;
|
||||
resp.iSlotNum = itemreq->iSlotNum;
|
||||
resp.Item = itemreq->Item;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
}
|
||||
|
||||
static void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
26
src/Chat.cpp
26
src/Chat.cpp
@@ -1,9 +1,6 @@
|
||||
#include "Chat.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "CustomCommands.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
@@ -22,6 +19,10 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the player has an onChat Lua event registered, call it
|
||||
if (plr->onChat != nullptr)
|
||||
plr->onChat->call(fullChat.c_str());
|
||||
|
||||
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__MUTE_FREECHAT)
|
||||
return;
|
||||
|
||||
@@ -228,6 +229,10 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
|
||||
@@ -250,15 +255,16 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
if (plr->group == nullptr)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
else
|
||||
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
@@ -273,9 +279,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
|
||||
if (plr->group == nullptr)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
// we only allow plain ascii, at least for now
|
||||
|
@@ -2,10 +2,7 @@
|
||||
|
||||
#define CMD_PREFIX '/'
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
namespace Chat {
|
||||
extern std::vector<std::string> dump;
|
||||
|
102
src/Chunking.cpp
102
src/Chunking.cpp
@@ -1,22 +1,17 @@
|
||||
#include "Chunking.hpp"
|
||||
|
||||
#include "MobAI.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include "settings.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Eggs.hpp"
|
||||
|
||||
using namespace Chunking;
|
||||
|
||||
/*
|
||||
* The initial chunkPos value before a player is placed into the world.
|
||||
*/
|
||||
const ChunkPos Chunking::INVALID_CHUNK = {};
|
||||
|
||||
std::map<ChunkPos, Chunk*> Chunking::chunks;
|
||||
|
||||
static void newChunk(ChunkPos pos) {
|
||||
if (chunkExists(pos)) {
|
||||
std::cout << "[WARN] Tried to create a chunk that already exists" << std::endl;
|
||||
std::cout << "[WARN] Tried to create a chunk that already exists\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,13 +21,13 @@ static void newChunk(ChunkPos pos) {
|
||||
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||
for (Chunk* c : surroundings)
|
||||
for (const EntityRef ref : c->entities)
|
||||
for (const EntityRef& ref : c->entities)
|
||||
ref.getEntity()->viewableChunks.insert(chunk);
|
||||
}
|
||||
|
||||
static void deleteChunk(ChunkPos pos) {
|
||||
if (!chunkExists(pos)) {
|
||||
std::cout << "[WARN] Tried to delete a chunk that doesn't exist" << std::endl;
|
||||
std::cout << "[WARN] Tried to delete a chunk that doesn't exist\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,24 +36,24 @@ static void deleteChunk(ChunkPos pos) {
|
||||
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||
for(Chunk* c : surroundings)
|
||||
for (const EntityRef ref : c->entities)
|
||||
for (const EntityRef& ref : c->entities)
|
||||
ref.getEntity()->viewableChunks.erase(chunk);
|
||||
|
||||
chunks.erase(pos); // remove from map
|
||||
delete chunk; // free from memory
|
||||
}
|
||||
|
||||
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
||||
if (!chunkExists(chunkPos))
|
||||
return; // shouldn't happen
|
||||
|
||||
chunks[chunkPos]->entities.insert(ref);
|
||||
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
chunks[chunkPos]->nplayers++;
|
||||
}
|
||||
|
||||
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
||||
if (!chunkExists(chunkPos))
|
||||
return; // do nothing if chunk doesn't even exist
|
||||
|
||||
@@ -66,7 +61,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
|
||||
chunk->entities.erase(ref); // gone
|
||||
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
chunks[chunkPos]->nplayers--;
|
||||
assert(chunks[chunkPos]->nplayers >= 0);
|
||||
|
||||
@@ -75,13 +70,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||
deleteChunk(chunkPos);
|
||||
}
|
||||
|
||||
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
||||
Entity *ent = ref.getEntity();
|
||||
bool alive = ent->isExtant();
|
||||
bool alive = ent->isAlive();
|
||||
|
||||
// TODO: maybe optimize this, potentially using AROUND packets?
|
||||
for (Chunk *chunk : chnks) {
|
||||
for (const EntityRef otherRef : chunk->entities) {
|
||||
for (const EntityRef& otherRef : chunk->entities) {
|
||||
// skip oneself
|
||||
if (ref == otherRef)
|
||||
continue;
|
||||
@@ -89,31 +84,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the existence of this Entity
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
||||
ent->enterIntoViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the existence of all visible Entities
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
if (ref.type == EntityType::PLAYER && other->isAlive()) {
|
||||
other->enterIntoViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, increment playersInView
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
||||
((Mob*)ent)->playersInView++;
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
||||
((Mob*)other)->playersInView++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
||||
Entity *ent = ref.getEntity();
|
||||
bool alive = ent->isExtant();
|
||||
bool alive = ent->isAlive();
|
||||
|
||||
// TODO: same as above
|
||||
for (Chunk *chunk : chnks) {
|
||||
for (const EntityRef otherRef : chunk->entities) {
|
||||
for (const EntityRef& otherRef : chunk->entities) {
|
||||
// skip oneself
|
||||
if (ref == otherRef)
|
||||
continue;
|
||||
@@ -121,19 +116,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef re
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the departure of this Entity
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
||||
ent->disappearFromViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the departure of all visible Entities
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
if (ref.type == EntityType::PLAYER && other->isAlive()) {
|
||||
other->disappearFromViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, decrement playersInView
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
||||
((Mob*)ent)->playersInView--;
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
||||
((Mob*)other)->playersInView--;
|
||||
}
|
||||
}
|
||||
@@ -154,8 +149,8 @@ static void emptyChunk(ChunkPos chunkPos) {
|
||||
|
||||
// unspawn all of the mobs/npcs
|
||||
std::set refs(chunk->entities);
|
||||
for (const EntityRef ref : refs) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
for (const EntityRef& ref : refs) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
assert(0);
|
||||
|
||||
// every call of this will check if the chunk is empty and delete it if so
|
||||
@@ -163,7 +158,7 @@ static void emptyChunk(ChunkPos chunkPos) {
|
||||
}
|
||||
}
|
||||
|
||||
void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) {
|
||||
void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) {
|
||||
Entity* ent = ref.getEntity();
|
||||
|
||||
// move to other chunk's player set
|
||||
@@ -205,7 +200,7 @@ bool Chunking::chunkExists(ChunkPos chunk) {
|
||||
}
|
||||
|
||||
ChunkPos Chunking::chunkPosAt(int posX, int posY, uint64_t instanceID) {
|
||||
return ChunkPos(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||
return std::make_tuple(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||
}
|
||||
|
||||
std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
||||
@@ -218,7 +213,7 @@ std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
||||
// grabs surrounding chunks if they exist
|
||||
for (int i = -1; i < 2; i++) {
|
||||
for (int z = -1; z < 2; z++) {
|
||||
ChunkPos pos = ChunkPos(x+i, y+z, inst);
|
||||
ChunkPos pos = std::make_tuple(x+i, y+z, inst);
|
||||
|
||||
// if chunk exists, add it to the set
|
||||
if (chunkExists(pos))
|
||||
@@ -267,53 +262,54 @@ void Chunking::createInstance(uint64_t instanceID) {
|
||||
|
||||
std::cout << "Creating instance " << instanceID << std::endl;
|
||||
for (ChunkPos &coords : templateChunks) {
|
||||
for (const EntityRef ref : chunks[coords]->entities) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
for (const EntityRef& ref : chunks[coords]->entities) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
continue;
|
||||
|
||||
int npcID = ref.id;
|
||||
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
||||
|
||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||
if (baseNPC->kind == EntityKind::MOB) {
|
||||
if (baseNPC->type == EntityType::MOB) {
|
||||
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
||||
continue; // follower; don't copy individually
|
||||
|
||||
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
|
||||
instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
|
||||
NPCManager::NPCs[newMob->id] = newMob;
|
||||
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--);
|
||||
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob;
|
||||
|
||||
// if in a group, copy over group members as well
|
||||
if (((Mob*)baseNPC)->groupLeader != 0) {
|
||||
newMob->groupLeader = newMob->id; // set leader ID for new leader
|
||||
newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader
|
||||
Mob* mobData = (Mob*)baseNPC;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (mobData->groupMember[i] != 0) {
|
||||
int followerID = NPCManager::nextId--; // id for follower
|
||||
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
||||
// new follower instance
|
||||
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
|
||||
instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
|
||||
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle,
|
||||
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID);
|
||||
// add follower to NPC maps
|
||||
NPCManager::NPCs[followerID] = newMobFollower;
|
||||
// set follower-specific properties
|
||||
newMobFollower->groupLeader = newMob->id;
|
||||
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID;
|
||||
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
||||
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
||||
// add follower copy to leader copy
|
||||
newMob->groupMember[i] = followerID;
|
||||
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
||||
instanceID, baseFollower->angle);
|
||||
instanceID, baseFollower->appearanceData.iAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->angle);
|
||||
NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->appearanceData.iAngle);
|
||||
} else {
|
||||
BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
|
||||
NPCManager::NPCs[newNPC->id] = newNPC;
|
||||
NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->angle);
|
||||
BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--);
|
||||
NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC;
|
||||
NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||
instanceID, baseNPC->appearanceData.iAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
//std::set<CNSocket*> players;
|
||||
//std::set<int32_t> NPCs;
|
||||
std::set<EntityRef> entities;
|
||||
int nplayers = 0;
|
||||
};
|
||||
|
||||
// to help the readability of ChunkPos
|
||||
typedef std::tuple<int, int, uint64_t> _ChunkPos;
|
||||
|
||||
class ChunkPos : public _ChunkPos {
|
||||
public:
|
||||
ChunkPos() : _ChunkPos(0, 0, (uint64_t) -1) {}
|
||||
ChunkPos(int x, int y, uint64_t inst) : _ChunkPos(x, y, inst) {}
|
||||
};
|
||||
|
||||
enum {
|
||||
INSTANCE_OVERWORLD, // default instance every player starts in
|
||||
INSTANCE_IZ, // these aren't actually used
|
||||
@@ -30,15 +26,13 @@ enum {
|
||||
namespace Chunking {
|
||||
extern std::map<ChunkPos, Chunk*> chunks;
|
||||
|
||||
extern const ChunkPos INVALID_CHUNK;
|
||||
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to);
|
||||
|
||||
void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to);
|
||||
void trackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
||||
void untrackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
||||
|
||||
void trackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||
void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||
|
||||
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref);
|
||||
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref);
|
||||
|
||||
bool chunkExists(ChunkPos chunk);
|
||||
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
||||
|
717
src/Combat.cpp
717
src/Combat.cpp
@@ -1,331 +1,22 @@
|
||||
#include "Combat.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Rand.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
|
||||
using namespace Combat;
|
||||
|
||||
/// Player Id -> Bullet Id -> Bullet
|
||||
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
||||
|
||||
#pragma region Player
|
||||
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||
if(!isAlive())
|
||||
return false;
|
||||
|
||||
if(!hasBuff(buffId)) {
|
||||
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||
buffs[buffId]->addStack(stack);
|
||||
return false;
|
||||
}
|
||||
|
||||
Buff* Player::getBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
return buffs[buffId];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Player::removeBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear();
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::removeBuff(int buffId, BuffClass buffClass) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear(buffClass);
|
||||
// buff might not be stale since another buff class might remain
|
||||
if(buffs[buffId]->isStale()) {
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::clearBuffs(bool force) {
|
||||
auto it = buffs.begin();
|
||||
while(it != buffs.end()) {
|
||||
Buff* buff = (*it).second;
|
||||
if(!force) buff->clear();
|
||||
delete buff;
|
||||
it = buffs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool Player::hasBuff(int buffId) {
|
||||
auto buff = buffs.find(buffId);
|
||||
return buff != buffs.end() && !buff->second->isStale();
|
||||
}
|
||||
|
||||
int Player::getCompositeCondition() {
|
||||
int conditionBitFlag = 0;
|
||||
for(auto buff : buffs) {
|
||||
if(!buff.second->isStale() && buff.second->id > 0)
|
||||
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
||||
}
|
||||
return conditionBitFlag;
|
||||
}
|
||||
|
||||
int Player::takeDamage(EntityRef src, int amt) {
|
||||
int dmg = amt;
|
||||
if(HP - dmg < 0) dmg = HP;
|
||||
HP -= dmg;
|
||||
|
||||
return dmg;
|
||||
}
|
||||
|
||||
int Player::heal(EntityRef src, int amt) {
|
||||
int heal = amt;
|
||||
if(HP + heal > getMaxHP()) heal = getMaxHP() - HP;
|
||||
HP += heal;
|
||||
|
||||
return heal;
|
||||
}
|
||||
|
||||
bool Player::isAlive() {
|
||||
return HP > 0;
|
||||
}
|
||||
|
||||
int Player::getCurrentHP() {
|
||||
return HP;
|
||||
}
|
||||
|
||||
int Player::getMaxHP() {
|
||||
return PC_MAXHEALTH(level);
|
||||
}
|
||||
|
||||
int Player::getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
std::vector<EntityRef> Player::getGroupMembers() {
|
||||
std::vector<EntityRef> members;
|
||||
if(group != nullptr)
|
||||
members = group->members;
|
||||
else
|
||||
members.push_back(PlayerManager::getSockFromID(iID));
|
||||
return members;
|
||||
}
|
||||
|
||||
int32_t Player::getCharType() {
|
||||
return 1; // eCharType (eCT_PC)
|
||||
}
|
||||
|
||||
int32_t Player::getID() {
|
||||
return iID;
|
||||
}
|
||||
|
||||
EntityRef Player::getRef() {
|
||||
return EntityRef(PlayerManager::getSockFromID(iID));
|
||||
}
|
||||
|
||||
void Player::step(time_t currTime) {
|
||||
CNSocket* sock = getRef().sock;
|
||||
|
||||
// nanos
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (activeNano != 0 && equippedNanos[i] == activeNano) { // tick active nano
|
||||
sNano& nano = Nanos[activeNano];
|
||||
int drainRate = 0;
|
||||
|
||||
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
|
||||
// nano has skill data
|
||||
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
|
||||
int boost = Nanos::getNanoBoost(this);
|
||||
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||
drainRate = skill->batteryUse[boost * 3];
|
||||
}
|
||||
|
||||
nano.iStamina -= 1 + drainRate / 5;
|
||||
if (nano.iStamina <= 0)
|
||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
||||
|
||||
} else if (Nanos[equippedNanos[i]].iStamina < 150) { // tick resting nano
|
||||
sNano& nano = Nanos[equippedNanos[i]];
|
||||
if (nano.iStamina < 150)
|
||||
nano.iStamina += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// buffs
|
||||
for(auto buffEntry : buffs) {
|
||||
buffEntry.second->combatTick(currTime);
|
||||
if(!isAlive())
|
||||
break; // unsafe to keep ticking if we're dead
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CombatNPC
|
||||
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||
if(!isAlive())
|
||||
return false;
|
||||
|
||||
if (this->state != AIState::COMBAT && this->state != AIState::ROAMING)
|
||||
return false;
|
||||
|
||||
if(!hasBuff(buffId)) {
|
||||
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||
buffs[buffId]->addStack(stack);
|
||||
return false;
|
||||
}
|
||||
|
||||
Buff* CombatNPC::getBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
return buffs[buffId];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CombatNPC::removeBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear();
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
}
|
||||
|
||||
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear(buffClass);
|
||||
// buff might not be stale since another buff class might remain
|
||||
if(buffs[buffId]->isStale()) {
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CombatNPC::clearBuffs(bool force) {
|
||||
auto it = buffs.begin();
|
||||
while(it != buffs.end()) {
|
||||
Buff* buff = (*it).second;
|
||||
if(!force) buff->clear();
|
||||
delete buff;
|
||||
it = buffs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool CombatNPC::hasBuff(int buffId) {
|
||||
auto buff = buffs.find(buffId);
|
||||
return buff != buffs.end() && !buff->second->isStale();
|
||||
}
|
||||
|
||||
int CombatNPC::getCompositeCondition() {
|
||||
int conditionBitFlag = 0;
|
||||
for(auto buff : buffs) {
|
||||
if(!buff.second->isStale() && buff.second->id > 0)
|
||||
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
||||
}
|
||||
return conditionBitFlag;
|
||||
}
|
||||
|
||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||
int dmg = amt;
|
||||
if(hp - dmg < 0) dmg = hp;
|
||||
hp -= dmg;
|
||||
|
||||
if(hp <= 0) transition(AIState::DEAD, src);
|
||||
|
||||
return dmg;
|
||||
}
|
||||
|
||||
int CombatNPC::heal(EntityRef src, int amt) {
|
||||
int heal = amt;
|
||||
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
|
||||
hp += heal;
|
||||
|
||||
return heal;
|
||||
}
|
||||
|
||||
bool CombatNPC::isAlive() {
|
||||
return hp > 0;
|
||||
}
|
||||
|
||||
int CombatNPC::getCurrentHP() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
int CombatNPC::getMaxHP() {
|
||||
return maxHealth;
|
||||
}
|
||||
|
||||
int CombatNPC::getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
std::vector<EntityRef> CombatNPC::getGroupMembers() {
|
||||
std::vector<EntityRef> members;
|
||||
if(group != nullptr)
|
||||
members = group->members;
|
||||
else
|
||||
members.push_back(id);
|
||||
return members;
|
||||
}
|
||||
|
||||
int32_t CombatNPC::getCharType() {
|
||||
if(kind == EntityKind::MOB)
|
||||
return 4; // eCharType (eCT_MOB)
|
||||
return 2; // eCharType (eCT_NPC)
|
||||
}
|
||||
|
||||
int32_t CombatNPC::getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
EntityRef CombatNPC::getRef() {
|
||||
return EntityRef(id);
|
||||
}
|
||||
|
||||
void CombatNPC::step(time_t currTime) {
|
||||
|
||||
if(stateHandlers.find(state) != stateHandlers.end())
|
||||
stateHandlers[state](this, currTime);
|
||||
else {
|
||||
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
|
||||
transition(AIState::INACTIVE, id);
|
||||
}
|
||||
}
|
||||
|
||||
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||
state = newState;
|
||||
|
||||
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
||||
transitionHandlers[newState](this, src);
|
||||
else {
|
||||
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
||||
transition(AIState::INACTIVE, id);
|
||||
}
|
||||
|
||||
// trigger special NPCEvents, if applicable
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.triggerState == newState && event.npcType == type)
|
||||
event.handler(this);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||
bool batteryBoost, int attackerStyle,
|
||||
int defenderStyle, int difficulty) {
|
||||
@@ -365,10 +56,14 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTargets) {
|
||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
time_t currTime = getTime();
|
||||
auto targets = (int32_t*)data->trailers;
|
||||
|
||||
// rapid fire anti-cheat
|
||||
// TODO: move this out of here, when generalizing packet frequency validation
|
||||
time_t currTime = getTime();
|
||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
||||
@@ -376,28 +71,11 @@ static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTarget
|
||||
|
||||
plr->lastShot = currTime;
|
||||
|
||||
// 3+ targets should never be possible
|
||||
if (!allowManyTargets && targetCount > 3)
|
||||
plr->suspicionRating += 10001;
|
||||
if (pkt->iNPCCnt > 3) // 3+ targets should never be possible
|
||||
plr->suspicionRating += 10000;
|
||||
|
||||
// kill the socket when the player is too suspicious
|
||||
if (plr->suspicionRating > 10000) {
|
||||
if (plr->suspicionRating > 10000) // kill the socket when the player is too suspicious
|
||||
sock->kill();
|
||||
CNShardServer::_killConnection(sock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
auto targets = (int32_t*)data->trailers;
|
||||
|
||||
// kick the player if firing too rapidly
|
||||
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt, false))
|
||||
return;
|
||||
|
||||
/*
|
||||
* IMPORTANT: This validates memory safety in addition to preventing
|
||||
@@ -423,7 +101,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
if (npc->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
@@ -446,11 +124,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
damage.first = mob->takeDamage(sock, damage.first);
|
||||
damage.first = hitMob(sock, mob, damage.first);
|
||||
|
||||
respdata[i].iID = mob->id;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = mob->hp;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||
}
|
||||
|
||||
@@ -472,12 +150,12 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
|
||||
|
||||
INITVARPACKET(respbuf, sP_FE2CL_NPC_ATTACK_PCs, pkt, sAttackResult, atk);
|
||||
|
||||
auto damage = getDamage(450 + (int)mob->data["m_iPower"], plr->defense, true, false, -1, -1, 0);
|
||||
auto damage = getDamage(450 + (int)mob->data["m_iPower"], plr->defense, false, false, -1, -1, 0);
|
||||
|
||||
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
plr->HP -= damage.first;
|
||||
|
||||
pkt->iNPC_ID = mob->id;
|
||||
pkt->iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
pkt->iPCCnt = 1;
|
||||
|
||||
atk->iID = plr->iID;
|
||||
@@ -489,25 +167,122 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
|
||||
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
||||
|
||||
if (plr->HP <= 0) {
|
||||
if (!MobAI::aggroCheck(mob, getTime()))
|
||||
mob->transition(AIState::RETREAT, mob->target);
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!MobAI::aggroCheck(mob, currTime)) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When a group of players is doing missions together, we want them to all get
|
||||
* quest items at the same time, but we don't want the odds of quest item
|
||||
* drops from different missions to be linked together. That's why we use a
|
||||
* single RNG roll per mission task, and every group member shares that same
|
||||
* set of rolls.
|
||||
*/
|
||||
void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) {
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
||||
// cannot kill mobs multiple times; cannot harm retreating mobs
|
||||
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) {
|
||||
return 0; // no damage
|
||||
}
|
||||
|
||||
Player* member = players[i];
|
||||
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
||||
if (member->tasks[j] != 0)
|
||||
rolls[member->tasks[j]] = Rand::rand();
|
||||
if (mob->skillStyle >= 0)
|
||||
return 0; // don't hurt a mob casting corruption
|
||||
|
||||
if (mob->state == MobState::ROAMING) {
|
||||
assert(mob->target == nullptr);
|
||||
MobAI::enterCombat(sock, mob);
|
||||
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::followToCombat(mob);
|
||||
}
|
||||
|
||||
mob->appearanceData.iHP -= damage;
|
||||
|
||||
// wake up sleeping monster
|
||||
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
|
||||
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = mob->appearanceData.iNPC_ID;
|
||||
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
|
||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
if (mob->appearanceData.iHP <= 0)
|
||||
killMob(mob->target, mob);
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
void Combat::killMob(CNSocket *sock, Mob *mob) {
|
||||
mob->state = MobState::DEAD;
|
||||
mob->target = nullptr;
|
||||
mob->appearanceData.iConditionBitFlag = 0;
|
||||
mob->skillStyle = -1;
|
||||
mob->unbuffTimes.clear();
|
||||
mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
||||
|
||||
// check for the edge case where hitting the mob did not aggro it
|
||||
if (sock != nullptr) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
Items::DropRoll rolled;
|
||||
Items::DropRoll eventRolled;
|
||||
int rolledQItem = Rand::rand();
|
||||
|
||||
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
||||
Items::giveMobDrop(sock, mob, rolled, eventRolled);
|
||||
Missions::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem);
|
||||
} else {
|
||||
Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlayer == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < otherPlayer->groupCnt; i++) {
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlayer->groupIDs[i]);
|
||||
if (sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
Player *otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
|
||||
// only contribute to group members' kills if they're close enough
|
||||
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
|
||||
if (dist > 5000)
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
|
||||
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delay the despawn animation
|
||||
mob->despawned = false;
|
||||
|
||||
// fire any triggered events
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType)
|
||||
event.handler(sock, mob);
|
||||
|
||||
auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID);
|
||||
if (it == Transport::NPCQueues.end() || it->second.empty())
|
||||
return;
|
||||
|
||||
// rewind or empty the movement queue
|
||||
if (mob->staticPath) {
|
||||
/*
|
||||
* This is inelegant, but we wind forward in the path until we find the point that
|
||||
* corresponds with the Mob's spawn point.
|
||||
*
|
||||
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
|
||||
*/
|
||||
auto& queue = it->second;
|
||||
for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) {
|
||||
queue.pop();
|
||||
queue.push(point);
|
||||
}
|
||||
} else {
|
||||
Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,27 +308,40 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
||||
plr->healCooldown = 4000;
|
||||
}
|
||||
|
||||
static void dealGooDamage(CNSocket *sock) {
|
||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
|
||||
return; // ignore completely
|
||||
|
||||
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
|
||||
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
||||
|
||||
pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID
|
||||
pkt1.eTBU = 1; // eTimeBuffUpdate
|
||||
pkt1.eTBT = 0; // eTimeBuffType 1 means nano
|
||||
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
|
||||
|
||||
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
|
||||
static void dealGooDamage(CNSocket *sock, int amount) {
|
||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
|
||||
int amount = PC_MAXHEALTH(plr->level) * 3 / 20;
|
||||
Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
|
||||
if (protectionBuff != nullptr) {
|
||||
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) {
|
||||
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
||||
dmg->bProtected = 1;
|
||||
|
||||
// eggs allow protection without nanos
|
||||
if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1)
|
||||
if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION))
|
||||
plr->Nanos[plr->activeNano].iStamina -= 3;
|
||||
} else {
|
||||
plr->HP -= amount;
|
||||
@@ -577,54 +365,27 @@ static void dealGooDamage(CNSocket *sock) {
|
||||
dmg->iID = plr->iID;
|
||||
dmg->iDamage = amount;
|
||||
dmg->iHP = plr->HP;
|
||||
dmg->iConditionBitFlag = plr->getCompositeCondition();
|
||||
dmg->iConditionBitFlag = plr->iConditionBitFlag;
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
}
|
||||
|
||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// infection debuff toggles as the client asks it to,
|
||||
// so we add and remove a permanent debuff
|
||||
if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) {
|
||||
BuffStack infection = {
|
||||
-1, // infinite
|
||||
0, // no value
|
||||
sock, // self-inflicted
|
||||
BuffClass::ENVIRONMENT
|
||||
};
|
||||
plr->addBuff(ECSB_INFECTION,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
if(self.kind == EntityKind::PLAYER)
|
||||
dealGooDamage(self.sock);
|
||||
},
|
||||
&infection);
|
||||
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
||||
plr->removeBuff(ECSB_INFECTION);
|
||||
}
|
||||
}
|
||||
|
||||
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// only GMs can use this variant
|
||||
// only GMs can use this this variant
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) {
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
|
||||
@@ -643,19 +404,11 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
resp->iTargetCnt = pkt->iTargetCnt;
|
||||
|
||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
||||
|
||||
ICombatant* target = nullptr;
|
||||
std::pair<int, int> damage;
|
||||
|
||||
if (pkt->iTargetCnt > 1)
|
||||
damage.first = plr->groupDamage;
|
||||
else
|
||||
damage.first = plr->pointDamage;
|
||||
|
||||
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
|
||||
if (pktdata[i*2+1] == 1) { // eCT == 1; attack player
|
||||
Player *target = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->iID == pktdata[i].iID) {
|
||||
if (pair.second->iID == pktdata[i*2]) {
|
||||
target = pair.second;
|
||||
break;
|
||||
}
|
||||
@@ -667,41 +420,67 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
return;
|
||||
}
|
||||
|
||||
damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
|
||||
std::pair<int,int> damage;
|
||||
|
||||
if (pkt->iTargetCnt > 1)
|
||||
damage.first = plr->groupDamage;
|
||||
else
|
||||
damage.first = plr->pointDamage;
|
||||
|
||||
damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
|
||||
|
||||
if (plr->batteryW >= 6 + plr->level)
|
||||
plr->batteryW -= 6 + plr->level;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
target->HP -= damage.first;
|
||||
|
||||
respdata[i].eCT = pktdata[i*2+1];
|
||||
respdata[i].iID = target->iID;
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = target->HP;
|
||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||
} else { // eCT == 4; attack mob
|
||||
|
||||
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
|
||||
if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]];
|
||||
if (npc->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
Mob* mob = (Mob*)npc;
|
||||
target = mob;
|
||||
|
||||
std::pair<int,int> damage;
|
||||
|
||||
if (pkt->iTargetCnt > 1)
|
||||
damage.first = plr->groupDamage;
|
||||
else
|
||||
damage.first = plr->pointDamage;
|
||||
|
||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||
|
||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
|
||||
if (plr->batteryW >= 6 + difficulty)
|
||||
plr->batteryW -= 6 + difficulty;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
damage.first = hitMob(sock, mob, damage.first);
|
||||
|
||||
respdata[i].eCT = pktdata[i*2+1];
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||
}
|
||||
|
||||
if (plr->batteryW >= 6 + plr->level)
|
||||
plr->batteryW -= 6 + plr->level;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
damage.first = target->takeDamage(sock, damage.first);
|
||||
|
||||
respdata[i].eCT = pktdata[i].eCT;
|
||||
respdata[i].iID = target->getID();
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = target->getCurrentHP();
|
||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
||||
@@ -837,9 +616,17 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// kick the player if firing too rapidly
|
||||
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iTargetCnt, true))
|
||||
return;
|
||||
// rapid fire anti-cheat
|
||||
time_t currTime = getTime();
|
||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
|
||||
|
||||
plr->lastShot = currTime;
|
||||
|
||||
if (plr->suspicionRating > 10000) // kill the socket when the player is too suspicious
|
||||
sock->kill();
|
||||
|
||||
/*
|
||||
* initialize response struct
|
||||
@@ -869,7 +656,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
if (npc->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
@@ -882,11 +669,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
|
||||
damage.first = mob->takeDamage(sock, damage.first);
|
||||
damage.first = hitMob(sock, mob, damage.first);
|
||||
|
||||
respdata[i].iID = mob->id;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = mob->hp;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
respdata[i].iHitFlag = damage.second;
|
||||
}
|
||||
|
||||
@@ -901,7 +688,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
static void playerTick(CNServer *serv, time_t currTime) {
|
||||
static time_t lastHealTime = 0;
|
||||
static time_t lastCombatTIme = 0;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
CNSocket *sock = pair.first;
|
||||
@@ -909,13 +695,18 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
bool transmit = false;
|
||||
|
||||
// group ticks
|
||||
if (plr->group != nullptr)
|
||||
Groups::groupTickInfo(sock);
|
||||
if (plr->groupCnt > 1)
|
||||
Groups::groupTickInfo(plr);
|
||||
|
||||
// do not tick dead players
|
||||
if (plr->HP <= 0)
|
||||
continue;
|
||||
|
||||
// fm patch/lake damage
|
||||
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION)
|
||||
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
|
||||
|
||||
// heal
|
||||
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
||||
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
||||
@@ -927,24 +718,22 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
plr->healCooldown -= 4000;
|
||||
}
|
||||
|
||||
// combat tick
|
||||
if(currTime - lastCombatTIme >= 2000) {
|
||||
plr->step(currTime);
|
||||
transmit = true;
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina
|
||||
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5;
|
||||
|
||||
// nanos
|
||||
if (plr->activeNano != 0) { // tick active nano
|
||||
sNano* nano = plr->getActiveNano();
|
||||
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||
// nano has skill data
|
||||
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||
int32_t targets[] = { plr->iID };
|
||||
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
|
||||
}
|
||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
||||
|
||||
transmit = true;
|
||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
|
||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
||||
nano.iStamina += 1;
|
||||
|
||||
if (nano.iStamina > 150)
|
||||
nano.iStamina = 150;
|
||||
|
||||
transmit = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,20 +749,6 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
|
||||
}
|
||||
|
||||
// process buffsets
|
||||
auto it = plr->buffs.begin();
|
||||
while(it != plr->buffs.end()) {
|
||||
Buff* buff = (*it).second;
|
||||
//buff->combatTick() gets called in Player::step
|
||||
buff->tick(currTime);
|
||||
if(buff->isStale()) {
|
||||
// garbage collect
|
||||
it = plr->buffs.erase(it);
|
||||
delete buff;
|
||||
}
|
||||
else it++;
|
||||
}
|
||||
|
||||
if (transmit) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||
|
||||
@@ -988,15 +763,13 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
}
|
||||
}
|
||||
|
||||
// if this was a heal/combat tick, update the counters outside of the loop
|
||||
// if this was a heal tick, update the counter outside of the loop
|
||||
if (currTime - lastHealTime >= 4000)
|
||||
lastHealTime = currTime;
|
||||
if(currTime - lastCombatTIme >= 2000)
|
||||
lastCombatTIme = currTime;
|
||||
}
|
||||
|
||||
void Combat::init() {
|
||||
REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK);
|
||||
REGISTER_SHARD_TIMER(playerTick, 2000);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
||||
|
||||
|
@@ -1,10 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "NPC.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
struct Bullet {
|
||||
int pointDamage;
|
||||
@@ -19,5 +24,6 @@ namespace Combat {
|
||||
void init();
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
|
||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
||||
void killMob(CNSocket *sock, Mob *mob);
|
||||
}
|
||||
|
@@ -1,20 +1,20 @@
|
||||
#include "CustomCommands.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Missions.hpp"
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <limits.h>
|
||||
#include <iterator>
|
||||
#include <math.h>
|
||||
|
||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
||||
|
||||
@@ -83,77 +83,7 @@ static void helpCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
}
|
||||
|
||||
static void accessCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
Chat::sendServerMessage(sock, "Usage: /access <id> [new_level]");
|
||||
Chat::sendServerMessage(sock, "Use . for id to select yourself");
|
||||
return;
|
||||
}
|
||||
|
||||
char *tmp;
|
||||
|
||||
Player* self = PlayerManager::getPlayer(sock);
|
||||
int selfAccess = self->accountLevel;
|
||||
|
||||
Player* player;
|
||||
if (args[1].compare(".") == 0) {
|
||||
player = self;
|
||||
} else {
|
||||
int id = std::strtol(args[1].c_str(), &tmp, 10);
|
||||
if (*tmp) {
|
||||
Chat::sendServerMessage(sock, "Invalid player ID " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
player = PlayerManager::getPlayerFromID(id);
|
||||
if (player == nullptr) {
|
||||
Chat::sendServerMessage(sock, "Could not find player with ID " + std::to_string(id));
|
||||
return;
|
||||
}
|
||||
|
||||
// Messing with other players requires a baseline access of 30
|
||||
if (player != self && selfAccess > 30) {
|
||||
Chat::sendServerMessage(sock, "Can't check or change other players access levels (insufficient privileges)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string playerName = PlayerManager::getPlayerName(player);
|
||||
int currentAccess = player->accountLevel;
|
||||
if (args.size() < 3) {
|
||||
// just check
|
||||
Chat::sendServerMessage(sock, playerName + " has access level " + std::to_string(currentAccess));
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't change the access level of someone with stronger privileges
|
||||
// N.B. lower value = stronger privileges
|
||||
if (currentAccess <= selfAccess) {
|
||||
Chat::sendServerMessage(sock, "Can't change this player's access level (insufficient privileges)");
|
||||
return;
|
||||
}
|
||||
|
||||
int newAccess = std::strtol(args[2].c_str(), &tmp, 10);
|
||||
if (*tmp) {
|
||||
Chat::sendServerMessage(sock, "Invalid access level " + args[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Can only assign an access level weaker than yours
|
||||
if (newAccess <= selfAccess) {
|
||||
Chat::sendServerMessage(sock, "Can only assign privileges weaker than your own");
|
||||
return;
|
||||
}
|
||||
|
||||
player->accountLevel = newAccess;
|
||||
|
||||
// Save to database
|
||||
int accountId = Database::getAccountIdForPlayer(player->iID);
|
||||
Database::updateAccountLevel(accountId, newAccess);
|
||||
|
||||
std::string msg = "Changed access level for " + playerName + " from " + std::to_string(currentAccess) + " to " + std::to_string(newAccess);
|
||||
if (newAccess <= 50 && currentAccess > 50)
|
||||
msg += " (they must log out and back in for some commands to be enabled)";
|
||||
Chat::sendServerMessage(sock, msg);
|
||||
Chat::sendServerMessage(sock, "Your access level is " + std::to_string(PlayerManager::getPlayer(sock)->accountLevel));
|
||||
}
|
||||
|
||||
static void populationCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -314,20 +244,20 @@ static void summonWCommand(std::string full, std::vector<std::string>& args, CNS
|
||||
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
|
||||
|
||||
// update angle
|
||||
npc->angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle);
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle);
|
||||
|
||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||
if (PLAYERID(plr->instanceID) != 0) {
|
||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
|
||||
|
||||
npc->angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle);
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle);
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
||||
", id: " + std::to_string(npc->id));
|
||||
TableData::RunningMobs[npc->id] = npc; // only record the one in the template
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
|
||||
}
|
||||
|
||||
static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -340,24 +270,24 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
return;
|
||||
}
|
||||
|
||||
if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) {
|
||||
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) +
|
||||
", id: " + std::to_string(npc->id));
|
||||
TableData::RunningEggs.erase(npc->id);
|
||||
NPCManager::destroyNPC(npc->id);
|
||||
if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) {
|
||||
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID);
|
||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()
|
||||
&& TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) {
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()
|
||||
&& TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) {
|
||||
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) {
|
||||
if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) {
|
||||
int leadId = ((Mob*)npc)->groupLeader;
|
||||
if (leadId != 0) {
|
||||
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) {
|
||||
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
||||
}
|
||||
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
|
||||
@@ -365,7 +295,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
if (leadNpc->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
@@ -379,12 +309,12 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
}
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) +
|
||||
", id: " + std::to_string(npc->id));
|
||||
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
|
||||
TableData::RunningMobs.erase(npc->id);
|
||||
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID);
|
||||
|
||||
NPCManager::destroyNPC(npc->id);
|
||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
||||
}
|
||||
|
||||
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -395,11 +325,11 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
|
||||
|
||||
// return all mobs to their spawn points
|
||||
for (auto& pair : NPCManager::NPCs) {
|
||||
if (pair.second->kind != EntityKind::MOB)
|
||||
if (pair.second->type != EntityType::MOB)
|
||||
continue;
|
||||
|
||||
Mob* mob = (Mob*)pair.second;
|
||||
mob->state = AIState::RETREAT;
|
||||
mob->state = MobState::RETREAT;
|
||||
mob->target = nullptr;
|
||||
mob->nextMovement = getTime();
|
||||
|
||||
@@ -427,27 +357,34 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
||||
}
|
||||
|
||||
int angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||
|
||||
bool isGruntworkNpc = true;
|
||||
// if it's a gruntwork NPC, rotate in-place
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||
|
||||
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
|
||||
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()) {
|
||||
TableData::RunningNPCRotations[npc->id] = angle;
|
||||
isGruntworkNpc = false;
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
} else {
|
||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
||||
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
|
||||
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->id));
|
||||
|
||||
// update rotation clientside by refreshing the player's chunks (same as the /refresh command)
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||
// update rotation clientside
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = npc->appearanceData;
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||
}
|
||||
|
||||
static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||
EntityRef ref = {sock};
|
||||
Entity* plr = ref.getEntity();
|
||||
ChunkPos currentChunk = plr->chunkPos;
|
||||
ChunkPos nullChunk = std::make_tuple(0, 0, 0);
|
||||
Chunking::updateEntityChunk(ref, currentChunk, nullChunk);
|
||||
Chunking::updateEntityChunk(ref, nullChunk, currentChunk);
|
||||
}
|
||||
|
||||
static void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -510,9 +447,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args,
|
||||
return;
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance));
|
||||
TableData::RunningNPCMapNumbers[npc->id] = instance;
|
||||
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle);
|
||||
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
|
||||
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle);
|
||||
}
|
||||
|
||||
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -568,12 +505,9 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
if (*tmp)
|
||||
return;
|
||||
|
||||
if (Abilities::SkillTable.count(skillId) == 0) {
|
||||
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0)
|
||||
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
||||
return;
|
||||
}
|
||||
|
||||
Eggs::eggBuffPlayer(sock, skillId, 0, duration);
|
||||
|
||||
}
|
||||
|
||||
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -602,7 +536,7 @@ static void eggCommand(std::string full, std::vector<std::string>& args, CNSocke
|
||||
int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
|
||||
int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI);
|
||||
|
||||
Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
|
||||
Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
|
||||
NPCManager::NPCs[id] = egg;
|
||||
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
|
||||
|
||||
@@ -679,40 +613,40 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
}
|
||||
|
||||
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
||||
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->id;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||
mob->groupLeader = leadNpc->id;
|
||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
mob->offsetX = x - plr->x;
|
||||
mob->offsetY = y - plr->y;
|
||||
}
|
||||
|
||||
npc->angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
||||
|
||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||
if (PLAYERID(plr->instanceID) != 0) {
|
||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
||||
|
||||
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->id;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||
mob->groupLeader = leadNpc->id;
|
||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
mob->offsetX = x - plr->x;
|
||||
mob->offsetY = y - plr->y;
|
||||
}
|
||||
|
||||
npc->angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
|
||||
", id: " + std::to_string(npc->id));
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
|
||||
if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) {
|
||||
if (i == 0 && team == 2 && npc->type == EntityType::MOB) {
|
||||
type = type2;
|
||||
leadNpc = (Mob*)NPCManager::NPCs[npc->id];
|
||||
leadNpc->groupLeader = leadNpc->id;
|
||||
leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
||||
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +658,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
return;
|
||||
}
|
||||
|
||||
TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader
|
||||
TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader
|
||||
}
|
||||
|
||||
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -741,14 +675,15 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
||||
return;
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle));
|
||||
std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}");
|
||||
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
|
||||
@@ -757,35 +692,39 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
||||
|
||||
static void lairUnlockCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
if (!Chunking::chunkExists(plr->chunkPos))
|
||||
return;
|
||||
|
||||
Chunk* chnk = Chunking::chunks[plr->chunkPos];
|
||||
int taskID = -1;
|
||||
int missionID = -1;
|
||||
int lastDist = INT_MAX;
|
||||
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
||||
for (const EntityRef& ref : chnk->entities) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
int found = 0;
|
||||
for (const EntityRef& ref : chnk->entities) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
continue;
|
||||
|
||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||
int32_t id = ref.id;
|
||||
if (NPCManager::NPCs.find(id) == NPCManager::NPCs.end())
|
||||
continue;
|
||||
|
||||
int distXY = std::hypot(plr->x - npc->x, plr->y - npc->y);
|
||||
int dist = std::hypot(distXY, plr->z - npc->z);
|
||||
if (dist >= lastDist)
|
||||
continue;
|
||||
|
||||
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
|
||||
if (it->second.npcID == npc->type) {
|
||||
taskID = it->second.limitTaskID;
|
||||
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
||||
lastDist = dist;
|
||||
break;
|
||||
}
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
|
||||
if ((*it).second.npcID == npc->appearanceData.iNPCType) {
|
||||
taskID = (*it).second.limitTaskID;
|
||||
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
||||
found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missionID == -1 || taskID == -1) {
|
||||
Chat::sendServerMessage(sock, "No nearby Lair portals found.");
|
||||
Chat::sendServerMessage(sock, "You are NOT standing near a lair portal; move around and try again!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (found > 1) {
|
||||
Chat::sendServerMessage(sock, "More than one lair found; decrease chunk size and try again!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1046,17 +985,11 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
|
||||
// add first point at NPC's current location
|
||||
std::vector<BaseNPC*> pathPoints;
|
||||
BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||
|
||||
// assign coords manually, since we aren't actually adding markers to the world
|
||||
marker->x = npc->x;
|
||||
marker->y = npc->y;
|
||||
marker->z = npc->z;
|
||||
|
||||
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||
pathPoints.push_back(marker);
|
||||
// map from player
|
||||
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you");
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you");
|
||||
updatePathMarkers(sock);
|
||||
return;
|
||||
}
|
||||
@@ -1073,12 +1006,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
|
||||
// /path kf
|
||||
if (args[1] == "kf") {
|
||||
BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||
|
||||
marker->x = npc->x;
|
||||
marker->y = npc->y;
|
||||
marker->z = npc->z;
|
||||
|
||||
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||
entry->second.push_back(marker);
|
||||
Chat::sendServerMessage(sock, "[PATH] Added keyframe");
|
||||
updatePathMarkers(sock);
|
||||
@@ -1088,8 +1016,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
// /path here
|
||||
if (args[1] == "here") {
|
||||
// bring the NPC to where the player is standing
|
||||
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
||||
npc->disappearFromViewOf(sock);
|
||||
npc->enterIntoViewOf(sock);
|
||||
Chat::sendServerMessage(sock, "[PATH] Come here");
|
||||
@@ -1127,9 +1055,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
speed = speedArg;
|
||||
}
|
||||
// return NPC to home
|
||||
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
||||
BaseNPC* home = entry->second[0];
|
||||
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
npc->disappearFromViewOf(sock);
|
||||
npc->enterIntoViewOf(sock);
|
||||
|
||||
@@ -1145,7 +1073,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||
from = to; // update point A
|
||||
}
|
||||
Transport::NPCQueues[npc->id] = keyframes;
|
||||
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes;
|
||||
entry->second.pop_back(); // remove temp end point
|
||||
|
||||
Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
|
||||
@@ -1155,9 +1083,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
// /path cancel
|
||||
if (args[1] == "cancel") {
|
||||
// return NPC to home
|
||||
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
||||
BaseNPC* home = entry->second[0];
|
||||
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
npc->disappearFromViewOf(sock);
|
||||
npc->enterIntoViewOf(sock);
|
||||
// deallocate markers
|
||||
@@ -1167,7 +1095,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
}
|
||||
// unmap
|
||||
TableData::RunningNPCPaths.erase(plr->iID);
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1195,9 +1123,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
}
|
||||
|
||||
// return NPC to home and set path to repeat
|
||||
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
||||
BaseNPC* home = entry->second[0];
|
||||
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
||||
npc->disappearFromViewOf(sock);
|
||||
npc->enterIntoViewOf(sock);
|
||||
npc->loopingPath = true;
|
||||
@@ -1214,7 +1142,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||
from = to; // update point A
|
||||
}
|
||||
Transport::NPCQueues[npc->id] = keyframes;
|
||||
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes;
|
||||
entry->second.pop_back(); // remove temp end point
|
||||
|
||||
// save to gruntwork
|
||||
@@ -1241,7 +1169,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
finishedPath.isLoop = true;
|
||||
finishedPath.speed = speed;
|
||||
finishedPath.points = finalPoints;
|
||||
finishedPath.targetIDs.push_back(npc->id);
|
||||
finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID);
|
||||
|
||||
TableData::FinishedNPCPaths.push_back(finishedPath);
|
||||
|
||||
@@ -1253,7 +1181,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
// unmap
|
||||
TableData::RunningNPCPaths.erase(plr->iID);
|
||||
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
|
||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you");
|
||||
|
||||
TableData::flush();
|
||||
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");
|
||||
@@ -1264,13 +1192,19 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
Chat::sendServerMessage(sock, "[PATH] Unknown argument '" + args[1] + "'");
|
||||
}
|
||||
|
||||
static void reloadScriptsCommand(std::string full, std::vector<std::string>& args, CNSocket *sock) {
|
||||
// reloads all scripts
|
||||
LuaManager::stopScripts();
|
||||
LuaManager::loadScripts();
|
||||
}
|
||||
|
||||
static void registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help) {
|
||||
commands[cmd] = ChatCommand(requiredLevel, handlr, help);
|
||||
}
|
||||
|
||||
void CustomCommands::init() {
|
||||
registerCommand("help", 100, helpCommand, "list all unlocked server-side commands");
|
||||
registerCommand("access", 100, accessCommand, "check or change access levels");
|
||||
registerCommand("access", 100, accessCommand, "print your access level");
|
||||
registerCommand("instance", 30, instanceCommand, "print or change your current instance");
|
||||
registerCommand("mss", 30, mssCommand, "edit Monkey Skyway routes");
|
||||
registerCommand("npcr", 30, npcRotateCommand, "rotate NPCs");
|
||||
@@ -1303,4 +1237,5 @@ void CustomCommands::init() {
|
||||
registerCommand("unregisterall", 50, unregisterallCommand, "clear all SCAMPER and MSS destinations");
|
||||
registerCommand("redeem", 100, redeemCommand, "redeem a code item");
|
||||
registerCommand("path", 30, pathCommand, "edit NPC paths");
|
||||
registerCommand("rscripts", 30, reloadScriptsCommand, "stops all script states and reloads all scripts");
|
||||
}
|
||||
|
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace CustomCommands {
|
||||
void init();
|
||||
|
||||
|
202
src/Eggs.cpp
202
src/Eggs.cpp
@@ -1,124 +1,141 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "Eggs.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Eggs;
|
||||
|
||||
/// sock, CBFlag -> until
|
||||
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
|
||||
std::unordered_map<int, EggType> Eggs::EggTypes;
|
||||
|
||||
void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
// eggId might be 0 if the buff is made by the /buff command
|
||||
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
||||
|
||||
if(Abilities::SkillTable.count(skillId) == 0) {
|
||||
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
||||
return;
|
||||
}
|
||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
||||
int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag);
|
||||
|
||||
SkillResult result = SkillResult();
|
||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// apply buff
|
||||
if(skill->targetType != SkillTargetType::PLAYERS) {
|
||||
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||
}
|
||||
size_t resplen;
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
int value = skill->values[0][0];
|
||||
BuffStack eggBuff = {
|
||||
duration * 1000 / MS_PER_PLAYER_TICK,
|
||||
value,
|
||||
src,
|
||||
BuffClass::EGG
|
||||
};
|
||||
plr->addBuff(timeBuffId,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&eggBuff);
|
||||
|
||||
sSkillResult_Buff resultBuff{};
|
||||
resultBuff.eCT = plr->getCharType();
|
||||
resultBuff.iID = plr->getID();
|
||||
resultBuff.bProtected = false;
|
||||
resultBuff.iConditionBitFlag = plr->getCompositeCondition();
|
||||
result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff);
|
||||
if (skillId == 183) {
|
||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage);
|
||||
} else if (skillId == 150) {
|
||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP);
|
||||
} else {
|
||||
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
||||
sSkillResult_Damage resultDamage{};
|
||||
sSkillResult_Heal_HP resultHeal{};
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case SkillType::DAMAGE:
|
||||
resultDamage.bProtected = false;
|
||||
resultDamage.eCT = plr->getCharType();
|
||||
resultDamage.iID = plr->getID();
|
||||
resultDamage.iDamage = plr->takeDamage(src, value);
|
||||
resultDamage.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
||||
break;
|
||||
case SkillType::HEAL_HP:
|
||||
resultHeal.eCT = plr->getCharType();
|
||||
resultHeal.iID = plr->getID();
|
||||
resultHeal.iHealHP = plr->heal(src, value);
|
||||
resultHeal.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
||||
return;
|
||||
}
|
||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff);
|
||||
}
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
|
||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
pkt->iNPC_ID = eggId;
|
||||
pkt->iSkillID = skillId;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->iTargetCnt = 1;
|
||||
|
||||
if(result.size > 0) {
|
||||
void* attached = (void*)(pkt + 1);
|
||||
memcpy(attached, result.payload, result.size);
|
||||
if (skillId == 183) { // damage egg
|
||||
auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
||||
memset(respbuf, 0, resplen);
|
||||
skill->eCT = 1;
|
||||
skill->iID = plr->iID;
|
||||
skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
|
||||
plr->HP -= skill->iDamage;
|
||||
if (plr->HP < 0)
|
||||
plr->HP = 0;
|
||||
skill->iHP = plr->HP;
|
||||
} else if (skillId == 150) { // heal egg
|
||||
auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
||||
memset(respbuf, 0, resplen);
|
||||
skill->eCT = 1;
|
||||
skill->iID = plr->iID;
|
||||
skill->iHealHP = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
|
||||
plr->HP += skill->iHealHP;
|
||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
skill->iHP = plr->HP;
|
||||
} else { // regular buff egg
|
||||
auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
||||
memset(respbuf, 0, resplen);
|
||||
skill->eCT = 1;
|
||||
skill->iID = plr->iID;
|
||||
skill->iConditionBitFlag = plr->iConditionBitFlag;
|
||||
}
|
||||
|
||||
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
|
||||
skillUse->iNPC_ID = eggId;
|
||||
skillUse->iSkillID = skillId;
|
||||
skillUse->eST = Nanos::SkillTable[skillId].skillType;
|
||||
skillUse->iTargetCnt = 1;
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
|
||||
if (CBFlag == 0)
|
||||
return -1;
|
||||
|
||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag);
|
||||
|
||||
// save the buff serverside;
|
||||
// if you get the same buff again, new duration will override the previous one
|
||||
time_t until = getTime() + (time_t)duration * 1000;
|
||||
EggBuffs[key] = until;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void eggStep(CNServer* serv, time_t currTime) {
|
||||
// tick buffs
|
||||
time_t timeStamp = currTime;
|
||||
auto it = EggBuffs.begin();
|
||||
while (it != EggBuffs.end()) {
|
||||
// check remaining time
|
||||
if (it->second > timeStamp) {
|
||||
it++;
|
||||
} else { // if time reached 0
|
||||
CNSocket* sock = it->first.first;
|
||||
int32_t CBFlag = it->first.second;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
int groupFlags = Groups::getGroupFlags(otherPlr);
|
||||
for (auto& pwr : Nanos::NanoPowers) {
|
||||
if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
||||
resp.eCSTB = pwr.timeBuffID;
|
||||
resp.eTBU = 2;
|
||||
resp.eTBT = 3; // for egg buffs
|
||||
plr->iConditionBitFlag &= ~CBFlag;
|
||||
resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
||||
sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
|
||||
resp2.eCT = 1;
|
||||
resp2.iID = plr->iID;
|
||||
resp2.iConditionBitFlag = plr->iConditionBitFlag;
|
||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
||||
}
|
||||
}
|
||||
// remove buff from the map
|
||||
it = EggBuffs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// check dead eggs and eggs in inactive chunks
|
||||
for (auto npc : NPCManager::NPCs) {
|
||||
if (npc.second->kind != EntityKind::EGG)
|
||||
if (npc.second->type != EntityType::EGG)
|
||||
continue;
|
||||
|
||||
auto egg = (Egg*)npc.second;
|
||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||
continue;
|
||||
|
||||
if (egg->deadUntil <= currTime) {
|
||||
if (egg->deadUntil <= timeStamp) {
|
||||
// respawn it
|
||||
egg->dead = false;
|
||||
egg->deadUntil = 0;
|
||||
egg->hp = 400;
|
||||
egg->appearanceData.iHP = 400;
|
||||
|
||||
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
||||
}
|
||||
@@ -146,7 +163,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
auto egg = (Egg*)eggRef.getEntity();
|
||||
if (egg->kind != EntityKind::EGG) {
|
||||
if (egg->type != EntityType::EGG) {
|
||||
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
@@ -163,7 +180,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
*/
|
||||
|
||||
int typeId = egg->type;
|
||||
int typeId = egg->appearanceData.iNPCType;
|
||||
if (EggTypes.find(typeId) == EggTypes.end()) {
|
||||
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
||||
return;
|
||||
@@ -171,13 +188,16 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
EggType* type = &EggTypes[typeId];
|
||||
|
||||
// buff the player
|
||||
if (type->effectId != 0)
|
||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||
|
||||
/*
|
||||
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
||||
* (buff icon pops up in the bottom of the screen)
|
||||
* so we don't send it for non-effect
|
||||
*/
|
||||
if (type->effectId != 0) {
|
||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||
resp.iSkillID = type->effectId;
|
||||
|
||||
@@ -235,7 +255,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
||||
egg->dead = true;
|
||||
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
||||
egg->hp = 0;
|
||||
egg->appearanceData.iHP = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
struct EggType {
|
||||
int dropCrateId;
|
||||
@@ -10,10 +11,12 @@ struct EggType {
|
||||
};
|
||||
|
||||
namespace Eggs {
|
||||
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
|
||||
extern std::unordered_map<int, EggType> EggTypes;
|
||||
|
||||
void init();
|
||||
|
||||
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||
/// returns -1 on fail
|
||||
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||
}
|
||||
|
@@ -1,17 +1,15 @@
|
||||
#include "Email.hpp"
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Chat.hpp"
|
||||
|
||||
using namespace Email;
|
||||
|
||||
std::vector<std::string> Email::dump;
|
||||
|
||||
// New email notification
|
||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
||||
@@ -93,7 +91,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
|
||||
return; // sanity check
|
||||
|
||||
// get email item from db and delete it
|
||||
@@ -228,10 +226,10 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_SUCC, resp);
|
||||
|
||||
Player otherPlr = {};
|
||||
Database::getPlayer(&otherPlr, pkt->iTo_PCUID);
|
||||
if (pkt->iCash || pkt->aItem[0].ItemInven.iID) {
|
||||
// if there are item or taro attachments
|
||||
Player otherPlr = {};
|
||||
Database::getPlayer(&otherPlr, pkt->iTo_PCUID);
|
||||
if (otherPlr.iID != 0 && plr->PCStyle2.iPayzoneFlag != otherPlr.PCStyle2.iPayzoneFlag) {
|
||||
// if the players are not in the same time period
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, resp);
|
||||
@@ -252,26 +250,11 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
if (attachment.ItemInven.iID == 0)
|
||||
continue;
|
||||
|
||||
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||
sItemBase* real = &plr->Inven[attachment.iSlotNum];
|
||||
|
||||
resp.aItem[i] = attachment;
|
||||
attachments.push_back(attachment.ItemInven);
|
||||
attSlots.push_back(attachment.iSlotNum);
|
||||
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack)
|
||||
*real = { 0, 0, 0, 0 };
|
||||
else // otherwise, decrement the item
|
||||
real->iOpt -= item->iOpt;
|
||||
|
||||
// HACK: update the slot
|
||||
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
|
||||
itemResp.iFromSlotNum = attachment.iSlotNum;
|
||||
itemResp.iToSlotNum = attachment.iSlotNum;
|
||||
itemResp.FromSlotItem = *real;
|
||||
itemResp.ToSlotItem = *real;
|
||||
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
|
||||
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
|
||||
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||
// delete item
|
||||
plr->Inven[attachment.iSlotNum] = { 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||
@@ -291,7 +274,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
0 // DeleteTime (unimplemented)
|
||||
};
|
||||
|
||||
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||
if (!Database::sendEmail(&email, attachments)) {
|
||||
plr->money += cost; // give money back
|
||||
// give items back
|
||||
while (!attachments.empty()) {
|
||||
@@ -321,17 +304,6 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iTo_PCUID = pkt->iTo_PCUID;
|
||||
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_SEND_EMAIL_SUCC);
|
||||
|
||||
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
|
||||
std::cout << logEmail << std::endl;
|
||||
dump.push_back(logEmail);
|
||||
|
||||
// notification to recipient if online
|
||||
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);
|
||||
if (recipient != nullptr)
|
||||
{
|
||||
emailUpdateCheck(recipient, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Email::init() {
|
||||
|
@@ -1,10 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Email {
|
||||
extern std::vector<std::string> dump;
|
||||
|
||||
void init();
|
||||
void init();
|
||||
}
|
||||
|
@@ -1,28 +1,30 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(std::is_standard_layout<EntityRef>::value);
|
||||
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
||||
|
||||
EntityRef::EntityRef(CNSocket *s) {
|
||||
kind = EntityKind::PLAYER;
|
||||
type = EntityType::PLAYER;
|
||||
sock = s;
|
||||
}
|
||||
|
||||
EntityRef::EntityRef(int32_t i) {
|
||||
id = i;
|
||||
|
||||
kind = EntityKind::INVALID;
|
||||
if (NPCManager::NPCs.find(id) != NPCManager::NPCs.end())
|
||||
kind = NPCManager::NPCs[id]->kind;
|
||||
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
||||
type = NPCManager::NPCs[id]->type;
|
||||
}
|
||||
|
||||
bool EntityRef::isValid() const {
|
||||
if (kind == EntityKind::PLAYER)
|
||||
if (type == EntityType::PLAYER)
|
||||
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
||||
|
||||
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
||||
@@ -31,38 +33,21 @@ bool EntityRef::isValid() const {
|
||||
Entity *EntityRef::getEntity() const {
|
||||
assert(isValid());
|
||||
|
||||
if (kind == EntityKind::PLAYER)
|
||||
if (type == EntityType::PLAYER)
|
||||
return PlayerManager::getPlayer(sock);
|
||||
|
||||
return NPCManager::NPCs[id];
|
||||
}
|
||||
|
||||
sNPCAppearanceData BaseNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = {};
|
||||
data.iAngle = angle;
|
||||
data.iBarkerType = 0; // unused?
|
||||
data.iConditionBitFlag = 0;
|
||||
data.iHP = hp;
|
||||
data.iNPCType = type;
|
||||
data.iNPC_ID = id;
|
||||
data.iX = x;
|
||||
data.iY = y;
|
||||
data.iZ = z;
|
||||
return data;
|
||||
}
|
||||
|
||||
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||
data.iConditionBitFlag = getCompositeCondition();
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entity coming into view.
|
||||
*/
|
||||
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = getAppearanceData();
|
||||
pkt.NPCAppearanceData = appearanceData;
|
||||
pkt.NPCAppearanceData.iX = x;
|
||||
pkt.NPCAppearanceData.iY = y;
|
||||
pkt.NPCAppearanceData.iZ = z;
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||
}
|
||||
|
||||
@@ -71,7 +56,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
||||
|
||||
// TODO: Potentially decouple this from BaseNPC?
|
||||
pkt.AppearanceData = {
|
||||
3, id, type,
|
||||
3, appearanceData.iNPC_ID, appearanceData.iNPCType,
|
||||
x, y, z
|
||||
};
|
||||
|
||||
@@ -81,40 +66,28 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
||||
void Egg::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
|
||||
|
||||
// TODO: Potentially decouple this from BaseNPC?
|
||||
pkt.ShinyAppearanceData = {
|
||||
id, type, 0, // client doesn't care about map num
|
||||
x, y, z
|
||||
};
|
||||
Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData);
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
||||
}
|
||||
|
||||
sNano* Player::getActiveNano() {
|
||||
return &Nanos[activeNano];
|
||||
}
|
||||
|
||||
sPCAppearanceData Player::getAppearanceData() {
|
||||
sPCAppearanceData data = {};
|
||||
data.iID = iID;
|
||||
data.iHP = HP;
|
||||
data.iLv = level;
|
||||
data.iX = x;
|
||||
data.iY = y;
|
||||
data.iZ = z;
|
||||
data.iAngle = angle;
|
||||
data.PCStyle = PCStyle;
|
||||
data.Nano = Nanos[activeNano];
|
||||
data.iPCState = iPCState;
|
||||
data.iSpecialState = iSpecialState;
|
||||
memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
// TODO: this is less effiecient than it was, because of memset()
|
||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||
pkt.PCAppearanceData = getAppearanceData();
|
||||
|
||||
pkt.PCAppearanceData.iID = iID;
|
||||
pkt.PCAppearanceData.iHP = HP;
|
||||
pkt.PCAppearanceData.iLv = level;
|
||||
pkt.PCAppearanceData.iX = x;
|
||||
pkt.PCAppearanceData.iY = y;
|
||||
pkt.PCAppearanceData.iZ = z;
|
||||
pkt.PCAppearanceData.iAngle = angle;
|
||||
pkt.PCAppearanceData.PCStyle = PCStyle;
|
||||
pkt.PCAppearanceData.Nano = Nanos[activeNano];
|
||||
pkt.PCAppearanceData.iPCState = iPCState;
|
||||
pkt.PCAppearanceData.iSpecialState = iSpecialState;
|
||||
memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
||||
}
|
||||
|
||||
@@ -123,20 +96,20 @@ void Player::enterIntoViewOf(CNSocket *sock) {
|
||||
*/
|
||||
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||
pkt.iNPC_ID = id;
|
||||
pkt.iNPC_ID = appearanceData.iNPC_ID;
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
||||
}
|
||||
|
||||
void Bus::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
||||
pkt.eTT = 3;
|
||||
pkt.iT_ID = id;
|
||||
pkt.iT_ID = appearanceData.iNPC_ID;
|
||||
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
||||
}
|
||||
|
||||
void Egg::disappearFromViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
||||
pkt.iShinyID = id;
|
||||
pkt.iShinyID = appearanceData.iNPC_ID;
|
||||
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
||||
}
|
||||
|
||||
|
178
src/Entities.hpp
178
src/Entities.hpp
@@ -2,25 +2,23 @@
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
enum class AIState {
|
||||
INACTIVE,
|
||||
ROAMING,
|
||||
COMBAT,
|
||||
RETREAT,
|
||||
DEAD
|
||||
enum class EntityType : uint8_t {
|
||||
INVALID,
|
||||
PLAYER,
|
||||
SIMPLE_NPC,
|
||||
COMBAT_NPC,
|
||||
MOB,
|
||||
EGG,
|
||||
BUS
|
||||
};
|
||||
|
||||
class Chunk;
|
||||
|
||||
struct Entity {
|
||||
EntityKind kind = EntityKind::INVALID;
|
||||
EntityType type = EntityType::INVALID;
|
||||
int x = 0, y = 0, z = 0;
|
||||
uint64_t instanceID = 0;
|
||||
ChunkPos chunkPos = {};
|
||||
@@ -29,39 +27,47 @@ struct Entity {
|
||||
// destructor must be virtual, apparently
|
||||
virtual ~Entity() {}
|
||||
|
||||
virtual bool isExtant() { return true; }
|
||||
virtual bool isAlive() { return true; }
|
||||
|
||||
// stubs
|
||||
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Interfaces
|
||||
*/
|
||||
class ICombatant {
|
||||
public:
|
||||
ICombatant() {}
|
||||
virtual ~ICombatant() {}
|
||||
struct EntityRef {
|
||||
EntityType type;
|
||||
union {
|
||||
CNSocket *sock;
|
||||
int32_t id;
|
||||
};
|
||||
|
||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||
virtual Buff* getBuff(int) = 0;
|
||||
virtual void removeBuff(int) = 0;
|
||||
virtual void removeBuff(int, BuffClass) = 0;
|
||||
virtual void clearBuffs(bool) = 0;
|
||||
virtual bool hasBuff(int) = 0;
|
||||
virtual int getCompositeCondition() = 0;
|
||||
virtual int takeDamage(EntityRef, int) = 0;
|
||||
virtual int heal(EntityRef, int) = 0;
|
||||
virtual bool isAlive() = 0;
|
||||
virtual int getCurrentHP() = 0;
|
||||
virtual int getMaxHP() = 0;
|
||||
virtual int getLevel() = 0;
|
||||
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||
virtual int32_t getCharType() = 0;
|
||||
virtual int32_t getID() = 0;
|
||||
virtual EntityRef getRef() = 0;
|
||||
virtual void step(time_t currTime) = 0;
|
||||
EntityRef(CNSocket *s);
|
||||
EntityRef(int32_t i);
|
||||
|
||||
bool isValid() const;
|
||||
Entity *getEntity() const;
|
||||
|
||||
bool operator==(const EntityRef& other) const {
|
||||
if (type != other.type)
|
||||
return false;
|
||||
|
||||
if (type == EntityType::PLAYER)
|
||||
return sock == other.sock;
|
||||
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
// arbitrary ordering
|
||||
bool operator<(const EntityRef& other) const {
|
||||
if (type == other.type) {
|
||||
if (type == EntityType::PLAYER)
|
||||
return sock < other.sock;
|
||||
else
|
||||
return id < other.id;
|
||||
}
|
||||
|
||||
return type < other.type;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -69,105 +75,75 @@ public:
|
||||
*/
|
||||
class BaseNPC : public Entity {
|
||||
public:
|
||||
int id;
|
||||
int type;
|
||||
int hp;
|
||||
int angle;
|
||||
sNPCAppearanceData appearanceData = {};
|
||||
bool loopingPath = false;
|
||||
|
||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||
kind = EntityKind::SIMPLE_NPC;
|
||||
type = t;
|
||||
hp = 400;
|
||||
angle = _A;
|
||||
id = _id;
|
||||
BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX
|
||||
x = _X;
|
||||
y = _Y;
|
||||
z = _Z;
|
||||
appearanceData.iNPCType = t;
|
||||
appearanceData.iHP = 400;
|
||||
appearanceData.iAngle = angle;
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
appearanceData.iBarkerType = 0;
|
||||
appearanceData.iNPC_ID = id;
|
||||
|
||||
instanceID = iID;
|
||||
};
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
||||
virtual sNPCAppearanceData getAppearanceData();
|
||||
};
|
||||
|
||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
struct CombatNPC : public BaseNPC {
|
||||
int maxHealth = 0;
|
||||
int spawnX = 0;
|
||||
int spawnY = 0;
|
||||
int spawnZ = 0;
|
||||
int level = 0;
|
||||
int speed = 300;
|
||||
AIState state = AIState::INACTIVE;
|
||||
Group* group = nullptr;
|
||||
int playersInView = 0; // for optimizing away AI in empty chunks
|
||||
|
||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||
void (*_stepAI)(CombatNPC*, time_t) = nullptr;
|
||||
|
||||
std::unordered_map<int, Buff*> buffs = {};
|
||||
// XXX
|
||||
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) :
|
||||
BaseNPC(x, y, z, angle, iID, t, id),
|
||||
maxHealth(maxHP) {}
|
||||
|
||||
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||
this->spawnX = spawnX;
|
||||
this->spawnY = spawnY;
|
||||
this->spawnZ = spawnZ;
|
||||
|
||||
kind = EntityKind::COMBAT_NPC;
|
||||
|
||||
stateHandlers[AIState::INACTIVE] = {};
|
||||
transitionHandlers[AIState::INACTIVE] = {};
|
||||
virtual void stepAI(time_t currTime) {
|
||||
if (_stepAI != nullptr)
|
||||
_stepAI(this, currTime);
|
||||
}
|
||||
|
||||
virtual sNPCAppearanceData getAppearanceData() override;
|
||||
|
||||
virtual bool isExtant() override { return hp > 0; }
|
||||
|
||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||
virtual Buff* getBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||
virtual void clearBuffs(bool force) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual int heal(EntityRef src, int amt) override;
|
||||
virtual bool isAlive() override;
|
||||
virtual int getCurrentHP() override;
|
||||
virtual int getMaxHP() override;
|
||||
virtual int getLevel() override;
|
||||
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||
virtual int32_t getCharType() override;
|
||||
virtual int32_t getID() override;
|
||||
virtual EntityRef getRef() override;
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
virtual void transition(AIState newState, EntityRef src);
|
||||
virtual bool isAlive() override { return appearanceData.iHP > 0; }
|
||||
};
|
||||
|
||||
// Mob is in MobAI.hpp, Player is in Player.hpp
|
||||
|
||||
// TODO: decouple from BaseNPC
|
||||
struct Egg : public BaseNPC {
|
||||
bool summoned = false;
|
||||
bool dead = false;
|
||||
time_t deadUntil;
|
||||
|
||||
Egg(uint64_t iID, int t, int32_t id, bool summon)
|
||||
: BaseNPC(0, iID, t, id) {
|
||||
Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon)
|
||||
: BaseNPC(x, y, z, 0, iID, t, id) {
|
||||
summoned = summon;
|
||||
kind = EntityKind::EGG;
|
||||
type = EntityType::EGG;
|
||||
}
|
||||
|
||||
virtual bool isExtant() override { return !dead; }
|
||||
virtual bool isAlive() override { return !dead; }
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
};
|
||||
|
||||
// TODO: decouple from BaseNPC
|
||||
struct Bus : public BaseNPC {
|
||||
Bus(int angle, uint64_t iID, int t, int id) :
|
||||
BaseNPC(angle, iID, t, id) {
|
||||
kind = EntityKind::BUS;
|
||||
loopingPath = true;
|
||||
Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) :
|
||||
BaseNPC(x, y, z, angle, iID, t, id) {
|
||||
type = EntityType::BUS;
|
||||
}
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
|
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
/* forward declaration(s) */
|
||||
struct Entity;
|
||||
|
||||
enum EntityKind {
|
||||
INVALID,
|
||||
PLAYER,
|
||||
SIMPLE_NPC,
|
||||
COMBAT_NPC,
|
||||
MOB,
|
||||
EGG,
|
||||
BUS
|
||||
};
|
||||
|
||||
struct EntityRef {
|
||||
EntityKind kind;
|
||||
union {
|
||||
CNSocket *sock;
|
||||
int32_t id;
|
||||
};
|
||||
|
||||
EntityRef(CNSocket *s);
|
||||
EntityRef(int32_t i);
|
||||
|
||||
bool isValid() const;
|
||||
Entity *getEntity() const;
|
||||
|
||||
bool operator==(const EntityRef& other) const {
|
||||
if (kind != other.kind)
|
||||
return false;
|
||||
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock == other.sock;
|
||||
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
bool operator!=(const EntityRef& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
// arbitrary ordering
|
||||
bool operator<(const EntityRef& other) const {
|
||||
if (kind == other.kind) {
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock < other.sock;
|
||||
else
|
||||
return id < other.id;
|
||||
}
|
||||
|
||||
return kind < other.kind;
|
||||
}
|
||||
};
|
457
src/Groups.cpp
457
src/Groups.cpp
@@ -1,10 +1,13 @@
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
/*
|
||||
* NOTE: Variadic response packets that list group members are technically
|
||||
@@ -16,186 +19,22 @@
|
||||
|
||||
using namespace Groups;
|
||||
|
||||
Group::Group(EntityRef leader) {
|
||||
addToGroup(this, leader);
|
||||
}
|
||||
|
||||
static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) {
|
||||
for(EntityRef pcRef : pcs) {
|
||||
sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(pcRef.sock);
|
||||
info->iPC_ID = plr->iID;
|
||||
info->iPCUID = plr->PCStyle.iPC_UID;
|
||||
info->iNameCheck = plr->PCStyle.iNameCheck;
|
||||
memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||
memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||
info->iSpecialState = plr->iSpecialState;
|
||||
info->iLv = plr->level;
|
||||
info->iHP = plr->HP;
|
||||
info->iMaxHP = PC_MAXHEALTH(plr->level);
|
||||
// info->iMapType = 0;
|
||||
// info->iMapNum = 0;
|
||||
info->iX = plr->x;
|
||||
info->iY = plr->y;
|
||||
info->iZ = plr->z;
|
||||
if(plr->activeNano > 0) {
|
||||
info->Nano = *plr->getActiveNano();
|
||||
info->bNano = true;
|
||||
}
|
||||
|
||||
pivot = (uint8_t*)(info + 1);
|
||||
}
|
||||
for(EntityRef npcRef : npcs) {
|
||||
sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot;
|
||||
|
||||
// probably should not assume that the combatant is an
|
||||
// entity, but it works for now
|
||||
BaseNPC* npc = (BaseNPC*)npcRef.getEntity();
|
||||
info->iNPC_ID = npcRef.id;
|
||||
info->iNPC_Type = npc->type;
|
||||
info->iHP = npc->hp;
|
||||
info->iX = npc->x;
|
||||
info->iY = npc->y;
|
||||
info->iZ = npc->z;
|
||||
|
||||
pivot = (uint8_t*)(info + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::addToGroup(Group* group, EntityRef member) {
|
||||
if (group == nullptr)
|
||||
return;
|
||||
|
||||
if (member.kind == EntityKind::PLAYER) {
|
||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||
plr->group = group;
|
||||
}
|
||||
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||
npc->group = group;
|
||||
}
|
||||
else {
|
||||
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
|
||||
}
|
||||
|
||||
group->members.push_back(member);
|
||||
|
||||
if(member.kind == EntityKind::PLAYER) {
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||
|
||||
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
// PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs
|
||||
// (and the client does care!) so we need to send one to the new member
|
||||
// and the other to the rest
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo);
|
||||
member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen);
|
||||
sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Groups::removeFromGroup(Group* group, EntityRef member) {
|
||||
if (group == nullptr)
|
||||
return false;
|
||||
|
||||
if (member.kind == EntityKind::PLAYER) {
|
||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||
plr->group = nullptr; // no dangling pointers here muahaahahah
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt);
|
||||
member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC);
|
||||
}
|
||||
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||
npc->group = nullptr;
|
||||
}
|
||||
else {
|
||||
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
|
||||
}
|
||||
|
||||
auto it = std::find(group->members.begin(), group->members.end(), member);
|
||||
if (it == group->members.end()) {
|
||||
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
|
||||
} else {
|
||||
group->members.erase(it);
|
||||
}
|
||||
|
||||
if(member.kind == EntityKind::PLAYER) {
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||
|
||||
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE,
|
||||
sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||
}
|
||||
}
|
||||
|
||||
if (group->members.size() == 1) {
|
||||
return removeFromGroup(group, group->members.back());
|
||||
}
|
||||
|
||||
if (group->members.empty()) {
|
||||
delete group; // cleanup memory
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Groups::disbandGroup(Group* group) {
|
||||
if (group == nullptr)
|
||||
return;
|
||||
|
||||
// remove everyone from the group!!
|
||||
bool done = false;
|
||||
while(!done) {
|
||||
EntityRef back = group->members.back();
|
||||
done = removeFromGroup(group, back);
|
||||
}
|
||||
}
|
||||
|
||||
static void requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) {
|
||||
if (plr->groupCnt >= 4 || otherPlr->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||
return;
|
||||
@@ -236,89 +75,255 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return; // disconnect or something
|
||||
return;
|
||||
|
||||
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->group != nullptr || size + 1 > 4) {
|
||||
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherPlr->group == nullptr) {
|
||||
// create group
|
||||
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||
otherPlr->group = new Group(otherPlrRef);
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
addToGroup(otherPlr->group, sock);
|
||||
|
||||
plr->iIDGroup = otherPlr->iID;
|
||||
otherPlr->groupCnt += 1;
|
||||
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
|
||||
|
||||
resp->iID_NewMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt;
|
||||
|
||||
int bitFlag = getGroupFlags(otherPlr);
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member
|
||||
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
|
||||
if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
|
||||
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);
|
||||
}
|
||||
}
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
}
|
||||
|
||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
groupKick(plr->group, sock);
|
||||
groupKickPlayer(plr);
|
||||
}
|
||||
|
||||
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||
if (group == nullptr)
|
||||
return;
|
||||
void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
||||
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef ref : players) {
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
if (sock == nullptr)
|
||||
continue;
|
||||
|
||||
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
|
||||
Player* leavingPlr = PlayerManager::getPlayer(sock);
|
||||
leavingPlr->iIDGroup = leavingPlr->iID;
|
||||
}
|
||||
|
||||
sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||
if (group == nullptr)
|
||||
void Groups::groupTickInfo(Player* plr) {
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef ref : players) {
|
||||
if(ref != excluded) ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupTickInfo(CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Group* group = plr->group;
|
||||
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||
size_t pcCount = pcs.size();
|
||||
size_t npcCount = npcs.size();
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||
|
||||
pkt->iID = plr->iID;
|
||||
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
||||
|
||||
resp->iID = plr->iID;
|
||||
resp->iMemberPCCnt = plr->groupCnt;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
if (varPlr->activeNano > 0) {
|
||||
respdata[i].bNano = 1;
|
||||
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
|
||||
}
|
||||
}
|
||||
|
||||
sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
|
||||
}
|
||||
|
||||
static void groupUnbuff(Player* plr) {
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
for (int n = 0; n < plr->groupCnt; n++) {
|
||||
if (i == n)
|
||||
continue;
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]);
|
||||
|
||||
Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupKick(Group* group, EntityRef ref) {
|
||||
|
||||
if (group == nullptr)
|
||||
return;
|
||||
|
||||
void Groups::groupKickPlayer(Player* plr) {
|
||||
// if you are the group leader, destroy your own group and kick everybody
|
||||
if (group->members[0] == ref) {
|
||||
disbandGroup(group);
|
||||
if (plr->iID == plr->iIDGroup) {
|
||||
groupUnbuff(plr);
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
plr->groupCnt = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
removeFromGroup(group, ref);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
|
||||
|
||||
resp->iID_LeaveMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
|
||||
|
||||
int bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
|
||||
int moveDown = 0;
|
||||
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
if (sock == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
if (moveDown == 1)
|
||||
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
|
||||
|
||||
respdata[i-moveDown].iPC_ID = varPlr->iID;
|
||||
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i-moveDown].iLv = varPlr->level;
|
||||
respdata[i-moveDown].iHP = varPlr->HP;
|
||||
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
// respdata[i-moveDown]].iMapType = 0;
|
||||
// respdata[i-moveDown]].iMapNum = 0;
|
||||
respdata[i-moveDown].iX = varPlr->x;
|
||||
respdata[i-moveDown].iY = varPlr->y;
|
||||
respdata[i-moveDown].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
if (varPlr == plr) {
|
||||
moveDown = 1;
|
||||
otherPlr->groupIDs[i] = 0;
|
||||
} else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
|
||||
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);
|
||||
}
|
||||
}
|
||||
|
||||
plr->iIDGroup = plr->iID;
|
||||
otherPlr->groupCnt -= 1;
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
}
|
||||
|
||||
int Groups::getGroupFlags(Player* plr) {
|
||||
int bitFlag = 0;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
continue;
|
||||
|
||||
bitFlag |= otherPlr->iGroupConditionBitFlag;
|
||||
}
|
||||
|
||||
return bitFlag;
|
||||
}
|
||||
|
||||
void Groups::init() {
|
||||
|
@@ -1,37 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
struct Group {
|
||||
std::vector<EntityRef> members;
|
||||
|
||||
std::vector<EntityRef> filter(EntityKind kind) {
|
||||
std::vector<EntityRef> filtered;
|
||||
std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) {
|
||||
return e.kind == kind;
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
EntityRef getLeader() {
|
||||
assert(members.size() > 0);
|
||||
return members[0];
|
||||
}
|
||||
|
||||
Group(EntityRef leader);
|
||||
};
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace Groups {
|
||||
void init();
|
||||
void init();
|
||||
|
||||
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(CNSocket* sock);
|
||||
|
||||
void groupKick(Group* group, EntityRef ref);
|
||||
void addToGroup(Group* group, EntityRef member);
|
||||
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
|
||||
void disbandGroup(Group* group);
|
||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(Player* plr);
|
||||
void groupKickPlayer(Player* plr);
|
||||
int getGroupFlags(Player* plr);
|
||||
}
|
||||
|
@@ -1,19 +1,16 @@
|
||||
#include "Items.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include <string.h> // for memset()
|
||||
#include <assert.h>
|
||||
#include <numeric>
|
||||
|
||||
using namespace Items;
|
||||
|
||||
@@ -202,7 +199,7 @@ static int getCrateItem(sItemBase* result, int itemSetId, int rarity, int player
|
||||
return -1;
|
||||
}
|
||||
|
||||
// initialize all weights as the default weight for all item slots
|
||||
// initialize all weights as the default weight for all item slots
|
||||
std::vector<int> itemWeights(validItems.size(), itemSet.defaultItemWeight);
|
||||
|
||||
if (!itemSet.alterItemWeightMap.empty()) {
|
||||
@@ -416,9 +413,6 @@ static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (itemdel->iSlotNum < 0 || itemdel->iSlotNum >= AINVEN_COUNT)
|
||||
return; // sanity check
|
||||
|
||||
resp.eIL = itemdel->eIL;
|
||||
resp.iSlotNum = itemdel->iSlotNum;
|
||||
|
||||
@@ -485,34 +479,30 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
resp->iSlotNum = request->iSlotNum;
|
||||
resp->RemainItem = gumball;
|
||||
resp->iTargetCnt = 1;
|
||||
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
||||
resp->eST = EST_NANOSTIMPAK;
|
||||
resp->iSkillID = 144;
|
||||
|
||||
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||
int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot;
|
||||
int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||
|
||||
respdata->eCT = 1;
|
||||
respdata->iID = player->iID;
|
||||
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
||||
respdata->iConditionBitFlag = value1;
|
||||
|
||||
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
||||
BuffStack gumballBuff = {
|
||||
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||
0,
|
||||
sock,
|
||||
BuffClass::CASH_ITEM // or BuffClass::ITEM?
|
||||
};
|
||||
player->addBuff(eCSB,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&gumballBuff);
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
||||
pkt.eTBU = 1; // eTimeBuffUpdate
|
||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1;
|
||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
||||
// update inventory serverside
|
||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
||||
|
||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, value1);
|
||||
time_t until = getTime() + (time_t)Nanos::SkillTable[144].durationTime[0] * 100;
|
||||
Eggs::EggBuffs[key] = until;
|
||||
}
|
||||
|
||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -765,7 +755,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||
plr->money += miscDropType.taroAmount;
|
||||
// money nano boost
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
@@ -780,7 +770,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
if (levelDifference > 0)
|
||||
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
||||
// scavenger nano boost
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
@@ -832,12 +822,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
|
||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
||||
// sanity check
|
||||
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||
if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) {
|
||||
std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl;
|
||||
return;
|
||||
}
|
||||
// find mob drop id
|
||||
int mobDropId = Items::MobToDropMap[mob->type];
|
||||
int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType];
|
||||
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||
|
||||
|
@@ -1,13 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "Rand.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Rand.hpp"
|
||||
|
||||
struct CrocPotEntry {
|
||||
int multStats, multLooks;
|
||||
|
170
src/Missions.cpp
170
src/Missions.cpp
@@ -1,10 +1,11 @@
|
||||
#include "Missions.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Missions.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include "string.h"
|
||||
|
||||
using namespace Missions;
|
||||
|
||||
@@ -26,12 +27,12 @@ static void saveMission(Player* player, int missionId) {
|
||||
}
|
||||
|
||||
static bool isMissionCompleted(Player* player, int missionId) {
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
return player->aQuestFlag[row] & (1ULL << column);
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
return player->aQuestFlag[row] & (1ULL << column);
|
||||
}
|
||||
|
||||
int Missions::findQSlot(Player *plr, int id) {
|
||||
static int findQSlot(Player *plr, int id) {
|
||||
int i;
|
||||
|
||||
// two passes. we mustn't fail to find an existing stack.
|
||||
@@ -51,7 +52,7 @@ int Missions::findQSlot(Player *plr, int id) {
|
||||
static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int slot = Missions::findQSlot(plr, itemId);
|
||||
int slot = findQSlot(plr, itemId);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
@@ -77,7 +78,7 @@ static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// find free quest item slot
|
||||
int slot = Missions::findQSlot(plr, id);
|
||||
int slot = findQSlot(plr, id);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
@@ -163,14 +164,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
|
||||
// update player
|
||||
plr->money += reward->money;
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
|
||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
plr->money += reward->money * (5 + boost) / 25;
|
||||
}
|
||||
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
@@ -218,18 +219,28 @@ static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) {
|
||||
// ugly pointer/reference juggling for the sake of operator overloading...
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// sanity check
|
||||
// update player
|
||||
int i;
|
||||
bool found = false;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
found = true;
|
||||
break;
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
if (!found) {
|
||||
std::cout << "[WARN] Player tried to end task that isn't in journal?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// mission rewards
|
||||
if (Rewards.find(taskNum) != Rewards.end()) {
|
||||
@@ -238,23 +249,6 @@ static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) {
|
||||
}
|
||||
// don't take away quest items if we haven't finished the quest
|
||||
|
||||
/*
|
||||
* Update player's active mission data.
|
||||
*
|
||||
* This must be done after all early returns have passed, otherwise we
|
||||
* risk introducing non-atomic changes. For example, failing to finish
|
||||
* a mission due to not having any inventory space could delete the
|
||||
* mission server-side; leading to a desync.
|
||||
*/
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == taskNum) {
|
||||
plr->tasks[i] = 0;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Give (or take away) quest items
|
||||
*
|
||||
@@ -297,13 +291,13 @@ bool Missions::startTask(Player* plr, int TaskID) {
|
||||
TaskData& task = *Missions::Tasks[TaskID];
|
||||
|
||||
if (task["m_iCTRReqLvMin"] > plr->level) {
|
||||
std::cout << "[WARN] Player tried to start a task above their level" << std::endl;
|
||||
return false;
|
||||
std::cout << "[WARN] Player tried to start a task below their level" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isMissionCompleted(plr, (int)(task["m_iHMissionID"]) - 1)) {
|
||||
std::cout << "[WARN] Player tried to start an already completed mission" << std::endl;
|
||||
return false;
|
||||
std::cout << "[WARN] Player tried to start an already completed mission" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// client freaks out if nano mission isn't sent first after relogging, so it's easiest to set it here
|
||||
@@ -366,15 +360,15 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
|
||||
// if escort task, assign matching paths to all nearby NPCs
|
||||
if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) {
|
||||
if (task["m_iHTaskType"] == 6) {
|
||||
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
||||
Chunk* chunk = Chunking::chunks[chunkPos];
|
||||
for (EntityRef ref : chunk->entities) {
|
||||
if (ref.kind != EntityKind::PLAYER) {
|
||||
if (ref.type != EntityType::PLAYER) {
|
||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||
NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
|
||||
NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum);
|
||||
if (path != nullptr) {
|
||||
Transport::constructPathNPC(npc->id, path);
|
||||
Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -386,44 +380,42 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
||||
|
||||
if (Missions::Tasks.find(missionData->iTaskNum) == Missions::Tasks.end())
|
||||
return;
|
||||
|
||||
TaskData* task = Missions::Tasks[missionData->iTaskNum];
|
||||
|
||||
// handle timed mission failure
|
||||
if (task->task["m_iSTGrantTimer"] > 0 && missionData->iNPC_ID == 0) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
/*
|
||||
* Enemy killing missions
|
||||
* this is gross and should be cleaned up later
|
||||
* once we comb over mission logic more throughly
|
||||
*/
|
||||
bool mobsAreKilled = false;
|
||||
if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
|
||||
mobsAreKilled = true;
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == missionData->iTaskNum) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (plr->RemainingNPCCount[i][j] > 0) {
|
||||
mobsAreKilled = false;
|
||||
break;
|
||||
// failed timed missions give an iNPC_ID of 0
|
||||
if (missionData->iNPC_ID == 0) {
|
||||
TaskData* task = Missions::Tasks[missionData->iTaskNum];
|
||||
if (task->task["m_iSTGrantTimer"] > 0) { // its a timed mission
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
/*
|
||||
* Enemy killing missions
|
||||
* this is gross and should be cleaned up later
|
||||
* once we comb over mission logic more throughly
|
||||
*/
|
||||
bool mobsAreKilled = false;
|
||||
if (task->task["m_iHTaskType"] == 5) {
|
||||
mobsAreKilled = true;
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == missionData->iTaskNum) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (plr->RemainingNPCCount[i][j] > 0) {
|
||||
mobsAreKilled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mobsAreKilled) {
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
Missions::quitTask(sock, missionData->iTaskNum, false);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
plr->tasks[i] = failTaskID;
|
||||
return;
|
||||
if (!mobsAreKilled) {
|
||||
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
Missions::quitTask(sock, missionData->iTaskNum, false);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
plr->tasks[i] = failTaskID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,12 +486,6 @@ void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
||||
memset(&plr->QInven[j], 0, sizeof(sItemBase));
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (task["m_iFItemID"][i] == 0)
|
||||
continue;
|
||||
dropQuestItem(sock, taskNum, task["m_iFItemNumNeeded"][i], task["m_iFItemID"][i], 0);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
|
||||
failResp.iErrorCode = 1;
|
||||
failResp.iTaskNum = taskNum;
|
||||
@@ -551,6 +537,9 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
#else
|
||||
if (plr->level >= 36)
|
||||
return;
|
||||
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||
plr->level++;
|
||||
|
||||
@@ -569,7 +558,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT));
|
||||
}
|
||||
|
||||
void Missions::mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls) {
|
||||
void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
bool missionmob = false;
|
||||
@@ -592,29 +581,12 @@ void Missions::mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls) {
|
||||
plr->RemainingNPCCount[i][j]--;
|
||||
}
|
||||
}
|
||||
|
||||
// drop quest item
|
||||
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
|
||||
bool drop = rolls[plr->tasks[i]] % 100 < task["m_iSTItemDropRate"][j];
|
||||
bool drop = rolledQItem % 100 < task["m_iSTItemDropRate"][j];
|
||||
if (drop) {
|
||||
// XXX: are CSUItemID and CSTItemID the same?
|
||||
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);
|
||||
|
||||
/*
|
||||
* Workaround: The client has a bug where it only sends a TASK_END request
|
||||
* for the first task of multiple that met their quest item requirements
|
||||
* at the same time. We deal with this by sending TASK_END response packets
|
||||
* proactively and then silently ignoring the extra TASK_END requests it
|
||||
* sends afterwards.
|
||||
*/
|
||||
if (isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j])) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, end);
|
||||
end.iTaskNum = plr->tasks[i];
|
||||
|
||||
if (!endTask(sock, plr->tasks[i]))
|
||||
continue;
|
||||
|
||||
sock->sendPacket(end, P_FE2CL_REP_PC_TASK_END_SUCC);
|
||||
}
|
||||
} else {
|
||||
// fail to drop (itemID == 0)
|
||||
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
#include <map>
|
||||
#include "JSON.hpp"
|
||||
|
||||
struct Reward {
|
||||
int32_t id;
|
||||
@@ -42,14 +40,13 @@ namespace Missions {
|
||||
extern std::map<int32_t, TaskData*> Tasks;
|
||||
extern nlohmann::json AvatarGrowth[37];
|
||||
void init();
|
||||
int findQSlot(Player *plr, int id);
|
||||
|
||||
bool startTask(Player* plr, int TaskID);
|
||||
|
||||
// checks if player doesn't have n/n quest items
|
||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
|
||||
void mobKilled(CNSocket *sock, int mobid, int rolledQItem);
|
||||
|
||||
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
|
||||
|
||||
|
669
src/MobAI.cpp
669
src/MobAI.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
enum class MobState {
|
||||
INACTIVE,
|
||||
ROAMING,
|
||||
COMBAT,
|
||||
RETREAT,
|
||||
DEAD
|
||||
};
|
||||
|
||||
namespace MobAI {
|
||||
void deadStep(CombatNPC* self, time_t currTime);
|
||||
void combatStep(CombatNPC* self, time_t currTime);
|
||||
void roamingStep(CombatNPC* self, time_t currTime);
|
||||
void retreatStep(CombatNPC* self, time_t currTime);
|
||||
|
||||
void onRoamStart(CombatNPC* self, EntityRef src);
|
||||
void onCombatStart(CombatNPC* self, EntityRef src);
|
||||
void onRetreat(CombatNPC* self, EntityRef src);
|
||||
void onDeath(CombatNPC* self, EntityRef src);
|
||||
}
|
||||
// needs to be declared before Mob's constructor
|
||||
void step(CombatNPC*, time_t);
|
||||
};
|
||||
|
||||
struct Mob : public CombatNPC {
|
||||
// general
|
||||
MobState state = MobState::INACTIVE;
|
||||
|
||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
||||
|
||||
// dead
|
||||
time_t killedTime = 0;
|
||||
@@ -47,13 +47,16 @@ struct Mob : public CombatNPC {
|
||||
int offsetX = 0, offsetY = 0;
|
||||
int groupMember[4] = {};
|
||||
|
||||
// for optimizing away AI in empty chunks
|
||||
int playersInView = 0;
|
||||
|
||||
// temporary; until we're sure what's what
|
||||
nlohmann::json data = {};
|
||||
|
||||
Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
||||
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
||||
sightRange(d["m_iSightRange"]) {
|
||||
state = AIState::ROAMING;
|
||||
state = MobState::ROAMING;
|
||||
|
||||
data = d;
|
||||
|
||||
@@ -62,28 +65,20 @@ struct Mob : public CombatNPC {
|
||||
idleRange = (int)data["m_iIdleRange"];
|
||||
level = data["m_iNpcLevel"];
|
||||
|
||||
roamX = spawnX;
|
||||
roamY = spawnY;
|
||||
roamZ = spawnZ;
|
||||
roamX = spawnX = x;
|
||||
roamY = spawnY = y;
|
||||
roamZ = spawnZ = z;
|
||||
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
hp = maxHealth;
|
||||
appearanceData.iHP = maxHealth;
|
||||
|
||||
kind = EntityKind::MOB;
|
||||
|
||||
// AI
|
||||
stateHandlers[AIState::DEAD] = MobAI::deadStep;
|
||||
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
|
||||
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
|
||||
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
|
||||
|
||||
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
|
||||
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
|
||||
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
|
||||
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
|
||||
type = EntityType::MOB;
|
||||
_stepAI = MobAI::step;
|
||||
}
|
||||
|
||||
// constructor for /summon
|
||||
@@ -94,9 +89,6 @@ struct Mob : public CombatNPC {
|
||||
|
||||
~Mob() {}
|
||||
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
auto operator[](std::string s) {
|
||||
return data[s];
|
||||
}
|
||||
@@ -111,4 +103,5 @@ namespace MobAI {
|
||||
void clearDebuff(Mob *mob);
|
||||
void followToCombat(Mob *mob);
|
||||
void groupRetreat(Mob *mob);
|
||||
void enterCombat(CNSocket *sock, Mob *mob);
|
||||
}
|
||||
|
4
src/NPC.hpp
Normal file
4
src/NPC.hpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "Chunking.hpp"
|
||||
#include "Entities.hpp"
|
@@ -1,8 +1,4 @@
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "Combat.hpp"
|
||||
@@ -24,6 +20,8 @@
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
using namespace NPCManager;
|
||||
|
||||
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
|
||||
@@ -69,7 +67,7 @@ void NPCManager::destroyNPC(int32_t id) {
|
||||
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
npc->angle = angle;
|
||||
npc->appearanceData.iAngle = angle;
|
||||
ChunkPos oldChunk = npc->chunkPos;
|
||||
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
||||
npc->x = X;
|
||||
@@ -81,11 +79,11 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I,
|
||||
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
||||
}
|
||||
|
||||
void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) {
|
||||
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
|
||||
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
@@ -94,49 +92,20 @@ void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t si
|
||||
static void npcBarkHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf;
|
||||
|
||||
int taskID = req->iMissionTaskID;
|
||||
// ignore req->iNPC_ID as it is often fixated on a single npc in the region
|
||||
|
||||
if (Missions::Tasks.find(taskID) == Missions::Tasks.end()) {
|
||||
std::cout << "mission task not found: " << taskID << std::endl;
|
||||
return;
|
||||
// get bark IDs from task data
|
||||
TaskData* td = Missions::Tasks[req->iMissionTaskID];
|
||||
std::vector<int> barks;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (td->task["m_iHBarkerTextID"][i] != 0) // non-zeroes only
|
||||
barks.push_back(td->task["m_iHBarkerTextID"][i]);
|
||||
}
|
||||
|
||||
TaskData* td = Missions::Tasks[taskID];
|
||||
auto& barks = td->task["m_iHBarkerTextID"];
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
std::vector<std::pair<int32_t, int32_t>> npcLines;
|
||||
|
||||
for (Chunk* chunk : plr->viewableChunks) {
|
||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||
if (ent->kind != EntityKind::SIMPLE_NPC)
|
||||
continue;
|
||||
|
||||
BaseNPC* npc = (BaseNPC*)ent->getEntity();
|
||||
if (npc->type < 0 || npc->type >= NPCData.size())
|
||||
continue; // npc unknown ?!
|
||||
|
||||
int barkType = NPCData[npc->type]["m_iBarkerType"];
|
||||
if (barkType < 1 || barkType > 4)
|
||||
continue; // no barks
|
||||
|
||||
int barkID = barks[barkType - 1];
|
||||
if (barkID == 0)
|
||||
continue; // no barks
|
||||
|
||||
npcLines.push_back(std::make_pair(npc->id, barkID));
|
||||
}
|
||||
}
|
||||
|
||||
if (npcLines.size() == 0)
|
||||
return; // totally no barks
|
||||
|
||||
auto& [npcID, missionStringID] = npcLines[Rand::rand(npcLines.size())];
|
||||
if (barks.empty())
|
||||
return; // no barks
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_BARKER, resp);
|
||||
resp.iNPC_ID = npcID;
|
||||
resp.iMissionStringID = missionStringID;
|
||||
resp.iNPC_ID = req->iNPC_ID;
|
||||
resp.iMissionStringID = barks[Rand::rand(barks.size())];
|
||||
sock->sendPacket(resp, P_FE2CL_REP_BARKER);
|
||||
}
|
||||
|
||||
@@ -151,20 +120,22 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// type must already be checked and updateNPCPosition() must be called on the result
|
||||
BaseNPC *NPCManager::summonNPC(int spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||
#define EXTRA_HEIGHT 0
|
||||
|
||||
//assert(nextId < INT32_MAX);
|
||||
int id = nextId--;
|
||||
int team = NPCData[type]["m_iTeam"];
|
||||
BaseNPC *npc = nullptr;
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id);
|
||||
npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id);
|
||||
|
||||
// re-enable respawning, if desired
|
||||
((Mob*)npc)->summoned = !respawn;
|
||||
} else
|
||||
npc = new BaseNPC(0, inst, type, id);
|
||||
npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id);
|
||||
|
||||
NPCs[id] = npc;
|
||||
|
||||
@@ -183,7 +154,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
for (int i = 0; i < req->iNPCCnt; i++) {
|
||||
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
|
||||
updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0);
|
||||
updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,12 +183,9 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
if (Warps[warpId].isInstance) {
|
||||
uint64_t instanceID = Warps[warpId].instanceID;
|
||||
|
||||
Player* leader = plr;
|
||||
if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock);
|
||||
|
||||
// if warp requires you to be on a mission, it's gotta be a unique instance
|
||||
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
|
||||
instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID
|
||||
instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID
|
||||
Chunking::createInstance(instanceID);
|
||||
|
||||
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
||||
@@ -227,13 +195,14 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
plr->recallInstance = instanceID;
|
||||
}
|
||||
|
||||
if (plr->group == nullptr)
|
||||
if (plr->iID == plr->iIDGroup && plr->groupCnt == 1)
|
||||
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||
else {
|
||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
for (int i = 0; i < leaderPlr->groupCnt; i++) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
|
||||
|
||||
if (otherPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
@@ -277,7 +246,8 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
Missions::failInstancedMissions(sock); // fail any instanced missions
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
||||
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD);
|
||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
||||
PlayerManager::updatePlayerPosition(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD, plr->angle);
|
||||
|
||||
// remove the player's ongoing race, if any
|
||||
if (Racing::EPRaces.find(sock) != Racing::EPRaces.end())
|
||||
@@ -307,7 +277,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
||||
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
||||
Chunk* chunk = *c;
|
||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||
if (ent->kind == EntityKind::PLAYER)
|
||||
if (ent->type == EntityType::PLAYER)
|
||||
continue;
|
||||
|
||||
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
||||
@@ -322,55 +292,57 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
||||
return npc;
|
||||
}
|
||||
|
||||
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
||||
// TODO: Move this to MobAI, possibly
|
||||
#pragma region NPCEvents
|
||||
|
||||
// summon right arm and stage 2 body
|
||||
static void lordFuseStageTwo(CombatNPC *npc) {
|
||||
static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::cout << "Lord Fuse stage two" << std::endl;
|
||||
|
||||
// Fuse doesn't move
|
||||
// Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData*
|
||||
// Blastons, Heal
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467);
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
||||
|
||||
// right arm, Adaptium, Stun
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469);
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
||||
}
|
||||
|
||||
// summon left arm and stage 3 body
|
||||
static void lordFuseStageThree(CombatNPC *npc) {
|
||||
static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::cout << "Lord Fuse stage three" << std::endl;
|
||||
|
||||
// Cosmix, Damage Point
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468);
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
newbody->instanceID, oldbody->angle);
|
||||
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
||||
|
||||
// Blastons, Heal
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470);
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
arm->instanceID, oldbody->angle);
|
||||
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
||||
}
|
||||
|
||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
||||
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
||||
NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
|
||||
NPCEvent(2467, ON_KILLED, lordFuseStageThree),
|
||||
};
|
||||
|
||||
#pragma endregion NPCEvents
|
||||
@@ -381,11 +353,11 @@ void NPCManager::queueNPCRemoval(int32_t id) {
|
||||
|
||||
static void step(CNServer *serv, time_t currTime) {
|
||||
for (auto& pair : NPCs) {
|
||||
if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
|
||||
if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB)
|
||||
continue;
|
||||
auto npc = (CombatNPC*)pair.second;
|
||||
|
||||
npc->step(currTime);
|
||||
npc->stepAI(currTime);
|
||||
}
|
||||
|
||||
// deallocate all NPCs queued for removal
|
||||
@@ -402,5 +374,5 @@ void NPCManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||
|
||||
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
||||
REGISTER_SHARD_TIMER(step, 200);
|
||||
}
|
||||
|
@@ -1,30 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPC.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#define RESURRECT_HEIGHT 400
|
||||
|
||||
typedef void (*NPCEventHandler)(CombatNPC*);
|
||||
enum Trigger {
|
||||
ON_KILLED,
|
||||
ON_COMBAT
|
||||
};
|
||||
|
||||
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
|
||||
|
||||
struct NPCEvent {
|
||||
int32_t npcType;
|
||||
AIState triggerState;
|
||||
int trigger;
|
||||
NPCEventHandler handler;
|
||||
|
||||
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
||||
: npcType(t), triggerState(tr), handler(hndlr) {}
|
||||
NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
|
||||
: npcType(t), trigger(tr), handler(hndlr) {}
|
||||
};
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
namespace NPCManager {
|
||||
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
||||
extern std::map<int32_t, WarpLocation> Warps;
|
||||
@@ -38,7 +44,7 @@ namespace NPCManager {
|
||||
void destroyNPC(int32_t);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
||||
|
||||
void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size);
|
||||
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
|
||||
|
||||
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
||||
|
||||
|
100
src/Nanos.cpp
100
src/Nanos.cpp
@@ -1,11 +1,11 @@
|
||||
#include "Nanos.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "Nanos.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -82,21 +82,40 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
||||
return; // prevent powerless nanos from summoning
|
||||
|
||||
plr->nanoDrainRate = 0;
|
||||
int16_t skillID = plr->Nanos[plr->activeNano].iSkillID;
|
||||
|
||||
// passive nano unbuffing
|
||||
if (SkillTable[skillID].drainType == 2) {
|
||||
std::vector<int> targetData = findTargets(plr, skillID);
|
||||
|
||||
for (auto& pwr : NanoPowers)
|
||||
if (pwr.skillType == SkillTable[skillID].skillType)
|
||||
nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3));
|
||||
}
|
||||
|
||||
if (nanoID >= NANO_COUNT || nanoID < 0)
|
||||
return; // sanity check
|
||||
|
||||
plr->activeNano = nanoID;
|
||||
sNano& nano = plr->Nanos[nanoID];
|
||||
skillID = plr->Nanos[nanoID].iSkillID;
|
||||
|
||||
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// passive buff effect
|
||||
resp.eCSTB___Add = 1;
|
||||
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||
int32_t targets[] = { plr->iID };
|
||||
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||
// passive nano buffing
|
||||
if (SkillTable[skillID].drainType == 2) {
|
||||
std::vector<int> targetData = findTargets(plr, skillID);
|
||||
|
||||
int boost = 0;
|
||||
if (getNanoBoost(plr))
|
||||
boost = 1;
|
||||
|
||||
for (auto& pwr : NanoPowers) {
|
||||
if (pwr.skillType == SkillTable[skillID].skillType) {
|
||||
resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
|
||||
plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3];
|
||||
|
||||
pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!silent) // silent nano death but only for the summoning player
|
||||
@@ -105,12 +124,12 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||
pkt1.iPC_ID = plr->iID;
|
||||
pkt1.Nano = nano;
|
||||
pkt1.Nano = plr->Nanos[nanoID];
|
||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||
}
|
||||
|
||||
static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
|
||||
if (skill == nullptr || skill->iNanoID >= NANO_COUNT || skill->iNanoID < 0)
|
||||
if (skill->iNanoID >= NANO_COUNT)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
@@ -192,7 +211,7 @@ int Nanos::nanoStyle(int nanoID) {
|
||||
bool Nanos::getNanoBoost(Player* plr) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (plr->equippedNanos[i] == plr->activeNano)
|
||||
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||
if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -207,15 +226,24 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
if (nano->iNanoID < 0 || nano->iNanoID >= NANO_COUNT)
|
||||
return;
|
||||
|
||||
resp.iNanoID = nano->iNanoID;
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
|
||||
// Update player
|
||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||
|
||||
// Unbuff gumballs
|
||||
int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum;
|
||||
if (plr->iConditionBitFlag & value1) {
|
||||
int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum;
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
||||
pkt.eTBU = 2; // eTimeBuffUpdate
|
||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1;
|
||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
||||
}
|
||||
|
||||
// unsummon nano if replaced
|
||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||
summonNano(sock, -1);
|
||||
@@ -258,26 +286,28 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// validate request check
|
||||
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sNano& nano = plr->Nanos[plr->activeNano];
|
||||
int16_t skillID = nano.iSkillID;
|
||||
SkillData* skillData = &Abilities::SkillTable[skillID];
|
||||
int16_t nanoID = plr->activeNano;
|
||||
int16_t skillID = plr->Nanos[nanoID].iSkillID;
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
|
||||
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||
std::vector<int> targetData = findTargets(plr, skillID, data);
|
||||
|
||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||
int boost = 0;
|
||||
if (getNanoBoost(plr))
|
||||
boost = 1;
|
||||
|
||||
plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3];
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
|
||||
for (auto& pwr : NanoPowers)
|
||||
if (pwr.skillType == SkillTable[skillID].skillType)
|
||||
pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]);
|
||||
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
summonNano(sock, -1);
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <map>
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
struct NanoData {
|
||||
int style;
|
||||
|
@@ -1,24 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Chunking.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Groups.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/* forward declaration(s) */
|
||||
class Buff;
|
||||
struct BuffStack;
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
|
||||
#define ACTIVE_MISSION_COUNT 6
|
||||
|
||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||
|
||||
struct Player : public Entity, public ICombatant {
|
||||
struct Player : public Entity {
|
||||
int accountId = 0;
|
||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
int64_t SerialKey = 0;
|
||||
int32_t iID = 0;
|
||||
uint64_t FEKey = 0;
|
||||
|
||||
int level = 0;
|
||||
int HP = 0;
|
||||
@@ -36,8 +36,9 @@ struct Player : public Entity, public ICombatant {
|
||||
int8_t iPCState = 0;
|
||||
int32_t iWarpLocationFlag = 0;
|
||||
int64_t aSkywayLocationFlag[2] = {};
|
||||
int32_t iConditionBitFlag = 0;
|
||||
int32_t iSelfConditionBitFlag = 0;
|
||||
int8_t iSpecialState = 0;
|
||||
std::unordered_map<int, Buff*> buffs = {};
|
||||
|
||||
int angle = 0;
|
||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||
@@ -52,6 +53,7 @@ struct Player : public Entity, public ICombatant {
|
||||
|
||||
bool inCombat = false;
|
||||
bool onMonkey = false;
|
||||
int nanoDrainRate = 0;
|
||||
int healCooldown = 0;
|
||||
|
||||
int pointDamage = 0;
|
||||
@@ -67,13 +69,16 @@ struct Player : public Entity, public ICombatant {
|
||||
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||
|
||||
Group* group = nullptr;
|
||||
int32_t iIDGroup = 0;
|
||||
int groupCnt = 0;
|
||||
int32_t groupIDs[4] = {};
|
||||
int32_t iGroupConditionBitFlag = 0;
|
||||
|
||||
bool notify = false;
|
||||
bool hidden = false;
|
||||
bool unwarpable = false;
|
||||
bool initialLoadDone = false;
|
||||
|
||||
bool buddiesSynced = false;
|
||||
int64_t buddyIDs[50] = {};
|
||||
bool isBuddyBlocked[50] = {};
|
||||
|
||||
@@ -84,31 +89,16 @@ struct Player : public Entity, public ICombatant {
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
// lua events
|
||||
lEvent *onChat = nullptr;
|
||||
|
||||
Player() { type = EntityType::PLAYER; }
|
||||
~Player() {
|
||||
// if an event was registered, free it
|
||||
if (onChat != nullptr)
|
||||
delete onChat;
|
||||
}
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||
virtual Buff* getBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId) override;
|
||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||
virtual void clearBuffs(bool force) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual int heal(EntityRef src, int amt) override;
|
||||
virtual bool isAlive() override;
|
||||
virtual int getCurrentHP() override;
|
||||
virtual int getMaxHP() override;
|
||||
virtual int getLevel() override;
|
||||
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||
virtual int32_t getCharType() override;
|
||||
virtual int32_t getID() override;
|
||||
virtual EntityRef getRef() override;
|
||||
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
sNano* getActiveNano();
|
||||
sPCAppearanceData getAppearanceData();
|
||||
};
|
||||
|
@@ -1,20 +1,27 @@
|
||||
#include "PlayerManager.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "core/CNShared.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Groups.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Buddies.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Buddies.hpp"
|
||||
#include "BuiltinCommands.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Eggs.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
@@ -23,26 +30,31 @@ using namespace PlayerManager;
|
||||
|
||||
std::map<CNSocket*, Player*> PlayerManager::players;
|
||||
|
||||
static void addPlayer(CNSocket* key, Player *plr) {
|
||||
players[key] = plr;
|
||||
plr->chunkPos = Chunking::INVALID_CHUNK;
|
||||
plr->lastHeartbeat = 0;
|
||||
static void addPlayer(CNSocket* key, Player plr) {
|
||||
Player *p = new Player();
|
||||
|
||||
std::cout << getPlayerName(plr) << " has joined!" << std::endl;
|
||||
// copy object into heap memory
|
||||
*p = plr;
|
||||
|
||||
players[key] = p;
|
||||
p->chunkPos = std::make_tuple(0, 0, 0); // TODO: maybe replace with specialized "no chunk" value
|
||||
p->lastHeartbeat = 0;
|
||||
|
||||
std::cout << getPlayerName(p) << " has joined!" << std::endl;
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
|
||||
// call events
|
||||
LuaManager::playerAdded(key);
|
||||
}
|
||||
|
||||
void PlayerManager::removePlayer(CNSocket* key) {
|
||||
Player* plr = getPlayer(key);
|
||||
uint64_t fromInstance = plr->instanceID;
|
||||
|
||||
// free buff memory
|
||||
for(auto buffEntry : plr->buffs)
|
||||
delete buffEntry.second;
|
||||
// call events
|
||||
LuaManager::playerRemoved(key);
|
||||
|
||||
// leave group
|
||||
if(plr->group != nullptr)
|
||||
Groups::groupKick(plr->group, key);
|
||||
Groups::groupKickPlayer(plr);
|
||||
|
||||
// remove player's bullets
|
||||
Combat::Bullets.erase(plr->iID);
|
||||
@@ -66,6 +78,16 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
||||
// if the player was in a lair, clean it up
|
||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||
|
||||
// remove player's buffs from the server
|
||||
auto it = Eggs::EggBuffs.begin();
|
||||
while (it != Eggs::EggBuffs.end()) {
|
||||
if (it->first.first == key) {
|
||||
it = Eggs::EggBuffs.erase(it);
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
}
|
||||
|
||||
@@ -77,28 +99,12 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui
|
||||
plr->x = X;
|
||||
plr->y = Y;
|
||||
plr->z = Z;
|
||||
if (plr->instanceID != I) {
|
||||
plr->instanceID = I;
|
||||
plr->recallInstance = INSTANCE_OVERWORLD;
|
||||
}
|
||||
plr->instanceID = I;
|
||||
if (oldChunk == newChunk)
|
||||
return; // didn't change chunks
|
||||
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Low-level helper function for correctly updating chunks when teleporting players.
|
||||
*
|
||||
* Use PlayerManager::sendPlayerTo() to actually teleport players.
|
||||
*/
|
||||
void PlayerManager::updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst) {
|
||||
Player *plr = getPlayer(sock);
|
||||
|
||||
// force player to reload chunks
|
||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, Chunking::INVALID_CHUNK);
|
||||
updatePlayerPosition(sock, X, Y, Z, inst, plr->angle);
|
||||
}
|
||||
|
||||
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
|
||||
Player* plr = getPlayer(sock);
|
||||
plr->onMonkey = false;
|
||||
@@ -126,13 +132,32 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
||||
}
|
||||
|
||||
if (I != INSTANCE_OVERWORLD) {
|
||||
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
||||
pkt.iInstanceMapNum = (int32_t)MAPNUM(I); // lower 32 bits are mapnum
|
||||
if (I != fromInstance // do not retransmit MAP_INFO on recall
|
||||
&& Racing::EPData.find(pkt.iInstanceMapNum) != Racing::EPData.end()) {
|
||||
EPInfo* ep = &Racing::EPData[pkt.iInstanceMapNum];
|
||||
pkt.iEP_ID = ep->EPID;
|
||||
pkt.iMapCoordX_Min = ep->zoneX * 51200;
|
||||
pkt.iMapCoordX_Max = (ep->zoneX + 1) * 51200;
|
||||
pkt.iMapCoordY_Min = ep->zoneY * 51200;
|
||||
pkt.iMapCoordY_Max = (ep->zoneY + 1) * 51200;
|
||||
pkt.iMapCoordZ_Min = INT32_MIN;
|
||||
pkt.iMapCoordZ_Max = INT32_MAX;
|
||||
}
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_INSTANCE_MAP_INFO);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt2);
|
||||
pkt2.iX = X;
|
||||
pkt2.iY = Y;
|
||||
pkt2.iZ = Z;
|
||||
sock->sendPacket(pkt2, P_FE2CL_REP_PC_GOTO_SUCC);
|
||||
|
||||
updatePlayerPositionForWarp(sock, X, Y, Z, I);
|
||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
||||
updatePlayerPosition(sock, X, Y, Z, I, plr->angle);
|
||||
|
||||
// post-warp: check if the source instance has no more players in it and delete it if so
|
||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||
@@ -155,8 +180,10 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) {
|
||||
* Nanos the player hasn't unlocked will (and should) be greyed out. Thus, all nanos should be accounted
|
||||
* for in these packets, even if the player hasn't unlocked them.
|
||||
*/
|
||||
static void sendNanoBookSubset(CNSocket *sock, Player *plr) {
|
||||
static void sendNanoBookSubset(CNSocket *sock) {
|
||||
#ifdef ACADEMY
|
||||
Player *plr = getPlayer(sock);
|
||||
|
||||
int16_t id = 0;
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt);
|
||||
|
||||
@@ -178,88 +205,78 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
auto enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
|
||||
|
||||
LoginMetadata *lm = CNShared::getLoginMetadata(enter->iEnterSerialKey);
|
||||
if (lm == nullptr) {
|
||||
std::cout << "[WARN] Refusing invalid REQ_PC_ENTER" << std::endl;
|
||||
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
|
||||
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
|
||||
|
||||
// send failure packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_FAIL, fail);
|
||||
sock->sendPacket(fail, P_FE2CL_REP_PC_ENTER_FAIL);
|
||||
plr.groupCnt = 1;
|
||||
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
|
||||
|
||||
// kill the connection
|
||||
sock->kill();
|
||||
// no need to call _killConnection(); Player isn't in shard yet
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = new Player();
|
||||
Database::getPlayer(plr, lm->playerId);
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
|
||||
std::cout << "\tID: " << AUTOU16TOU8(enter->szID) << std::endl;
|
||||
std::cout << "\tSerial: " << enter->iEnterSerialKey << std::endl;
|
||||
std::cout << "\tTemp: " << enter->iTempValue << std::endl;
|
||||
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
|
||||
)
|
||||
|
||||
// check if account is already in use
|
||||
if (isAccountInUse(plr->accountId)) {
|
||||
if (isAccountInUse(plr.accountId)) {
|
||||
// kick the other player
|
||||
exitDuplicate(plr->accountId);
|
||||
|
||||
// re-read the player from disk, in case it was just flushed
|
||||
*plr = {};
|
||||
Database::getPlayer(plr, lm->playerId);
|
||||
exitDuplicate(plr.accountId);
|
||||
}
|
||||
|
||||
plr->group = nullptr;
|
||||
|
||||
response.iID = plr->iID;
|
||||
response.iID = plr.iID;
|
||||
response.uiSvrTime = getTime();
|
||||
|
||||
response.PCLoadData2CL.iUserLevel = plr->accountLevel;
|
||||
response.PCLoadData2CL.iHP = plr->HP;
|
||||
response.PCLoadData2CL.iLevel = plr->level;
|
||||
response.PCLoadData2CL.iCandy = plr->money;
|
||||
response.PCLoadData2CL.iFusionMatter = plr->fusionmatter;
|
||||
response.PCLoadData2CL.iMentor = plr->mentor;
|
||||
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
||||
response.PCLoadData2CL.iHP = plr.HP;
|
||||
response.PCLoadData2CL.iLevel = plr.level;
|
||||
response.PCLoadData2CL.iCandy = plr.money;
|
||||
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
|
||||
response.PCLoadData2CL.iMentor = plr.mentor;
|
||||
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
||||
response.PCLoadData2CL.iX = plr->x;
|
||||
response.PCLoadData2CL.iY = plr->y;
|
||||
response.PCLoadData2CL.iZ = plr->z;
|
||||
response.PCLoadData2CL.iAngle = plr->angle;
|
||||
response.PCLoadData2CL.iBatteryN = plr->batteryN;
|
||||
response.PCLoadData2CL.iBatteryW = plr->batteryW;
|
||||
response.PCLoadData2CL.iX = plr.x;
|
||||
response.PCLoadData2CL.iY = plr.y;
|
||||
response.PCLoadData2CL.iZ = plr.z;
|
||||
response.PCLoadData2CL.iAngle = plr.angle;
|
||||
response.PCLoadData2CL.iBatteryN = plr.batteryN;
|
||||
response.PCLoadData2CL.iBatteryW = plr.batteryW;
|
||||
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
||||
|
||||
response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag;
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
|
||||
|
||||
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
||||
response.PCLoadData2CL.iFatigue = 50;
|
||||
response.PCLoadData2CL.PCStyle = plr->PCStyle;
|
||||
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
||||
|
||||
// client doesnt read this, it gets it from charinfo
|
||||
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
||||
// inventory
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
|
||||
// quest inventory
|
||||
for (int i = 0; i < AQINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aQInven[i] = plr->QInven[i];
|
||||
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
|
||||
// nanos
|
||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||
response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i];
|
||||
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
||||
//response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i] = {0};
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i];
|
||||
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
|
||||
}
|
||||
// missions in progress
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0)
|
||||
if (plr.tasks[i] == 0)
|
||||
break;
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i];
|
||||
TaskData &task = *Missions::Tasks[plr->tasks[i]];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
||||
TaskData &task = *Missions::Tasks[plr.tasks[i]];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
|
||||
/*
|
||||
* client doesn't care about NeededItem ID and Count,
|
||||
* it gets Count from Quest Inventory
|
||||
@@ -269,12 +286,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
*/
|
||||
}
|
||||
}
|
||||
response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID;
|
||||
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
||||
|
||||
// completed missions
|
||||
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
||||
for (int i = 0; i < 16; i++) {
|
||||
response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i];
|
||||
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
||||
}
|
||||
|
||||
// Computress tips
|
||||
@@ -283,45 +300,39 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
||||
}
|
||||
else {
|
||||
response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0];
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1];
|
||||
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
|
||||
}
|
||||
|
||||
plr->instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
||||
plr.SerialKey = enter->iEnterSerialKey;
|
||||
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
||||
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
||||
sock->setFEKey(lm->FEKey);
|
||||
sock->setFEKey(plr.FEKey);
|
||||
sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on
|
||||
|
||||
// Academy builds receive nanos in a separate packet. These need to be sent
|
||||
// before P_FE2CL_REP_PC_ENTER_SUCC as well as after
|
||||
// due to a race condition in the client :(
|
||||
sendNanoBookSubset(sock, plr);
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC);
|
||||
|
||||
sendNanoBookSubset(sock, plr);
|
||||
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
||||
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
||||
|
||||
// transfer ownership of Player object into the shard (still valid in this function though)
|
||||
addPlayer(sock, plr);
|
||||
// check if there is an expiring vehicle
|
||||
Items::checkItemExpire(sock, getPlayer(sock));
|
||||
|
||||
// set player equip stats
|
||||
Items::setItemStats(plr);
|
||||
Items::setItemStats(getPlayer(sock));
|
||||
|
||||
Missions::failInstancedMissions(sock);
|
||||
|
||||
sendNanoBookSubset(sock);
|
||||
|
||||
// initial buddy sync
|
||||
Buddies::refreshBuddyList(sock);
|
||||
|
||||
for (auto& pair : players)
|
||||
if (pair.second->notify)
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
|
||||
|
||||
// deallocate lm
|
||||
delete lm;
|
||||
}
|
||||
|
||||
void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
Player* plr = getPlayer(sock);
|
||||
if (plr->group == nullptr)
|
||||
return;
|
||||
for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER))
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
||||
}
|
||||
|
||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
@@ -329,7 +340,7 @@ void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, siz
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
||||
continue;
|
||||
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
@@ -352,35 +363,6 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->instanceID, plr->angle);
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC);
|
||||
|
||||
if (plr->instanceID != INSTANCE_OVERWORLD) {
|
||||
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
||||
pkt.iInstanceMapNum = (int32_t)MAPNUM(plr->instanceID); // lower 32 bits are mapnum
|
||||
if (pkt.iInstanceMapNum != plr->recallInstance // do not retransmit MAP_INFO on recall
|
||||
&& Racing::EPData.find(pkt.iInstanceMapNum) != Racing::EPData.end()) {
|
||||
EPInfo* ep = &Racing::EPData[pkt.iInstanceMapNum];
|
||||
pkt.iEP_ID = ep->EPID;
|
||||
pkt.iMapCoordX_Min = ep->zoneX * 51200;
|
||||
pkt.iMapCoordX_Max = (ep->zoneX + 1) * 51200;
|
||||
pkt.iMapCoordY_Min = ep->zoneY * 51200;
|
||||
pkt.iMapCoordY_Max = (ep->zoneY + 1) * 51200;
|
||||
pkt.iMapCoordZ_Min = INT32_MIN;
|
||||
pkt.iMapCoordZ_Max = INT32_MAX;
|
||||
}
|
||||
|
||||
sock->sendPacket(pkt, P_FE2CL_INSTANCE_MAP_INFO);
|
||||
}
|
||||
|
||||
if (!plr->initialLoadDone) {
|
||||
// these should be called only once, but not until after
|
||||
// first load-in or else the client may ignore the packets
|
||||
Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD
|
||||
Missions::failInstancedMissions(sock); // auto-fail missions
|
||||
Buddies::sendBuddyList(sock); // buddy list
|
||||
Items::checkItemExpire(sock, plr); // vehicle expiration
|
||||
|
||||
plr->initialLoadDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -395,9 +377,6 @@ static void exitGame(CNSocket* sock, CNPacketData* data) {
|
||||
response.iExitCode = 1;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
|
||||
|
||||
sock->kill();
|
||||
CNShardServer::_killConnection(sock);
|
||||
}
|
||||
|
||||
static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -411,23 +390,17 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
int activeSlot = -1;
|
||||
bool move = false;
|
||||
|
||||
switch ((ePCRegenType)reviveData->iRegenType) {
|
||||
case ePCRegenType::HereByPhoenix: // nano revive
|
||||
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||
return; // sanity check
|
||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
||||
// nano revive
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
// fallthrough
|
||||
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
break;
|
||||
|
||||
default: // plain respawn
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
plr->clearBuffs(false);
|
||||
// fallthrough
|
||||
case ePCRegenType::Unstick: // warp away
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||
} else if (reviveData->iRegenType == 4) {
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
} else {
|
||||
move = true;
|
||||
break;
|
||||
if (reviveData->iRegenType != 5)
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -479,9 +452,11 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
|
||||
if (plr->group != nullptr) {
|
||||
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||
Player *otherPlr = getPlayerFromID(plr->iIDGroup);
|
||||
if (otherPlr != nullptr) {
|
||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
||||
|
||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||
@@ -492,7 +467,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (!move)
|
||||
return;
|
||||
|
||||
updatePlayerPositionForWarp(sock, x, y, z, plr->instanceID);
|
||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
||||
updatePlayerPosition(sock, x, y, z, plr->instanceID, plr->angle);
|
||||
}
|
||||
|
||||
static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -584,7 +560,7 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (flag->iFlagCode <= 64)
|
||||
plr->iFirstUseFlag[0] |= (1ULL << (flag->iFlagCode - 1));
|
||||
else
|
||||
@@ -672,16 +648,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last
|
||||
}
|
||||
|
||||
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
|
||||
switch ((eCN_GM_TargetSearchBy)by) {
|
||||
case eCN_GM_TargetSearchBy::PC_ID:
|
||||
switch (by) {
|
||||
case eCN_GM_TargetSearchBy__PC_ID:
|
||||
assert(id != 0);
|
||||
return getSockFromID(id);
|
||||
case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id
|
||||
case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id
|
||||
assert(uid != 0);
|
||||
for (auto& pair : players)
|
||||
if (pair.second->accountId == uid)
|
||||
return pair.first;
|
||||
case eCN_GM_TargetSearchBy::PC_Name:
|
||||
case eCN_GM_TargetSearchBy__PC_Name:
|
||||
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
|
||||
return getSockFromName(firstname, lastname);
|
||||
}
|
||||
@@ -719,6 +695,4 @@ void PlayerManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, exitPlayerVehicle);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, changePlayerGuide);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET, setFirstUseFlag);
|
||||
|
||||
REGISTER_SHARD_TIMER(CNShared::pruneLoginMetadata, CNSHARED_PERIOD);
|
||||
}
|
||||
|
@@ -1,15 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "core/Core.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
namespace PlayerManager {
|
||||
extern std::map<CNSocket*, Player*> players;
|
||||
void init();
|
||||
@@ -17,7 +18,6 @@ namespace PlayerManager {
|
||||
void removePlayer(CNSocket* key);
|
||||
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
||||
void updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst);
|
||||
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
|
||||
@@ -33,7 +33,6 @@ namespace PlayerManager {
|
||||
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
||||
WarpLocation *getRespawnPoint(Player *plr);
|
||||
|
||||
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
|
||||
// TODO: unify this under the new Entity system
|
||||
@@ -43,7 +42,7 @@ namespace PlayerManager {
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
||||
continue;
|
||||
|
||||
ref.sock->sendPacket(pkt, type);
|
||||
|
@@ -1,7 +1,4 @@
|
||||
#include "PlayerMovement.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "core/Core.hpp"
|
||||
@@ -36,7 +33,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
// [gruntwork] check if player has a follower and move it
|
||||
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
||||
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
|
||||
Transport::NPCQueues.erase(follower->id); // erase existing points
|
||||
Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points
|
||||
std::queue<Vec3> queue;
|
||||
Vec3 from = { follower->x, follower->y, follower->z };
|
||||
float drag = 0.95f; // this ensures that they don't bump into the player
|
||||
@@ -48,7 +45,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
|
||||
Transport::NPCQueues[follower->id] = queue;
|
||||
Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,10 @@
|
||||
#include "Racing.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
using namespace Racing;
|
||||
|
||||
@@ -66,23 +64,9 @@ static void racingCancel(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
|
||||
|
||||
/*
|
||||
* This request packet is used for both cancelling the race via the
|
||||
* NPC at the start, *and* failing the race by running out of time.
|
||||
* If the latter is to happen, the client disables movement until it
|
||||
* receives a packet from the server that re-enables it.
|
||||
*
|
||||
* So, in order to prevent a potential softlock we respawn the player.
|
||||
*/
|
||||
|
||||
// we have to teleport the player back after this, otherwise they are unable to move
|
||||
WarpLocation* respawnLoc = PlayerManager::getRespawnPoint(plr);
|
||||
|
||||
if (respawnLoc != nullptr) {
|
||||
PlayerManager::sendPlayerTo(sock, respawnLoc->x, respawnLoc->y, respawnLoc->z, respawnLoc->instanceID);
|
||||
} else {
|
||||
// fallback, just respawn the player in-place if no suitable point is found
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||
}
|
||||
PlayerManager::sendPlayerTo(sock, respawnLoc->x, respawnLoc->y, respawnLoc->z, respawnLoc->instanceID);
|
||||
}
|
||||
|
||||
static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -99,43 +83,35 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||
return; // IZ not found
|
||||
|
||||
EPInfo& epInfo = EPData[mapNum];
|
||||
EPRace& epRace = EPRaces[sock];
|
||||
|
||||
uint64_t now = getTime() / 1000;
|
||||
int timeDiff = now - epRace.startTime;
|
||||
int podsCollected = epRace.collectedRings.size();
|
||||
|
||||
int score = std::exp(
|
||||
(epInfo.podFactor * podsCollected) / epInfo.maxPods
|
||||
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
|
||||
+ epInfo.scaleFactor);
|
||||
score = (settings::IZRACESCORECAPPED && score > epInfo.maxScore) ? epInfo.maxScore : score;
|
||||
int fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
|
||||
int timeDiff = now - EPRaces[sock].startTime;
|
||||
int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
|
||||
if (score < 0) score = 0; // lol
|
||||
int fm = score * plr->level * (1.0f / 36) * 0.3f;
|
||||
|
||||
// we submit the ranking first...
|
||||
Database::RaceRanking postRanking = {};
|
||||
postRanking.EPID = epInfo.EPID;
|
||||
postRanking.EPID = EPData[mapNum].EPID;
|
||||
postRanking.PlayerID = plr->iID;
|
||||
postRanking.RingCount = podsCollected;
|
||||
postRanking.RingCount = EPRaces[sock].collectedRings.size();
|
||||
postRanking.Score = score;
|
||||
postRanking.Time = timeDiff;
|
||||
postRanking.Timestamp = getTimestamp();
|
||||
Database::postRaceRanking(postRanking);
|
||||
|
||||
// ...then we get the top ranking, which may or may not be what we just submitted
|
||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
|
||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(EPData[mapNum].EPID, plr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
||||
|
||||
// get rank scores and rewards
|
||||
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
|
||||
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
|
||||
std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
|
||||
std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
|
||||
|
||||
// top ranking
|
||||
int maxRank = rankScores->size() - 1;
|
||||
int topRank = 0;
|
||||
while (topRank < maxRank && rankScores->at(topRank) > topRankingPlayer.Score)
|
||||
while (rankScores->at(topRank) > topRankingPlayer.Score)
|
||||
topRank++;
|
||||
|
||||
resp.iEPTopRank = topRank + 1;
|
||||
@@ -145,7 +121,7 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// this ranking
|
||||
int rank = 0;
|
||||
while (rank < maxRank && rankScores->at(rank) > postRanking.Score)
|
||||
while (rankScores->at(rank) > postRanking.Score)
|
||||
rank++;
|
||||
|
||||
resp.iEPRank = rank + 1;
|
||||
|
@@ -1,21 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
struct EPInfo {
|
||||
// available through XDT (maxScore may be updated by drops)
|
||||
int zoneX, zoneY, EPID, maxScore;
|
||||
// available through drops
|
||||
int maxTime, maxPods;
|
||||
double scaleFactor, podFactor, timeFactor;
|
||||
int zoneX, zoneY, EPID, maxScore, maxTime;
|
||||
};
|
||||
|
||||
struct EPRace {
|
||||
std::set<int> collectedRings;
|
||||
std::set<int> collectedRings;
|
||||
int mode, ticketSlot;
|
||||
time_t startTime;
|
||||
};
|
||||
|
53
src/Rand.cpp
53
src/Rand.cpp
@@ -1,5 +1,4 @@
|
||||
#include "Rand.hpp"
|
||||
#include "core/Core.hpp"
|
||||
|
||||
std::unique_ptr<std::mt19937> Rand::generator;
|
||||
|
||||
@@ -34,58 +33,6 @@ float Rand::randFloat() {
|
||||
return Rand::randFloat(0.0f, 1.0f);
|
||||
}
|
||||
|
||||
#define RANDBYTES 8
|
||||
|
||||
/*
|
||||
* Cryptographically secure RNG. Borrowed from bcrypt_gensalt().
|
||||
*/
|
||||
uint64_t Rand::cryptoRand() {
|
||||
uint8_t buf[RANDBYTES];
|
||||
|
||||
#ifdef _WIN32
|
||||
HCRYPTPROV p;
|
||||
|
||||
// Acquire a crypt context for generating random bytes.
|
||||
if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (CryptGenRandom(p, RANDBYTES, (BYTE*)buf) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (CryptReleaseContext(p, 0) == FALSE) {
|
||||
goto fail;
|
||||
}
|
||||
#else
|
||||
int fd;
|
||||
|
||||
// Get random bytes on Unix/Linux.
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (read(fd, buf, RANDBYTES) < RANDBYTES) {
|
||||
perror("read");
|
||||
close(fd);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
#endif
|
||||
|
||||
return *(uint64_t*)buf;
|
||||
|
||||
fail:
|
||||
std::cout << "[FATAL] Failed to generate cryptographic random number" << std::endl;
|
||||
terminate(0);
|
||||
|
||||
/* not reached */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Rand::init(uint64_t seed) {
|
||||
Rand::generator = std::make_unique<std::mt19937>(std::mt19937(seed));
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Rand {
|
||||
extern std::unique_ptr<std::mt19937> generator;
|
||||
@@ -15,8 +14,6 @@ namespace Rand {
|
||||
|
||||
int32_t randWeighted(const std::vector<int32_t>& weights);
|
||||
|
||||
uint64_t cryptoRand();
|
||||
|
||||
float randFloat(float startInclusive, float endExclusive);
|
||||
float randFloat(float endExclusive);
|
||||
float randFloat();
|
||||
|
@@ -1,14 +1,18 @@
|
||||
#include "TableData.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Vendors.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Chunking.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Racing.hpp"
|
||||
#include "Vendors.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Eggs.hpp"
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
@@ -33,22 +37,6 @@ public:
|
||||
const char *what() const throw() { return msg.c_str(); }
|
||||
};
|
||||
|
||||
/*
|
||||
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
|
||||
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
|
||||
* silently dropped from the gruntwork file, which would be confusing in situations
|
||||
* where a gruntwork file for the wrong game build was accidentally loaded.
|
||||
*/
|
||||
static void ensureValidNPCType(int type, std::string filename) {
|
||||
// last known NPC type
|
||||
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
|
||||
|
||||
if (type > npcLimit) {
|
||||
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a full and properly-paced path by interpolating between keyframes.
|
||||
*/
|
||||
@@ -227,34 +215,18 @@ static void loadXDT(json& xdtData) {
|
||||
// load nano powers
|
||||
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
||||
|
||||
for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) {
|
||||
auto skill = _skill.value();
|
||||
SkillData skillData = {
|
||||
skill["m_iSkillType"],
|
||||
skill["m_iEffectTarget"],
|
||||
skill["m_iEffectType"],
|
||||
skill["m_iTargetType"],
|
||||
skill["m_iBatteryDrainType"],
|
||||
skill["m_iEffectArea"]
|
||||
};
|
||||
|
||||
skillData.valueTypes[0] = skill["m_iValueA_Type"];
|
||||
skillData.valueTypes[1] = skill["m_iValueB_Type"];
|
||||
skillData.valueTypes[2] = skill["m_iValueC_Type"];
|
||||
|
||||
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) {
|
||||
auto skills = _skills.value();
|
||||
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i];
|
||||
skillData.durationTime[i] = skill["m_iDurationTime"][i];
|
||||
|
||||
skillData.values[0][i] = skill["m_iValueA"][i];
|
||||
skillData.values[1][i] = skill["m_iValueB"][i];
|
||||
skillData.values[2][i] = skill["m_iValueC"][i];
|
||||
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i];
|
||||
skillData.durationTime[i] = skills["m_iDurationTime"][i];
|
||||
skillData.powerIntensity[i] = skills["m_iValueA"][i];
|
||||
}
|
||||
|
||||
Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData;
|
||||
Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl;
|
||||
std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl;
|
||||
|
||||
// load EP data
|
||||
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
|
||||
@@ -326,10 +298,10 @@ static void loadPaths(json& pathData, int32_t* nextId) {
|
||||
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
|
||||
passedDistance -= SLIDER_GAP_SIZE; // step down
|
||||
// spawn a slider
|
||||
Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
||||
NPCManager::NPCs[slider->id] = slider;
|
||||
NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0);
|
||||
Transport::NPCQueues[slider->id] = route;
|
||||
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
||||
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider;
|
||||
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0);
|
||||
Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route;
|
||||
}
|
||||
// rotate
|
||||
route.pop();
|
||||
@@ -375,7 +347,7 @@ static void loadPaths(json& pathData, int32_t* nextId) {
|
||||
Transport::NPCPaths.push_back(pathTemplate);
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
|
||||
|
||||
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
||||
@@ -584,17 +556,8 @@ static void loadDrops(json& dropData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EPInfo& epInfo = Racing::EPData[EPMap];
|
||||
|
||||
// max score is specified in the XDT, but can be updated if specified in the drops JSON
|
||||
epInfo.maxScore = (int)race["ScoreCap"];
|
||||
// time limit and total pods are not stored in the XDT, so we include it in the drops JSON
|
||||
epInfo.maxTime = (int)race["TimeLimit"];
|
||||
epInfo.maxPods = (int)race["TotalPods"];
|
||||
// IZ-specific calculated constants included in the drops JSON
|
||||
epInfo.scaleFactor = (double)race["ScaleFactor"];
|
||||
epInfo.podFactor = (double)race["PodFactor"];
|
||||
epInfo.timeFactor = (double)race["TimeFactor"];
|
||||
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
||||
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
|
||||
|
||||
// score cutoffs
|
||||
std::vector<int> rankScores;
|
||||
@@ -610,7 +573,7 @@ static void loadDrops(json& dropData) {
|
||||
|
||||
if (rankScores.size() != 5 || rankScores.size() != rankRewards.size()) {
|
||||
char buff[255];
|
||||
snprintf(buff, 255, "Race in EP %d doesn't have exactly 5 score/reward pairs", raceEPID);
|
||||
sprintf(buff, "Race in EP %d doesn't have exactly 5 score/reward pairs", raceEPID);
|
||||
throw TableException(std::string(buff));
|
||||
}
|
||||
|
||||
@@ -680,7 +643,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
||||
int id = (*nextId)--;
|
||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
||||
|
||||
Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
|
||||
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
||||
NPCManager::NPCs[id] = addEgg;
|
||||
eggCount++;
|
||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||
@@ -695,12 +658,10 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* Load gruntwork output, if it exists
|
||||
*/
|
||||
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||
if (gruntwork.is_null())
|
||||
return;
|
||||
|
||||
try {
|
||||
auto paths = gruntwork["paths"];
|
||||
@@ -710,10 +671,10 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||
std::vector<int32_t> targetIDs;
|
||||
std::vector<int32_t> targetTypes; // target types are not exportable from gw, but load them anyway
|
||||
std::vector<Vec3> pathPoints;
|
||||
int speed = path.find("iBaseSpeed") == path.end() ? NPC_DEFAULT_SPEED : (int)path["iBaseSpeed"];
|
||||
int taskID = path.find("iTaskID") == path.end() ? -1 : (int)path["iTaskID"];
|
||||
bool relative = path.find("bRelative") == path.end() ? false : (bool)path["bRelative"];
|
||||
bool loop = path.find("bLoop") == path.end() ? true : (bool)path["bLoop"]; // loop by default
|
||||
int speed = (int)path["iBaseSpeed"];
|
||||
int taskID = (int)path["iTaskID"];
|
||||
bool relative = (bool)path["bRelative"];
|
||||
bool loop = (bool)path["bLoop"];
|
||||
|
||||
// target IDs
|
||||
for (json::iterator _tID = path["aNPCIDs"].begin(); _tID != path["aNPCIDs"].end(); _tID++)
|
||||
@@ -750,8 +711,8 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||
}
|
||||
|
||||
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
if (gruntwork.is_null())
|
||||
return;
|
||||
|
||||
if (gruntwork.is_null()) return;
|
||||
|
||||
try {
|
||||
// skyway paths
|
||||
@@ -776,7 +737,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||
continue; // NPC not found
|
||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||
npc->angle = angle;
|
||||
npc->appearanceData.iAngle = angle;
|
||||
|
||||
RunningNPCRotations[npcID] = angle;
|
||||
}
|
||||
@@ -789,8 +750,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||
continue; // NPC not found
|
||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
|
||||
npc->z, instanceID, npc->angle);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y,
|
||||
npc->z, instanceID, npc->appearanceData.iAngle);
|
||||
|
||||
RunningNPCMapNumbers[npcID] = instanceID;
|
||||
}
|
||||
@@ -803,8 +764,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
int id = (*nextId)--;
|
||||
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
||||
|
||||
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
||||
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
||||
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
||||
@@ -812,21 +771,18 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
// re-enable respawning
|
||||
((Mob*)npc)->summoned = false;
|
||||
} else {
|
||||
npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id);
|
||||
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
|
||||
}
|
||||
|
||||
NPCManager::NPCs[npc->id] = npc;
|
||||
RunningMobs[npc->id] = npc;
|
||||
NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
RunningMobs[npc->appearanceData.iNPC_ID] = npc;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
||||
}
|
||||
|
||||
// mob groups
|
||||
auto groups = gruntwork["groups"];
|
||||
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
||||
auto leader = _group.value();
|
||||
|
||||
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||
|
||||
@@ -847,9 +803,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
int followerCount = 0;
|
||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||
auto follower = _fol.value();
|
||||
|
||||
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||
|
||||
@@ -861,7 +814,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
|
||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
||||
tmpFol->groupLeader = tmp->id;
|
||||
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID;
|
||||
tmp->groupMember[followerCount++] = *nextId;
|
||||
|
||||
(*nextId)--;
|
||||
@@ -871,7 +824,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
|
||||
}
|
||||
|
||||
RunningGroups[tmp->id] = tmp; // store as running
|
||||
RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running
|
||||
}
|
||||
|
||||
auto eggs = gruntwork["eggs"];
|
||||
@@ -880,7 +833,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
int id = (*nextId)--;
|
||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
||||
|
||||
Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
|
||||
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
||||
NPCManager::NPCs[id] = addEgg;
|
||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||
RunningEggs[id] = addEgg;
|
||||
@@ -906,15 +859,16 @@ static void loadNPCs(json& npcData) {
|
||||
npcID += NPC_ID_OFFSET;
|
||||
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||
int type = (int)npc["iNPCType"];
|
||||
|
||||
ensureValidNPCType(type, settings::NPCJSON);
|
||||
|
||||
if (NPCManager::NPCData[type].is_null()) {
|
||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
||||
continue;
|
||||
}
|
||||
#ifdef ACADEMY
|
||||
// do not spawn NPCs in the future
|
||||
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
||||
continue;
|
||||
#endif
|
||||
BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID);
|
||||
BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID);
|
||||
|
||||
NPCManager::NPCs[npcID] = tmp;
|
||||
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
||||
@@ -950,9 +904,10 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||
npcID += MOB_ID_OFFSET;
|
||||
int type = (int)npc["iNPCType"];
|
||||
|
||||
ensureValidNPCType(type, settings::MOBJSON);
|
||||
|
||||
if (NPCManager::NPCData[type].is_null()) {
|
||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
||||
continue;
|
||||
}
|
||||
auto td = NPCManager::NPCData[type];
|
||||
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||
|
||||
@@ -980,10 +935,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
||||
auto leader = _group.value();
|
||||
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||
|
||||
leadID += MOB_GROUP_ID_OFFSET;
|
||||
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
|
||||
|
||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||
auto followers = leader["aFollowers"];
|
||||
@@ -1013,9 +965,6 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
int followerCount = 0;
|
||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||
auto follower = _fol.value();
|
||||
|
||||
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
|
||||
|
||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||
|
||||
@@ -1024,7 +973,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
|
||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
||||
tmpFol->groupLeader = tmp->id;
|
||||
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID;
|
||||
tmp->groupMember[followerCount++] = *nextId;
|
||||
|
||||
(*nextId)--;
|
||||
@@ -1121,39 +1070,19 @@ void TableData::init() {
|
||||
};
|
||||
|
||||
// load JSON data into tables
|
||||
std::ifstream fstream;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
std::pair<json*, std::string>& table = tables[i];
|
||||
|
||||
// scope for fstream
|
||||
{
|
||||
std::ifstream fstream;
|
||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||
|
||||
// did we fail to open the file?
|
||||
if (fstream.fail()) {
|
||||
// gruntwork isn't critical
|
||||
if (table.first == &gruntwork)
|
||||
continue;
|
||||
|
||||
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
||||
fstream.open(settings::TDATADIR + table.second); // open file
|
||||
if (!fstream.fail()) {
|
||||
fstream >> *table.first; // load file contents into table
|
||||
} else {
|
||||
if (table.first != &gruntwork) { // gruntwork isn't critical
|
||||
std::cerr << "[FATAL] Critical tdata file missing: " << settings::TDATADIR << table.second << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// is the file empty?
|
||||
if (fstream.peek() == std::ifstream::traits_type::eof()) {
|
||||
// tolerate empty gruntwork file
|
||||
if (table.first == &gruntwork) {
|
||||
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// load file contents into table
|
||||
fstream >> *table.first;
|
||||
}
|
||||
fstream.close();
|
||||
|
||||
// patching: load each patch directory specified in the config file
|
||||
|
||||
@@ -1168,11 +1097,11 @@ void TableData::init() {
|
||||
std::string patchModuleName = *it;
|
||||
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
||||
try {
|
||||
std::ifstream fstream;
|
||||
fstream.open(patchFile);
|
||||
fstream >> patch; // load into temporary json object
|
||||
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
||||
patchJSON(table.first, &patch); // patch
|
||||
fstream.close();
|
||||
} catch (const std::exception& err) {
|
||||
// no-op
|
||||
}
|
||||
@@ -1198,7 +1127,7 @@ void TableData::init() {
|
||||
* Write gruntwork output to file
|
||||
*/
|
||||
void TableData::flush() {
|
||||
std::ofstream file(settings::TDATADIR + "/" + settings::GRUNTWORKJSON);
|
||||
std::ofstream file(settings::TDATADIR + settings::GRUNTWORKJSON);
|
||||
json gruntwork;
|
||||
|
||||
for (auto& pair : RunningSkywayRoutes) {
|
||||
@@ -1247,7 +1176,7 @@ void TableData::flush() {
|
||||
continue;
|
||||
|
||||
int x, y, z;
|
||||
if (npc->kind == EntityKind::MOB) {
|
||||
if (npc->type == EntityType::MOB) {
|
||||
Mob *m = (Mob*)npc;
|
||||
x = m->spawnX;
|
||||
y = m->spawnY;
|
||||
@@ -1259,13 +1188,13 @@ void TableData::flush() {
|
||||
}
|
||||
|
||||
// NOTE: this format deviates slightly from the one in mobs.json
|
||||
mob["iNPCType"] = (int)npc->type;
|
||||
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
|
||||
mob["iX"] = x;
|
||||
mob["iY"] = y;
|
||||
mob["iZ"] = z;
|
||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
||||
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
|
||||
mob["iAngle"] = npc->angle;
|
||||
mob["iAngle"] = npc->appearanceData.iAngle;
|
||||
|
||||
// it's called mobs, but really it's everything
|
||||
gruntwork["mobs"].push_back(mob);
|
||||
@@ -1280,19 +1209,19 @@ void TableData::flush() {
|
||||
|
||||
int x, y, z;
|
||||
std::vector<Mob*> followers;
|
||||
if (npc->kind == EntityKind::MOB) {
|
||||
if (npc->type == EntityType::MOB) {
|
||||
Mob* m = (Mob*)npc;
|
||||
x = m->spawnX;
|
||||
y = m->spawnY;
|
||||
z = m->spawnZ;
|
||||
if (m->groupLeader != m->id) { // make sure this is a leader
|
||||
if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader
|
||||
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// add follower data to vector; go until OOB or until follower ID is 0
|
||||
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
|
||||
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) {
|
||||
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
||||
continue;
|
||||
}
|
||||
@@ -1306,13 +1235,13 @@ void TableData::flush() {
|
||||
}
|
||||
|
||||
// NOTE: this format deviates slightly from the one in mobs.json
|
||||
mob["iNPCType"] = (int)npc->type;
|
||||
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
|
||||
mob["iX"] = x;
|
||||
mob["iY"] = y;
|
||||
mob["iZ"] = z;
|
||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
||||
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
|
||||
mob["iAngle"] = npc->angle;
|
||||
mob["iAngle"] = npc->appearanceData.iAngle;
|
||||
|
||||
// followers
|
||||
while (followers.size() > 0) {
|
||||
@@ -1321,7 +1250,7 @@ void TableData::flush() {
|
||||
|
||||
// populate JSON entry
|
||||
json fol;
|
||||
fol["iNPCType"] = follower->type;
|
||||
fol["iNPCType"] = follower->appearanceData.iNPCType;
|
||||
fol["iOffsetX"] = follower->offsetX;
|
||||
fol["iOffsetY"] = follower->offsetY;
|
||||
|
||||
@@ -1346,7 +1275,7 @@ void TableData::flush() {
|
||||
int mapnum = MAPNUM(npc->instanceID);
|
||||
if (mapnum != 0)
|
||||
egg["iMapNum"] = mapnum;
|
||||
egg["iType"] = npc->type;
|
||||
egg["iType"] = npc->appearanceData.iNPCType;
|
||||
|
||||
gruntwork["eggs"].push_back(egg);
|
||||
}
|
||||
@@ -1370,7 +1299,7 @@ void TableData::flush() {
|
||||
targetIDs.push_back(tID);
|
||||
for (int32_t tType : path.targetTypes)
|
||||
targetTypes.push_back(tType);
|
||||
|
||||
|
||||
pathObj["iBaseSpeed"] = path.speed;
|
||||
pathObj["iTaskID"] = path.escortTaskID;
|
||||
pathObj["bRelative"] = path.isRelative;
|
||||
|
@@ -1,13 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
// these are added to the NPC's static key to avoid collisions
|
||||
const int NPC_ID_OFFSET = 1;
|
||||
|
@@ -1,9 +1,5 @@
|
||||
#include "Trading.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
using namespace Trading;
|
||||
|
||||
@@ -186,36 +182,6 @@ static void tradeOfferRefusal(CNSocket* sock, CNPacketData* data) {
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL));
|
||||
}
|
||||
|
||||
static void tradeOfferCancel(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_OFFER_CANCEL* pacdat = (sP_CL2FE_REQ_PC_TRADE_OFFER_CANCEL*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_CANCEL, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_CANCEL, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_CANCEL));
|
||||
}
|
||||
|
||||
static void tradeOfferAbort(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_OFFER_ABORT* pacdat = (sP_CL2FE_REQ_PC_TRADE_OFFER_ABORT*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(pacdat->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_ABORT, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_OFFER_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_OFFER_ABORT));
|
||||
}
|
||||
|
||||
static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_TRADE_CONFIRM* pacdat = (sP_CL2FE_REQ_PC_TRADE_CONFIRM*)data->buf;
|
||||
|
||||
@@ -239,13 +205,6 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
resp.iID_Request = plr->iID;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
|
||||
// both players are no longer trading
|
||||
plr->isTrading = false;
|
||||
plr2->isTrading = false;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,23 +247,14 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
otherSock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC));
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp);
|
||||
resp.iID_Request = plr->iID;
|
||||
resp.iID_Request = plr2->iID;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
resp.iID_To = pacdat->iID_To;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
resp.iID_Request = plr2->iID;
|
||||
resp.iID_Request = plr->iID;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT));
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
std::string text = "Trade Failed";
|
||||
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
msg.iDuringTime = 3;
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
return;
|
||||
}
|
||||
|
||||
Database::commitTrade(plr, plr2);
|
||||
}
|
||||
|
||||
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -351,10 +301,8 @@ static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->Trade[pacdat->Item.iSlotNum] = pacdat->Item;
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
@@ -395,9 +343,7 @@ static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_ITEM_UNREGISTER_SUCC, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
@@ -428,7 +374,7 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (pacdat->iCandy < 0 || pacdat->iCandy > plr->money)
|
||||
return; // famous glitch, begone
|
||||
return; // famous glitch, begone
|
||||
|
||||
CNSocket* otherSock; // weird flip flop because we need to know who the other player is
|
||||
if (pacdat->iID_Request == pacdat->iID_From)
|
||||
@@ -439,10 +385,6 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) {
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
plr->isTradeConfirm = false;
|
||||
plr2->isTradeConfirm = false;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, resp);
|
||||
resp.iID_Request = pacdat->iID_Request;
|
||||
resp.iID_From = pacdat->iID_From;
|
||||
@@ -460,8 +402,6 @@ void Trading::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER, tradeOffer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_ACCEPT, tradeOfferAccept);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_REFUSAL, tradeOfferRefusal);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_CANCEL, tradeOfferCancel);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_OFFER_ABORT, tradeOfferAbort);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CONFIRM, tradeConfirm);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CONFIRM_CANCEL, tradeConfirmCancel);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_ITEM_REGISTER, tradeRegisterItem);
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Items.hpp"
|
||||
|
||||
namespace Trading {
|
||||
void init();
|
||||
}
|
@@ -1,12 +1,9 @@
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Transport.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
@@ -168,9 +165,9 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
if (target == nullptr)
|
||||
return;
|
||||
|
||||
// we warped; update position and chunks
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD);
|
||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
||||
PlayerManager::updatePlayerPosition(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD, plr->angle);
|
||||
}
|
||||
|
||||
void Transport::testMssRoute(CNSocket *sock, std::vector<Vec3>* route) {
|
||||
@@ -260,13 +257,13 @@ static void stepNPCPathing() {
|
||||
}
|
||||
|
||||
// skip if not simulating mobs
|
||||
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
||||
if (npc->type == EntityType::MOB && !MobAI::simulateMobs) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not roam if not roaming
|
||||
if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
||||
if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
@@ -279,15 +276,15 @@ static void stepNPCPathing() {
|
||||
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
||||
|
||||
// update NPC location to update viewables
|
||||
NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle);
|
||||
|
||||
// TODO: move walking logic into Entity stack
|
||||
switch (npc->kind) {
|
||||
case EntityKind::BUS:
|
||||
switch (npc->type) {
|
||||
case EntityType::BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||
|
||||
busMove.eTT = 3;
|
||||
busMove.iT_ID = npc->id;
|
||||
busMove.iT_ID = npc->appearanceData.iNPC_ID;
|
||||
busMove.iMoveStyle = 0; // ???
|
||||
busMove.iToX = point.x;
|
||||
busMove.iToY = point.y;
|
||||
@@ -296,12 +293,12 @@ static void stepNPCPathing() {
|
||||
|
||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||
break;
|
||||
case EntityKind::MOB:
|
||||
case EntityType::MOB:
|
||||
MobAI::incNextMovement((Mob*)npc);
|
||||
/* fallthrough */
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||
move.iNPC_ID = npc->id;
|
||||
move.iNPC_ID = npc->appearanceData.iNPC_ID;
|
||||
move.iMoveStyle = 0; // ???
|
||||
move.iToX = point.x;
|
||||
move.iToY = point.y;
|
||||
@@ -388,7 +385,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
|
||||
|
||||
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
if (npc->kind == EntityKind::MOB)
|
||||
if (npc->type == EntityType::MOB)
|
||||
((Mob*)(npc))->staticPath = true;
|
||||
npc->loopingPath = path->isLoop;
|
||||
|
||||
|
@@ -1,11 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
const int SLIDER_SPEED = 1200;
|
||||
const int SLIDER_STOP_TICKS = 16;
|
||||
|
@@ -1,14 +1,6 @@
|
||||
#include "Vendors.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
// 7 days
|
||||
#define VEHICLE_EXPIRY_DURATION 604800
|
||||
|
||||
using namespace Vendors;
|
||||
|
||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||
@@ -21,25 +13,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
|
||||
if (req->iVendorID != req->iNPC_ID || Vendors::VendorTables.find(req->iVendorID) == Vendors::VendorTables.end()) {
|
||||
std::cout << "[WARN] Vendor with ID " << req->iVendorID << " mismatched or not found (buy)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<VendorListing>* listings = &Vendors::VendorTables[req->iVendorID];
|
||||
VendorListing reqItem;
|
||||
reqItem.id = req->Item.iID;
|
||||
reqItem.type = req->Item.iType;
|
||||
reqItem.sort = 0; // just to be safe
|
||||
|
||||
if (std::find(listings->begin(), listings->end(), reqItem) == listings->end()) { // item not found in listing
|
||||
std::cout << "[WARN] Player " << PlayerManager::getPlayerName(plr) << " tried to buy an item that wasn't on sale" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
Items::Item* itemDat = Items::getItemData(req->Item.iID, req->Item.iType);
|
||||
|
||||
if (itemDat == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL);
|
||||
@@ -61,8 +36,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// if vehicle
|
||||
if (req->Item.iType == 10) {
|
||||
// set time limit: current time + expiry duration
|
||||
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION;
|
||||
// set time limit: current time + 7days
|
||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
@@ -227,25 +202,17 @@ static void vendorTable(CNSocket* sock, CNPacketData* data) {
|
||||
if (req->iVendorID != req->iNPC_ID || Vendors::VendorTables.find(req->iVendorID) == Vendors::VendorTables.end())
|
||||
return;
|
||||
|
||||
std::vector<VendorListing>& listings = Vendors::VendorTables[req->iVendorID];
|
||||
std::vector<VendorListing> listings = Vendors::VendorTables[req->iVendorID];
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
||||
|
||||
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||
sItemBase base = {};
|
||||
base.iID = listings[i].id;
|
||||
sItemBase base;
|
||||
base.iID = listings[i].iID;
|
||||
base.iOpt = 0;
|
||||
base.iTimeLimit = 0;
|
||||
base.iType = listings[i].type;
|
||||
|
||||
/*
|
||||
* Set vehicle expiry value.
|
||||
*
|
||||
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
|
||||
* a duration, unlike in most other contexts where it contains the
|
||||
* expiration timestamp.
|
||||
*/
|
||||
if (listings[i].type == 10)
|
||||
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
|
||||
|
||||
sItemVendor vItem;
|
||||
vItem.item = base;
|
||||
vItem.iSortNum = listings[i].sort;
|
||||
|
@@ -1,17 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Items.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
|
||||
struct VendorListing {
|
||||
int sort, type, id;
|
||||
|
||||
// when validating a listing, we don't really care about the sorting index
|
||||
bool operator==(const VendorListing& other) const {
|
||||
return type == other.type && id == other.id;
|
||||
}
|
||||
int sort, type, iID;
|
||||
};
|
||||
|
||||
namespace Vendors {
|
||||
|
@@ -41,8 +41,7 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||
uint64_t num = (uint64_t)(iv1 + 1);
|
||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||
uint64_t dEKey;
|
||||
memcpy(&dEKey, defaultKey, sizeof(dEKey));
|
||||
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
||||
return dEKey * (uTime * num * num2);
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
|
||||
// ========================================================[[ CNSocket ]]========================================================
|
||||
|
||||
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
||||
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
|
||||
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
||||
}
|
||||
|
||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||
@@ -110,11 +109,7 @@ bool CNSocket::isAlive() {
|
||||
}
|
||||
|
||||
void CNSocket::kill() {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
alive = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
shutdown(sock, SD_BOTH);
|
||||
closesocket(sock);
|
||||
@@ -246,10 +241,9 @@ void CNSocket::step() {
|
||||
if (readSize <= 0) {
|
||||
// we aren't reading a packet yet, try to start looking for one
|
||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||
if (recved >= 0 && recved < sizeof(int32_t)) {
|
||||
// too little data for readSize or the socket was closed normally (when 0 bytes were read)
|
||||
if (recved == 0) {
|
||||
// the socket was closed normally
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved)) {
|
||||
// we got our packet size!!!!
|
||||
readSize = *((int32_t*)readBuffer);
|
||||
@@ -270,12 +264,11 @@ void CNSocket::step() {
|
||||
}
|
||||
|
||||
if (readSize > 0 && readBufferIndex < readSize) {
|
||||
// read until the end of the packet (or at least try to)
|
||||
// read until the end of the packet! (or at least try too)
|
||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||
if (recved == 0) {
|
||||
// the socket was closed normally
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved))
|
||||
readBufferIndex += recved;
|
||||
else if (OF_ERRNO != OF_EWOULD) {
|
||||
@@ -418,9 +411,9 @@ void CNServer::addPollFD(SOCKET s) {
|
||||
fds.push_back({s, POLLIN});
|
||||
}
|
||||
|
||||
void CNServer::removePollFD(int fd) {
|
||||
void CNServer::removePollFD(int i) {
|
||||
auto it = fds.begin();
|
||||
while (it != fds.end() && it->fd != fd)
|
||||
while (it != fds.end() && it->fd != fds[i].fd)
|
||||
it++;
|
||||
assert(it != fds.end());
|
||||
|
||||
@@ -465,7 +458,7 @@ void CNServer::start() {
|
||||
if (!setSockNonblocking(sock, newConnectionSocket))
|
||||
continue;
|
||||
|
||||
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
addPollFD(newConnectionSocket);
|
||||
|
||||
@@ -480,15 +473,10 @@ void CNServer::start() {
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
||||
|
||||
// halt packet handling if server is shutting down
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// player sockets
|
||||
if (connections.find(fds[i].fd) == connections.end()) {
|
||||
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl;
|
||||
assert(0);
|
||||
/* not reached */
|
||||
std::cout << "[WARN] Event on non-existant socket?" << std::endl;
|
||||
continue; // just to be safe
|
||||
}
|
||||
|
||||
CNSocket* cSock = connections[fds[i].fd];
|
||||
@@ -497,29 +485,22 @@ void CNServer::start() {
|
||||
if (fds[i].revents & ~POLLIN)
|
||||
cSock->kill();
|
||||
|
||||
if (cSock->isAlive())
|
||||
if (cSock->isAlive()) {
|
||||
cSock->step();
|
||||
} else {
|
||||
killConnection(cSock);
|
||||
connections.erase(fds[i].fd);
|
||||
delete cSock;
|
||||
|
||||
removePollFD(i);
|
||||
|
||||
// a new entry was moved to this position, so we check it again
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onStep();
|
||||
|
||||
// clean up dead connection sockets
|
||||
auto it = connections.begin();
|
||||
while (it != connections.end()) {
|
||||
CNSocket *cSock = it->second;
|
||||
|
||||
if (!cSock->isAlive()) {
|
||||
killConnection(cSock);
|
||||
it = connections.erase(it);
|
||||
|
||||
removePollFD(cSock->sock);
|
||||
|
||||
delete cSock;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
|
||||
|
||||
// overflow-safe validation of variable-length packets
|
||||
// for outbound packets
|
||||
inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
|
||||
inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
@@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t pl
|
||||
}
|
||||
|
||||
// for inbound packets
|
||||
inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
|
||||
inline constexpr bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
@@ -230,7 +230,6 @@ protected:
|
||||
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
||||
std::vector<PollFD> fds;
|
||||
|
||||
std::string serverType = "invalid";
|
||||
SOCKET sock;
|
||||
uint16_t port;
|
||||
socklen_t addressSize;
|
||||
|
@@ -1,45 +1,27 @@
|
||||
#include "core/CNShared.hpp"
|
||||
|
||||
static std::unordered_map<int64_t, LoginMetadata*> logins;
|
||||
static std::mutex mtx;
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
std::map<int64_t, Player> CNSharedData::players;
|
||||
std::mutex playerCrit;
|
||||
|
||||
void CNShared::storeLoginMetadata(int64_t sk, LoginMetadata *lm) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
void CNSharedData::setPlayer(int64_t sk, Player& plr) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
// take ownership of connection data
|
||||
logins[sk] = lm;
|
||||
players[sk] = plr;
|
||||
}
|
||||
|
||||
LoginMetadata* CNShared::getLoginMetadata(int64_t sk) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
Player CNSharedData::getPlayer(int64_t sk) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
// fail if the key isn't found
|
||||
if (logins.find(sk) == logins.end())
|
||||
return nullptr;
|
||||
|
||||
// transfer ownership of connection data to shard
|
||||
LoginMetadata *lm = logins[sk];
|
||||
logins.erase(sk);
|
||||
|
||||
return lm;
|
||||
return players[sk];
|
||||
}
|
||||
|
||||
void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
void CNSharedData::erasePlayer(int64_t sk) {
|
||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
||||
|
||||
auto it = logins.begin();
|
||||
while (it != logins.end()) {
|
||||
auto& sk = it->first;
|
||||
auto& lm = it->second;
|
||||
|
||||
if (currTime > lm->timestamp + CNSHARED_TIMEOUT) {
|
||||
std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
|
||||
|
||||
// deallocate object and remove map entry
|
||||
delete logins[sk];
|
||||
it = logins.erase(it); // skip the invalidated iterator
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
players.erase(sk);
|
||||
}
|
||||
|
@@ -10,20 +10,11 @@
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
/*
|
||||
* Connecions time out after 5 minutes, checked every 30 seconds.
|
||||
*/
|
||||
#define CNSHARED_TIMEOUT 300000
|
||||
#define CNSHARED_PERIOD 30000
|
||||
namespace CNSharedData {
|
||||
// serialkey corresponds to player data
|
||||
extern std::map<int64_t, Player> players;
|
||||
|
||||
struct LoginMetadata {
|
||||
uint64_t FEKey;
|
||||
int32_t playerId;
|
||||
time_t timestamp;
|
||||
};
|
||||
|
||||
namespace CNShared {
|
||||
void storeLoginMetadata(int64_t sk, LoginMetadata *lm);
|
||||
LoginMetadata* getLoginMetadata(int64_t sk);
|
||||
void pruneLoginMetadata(CNServer *serv, time_t currTime);
|
||||
void setPlayer(int64_t sk, Player& plr);
|
||||
Player getPlayer(int64_t sk);
|
||||
void erasePlayer(int64_t sk);
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <tuple>
|
||||
|
||||
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
|
||||
#define INITSTRUCT(T, x) T x; \
|
||||
@@ -42,6 +41,9 @@
|
||||
#define ARRLEN(x) (sizeof(x)/sizeof(*x))
|
||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||
|
||||
// typedef for chunk position tuple
|
||||
typedef std::tuple<int, int, uint64_t> ChunkPos;
|
||||
|
||||
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
||||
|
||||
std::string U16toU8(char16_t* src, size_t max);
|
||||
@@ -50,15 +52,13 @@ time_t getTime();
|
||||
time_t getTimestamp();
|
||||
void terminate(int);
|
||||
|
||||
// The PROTOCOL_VERSION definition can be defined by the build system.
|
||||
// The PROTOCOL_VERSION definition is defined by the build system.
|
||||
#if !defined(PROTOCOL_VERSION)
|
||||
#define PROTOCOL_VERSION 104
|
||||
#endif
|
||||
|
||||
#if PROTOCOL_VERSION == 104
|
||||
#include "structs/0104.hpp"
|
||||
#elif PROTOCOL_VERSION == 728
|
||||
#include "structs/0728.hpp"
|
||||
#elif PROTOCOL_VERSION == 104
|
||||
#include "structs/0104.hpp"
|
||||
#elif PROTOCOL_VERSION == 1013
|
||||
#include "structs/1013.hpp"
|
||||
#else
|
||||
|
@@ -10,44 +10,63 @@ const float CN_EP_RANK_4 = 0.3f;
|
||||
const float CN_EP_RANK_5 = 0.29f;
|
||||
|
||||
// methods of finding players for GM commands
|
||||
enum class eCN_GM_TargetSearchBy {
|
||||
PC_ID, // player id
|
||||
PC_Name, // firstname, lastname
|
||||
PC_UID // account id
|
||||
enum eCN_GM_TargetSearchBy {
|
||||
eCN_GM_TargetSearchBy__PC_ID, // player id
|
||||
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname
|
||||
eCN_GM_TargetSearchBy__PC_UID // account id
|
||||
};
|
||||
|
||||
enum class eCN_GM_TeleportType {
|
||||
XYZ,
|
||||
MapXYZ,
|
||||
MyLocation,
|
||||
SomeoneLocation,
|
||||
Unstick
|
||||
enum eCN_GM_TeleportType {
|
||||
eCN_GM_TeleportMapType__XYZ,
|
||||
eCN_GM_TeleportMapType__MapXYZ,
|
||||
eCN_GM_TeleportMapType__MyLocation,
|
||||
eCN_GM_TeleportMapType__SomeoneLocation,
|
||||
eCN_GM_TeleportMapType__Unstick
|
||||
};
|
||||
|
||||
enum class eTaskTypeProperty {
|
||||
None = -1,
|
||||
Talk = 1,
|
||||
GotoLocation = 2,
|
||||
UseItems = 3,
|
||||
Delivery = 4,
|
||||
Defeat = 5,
|
||||
EscortDefence = 6,
|
||||
Max = 7
|
||||
};
|
||||
|
||||
enum class ePCRegenType {
|
||||
None,
|
||||
Xcom,
|
||||
Here,
|
||||
HereByPhoenix,
|
||||
HereByPhoenixGroup,
|
||||
Unstick,
|
||||
HereByPhoenixItem,
|
||||
End
|
||||
};
|
||||
|
||||
// nano power flags
|
||||
// nano powers
|
||||
enum {
|
||||
EST_NONE = 0,
|
||||
EST_DAMAGE = 1,
|
||||
EST_HEAL_HP = 2,
|
||||
EST_KNOCKDOWN = 3,
|
||||
EST_SLEEP = 4,
|
||||
EST_SNARE = 5,
|
||||
EST_HEAL_STAMINA = 6,
|
||||
EST_STAMINA_SELF = 7,
|
||||
EST_STUN = 8,
|
||||
EST_WEAPONSLOW = 9,
|
||||
EST_JUMP = 10,
|
||||
EST_RUN = 11,
|
||||
EST_STEALTH = 12,
|
||||
EST_SWIM = 13,
|
||||
EST_MINIMAPENEMY = 14,
|
||||
EST_MINIMAPTRESURE = 15,
|
||||
EST_PHOENIX = 16,
|
||||
EST_PROTECTBATTERY = 17,
|
||||
EST_PROTECTINFECTION = 18,
|
||||
EST_REWARDBLOB = 19,
|
||||
EST_REWARDCASH = 20,
|
||||
EST_BATTERYDRAIN = 21,
|
||||
EST_CORRUPTIONATTACK = 22,
|
||||
EST_INFECTIONDAMAGE = 23,
|
||||
EST_KNOCKBACK = 24,
|
||||
EST_FREEDOM = 25,
|
||||
EST_PHOENIX_GROUP = 26,
|
||||
EST_RECALL = 27,
|
||||
EST_RECALL_GROUP = 28,
|
||||
EST_RETROROCKET_SELF = 29,
|
||||
EST_BLOODSUCKING = 30,
|
||||
EST_BOUNDINGBALL = 31,
|
||||
EST_INVULNERABLE = 32,
|
||||
EST_NANOSTIMPAK = 33,
|
||||
EST_RETURNHOMEHEAL = 34,
|
||||
EST_BUFFHEAL = 35,
|
||||
EST_EXTRABANK = 36,
|
||||
EST__END = 37,
|
||||
EST_CORRUPTIONATTACKWIN = 38,
|
||||
EST_CORRUPTIONATTACKLOSE = 39,
|
||||
|
||||
ECSB_NONE = 0,
|
||||
ECSB_UP_MOVE_SPEED = 1,
|
||||
ECSB_UP_SWIM_SPEED = 2,
|
||||
@@ -77,27 +96,6 @@ enum {
|
||||
ECSTB__END = 26,
|
||||
};
|
||||
|
||||
enum {
|
||||
ETBU_NONE = 0,
|
||||
ETBU_ADD = 1,
|
||||
ETBU_DEL = 2,
|
||||
ETBU_CHANGE = 3,
|
||||
ETBU__END = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
ETBT_NONE = 0,
|
||||
ETBT_NANO = 1,
|
||||
ETBT_GROUPNANO = 2,
|
||||
ETBT_SHINY = 3,
|
||||
ETBT_LANDEFFECT = 4,
|
||||
ETBT_ITEM = 5,
|
||||
ETBT_CASHITEM = 6,
|
||||
ETBT__END = 7,
|
||||
ETBT_SKILL = 1,
|
||||
ETBT_GROUPSKILL = 2
|
||||
};
|
||||
|
||||
enum {
|
||||
SUCC = 1,
|
||||
FAIL = 0,
|
||||
@@ -927,10 +925,10 @@ enum {
|
||||
* Each is the last packet - the upper bits + 1
|
||||
*/
|
||||
enum {
|
||||
N_CL2LS = 0xf,
|
||||
N_CL2FE = 0xa5,
|
||||
N_FE2CL = 0x12f,
|
||||
N_LS2CL = 0x1a,
|
||||
N_CL2LS = 0xf,
|
||||
N_CL2FE = 0xa5,
|
||||
N_FE2CL = 0x12f,
|
||||
N_LS2CL = 0x1a,
|
||||
|
||||
N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL
|
||||
N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL
|
||||
};
|
||||
|
@@ -47,8 +47,8 @@ struct PacketDesc {
|
||||
* really should.
|
||||
*/
|
||||
struct sGM_PVPTarget {
|
||||
uint32_t iID;
|
||||
uint32_t eCT;
|
||||
uint32_t iID;
|
||||
};
|
||||
|
||||
struct sSkillResult_Leech {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define DATABASE_VERSION 4
|
||||
#define DATABASE_VERSION 3
|
||||
|
||||
namespace Database {
|
||||
|
||||
@@ -41,24 +41,18 @@ namespace Database {
|
||||
uint64_t Timestamp;
|
||||
};
|
||||
|
||||
void init();
|
||||
void open();
|
||||
void close();
|
||||
|
||||
void findAccount(Account* account, std::string login);
|
||||
|
||||
// return ID, 0 if something failed
|
||||
int getAccountIdForPlayer(int playerId);
|
||||
// returns ID, 0 if something failed
|
||||
int addAccount(std::string login, std::string password);
|
||||
|
||||
void updateAccountLevel(int accountId, int accountLevel);
|
||||
|
||||
// interface for the /ban command
|
||||
bool banPlayer(int playerId, std::string& reason);
|
||||
bool unbanPlayer(int playerId);
|
||||
|
||||
void updateSelected(int accountId, int slot);
|
||||
void updateSelectedByPlayerId(int accountId, int playerId);
|
||||
void updateSelected(int accountId, int playerId);
|
||||
|
||||
bool validateCharacter(int characterID, int userID);
|
||||
bool isNameFree(std::string firstName, std::string lastName);
|
||||
@@ -84,9 +78,7 @@ namespace Database {
|
||||
|
||||
// getting players
|
||||
void getPlayer(Player* plr, int id);
|
||||
bool _updatePlayer(Player *player);
|
||||
void updatePlayer(Player *player);
|
||||
void commitTrade(Player *plr1, Player *plr2);
|
||||
|
||||
// buddies
|
||||
int getNumBuddies(Player* player);
|
||||
@@ -106,7 +98,7 @@ namespace Database {
|
||||
void deleteEmailAttachments(int playerID, int index, int slot);
|
||||
void deleteEmails(int playerID, int64_t* indices);
|
||||
int getNextEmailIndex(int playerID);
|
||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender);
|
||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
|
||||
|
||||
// racing
|
||||
RaceRanking getTopRaceRanking(int epID, int playerID);
|
||||
|
@@ -152,8 +152,7 @@ void Database::updateEmailContent(EmailData* data) {
|
||||
sqlite3_step(stmt);
|
||||
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
||||
|
||||
// set attachment flag dynamically
|
||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
|
||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
@@ -266,7 +265,7 @@ int Database::getNextEmailIndex(int playerID) {
|
||||
return (index > 0 ? index + 1 : 1);
|
||||
}
|
||||
|
||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) {
|
||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
@@ -331,13 +330,6 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!_updatePlayer(sender)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
@@ -9,37 +9,6 @@
|
||||
std::mutex dbCrit;
|
||||
sqlite3 *db;
|
||||
|
||||
/*
|
||||
* When migrating from DB version 3 to 4, we change the username column
|
||||
* to be case-insensitive. This function ensures there aren't any
|
||||
* duplicates, e.g. username and USERNAME, before doing the migration.
|
||||
* I handled this in the code itself rather than the migration file just so
|
||||
* we can have a more detailed error message than what SQLite provides.
|
||||
*/
|
||||
static void checkCaseSensitiveDupes() {
|
||||
const char* sql = "SELECT Login, COUNT(*) FROM Accounts GROUP BY LOWER(Login) HAVING COUNT(*) > 1;";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
int stat = sqlite3_step(stmt);
|
||||
|
||||
if (stat == SQLITE_DONE) {
|
||||
// no rows returned, so we're good
|
||||
sqlite3_finalize(stmt);
|
||||
return;
|
||||
} else if (stat != SQLITE_ROW) {
|
||||
std::cout << "[FATAL] Failed to check for duplicate accounts: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::cout << "[FATAL] Case-sensitive duplicates detected in the Login column." << std::endl;
|
||||
std::cout << "Either manually delete/rename the offending accounts, or run the pruning script:" << std::endl;
|
||||
std::cout << "https://github.com/OpenFusionProject/scripts/tree/main/db_migration/caseinsens.py" << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void createMetaTable() {
|
||||
std::lock_guard<std::mutex> lock(dbCrit); // XXX
|
||||
|
||||
@@ -99,7 +68,7 @@ static void checkMetaTable() {
|
||||
sqlite3_stmt* stmt;
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||
std::cout << "[FATAL] Failed to check meta table: " << sqlite3_errmsg(db) << std::endl;
|
||||
std::cout << "[FATAL] Failed to check meta table" << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
exit(1);
|
||||
}
|
||||
@@ -174,10 +143,6 @@ static void checkMetaTable() {
|
||||
}
|
||||
|
||||
while (dbVersion != DATABASE_VERSION) {
|
||||
// need to run this before we do any migration logic
|
||||
if (dbVersion == 3)
|
||||
checkCaseSensitiveDupes();
|
||||
|
||||
// db migrations
|
||||
std::cout << "[INFO] Migrating Database to Version " << dbVersion + 1 << std::endl;
|
||||
|
||||
@@ -226,31 +191,17 @@ static void createTables() {
|
||||
static int getTableSize(std::string tableName) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit); // XXX
|
||||
|
||||
// you aren't allowed to bind the table name
|
||||
const char* sql = "SELECT COUNT(*) FROM ";
|
||||
tableName.insert(0, sql);
|
||||
const char* sql = "SELECT COUNT(*) FROM ?";
|
||||
sqlite3_stmt* stmt;
|
||||
sqlite3_prepare_v2(db, tableName.c_str(), -1, &stmt, NULL);
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_text(stmt, 1, tableName.c_str(), -1, NULL);
|
||||
sqlite3_step(stmt);
|
||||
int result = sqlite3_column_int(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Database::init() {
|
||||
std::cout << "[INFO] Built with libsqlite " SQLITE_VERSION << std::endl;
|
||||
|
||||
if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER)
|
||||
std::cout << "[INFO] Using libsqlite " << std::string(sqlite3_libversion()) << std::endl;
|
||||
|
||||
if (sqlite3_libversion_number() < MIN_SUPPORTED_SQLITE_NUMBER) {
|
||||
std::cerr << "[FATAL] Runtime sqlite version too old. Minimum compatible version: " MIN_SUPPORTED_SQLITE << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Database::open() {
|
||||
|
||||
// XXX: move locks here
|
||||
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
||||
if (rc != SQLITE_OK) {
|
||||
@@ -267,17 +218,17 @@ void Database::open() {
|
||||
checkMetaTable();
|
||||
createTables();
|
||||
|
||||
std::cout << "[INFO] Database in operation";
|
||||
std::cout << "[INFO] Database in operation ";
|
||||
int accounts = getTableSize("Accounts");
|
||||
int players = getTableSize("Players");
|
||||
std::string message = "";
|
||||
if (accounts > 0) {
|
||||
message += ": Found " + std::to_string(accounts) + " account";
|
||||
message += ": Found " + std::to_string(accounts) + " Account";
|
||||
if (accounts > 1)
|
||||
message += "s";
|
||||
}
|
||||
if (players > 0) {
|
||||
message += " and " + std::to_string(players) + " player";
|
||||
message += " and " + std::to_string(players) + " Player Character";
|
||||
if (players > 1)
|
||||
message += "s";
|
||||
}
|
||||
|
@@ -3,13 +3,10 @@
|
||||
#include "db/Database.hpp"
|
||||
#include <sqlite3.h>
|
||||
|
||||
#define MIN_SUPPORTED_SQLITE_NUMBER 3033000
|
||||
#define MIN_SUPPORTED_SQLITE "3.33.0"
|
||||
// we can't use this in #error, since it doesn't expand macros
|
||||
|
||||
// Compile-time libsqlite version check
|
||||
#if SQLITE_VERSION_NUMBER < MIN_SUPPORTED_SQLITE_NUMBER
|
||||
#error libsqlite version too old. Minimum compatible version: 3.33.0
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
extern std::mutex dbCrit;
|
||||
|
@@ -27,32 +27,6 @@ void Database::findAccount(Account* account, std::string login) {
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
int Database::getAccountIdForPlayer(int playerId) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
SELECT AccountID
|
||||
FROM Players
|
||||
WHERE PlayerID = ?
|
||||
LIMIT 1;
|
||||
)";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, playerId);
|
||||
|
||||
int rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW) {
|
||||
std::cout << "[WARN] Database: couldn't get account id for player " << playerId << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int accountId = sqlite3_column_int(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
return accountId;
|
||||
}
|
||||
|
||||
int Database::addAccount(std::string login, std::string password) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
@@ -78,26 +52,6 @@ int Database::addAccount(std::string login, std::string password) {
|
||||
return sqlite3_last_insert_rowid(db);
|
||||
}
|
||||
|
||||
void Database::updateAccountLevel(int accountId, int accountLevel) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
UPDATE Accounts SET
|
||||
AccountLevel = ?
|
||||
WHERE AccountID = ?;
|
||||
)";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, accountLevel);
|
||||
sqlite3_bind_int(stmt, 2, accountId);
|
||||
|
||||
int rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE)
|
||||
std::cout << "[WARN] Database fail on updateAccountLevel(): " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
void Database::updateSelected(int accountId, int slot) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
@@ -125,29 +79,6 @@ void Database::updateSelected(int accountId, int slot) {
|
||||
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
||||
}
|
||||
|
||||
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
UPDATE Accounts SET
|
||||
Selected = p.Slot,
|
||||
LastLogin = (strftime('%s', 'now'))
|
||||
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
|
||||
WHERE AccountID = ?;
|
||||
)";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, playerId);
|
||||
sqlite3_bind_int(stmt, 2, accountId);
|
||||
int rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE)
|
||||
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
|
||||
}
|
||||
|
||||
bool Database::validateCharacter(int characterID, int userID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
|
@@ -285,13 +285,11 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Low-level function to save a player to DB.
|
||||
* Must be run in a SQL transaction and with dbCrit locked.
|
||||
* The caller manages the transacstion, so if this function returns false,
|
||||
* the caller must roll it back.
|
||||
*/
|
||||
bool Database::_updatePlayer(Player *player) {
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
const char* sql = R"(
|
||||
UPDATE Players
|
||||
SET
|
||||
@@ -338,8 +336,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 21, player->iID);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
@@ -375,8 +375,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -393,8 +395,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -411,8 +415,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -445,8 +451,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -479,8 +487,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -514,47 +524,14 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
if (!_updatePlayer(player)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void Database::commitTrade(Player *plr1, Player *plr2) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
if (!_updatePlayer(plr1)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_updatePlayer(plr2)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
@@ -208,8 +208,6 @@ void Database::addBlock(int playerId, int blockedPlayerId) {
|
||||
}
|
||||
|
||||
void Database::removeBlock(int playerId, int blockedPlayerId) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
DELETE FROM Blocks
|
||||
WHERE PlayerID = ? AND BlockedPlayerID = ?;
|
||||
|
174
src/lua/EntityWrapper.cpp
Normal file
174
src/lua/EntityWrapper.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/EntityWrapper.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
|
||||
#define LIBNAME "Entity"
|
||||
#define SUPERTBL "__entSUPERCLASSES"
|
||||
|
||||
#define ENTYGONESTR "Entity doesn't exist anymore!"
|
||||
|
||||
EntityRef* grabBaseEntityRef(lua_State *state, int indx) {
|
||||
// first, make sure its a userdata
|
||||
luaL_checktype(state, indx, LUA_TUSERDATA);
|
||||
|
||||
// grab the super class table
|
||||
lua_getfield(state, LUA_REGISTRYINDEX, SUPERTBL);
|
||||
|
||||
// grab the userdata
|
||||
EntityRef *data = (EntityRef*)lua_touserdata(state, indx);
|
||||
|
||||
// check if it doesn't have a metatable
|
||||
if (!lua_getmetatable(state, indx)) {
|
||||
luaL_typerror(state, indx, LIBNAME);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// index the super class table
|
||||
lua_gettable(state, -2);
|
||||
|
||||
// if the index was nil, it doesn't exist
|
||||
if (lua_isnil(state, -1)) {
|
||||
lua_pop(state, 1);
|
||||
luaL_typerror(state, indx, LIBNAME);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// it's good :)
|
||||
lua_pop(state, 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
// check at index
|
||||
EntityRef* grabEntityRef(lua_State *state, int indx) {
|
||||
EntityRef *ref = grabBaseEntityRef(state, indx);
|
||||
|
||||
if (ref == NULL || !ref->isValid())
|
||||
return NULL;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
// =============================================== [[ GETTERS ]] ===============================================
|
||||
|
||||
static int ent_getX(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushnumber(state, ref->getEntity()->x);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ent_getY(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushnumber(state, ref->getEntity()->y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ent_getZ(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushnumber(state, ref->getEntity()->z);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ent_getType(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
// push the type string
|
||||
switch(ref->type) {
|
||||
case EntityType::PLAYER:
|
||||
lua_pushstring(state, "Player");
|
||||
break;
|
||||
case EntityType::MOB:
|
||||
lua_pushstring(state, "Mob");
|
||||
break;
|
||||
case EntityType::EGG:
|
||||
lua_pushstring(state, "Egg");
|
||||
break;
|
||||
case EntityType::BUS:
|
||||
lua_pushstring(state, "Bus");
|
||||
break;
|
||||
default: // INVALID, COMBAT_NPC, SIMPLE_NPC
|
||||
lua_pushstring(state, "Entity");
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static luaL_Reg ent_getters[] = {
|
||||
{"x", ent_getX},
|
||||
{"y", ent_getY},
|
||||
{"z", ent_getZ},
|
||||
{"type", ent_getType},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// =============================================== [[ METHODS ]] ===============================================
|
||||
|
||||
static int ent_exists(lua_State *state) {
|
||||
EntityRef *data = (EntityRef*)grabBaseEntityRef(state, 1);
|
||||
|
||||
lua_pushboolean(state, !(data == NULL || !data->isValid()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static luaL_Reg ent_methods[] = {
|
||||
{"exists", ent_exists},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
void LuaManager::Entity::init(lua_State *state) {
|
||||
// register our library as a global (and leave it on the stack)
|
||||
luaL_register(state, LIBNAME, ent_methods);
|
||||
lua_pop(state, 1); // pop library table
|
||||
|
||||
// will hold our super classes
|
||||
lua_pushstring(state, SUPERTBL);
|
||||
lua_newtable(state);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
void LuaManager::Entity::registerSuper(lua_State *state, const char *tname) {
|
||||
// grab the super class table
|
||||
lua_getfield(state, LUA_REGISTRYINDEX, SUPERTBL);
|
||||
|
||||
// grab the metatable
|
||||
lua_getfield(state, LUA_REGISTRYINDEX, tname);
|
||||
lua_pushboolean(state, 1);
|
||||
|
||||
// finally, set the index
|
||||
lua_rawset(state, -3);
|
||||
lua_pop(state, 1); // pop the super class table
|
||||
}
|
||||
|
||||
void LuaManager::Entity::addGetters(lua_State *state) {
|
||||
luaL_register(state, NULL, ent_getters);
|
||||
}
|
||||
|
||||
void LuaManager::Entity::addMethods(lua_State *state) {
|
||||
luaL_register(state, NULL, ent_methods);
|
||||
}
|
||||
|
||||
void LuaManager::Entity::push(lua_State *state, EntityRef ref, const char *tname) {
|
||||
// creates the udata and copies the reference to the udata
|
||||
EntityRef *ent = (EntityRef*)lua_newuserdata(state, sizeof(EntityRef));
|
||||
*ent = ref;
|
||||
|
||||
// attaches our metatable from the registry to the udata
|
||||
luaL_getmetatable(state, tname);
|
||||
lua_setmetatable(state, -2);
|
||||
}
|
19
src/lua/EntityWrapper.hpp
Normal file
19
src/lua/EntityWrapper.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
namespace Entity {
|
||||
void init(lua_State *state);
|
||||
|
||||
void registerSuper(lua_State *state, const char *tname);
|
||||
|
||||
void addGetters(lua_State *state);
|
||||
void addMethods(lua_State *state);
|
||||
|
||||
void push(lua_State *state, EntityRef ref, const char *tname);
|
||||
}
|
||||
}
|
190
src/lua/EventWrapper.cpp
Normal file
190
src/lua/EventWrapper.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
This loads the Event library, basically a wrapper for lEvents, allows the lua script to register callbacks or wait for an event to be fired
|
||||
*/
|
||||
|
||||
#include "lua/EventWrapper.hpp"
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#define LIBNAME "Event"
|
||||
#define LISTNR "Listener"
|
||||
|
||||
typedef lEvent* eventData;
|
||||
|
||||
std::unordered_set<lEvent*> activeEvents;
|
||||
|
||||
struct lstnrData {
|
||||
lEvent *event;
|
||||
uint32_t rawListener;
|
||||
};
|
||||
|
||||
static void pushListener(lua_State *state, lEvent *event, uint32_t listener) {
|
||||
lstnrData *lstnr = (lstnrData*)lua_newuserdata(state, sizeof(lstnrData));
|
||||
lstnr->event = event;
|
||||
lstnr->rawListener = listener;
|
||||
|
||||
// attaches our metatable from the registry to the udata
|
||||
luaL_getmetatable(state, LISTNR);
|
||||
lua_setmetatable(state, -2);
|
||||
}
|
||||
|
||||
static void pushEvent(lua_State *state, lEvent *event) {
|
||||
eventData *eData = (eventData*)lua_newuserdata(state, sizeof(eventData));
|
||||
*eData = event;
|
||||
|
||||
// attaches our metatable from the registry to the udata
|
||||
luaL_getmetatable(state, LIBNAME);
|
||||
lua_setmetatable(state, -2);
|
||||
}
|
||||
|
||||
static lEvent *grabEvent(lua_State *state, int indx) {
|
||||
// first, make sure its a userdata
|
||||
luaL_checktype(state, indx, LUA_TUSERDATA);
|
||||
|
||||
// now, check and make sure its our libraries metatable attached to this userdata
|
||||
eventData *event = (eventData*)luaL_checkudata(state, indx, LIBNAME);
|
||||
if (event == NULL) {
|
||||
luaL_typerror(state, indx, LIBNAME);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error)
|
||||
if (activeEvents.find(*event) == activeEvents.end())
|
||||
return NULL;
|
||||
|
||||
// return the pointer to the lEvent
|
||||
return *event;
|
||||
}
|
||||
|
||||
static lstnrData *grabListener(lua_State *state, int indx) {
|
||||
// first, make sure its a userdata
|
||||
luaL_checktype(state, indx, LUA_TUSERDATA);
|
||||
|
||||
// now, check and make sure its our libraries metatable attached to this userdata
|
||||
lstnrData *lstnr = (lstnrData*)luaL_checkudata(state, indx, LISTNR);
|
||||
if (lstnr == NULL) {
|
||||
luaL_typerror(state, indx, LISTNR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error)
|
||||
if (activeEvents.find(lstnr->event) == activeEvents.end())
|
||||
return NULL;
|
||||
|
||||
// return the pointer to the lEvent
|
||||
return lstnr;
|
||||
}
|
||||
|
||||
// connects a callback to an event
|
||||
static int evnt_listen(lua_State *state) {
|
||||
int nargs = lua_gettop(state);
|
||||
lEvent *event = grabEvent(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (event == NULL)
|
||||
return 0;
|
||||
|
||||
// for each argument passed, check that it's a function and add it to the event
|
||||
for (int i = 2; i <= nargs; i++) {
|
||||
luaL_checktype(state, i, LUA_TFUNCTION);
|
||||
lua_pushvalue(state, i);
|
||||
pushListener(state, event, event->addCallback(state, luaL_ref(state, LUA_REGISTRYINDEX)));
|
||||
}
|
||||
|
||||
// we return this many listeners
|
||||
return nargs - 1;
|
||||
}
|
||||
|
||||
// yields the thread until the event is triggered
|
||||
static int evnt_wait(lua_State *state) {
|
||||
lEvent *event = grabEvent(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (event == NULL)
|
||||
return 0;
|
||||
|
||||
event->addWait(state);
|
||||
return lua_yield(state, 0);
|
||||
}
|
||||
|
||||
static int lstnr_disconnect(lua_State *state) {
|
||||
lstnrData *lstnr = grabListener(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (lstnr == NULL)
|
||||
return 0;
|
||||
|
||||
// disconnect the event
|
||||
lstnr->event->disconnectEvent(lstnr->rawListener);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lstnr_reconnect(lua_State *state) {
|
||||
lstnrData *lstnr = grabListener(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (lstnr == NULL)
|
||||
return 0;
|
||||
|
||||
// disconnect the event
|
||||
lstnr->event->reconnectEvent(lstnr->rawListener);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lstnr_gc(lua_State *state) {
|
||||
lstnrData *lstnr = grabListener(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (lstnr == NULL)
|
||||
return 0;
|
||||
|
||||
// if the listener is disabled, clear it
|
||||
if (lstnr->event->isDisabled(lstnr->rawListener))
|
||||
lstnr->event->clear(lstnr->rawListener);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg evnt_methods[] = {
|
||||
{"listen", evnt_listen},
|
||||
{"wait", evnt_wait},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static luaL_Reg lstnr_methods[] = {
|
||||
{"disconnect", lstnr_disconnect},
|
||||
{"reconnect", lstnr_reconnect},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
void LuaManager::Event::init(lua_State *state) {
|
||||
// register the library & pop it
|
||||
luaL_register(state, LIBNAME, evnt_methods);
|
||||
|
||||
// create the event metatable
|
||||
luaL_newmetatable(state, LIBNAME);
|
||||
lua_pushstring(state, "__index");
|
||||
lua_pushvalue(state, -3);
|
||||
lua_rawset(state, -3);
|
||||
|
||||
// create the listener metatable
|
||||
luaL_newmetatable(state, LISTNR);
|
||||
lua_pushstring(state, "__index");
|
||||
lua_newtable(state);
|
||||
luaL_register(state, NULL, lstnr_methods);
|
||||
lua_rawset(state, -3);
|
||||
lua_pushstring(state, "__gc");
|
||||
lua_pushcfunction(state, lstnr_gc);
|
||||
lua_rawset(state, -3);
|
||||
|
||||
// pop the tables off the stack
|
||||
lua_pop(state, 3);
|
||||
|
||||
activeEvents = std::unordered_set<lEvent*>();
|
||||
}
|
||||
|
||||
// just a wrapper for pushEvent()
|
||||
void LuaManager::Event::push(lua_State *state, lEvent *event) {
|
||||
pushEvent(state, event);
|
||||
}
|
10
src/lua/EventWrapper.hpp
Normal file
10
src/lua/EventWrapper.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
namespace Event {
|
||||
void init(lua_State *state);
|
||||
|
||||
void push(lua_State *state, lEvent *event);
|
||||
}
|
||||
}
|
185
src/lua/LuaManager.cpp
Normal file
185
src/lua/LuaManager.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/EventWrapper.hpp"
|
||||
#include "lua/WorldWrapper.hpp"
|
||||
#include "lua/PlayerWrapper.hpp"
|
||||
#include "lua/NPCWrapper.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
time_t getTime();
|
||||
class Script;
|
||||
|
||||
// our "main" state, holds our environment
|
||||
lua_State *LuaManager::global;
|
||||
std::map<lua_State*, Script*> activeScripts;
|
||||
|
||||
/*
|
||||
Basically each script is treated as a coroutine, when wait() is called it gets yielded and is pushed onto the scheduler queue to be resumed.
|
||||
*/
|
||||
class Script {
|
||||
private:
|
||||
lua_State *thread;
|
||||
lRegistry threadRef; // we'll need to unref this when closing this state
|
||||
|
||||
public:
|
||||
Script(std::string source) {
|
||||
// make the thread & register it in the registry
|
||||
thread = lua_newthread(LuaManager::global);
|
||||
threadRef = luaL_ref(LuaManager::global, LUA_REGISTRYINDEX);
|
||||
|
||||
// add this script to the map
|
||||
activeScripts[thread] = this;
|
||||
|
||||
// compile & run the script, if it error'd, print the error
|
||||
int _retCode;
|
||||
if (luaL_loadfile(thread, source.c_str()) || ((_retCode = lua_resume(thread, 0)) != 0 && (_retCode != LUA_YIELD))) {
|
||||
std::cout << "[LUA ERROR]: " << lua_tostring(thread, -1) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// unregister all of our events from the wrappers
|
||||
~Script() {
|
||||
LuaManager::clearState(thread);
|
||||
|
||||
// remove it from the global registry
|
||||
luaL_unref(LuaManager::global, LUA_REGISTRYINDEX, threadRef);
|
||||
}
|
||||
|
||||
// c++ moment....
|
||||
lua_State* getState() {
|
||||
return thread;
|
||||
}
|
||||
};
|
||||
|
||||
struct scheduledThread {
|
||||
lRegistry ref; // ref that should be unref'd before resuming
|
||||
time_t time;
|
||||
};
|
||||
std::map<lua_State*, scheduledThread> scheduleQueue;
|
||||
|
||||
// pauses the script for x seconds, not very accurate but should be
|
||||
// called within ~60ms or less of when it was schedueled
|
||||
// will also return the time the thread was paused
|
||||
int OF_wait(lua_State *state) {
|
||||
double seconds = luaL_checknumber(state, 1);
|
||||
|
||||
// register the thread in the global registry so we don't get GC'd
|
||||
lua_pushthread(state);
|
||||
lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX); // threads decentant of a global state all share the global registry
|
||||
|
||||
// yield the state and push the state onto our scheduler queue
|
||||
scheduleQueue[state] = {ref, (int)(seconds*1000) + getTime()};
|
||||
return lua_yield(state, 0);
|
||||
}
|
||||
|
||||
void luaScheduler(CNServer *serv, time_t currtime) {
|
||||
for (auto iter = scheduleQueue.begin(); iter != scheduleQueue.end();) {
|
||||
time_t event = (*iter).second.time;
|
||||
lRegistry ref = (*iter).second.ref;
|
||||
lua_State *thread = (*iter).first;
|
||||
// is it time to run the event?
|
||||
if (event <= currtime) {
|
||||
// remove from the scheduler queue
|
||||
scheduleQueue.erase(iter++);
|
||||
|
||||
// unregister the thread
|
||||
luaL_unref(thread, LUA_REGISTRYINDEX, ref);
|
||||
|
||||
// resume the state, (wait() returns the delta time since call)
|
||||
lua_pushnumber(thread, ((double)currtime - event)/10);
|
||||
yieldCall(thread, 1);
|
||||
} else // go to the next iteration
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::printError(std::string err) {
|
||||
std::cerr << "[LUA ERR]: " << err << std::endl;
|
||||
}
|
||||
|
||||
void LuaManager::init() {
|
||||
// allocate our state
|
||||
global = luaL_newstate();
|
||||
|
||||
// open lua's base libraries (excluding the IO for now)
|
||||
luaopen_base(global);
|
||||
luaopen_table(global);
|
||||
luaopen_string(global);
|
||||
luaopen_math(global);
|
||||
luaopen_debug(global);
|
||||
|
||||
// add wait()
|
||||
lua_register(global, "wait", OF_wait);
|
||||
|
||||
// register our libraries
|
||||
Event::init(global);
|
||||
World::init(global);
|
||||
Entity::init(global);
|
||||
Player::init(global);
|
||||
NPC::init(global);
|
||||
|
||||
activeScripts = std::map<lua_State*, Script*>();
|
||||
|
||||
// we want to be called after every poll(), so our timer delta is set to 0
|
||||
REGISTER_SHARD_TIMER(luaScheduler, 0);
|
||||
|
||||
// load our scripts
|
||||
loadScripts();
|
||||
}
|
||||
|
||||
void LuaManager::runScript(std::string filename) {
|
||||
new Script(filename);
|
||||
}
|
||||
|
||||
void LuaManager::stopScripts() {
|
||||
// clear the scheduler queue
|
||||
scheduleQueue.clear();
|
||||
|
||||
// free all the scripts, they'll take care of everything for us :)
|
||||
for (auto as : activeScripts) {
|
||||
delete as.second;
|
||||
}
|
||||
|
||||
// finally clear the map
|
||||
activeScripts.clear();
|
||||
|
||||
// walk through each player and unregister each event
|
||||
for (auto pair: PlayerManager::players) {
|
||||
if (pair.second->onChat != nullptr) {
|
||||
delete pair.second->onChat;
|
||||
pair.second->onChat = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::loadScripts() {
|
||||
if (!std::filesystem::exists(settings::SCRIPTSDIR)) {
|
||||
std::cout << "[WARN] scripts directory \"" << settings::SCRIPTSDIR << "\" doesn't exist!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// for each file in the scripts director, load the script
|
||||
std::filesystem::path dir(settings::SCRIPTSDIR);
|
||||
for (auto &d : std::filesystem::directory_iterator(dir)) {
|
||||
if (d.path().extension().u8string() == ".lua")
|
||||
runScript(d.path().u8string());
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::clearState(lua_State *state) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void LuaManager::playerAdded(CNSocket *sock) {
|
||||
World::playerAdded(sock);
|
||||
}
|
||||
|
||||
void LuaManager::playerRemoved(CNSocket *sock) {
|
||||
World::playerRemoved(sock);
|
||||
}
|
28
src/lua/LuaManager.hpp
Normal file
28
src/lua/LuaManager.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/CNProtocol.hpp"
|
||||
|
||||
#include <string>
|
||||
#ifdef _MSC_VER
|
||||
#include <luajit/lua.hpp>
|
||||
#else
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
typedef int lRegistry;
|
||||
|
||||
namespace LuaManager {
|
||||
extern lua_State *global;
|
||||
void init();
|
||||
void printError(std::string err);
|
||||
|
||||
// runs the script in the passed file
|
||||
void runScript(std::string filename);
|
||||
void stopScripts();
|
||||
void loadScripts();
|
||||
|
||||
// unregisters the events tied to this state with all wrappers
|
||||
void clearState(lua_State *state);
|
||||
void playerAdded(CNSocket *sock);
|
||||
void playerRemoved(CNSocket *sock);
|
||||
}
|
223
src/lua/LuaWrapper.hpp
Normal file
223
src/lua/LuaWrapper.hpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#pragma once
|
||||
/*
|
||||
This is a very simple header that adds a small "event" layer, where scripts can register callbacks & "waits" on events. The actual
|
||||
Event metatables & library is in EventWrapper.[hc]pp
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <cassert>
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/PlayerWrapper.hpp"
|
||||
|
||||
#define yieldCall(state, nargs) \
|
||||
int _retCode = lua_resume(state, nargs); \
|
||||
if (_retCode != 0 && _retCode != LUA_YIELD) \
|
||||
LuaManager::printError(lua_tostring(state, -1)); \
|
||||
|
||||
inline static int lua_autoPush(lua_State* state, int nargs) {
|
||||
// return the number of pushed arguments :)
|
||||
return nargs;
|
||||
}
|
||||
|
||||
/*
|
||||
This function will automatically push all of the passed arguments onto the stack and returns the # of pushed values
|
||||
|
||||
Supported datatypes are:
|
||||
double or int : LUA_TNUMBER
|
||||
char* or const char* : LUA_TSTRING
|
||||
bool : LUA_TBOOLEAN
|
||||
lRegistry : grabs the object from the lua registry and pushes it onto the stack
|
||||
CNSocket* : Pushes the Player Entity
|
||||
*/
|
||||
template<typename T, class... Rest>
|
||||
inline static int lua_autoPush(lua_State* state, int nargs, T arg, Rest... rest) {
|
||||
// pick which branch to compile based on the type of arg
|
||||
if constexpr(std::is_same<T, int>::value || std::is_same<T, double>::value) {
|
||||
lua_pushnumber(state, (lua_Number)arg);
|
||||
} else if constexpr(std::is_same<T, char*>::value || std::is_same<T, const char*>::value) {
|
||||
lua_pushstring(state, (const char*)arg);
|
||||
} else if constexpr(std::is_same<T, lRegistry>::value) {
|
||||
// grab the value from the registry
|
||||
lua_rawgeti(state, LUA_REGISTRYINDEX, (int)arg);
|
||||
} else if constexpr(std::is_same<T, CNSocket*>::value) { // pushes a Player Entity
|
||||
LuaManager::Player::push(state, arg);
|
||||
} else if constexpr(std::is_same<T, bool>::value) {
|
||||
lua_pushboolean(state, arg);
|
||||
}
|
||||
|
||||
// recursively call, expanding rest and pushing the left-most rvalue into arg
|
||||
return lua_autoPush(state, ++nargs, rest...);
|
||||
}
|
||||
|
||||
enum eventType {
|
||||
EVENT_CALLBACK, // standard callback
|
||||
EVENT_WAIT // state needs to be resumed with the arguments
|
||||
};
|
||||
|
||||
class lEvent;
|
||||
|
||||
extern std::unordered_set<lEvent*> activeEvents;
|
||||
|
||||
class lEvent {
|
||||
private:
|
||||
struct rawEvent {
|
||||
lua_State *state;
|
||||
eventType type;
|
||||
lRegistry ref;
|
||||
bool disabled;
|
||||
};
|
||||
|
||||
uint32_t nextId; // next ID
|
||||
std::map<uint32_t, rawEvent> events;
|
||||
|
||||
uint32_t registerEvent(lua_State *state, lRegistry ref, eventType type) {
|
||||
uint32_t id = nextId++;
|
||||
|
||||
assert(nextId < UINT32_MAX);
|
||||
events[id] = {state, type, ref, false};
|
||||
return id;
|
||||
}
|
||||
|
||||
void freeEvent(rawEvent *event) {
|
||||
// the registry of all states is the same!
|
||||
luaL_unref(event->state, LUA_REGISTRYINDEX, event->ref);
|
||||
}
|
||||
|
||||
public:
|
||||
lEvent() {
|
||||
events = std::map<uint32_t, rawEvent>();
|
||||
nextId = 0; // start at 0
|
||||
activeEvents.insert(this);
|
||||
}
|
||||
|
||||
~lEvent() {
|
||||
// remove from the active set and disable all existing callbacks
|
||||
activeEvents.erase(this);
|
||||
clear();
|
||||
}
|
||||
|
||||
uint32_t addCallback(lua_State *state, lRegistry ref) {
|
||||
return registerEvent(state, ref, EVENT_CALLBACK);
|
||||
}
|
||||
|
||||
// yields the thread until the event is called
|
||||
uint32_t addWait(lua_State *state) {
|
||||
// push the thread onto the stack and register it in the global registry
|
||||
lua_pushthread(state);
|
||||
lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX);
|
||||
return registerEvent(state, ref, EVENT_WAIT);
|
||||
}
|
||||
|
||||
// walks through the events and unregister them from the state
|
||||
void clear() {
|
||||
for (auto &pair : events) {
|
||||
freeEvent(&pair.second);
|
||||
}
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
// free the event based on id
|
||||
void clear(uint32_t id) {
|
||||
auto iter = events.find(id);
|
||||
|
||||
// sanity check
|
||||
if (iter == events.end())
|
||||
return;
|
||||
|
||||
// free the event
|
||||
freeEvent(&(*iter).second);
|
||||
events.erase(iter);
|
||||
}
|
||||
|
||||
// frees all events to this state
|
||||
void clear(lua_State *state) {
|
||||
for (auto iter = events.begin(); iter != events.end();) {
|
||||
rawEvent *event = &(*iter).second;
|
||||
|
||||
// if this is our state, free this event and erase it from the map
|
||||
if (event->state == state) {
|
||||
freeEvent(event);
|
||||
events.erase(iter++);
|
||||
} else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void disconnectEvent(uint32_t id) {
|
||||
auto iter = events.find(id);
|
||||
|
||||
// sanity check
|
||||
if (iter == events.end())
|
||||
return;
|
||||
|
||||
(*iter).second.disabled = true;
|
||||
}
|
||||
|
||||
void reconnectEvent(uint32_t id) {
|
||||
auto iter = events.find(id);
|
||||
|
||||
// sanity check
|
||||
if (iter == events.end())
|
||||
return;
|
||||
|
||||
(*iter).second.disabled = false;
|
||||
}
|
||||
|
||||
bool isDisabled(uint32_t id) {
|
||||
auto iter = events.find(id);
|
||||
|
||||
// sanity check
|
||||
if (iter == events.end())
|
||||
return true;
|
||||
|
||||
return (*iter).second.disabled;
|
||||
}
|
||||
|
||||
template<class... Args> inline void call(Args... args) {
|
||||
auto eventsClone = events; // so if a callback is added, we don't iterate into undefined behavior
|
||||
for (auto &pair : eventsClone) {
|
||||
rawEvent *event = &pair.second;
|
||||
|
||||
// if the event is disabled, skip it
|
||||
if (event->disabled)
|
||||
continue;
|
||||
|
||||
switch (event->type) {
|
||||
case EVENT_CALLBACK: {
|
||||
// make thread for this callback
|
||||
if (!lua_checkstack(event->state, 1)) {
|
||||
std::cout << "[FATAL] Failed to create new lua thread! out of memory!" << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
lua_State *nThread = lua_newthread(event->state);
|
||||
|
||||
// push the callable first, the push all the arguments
|
||||
lua_rawgeti(nThread, LUA_REGISTRYINDEX, (int)event->ref);
|
||||
int nargs = lua_autoPush(nThread, 0, args...);
|
||||
|
||||
// then call it :)
|
||||
yieldCall(nThread, nargs);
|
||||
break;
|
||||
}
|
||||
case EVENT_WAIT: {
|
||||
// erase this event
|
||||
freeEvent(&pair.second);
|
||||
events.erase(pair.first);
|
||||
|
||||
// the :wait() will return the passed arguments
|
||||
int nargs = lua_autoPush(event->state, 0, args...);
|
||||
yieldCall(event->state, nargs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "[WARN] INVALID EVENT TYPE : " << event->type << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
266
src/lua/NPCWrapper.cpp
Normal file
266
src/lua/NPCWrapper.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/NPCWrapper.hpp"
|
||||
|
||||
#include "NPC.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "Transport.hpp"
|
||||
|
||||
#define LIBNAME "NPC"
|
||||
#define NPCGONESTR "NPC was destoryed and no longer exists!"
|
||||
#define GETTERTBL "__npcGETTERS"
|
||||
#define SETTERTBL "__npcSETTERS"
|
||||
#define METHODTBL "__npcMETHODS"
|
||||
|
||||
static EntityRef* grabEntityRef(lua_State *state, int indx) {
|
||||
// first, make sure its a userdata
|
||||
luaL_checktype(state, indx, LUA_TUSERDATA);
|
||||
|
||||
// now, check and make sure its our library's metatable attached to this userdata
|
||||
EntityRef *ref = (EntityRef*)luaL_checkudata(state, indx, LIBNAME);
|
||||
if (ref == NULL) {
|
||||
luaL_typerror(state, indx, LIBNAME);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// check if the npc exists still & return NULL if it doesn't
|
||||
if (!ref->isValid()) {
|
||||
luaL_argerror(state, indx, NPCGONESTR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static CombatNPC* grabNPC(lua_State *state, int indx) {
|
||||
EntityRef *ref = grabEntityRef(state, indx);
|
||||
|
||||
// if grabEntityRef failed, return error result
|
||||
return (ref == NULL) ? NULL : (CombatNPC*)ref->getEntity();
|
||||
}
|
||||
|
||||
static int32_t grabID(lua_State *state, int indx) {
|
||||
EntityRef *ref = grabEntityRef(state, indx);
|
||||
|
||||
// if grabEntityRef failed, return error result
|
||||
return (ref == NULL) ? -1 : ref->id;
|
||||
}
|
||||
|
||||
// =============================================== [[ GETTERS ]] ===============================================
|
||||
|
||||
static int npc_getMaxHealth(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (npc == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushinteger(state, npc->maxHealth);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int npc_getSpeed(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
|
||||
// sanity check
|
||||
if (npc == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushinteger(state, npc->speed);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg npc_getters[] = {
|
||||
{"maxHealth", npc_getMaxHealth},
|
||||
{"speed", npc_getSpeed},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// =============================================== [[ SETTERS ]] ===============================================
|
||||
|
||||
static int npc_setMaxHealth(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
int newMH = luaL_checkint(state, 2);
|
||||
|
||||
// sanity check
|
||||
if (npc != NULL)
|
||||
npc->maxHealth = newMH;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int npc_setSpeed(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
int newSpeed = luaL_checkint(state, 2);
|
||||
|
||||
// sanity check
|
||||
if (npc != NULL)
|
||||
npc->speed = newSpeed;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg npc_setters[] = {
|
||||
{"maxHealth", npc_setMaxHealth},
|
||||
{"speed", npc_setSpeed},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// =============================================== [[ METHODS ]] ===============================================
|
||||
|
||||
static int npc_moveto(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
int X = luaL_checkint(state, 2);
|
||||
int Y = luaL_checkint(state, 3);
|
||||
int Z = luaL_checkint(state, 4);
|
||||
|
||||
// sanity check
|
||||
if (npc == NULL)
|
||||
return 0;
|
||||
|
||||
std::queue<Vec3> queue;
|
||||
Vec3 from = { npc->x, npc->y, npc->z };
|
||||
Vec3 to = { X, Y, Z };
|
||||
|
||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||
Transport::lerp(&queue, from, to, npc->speed);
|
||||
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = queue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int npc_say(lua_State *state) {
|
||||
CombatNPC *npc = grabNPC(state, 1);
|
||||
std::string msg = std::string(luaL_checkstring(state, 2));
|
||||
|
||||
// sanity check
|
||||
if (npc != NULL) {
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, res);
|
||||
|
||||
U8toU16(msg, (char16_t*)&res.szFreeChat, sizeof(res.szFreeChat));
|
||||
res.iPC_ID = npc->appearanceData.iNPC_ID;
|
||||
res.iEmoteCode = 421;
|
||||
|
||||
// send the packet
|
||||
NPCManager::sendToViewable(npc, &res, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg npc_methods[] = {
|
||||
{"moveTo", npc_moveto},
|
||||
{"say", npc_say},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// in charge of calling the correct getter method
|
||||
static int plr_index(lua_State *state) {
|
||||
// grab the function from the getters lookup table
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// if it's not nil, call it and run the getter method
|
||||
if (!lua_isnil(state, -1)) {
|
||||
// push userdata & call the function
|
||||
lua_pushvalue(state, 1);
|
||||
lua_call(state, 1, 1);
|
||||
|
||||
// return # of results
|
||||
return 1;
|
||||
}
|
||||
|
||||
// grab the function from the methods lookup table
|
||||
lua_pop(state, 1);
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// return result
|
||||
return 1;
|
||||
}
|
||||
|
||||
// in charge of calling the correct setter method
|
||||
static int plr_newindex(lua_State *state) {
|
||||
// grab the function from the getters lookup table
|
||||
lua_pushstring(state, SETTERTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// if it's nil return
|
||||
if (lua_isnil(state, -1))
|
||||
return 0;
|
||||
|
||||
// push userdata & call the function
|
||||
lua_pushvalue(state, 1);
|
||||
lua_call(state, 1, 0);
|
||||
|
||||
// return # of results
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int npc_new(lua_State *state) {
|
||||
int X = luaL_checkint(state, 1);
|
||||
int Y = luaL_checkint(state, 2);
|
||||
int Z = luaL_checkint(state, 3);
|
||||
int type = luaL_checkint(state, 4);
|
||||
int id = NPCManager::nextId++;
|
||||
|
||||
// create & initalize the NPC
|
||||
CombatNPC *NPC = new CombatNPC(X, Y, Z, 0, INSTANCE_OVERWORLD, type, id, 1000);
|
||||
NPCManager::NPCs[id] = NPC;
|
||||
NPCManager::updateNPCPosition(id, X, Y, Z, INSTANCE_OVERWORLD, 0);
|
||||
|
||||
// push it to the lua stack & return
|
||||
LuaManager::NPC::push(state, NPC);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LuaManager::NPC::init(lua_State *state) {
|
||||
// register our library as a global (and leave it on the stack)
|
||||
luaL_register(state, LIBNAME, npc_methods);
|
||||
|
||||
// sets NPC.new
|
||||
lua_pushstring(state, "new");
|
||||
lua_pushcfunction(state, npc_new);
|
||||
lua_rawset(state, -3);
|
||||
|
||||
// create the meta table and populate it with our functions
|
||||
luaL_newmetatable(state, LIBNAME);
|
||||
lua_pushstring(state, "__index");
|
||||
lua_pushcfunction(state, plr_index);
|
||||
lua_rawset(state, -3); // sets meta.__index = plr_index
|
||||
lua_pushstring(state, "__newindex");
|
||||
lua_pushcfunction(state, plr_newindex);
|
||||
lua_rawset(state, -3); // sets meta.__newindex = plr_newindex
|
||||
lua_pop(state, 2); // pop meta & library table
|
||||
|
||||
// create the methods table
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_newtable(state);
|
||||
Entity::addMethods(state); // register the base Entity methods
|
||||
luaL_register(state, NULL, npc_methods);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
// create the getters table
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_newtable(state);
|
||||
Entity::addGetters(state); // register the base Entity getters
|
||||
luaL_register(state, NULL, npc_getters);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
// create the setters table
|
||||
lua_pushstring(state, SETTERTBL);
|
||||
lua_newtable(state);
|
||||
luaL_register(state, NULL, npc_setters);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
LuaManager::Entity::registerSuper(state, LIBNAME);
|
||||
}
|
||||
|
||||
void LuaManager::NPC::push(lua_State *state, CombatNPC *npc) {
|
||||
Entity::push(state, EntityRef(npc->appearanceData.iNPC_ID), LIBNAME);
|
||||
}
|
11
src/lua/NPCWrapper.hpp
Normal file
11
src/lua/NPCWrapper.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/EntityWrapper.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
namespace NPC {
|
||||
void init(lua_State *state);
|
||||
|
||||
void push(lua_State *state, CombatNPC *npc);
|
||||
}
|
||||
}
|
278
src/lua/PlayerWrapper.cpp
Normal file
278
src/lua/PlayerWrapper.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "lua/EntityWrapper.hpp"
|
||||
#include "lua/EventWrapper.hpp"
|
||||
#include "lua/PlayerWrapper.hpp"
|
||||
|
||||
#include "core/CNProtocol.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Chat.hpp"
|
||||
|
||||
#define LIBNAME "Player"
|
||||
#define PLRGONESTR "Player doesn't exist anymore, they left!"
|
||||
#define GETTERTBL "__plrGETTERS"
|
||||
#define SETTERTBL "__plrSETTERS"
|
||||
#define METHODTBL "__plrMETHODS"
|
||||
|
||||
static EntityRef* grabEntityRef(lua_State *state, int indx) {
|
||||
// first, make sure its a userdata
|
||||
luaL_checktype(state, indx, LUA_TUSERDATA);
|
||||
|
||||
// now, check and make sure its our library's metatable attached to this userdata
|
||||
EntityRef *ref = (EntityRef*)luaL_checkudata(state, indx, LIBNAME);
|
||||
if (ref == NULL) {
|
||||
luaL_typerror(state, indx, LIBNAME);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// check if the player exists still & return NULL if it doesn't
|
||||
if (!ref->isValid()) {
|
||||
luaL_argerror(state, indx, PLRGONESTR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static Player* grabPlayer(lua_State *state, int indx) {
|
||||
EntityRef *ref = grabEntityRef(state, indx);
|
||||
|
||||
if (ref == NULL)
|
||||
return NULL;
|
||||
|
||||
return (Player*)ref->getEntity();
|
||||
}
|
||||
|
||||
static CNSocket* grabSock(lua_State *state, int indx) {
|
||||
EntityRef *ref = grabEntityRef(state, indx);
|
||||
|
||||
if (ref == NULL)
|
||||
return NULL;
|
||||
|
||||
return ref->sock;
|
||||
}
|
||||
|
||||
// =============================================== [[ GETTERS ]] ===============================================
|
||||
|
||||
static int plr_getName(lua_State *state) {
|
||||
Player *plr = grabPlayer(state, 1);
|
||||
|
||||
if (plr == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushstring(state, PlayerManager::getPlayerName(plr).c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int plr_getInstance(lua_State *state) {
|
||||
Player *plr = grabPlayer(state, 1);
|
||||
|
||||
if (plr == NULL)
|
||||
return 0;
|
||||
|
||||
lua_pushnumber(state, plr->instanceID);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int plr_getChatted(lua_State *state) {
|
||||
Player *plr = grabPlayer(state, 1);
|
||||
|
||||
if (plr == NULL)
|
||||
return 0;
|
||||
|
||||
// the Player* entity doesn't actually have an lEvent setup until a lua script asks for it, so
|
||||
// if Player->onChat is nullptr, create the lEvent and then push it :D
|
||||
if (plr->onChat == nullptr)
|
||||
plr->onChat = new lEvent();
|
||||
|
||||
LuaManager::Event::push(state, plr->onChat);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg plr_getters[] = {
|
||||
{"name", plr_getName},
|
||||
{"instance", plr_getInstance},
|
||||
{"onChat", plr_getChatted},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// =============================================== [[ SETTERS ]] ===============================================
|
||||
|
||||
static int plr_setInstance(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
int newInst = luaL_checkint(state, 2);
|
||||
Player *plr;
|
||||
CNSocket *sock;
|
||||
|
||||
// sanity check
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
plr = (Player*)ref->getEntity();
|
||||
sock = ref->sock;
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, newInst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg plr_setters[] = {
|
||||
{"instance", plr_setInstance},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// =============================================== [[ METHODS ]] ===============================================
|
||||
|
||||
static int plr_kick(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
Player *plr;
|
||||
CNSocket *sock;
|
||||
|
||||
// sanity check
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
plr = (Player*)ref->getEntity();
|
||||
sock = ref->sock;
|
||||
|
||||
// construct packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
|
||||
|
||||
response.iID = plr->iID;
|
||||
response.iExitCode = 3; // "a GM has terminated your connection"
|
||||
|
||||
// send to target player
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
|
||||
|
||||
// ensure that the connection has terminated
|
||||
sock->kill();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int plr_teleport(lua_State *state) {
|
||||
EntityRef *ref = grabEntityRef(state, 1);
|
||||
Player *plr;
|
||||
CNSocket *sock;
|
||||
|
||||
// sanity check
|
||||
if (ref == NULL)
|
||||
return 0;
|
||||
|
||||
plr = (Player*)ref->getEntity();
|
||||
sock = ref->sock;
|
||||
|
||||
int X = luaL_checkint(state, 2);
|
||||
int Y = luaL_checkint(state, 3);
|
||||
int Z = luaL_checkint(state, 4);
|
||||
int inst = luaL_optint(state, 5, plr->instanceID);
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, X, Y, Z, inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int plr_msg(lua_State *state) {
|
||||
CNSocket *sock = grabSock(state, 1);
|
||||
const char *msg = luaL_checkstring(state, 2);
|
||||
|
||||
// sanity check
|
||||
if (sock != NULL)
|
||||
Chat::sendServerMessage(sock, std::string(msg));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg plr_methods[] = {
|
||||
{"kick", plr_kick},
|
||||
{"teleport", plr_teleport},
|
||||
{"message", plr_msg},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// in charge of calling the correct getter method
|
||||
static int plr_index(lua_State *state) {
|
||||
// grab the function from the getters lookup table
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// if it's not nil, call it and run the getter method
|
||||
if (!lua_isnil(state, -1)) {
|
||||
// push userdata & call the function
|
||||
lua_pushvalue(state, 1);
|
||||
lua_call(state, 1, 1);
|
||||
|
||||
// return # of results
|
||||
return 1;
|
||||
}
|
||||
|
||||
// grab the function from the methods lookup table
|
||||
lua_pop(state, 1);
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// return result
|
||||
return 1;
|
||||
}
|
||||
|
||||
// in charge of calling the correct setter method
|
||||
static int plr_newindex(lua_State *state) {
|
||||
// grab the function from the getters lookup table
|
||||
lua_pushstring(state, SETTERTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// if it's nil return
|
||||
if (lua_isnil(state, -1))
|
||||
return 0;
|
||||
|
||||
// push userdata & call the function
|
||||
lua_pushvalue(state, 1);
|
||||
lua_call(state, 1, 0);
|
||||
|
||||
// return # of results
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LuaManager::Player::init(lua_State *state) {
|
||||
// register our library as a global (and leave it on the stack)
|
||||
luaL_register(state, LIBNAME, plr_methods);
|
||||
|
||||
// create the meta table and populate it with our functions
|
||||
luaL_newmetatable(state, LIBNAME);
|
||||
lua_pushstring(state, "__index");
|
||||
lua_pushcfunction(state, plr_index);
|
||||
lua_rawset(state, -3); // sets meta.__index = plr_index
|
||||
lua_pushstring(state, "__newindex");
|
||||
lua_pushcfunction(state, plr_newindex);
|
||||
lua_rawset(state, -3); // sets meta.__newindex = plr_newindex
|
||||
lua_pop(state, 2); // pop meta & library table
|
||||
|
||||
// create the methods table
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_newtable(state);
|
||||
Entity::addMethods(state); // register the base Entity methods
|
||||
luaL_register(state, NULL, plr_methods);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
// create the getters table
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_newtable(state);
|
||||
Entity::addGetters(state); // register the base Entity getters
|
||||
luaL_register(state, NULL, plr_getters);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
// create the setters table
|
||||
lua_pushstring(state, SETTERTBL);
|
||||
lua_newtable(state);
|
||||
luaL_register(state, NULL, plr_setters);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
LuaManager::Entity::registerSuper(state, LIBNAME);
|
||||
}
|
||||
|
||||
void LuaManager::Player::push(lua_State *state, CNSocket *sock) {
|
||||
Entity::push(state, EntityRef(sock), LIBNAME);
|
||||
}
|
11
src/lua/PlayerWrapper.hpp
Normal file
11
src/lua/PlayerWrapper.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/EntityWrapper.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
namespace Player {
|
||||
void init(lua_State *state);
|
||||
|
||||
void push(lua_State *state, CNSocket *sock);
|
||||
}
|
||||
}
|
130
src/lua/WorldWrapper.cpp
Normal file
130
src/lua/WorldWrapper.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
#include "lua/EventWrapper.hpp"
|
||||
#include "lua/WorldWrapper.hpp"
|
||||
#include "core/CNStructs.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
|
||||
static lEvent *addedEvent;
|
||||
static lEvent *removedEvent;
|
||||
|
||||
#define LIBNAME "World"
|
||||
#define GETTERTBL "__wrldGETTERS"
|
||||
#define METHODTBL "__wrldMETHODS"
|
||||
|
||||
// =============================================== [[ GETTERS ]] ===============================================
|
||||
|
||||
int wrld_getPlrAdded(lua_State *state) {
|
||||
LuaManager::Event::push(state, addedEvent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wrld_getPlrRemoved(lua_State *state) {
|
||||
LuaManager::Event::push(state, removedEvent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wrld_getVersion(lua_State *state) {
|
||||
lua_pushnumber(state, PROTOCOL_VERSION);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wrld_getPlayers(lua_State *state) {
|
||||
// create a new lua table and push it onto the stack
|
||||
int entries = 0;
|
||||
lua_newtable(state);
|
||||
|
||||
// walk through the current list of players and add them to the table
|
||||
for (auto pair : PlayerManager::players) {
|
||||
lua_pushinteger(state, ++entries);
|
||||
LuaManager::Player::push(state, pair.first);
|
||||
lua_rawset(state, -3);
|
||||
}
|
||||
|
||||
// returns the player table :)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =============================================== [[ METHODS ]] ===============================================
|
||||
|
||||
int wrld_index(lua_State *state) {
|
||||
// grab the function from the getters lookup table
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// if it's not nil, call it and run the getter method
|
||||
if (!lua_isnil(state, -1)) {
|
||||
// push userdata & call the function
|
||||
lua_pushvalue(state, 1);
|
||||
lua_call(state, 1, 1);
|
||||
|
||||
// return # of results
|
||||
return 1;
|
||||
}
|
||||
|
||||
// grab the function from the methods lookup table
|
||||
lua_pop(state, 1);
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_rawget(state, LUA_REGISTRYINDEX);
|
||||
lua_pushvalue(state, 2);
|
||||
lua_rawget(state, -2);
|
||||
|
||||
// return result
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg getters[] {
|
||||
{"onPlayerAdded", wrld_getPlrAdded},
|
||||
{"onPlayerRemoved", wrld_getPlrRemoved},
|
||||
{"players", wrld_getPlayers},
|
||||
{"version", wrld_getVersion},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
// TODO
|
||||
static const luaL_Reg methods[] = {
|
||||
//{"getNearbyPlayers", wrld_getNPlrs},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
|
||||
void LuaManager::World::init(lua_State *state) {
|
||||
lua_newtable(state);
|
||||
luaL_newmetatable(state, LIBNAME);
|
||||
lua_pushstring(state, "__index");
|
||||
lua_pushcfunction(state, wrld_index);
|
||||
lua_rawset(state, -3); // sets meta.__index = wrld_index
|
||||
lua_setmetatable(state, -2); // sets world.__metatable = meta
|
||||
lua_setglobal(state, LIBNAME);
|
||||
|
||||
// setup the __wrldGETTERS table in the registry
|
||||
lua_pushstring(state, GETTERTBL);
|
||||
lua_newtable(state);
|
||||
luaL_register(state, NULL, getters);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
// setup the __wrldMETHODS table in the registry
|
||||
lua_pushstring(state, METHODTBL);
|
||||
lua_newtable(state);
|
||||
luaL_register(state, NULL, methods);
|
||||
lua_rawset(state, LUA_REGISTRYINDEX);
|
||||
|
||||
addedEvent = new lEvent();
|
||||
removedEvent = new lEvent();
|
||||
}
|
||||
|
||||
void LuaManager::World::clearState(lua_State *state) {
|
||||
addedEvent->clear(state);
|
||||
removedEvent->clear(state);
|
||||
}
|
||||
|
||||
void LuaManager::World::playerAdded(CNSocket *sock) {
|
||||
addedEvent->call(sock);
|
||||
}
|
||||
|
||||
void LuaManager::World::playerRemoved(CNSocket *sock) {
|
||||
removedEvent->call(sock);
|
||||
}
|
13
src/lua/WorldWrapper.hpp
Normal file
13
src/lua/WorldWrapper.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
namespace World {
|
||||
void init(lua_State *state);
|
||||
|
||||
void clearState(lua_State *state);
|
||||
void playerAdded(CNSocket *sock);
|
||||
void playerRemoved(CNSocket *sock);
|
||||
}
|
||||
}
|
51
src/main.cpp
51
src/main.cpp
@@ -1,5 +1,6 @@
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "lua/LuaManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "PlayerMovement.hpp"
|
||||
#include "BuiltinCommands.hpp"
|
||||
@@ -25,7 +26,6 @@
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
#include "sandbox/Sandbox.hpp"
|
||||
|
||||
#include "../version.h"
|
||||
|
||||
@@ -63,20 +63,8 @@ void terminate(int arg) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static BOOL WINAPI winTerminate(DWORD arg) {
|
||||
terminate(0);
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
void initsignals() {
|
||||
#ifdef _WIN32
|
||||
if (!SetConsoleCtrlHandler(winTerminate, TRUE)) {
|
||||
std::cerr << "[FATAL] Failed to set control handler" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
struct sigaction act;
|
||||
|
||||
memset((void*)&act, 0, sizeof(act));
|
||||
@@ -94,29 +82,25 @@ void initsignals() {
|
||||
perror("sigaction");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
||||
std::cerr << "OpenFusion: WSAStartup failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
initsignals();
|
||||
settings::init();
|
||||
Database::init();
|
||||
#endif
|
||||
Rand::init(getTime());
|
||||
TableData::init();
|
||||
|
||||
settings::init();
|
||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
||||
|
||||
TableData::init();
|
||||
PlayerManager::init();
|
||||
PlayerMovement::init();
|
||||
BuiltinCommands::init();
|
||||
@@ -135,9 +119,9 @@ int main() {
|
||||
Email::init();
|
||||
Groups::init();
|
||||
Racing::init();
|
||||
Trading::init();
|
||||
|
||||
Database::open();
|
||||
Trading::init();
|
||||
LuaManager::init();
|
||||
|
||||
switch (settings::EVENTMODE) {
|
||||
case 0: break; // no event
|
||||
@@ -156,8 +140,6 @@ int main() {
|
||||
|
||||
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
|
||||
|
||||
sandbox_start();
|
||||
|
||||
loginServer.start();
|
||||
|
||||
shardServer->kill();
|
||||
@@ -172,15 +154,10 @@ int main() {
|
||||
// helper functions
|
||||
|
||||
std::string U16toU8(char16_t* src, size_t max) {
|
||||
src[max-1] = '\0'; // force a NULL terminator
|
||||
src[max-1] = '\0'; // force a NULL terminatorstd::string U16toU8(char16_t* src) {
|
||||
try {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
std::string ret = convert.to_bytes(src);
|
||||
|
||||
if (ret.size() >= max)
|
||||
ret.resize(max-2);
|
||||
|
||||
return ret;
|
||||
return convert.to_bytes(src);
|
||||
} catch(const std::exception& e) {
|
||||
return "";
|
||||
}
|
||||
@@ -204,7 +181,7 @@ size_t U8toU16(std::string src, char16_t* des, size_t max) {
|
||||
time_t getTime() {
|
||||
using namespace std::chrono;
|
||||
|
||||
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(steady_clock::now())).time_since_epoch());
|
||||
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(high_resolution_clock::now())).time_since_epoch());
|
||||
|
||||
return (time_t)value.count();
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// use the sandbox on supported platforms, unless disabled
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
|
||||
# if !defined(CONFIG_NOSANDBOX)
|
||||
void sandbox_start();
|
||||
# else
|
||||
|
||||
#include <iostream>
|
||||
|
||||
inline void sandbox_start() {
|
||||
std::cout << "[WARN] Built without a sandbox" << std::endl;
|
||||
}
|
||||
|
||||
# endif // CONFIG_NOSANDBOX
|
||||
|
||||
#else
|
||||
// stub for unsupported platforms
|
||||
inline void sandbox_start() {}
|
||||
#endif
|
@@ -1,45 +0,0 @@
|
||||
#if defined(__OpenBSD__) && !defined(CONFIG_NOSANDBOX)
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <err.h>
|
||||
|
||||
static void eunveil(const char *path, const char *permissions) {
|
||||
if (unveil(path, permissions) < 0)
|
||||
err(1, "unveil");
|
||||
}
|
||||
|
||||
void sandbox_start() {
|
||||
/*
|
||||
* There shouldn't ever be a reason to disable this one, but might as well
|
||||
* be consistent with the Linux sandbox.
|
||||
*/
|
||||
if (!settings::SANDBOX) {
|
||||
std::cout << "[WARN] Running without a sandbox" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Starting pledge+unveil sandbox..." << std::endl;
|
||||
|
||||
if (pledge("stdio rpath wpath cpath inet flock unveil", NULL) < 0)
|
||||
err(1, "pledge");
|
||||
|
||||
// database stuff
|
||||
eunveil(settings::DBPATH.c_str(), "rwc");
|
||||
eunveil((settings::DBPATH + "-journal").c_str(), "rwc");
|
||||
eunveil((settings::DBPATH + "-wal").c_str(), "rwc");
|
||||
|
||||
// tabledata stuff
|
||||
eunveil((settings::TDATADIR + "/" + settings::GRUNTWORKJSON).c_str(), "wc");
|
||||
|
||||
// for bcrypt_gensalt()
|
||||
eunveil("/dev/urandom", "r");
|
||||
|
||||
eunveil(NULL, NULL);
|
||||
}
|
||||
|
||||
#endif
|
@@ -1,324 +0,0 @@
|
||||
#if defined(__linux__) && !defined(CONFIG_NOSANDBOX)
|
||||
|
||||
#include "core/Core.hpp" // mostly for ARRLEN
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/mman.h> // for mmap() args
|
||||
#include <sys/ioctl.h> // for ioctl() args
|
||||
#include <termios.h> // for ioctl() args
|
||||
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/net.h> // for socketcall() args
|
||||
|
||||
/*
|
||||
* Macros adapted from https://outflux.net/teach-seccomp/
|
||||
* Relevant license:
|
||||
* https://source.chromium.org/chromium/chromium/src/+/master:LICENSE
|
||||
*/
|
||||
#define syscall_nr (offsetof(struct seccomp_data, nr))
|
||||
#define arch_nr (offsetof(struct seccomp_data, arch))
|
||||
|
||||
#if defined(__i386__)
|
||||
# define ARCH_NR AUDIT_ARCH_I386
|
||||
#elif defined(__x86_64__)
|
||||
# define ARCH_NR AUDIT_ARCH_X86_64
|
||||
#elif defined(__arm__)
|
||||
# define ARCH_NR AUDIT_ARCH_ARM
|
||||
#elif defined(__aarch64__)
|
||||
# define ARCH_NR AUDIT_ARCH_AARCH64
|
||||
#else
|
||||
# error "Seccomp-bpf sandbox unsupported on this architecture"
|
||||
#endif
|
||||
|
||||
#define VALIDATE_ARCHITECTURE \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS)
|
||||
|
||||
#define EXAMINE_SYSCALL \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
|
||||
|
||||
#define ALLOW_SYSCALL(name) \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
|
||||
|
||||
#define DENY_SYSCALL_ERRNO(name, _errno) \
|
||||
BPF_JUMP(BPF_JMP+BPF_K+BPF_JEQ, __NR_##name, 0, 1), \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno))
|
||||
|
||||
#define KILL_PROCESS \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS)
|
||||
|
||||
/*
|
||||
* Macros adapted from openssh's sandbox-seccomp-filter.c
|
||||
* Relevant license:
|
||||
* https://github.com/openssh/openssh-portable/blob/master/LICENCE
|
||||
*/
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
# define ARG_LO_OFFSET 0
|
||||
# define ARG_HI_OFFSET sizeof(uint32_t)
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# define ARG_LO_OFFSET sizeof(uint32_t)
|
||||
# define ARG_HI_OFFSET 0
|
||||
#else
|
||||
#error "Unknown endianness"
|
||||
#endif
|
||||
|
||||
#define ALLOW_SYSCALL_ARG(_nr, _arg_nr, _arg_val) \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (__NR_##_nr), 0, 6), \
|
||||
/* load and test syscall argument, low word */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_LO_OFFSET), \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, \
|
||||
((_arg_val) & 0xFFFFFFFF), 0, 3), \
|
||||
/* load and test syscall argument, high word */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_HI_OFFSET), \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, \
|
||||
(((uint32_t)((uint64_t)(_arg_val) >> 32)) & 0xFFFFFFFF), 0, 1), \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), \
|
||||
/* reload syscall number; all rules expect it in accumulator */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, nr))
|
||||
|
||||
/* Allow if syscall argument contains only values in mask */
|
||||
#define ALLOW_SYSCALL_ARG_MASK(_nr, _arg_nr, _arg_mask) \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (__NR_##_nr), 0, 8), \
|
||||
/* load, mask and test syscall argument, low word */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_LO_OFFSET), \
|
||||
BPF_STMT(BPF_ALU+BPF_AND+BPF_K, ~((_arg_mask) & 0xFFFFFFFF)), \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 4), \
|
||||
/* load, mask and test syscall argument, high word */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_HI_OFFSET), \
|
||||
BPF_STMT(BPF_ALU+BPF_AND+BPF_K, \
|
||||
~(((uint32_t)((uint64_t)(_arg_mask) >> 32)) & 0xFFFFFFFF)), \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 1), \
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), \
|
||||
/* reload syscall number; all rules expect it in accumulator */ \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||
offsetof(struct seccomp_data, nr))
|
||||
|
||||
/*
|
||||
* This is a special case for AArch64 where this syscall apparently only
|
||||
* exists in 32-bit compatibility mode, so we can't include the definition
|
||||
* even though it gets called somewhere in libc.
|
||||
*/
|
||||
#if defined(__aarch64__) && !defined(__NR_fstatat64)
|
||||
#define __NR_fstatat64 0x4f
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The main supported configuration is Linux on x86_64 with either glibc or
|
||||
* musl-libc, with secondary support for x86, ARM and ARM64 (AAarch64) Linux.
|
||||
*
|
||||
* Syscalls marked with "maybe" don't seem to be used in the default
|
||||
* configuration, but should probably be whitelisted anyway.
|
||||
*
|
||||
* Syscalls marked with comments like "musl-libc", "raspi" or "alt DB" were
|
||||
* observed to be necessary on that particular configuration, but there are
|
||||
* probably other configurations in which they are neccessary as well.
|
||||
* ("alt DB" represents libsqlite compiled with different options.)
|
||||
*
|
||||
* Syscalls marked "vdso" aren't normally caught by seccomp because they are
|
||||
* implemented in the vdso(7) in most configurations, but it's still prudent
|
||||
* to whitelist them here.
|
||||
*/
|
||||
static sock_filter filter[] = {
|
||||
VALIDATE_ARCHITECTURE,
|
||||
EXAMINE_SYSCALL,
|
||||
|
||||
// memory management
|
||||
#ifdef __NR_mmap
|
||||
ALLOW_SYSCALL_ARG_MASK(mmap, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||
#endif
|
||||
ALLOW_SYSCALL(munmap),
|
||||
ALLOW_SYSCALL_ARG_MASK(mprotect, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||
ALLOW_SYSCALL(madvise),
|
||||
ALLOW_SYSCALL(brk),
|
||||
|
||||
// basic file IO
|
||||
#ifdef __NR_open
|
||||
ALLOW_SYSCALL(open),
|
||||
#endif
|
||||
ALLOW_SYSCALL(openat),
|
||||
ALLOW_SYSCALL(read),
|
||||
ALLOW_SYSCALL(write),
|
||||
ALLOW_SYSCALL(close),
|
||||
#ifdef __NR_stat
|
||||
ALLOW_SYSCALL(stat),
|
||||
#endif
|
||||
ALLOW_SYSCALL(fstat),
|
||||
#ifdef __NR_newfstatat
|
||||
ALLOW_SYSCALL(newfstatat),
|
||||
#endif
|
||||
ALLOW_SYSCALL(fsync), // maybe
|
||||
#ifdef __NR_creat
|
||||
ALLOW_SYSCALL(creat), // maybe; for DB journal
|
||||
#endif
|
||||
#ifdef __NR_unlink
|
||||
ALLOW_SYSCALL(unlink), // for DB journal
|
||||
#endif
|
||||
ALLOW_SYSCALL(lseek), // musl-libc; alt DB
|
||||
ALLOW_SYSCALL(truncate), // for truncate-mode DB
|
||||
ALLOW_SYSCALL(ftruncate), // for truncate-mode DB
|
||||
ALLOW_SYSCALL(dup), // for perror(), apparently
|
||||
|
||||
// more IO
|
||||
ALLOW_SYSCALL(pread64),
|
||||
ALLOW_SYSCALL(pwrite64),
|
||||
ALLOW_SYSCALL(fdatasync),
|
||||
ALLOW_SYSCALL(writev), // musl-libc
|
||||
ALLOW_SYSCALL(preadv), // maybe; alt-DB
|
||||
ALLOW_SYSCALL(preadv2), // maybe
|
||||
|
||||
// misc syscalls called from libc
|
||||
ALLOW_SYSCALL(getcwd),
|
||||
ALLOW_SYSCALL(getpid),
|
||||
ALLOW_SYSCALL(geteuid),
|
||||
ALLOW_SYSCALL(gettid), // maybe
|
||||
ALLOW_SYSCALL_ARG(ioctl, 1, TIOCGWINSZ), // musl-libc
|
||||
ALLOW_SYSCALL_ARG(fcntl, 1, F_GETFL),
|
||||
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETFL),
|
||||
ALLOW_SYSCALL_ARG(fcntl, 1, F_GETLK),
|
||||
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETLK),
|
||||
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETLKW), // maybe
|
||||
ALLOW_SYSCALL(exit),
|
||||
ALLOW_SYSCALL(exit_group),
|
||||
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
|
||||
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
|
||||
#ifdef __NR_rseq
|
||||
ALLOW_SYSCALL(rseq),
|
||||
#endif
|
||||
|
||||
// to crash properly on SIGSEGV
|
||||
DENY_SYSCALL_ERRNO(tgkill, EPERM),
|
||||
DENY_SYSCALL_ERRNO(tkill, EPERM), // musl-libc
|
||||
DENY_SYSCALL_ERRNO(rt_sigaction, EPERM),
|
||||
|
||||
// threading
|
||||
ALLOW_SYSCALL(futex),
|
||||
|
||||
// networking
|
||||
#ifdef __NR_poll
|
||||
ALLOW_SYSCALL(poll),
|
||||
#endif
|
||||
#ifdef __NR_accept
|
||||
ALLOW_SYSCALL(accept),
|
||||
#endif
|
||||
ALLOW_SYSCALL(setsockopt),
|
||||
ALLOW_SYSCALL(sendto),
|
||||
ALLOW_SYSCALL(recvfrom),
|
||||
ALLOW_SYSCALL(shutdown),
|
||||
|
||||
// vdso
|
||||
ALLOW_SYSCALL(clock_gettime),
|
||||
ALLOW_SYSCALL(gettimeofday),
|
||||
#ifdef __NR_time
|
||||
ALLOW_SYSCALL(time),
|
||||
#endif
|
||||
ALLOW_SYSCALL(rt_sigreturn),
|
||||
|
||||
// i386
|
||||
#ifdef __NR_socketcall
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_ACCEPT),
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SETSOCKOPT),
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SEND),
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_RECV),
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SENDTO), // maybe
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_RECVFROM), // maybe
|
||||
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SHUTDOWN),
|
||||
#endif
|
||||
|
||||
// Raspberry Pi (ARM)
|
||||
#ifdef __NR_set_robust_list
|
||||
ALLOW_SYSCALL(set_robust_list),
|
||||
#endif
|
||||
#ifdef __NR_clock_gettime64
|
||||
ALLOW_SYSCALL(clock_gettime64),
|
||||
#endif
|
||||
#ifdef __NR_mmap2
|
||||
ALLOW_SYSCALL_ARG_MASK(mmap2, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||
#endif
|
||||
#ifdef __NR_fcntl64
|
||||
ALLOW_SYSCALL(fcntl64),
|
||||
#endif
|
||||
#ifdef __NR_stat64
|
||||
ALLOW_SYSCALL(stat64),
|
||||
#endif
|
||||
#ifdef __NR_send
|
||||
ALLOW_SYSCALL(send),
|
||||
#endif
|
||||
#ifdef __NR_recv
|
||||
ALLOW_SYSCALL(recv),
|
||||
#endif
|
||||
#ifdef __NR_fstat64
|
||||
ALLOW_SYSCALL(fstat64),
|
||||
#endif
|
||||
#ifdef __NR_geteuid32
|
||||
ALLOW_SYSCALL(geteuid32),
|
||||
#endif
|
||||
#ifdef __NR_truncate64
|
||||
ALLOW_SYSCALL(truncate64),
|
||||
#endif
|
||||
#ifdef __NR_ftruncate64
|
||||
ALLOW_SYSCALL(ftruncate64),
|
||||
#endif
|
||||
#ifdef __NR_sigreturn
|
||||
ALLOW_SYSCALL(sigreturn), // vdso
|
||||
#endif
|
||||
#ifdef __NR_clock_nanosleep_time64
|
||||
ALLOW_SYSCALL(clock_nanosleep_time64), // maybe
|
||||
#endif
|
||||
|
||||
// AArch64 (ARM64)
|
||||
#ifdef __NR_unlinkat
|
||||
ALLOW_SYSCALL(unlinkat),
|
||||
#endif
|
||||
#ifdef __NR_fstatat64
|
||||
ALLOW_SYSCALL(fstatat64),
|
||||
#endif
|
||||
#ifdef __NR_ppoll
|
||||
ALLOW_SYSCALL(ppoll),
|
||||
#endif
|
||||
|
||||
KILL_PROCESS
|
||||
};
|
||||
|
||||
static sock_fprog prog = {
|
||||
ARRLEN(filter), filter
|
||||
};
|
||||
|
||||
// our own wrapper for the seccomp() syscall
|
||||
int seccomp(unsigned int operation, unsigned int flags, void *args) {
|
||||
return syscall(__NR_seccomp, operation, flags, args);
|
||||
}
|
||||
|
||||
void sandbox_start() {
|
||||
if (!settings::SANDBOX) {
|
||||
std::cout << "[WARN] Running without a sandbox" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl;
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
|
||||
perror("prctl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) < 0) {
|
||||
perror("seccomp");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@@ -1,19 +1,16 @@
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
|
||||
#include "core/CNShared.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "bcrypt/BCrypt.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <regex>
|
||||
#include "bcrypt/BCrypt.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||
|
||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||
serverType = "login";
|
||||
port = p;
|
||||
pHandler = &CNLoginServer::handlePacket;
|
||||
init();
|
||||
@@ -22,17 +19,6 @@ CNLoginServer::CNLoginServer(uint16_t p) {
|
||||
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
printPacket(data);
|
||||
|
||||
if (loginSessions.find(sock) == loginSessions.end() &&
|
||||
data->type != P_CL2LS_REQ_LOGIN && data->type != P_CL2LS_REP_LIVE_CHECK) {
|
||||
|
||||
if (settings::VERBOSITY > 0) {
|
||||
std::cerr << "OpenFusion: LOGIN PKT OUT-OF-SEQ. PacketType: " <<
|
||||
Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data->type) {
|
||||
case P_CL2LS_REQ_LOGIN: {
|
||||
login(sock, data);
|
||||
@@ -147,12 +133,8 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
||||
Database::findAccount(&findUser, userLogin);
|
||||
|
||||
// account was not found
|
||||
if (findUser.AccountID == 0) {
|
||||
if (settings::AUTOCREATEACCOUNTS)
|
||||
return newAccount(sock, userLogin, userPassword, login->iClientVerC);
|
||||
|
||||
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
|
||||
}
|
||||
if (findUser.AccountID == 0)
|
||||
return newAccount(sock, userLogin, userPassword, login->iClientVerC);
|
||||
|
||||
if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword))
|
||||
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
|
||||
@@ -211,12 +193,9 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
||||
// send the resp in with original key
|
||||
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
||||
|
||||
uint64_t defaultKey;
|
||||
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||
@@ -466,7 +445,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
||||
* the shard IP has been configured to an address the local machine can't
|
||||
* reach itself from.
|
||||
*/
|
||||
if (settings::LOCALHOSTWORKAROUND && sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
|
||||
if (sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
|
||||
shard_ip = "127.0.0.1";
|
||||
|
||||
memcpy(resp.g_FE_ServerIP, shard_ip, strlen(shard_ip));
|
||||
@@ -474,20 +453,21 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
||||
resp.g_FE_ServerIP[strlen(shard_ip)] = '\0';
|
||||
resp.g_FE_ServerPort = settings::SHARDPORT;
|
||||
|
||||
LoginMetadata *lm = new LoginMetadata();
|
||||
lm->FEKey = sock->getFEKey();
|
||||
lm->timestamp = getTime();
|
||||
lm->playerId = selection->iPC_UID;
|
||||
// pass player to CNSharedData
|
||||
Player passPlayer = {};
|
||||
Database::getPlayer(&passPlayer, selection->iPC_UID);
|
||||
// this should never happen but for extra safety
|
||||
if (passPlayer.iID == 0)
|
||||
return invalidCharacter(sock);
|
||||
|
||||
resp.iEnterSerialKey = Rand::cryptoRand();
|
||||
|
||||
// transfer ownership of connection data to CNShared
|
||||
CNShared::storeLoginMetadata(resp.iEnterSerialKey, lm);
|
||||
passPlayer.FEKey = sock->getFEKey();
|
||||
resp.iEnterSerialKey = passPlayer.iID;
|
||||
CNSharedData::setPlayer(resp.iEnterSerialKey, passPlayer);
|
||||
|
||||
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
||||
|
||||
// update current slot in DB
|
||||
Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID);
|
||||
Database::updateSelected(loginSessions[sock].userID, passPlayer.slot);
|
||||
}
|
||||
|
||||
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
|
||||
#include <map>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user