mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-10-15 18:20:08 +00:00
Compare commits
14 Commits
876a9c82cd
...
lua
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9af3b60d4d | ||
![]() |
d5fc36f877 | ||
aa3dacc84b | |||
a29de394ee | |||
ca299e0a5b | |||
794b881c4d | |||
e8659962a5 | |||
2090062c75 | |||
b25b229eb2 | |||
c2853d9271 | |||
cb85f7b7c9 | |||
80f0ff7479 | |||
2aa23a83de | |||
43aa4eaeb8 |
146
.github/workflows/check-builds.yaml
vendored
146
.github/workflows/check-builds.yaml
vendored
@@ -1,146 +0,0 @@
|
||||
name: Check Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- .github/workflows/check-builds.yaml
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
pull_request:
|
||||
types: ready_for_review
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
|
||||
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: 'ubuntu20_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-latest
|
||||
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
|
@@ -43,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}")
|
||||
|
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 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
|
||||
|
34
Makefile
34
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,13 +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/Chat.cpp\
|
||||
src/CustomCommands.cpp\
|
||||
src/Entities.cpp\
|
||||
@@ -88,9 +94,15 @@ 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\
|
||||
@@ -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
|
||||
|
23
README.md
23
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,32 +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.4/OpenFusionClient-1.4-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.4/OpenFusionClient-1.4.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.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
|
||||
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:
|
||||
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.
|
||||
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 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/)
|
||||
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.
|
||||
|
||||
|
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
|
||||
|
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,
|
||||
|
@@ -5,8 +5,8 @@
|
||||
#include "core/Core.hpp"
|
||||
|
||||
namespace Buddies {
|
||||
void init();
|
||||
void init();
|
||||
|
||||
// Buddy list
|
||||
void refreshBuddyList(CNSocket* sock);
|
||||
// Buddy list
|
||||
void refreshBuddyList(CNSocket* sock);
|
||||
}
|
||||
|
@@ -283,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) {
|
||||
|
@@ -19,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;
|
||||
|
||||
|
@@ -7,16 +7,11 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -32,7 +27,7 @@ static void newChunk(ChunkPos pos) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <set>
|
||||
@@ -8,23 +9,14 @@
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
|
||||
struct EntityRef;
|
||||
|
||||
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
|
||||
@@ -34,8 +26,6 @@ enum {
|
||||
namespace Chunking {
|
||||
extern std::map<ChunkPos, Chunk*> chunks;
|
||||
|
||||
extern const ChunkPos INVALID_CHUNK;
|
||||
|
||||
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to);
|
||||
|
||||
void trackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
||||
|
@@ -56,10 +56,14 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
||||
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)
|
||||
@@ -67,28 +71,11 @@ static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
||||
|
||||
plr->lastShot = currTime;
|
||||
|
||||
// 3+ targets should never be possible
|
||||
if (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))
|
||||
return;
|
||||
|
||||
/*
|
||||
* IMPORTANT: This validates memory safety in addition to preventing
|
||||
@@ -163,7 +150,7 @@ 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;
|
||||
@@ -226,30 +213,6 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
||||
return damage;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) {
|
||||
for (int i = 0; i < leader->groupCnt; i++) {
|
||||
if (leader->groupIDs[i] == 0)
|
||||
continue;
|
||||
|
||||
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
if (otherSock == nullptr)
|
||||
continue;
|
||||
|
||||
Player *member = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
||||
if (member->tasks[j] != 0)
|
||||
rolls[member->tasks[j]] = Rand::rand();
|
||||
}
|
||||
}
|
||||
|
||||
void Combat::killMob(CNSocket *sock, Mob *mob) {
|
||||
mob->state = MobState::DEAD;
|
||||
mob->target = nullptr;
|
||||
@@ -264,19 +227,19 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
|
||||
|
||||
Items::DropRoll rolled;
|
||||
Items::DropRoll eventRolled;
|
||||
std::map<int, int> qitemRolls;
|
||||
|
||||
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
assert(leader != nullptr); // should never happen
|
||||
|
||||
genQItemRolls(leader, qitemRolls);
|
||||
int rolledQItem = Rand::rand();
|
||||
|
||||
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
||||
Items::giveMobDrop(sock, mob, rolled, eventRolled);
|
||||
Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
|
||||
Missions::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem);
|
||||
} else {
|
||||
for (int i = 0; i < leader->groupCnt; i++) {
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
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;
|
||||
|
||||
@@ -288,7 +251,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
|
||||
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls);
|
||||
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,7 +375,7 @@ 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;
|
||||
|
||||
@@ -662,11 +625,8 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
plr->lastShot = currTime;
|
||||
|
||||
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
|
||||
if (plr->suspicionRating > 10000) // kill the socket when the player is too suspicious
|
||||
sock->kill();
|
||||
CNShardServer::_killConnection(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize response struct
|
||||
|
@@ -10,10 +10,11 @@
|
||||
#include "Transport.hpp"
|
||||
#include "Missions.hpp"
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
||||
|
||||
@@ -378,9 +379,12 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -688,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.type == EntityType::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->appearanceData.iNPCType) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1184,6 +1192,12 @@ 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);
|
||||
}
|
||||
@@ -1223,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");
|
||||
}
|
||||
|
@@ -10,8 +10,6 @@
|
||||
|
||||
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,10 +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);
|
||||
}
|
||||
|
||||
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,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "Chunking.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <set>
|
||||
@@ -16,6 +15,8 @@ enum class EntityType : uint8_t {
|
||||
BUS
|
||||
};
|
||||
|
||||
class Chunk;
|
||||
|
||||
struct Entity {
|
||||
EntityType type = EntityType::INVALID;
|
||||
int x = 0, y = 0, z = 0;
|
||||
@@ -143,7 +144,6 @@ struct Bus : public BaseNPC {
|
||||
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;
|
||||
loopingPath = true;
|
||||
}
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
|
@@ -8,7 +8,7 @@
|
||||
#include <list>
|
||||
|
||||
namespace Groups {
|
||||
void init();
|
||||
void init();
|
||||
|
||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(Player* plr);
|
||||
|
@@ -199,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()) {
|
||||
|
146
src/Missions.cpp
146
src/Missions.cpp
@@ -27,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.
|
||||
@@ -52,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;
|
||||
@@ -78,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;
|
||||
@@ -219,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()) {
|
||||
@@ -239,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
|
||||
*
|
||||
@@ -298,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
|
||||
@@ -387,41 +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;
|
||||
|
||||
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"] == 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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,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;
|
||||
@@ -549,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++;
|
||||
|
||||
@@ -567,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;
|
||||
@@ -590,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);
|
||||
|
@@ -40,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);
|
||||
|
||||
|
@@ -98,17 +98,11 @@ void MobAI::groupRetreat(Mob *mob) {
|
||||
}
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
|
||||
|
||||
if (followerMob->state != MobState::COMBAT)
|
||||
continue;
|
||||
|
||||
followerMob->target = nullptr;
|
||||
followerMob->state = MobState::RETREAT;
|
||||
clearDebuff(followerMob);
|
||||
}
|
||||
|
||||
if (leadMob->state != MobState::COMBAT)
|
||||
return;
|
||||
|
||||
leadMob->target = nullptr;
|
||||
leadMob->state = MobState::RETREAT;
|
||||
clearDebuff(leadMob);
|
||||
@@ -133,7 +127,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
||||
CNSocket *s = ref.sock;
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (plr->HP <= 0 || plr->onMonkey)
|
||||
if (plr->HP <= 0)
|
||||
continue;
|
||||
|
||||
int mobRange = mob->sightRange;
|
||||
|
@@ -246,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())
|
||||
|
@@ -226,9 +226,6 @@ 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;
|
||||
|
||||
|
@@ -7,6 +7,8 @@
|
||||
#include "Chunking.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
#include "lua/LuaWrapper.hpp"
|
||||
|
||||
#define ACTIVE_MISSION_COUNT 6
|
||||
|
||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||
@@ -14,7 +16,9 @@
|
||||
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;
|
||||
@@ -85,7 +89,15 @@ struct Player : public Entity {
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
// 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;
|
||||
|
@@ -18,6 +18,8 @@
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "lua/LuaManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -28,19 +30,30 @@ 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;
|
||||
|
||||
// call events
|
||||
LuaManager::playerRemoved(key);
|
||||
|
||||
Groups::groupKickPlayer(plr);
|
||||
|
||||
// remove player's bullets
|
||||
@@ -92,19 +105,6 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui
|
||||
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;
|
||||
@@ -156,7 +156,8 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I
|
||||
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);
|
||||
@@ -204,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->groupCnt = 1;
|
||||
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
|
||||
|
||||
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
|
||||
@@ -295,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
|
||||
@@ -309,14 +300,15 @@ 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
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC);
|
||||
@@ -324,14 +316,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
// 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, plr);
|
||||
Items::checkItemExpire(sock, getPlayer(sock));
|
||||
|
||||
// set player equip stats
|
||||
Items::setItemStats(plr);
|
||||
Items::setItemStats(getPlayer(sock));
|
||||
|
||||
Missions::failInstancedMissions(sock);
|
||||
|
||||
@@ -342,10 +332,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
for (auto& pair : players)
|
||||
if (pair.second->notify)
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
|
||||
|
||||
// deallocate lm
|
||||
delete lm;
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
||||
}
|
||||
|
||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
@@ -390,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) {
|
||||
@@ -409,18 +393,14 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
||||
// nano revive
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||
} else if (reviveData->iRegenType == 4) {
|
||||
// revived by group member's nano
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
} else if (reviveData->iRegenType == 5) {
|
||||
// warp away
|
||||
move = true;
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
} else {
|
||||
// plain respawn
|
||||
move = true;
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
if (reviveData->iRegenType != 5)
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -487,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) {
|
||||
@@ -714,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);
|
||||
}
|
||||
|
@@ -18,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);
|
||||
|
@@ -64,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) {
|
||||
|
@@ -9,7 +9,7 @@ struct EPInfo {
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
@@ -14,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();
|
||||
|
@@ -671,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++)
|
||||
@@ -1073,12 +1073,12 @@ void TableData::init() {
|
||||
std::ifstream fstream;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
std::pair<json*, std::string>& table = tables[i];
|
||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||
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: " << table.second << std::endl;
|
||||
std::cerr << "[FATAL] Critical tdata file missing: " << settings::TDATADIR << table.second << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1127,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) {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#include "Trading.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
using namespace Trading;
|
||||
|
||||
@@ -206,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;
|
||||
}
|
||||
|
||||
@@ -255,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) {
|
||||
@@ -318,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.
|
||||
@@ -362,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;
|
||||
@@ -395,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)
|
||||
@@ -406,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;
|
||||
|
@@ -165,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) {
|
||||
|
@@ -1,9 +1,6 @@
|
||||
#include "Vendors.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
// 7 days
|
||||
#define VEHICLE_EXPIRY_DURATION 604800
|
||||
|
||||
using namespace Vendors;
|
||||
|
||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||
@@ -16,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);
|
||||
@@ -56,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) {
|
||||
@@ -222,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;
|
||||
|
@@ -7,12 +7,7 @@
|
||||
#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 {
|
||||
|
@@ -473,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];
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -41,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);
|
||||
|
@@ -11,17 +11,17 @@ const float CN_EP_RANK_5 = 0.29f;
|
||||
|
||||
// methods of finding players for GM commands
|
||||
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
|
||||
eCN_GM_TargetSearchBy__PC_ID, // player id
|
||||
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname
|
||||
eCN_GM_TargetSearchBy__PC_UID // account id
|
||||
};
|
||||
|
||||
enum eCN_GM_TeleportType {
|
||||
eCN_GM_TeleportMapType__XYZ,
|
||||
eCN_GM_TeleportMapType__MapXYZ,
|
||||
eCN_GM_TeleportMapType__MyLocation,
|
||||
eCN_GM_TeleportMapType__SomeoneLocation,
|
||||
eCN_GM_TeleportMapType__Unstick
|
||||
eCN_GM_TeleportMapType__XYZ,
|
||||
eCN_GM_TeleportMapType__MapXYZ,
|
||||
eCN_GM_TeleportMapType__MyLocation,
|
||||
eCN_GM_TeleportMapType__SomeoneLocation,
|
||||
eCN_GM_TeleportMapType__Unstick
|
||||
};
|
||||
|
||||
// nano powers
|
||||
@@ -925,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
|
||||
};
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define DATABASE_VERSION 4
|
||||
#define DATABASE_VERSION 3
|
||||
|
||||
namespace Database {
|
||||
|
||||
@@ -52,8 +52,7 @@ namespace Database {
|
||||
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);
|
||||
@@ -79,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);
|
||||
@@ -101,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;
|
||||
|
||||
|
@@ -3,6 +3,12 @@
|
||||
#include "db/Database.hpp"
|
||||
#include <sqlite3.h>
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
extern std::mutex dbCrit;
|
||||
extern sqlite3 *db;
|
||||
|
||||
|
@@ -79,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);
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
39
src/main.cpp
39
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 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,8 +82,8 @@ void initsignals() {
|
||||
perror("sigaction");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
#ifdef _WIN32
|
||||
@@ -104,15 +92,14 @@ int main() {
|
||||
std::cerr << "OpenFusion: WSAStartup failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
initsignals();
|
||||
#endif
|
||||
Rand::init(getTime());
|
||||
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;
|
||||
|
||||
Rand::init(getTime());
|
||||
TableData::init();
|
||||
PlayerManager::init();
|
||||
PlayerMovement::init();
|
||||
@@ -134,6 +121,7 @@ int main() {
|
||||
Racing::init();
|
||||
Database::open();
|
||||
Trading::init();
|
||||
LuaManager::init();
|
||||
|
||||
switch (settings::EVENTMODE) {
|
||||
case 0: break; // no event
|
||||
@@ -152,8 +140,6 @@ int main() {
|
||||
|
||||
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
|
||||
|
||||
sandbox_start();
|
||||
|
||||
loginServer.start();
|
||||
|
||||
shardServer->kill();
|
||||
@@ -168,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 "";
|
||||
}
|
||||
@@ -200,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
|
@@ -19,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);
|
||||
@@ -144,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);
|
||||
@@ -460,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));
|
||||
@@ -468,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) {
|
||||
|
@@ -29,31 +29,11 @@ CNShardServer::CNShardServer(uint16_t p) {
|
||||
void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
printPacket(data);
|
||||
|
||||
// if it's a valid packet
|
||||
if (ShardPackets.find(data->type) != ShardPackets.end()) {
|
||||
|
||||
// reject gameplay packets if not yet fully connected
|
||||
if (PlayerManager::players.find(sock) == PlayerManager::players.end()
|
||||
&& data->type != P_CL2FE_REQ_PC_ENTER && data->type != P_CL2FE_REP_LIVE_CHECK) {
|
||||
|
||||
if (settings::VERBOSITY > 0) {
|
||||
std::cerr << "OpenFusion: SHARD PKT OUT-OF-SEQ. PacketType: " <<
|
||||
Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// run the appropriate packet handler
|
||||
if (ShardPackets.find(data->type) != ShardPackets.end())
|
||||
ShardPackets[data->type](sock, data);
|
||||
} else if (settings::VERBOSITY > 0) {
|
||||
else if (settings::VERBOSITY > 0)
|
||||
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||
}
|
||||
|
||||
/*
|
||||
* We must re-check if the player is still connected in case
|
||||
* they were dropped when handling the packet.
|
||||
*/
|
||||
if (PlayerManager::players.find(sock) != PlayerManager::players.end())
|
||||
PlayerManager::players[sock]->lastHeartbeat = getTime();
|
||||
}
|
||||
@@ -99,7 +79,14 @@ void CNShardServer::_killConnection(CNSocket* cns) {
|
||||
if (PlayerManager::players.find(cns) == PlayerManager::players.end())
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(cns);
|
||||
|
||||
int64_t key = plr->SerialKey;
|
||||
|
||||
PlayerManager::removePlayer(cns); // removes the player from the list and saves it to DB
|
||||
|
||||
// remove from CNSharedData
|
||||
CNSharedData::erasePlayer(key);
|
||||
}
|
||||
|
||||
void CNShardServer::killConnection(CNSocket *cns) {
|
||||
@@ -115,10 +102,6 @@ void CNShardServer::kill() {
|
||||
void CNShardServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
|
||||
// do not evaluate timers if the server is shutting down
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
for (TimerEvent& event : Timers) {
|
||||
if (event.scheduledEvent == 0) {
|
||||
// event hasn't been queued yet, go ahead and do that
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "Email.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
@@ -39,42 +38,9 @@ static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The longest protocol message is for an email.
|
||||
*
|
||||
* message type + two formatted character names + the other formatting chars
|
||||
* + the email title + the email body = ~1154
|
||||
*
|
||||
* The body can grow to twice its usual size (512) in the pathological case
|
||||
* where every character is a newline.
|
||||
*
|
||||
* Multi-byte Unicode characters aren't a factor, as they should've been
|
||||
* stripped out by sanitizeText().
|
||||
*/
|
||||
#define BUFSIZE 2048
|
||||
|
||||
static int process_email(char *buff, std::string email) {
|
||||
strncpy(buff, "email ", 6);
|
||||
int i = 6;
|
||||
|
||||
for (char c : email) {
|
||||
if (i == BUFSIZE-2)
|
||||
break;
|
||||
|
||||
buff[i++] = c;
|
||||
|
||||
// indent each line to prevent "endemail" spoofing
|
||||
if (c == '\n')
|
||||
buff[i++] = '\t';
|
||||
}
|
||||
|
||||
buff[i++] = '\n';
|
||||
return i;
|
||||
}
|
||||
|
||||
static void tick(CNServer *serv, time_t delta) {
|
||||
std::lock_guard<std::mutex> lock(sockLock);
|
||||
char buff[BUFSIZE];
|
||||
char buff[256];
|
||||
int n;
|
||||
|
||||
auto it = sockets.begin();
|
||||
@@ -104,17 +70,6 @@ outer:
|
||||
goto outer;
|
||||
}
|
||||
|
||||
// emails
|
||||
for (auto& str : Email::dump) {
|
||||
n = process_email(buff, str);
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
goto outer;
|
||||
|
||||
if (!transmit(it, (char*)"endemail\n", 9))
|
||||
goto outer;
|
||||
}
|
||||
|
||||
if (!transmit(it, (char*)"end\n", 4))
|
||||
continue;
|
||||
|
||||
@@ -122,7 +77,6 @@ outer:
|
||||
}
|
||||
|
||||
Chat::dump.clear();
|
||||
Email::dump.clear();
|
||||
}
|
||||
|
||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||
|
@@ -7,20 +7,16 @@
|
||||
|
||||
// defaults :)
|
||||
int settings::VERBOSITY = 1;
|
||||
bool settings::SANDBOX = true;
|
||||
|
||||
int settings::LOGINPORT = 23000;
|
||||
bool settings::APPROVEALLNAMES = true;
|
||||
bool settings::AUTOCREATEACCOUNTS = true;
|
||||
int settings::DBSAVEINTERVAL = 240;
|
||||
|
||||
int settings::SHARDPORT = 23001;
|
||||
std::string settings::SHARDSERVERIP = "127.0.0.1";
|
||||
bool settings::LOCALHOSTWORKAROUND = true;
|
||||
time_t settings::TIMEOUT = 60000;
|
||||
int settings::VIEWDISTANCE = 25600;
|
||||
bool settings::SIMULATEMOBS = true;
|
||||
bool settings::ANTICHEAT = true;
|
||||
|
||||
// default spawn point
|
||||
#ifndef ACADEMY
|
||||
@@ -39,6 +35,7 @@ int settings::SPAWN_ANGLE = 130;
|
||||
std::string settings::DBPATH = "database.db";
|
||||
std::string settings::TDATADIR = "tdata/";
|
||||
std::string settings::PATCHDIR = "tdata/patch/";
|
||||
std::string settings::SCRIPTSDIR = "scripts";
|
||||
|
||||
std::string settings::NPCJSON = "NPCs.json";
|
||||
std::string settings::MOBJSON = "mobs.json";
|
||||
@@ -78,15 +75,12 @@ void settings::init() {
|
||||
return;
|
||||
}
|
||||
|
||||
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
|
||||
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
|
||||
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
|
||||
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
||||
APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
|
||||
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
|
||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
|
||||
SHARDSERVERIP = reader.Get("shard", "ip", SHARDSERVERIP);
|
||||
LOCALHOSTWORKAROUND = reader.GetBoolean("shard", "localhostworkaround", LOCALHOSTWORKAROUND);
|
||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
|
||||
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
|
||||
VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE);
|
||||
SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);
|
||||
@@ -109,7 +103,6 @@ void settings::init() {
|
||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
||||
|
@@ -2,15 +2,11 @@
|
||||
|
||||
namespace settings {
|
||||
extern int VERBOSITY;
|
||||
extern bool SANDBOX;
|
||||
extern int LOGINPORT;
|
||||
extern bool APPROVEALLNAMES;
|
||||
extern bool AUTOCREATEACCOUNTS;
|
||||
extern int DBSAVEINTERVAL;
|
||||
extern int SHARDPORT;
|
||||
extern std::string SHARDSERVERIP;
|
||||
extern bool LOCALHOSTWORKAROUND;
|
||||
extern bool ANTICHEAT;
|
||||
extern time_t TIMEOUT;
|
||||
extern int VIEWDISTANCE;
|
||||
extern bool SIMULATEMOBS;
|
||||
@@ -31,6 +27,7 @@ namespace settings {
|
||||
extern std::string PATCHDIR;
|
||||
extern std::string ENABLEDPATCHES;
|
||||
extern std::string TDATADIR;
|
||||
extern std::string SCRIPTSDIR;
|
||||
extern int EVENTMODE;
|
||||
extern bool MONITORENABLED;
|
||||
extern int MONITORPORT;
|
||||
|
2
tdata
2
tdata
Submodule tdata updated: cc65dbb402...06fb2e533e
2
vendor/bcrypt/crypt_blowfish.c
vendored
2
vendor/bcrypt/crypt_blowfish.c
vendored
@@ -58,7 +58,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef __i386__
|
||||
#define BF_ASM 0
|
||||
#define BF_ASM 1
|
||||
#define BF_SCALE 1
|
||||
#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
|
||||
#define BF_ASM 0
|
||||
|
Reference in New Issue
Block a user