mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-09-26 01:50:23 +00:00
Compare commits
254 Commits
Author | SHA1 | Date | |
---|---|---|---|
10534886b8 | |||
![]() |
d713fafb1c | ||
e97b58ccaf | |||
55be58cc24 | |||
![]() |
deb3e5b897 | ||
4a5857a126 | |||
![]() |
f7e7f99017 | ||
![]() |
6d9d66954e | ||
bbd695cad1 | |||
3ce8cf2129 | |||
7f716c7278 | |||
![]() |
dd54668697 | ||
![]() |
ab5857e7e2 | ||
![]() |
da725d21e6 | ||
![]() |
6473951b9a | ||
![]() |
3050801399 | ||
![]() |
efd729710f | ||
![]() |
85530ef57f | ||
![]() |
b87f20e2dc | ||
![]() |
d4aed0abf4 | ||
![]() |
c1fd51b721 | ||
![]() |
bd34bb294c | ||
5a80c53e79 | |||
5784e77654 | |||
6ee5e6d1ae | |||
3586b76888 | |||
843b2e6e38 | |||
a8c8065920 | |||
63414ea9a2 | |||
599bbedd8c | |||
c792fb9d0d | |||
f8d64234d7 | |||
901e011740 | |||
![]() |
a53f38b87d | ||
f3b6f9619b | |||
4d687a82ea | |||
d99dad261c | |||
![]() |
1564cc7724 | ||
2b9d0f6bab | |||
9f280c2c31 | |||
bae834fefa | |||
1fe23b97fd | |||
0d0332e551 | |||
7a83a3b45c | |||
606384445c | |||
7caa73caca | |||
6e3d0868cb | |||
![]() |
5ed332d836 | ||
![]() |
42fc018097 | ||
![]() |
1b68b5e2e2 | ||
![]() |
5009fe1994 | ||
![]() |
4873eba160 | ||
d4d0f388c4 | |||
ce58411ff8 | |||
661070dc3a | |||
![]() |
b8f586bc10 | ||
131eb94919 | |||
755bb75306 | |||
5015e2575d | |||
![]() |
a9837d6c1b | ||
![]() |
47da895544 | ||
a852c26e5e | |||
316239dadc | |||
f5939353b1 | |||
f82d203377 | |||
![]() |
cbd04c2ce6 | ||
![]() |
1b55ab44e3 | ||
21b7500e13 | |||
35a2110698 | |||
8a144a359f | |||
2fe4b2bac1 | |||
839f9a813c | |||
4fe4aeb0d3 | |||
600c26024b | |||
![]() |
3c734e3e76 | ||
4cd7b7cb53 | |||
8ff97ec0b3 | |||
5f65c1530b | |||
![]() |
2c831ee115 | ||
![]() |
941e986ee1 | ||
![]() |
1eb806af58 | ||
ab990116a2 | |||
fb281b0237 | |||
3f35d2e960 | |||
884b844d65 | |||
4079806436 | |||
efb3df7133 | |||
c9be0e5402 | |||
97c2c532f1 | |||
a324f3fda9 | |||
6ea47ddb56 | |||
2b4a1387f9 | |||
dacae8d6de | |||
![]() |
b03cc563eb | ||
![]() |
dd374b2ea1 | ||
f8f2088e38 | |||
062302a7aa | |||
a5d3160588 | |||
4fea2ae896 | |||
![]() |
56a92d302f | ||
![]() |
0ea5712f8c | ||
b4fb449e69 | |||
4fa6618abb | |||
43d268e142 | |||
9657aaf202 | |||
dccd92aff9 | |||
1b35aab958 | |||
6b577ed642 | |||
![]() |
0931cf1fbc | ||
![]() |
805c64eff0 | ||
![]() |
8c63cd575c | ||
5068b38c5e | |||
231a4a441b | |||
![]() |
d4f1515f5d | ||
e5a24bcb70 | |||
874479d1cf | |||
8f84c4c2f8 | |||
![]() |
320a82997a | ||
![]() |
d87306930d | ||
![]() |
86c1cbd0f2 | ||
279cb78d5f | |||
72d625fd8d | |||
![]() |
db33ca2bbb | ||
cfb3d25bc5 | |||
1f18104a6f | |||
006d1000c7 | |||
![]() |
1874f1081b | ||
![]() |
df936e8c9c | ||
![]() |
72c16587e0 | ||
c33f218e56 | |||
4caca07856 | |||
63c14aff58 | |||
78930916ad | |||
![]() |
9cfced88c9 | ||
![]() |
f2596bfb6a | ||
![]() |
65bd2d120b | ||
![]() |
7bcdc111da | ||
![]() |
016c48645e | ||
![]() |
09f1f67778 | ||
![]() |
7dfc888552 | ||
![]() |
6f05f0f2c8 | ||
c722044bf5 | |||
![]() |
076f89927d | ||
95a79ec815 | |||
![]() |
7ba9b9a54f | ||
ba5998d53a | |||
ac1fd1e5be | |||
94ab5b8b64 | |||
![]() |
e0e474924d | ||
8896a103ba | |||
![]() |
0931c88541 | ||
![]() |
5a58908462 | ||
![]() |
00f64ce992 | ||
![]() |
153b3a9ef5 | ||
![]() |
adf017b07c | ||
5d8bb7f8a5 | |||
![]() |
2c8243e136 | ||
12fbdc9621 | |||
a768a4f539 | |||
d6357197d3 | |||
4cc1cf4f7e | |||
b67a0b6946 | |||
![]() |
5e0948ea93 | ||
![]() |
90134cd1fa | ||
321dca3f79 | |||
113ecc8f60 | |||
dc9de5a54a | |||
0fc072d591 | |||
24341c578a | |||
![]() |
a05bb15697 | ||
![]() |
135424b855 | ||
![]() |
cb984c029b | ||
![]() |
a5ffe26c44 | ||
![]() |
6a78a301c9 | ||
![]() |
a5c40b66f5 | ||
![]() |
94583e534b | ||
![]() |
ff7c78d545 | ||
![]() |
77df7b7160 | ||
![]() |
6eb21e6d67 | ||
228a181b74 | |||
![]() |
27df1bd7d0 | ||
![]() |
6a05ce4504 | ||
![]() |
c6ec1c46c2 | ||
![]() |
d1c5e272a8 | ||
![]() |
9bb19efc99 | ||
![]() |
7757238a47 | ||
![]() |
4d437bcb34 | ||
![]() |
5dbca0b7b1 | ||
ce9285bab5 | |||
![]() |
cd7fec2d5b | ||
d9d781c37d | |||
![]() |
b929d12902 | ||
9f78735caa | |||
31ef03610d | |||
22e3e9e4de | |||
f4db0830ba | |||
001564a257 | |||
e79f179628 | |||
027b783571 | |||
e03da83ff3 | |||
5efc8ac089 | |||
efda6673b5 | |||
f7571607ba | |||
![]() |
501d153894 | ||
4d21410980 | |||
![]() |
148d90f4f1 | ||
![]() |
a976fef2b4 | ||
![]() |
da8c833587 | ||
![]() |
c91022030c | ||
![]() |
f55cc8f36d | ||
9cc5f3e4d5 | |||
131997f34f | |||
ed86bc9160 | |||
![]() |
38d5998a6e | ||
![]() |
c7189a5cef | ||
![]() |
de15e2004b | ||
![]() |
480cca82fa | ||
![]() |
3d83f93167 | ||
![]() |
1d9a7139a8 | ||
![]() |
2fd7a8c6fc | ||
![]() |
fc57cae37d | ||
![]() |
3cfec7aab3 | ||
![]() |
29e53117e7 | ||
c1ac2250a0 | |||
a4716b0164 | |||
![]() |
91f512d740 | ||
![]() |
4880e4af12 | ||
fe370df534 | |||
![]() |
2b1a028b3d | ||
be99714495 | |||
4c06163b51 | |||
0c97969757 | |||
![]() |
4e7352da66 | ||
5747c24479 | |||
579aa9d31d | |||
![]() |
3865249387 | ||
468840c9ea | |||
52f02168bc | |||
ddb5f782b7 | |||
3665dc2c93 | |||
ae654f996c | |||
e33b7f20e9 | |||
5b49e71de7 | |||
3172724596 | |||
8887c6349b | |||
![]() |
6e0b101a76 | ||
29cde56fb1 | |||
e65f07780b | |||
eb1ad6bb37 | |||
![]() |
e409b8bb39 | ||
45a33758a5 | |||
![]() |
266fddbffa | ||
e90ae10746 | |||
b797993014 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/contrib/* linguist-vendored
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,3 +11,5 @@ build/
|
||||
.vs/
|
||||
.idea/
|
||||
*.db
|
||||
version.h
|
||||
infer-out
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "tdata"]
|
||||
path = tdata
|
||||
url = https://github.com/OpenFusionProject/tabledata.git
|
@@ -3,6 +3,8 @@ project(OpenFusion)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# OpenFusion supports multiple packet/struct versions
|
||||
# 104 is the default version to build which can be changed
|
||||
# For example: cmake -B build -DPROTOCOL_VERSION=728
|
||||
@@ -33,7 +35,9 @@ endif()
|
||||
|
||||
include_directories(src)
|
||||
|
||||
file(GLOB_RECURSE SOURCES src/**.cpp src/**.hpp src/**.c src/**.h)
|
||||
file(GLOB_RECURSE SOURCES src/**.cpp src/**.hpp src/**.c src/**.h version.h)
|
||||
|
||||
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
|
||||
|
||||
add_executable(openfusion ${SOURCES})
|
||||
|
||||
@@ -45,4 +49,5 @@ set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(openfusion pthread)
|
||||
target_link_libraries(openfusion dl)
|
||||
endif()
|
||||
|
32
Makefile
32
Makefile
@@ -1,8 +1,9 @@
|
||||
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=-O3 #-g3 -fsanitize=address
|
||||
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address
|
||||
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
|
||||
LDFLAGS=-lpthread -ldl #-g3 -fsanitize=address
|
||||
# specifies the name of our exectuable
|
||||
SERVER=bin/fusion
|
||||
@@ -15,7 +16,9 @@ PROTOCOL_VERSION?=104
|
||||
WIN_CC=x86_64-w64-mingw32-gcc
|
||||
WIN_CXX=x86_64-w64-mingw32-g++
|
||||
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
|
||||
WIN_CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O3 -fno-tree-dce -fno-inline-small-functions -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address
|
||||
WIN_CXX_VANILLA_MINGW_OPT_DISABLES=-fno-tree-dce -fno-inline-small-functions
|
||||
WIN_CXX_MSYS2_MINGW_OPT_DISABLES=-fno-tree-dce -fno-tree-fre -fno-tree-vrp -fno-ipa-sra
|
||||
WIN_CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O3 $(WIN_CXX_OPT_DISABLES) -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
|
||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 #-g3 -fsanitize=address
|
||||
WIN_SERVER=bin/winfusion.exe
|
||||
|
||||
@@ -28,23 +31,25 @@ CSRC=\
|
||||
|
||||
CXXSRC=\
|
||||
src/ChatManager.cpp\
|
||||
src/CombatManager.cpp\
|
||||
src/CNLoginServer.cpp\
|
||||
src/CNProtocol.cpp\
|
||||
src/CNShardServer.cpp\
|
||||
src/CNShared.cpp\
|
||||
src/CNStructs.cpp\
|
||||
src/Database.cpp\
|
||||
src/Defines.cpp\
|
||||
src/main.cpp\
|
||||
src/MissionManager.cpp\
|
||||
src/MobManager.cpp\
|
||||
src/NanoManager.cpp\
|
||||
src/ItemManager.cpp\
|
||||
src/NPCManager.cpp\
|
||||
src/Player.cpp\
|
||||
src/PlayerManager.cpp\
|
||||
src/settings.cpp\
|
||||
src/TransportManager.cpp\
|
||||
src/TableData.cpp\
|
||||
src/ChunkManager.cpp\
|
||||
src/BuddyManager.cpp\
|
||||
src/GroupManager.cpp\
|
||||
|
||||
# headers (for timestamp purposes)
|
||||
CHDR=\
|
||||
@@ -61,7 +66,6 @@ CXXHDR=\
|
||||
src/contrib/INIReader.hpp\
|
||||
src/contrib/JSON.hpp\
|
||||
src/ChatManager.hpp\
|
||||
src/CombatManager.hpp\
|
||||
src/CNLoginServer.hpp\
|
||||
src/CNProtocol.hpp\
|
||||
src/CNShardServer.hpp\
|
||||
@@ -72,6 +76,7 @@ CXXHDR=\
|
||||
src/contrib/INIReader.hpp\
|
||||
src/contrib/JSON.hpp\
|
||||
src/MissionManager.hpp\
|
||||
src/MobManager.hpp\
|
||||
src/NanoManager.hpp\
|
||||
src/ItemManager.hpp\
|
||||
src/NPCManager.hpp\
|
||||
@@ -79,6 +84,10 @@ CXXHDR=\
|
||||
src/PlayerManager.hpp\
|
||||
src/settings.hpp\
|
||||
src/TransportManager.hpp\
|
||||
src/TableData.hpp\
|
||||
src/ChunkManager.hpp\
|
||||
src/BuddyManager.hpp\
|
||||
src/GroupManager.hpp\
|
||||
|
||||
COBJ=$(CSRC:.c=.o)
|
||||
CXXOBJ=$(CXXSRC:.cpp=.o)
|
||||
@@ -98,6 +107,7 @@ windows : CFLAGS=$(WIN_CFLAGS)
|
||||
windows : CXXFLAGS=$(WIN_CXXFLAGS)
|
||||
windows : LDFLAGS=$(WIN_LDFLAGS)
|
||||
windows : SERVER=$(WIN_SERVER)
|
||||
windows : WIN_CXX_OPT_DISABLES=$(if $(filter-out 10, $(shell $(WIN_CXX) -dumpversion | egrep -o ^[0-9]+)), $(WIN_CXX_VANILLA_MINGW_OPT_DISABLES), $(WIN_CXX_MSYS2_MINGW_OPT_DISABLES))
|
||||
|
||||
.SUFFIX: .o .c .cpp .h .hpp
|
||||
|
||||
@@ -114,13 +124,19 @@ $(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
|
||||
mkdir -p bin
|
||||
$(CXX) $(OBJ) $(LDFLAGS) -o $(SERVER)
|
||||
|
||||
# compatibility with how cmake injects GIT_VERSION
|
||||
version.h:
|
||||
touch version.h
|
||||
|
||||
src/main.o: version.h
|
||||
|
||||
.PHONY: all windows clean nuke
|
||||
|
||||
# only gets rid of OpenFusion objects, so we don't need to
|
||||
# recompile the libs every time
|
||||
clean:
|
||||
rm -f src/*.o $(SERVER) $(WIN_SERVER)
|
||||
rm -f src/*.o $(SERVER) $(WIN_SERVER) version.h
|
||||
|
||||
# gets rid of all compiled objects, including the libraries
|
||||
nuke:
|
||||
rm -f $(OBJ) $(SERVER) $(WIN_SERVER)
|
||||
rm -f $(OBJ) $(SERVER) $(WIN_SERVER) version.h
|
||||
|
16
README.md
16
README.md
@@ -3,7 +3,7 @@
|
||||
[](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
|
||||
[](https://discord.gg/DYavckB)
|
||||
|
||||
OpenFusion is a landwalker server for FusionFall. It currently supports versions `beta-20100104` and `beta-20100728` of the original game.
|
||||
OpenFusion is a reverse-engineered server for FusionFall. It currently primarily targets version `beta-20100104` and has some support for version `beta-20100728` of the original game.
|
||||
|
||||
Further documentation pending.
|
||||
|
||||
@@ -11,7 +11,7 @@ Further documentation pending.
|
||||
|
||||
tl;dr:
|
||||
|
||||
1. Download the client+server bundle from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.0/OpenFusion.zip).
|
||||
1. Download the client+server bundle from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.1/OpenFusion.zip).
|
||||
2. Run `FreeClient/installUnity.bat` once
|
||||
|
||||
From then on, any time you want to run the "game":
|
||||
@@ -91,15 +91,15 @@ A detailed guide is available [in the wiki](https://github.com/OpenFusionProject
|
||||
|
||||
If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## "Gameplay"
|
||||
## Gameplay
|
||||
|
||||
Notice the quotes. This is not a full-fledged game that can be played.
|
||||
It's what's called a landwalker; enough of the server has been implemented to allow players to run around in the game world, and not much else.
|
||||
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
|
||||
The server is not yet complete, however, and some functionality is still missing.
|
||||
|
||||
Because the server is still in development, ordinary players are allowed access to a few admin commands:
|
||||
|
||||

|
||||
|
||||
To make your landwalking experience more pleasant, you can make use of a few admin commands to get around easier:
|
||||
|
||||
### Movement commands
|
||||
* A `/speed` of around 2400 or 3000 is nice.
|
||||
* A `/jump` of about 50 will send you soaring
|
||||
@@ -118,4 +118,4 @@ To make your landwalking experience more pleasant, you can make use of a few adm
|
||||
|
||||
## Accounts
|
||||
|
||||
A basic account system has been added, when logging in if the username doesn't exist in the database, a new account with the provided password will be made and you'll be automatically logged in. Otherwise a login attempt will be made. A username must be between 4 and 32 characters, and a password must be between 8 and 32 characters otherwise the account will be rejected. Characters currently save only upon creation, any items add/traded will not be saved.
|
||||
A basic account system has been added, when logging in if the username doesn't exist in the database, a new account with the provided password will be made and you'll be automatically logged in. Otherwise a login attempt will be made. A username must be between 4 and 32 characters, and a password must be between 8 and 32 characters otherwise the account will be rejected.
|
||||
|
51
config.ini
51
config.ini
@@ -9,29 +9,56 @@ verbosity=1
|
||||
[login]
|
||||
# must be kept in sync with loginInfo.php
|
||||
port=8001
|
||||
# enables two randomly generated characters in the
|
||||
# character selection menu for convenience
|
||||
randomcharacters=true
|
||||
# will all custom names be approved instantly?
|
||||
acceptallcustomnames=true
|
||||
# how often should everything be flushed to the database?
|
||||
# the default is 4 minutes
|
||||
dbsaveinterval=240
|
||||
|
||||
# Shard Server configuration
|
||||
[shard]
|
||||
port=8002
|
||||
ip=127.0.0.1
|
||||
# distance at which other players and NPCs become visible
|
||||
playerdistance=20000
|
||||
npcdistance=16000
|
||||
# distance at which other players and NPCs become visible.
|
||||
# this value is used for calculating chunk size
|
||||
viewdistance=30000
|
||||
# time, in milliseconds, to wait before kicking a non-responsive client
|
||||
# default is 1 minute
|
||||
timeout=60000
|
||||
# should mobs move around and fight back?
|
||||
# can be disabled for easier mob placement
|
||||
simulatemobs=true
|
||||
# little message players see when they enter the game
|
||||
motd=Welcome to OpenFusion!
|
||||
# NPC json data
|
||||
npcdata=data/NPCs.json
|
||||
# warp target json data
|
||||
warpdata=data/warps.json
|
||||
npcdata=tdata/NPCs.json
|
||||
# xdt json data
|
||||
xdtdata=tdata/xdt.json
|
||||
# mob json
|
||||
mobdata=data/mobs.json
|
||||
# is everyone a GM?
|
||||
gm=true
|
||||
mobdata=tdata/mobs.json
|
||||
# path json
|
||||
pathdata=tdata/paths.json
|
||||
# drop json
|
||||
dropdata=tdata/drops.json
|
||||
# gruntwork output (this is what you submit)
|
||||
gruntwork=tdata/gruntwork.json
|
||||
|
||||
# account permission level that will be set upon character creation
|
||||
# 1 = default, will allow *all* commands
|
||||
# 30 = allow some more "abusable" commands such as /summon
|
||||
# 50 = only allow cheat commands, like /itemN and /speed
|
||||
# 99 = standard user account, no cheats allowed
|
||||
# any number higher than 50 will disable commands
|
||||
accountlevel=1
|
||||
|
||||
# should mobs drop event crates?
|
||||
# 0 = no event
|
||||
# 1 = Knishmas
|
||||
# 2 = Halloween
|
||||
# 3 = Easter
|
||||
eventmode=0
|
||||
# percent chance of an event crate dropping each kill
|
||||
eventcratechance=10
|
||||
|
||||
# spawn coordinates (Z is height)
|
||||
# the supplied defaults are at Sector V (future)
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
441
src/BuddyManager.cpp
Normal file
441
src/BuddyManager.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "BuddyManager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
void BuddyManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REQUEST_MAKE_BUDDY, requestBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY, reqBuddyByName);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ACCEPT_MAKE_BUDDY, reqAcceptBuddy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY, reqFindNameBuddyAccept);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE, reqBuddyFreechat);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE, reqBuddyMenuchat);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_GET_BUDDY_STATE, reqPktGetBuddyState);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SET_BUDDY_BLOCK, reqBuddyBlock);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REMOVE_BUDDY, reqBuddyDelete);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BUDDY_WARP, reqBuddyWarp);
|
||||
}
|
||||
|
||||
// Buddy request
|
||||
void BuddyManager::requestBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REQUEST_MAKE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_REQUEST_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_REQUEST_MAKE_BUDDY*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
resp.iRequestID = plr.plr->iID;
|
||||
resp.iBuddyID = plr.plr->iID;
|
||||
resp.iBuddyPCUID = plr.plr->PCStyle.iPC_UID;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC)); // informs the player that the request was sent
|
||||
requestedBuddy(otherSock, plrReq, plr); // The other player will see the request
|
||||
|
||||
}
|
||||
|
||||
// Sending buddy request by player name
|
||||
void BuddyManager::reqBuddyByName(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY)) {
|
||||
return; // malformed packet
|
||||
}
|
||||
|
||||
sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
int sizeOfRes = sizeof(pkt->szFirstName) / 9; // Maximum size of a player's first name
|
||||
int sizeOfLNRes = sizeof(pkt->szLastName) / 17; // Maximum size of a player's last name
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
|
||||
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
|
||||
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) { // This long line of gorgeous parameters is to check if the player's name matches :eyes:
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resp.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
|
||||
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC));
|
||||
|
||||
}
|
||||
|
||||
// Accepting buddy request
|
||||
void BuddyManager::reqAcceptBuddy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
if (pkt->iAcceptFlag == 1) {
|
||||
//resp.iBuddySlot = 0; // hard-coding this for now
|
||||
resp.BuddyInfo.iID = pkt->iBuddyID;
|
||||
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr.plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo); // saves buddy data to player's buddylist
|
||||
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plrReq->iPCState;
|
||||
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(otherSock, resp.BuddyInfo); // saves requester's data to this player's buddylist
|
||||
}
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyID = pkt->iBuddyID;
|
||||
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
|
||||
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL)); // tells the requester that the player declined
|
||||
}
|
||||
}
|
||||
|
||||
// Accepting buddy request from the find name request
|
||||
void BuddyManager::reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY)) {
|
||||
return; // malformed packet
|
||||
}
|
||||
|
||||
sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY*)data->buf;
|
||||
Player* plrReq = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plrReq == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
int sizeOfRes = sizeof(pkt->szFirstName) / 9;
|
||||
int sizeOfLNRes = sizeof(pkt->szLastName) / 17;
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
|
||||
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
|
||||
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) {
|
||||
otherSock = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerView& plr = PlayerManager::players[otherSock];
|
||||
|
||||
if (pkt->iAcceptFlag == 1) {
|
||||
//resp.iBuddySlot = 0; // hard-coding this for now
|
||||
//resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr.plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo);
|
||||
|
||||
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
|
||||
resp.BuddyInfo.iID = plrReq->iID;
|
||||
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plrReq->iPCState;
|
||||
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(otherSock, resp.BuddyInfo);
|
||||
}
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
|
||||
|
||||
declineResp.iErrorCode = 6; // Buddy declined notification
|
||||
declineResp.iBuddyID = plr.plr->iID;
|
||||
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
|
||||
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Buddy freechatting
|
||||
void BuddyManager::reqBuddyFreechat(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
|
||||
otherSock = pair.first;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // sends the message to the buddy.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Buddy menuchat
|
||||
void BuddyManager::reqBuddyMenuchat(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
CNSocket* otherSock = sock;
|
||||
|
||||
resp.iFromPCUID = plr->PCStyle.iPC_UID;
|
||||
resp.iToPCUID = pkt->iBuddyPCUID;
|
||||
resp.iEmoteCode = pkt->iEmoteCode;
|
||||
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
|
||||
|
||||
for (auto pair : PlayerManager::players) {
|
||||
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
|
||||
otherSock = pair.first;
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // sends the message to the buddy.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getting buddy state
|
||||
void BuddyManager::reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp);
|
||||
INITSTRUCT(sBuddyBaseInfo, buddyInfo);
|
||||
|
||||
for (int BuddySlot = 0; BuddySlot < 50; BuddySlot++) {
|
||||
resp.aBuddyState[BuddySlot] = 1; // this sets every buddy to online. Will get the pcstate right directly from the DB.
|
||||
resp.aBuddyID[BuddySlot] = buddyInfo.iID;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_GET_BUDDY_STATE_SUCC, sizeof(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC));
|
||||
}
|
||||
}
|
||||
|
||||
// Blocking the buddy
|
||||
void BuddyManager::reqBuddyBlock(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SET_BUDDY_BLOCK))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SET_BUDDY_BLOCK* pkt = (sP_CL2FE_REQ_SET_BUDDY_BLOCK*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, resp);
|
||||
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, sizeof(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC));
|
||||
|
||||
}
|
||||
|
||||
// Deleting the buddy
|
||||
void BuddyManager::reqBuddyDelete(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REMOVE_BUDDY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_REMOVE_BUDDY* pkt = (sP_CL2FE_REQ_REMOVE_BUDDY*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp);
|
||||
|
||||
resp.iBuddyPCUID = pkt->iBuddyPCUID;
|
||||
resp.iBuddySlot = pkt->iBuddySlot;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REMOVE_BUDDY_SUCC));
|
||||
}
|
||||
|
||||
// Warping to buddy
|
||||
void BuddyManager::reqBuddyWarp(CNSocket* sock, CNPacketData* data) {} // stub
|
||||
|
||||
#pragma region Helper methods
|
||||
|
||||
void BuddyManager::requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr) {
|
||||
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, resp);
|
||||
|
||||
resp.iRequestID = plrReq->iID;
|
||||
resp.iBuddyID = plr.plr->iID;
|
||||
|
||||
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
|
||||
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER)); // player get the buddy request.
|
||||
|
||||
}
|
||||
|
||||
// Buddy list load
|
||||
void BuddyManager::buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo) {
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + sizeof(sBuddyBaseInfo);
|
||||
uint8_t respbuf[4096];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf;
|
||||
sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC));
|
||||
|
||||
resp->iID = BuddyInfo.iID;
|
||||
resp->iPCUID = BuddyInfo.iPCUID;
|
||||
|
||||
for (int i = 0; i < resp->iBuddyCnt && i < resp->iListNum; i++) {
|
||||
respdata->iID = BuddyInfo.iID;
|
||||
respdata->iPCUID = BuddyInfo.iPCUID;
|
||||
respdata->iGender = BuddyInfo.iGender;
|
||||
respdata->iPCState = BuddyInfo.iPCState;
|
||||
respdata->iNameCheckFlag = BuddyInfo.iNameCheckFlag;
|
||||
respdata->bBlocked = 0;
|
||||
respdata->bFreeChat = 1;
|
||||
memcpy(respdata->szFirstName, BuddyInfo.szFirstName, sizeof(BuddyInfo.szFirstName));
|
||||
memcpy(respdata->szLastName, BuddyInfo.szLastName, sizeof(BuddyInfo.szLastName));
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC, resplen); // updates/loads player's buddy list
|
||||
|
||||
}
|
||||
|
||||
// If the requested player accepts the buddy request, the requester's buddylist will get loaded up.
|
||||
void BuddyManager::otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr) {
|
||||
|
||||
// resp.iBuddySlot = 0; //hard-coding this for now
|
||||
resp.BuddyInfo.iID = BuddyID;
|
||||
resp.BuddyInfo.iPCUID = BuddyPCUID;
|
||||
resp.BuddyInfo.iNameCheckFlag = plr->PCStyle.iNameCheck;
|
||||
resp.BuddyInfo.iPCState = plr->iPCState;
|
||||
resp.BuddyInfo.iGender = plr->PCStyle.iGender;
|
||||
resp.BuddyInfo.bBlocked = 0;
|
||||
resp.BuddyInfo.bFreeChat = 1;
|
||||
|
||||
memcpy(resp.BuddyInfo.szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||
memcpy(resp.BuddyInfo.szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
|
||||
buddyList(sock, resp.BuddyInfo);
|
||||
}
|
||||
|
||||
// Check if the requested name matches the requested player's name
|
||||
bool BuddyManager::firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes) {
|
||||
// If lengths of array are not equal means
|
||||
// array are not equal
|
||||
if (sizeOfReq != sizeOfRes)
|
||||
return false;
|
||||
|
||||
// Sort both arrays
|
||||
std::sort(reqFirstName, reqFirstName + sizeOfReq);
|
||||
std::sort(resFirstName, resFirstName + sizeOfRes);
|
||||
|
||||
// Linearly compare elements
|
||||
for (int i = 0; i < sizeOfReq; i++)
|
||||
if (reqFirstName[i] != resFirstName[i])
|
||||
return false;
|
||||
|
||||
// If all elements were same.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuddyManager::lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes) {
|
||||
// If lengths of array are not equal means
|
||||
// array are not equal
|
||||
if (sizeOfLNReq != sizeOfLNRes)
|
||||
return false;
|
||||
|
||||
// Sort both arrays
|
||||
std::sort(reqLastName, reqLastName + sizeOfLNReq);
|
||||
std::sort(resLastName, resLastName + sizeOfLNRes);
|
||||
|
||||
// Linearly compare elements
|
||||
for (int i = 0; i < sizeOfLNReq; i++)
|
||||
if (reqLastName[i] != resLastName[i])
|
||||
return false;
|
||||
|
||||
// If all elements were same.
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
44
src/BuddyManager.hpp
Normal file
44
src/BuddyManager.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace BuddyManager {
|
||||
void init();
|
||||
|
||||
// Buddy requests
|
||||
void requestBuddy(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyByName(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy accepting
|
||||
void reqAcceptBuddy(CNSocket* sock, CNPacketData* data);
|
||||
void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy Messaging
|
||||
void reqBuddyFreechat(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyMenuchat(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Getting buddy state
|
||||
void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Blocking/removing buddies
|
||||
void reqBuddyBlock(CNSocket* sock, CNPacketData* data);
|
||||
void reqBuddyDelete(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Buddy warping
|
||||
void reqBuddyWarp(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// helper methods
|
||||
void requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr);
|
||||
void buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo); // updates the buddylist
|
||||
void otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr); // tells the other player that they are now buddies with the requester.
|
||||
|
||||
// Name checks
|
||||
bool firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes); // checks if the request and requested player's first names match
|
||||
bool lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes); // checks if the request and requested player's last names match
|
||||
}
|
@@ -24,57 +24,62 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_LOGIN))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
|
||||
//TODO: implement better way of sending credentials
|
||||
std::string userLogin = U16toU8(login->szID);
|
||||
std::string userPassword = U16toU8(login->szPassword);
|
||||
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
|
||||
// TODO: implement better way of sending credentials
|
||||
std::string userLogin((char*)login->szCookie_TEGid);
|
||||
std::string userPassword((char*)login->szCookie_authid);
|
||||
|
||||
/*
|
||||
* Sometimes the client sends garbage cookie data.
|
||||
* Validate it as normal credentials instead of using a length check before falling back.
|
||||
*/
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
/*
|
||||
* The std::string -> char* -> std::string maneuver should remove any
|
||||
* trailing garbage after the null terminator.
|
||||
*/
|
||||
userLogin = std::string(U16toU8(login->szID).c_str());
|
||||
userPassword = std::string(U16toU8(login->szPassword).c_str());
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
int errorCode = 0;
|
||||
|
||||
//checking regex
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword))
|
||||
{
|
||||
errorCode = (int)LOGINERRORID::login_error;
|
||||
}
|
||||
else
|
||||
{
|
||||
// checking regex
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
errorCode = (int)LoginError::LOGIN_ERROR;
|
||||
} else {
|
||||
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin);
|
||||
//if account not found, create it
|
||||
if (findUser == nullptr)
|
||||
{
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
|
||||
loginSessions[sock].slot = 1;
|
||||
success = true;
|
||||
}
|
||||
//if user exists, check if password is correct
|
||||
else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword))
|
||||
{
|
||||
//check if account isn't currently in use
|
||||
if (CNLoginServer::isAccountInUse(findUser->AccountID) ||
|
||||
PlayerManager::isAccountInUse(findUser->AccountID))
|
||||
{
|
||||
errorCode = (int)LOGINERRORID::id_already_in_use;
|
||||
}
|
||||
//if not, login success
|
||||
else
|
||||
{
|
||||
// if account not found, make new one
|
||||
if (findUser == nullptr) {
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
|
||||
loginSessions[sock].slot = 1;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
success = true;
|
||||
|
||||
// if user exists, check if password is correct
|
||||
} else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) {
|
||||
/*calling this here to timestamp login attempt,
|
||||
* in order to make duplicate exit sanity check work*/
|
||||
Database::updateSelected(findUser->AccountID, findUser->Selected);
|
||||
// check if account isn't currently in use
|
||||
if (CNLoginServer::isAccountInUse(findUser->AccountID)) {
|
||||
errorCode = (int)LoginError::ID_ALREADY_IN_USE;
|
||||
} else { // if not, login success
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = findUser->AccountID;
|
||||
loginSessions[sock].slot = findUser->Selected;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorCode = (int)LOGINERRORID::id_and_password_do_not_match;
|
||||
} else {
|
||||
errorCode = (int)LoginError::ID_AND_PASSWORD_DO_NOT_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (success) {
|
||||
std::vector<Player> characters = Database::getCharacters(loginSessions[sock].userID);
|
||||
int charCount = characters.size();
|
||||
|
||||
@@ -87,18 +92,17 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iPaymentFlag = 1;
|
||||
resp.iOpenBetaFlag = 0;
|
||||
resp.uiSvrTime = getTime();
|
||||
|
||||
|
||||
// send the resp in with original key
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
||||
|
||||
// now send the characters :)
|
||||
|
||||
// now send the characters :)
|
||||
std::vector<Player>::iterator it;
|
||||
for (it = characters.begin(); it != characters.end(); it++)
|
||||
{
|
||||
for (it = characters.begin(); it != characters.end(); it++) {
|
||||
sP_LS2CL_REP_CHAR_INFO charInfo = sP_LS2CL_REP_CHAR_INFO();
|
||||
|
||||
charInfo.iSlot = (int8_t)it->slot;
|
||||
@@ -111,86 +115,80 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
charInfo.iY = it->y;
|
||||
charInfo.iZ = it->z;
|
||||
|
||||
//save character in session (for char select)
|
||||
// save character in session (for char select)
|
||||
int UID = it->iID;
|
||||
loginSessions[sock].characters[UID] = Player(*it);
|
||||
loginSessions[sock].characters[UID] = Player(*it);
|
||||
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
|
||||
|
||||
//temporary inventory stuff
|
||||
for (int i = 0; i < 4; i++) {
|
||||
//equip char creation clothes and lightning rifle
|
||||
// Equip info
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++) {
|
||||
charInfo.aEquip[i] = it->Equip[i];
|
||||
}
|
||||
|
||||
for (int i = 5; i < AEQUIP_COUNT; i++) {
|
||||
// empty equips
|
||||
charInfo.aEquip[i].iID = 0;
|
||||
charInfo.aEquip[i].iType = i;
|
||||
charInfo.aEquip[i].iOpt = 0;
|
||||
}
|
||||
|
||||
// set default to the first character
|
||||
if (it == characters.begin())
|
||||
loginSessions[sock].selectedChar = UID;
|
||||
|
||||
sock->sendPacket((void*)&charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
|
||||
sock->sendPacket((void*)&charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
|
||||
}
|
||||
}
|
||||
//Failure
|
||||
else {
|
||||
} else {
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
|
||||
|
||||
memcpy(resp.szID, login->szID, sizeof(char16_t) * 33);
|
||||
U8toU16(userLogin, resp.szID, sizeof(resp.szID));
|
||||
resp.iErrorCode = errorCode;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REP_LIVE_CHECK: {
|
||||
// stubbed, the client really doesn't care LOL
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHECK_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHECK_CHAR_NAME))
|
||||
return;
|
||||
|
||||
// naughty words allowed!!!!!!!! (also for some reason, the client will always show 'Player 0' if you manually type a name. It will show up for other connected players though)
|
||||
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
|
||||
//check if name is occupied
|
||||
if (Database::isNameFree(nameCheck))
|
||||
{
|
||||
// naughty words allowed!!!!!!!! (also for some reason, the client will always show 'Player + ID' if you manually type a name. It will show up for other connected players though)
|
||||
|
||||
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
|
||||
bool success = true;
|
||||
int errorcode = 0;
|
||||
|
||||
// check regex
|
||||
if (!CNLoginServer::isCharacterNameGood(U16toU8(nameCheck->szFirstName), U16toU8(nameCheck->szLastName))) {
|
||||
success = false;
|
||||
errorcode = 4;
|
||||
} else if (!Database::isNameFree(nameCheck)){ // check if name isn't already occupied
|
||||
success = false;
|
||||
errorcode = 1;
|
||||
}
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
if (success) {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC, resp);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
|
||||
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
|
||||
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
|
||||
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
|
||||
)
|
||||
|
||||
memcpy(resp.szFirstName, nameCheck->szFirstName, sizeof(char16_t) * 9);
|
||||
memcpy(resp.szLastName, nameCheck->szLastName, sizeof(char16_t) * 17);
|
||||
|
||||
// fr*ck allowed!!!
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
|
||||
resp.iErrorCode = 1;
|
||||
resp.iErrorCode = errorcode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_FAIL, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
case P_CL2LS_REQ_SAVE_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
|
||||
sP_CL2LS_REQ_SAVE_CHAR_NAME* save = (sP_CL2LS_REQ_SAVE_CHAR_NAME*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
|
||||
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2LS_REQ_SAVE_CHAR_NAME:" << std::endl;
|
||||
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
||||
@@ -203,6 +201,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t) * 9);
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC));
|
||||
|
||||
Database::updateSelected(loginSessions[sock].userID, save->iSlotNum);
|
||||
@@ -211,7 +211,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
case P_CL2LS_REQ_CHAR_CREATE: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_CREATE))
|
||||
return;
|
||||
|
||||
|
||||
sP_CL2LS_REQ_CHAR_CREATE* character = (sP_CL2LS_REQ_CHAR_CREATE*)data->buf;
|
||||
Database::finishCharacter(character);
|
||||
|
||||
@@ -232,12 +232,9 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "\tiEquipUBID: " << (int)character->sOn_Item.iEquipUBID << std::endl;
|
||||
std::cout << "\tiEquipLBID: " << (int)character->sOn_Item.iEquipLBID << std::endl;
|
||||
std::cout << "\tiEquipFootID: " << (int)character->sOn_Item.iEquipFootID << std::endl;
|
||||
)
|
||||
|
||||
Player player =
|
||||
Database::DbToPlayer(
|
||||
Database::getDbPlayerById(character->PCStyle.iPC_UID)
|
||||
);
|
||||
)
|
||||
|
||||
Player player = Database::getPlayer(character->PCStyle.iPC_UID);
|
||||
int64_t UID = player.iID;
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_CREATE_SUCC, resp);
|
||||
@@ -246,10 +243,12 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iLevel = player.level;
|
||||
resp.sOn_Item = character->sOn_Item;
|
||||
|
||||
//save player in session
|
||||
// save player in session
|
||||
loginSessions[sock].characters[UID] = Player(player);
|
||||
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
|
||||
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_CREATE_SUCC, sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC));
|
||||
Database::updateSelected(loginSessions[sock].userID, player.slot);
|
||||
break;
|
||||
@@ -259,18 +258,18 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_DELETE* del = (sP_CL2LS_REQ_CHAR_DELETE*)data->buf;
|
||||
int operationResult = Database::deleteCharacter(del->iPC_UID);
|
||||
|
||||
int operationResult = Database::deleteCharacter(del->iPC_UID, loginSessions[sock].userID);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_SUCC, resp);
|
||||
resp.iSlotNum = operationResult;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_SUCC, sizeof(sP_LS2CL_REP_CHAR_DELETE_SUCC));
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_SELECT: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_SELECT))
|
||||
return;
|
||||
|
||||
|
||||
// character selected
|
||||
sP_CL2LS_REQ_CHAR_SELECT* chararacter = (sP_CL2LS_REQ_CHAR_SELECT*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_SELECT_SUCC, resp);
|
||||
@@ -280,6 +279,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "\tPC_UID: " << chararacter->iPC_UID << std::endl;
|
||||
)
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
loginSessions[sock].selectedChar = chararacter->iPC_UID;
|
||||
Database::updateSelected(loginSessions[sock].userID, loginSessions[sock].characters[chararacter->iPC_UID].slot);
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_SELECT_SUCC, sizeof(sP_LS2CL_REP_CHAR_SELECT_SUCC));
|
||||
@@ -288,7 +289,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
case P_CL2LS_REQ_SHARD_SELECT: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SHARD_SELECT))
|
||||
return;
|
||||
|
||||
|
||||
// tell client to connect to the shard server
|
||||
sP_CL2LS_REQ_SHARD_SELECT* shard = (sP_CL2LS_REQ_SHARD_SELECT*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_SHARD_SELECT_SUCC, resp);
|
||||
@@ -317,23 +318,30 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
sP_CL2LS_REQ_SAVE_CHAR_TUTOR* save = (sP_CL2LS_REQ_SAVE_CHAR_TUTOR*)data->buf;
|
||||
Database::finishTutorial(save->iPC_UID);
|
||||
loginSessions[sock].characters[save->iPC_UID].PCStyle2.iTutorialFlag = 1;
|
||||
loginSessions[sock].characters[save->iPC_UID].Equip[0].iID = 328;
|
||||
loginSessions[sock].characters[save->iPC_UID].Equip[0].iType = 0;
|
||||
loginSessions[sock].characters[save->iPC_UID].Equip[0].iOpt = 1;
|
||||
// update character in session
|
||||
auto key = loginSessions[sock].characters[save->iPC_UID].FEKey;
|
||||
loginSessions[sock].characters[save->iPC_UID] = Player(Database::getPlayer(save->iPC_UID));
|
||||
loginSessions[sock].characters[save->iPC_UID].FEKey = key;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
// no response here
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHANGE_CHAR_NAME: {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHANGE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHANGE_CHAR_NAME* save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
|
||||
Database::changeName(save);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
|
||||
resp.iPC_UID = save->iPCUID;
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t)*9);
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
|
||||
resp.iSlotNum = save->iSlotNum;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC));
|
||||
break;
|
||||
}
|
||||
@@ -343,24 +351,26 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf;
|
||||
auto account = Database::findAccount(U16toU8(exit->szID));
|
||||
if (account == nullptr)
|
||||
break;
|
||||
|
||||
int accountId = account->AccountID;
|
||||
if (!exitDuplicate(accountId))
|
||||
PlayerManager::exitDuplicate(accountId);
|
||||
|
||||
// sanity check
|
||||
if (account == nullptr) {
|
||||
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
exitDuplicate(account->AccountID);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (settings::VERBOSITY)
|
||||
std::cerr << "OpenFusion: LOGIN UNIMPLM ERR. PacketType: " << Defines::p2str(CL2LS, data->type) << " (" << data->type << ")" << std::endl;
|
||||
break;
|
||||
/* Unimplemented CL2LS packets:
|
||||
P_CL2LS_CHECK_NAME_LIST - unused by the client
|
||||
P_CL2LS_REQ_SERVER_SELECT
|
||||
P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
|
||||
*/
|
||||
/*
|
||||
* Unimplemented CL2LS packets:
|
||||
* P_CL2LS_CHECK_NAME_LIST - unused by the client
|
||||
* P_CL2LS_REQ_SERVER_SELECT
|
||||
* P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,23 +382,39 @@ void CNLoginServer::killConnection(CNSocket* cns) {
|
||||
loginSessions.erase(cns);
|
||||
}
|
||||
|
||||
void CNLoginServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
static time_t lastCheck = 0;
|
||||
|
||||
if (currTime - lastCheck < 16000)
|
||||
return;
|
||||
lastCheck = currTime;
|
||||
|
||||
for (auto& pair : loginSessions) {
|
||||
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 32000) {
|
||||
pair.first->kill();
|
||||
continue;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REQ_LIVE_CHECK, pkt);
|
||||
pair.first->sendPacket((void*)&pkt, P_LS2CL_REQ_LIVE_CHECK, sizeof(sP_LS2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region helperMethods
|
||||
bool CNLoginServer::isAccountInUse(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++)
|
||||
{
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CNLoginServer::exitDuplicate(int accountId)
|
||||
{
|
||||
|
||||
bool CNLoginServer::exitDuplicate(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++)
|
||||
{
|
||||
if (it->second.userID == accountId)
|
||||
{
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId) {
|
||||
CNSocket* sock = it->first;
|
||||
INITSTRUCT(sP_LS2CL_REP_PC_EXIT_DUPLICATE, resp);
|
||||
resp.iErrorCode = 0;
|
||||
@@ -399,14 +425,21 @@ bool CNLoginServer::exitDuplicate(int accountId)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CNLoginServer::isLoginDataGood(std::string login, std::string password)
|
||||
{
|
||||
|
||||
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
|
||||
std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
|
||||
std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
|
||||
|
||||
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
|
||||
}
|
||||
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword)
|
||||
{
|
||||
|
||||
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) {
|
||||
return BCrypt::validatePassword(tryPassword, actualPassword);
|
||||
}
|
||||
|
||||
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
|
||||
std::regex firstnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
|
||||
std::regex lastnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
|
||||
return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck));
|
||||
}
|
||||
#pragma endregion helperMethods
|
||||
|
@@ -10,18 +10,19 @@ struct CNLoginData {
|
||||
std::map<int64_t, Player> characters;
|
||||
int64_t selectedChar;
|
||||
int userID; int slot;
|
||||
time_t lastHeartbeat;
|
||||
};
|
||||
|
||||
enum class LOGINERRORID {
|
||||
database_error = 0,
|
||||
id_doesnt_exist = 1,
|
||||
id_and_password_do_not_match = 2,
|
||||
id_already_in_use = 3,
|
||||
login_error = 4,
|
||||
client_version_outdated = 6,
|
||||
you_are_not_an_authorized_beta_tester = 7,
|
||||
authentication_connection_error = 8,
|
||||
updated_euala_required = 9
|
||||
enum class LoginError {
|
||||
DATABASE_ERROR = 0,
|
||||
ID_DOESNT_EXIST = 1,
|
||||
ID_AND_PASSWORD_DO_NOT_MATCH = 2,
|
||||
ID_ALREADY_IN_USE = 3,
|
||||
LOGIN_ERROR = 4,
|
||||
CLIENT_VERSION_OUTDATED = 6,
|
||||
YOU_ARE_NOT_AN_AUTHORIZED_BETA_TESTER = 7,
|
||||
AUTHENTICATION_CONNECTION_ERROR = 8,
|
||||
UPDATED_EUALA_REQUIRED = 9
|
||||
};
|
||||
|
||||
// WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static
|
||||
@@ -29,15 +30,17 @@ class CNLoginServer : public CNServer {
|
||||
private:
|
||||
static void handlePacket(CNSocket* sock, CNPacketData* data);
|
||||
static std::map<CNSocket*, CNLoginData> loginSessions;
|
||||
|
||||
|
||||
static bool isLoginDataGood(std::string login, std::string password);
|
||||
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
|
||||
static bool isAccountInUse(int accountId);
|
||||
//returns true if success
|
||||
static bool isCharacterNameGood(std::string Firstname, std::string Lastname);
|
||||
// returns true if success
|
||||
static bool exitDuplicate(int accountId);
|
||||
public:
|
||||
CNLoginServer(uint16_t p);
|
||||
|
||||
void newConnection(CNSocket* cns);
|
||||
void killConnection(CNSocket* cns);
|
||||
void killConnection(CNSocket* cns);
|
||||
void onStep();
|
||||
};
|
||||
|
@@ -9,8 +9,7 @@ int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int siz
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
|
||||
while (num + ERSize <= size)
|
||||
{
|
||||
while (num + ERSize <= size) {
|
||||
int num4 = num + num3;
|
||||
int num5 = num + (ERSize - 1 - num3);
|
||||
|
||||
@@ -19,8 +18,7 @@ int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int siz
|
||||
data[num5] = b;
|
||||
num += ERSize;
|
||||
num3++;
|
||||
if (num3 > ERSize / 2)
|
||||
{
|
||||
if (num3 > ERSize / 2) {
|
||||
num3 = 0;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +81,7 @@ bool CNSocket::sendData(uint8_t* data, int size) {
|
||||
}
|
||||
sentBytes += sent;
|
||||
}
|
||||
|
||||
|
||||
return true; // it worked!
|
||||
}
|
||||
|
||||
@@ -122,7 +120,7 @@ void CNSocket::kill() {
|
||||
void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
|
||||
size_t bodysize = size + sizeof(uint32_t);
|
||||
uint8_t* fullpkt = (uint8_t*)xmalloc(bodysize+4);
|
||||
uint8_t* body = fullpkt+4;
|
||||
@@ -150,7 +148,7 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
|
||||
}
|
||||
|
||||
// send packet data!
|
||||
if (alive && !sendData(fullpkt, bodysize+4))
|
||||
if (alive && !sendData(fullpkt, bodysize+4))
|
||||
kill();
|
||||
|
||||
free(fullpkt);
|
||||
@@ -167,7 +165,7 @@ void CNSocket::step() {
|
||||
// we aren't reading a packet yet, try to start looking for one
|
||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||
if (!SOCKETERROR(recved)) {
|
||||
// we got out packet size!!!!
|
||||
// we got our packet size!!!!
|
||||
readSize = *((int32_t*)readBuffer);
|
||||
// sanity check
|
||||
if (readSize > CN_PACKET_BUFFER_SIZE) {
|
||||
@@ -183,7 +181,7 @@ void CNSocket::step() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (readSize > 0 && readBufferIndex < readSize) {
|
||||
// read until the end of the packet! (or at least try too)
|
||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||
@@ -196,7 +194,7 @@ void CNSocket::step() {
|
||||
}
|
||||
}
|
||||
|
||||
if (activelyReading && readBufferIndex - readSize <= 0) {
|
||||
if (activelyReading && readBufferIndex - readSize <= 0) {
|
||||
// decrypt readBuffer and copy to CNPacketData
|
||||
CNSocketEncryption::decryptData((uint8_t*)&readBuffer, (uint8_t*)(&EKey), readSize);
|
||||
|
||||
@@ -216,38 +214,38 @@ void CNSocket::step() {
|
||||
// ========================================================[[ CNServer ]]========================================================
|
||||
|
||||
void CNServer::init() {
|
||||
// create socket file descriptor
|
||||
// create socket file descriptor
|
||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (SOCKETINVALID(sock)) {
|
||||
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
if (SOCKETINVALID(sock)) {
|
||||
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// attach socket to the port
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
|
||||
#else
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
|
||||
addressSize = sizeof(address);
|
||||
|
||||
// Bind to the port
|
||||
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
|
||||
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
|
||||
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
|
||||
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// set server listener to non-blocking
|
||||
@@ -257,8 +255,8 @@ void CNServer::init() {
|
||||
#else
|
||||
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +279,7 @@ void CNServer::start() {
|
||||
#else
|
||||
if (fcntl(newConnectionSocket, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
|
||||
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
|
||||
#ifdef _WIN32
|
||||
shutdown(newConnectionSocket, SD_BOTH);
|
||||
closesocket(newConnectionSocket);
|
||||
@@ -295,7 +293,7 @@ void CNServer::start() {
|
||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
// add connection to list!
|
||||
CNSocket* tmp = new CNSocket(newConnectionSocket, pHandler);
|
||||
CNSocket* tmp = new CNSocket(newConnectionSocket, pHandler);
|
||||
connections.push_back(tmp);
|
||||
newConnection(tmp);
|
||||
}
|
||||
|
@@ -5,8 +5,11 @@
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#ifdef _WIN32
|
||||
#ifdef _WIN32
|
||||
// windows
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
@@ -46,18 +49,18 @@
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
/*
|
||||
Packets format (sent from the client):
|
||||
[4 bytes] - size of packet including the 4 byte packet type
|
||||
[size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
|
||||
[4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
|
||||
[structure] - one member contains length of trailing data (expressed in packet-dependant structures)
|
||||
[trailing data] - optional variable-length data that only some packets make use of
|
||||
*/
|
||||
* Packets format (sent from the client):
|
||||
* [4 bytes] - size of packet including the 4 byte packet type
|
||||
* [size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
|
||||
* [4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
|
||||
* [structure] - one member contains length of trailing data (expressed in packet-dependant structures)
|
||||
* [trailing data] - optional variable-length data that only some packets make use of
|
||||
*/
|
||||
|
||||
// error checking calloc wrapper
|
||||
inline void* xmalloc(size_t sz) {
|
||||
@@ -75,14 +78,14 @@ inline void* xmalloc(size_t sz) {
|
||||
// for outbound packets
|
||||
inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
|
||||
// it's safe to multiply
|
||||
size_t trailing = npayloads * plsize;
|
||||
|
||||
// does it fit in a packet?
|
||||
if (base + trailing > CN_PACKET_BUFFER_SIZE)
|
||||
if (base + trailing > CN_PACKET_BUFFER_SIZE - 8)
|
||||
return false;
|
||||
|
||||
// everything is a-ok!
|
||||
@@ -92,7 +95,7 @@ inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
||||
// for inbound packets
|
||||
inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
|
||||
// check for multiplication overflow
|
||||
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
|
||||
// it's safe to multiply
|
||||
@@ -132,6 +135,8 @@ enum ACTIVEKEY {
|
||||
SOCKETKEY_FE
|
||||
};
|
||||
|
||||
struct Player;
|
||||
|
||||
class CNSocket;
|
||||
typedef void (*PacketHandler)(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
@@ -153,6 +158,7 @@ private:
|
||||
public:
|
||||
SOCKET sock;
|
||||
PacketHandler pHandler;
|
||||
Player *plr = nullptr;
|
||||
|
||||
CNSocket(SOCKET s, PacketHandler ph);
|
||||
|
||||
@@ -169,15 +175,15 @@ public:
|
||||
};
|
||||
|
||||
class CNServer;
|
||||
typedef void (*TimerHandler)(CNServer* serv, uint64_t time);
|
||||
typedef void (*TimerHandler)(CNServer* serv, time_t time);
|
||||
|
||||
// timer struct
|
||||
struct TimerEvent {
|
||||
TimerHandler handlr;
|
||||
uint64_t delta; // time to be added to the current time on reset
|
||||
uint64_t scheduledEvent; // time to call handlr()
|
||||
time_t delta; // time to be added to the current time on reset
|
||||
time_t scheduledEvent; // time to call handlr()
|
||||
|
||||
TimerEvent(TimerHandler h, uint64_t d): handlr(h), delta(d) {
|
||||
TimerEvent(TimerHandler h, time_t d): handlr(h), delta(d) {
|
||||
scheduledEvent = 0;
|
||||
}
|
||||
};
|
||||
@@ -207,5 +213,5 @@ public:
|
||||
static void printPacket(CNPacketData *data, int type);
|
||||
virtual void newConnection(CNSocket* cns);
|
||||
virtual void killConnection(CNSocket* cns);
|
||||
virtual void onStep(); // called every 2 seconds
|
||||
virtual void onStep();
|
||||
};
|
||||
|
@@ -2,8 +2,11 @@
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "TableData.hpp" // for flush()
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -15,7 +18,8 @@ std::list<TimerEvent> CNShardServer::Timers;
|
||||
CNShardServer::CNShardServer(uint16_t p) {
|
||||
port = p;
|
||||
pHandler = &CNShardServer::handlePacket;
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 2000);
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -26,41 +30,76 @@ void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
ShardPackets[data->type](sock, data);
|
||||
else if (settings::VERBOSITY > 0)
|
||||
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Defines::p2str(CL2FE, data->type) << " (" << data->type << ")" << std::endl;
|
||||
|
||||
PlayerManager::players[sock].lastHeartbeat = getTime();
|
||||
}
|
||||
|
||||
void CNShardServer::keepAliveTimer(CNServer* serv, uint64_t currTime) {
|
||||
auto cachedPlayers = PlayerManager::players;
|
||||
|
||||
for (auto pair : cachedPlayers) {
|
||||
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 60000) { // if the client hadn't responded in 60 seconds, its a dead connection so throw it out
|
||||
void CNShardServer::keepAliveTimer(CNServer* serv, time_t currTime) {
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > settings::TIMEOUT) {
|
||||
// if the client hasn't responded in 60 seconds, its a dead connection so throw it out
|
||||
pair.first->kill();
|
||||
continue;
|
||||
} else if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > settings::TIMEOUT/2) {
|
||||
// if the player hasn't responded in 30 seconds, send a live check
|
||||
INITSTRUCT(sP_FE2CL_REQ_LIVE_CHECK, data);
|
||||
pair.first->sendPacket((void*)&data, P_FE2CL_REQ_LIVE_CHECK, sizeof(sP_FE2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
|
||||
// passed the heartbeat, send another
|
||||
INITSTRUCT(sP_FE2CL_REQ_LIVE_CHECK, data);
|
||||
pair.first->sendPacket((void*)&data, P_FE2CL_REQ_LIVE_CHECK, sizeof(sP_FE2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
}
|
||||
|
||||
void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
|
||||
if (PlayerManager::players.empty())
|
||||
return;
|
||||
|
||||
std::cout << "[INFO] Saving " << PlayerManager::players.size() << " players to DB..." << std::endl;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
Database::updatePlayer(pair.second.plr);
|
||||
}
|
||||
|
||||
TableData::flush();
|
||||
std::cout << "[INFO] Done." << std::endl;
|
||||
}
|
||||
|
||||
void CNShardServer::newConnection(CNSocket* cns) {
|
||||
cns->setActiveKey(SOCKETKEY_E); // by default they accept keys encrypted with the default key
|
||||
}
|
||||
|
||||
void CNShardServer::killConnection(CNSocket* cns) {
|
||||
// must be static to be called from PlayerManager::exitDuplicate()
|
||||
void CNShardServer::_killConnection(CNSocket* cns) {
|
||||
// check if the player ever sent a REQ_PC_ENTER
|
||||
if (PlayerManager::players.find(cns) == PlayerManager::players.end())
|
||||
return;
|
||||
|
||||
// remove from CNSharedData
|
||||
int64_t key = PlayerManager::getPlayer(cns)->SerialKey;
|
||||
PlayerManager::removePlayer(cns);
|
||||
Player* plr = PlayerManager::getPlayer(cns);
|
||||
|
||||
if (plr == nullptr) { // this shouldn't happen if everything works correctly...
|
||||
PlayerManager::removePlayer(cns);
|
||||
|
||||
// also, hopefully the player's progress was already saved since the last db save interval, but rip those 2 mins of progress lol
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
_killConnection(cns);
|
||||
}
|
||||
|
||||
// flush the DB when terminating the server
|
||||
void CNShardServer::kill() {
|
||||
periodicSaveTimer(nullptr, 0);
|
||||
CNServer::kill();
|
||||
}
|
||||
|
||||
void CNShardServer::onStep() {
|
||||
uint64_t currTime = getTime();
|
||||
time_t currTime = getTime();
|
||||
|
||||
for (TimerEvent& event : Timers) {
|
||||
if (event.scheduledEvent == 0) {
|
||||
|
@@ -12,7 +12,8 @@ class CNShardServer : public CNServer {
|
||||
private:
|
||||
static void handlePacket(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
static void keepAliveTimer(CNServer*, uint64_t);
|
||||
static void keepAliveTimer(CNServer*, time_t);
|
||||
static void periodicSaveTimer(CNServer* serv, time_t currTime);
|
||||
|
||||
public:
|
||||
static std::map<uint32_t, PacketHandler> ShardPackets;
|
||||
@@ -20,7 +21,10 @@ public:
|
||||
|
||||
CNShardServer(uint16_t p);
|
||||
|
||||
static void _killConnection(CNSocket *cns);
|
||||
|
||||
void newConnection(CNSocket* cns);
|
||||
void killConnection(CNSocket* cns);
|
||||
void kill();
|
||||
void onStep();
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
std::map<int64_t, Player> CNSharedData::players;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
CNShared.hpp
|
||||
There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
|
||||
*/
|
||||
* CNShared.hpp
|
||||
* There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@@ -1,36 +0,0 @@
|
||||
#include "CNStructs.hpp"
|
||||
#if defined _MSC_VER
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
std::string U16toU8(char16_t* src) {
|
||||
try {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
return convert.to_bytes(src);
|
||||
} catch(std::exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// returns number of char16_t that was written at des
|
||||
size_t U8toU16(std::string src, char16_t* des) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
std::u16string tmp = convert.from_bytes(src);
|
||||
|
||||
// copy utf16 string to buffer
|
||||
memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length());
|
||||
des[tmp.length()] = '\0';
|
||||
|
||||
return tmp.length();
|
||||
}
|
||||
|
||||
uint64_t getTime() {
|
||||
#ifndef _MSC_VER
|
||||
struct timeval tp;
|
||||
gettimeofday(&tp, NULL);
|
||||
return tp.tv_sec * 1000 + tp.tv_usec / 1000;
|
||||
#else
|
||||
std::chrono::milliseconds value = std::chrono::duration_cast<std::chrono::milliseconds>((std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch());
|
||||
return (uint64_t)(value.count());
|
||||
#endif
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
CNStructs.hpp - defines some basic structs & useful methods for packets used by FusionFall based on the version defined
|
||||
*/
|
||||
* CNStructs.hpp - defines some basic structs & useful methods for packets used by FusionFall based on the version defined
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -24,15 +24,21 @@
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
|
||||
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
|
||||
#define INITSTRUCT(T, x) T x; \
|
||||
memset(&x, 0, sizeof(T));
|
||||
|
||||
// macros to extract fields from instanceIDs
|
||||
#define MAPNUM(x) ((x) & 0xffffffff)
|
||||
#define PLAYERID(x) ((x) >> 32)
|
||||
|
||||
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
||||
|
||||
std::string U16toU8(char16_t* src);
|
||||
size_t U8toU16(std::string src, char16_t* des); // returns number of char16_t that was written at des
|
||||
uint64_t getTime();
|
||||
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
|
||||
time_t getTime();
|
||||
time_t getTimestamp();
|
||||
void terminate(int);
|
||||
|
||||
// The PROTOCOL_VERSION definition is defined by the build system.
|
||||
#if !defined(PROTOCOL_VERSION)
|
||||
|
@@ -2,36 +2,499 @@
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
|
||||
std::map<std::string, ChatCommand> ChatManager::commands;
|
||||
|
||||
std::vector<std::string> parseArgs(std::string full) {
|
||||
std::stringstream ss(full);
|
||||
std::istream_iterator<std::string> begin(ss);
|
||||
std::istream_iterator<std::string> end;
|
||||
return std::vector<std::string>(begin, end);
|
||||
}
|
||||
|
||||
bool runCmd(std::string full, CNSocket* sock) {
|
||||
std::vector<std::string> args = parseArgs(full);
|
||||
std::string cmd = args[0].substr(1, args[0].size() - 1);
|
||||
|
||||
// check if the command exists
|
||||
if (ChatManager::commands.find(cmd) != ChatManager::commands.end()) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ChatCommand command = ChatManager::commands[cmd];
|
||||
|
||||
// sanity check + does the player have the required account level to use the command?
|
||||
if (plr != nullptr && plr->accountLevel <= command.requiredAccLevel) {
|
||||
command.handlr(full, args, sock);
|
||||
return true;
|
||||
} else {
|
||||
ChatManager::sendServerMessage(sock, "You don't have access to that command!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "Unknown command!");
|
||||
return false;
|
||||
}
|
||||
|
||||
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, "Commands available to you:");
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
for (auto& cmd : ChatManager::commands) {
|
||||
if (cmd.second.requiredAccLevel >= plr->accountId)
|
||||
ChatManager::sendServerMessage(sock, "/" + cmd.first + (cmd.second.help.length() > 0 ? " - " + cmd.second.help : ""));
|
||||
}
|
||||
}
|
||||
|
||||
void accessCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, "Your access level is " + std::to_string(PlayerManager::getPlayer(sock)->accountLevel));
|
||||
}
|
||||
|
||||
void populationCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
ChatManager::sendServerMessage(sock, std::to_string(PlayerManager::players.size()) + " players online");
|
||||
}
|
||||
|
||||
void levelCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "/level: no level specified");
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
char *tmp;
|
||||
int level = std::strtol(args[1].c_str(), &tmp, 10);
|
||||
if (*tmp)
|
||||
return;
|
||||
|
||||
if ((level < 1 || level > 36) && plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
if (!(level < 1 || level > 36))
|
||||
plr->level = level;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp);
|
||||
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iPC_Level = level;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
}
|
||||
|
||||
void mssCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test/export> <<height>>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate route number
|
||||
char* routeNumC;
|
||||
int routeNum = std::strtol(args[1].c_str(), &routeNumC, 10);
|
||||
if (*routeNumC) {
|
||||
// not an integer
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Invalid route number '" + args[1] + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() < 3) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test> <<height>>");
|
||||
return;
|
||||
}
|
||||
|
||||
// get the route (if it doesn't exist yet, this will also make it)
|
||||
std::vector<WarpLocation>* route = &TableData::RunningSkywayRoutes[routeNum];
|
||||
|
||||
// mss <route> add <height>
|
||||
if (args[2] == "add") {
|
||||
// make sure height token exists
|
||||
if (args.size() < 4) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Point height must be specified");
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> add <height>");
|
||||
return;
|
||||
}
|
||||
// validate height token
|
||||
char* heightC;
|
||||
int height = std::strtol(args[3].c_str(), &heightC, 10);
|
||||
if (*heightC) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Invalid height " + args[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
route->push_back({ plr->x, plr->y, height }); // add point
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Added point (" + std::to_string(plr->x) + ", " + std::to_string(plr->y) + ", " + std::to_string(height) + ") to route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> remove
|
||||
if (args[2] == "remove") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->back();
|
||||
route->pop_back(); // remove point at top of stack
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Removed point (" + std::to_string(pulled.x) + ", " + std::to_string(pulled.y) + ", " + std::to_string(pulled.z) + ") from route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> goto
|
||||
if (args[2] == "goto") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->back();
|
||||
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> clear
|
||||
if (args[2] == "clear") {
|
||||
route->clear();
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Cleared route " + std::to_string(routeNum));
|
||||
return;
|
||||
}
|
||||
|
||||
// mss <route> test
|
||||
if (args[2] == "test") {
|
||||
if (route->empty()) {
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpLocation pulled = route->front();
|
||||
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
|
||||
TransportManager::testMssRoute(sock, route);
|
||||
return;
|
||||
}
|
||||
|
||||
// for compatibility: mss <route> export
|
||||
if (args[2] == "export") {
|
||||
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
|
||||
TableData::flush();
|
||||
return;
|
||||
}
|
||||
|
||||
// mss ????
|
||||
ChatManager::sendServerMessage(sock, "[MSS] Unknown command '" + args[2] + "'");
|
||||
}
|
||||
|
||||
void summonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "/summonW: no mob type specified");
|
||||
return;
|
||||
}
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
char *rest;
|
||||
int type = std::strtol(args[1].c_str(), &rest, 10);
|
||||
if (*rest) {
|
||||
ChatManager::sendServerMessage(sock, "Invalid NPC number: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// permission & sanity check
|
||||
if (plr == nullptr || type >= 3314)
|
||||
return;
|
||||
|
||||
int team = NPCManager::NPCData[type]["m_iTeam"];
|
||||
|
||||
assert(NPCManager::nextId < INT32_MAX);
|
||||
|
||||
#define EXTRA_HEIGHT 200
|
||||
BaseNPC *npc = nullptr;
|
||||
int id = NPCManager::nextId++;
|
||||
if (team == 2) {
|
||||
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, plr->instanceID, type, NPCManager::NPCData[type], id);
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
|
||||
// re-enable respawning
|
||||
((Mob*)npc)->summoned = false;
|
||||
} else {
|
||||
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, plr->instanceID, type, id);
|
||||
}
|
||||
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
|
||||
|
||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||
if (PLAYERID(plr->instanceID) != 0) {
|
||||
id = NPCManager::nextId++;
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, MAPNUM(plr->instanceID), type, NPCManager::NPCData[type], id);
|
||||
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
|
||||
((Mob*)npc)->summoned = false;
|
||||
} else {
|
||||
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, MAPNUM(plr->instanceID), type, id);
|
||||
}
|
||||
|
||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
|
||||
}
|
||||
|
||||
void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||
|
||||
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID);
|
||||
|
||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
||||
}
|
||||
|
||||
void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
MobManager::simulateMobs = !MobManager::simulateMobs;
|
||||
|
||||
if (MobManager::simulateMobs)
|
||||
return;
|
||||
|
||||
// return all mobs to their spawn points
|
||||
for (auto& pair : MobManager::Mobs) {
|
||||
pair.second->state = MobState::RETREAT;
|
||||
pair.second->target = nullptr;
|
||||
pair.second->nextMovement = getTime();
|
||||
|
||||
// mobs with static paths can chill where they are
|
||||
if (pair.second->staticPath) {
|
||||
pair.second->roamX = pair.second->appearanceData.iX;
|
||||
pair.second->roamY = pair.second->appearanceData.iY;
|
||||
pair.second->roamZ = pair.second->appearanceData.iZ;
|
||||
} else {
|
||||
pair.second->roamX = pair.second->spawnX;
|
||||
pair.second->roamY = pair.second->spawnY;
|
||||
pair.second->roamZ = pair.second->spawnZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void npcRotateCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
int angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
|
||||
|
||||
// if it's a gruntwork NPC, rotate in-place
|
||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
} else {
|
||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
||||
}
|
||||
|
||||
// update rotation clientside
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = npc->appearanceData;
|
||||
sock->sendPacket((void*)&pkt, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
}
|
||||
|
||||
void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z);
|
||||
}
|
||||
|
||||
void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// no additional arguments: report current instance ID
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[INST] Current instance ID: " + std::to_string(plr->instanceID));
|
||||
ChatManager::sendServerMessage(sock, "[INST] (Map " + std::to_string(MAPNUM(plr->instanceID)) + ", instance " + std::to_string(PLAYERID(plr->instanceID)) + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// move player to specified instance
|
||||
// validate instance ID
|
||||
char* instanceS;
|
||||
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
|
||||
if (*instanceS) {
|
||||
ChatManager::sendServerMessage(sock, "[INST] Invalid instance ID: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, instance);
|
||||
ChatManager::sendServerMessage(sock, "[INST] Switched to instance with ID " + std::to_string(instance));
|
||||
}
|
||||
|
||||
void npcInstanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
if (args.size() < 2) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Instance ID must be specified");
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Usage: /npci <instance ID>");
|
||||
return;
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
|
||||
|
||||
if (npc == nullptr) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] No NPCs found nearby");
|
||||
return;
|
||||
}
|
||||
|
||||
// validate instance ID
|
||||
char* instanceS;
|
||||
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
|
||||
if (*instanceS) {
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Invalid instance ID: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
ChatManager::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
|
||||
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
|
||||
NPCManager::updateNPCInstance(npc->appearanceData.iNPC_ID, instance);
|
||||
}
|
||||
|
||||
void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current mission ID: " + std::to_string(plr->CurrentMissionID));
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] != 0) {
|
||||
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
|
||||
if ((int)(task["m_iHMissionID"]) == plr->CurrentMissionID) {
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task ID: " + std::to_string(plr->tasks[i]));
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task type: " + std::to_string((int)(task["m_iHTaskType"])));
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current waypoint NPC ID: " + std::to_string((int)(task["m_iSTGrantWayPoint"])));
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
if ((int)(task["m_iCSUEnemyID"][j]) != 0)
|
||||
ChatManager::sendServerMessage(sock, "[MINFO] Current task mob #" + std::to_string(j+1) +": " + std::to_string((int)(task["m_iCSUEnemyID"][j])));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tasksCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] != 0) {
|
||||
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
|
||||
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] mission ID: " + std::to_string((int)(task["m_iHMissionID"])));
|
||||
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] task ID: " + std::to_string(plr->tasks[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
TableData::flush();
|
||||
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
|
||||
}
|
||||
|
||||
void ChatManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_MENUCHAT_MESSAGE, menuChatHandler);
|
||||
|
||||
registerCommand("help", 100, helpCommand, "list all unlocked server-side commands");
|
||||
registerCommand("access", 100, accessCommand, "print your access level");
|
||||
registerCommand("instance", 30, instanceCommand, "print or change your current instance");
|
||||
registerCommand("mss", 30, mssCommand, "edit Monkey Skyway routes");
|
||||
registerCommand("npcr", 30, npcRotateCommand, "rotate NPCs");
|
||||
registerCommand("npci", 30, npcInstanceCommand, "move NPCs across instances");
|
||||
registerCommand("summonW", 30, summonWCommand, "permanently summon NPCs");
|
||||
registerCommand("unsummonW", 30, unsummonWCommand, "delete permanently summoned NPCs");
|
||||
registerCommand("toggleai", 30, toggleAiCommand, "enable/disable mob AI");
|
||||
registerCommand("flush", 30, flushCommand, "save gruntwork to file");
|
||||
registerCommand("level", 50, levelCommand, "change your character's level");
|
||||
registerCommand("population", 100, populationCommand, "check how many players are online");
|
||||
registerCommand("refresh", 100, refreshCommand, "teleport yourself to your current location");
|
||||
registerCommand("minfo", 30, minfoCommand, "show details of the current mission and task.");
|
||||
registerCommand("tasks", 30, tasksCommand, "list all active missions and their respective task ids.");
|
||||
}
|
||||
|
||||
void ChatManager::registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help) {
|
||||
commands[cmd] = ChatCommand(requiredLevel, handlr, help);
|
||||
}
|
||||
|
||||
void ChatManager::chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf;
|
||||
PlayerView plr = PlayerManager::players[sock];
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::string fullChat = U16toU8(chat->szFreeChat);
|
||||
|
||||
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
|
||||
runCmd(fullChat, sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iPC_ID = plr.plr->iID;
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
|
||||
|
||||
// send to visible players
|
||||
for (CNSocket* otherSock : plr.viewable) {
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void ChatManager::menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE*)data->buf;
|
||||
PlayerView plr = PlayerManager::players[sock];
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
@@ -41,10 +504,9 @@ void ChatManager::menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
|
||||
// send to visible players
|
||||
for (CNSocket* otherSock : plr.viewable) {
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT))
|
||||
return; // ignore the malformed packet
|
||||
@@ -52,16 +514,25 @@ void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
// you can dance with friends!!!!!!!!
|
||||
|
||||
sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT* emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf;
|
||||
PlayerView plr = PlayerManager::players[sock];
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, resp);
|
||||
resp.iEmoteCode = emote->iEmoteCode;
|
||||
resp.iID_From = plr.plr->iID;
|
||||
resp.iID_From = plr->iID;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
|
||||
|
||||
// send to visible players (players within render distance)
|
||||
for (CNSocket* otherSock : plr.viewable) {
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
|
||||
}
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
|
||||
}
|
||||
|
||||
void ChatManager::sendServerMessage(CNSocket* sock, std::string msg) {
|
||||
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
|
||||
|
||||
motd.iType = 1;
|
||||
// convert string to u16 and write it to the buffer
|
||||
U8toU16(msg, (char16_t*)motd.szSystemMsg, sizeof(motd.szSystemMsg));
|
||||
|
||||
// send the packet :)
|
||||
sock->sendPacket((void*)&motd, P_FE2CL_PC_MOTD_LOGIN, sizeof(sP_FE2CL_PC_MOTD_LOGIN));
|
||||
}
|
||||
|
@@ -2,10 +2,28 @@
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#define CMD_PREFIX '/'
|
||||
|
||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
||||
|
||||
struct ChatCommand {
|
||||
int requiredAccLevel;
|
||||
std::string help;
|
||||
CommandHandler handlr;
|
||||
|
||||
ChatCommand(int r, CommandHandler h): requiredAccLevel(r), handlr(h) {}
|
||||
ChatCommand(int r, CommandHandler h, std::string str): requiredAccLevel(r), help(str), handlr(h) {}
|
||||
ChatCommand(): ChatCommand(0, nullptr) {}
|
||||
};
|
||||
|
||||
namespace ChatManager {
|
||||
extern std::map<std::string, ChatCommand> commands;
|
||||
void init();
|
||||
|
||||
void registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help = "");
|
||||
|
||||
void chatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void emoteHandler(CNSocket* sock, CNPacketData* data);
|
||||
void menuChatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||
}
|
||||
|
253
src/ChunkManager.cpp
Normal file
253
src/ChunkManager.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "ChunkManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
|
||||
std::map<std::tuple<int, int, uint64_t>, Chunk*> ChunkManager::chunks;
|
||||
|
||||
void ChunkManager::init() {} // stubbed
|
||||
|
||||
void ChunkManager::addNPC(int posX, int posY, uint64_t instanceID, int32_t id) {
|
||||
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
|
||||
|
||||
// make chunk if it doesn't exist!
|
||||
if (chunks.find(pos) == chunks.end()) {
|
||||
chunks[pos] = new Chunk();
|
||||
chunks[pos]->players = std::set<CNSocket*>();
|
||||
chunks[pos]->NPCs = std::set<int32_t>();
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[pos];
|
||||
|
||||
chunk->NPCs.insert(id);
|
||||
}
|
||||
|
||||
void ChunkManager::addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock) {
|
||||
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
|
||||
|
||||
// make chunk if it doesn't exist!
|
||||
if (chunks.find(pos) == chunks.end()) {
|
||||
chunks[pos] = new Chunk();
|
||||
chunks[pos]->players = std::set<CNSocket*>();
|
||||
chunks[pos]->NPCs = std::set<int32_t>();
|
||||
}
|
||||
|
||||
Chunk* chunk = chunks[pos];
|
||||
|
||||
chunk->players.insert(sock);
|
||||
}
|
||||
|
||||
bool ChunkManager::removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return false; // do nothing if chunk doesn't even exist
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
chunk->players.erase(sock); // gone
|
||||
|
||||
// if players and NPCs are empty, free chunk and remove it from surrounding views
|
||||
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
|
||||
destroyChunk(chunkPos);
|
||||
|
||||
// the chunk we left was destroyed
|
||||
return true;
|
||||
}
|
||||
|
||||
// the chunk we left was not destroyed
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChunkManager::removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return false; // do nothing if chunk doesn't even exist
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
chunk->NPCs.erase(id); // gone
|
||||
|
||||
// if players and NPCs are empty, free chunk and remove it from surrounding views
|
||||
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
|
||||
destroyChunk(chunkPos);
|
||||
|
||||
// the chunk we left was destroyed
|
||||
return true;
|
||||
}
|
||||
|
||||
// the chunk we left was not destroyed
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChunkManager::destroyChunk(std::tuple<int, int, uint64_t> chunkPos) {
|
||||
if (!checkChunk(chunkPos))
|
||||
return; // chunk doesn't exist, we don't need to do anything
|
||||
|
||||
Chunk* chunk = chunks[chunkPos];
|
||||
|
||||
// unspawn all of the mobs/npcs
|
||||
std::set npcIDs(chunk->NPCs);
|
||||
for (uint32_t id : npcIDs) {
|
||||
NPCManager::destroyNPC(id);
|
||||
}
|
||||
|
||||
// we also need to remove it from all NPCs/Players views
|
||||
for (Chunk* otherChunk : grabChunks(chunkPos)) {
|
||||
if (otherChunk == chunk)
|
||||
continue;
|
||||
|
||||
// remove from NPCs
|
||||
for (uint32_t id : otherChunk->NPCs) {
|
||||
if (std::find(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk) != NPCManager::NPCs[id]->currentChunks.end()) {
|
||||
NPCManager::NPCs[id]->currentChunks.erase(std::remove(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk), NPCManager::NPCs[id]->currentChunks.end());
|
||||
}
|
||||
}
|
||||
|
||||
// remove from players
|
||||
for (CNSocket* sock : otherChunk->players) {
|
||||
PlayerView* plyr = &PlayerManager::players[sock];
|
||||
if (std::find(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk) != plyr->currentChunks.end()) {
|
||||
plyr->currentChunks.erase(std::remove(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk), plyr->currentChunks.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert(chunk->players.size() == 0);
|
||||
|
||||
// remove from the map
|
||||
chunks.erase(chunkPos);
|
||||
|
||||
delete chunk;
|
||||
}
|
||||
|
||||
bool ChunkManager::checkChunk(std::tuple<int, int, uint64_t> chunk) {
|
||||
return chunks.find(chunk) != chunks.end();
|
||||
}
|
||||
|
||||
std::tuple<int, int, uint64_t> ChunkManager::grabChunk(int posX, int posY, uint64_t instanceID) {
|
||||
return std::make_tuple(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||
}
|
||||
|
||||
std::vector<Chunk*> ChunkManager::grabChunks(std::tuple<int, int, uint64_t> chunk) {
|
||||
std::vector<Chunk*> chnks;
|
||||
chnks.reserve(9);
|
||||
|
||||
int x, y;
|
||||
uint64_t inst;
|
||||
std::tie(x, y, inst) = chunk;
|
||||
|
||||
// grabs surrounding chunks if they exist
|
||||
for (int i = -1; i < 2; i++) {
|
||||
for (int z = -1; z < 2; z++) {
|
||||
std::tuple<int, int, uint64_t> pos = std::make_tuple(x+i, y+z, inst);
|
||||
|
||||
// if chunk exists, add it to the vector
|
||||
if (checkChunk(pos))
|
||||
chnks.push_back(chunks[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
// returns the chunks that aren't shared (only from from)
|
||||
std::vector<Chunk*> ChunkManager::getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to) {
|
||||
std::vector<Chunk*> delta;
|
||||
|
||||
for (Chunk* i : from) {
|
||||
bool found = false;
|
||||
|
||||
// search for it in the other array
|
||||
for (Chunk* z : to) {
|
||||
if (i == z) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add it to the vector if we didn't find it!
|
||||
if (!found)
|
||||
delta.push_back(i);
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
/*
|
||||
* inefficient algorithm to get all chunks from a specific instance
|
||||
*/
|
||||
std::vector<std::tuple<int, int, uint64_t>> ChunkManager::getChunksInMap(uint64_t mapNum) {
|
||||
std::vector<std::tuple<int, int, uint64_t>> chnks;
|
||||
|
||||
for (auto it = ChunkManager::chunks.begin(); it != ChunkManager::chunks.end(); it++) {
|
||||
if (std::get<2>(it->first) == mapNum) {
|
||||
chnks.push_back(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
return chnks;
|
||||
}
|
||||
|
||||
bool ChunkManager::inPopulatedChunks(int posX, int posY, uint64_t instanceID) {
|
||||
auto chunk = ChunkManager::grabChunk(posX, posY, instanceID);
|
||||
auto nearbyChunks = ChunkManager::grabChunks(chunk);
|
||||
|
||||
for (Chunk *c: nearbyChunks) {
|
||||
if (!c->players.empty())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChunkManager::createInstance(uint64_t instanceID) {
|
||||
|
||||
std::vector<std::tuple<int, int, uint64_t>> templateChunks = ChunkManager::getChunksInMap(MAPNUM(instanceID)); // base instance chunks
|
||||
if (ChunkManager::getChunksInMap(instanceID).size() == 0) { // only instantiate if the instance doesn't exist already
|
||||
std::cout << "Creating instance " << instanceID << std::endl;
|
||||
for (std::tuple<int, int, uint64_t> &coords : templateChunks) {
|
||||
for (int npcID : chunks[coords]->NPCs) {
|
||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||
int newID = NPCManager::nextId++;
|
||||
BaseNPC* baseNPC = NPCManager::NPCs[npcID];
|
||||
if (baseNPC->npcClass == NPC_MOB) {
|
||||
Mob* newMob = new Mob(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, baseNPC->appearanceData.iHP, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], newID);
|
||||
NPCManager::NPCs[newID] = newMob;
|
||||
MobManager::Mobs[newID] = newMob;
|
||||
} else {
|
||||
BaseNPC* newNPC = new BaseNPC(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, newID);
|
||||
NPCManager::NPCs[newID] = newNPC;
|
||||
}
|
||||
NPCManager::updateNPCInstance(newID, instanceID); // make sure the npc state gets updated
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "Instance " << instanceID << " already exists" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkManager::destroyInstance(uint64_t instanceID) {
|
||||
std::cout << "Deleting instance " << instanceID << std::endl;
|
||||
std::vector<std::tuple<int, int, uint64_t>> instanceChunks = ChunkManager::getChunksInMap(instanceID);
|
||||
for (std::tuple<int, int, uint64_t>& coords : instanceChunks) {
|
||||
destroyChunk(coords);
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkManager::destroyInstanceIfEmpty(uint64_t instanceID) {
|
||||
if (PLAYERID(instanceID) == 0)
|
||||
return; // don't clean up overworld/IZ chunks
|
||||
|
||||
std::vector<std::tuple<int, int, uint64_t>> sourceChunkCoords = getChunksInMap(instanceID);
|
||||
|
||||
for (std::tuple<int, int, uint64_t>& coords : sourceChunkCoords) {
|
||||
Chunk* chunk = chunks[coords];
|
||||
|
||||
if (chunk->players.size() > 0)
|
||||
return; // there are still players inside
|
||||
}
|
||||
|
||||
destroyInstance(instanceID);
|
||||
}
|
44
src/ChunkManager.hpp
Normal file
44
src/ChunkManager.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
std::set<CNSocket*> players;
|
||||
std::set<int32_t> NPCs;
|
||||
};
|
||||
|
||||
enum {
|
||||
INSTANCE_OVERWORLD, // default instance every player starts in
|
||||
INSTANCE_IZ, // these aren't actually used
|
||||
INSTANCE_UNIQUE // these aren't actually used
|
||||
};
|
||||
|
||||
namespace ChunkManager {
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
extern std::map<std::tuple<int, int, uint64_t>, Chunk*> chunks;
|
||||
|
||||
void addNPC(int posX, int posY, uint64_t instanceID, int32_t id);
|
||||
void addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock);
|
||||
bool removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock);
|
||||
bool removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id);
|
||||
bool checkChunk(std::tuple<int, int, uint64_t> chunk);
|
||||
void destroyChunk(std::tuple<int, int, uint64_t> chunkPos);
|
||||
std::tuple<int, int, uint64_t> grabChunk(int posX, int posY, uint64_t instanceID);
|
||||
std::vector<Chunk*> grabChunks(std::tuple<int, int, uint64_t> chunkPos);
|
||||
std::vector<Chunk*> getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to);
|
||||
std::vector<std::tuple<int, int, uint64_t>> getChunksInMap(uint64_t mapNum);
|
||||
bool inPopulatedChunks(int posX, int posY, uint64_t instanceID);
|
||||
|
||||
void createInstance(uint64_t);
|
||||
void destroyInstance(uint64_t);
|
||||
void destroyInstanceIfEmpty(uint64_t);
|
||||
}
|
@@ -1,131 +0,0 @@
|
||||
#include "CombatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
void CombatManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_BEGIN, combatBegin);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_END, combatEnd);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_DOT_DAMAGE_ONOFF, dotDamageOnOff);
|
||||
}
|
||||
|
||||
void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs), pkt->iNPCCnt, sizeof(int32_t), data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs));
|
||||
|
||||
/*
|
||||
* Due to the possibility of multiplication overflow (and regular buffer overflow),
|
||||
* both incoming and outgoing variable-length packets must be validated, at least if
|
||||
* the number of trailing structs isn't well known (ie. it's from the client).
|
||||
*/
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_NPCs_SUCC packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult);
|
||||
uint8_t respbuf[4096];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_ATTACK_NPCs_SUCC *resp = (sP_FE2CL_PC_ATTACK_NPCs_SUCC*)respbuf;
|
||||
sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC));
|
||||
|
||||
resp->iNPCCnt = pkt->iNPCCnt;
|
||||
|
||||
for (int i = 0; i < pkt->iNPCCnt; i++) {
|
||||
if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] pcAttackNpcs: mob ID not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
BaseNPC& mob = NPCManager::NPCs[pktdata[i]];
|
||||
|
||||
mob.appearanceData.iHP -= 100;
|
||||
|
||||
if (mob.appearanceData.iHP <= 0)
|
||||
giveReward(sock);
|
||||
// TODO: despawn mobs when they die
|
||||
|
||||
respdata[i].iID = mob.appearanceData.iNPC_ID;
|
||||
respdata[i].iDamage = 100;
|
||||
respdata[i].iHP = mob.appearanceData.iHP;
|
||||
respdata[i].iHitFlag = 2;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen);
|
||||
|
||||
// a bit of a hack: these are the same size, so we can reuse the output packet
|
||||
assert(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_NPCs));
|
||||
sP_FE2CL_PC_ATTACK_NPCs *resp1 = (sP_FE2CL_PC_ATTACK_NPCs*)respbuf;
|
||||
|
||||
resp1->iPC_ID = plr->iID;
|
||||
|
||||
// send to other players
|
||||
for (CNSocket *s : PlayerManager::players[sock].viewable) {
|
||||
if (s == sock)
|
||||
continue;
|
||||
|
||||
s->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen);
|
||||
}
|
||||
}
|
||||
|
||||
void CombatManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub
|
||||
void CombatManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub
|
||||
void CombatManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub
|
||||
|
||||
void CombatManager::giveReward(CNSocket *sock) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// update player
|
||||
plr->money += 50;
|
||||
plr->fusionmatter += 70;
|
||||
|
||||
// simple rewards
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
|
||||
int slot = ItemManager::findFreeSlot(plr);
|
||||
if (slot == -1) {
|
||||
// no room for an item, but you still get FM and taros
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
} else {
|
||||
// item reward
|
||||
item->sItem.iType = 9;
|
||||
item->sItem.iID = 1;
|
||||
item->iSlotNum = slot;
|
||||
item->eIL = 1; // Inventory Location. 1 means player inventory.
|
||||
|
||||
// update player
|
||||
plr->Inven[slot] = item->sItem;
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
namespace CombatManager {
|
||||
void init();
|
||||
|
||||
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
|
||||
void combatBegin(CNSocket *sock, CNPacketData *data);
|
||||
void combatEnd(CNSocket *sock, CNPacketData *data);
|
||||
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
|
||||
|
||||
void giveReward(CNSocket *sock);
|
||||
}
|
681
src/Database.cpp
681
src/Database.cpp
@@ -1,4 +1,5 @@
|
||||
#include "Database.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "contrib/bcrypt/BCrypt.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include <string>
|
||||
@@ -8,51 +9,90 @@
|
||||
#include "Player.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "contrib/sqlite/sqlite_orm.h"
|
||||
#include "MissionManager.hpp"
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.mutex.h"
|
||||
#else
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
using namespace sqlite_orm;
|
||||
|
||||
std::mutex dbCrit;
|
||||
|
||||
# pragma region DatabaseScheme
|
||||
auto db = make_storage("database.db",
|
||||
make_table("Accounts",
|
||||
make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()),
|
||||
make_column("Login", &Database::Account::Login),
|
||||
make_column("Password", &Database::Account::Password),
|
||||
make_column("Selected", &Database::Account::Selected)
|
||||
make_column("Selected", &Database::Account::Selected),
|
||||
make_column("Created", &Database::Account::Created),
|
||||
make_column("LastLogin", &Database::Account::LastLogin)
|
||||
),
|
||||
make_table("Players",
|
||||
make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()),
|
||||
make_column("AccountID", &Database::DbPlayer::AccountID),
|
||||
make_column("Slot", &Database::DbPlayer::slot),
|
||||
make_column("Firstname", &Database::DbPlayer::FirstName),
|
||||
make_column("LastName", &Database::DbPlayer::LastName),
|
||||
make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()),
|
||||
make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()),
|
||||
make_column("Created", &Database::DbPlayer::Created),
|
||||
make_column("LastLogin", &Database::DbPlayer::LastLogin),
|
||||
make_column("Level", &Database::DbPlayer::Level),
|
||||
make_column("Nano1", &Database::DbPlayer::Nano1),
|
||||
make_column("Nano2", &Database::DbPlayer::Nano2),
|
||||
make_column("Nano3", &Database::DbPlayer::Nano3),
|
||||
make_column("AppearanceFlag", &Database::DbPlayer::AppearanceFlag),
|
||||
make_column("TutorialFlag", &Database::DbPlayer::TutorialFlag),
|
||||
make_column("PayZoneFlag", &Database::DbPlayer::PayZoneFlag),
|
||||
make_column("XCoordinates", &Database::DbPlayer::x_coordinates),
|
||||
make_column("YCoordinates", &Database::DbPlayer::y_coordinates),
|
||||
make_column("ZCoordinates", &Database::DbPlayer::z_coordinates),
|
||||
make_column("ZCoordinates", &Database::DbPlayer::z_coordinates),
|
||||
make_column("Angle", &Database::DbPlayer::angle),
|
||||
make_column("Body", &Database::DbPlayer::Body),
|
||||
make_column("Class", &Database::DbPlayer::Class),
|
||||
make_column("EquipFoot", &Database::DbPlayer::EquipFoot),
|
||||
make_column("EquipLB", &Database::DbPlayer::EquipLB),
|
||||
make_column("EquipUB", &Database::DbPlayer::EquipUB),
|
||||
make_column("EquipWeapon1", &Database::DbPlayer::EquipWeapon1),
|
||||
make_column("EyeColor", &Database::DbPlayer::EyeColor),
|
||||
make_column("FaceStyle", &Database::DbPlayer::FaceStyle),
|
||||
make_column("Gender", &Database::DbPlayer::Gender),
|
||||
make_column("HP", &Database::DbPlayer::HP),
|
||||
make_column("HairColor", &Database::DbPlayer::HairColor),
|
||||
make_column("HairStyle", &Database::DbPlayer::HairStyle),
|
||||
make_column("Height", &Database::DbPlayer::Height),
|
||||
make_column("NameCheck", &Database::DbPlayer::NameCheck),
|
||||
make_column("SkinColor", &Database::DbPlayer::SkinColor),
|
||||
make_column("isGM", &Database::DbPlayer::isGM),
|
||||
make_column("Height", &Database::DbPlayer::Height),
|
||||
make_column("NameCheck", &Database::DbPlayer::NameCheck),
|
||||
make_column("SkinColor", &Database::DbPlayer::SkinColor),
|
||||
make_column("AccountLevel", &Database::DbPlayer::AccountLevel),
|
||||
make_column("FusionMatter", &Database::DbPlayer::FusionMatter),
|
||||
make_column("Taros", &Database::DbPlayer::Taros)
|
||||
make_column("Taros", &Database::DbPlayer::Taros),
|
||||
make_column("Quests", &Database::DbPlayer::QuestFlag),
|
||||
make_column("BatteryW", &Database::DbPlayer::BatteryW),
|
||||
make_column("BatteryN", &Database::DbPlayer::BatteryN),
|
||||
make_column("Mentor", &Database::DbPlayer::Mentor),
|
||||
make_column("WarpLocationFlag", &Database::DbPlayer::WarpLocationFlag),
|
||||
make_column("SkywayLocationFlag1", &Database::DbPlayer::SkywayLocationFlag1),
|
||||
make_column("SkywayLocationFlag2", &Database::DbPlayer::SkywayLocationFlag2),
|
||||
make_column("CurrentMissionID", &Database::DbPlayer::CurrentMissionID)
|
||||
),
|
||||
make_table("Inventory",
|
||||
make_column("AccountID", &Database::Inventory::AccountID, primary_key())
|
||||
make_column("PlayerId", &Database::Inventory::playerId),
|
||||
make_column("Slot", &Database::Inventory::slot),
|
||||
make_column("Id", &Database::Inventory::id),
|
||||
make_column("Type", &Database::Inventory::Type),
|
||||
make_column("Opt", &Database::Inventory::Opt),
|
||||
make_column("TimeLimit", &Database::Inventory::TimeLimit)
|
||||
),
|
||||
make_table("Nanos",
|
||||
make_column("PlayerId", &Database::Nano::playerId),
|
||||
make_column("Id", &Database::Nano::iID),
|
||||
make_column("Skill", &Database::Nano::iSkillID),
|
||||
make_column("Stamina", &Database::Nano::iStamina)
|
||||
),
|
||||
make_table("RunningQuests",
|
||||
make_column("PlayerId", &Database::DbQuest::PlayerId),
|
||||
make_column("TaskId", &Database::DbQuest::TaskId),
|
||||
make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1),
|
||||
make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2),
|
||||
make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -60,113 +100,142 @@ auto db = make_storage("database.db",
|
||||
|
||||
#pragma region LoginServer
|
||||
|
||||
void Database::open()
|
||||
{
|
||||
//this parameter means it will try to preserve data during migration
|
||||
void Database::open() {
|
||||
// this parameter means it will try to preserve data during migration
|
||||
bool preserve = true;
|
||||
db.sync_schema(preserve);
|
||||
DEBUGLOG(
|
||||
std::cout << "[DB] Database in operation" << std::endl;
|
||||
)
|
||||
std::cout << "[INFO] Database in operation ";
|
||||
int accounts = getAccountsCount();
|
||||
int players = getPlayersCount();
|
||||
std::string message = "";
|
||||
if (accounts > 0) {
|
||||
message += ": Found " + std::to_string(accounts) + " Account";
|
||||
if (accounts > 1)
|
||||
message += "s";
|
||||
}
|
||||
if (players > 0) {
|
||||
message += " and " + std::to_string(players) + " Player Character";
|
||||
if (players > 1)
|
||||
message += "s";
|
||||
}
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
|
||||
int Database::addAccount(std::string login, std::string password)
|
||||
{
|
||||
int Database::getAccountsCount() {
|
||||
return db.count<Account>();
|
||||
}
|
||||
|
||||
int Database::getPlayersCount() {
|
||||
return db.count<DbPlayer>();
|
||||
}
|
||||
|
||||
int Database::addAccount(std::string login, std::string password) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
password = BCrypt::generateHash(password);
|
||||
Account x = {};
|
||||
x.Login = login;
|
||||
x.Password = password;
|
||||
x.Selected = 1;
|
||||
return db.insert(x);
|
||||
Account account = {};
|
||||
account.Login = login;
|
||||
account.Password = password;
|
||||
account.Selected = 1;
|
||||
account.Created = getTimestamp();
|
||||
return db.insert(account);
|
||||
}
|
||||
|
||||
void Database::updateSelected(int accountId, int slot)
|
||||
{
|
||||
void Database::updateSelected(int accountId, int slot) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
Account acc = db.get<Account>(accountId);
|
||||
acc.Selected = slot;
|
||||
// timestamp
|
||||
acc.LastLogin = getTimestamp();
|
||||
db.update(acc);
|
||||
}
|
||||
|
||||
std::unique_ptr<Database::Account> Database::findAccount(std::string login)
|
||||
{
|
||||
//this is awful, I've tried everything to improve it
|
||||
std::unique_ptr<Database::Account> Database::findAccount(std::string login) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
// this is awful, I've tried everything to improve it
|
||||
auto find = db.get_all<Account>(
|
||||
where(c(&Account::Login) == login), limit(1));
|
||||
if (find.empty())
|
||||
return nullptr;
|
||||
return
|
||||
std::unique_ptr<Account>(new Account(find.front()));
|
||||
std::unique_ptr<Account>(new Account(find.front()));
|
||||
}
|
||||
|
||||
bool Database::isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck)
|
||||
{
|
||||
//TODO: add colate nocase
|
||||
bool Database::isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
std::string First = U16toU8(nameCheck->szFirstName);
|
||||
std::string Last = U16toU8(nameCheck->szLastName);
|
||||
return
|
||||
(db.get_all<DbPlayer>
|
||||
(where((c(&DbPlayer::FirstName) == First)
|
||||
and (c(&DbPlayer::LastName) == Last)))
|
||||
.empty());
|
||||
.empty());
|
||||
}
|
||||
|
||||
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
|
||||
{
|
||||
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
// fail if the player already has 4 or more characters
|
||||
if (db.count<DbPlayer>(where(c(&DbPlayer::AccountID) == AccountID)) >= 4)
|
||||
return -1;
|
||||
|
||||
DbPlayer create = {};
|
||||
//save packet data
|
||||
|
||||
// set timestamp
|
||||
create.Created = getTimestamp();
|
||||
// save packet data
|
||||
create.FirstName = U16toU8(save->szFirstName);
|
||||
create.LastName = U16toU8(save->szLastName);
|
||||
create.slot = save->iSlotNum;
|
||||
create.AccountID = AccountID;
|
||||
//set flags
|
||||
|
||||
// set flags
|
||||
create.AppearanceFlag = 0;
|
||||
create.TutorialFlag = 0;
|
||||
create.PayZoneFlag = 0;
|
||||
//set namecheck based on setting
|
||||
|
||||
// set namecheck based on setting
|
||||
if (settings::APPROVEALLNAMES || save->iFNCode)
|
||||
create.NameCheck = 1;
|
||||
else
|
||||
create.NameCheck = 0;
|
||||
//create default body character
|
||||
create.Body= 0;
|
||||
create.Class= 0;
|
||||
create.EquipFoot= 0;
|
||||
create.EquipLB= 0;
|
||||
create.EquipUB= 0;
|
||||
create.EquipWeapon1= 0;
|
||||
create.EquipWeapon2= 0;
|
||||
create.EyeColor= 1;
|
||||
create.FaceStyle= 1;
|
||||
create.Gender= 1;
|
||||
create.HP= 1000;
|
||||
create.HairColor= 1;
|
||||
|
||||
// create default body character
|
||||
create.Body = 0;
|
||||
create.Class = 0;
|
||||
create.EyeColor = 1;
|
||||
create.FaceStyle = 1;
|
||||
create.Gender = 1;
|
||||
create.Level = 1;
|
||||
create.HP = PC_MAXHEALTH(create.Level);
|
||||
create.HairColor = 1;
|
||||
create.HairStyle = 1;
|
||||
create.Height= 0;
|
||||
create.Level= 1;
|
||||
create.SkinColor= 1;
|
||||
create.isGM = false;
|
||||
//commented and disabled for now
|
||||
//if (U16toU8(save->szFirstName) == settings::GMPASS) {
|
||||
// create.isGM = true;
|
||||
//}
|
||||
|
||||
create.FusionMatter= 0;
|
||||
create.Taros= 0;
|
||||
create.Height = 0;
|
||||
create.SkinColor = 1;
|
||||
create.AccountLevel = settings::ACCLEVEL;
|
||||
create.x_coordinates = settings::SPAWN_X;
|
||||
create.y_coordinates= settings::SPAWN_Y;
|
||||
create.z_coordinates= settings::SPAWN_Z;
|
||||
create.y_coordinates = settings::SPAWN_Y;
|
||||
create.z_coordinates = settings::SPAWN_Z;
|
||||
create.angle = settings::SPAWN_ANGLE;
|
||||
// set mentor to computress
|
||||
create.Mentor = 5;
|
||||
|
||||
// initialize the quest blob to 128 0-bytes
|
||||
create.QuestFlag = std::vector<char>(128, 0);
|
||||
|
||||
return db.insert(create);
|
||||
}
|
||||
|
||||
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character)
|
||||
{
|
||||
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer finish = getDbPlayerById(character->PCStyle.iPC_UID);
|
||||
finish.AppearanceFlag = 1;
|
||||
finish.Body = character->PCStyle.iBody;
|
||||
finish.Class = character->PCStyle.iClass;
|
||||
finish.EquipFoot = character->sOn_Item.iEquipFootID;
|
||||
finish.EquipLB = character->sOn_Item.iEquipLBID;
|
||||
finish.EquipUB = character->sOn_Item.iEquipUBID;
|
||||
finish.EyeColor = character->PCStyle.iEyeColor;
|
||||
finish.FaceStyle = character->PCStyle.iFaceStyle;
|
||||
finish.Gender = character->PCStyle.iGender;
|
||||
@@ -176,35 +245,83 @@ void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character)
|
||||
finish.Level = 1;
|
||||
finish.SkinColor = character->PCStyle.iSkinColor;
|
||||
db.update(finish);
|
||||
// clothes
|
||||
Inventory Foot, LB, UB;
|
||||
Foot.playerId = character->PCStyle.iPC_UID;
|
||||
Foot.id = character->sOn_Item.iEquipFootID;
|
||||
Foot.Type = 3;
|
||||
Foot.slot = 3;
|
||||
Foot.Opt = 1;
|
||||
Foot.TimeLimit = 0;
|
||||
db.insert(Foot);
|
||||
LB.playerId = character->PCStyle.iPC_UID;
|
||||
LB.id = character->sOn_Item.iEquipLBID;
|
||||
LB.Type = 2;
|
||||
LB.slot = 2;
|
||||
LB.Opt = 1;
|
||||
LB.TimeLimit = 0;
|
||||
db.insert(LB);
|
||||
UB.playerId = character->PCStyle.iPC_UID;
|
||||
UB.id = character->sOn_Item.iEquipUBID;
|
||||
UB.Type = 1;
|
||||
UB.slot = 1;
|
||||
UB.Opt = 1;
|
||||
UB.TimeLimit = 0;
|
||||
db.insert(UB);
|
||||
}
|
||||
|
||||
void Database::finishTutorial(int PlayerID)
|
||||
{
|
||||
DbPlayer finish = getDbPlayerById(PlayerID);
|
||||
finish.TutorialFlag = 1;
|
||||
//equip lightning gun
|
||||
finish.EquipWeapon1 = 328;
|
||||
db.update(finish);
|
||||
void Database::finishTutorial(int PlayerID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
Player finish = getPlayer(PlayerID);
|
||||
// set flag
|
||||
finish.PCStyle2.iTutorialFlag= 1;
|
||||
// add Gun
|
||||
Inventory LightningGun = {};
|
||||
LightningGun.playerId = PlayerID;
|
||||
LightningGun.id = 328;
|
||||
LightningGun.slot = 0;
|
||||
LightningGun.Type = 0;
|
||||
LightningGun.Opt = 1;
|
||||
db.insert(LightningGun);
|
||||
// add Nano
|
||||
Nano Buttercup = {};
|
||||
Buttercup.playerId = PlayerID;
|
||||
Buttercup.iID = 1;
|
||||
Buttercup.iSkillID = 1;
|
||||
Buttercup.iStamina = 150;
|
||||
finish.equippedNanos[0] = 1;
|
||||
db.insert(Buttercup);
|
||||
// save missions
|
||||
MissionManager::saveMission(&finish, 0);
|
||||
MissionManager::saveMission(&finish, 1);
|
||||
|
||||
db.update(playerToDb(&finish));
|
||||
}
|
||||
|
||||
int Database::deleteCharacter(int characterID)
|
||||
{
|
||||
int Database::deleteCharacter(int characterID, int userID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
auto find =
|
||||
db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == characterID));
|
||||
db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == characterID and c(&DbPlayer::AccountID)==userID));
|
||||
int slot = find.front().slot;
|
||||
db.remove<DbPlayer>(find.front().PlayerID);
|
||||
db.remove_all<Inventory>(where(c(&Inventory::playerId) == characterID));
|
||||
db.remove_all<Nano>(where(c(&Nano::playerId) == characterID));
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
std::vector <Player> Database::getCharacters(int UserID)
|
||||
{
|
||||
std::vector <Player> Database::getCharacters(int UserID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
std::vector<DbPlayer>characters =
|
||||
db.get_all<DbPlayer>(where
|
||||
(c(&DbPlayer::AccountID) == UserID));
|
||||
//parsing DbPlayer to Player
|
||||
// parsing DbPlayer to Player
|
||||
std::vector<Player> result = std::vector<Player>();
|
||||
for (auto &character : characters) {
|
||||
Player toadd = DbToPlayer(character);
|
||||
Player toadd = DbToPlayer(character);
|
||||
result.push_back(
|
||||
toadd
|
||||
);
|
||||
@@ -212,13 +329,18 @@ std::vector <Player> Database::getCharacters(int UserID)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Database::evaluateCustomName(int characterID, CUSTOMNAME decision) {
|
||||
// XXX: This is never called?
|
||||
void Database::evaluateCustomName(int characterID, CustomName decision) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer player = getDbPlayerById(characterID);
|
||||
player.NameCheck = (int)decision;
|
||||
db.update(player);
|
||||
}
|
||||
|
||||
void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer Player = getDbPlayerById(save->iPCUID);
|
||||
Player.FirstName = U16toU8(save->szFirstName);
|
||||
Player.LastName = U16toU8(save->szLastName);
|
||||
@@ -229,59 +351,85 @@ void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
|
||||
db.update(Player);
|
||||
}
|
||||
|
||||
Database::DbPlayer Database::playerToDb(Player player)
|
||||
{
|
||||
DbPlayer result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though)
|
||||
Database::DbPlayer Database::playerToDb(Player *player) {
|
||||
// TODO: move stuff that is never updated to separate table so it doesn't try to update it every time
|
||||
DbPlayer result = {};
|
||||
|
||||
result.PlayerID = player.iID;
|
||||
result.AccountID = player.accountId;
|
||||
result.AppearanceFlag = player.PCStyle2.iAppearanceFlag;
|
||||
result.Body = player.PCStyle.iBody;
|
||||
result.Class = player.PCStyle.iClass;
|
||||
//equipment
|
||||
result.EyeColor = player.PCStyle.iEyeColor;
|
||||
result.FaceStyle = player.PCStyle.iFaceStyle;
|
||||
result.FirstName = U16toU8( player.PCStyle.szFirstName);
|
||||
//fm
|
||||
result.Gender = player.PCStyle.iGender;
|
||||
result.HairColor = player.PCStyle.iHairColor;
|
||||
result.HairStyle = player.PCStyle.iHairStyle;
|
||||
result.Height = player.PCStyle.iHeight;
|
||||
result.HP = player.HP;
|
||||
result.isGM = player.IsGM;
|
||||
result.LastName = U16toU8(player.PCStyle.szLastName);
|
||||
result.Level = player.level;
|
||||
result.NameCheck = player.PCStyle.iNameCheck;
|
||||
result.PayZoneFlag = player.PCStyle2.iPayzoneFlag;
|
||||
result.PlayerID = player.PCStyle.iPC_UID;
|
||||
result.SkinColor = player.PCStyle.iSkinColor;
|
||||
result.slot = player.slot;
|
||||
//taros
|
||||
result.TutorialFlag = player.PCStyle2.iTutorialFlag;
|
||||
result.x_coordinates = player.x;
|
||||
result.y_coordinates = player.y;
|
||||
result.z_coordinates = player.z;
|
||||
result.PlayerID = player->iID;
|
||||
result.AccountID = player->accountId;
|
||||
result.AppearanceFlag = player->PCStyle2.iAppearanceFlag;
|
||||
result.Body = player->PCStyle.iBody;
|
||||
result.Class = player->PCStyle.iClass;
|
||||
result.EyeColor = player->PCStyle.iEyeColor;
|
||||
result.FaceStyle = player->PCStyle.iFaceStyle;
|
||||
result.FirstName = U16toU8( player->PCStyle.szFirstName);
|
||||
result.FusionMatter = player->fusionmatter;
|
||||
result.Gender = player->PCStyle.iGender;
|
||||
result.HairColor = player->PCStyle.iHairColor;
|
||||
result.HairStyle = player->PCStyle.iHairStyle;
|
||||
result.Height = player->PCStyle.iHeight;
|
||||
result.HP = player->HP;
|
||||
result.AccountLevel = player->accountLevel;
|
||||
result.LastName = U16toU8(player->PCStyle.szLastName);
|
||||
result.Level = player->level;
|
||||
result.NameCheck = player->PCStyle.iNameCheck;
|
||||
result.PayZoneFlag = player->PCStyle2.iPayzoneFlag;
|
||||
result.PlayerID = player->PCStyle.iPC_UID;
|
||||
result.SkinColor = player->PCStyle.iSkinColor;
|
||||
result.slot = player->slot;
|
||||
result.Taros = player->money;
|
||||
result.TutorialFlag = player->PCStyle2.iTutorialFlag;
|
||||
if (player->instanceID == 0) { // only save coords if player isn't instanced
|
||||
result.x_coordinates = player->x;
|
||||
result.y_coordinates = player->y;
|
||||
result.z_coordinates = player->z;
|
||||
result.angle = player->angle;
|
||||
} else {
|
||||
result.x_coordinates = player->lastX;
|
||||
result.y_coordinates = player->lastY;
|
||||
result.z_coordinates = player->lastZ;
|
||||
result.angle = player->lastAngle;
|
||||
}
|
||||
result.Nano1 = player->equippedNanos[0];
|
||||
result.Nano2 = player->equippedNanos[1];
|
||||
result.Nano3 = player->equippedNanos[2];
|
||||
result.BatteryN = player->batteryN;
|
||||
result.BatteryW = player->batteryW;
|
||||
result.Mentor = player->mentor;
|
||||
result.WarpLocationFlag = player->iWarpLocationFlag;
|
||||
result.SkywayLocationFlag1 = player->aSkywayLocationFlag[0];
|
||||
result.SkywayLocationFlag2 = player->aSkywayLocationFlag[1];
|
||||
result.CurrentMissionID = player->CurrentMissionID;
|
||||
|
||||
// timestamp
|
||||
result.LastLogin = getTimestamp();
|
||||
result.Created = player->creationTime;
|
||||
|
||||
// save completed quests
|
||||
result.QuestFlag = std::vector<char>((char*)player->aQuestFlag, (char*)player->aQuestFlag + 128);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Player Database::DbToPlayer(DbPlayer player) {
|
||||
Player result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though)
|
||||
|
||||
|
||||
result.iID = player.PlayerID;
|
||||
result.accountId = player.AccountID;
|
||||
result.creationTime = player.Created;
|
||||
result.PCStyle2.iAppearanceFlag = player.AppearanceFlag;
|
||||
result.PCStyle.iBody = player.Body;
|
||||
result.PCStyle.iClass = player.Class;
|
||||
result.PCStyle.iEyeColor = player.EyeColor;
|
||||
result.PCStyle.iFaceStyle = player.FaceStyle;
|
||||
U8toU16(player.FirstName, result.PCStyle.szFirstName);
|
||||
U8toU16(player.FirstName, result.PCStyle.szFirstName, sizeof(result.PCStyle.szFirstName));
|
||||
result.PCStyle.iGender = player.Gender;
|
||||
result.PCStyle.iHairColor = player.HairColor;
|
||||
result.PCStyle.iHairStyle = player.HairStyle;
|
||||
result.PCStyle.iHeight = player.Height;
|
||||
result.HP = player.HP;
|
||||
result.IsGM = player.isGM;
|
||||
U8toU16(player.LastName, result.PCStyle.szLastName);
|
||||
result.accountLevel = player.AccountLevel;
|
||||
U8toU16(player.LastName, result.PCStyle.szLastName, sizeof(result.PCStyle.szLastName));
|
||||
result.level = player.Level;
|
||||
result.PCStyle.iNameCheck = player.NameCheck;
|
||||
result.PCStyle2.iPayzoneFlag = player.PayZoneFlag;
|
||||
@@ -293,65 +441,254 @@ Player Database::DbToPlayer(DbPlayer player) {
|
||||
result.x = player.x_coordinates;
|
||||
result.y = player.y_coordinates;
|
||||
result.z = player.z_coordinates;
|
||||
result.angle = player.angle;
|
||||
result.money = player.Taros;
|
||||
result.fusionmatter = player.FusionMatter;
|
||||
result.batteryN = player.BatteryN;
|
||||
result.batteryW = player.BatteryW;
|
||||
result.mentor = player.Mentor;
|
||||
result.CurrentMissionID = player.CurrentMissionID;
|
||||
|
||||
//TODO:: implement all of below
|
||||
result.SerialKey = 0;
|
||||
result.money = 0;
|
||||
result.fusionmatter = 0;
|
||||
result.activeNano = 0;
|
||||
result.iPCState = 0;
|
||||
result.equippedNanos[0] = 1;
|
||||
result.equippedNanos[1] = 0;
|
||||
result.equippedNanos[2] = 0;
|
||||
result.isTrading = false;
|
||||
result.isTradeConfirm = false;
|
||||
result.equippedNanos[0] = player.Nano1;
|
||||
result.equippedNanos[1] = player.Nano2;
|
||||
result.equippedNanos[2] = player.Nano3;
|
||||
|
||||
result.Nanos[1].iID = 1;
|
||||
result.Nanos[1].iSkillID = 1;
|
||||
result.Nanos[1].iStamina = 150;
|
||||
result.inCombat = false;
|
||||
|
||||
for (int i = 0; i < 37; i++) {
|
||||
result.Nanos[i].iID = 0;
|
||||
result.Nanos[i].iSkillID = 0;
|
||||
result.Nanos[i].iStamina = 0;
|
||||
}
|
||||
|
||||
result.Equip[0].iID = player.EquipWeapon1;
|
||||
result.Equip[0].iType = 0;
|
||||
(player.EquipWeapon1) ? result.Equip[0].iOpt = 1 : result.Equip[0].iOpt = 0;
|
||||
result.iWarpLocationFlag = player.WarpLocationFlag;
|
||||
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
|
||||
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;
|
||||
|
||||
result.Equip[1].iID = player.EquipUB;
|
||||
result.Equip[1].iType = 1;
|
||||
(player.EquipUB) ? result.Equip[1].iOpt = 1 : result.Equip[1].iOpt = 0;
|
||||
Database::getInventory(&result);
|
||||
Database::removeExpiredVehicles(&result);
|
||||
Database::getNanos(&result);
|
||||
Database::getQuests(&result);
|
||||
|
||||
result.Equip[2].iID = player.EquipLB;
|
||||
result.Equip[2].iType = 2;
|
||||
(player.EquipLB) ? result.Equip[2].iOpt = 1 : result.Equip[2].iOpt = 0;
|
||||
// load completed quests
|
||||
memcpy(&result.aQuestFlag, player.QuestFlag.data(), std::min(sizeof(result.aQuestFlag), player.QuestFlag.size()));
|
||||
|
||||
result.Equip[3].iID = player.EquipFoot;
|
||||
result.Equip[3].iType = 3;
|
||||
(player.EquipFoot) ? result.Equip[3].iOpt = 1 : result.Equip[3].iOpt = 0;
|
||||
|
||||
|
||||
|
||||
for (int i = 4; i < AEQUIP_COUNT; i++) {
|
||||
// empty equips
|
||||
result.Equip[i].iID = 0;
|
||||
result.Equip[i].iType = i;
|
||||
result.Equip[i].iOpt = 0;
|
||||
}
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
// setup inventories
|
||||
result.Inven[i].iID = 0;
|
||||
result.Inven[i].iType = 0;
|
||||
result.Inven[i].iOpt = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Database::DbPlayer Database::getDbPlayerById(int id) {
|
||||
return db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == id))
|
||||
.front();
|
||||
return db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == id)).front();
|
||||
}
|
||||
|
||||
Player Database::getPlayer(int id) {
|
||||
return DbToPlayer(
|
||||
getDbPlayerById(id)
|
||||
);
|
||||
}
|
||||
|
||||
#pragma endregion LoginServer
|
||||
|
||||
#pragma region ShardServer
|
||||
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
DbPlayer toUpdate = playerToDb(player);
|
||||
db.update(toUpdate);
|
||||
updateInventory(player);
|
||||
updateNanos(player);
|
||||
updateQuests(player);
|
||||
}
|
||||
|
||||
void Database::updateInventory(Player *player){
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<Inventory>(
|
||||
where(c(&Inventory::playerId) == player->iID)
|
||||
);
|
||||
// insert equip
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++) {
|
||||
if (player->Equip[i].iID != 0) {
|
||||
sItemBase* next = &player->Equip[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iID != 0) {
|
||||
sItemBase* next = &player->Inven[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert bank
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iID != 0) {
|
||||
sItemBase* next = &player->Bank[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
// insert quest items
|
||||
for (int i = 0; i < AQINVEN_COUNT; i++) {
|
||||
if (player->QInven[i].iID != 0) {
|
||||
sItemBase* next = &player->QInven[i];
|
||||
Inventory toAdd = {};
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT;
|
||||
toAdd.id = next->iID;
|
||||
toAdd.Opt = next->iOpt;
|
||||
toAdd.Type = next->iType;
|
||||
toAdd.TimeLimit = next->iTimeLimit;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::updateNanos(Player *player) {
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<Nano>(
|
||||
where(c(&Nano::playerId) == player->iID)
|
||||
);
|
||||
// insert
|
||||
for (int i=1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||
if ((player->Nanos[i]).iID == 0)
|
||||
continue;
|
||||
Nano toAdd = {};
|
||||
sNano* next = &player->Nanos[i];
|
||||
toAdd.playerId = player->iID;
|
||||
toAdd.iID = next->iID;
|
||||
toAdd.iSkillID = next->iSkillID;
|
||||
toAdd.iStamina = next->iStamina;
|
||||
db.insert(toAdd);
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::updateQuests(Player* player) {
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
// remove all
|
||||
db.remove_all<DbQuest>(
|
||||
where(c(&DbQuest::PlayerId) == player->iID)
|
||||
);
|
||||
// insert
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (player->tasks[i] == 0)
|
||||
continue;
|
||||
DbQuest toAdd = {};
|
||||
toAdd.PlayerId = player->iID;
|
||||
toAdd.TaskId = player->tasks[i];
|
||||
toAdd.RemainingNPCCount1 = player->RemainingNPCCount[i][0];
|
||||
toAdd.RemainingNPCCount2 = player->RemainingNPCCount[i][1];
|
||||
toAdd.RemainingNPCCount3 = player->RemainingNPCCount[i][2];
|
||||
db.insert(toAdd);
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void Database::getInventory(Player* player) {
|
||||
// get items from DB
|
||||
auto items = db.get_all<Inventory>(
|
||||
where(c(&Inventory::playerId) == player->iID)
|
||||
);
|
||||
// set items
|
||||
for (const Inventory ¤t : items) {
|
||||
sItemBase toSet = {};
|
||||
toSet.iID = current.id;
|
||||
toSet.iType = current.Type;
|
||||
toSet.iOpt = current.Opt;
|
||||
toSet.iTimeLimit = current.TimeLimit;
|
||||
// assign to proper arrays
|
||||
if (current.slot < AEQUIP_COUNT)
|
||||
player->Equip[current.slot] = toSet;
|
||||
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT))
|
||||
player->Inven[current.slot - AEQUIP_COUNT] = toSet;
|
||||
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT))
|
||||
player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet;
|
||||
else
|
||||
player->QInven[current.slot - AEQUIP_COUNT - AINVEN_COUNT - ABANK_COUNT] = toSet;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Database::removeExpiredVehicles(Player* player) {
|
||||
int32_t currentTime = getTimestamp();
|
||||
// remove from bank immediately
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime)
|
||||
player->Bank[i] = {};
|
||||
}
|
||||
// for the rest, we want to leave only 1 expired vehicle on player to delete it with the client packet
|
||||
std::vector<sItemBase*> toRemove;
|
||||
|
||||
// equiped vehicle
|
||||
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime) {
|
||||
toRemove.push_back(&player->Equip[8]);
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 8;
|
||||
}
|
||||
// inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime) {
|
||||
toRemove.push_back(&player->Inven[i]);
|
||||
player->toRemoveVehicle.eIL = 1;
|
||||
player->toRemoveVehicle.iSlotNum = i;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all but one vehicles, leave last one for ceremonial deletion
|
||||
for (int i = 0; i < (int)toRemove.size()-1; i++) {
|
||||
memset(toRemove[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getNanos(Player* player) {
|
||||
// get from DB
|
||||
auto nanos = db.get_all<Nano>(
|
||||
where(c(&Nano::playerId) == player->iID)
|
||||
);
|
||||
// set
|
||||
for (const Nano& current : nanos) {
|
||||
sNano *toSet = &player->Nanos[current.iID];
|
||||
toSet->iID = current.iID;
|
||||
toSet->iSkillID = current.iSkillID;
|
||||
toSet->iStamina = current.iStamina;
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getQuests(Player* player) {
|
||||
// get from DB
|
||||
auto quests = db.get_all<DbQuest>(
|
||||
where(c(&DbQuest::PlayerId) == player->iID)
|
||||
);
|
||||
// set
|
||||
int i = 0;
|
||||
for (const DbQuest& current : quests) {
|
||||
player->tasks[i] = current.TaskId;
|
||||
player->RemainingNPCCount[i][0] = current.RemainingNPCCount1;
|
||||
player->RemainingNPCCount[i][1] = current.RemainingNPCCount2;
|
||||
player->RemainingNPCCount[i][2] = current.RemainingNPCCount3;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion ShardServer
|
||||
|
104
src/Database.hpp
104
src/Database.hpp
@@ -5,34 +5,45 @@
|
||||
#include <vector>
|
||||
|
||||
namespace Database {
|
||||
#pragma region DatabaseStructs
|
||||
#pragma region DatabaseStructs
|
||||
|
||||
struct Account
|
||||
{
|
||||
struct Account {
|
||||
int AccountID;
|
||||
std::string Login;
|
||||
std::string Password;
|
||||
int Selected;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
};
|
||||
struct Inventory
|
||||
{
|
||||
int AccountID;
|
||||
struct Inventory {
|
||||
int playerId;
|
||||
int slot;
|
||||
int16_t Type;
|
||||
int16_t id;
|
||||
int32_t Opt;
|
||||
int32_t TimeLimit;
|
||||
};
|
||||
struct DbPlayer
|
||||
{
|
||||
struct Nano {
|
||||
int playerId;
|
||||
int16_t iID;
|
||||
int16_t iSkillID;
|
||||
int16_t iStamina;
|
||||
};
|
||||
struct DbPlayer {
|
||||
int PlayerID;
|
||||
int AccountID;
|
||||
short int slot;
|
||||
std::string FirstName;
|
||||
std::string LastName;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
short int Level;
|
||||
int Nano1;
|
||||
int Nano2;
|
||||
int Nano3;
|
||||
short int AppearanceFlag;
|
||||
short int Body;
|
||||
short int Class;
|
||||
short int EquipFoot;
|
||||
short int EquipLB;
|
||||
short int EquipUB;
|
||||
short int EquipWeapon1;
|
||||
short int EquipWeapon2;
|
||||
short int EyeColor;
|
||||
short int FaceStyle;
|
||||
short int Gender;
|
||||
@@ -40,52 +51,83 @@ namespace Database {
|
||||
short int HairColor;
|
||||
short int HairStyle;
|
||||
short int Height;
|
||||
short int Level;
|
||||
short int NameCheck;
|
||||
short int PayZoneFlag;
|
||||
short int SkinColor;
|
||||
bool TutorialFlag;
|
||||
bool isGM;
|
||||
int AccountLevel;
|
||||
int FusionMatter;
|
||||
int Taros;
|
||||
int x_coordinates;
|
||||
int y_coordinates;
|
||||
int z_coordinates;
|
||||
int angle;
|
||||
short int PCState;
|
||||
int BatteryW;
|
||||
int BatteryN;
|
||||
int16_t Mentor;
|
||||
std::vector<char> QuestFlag;
|
||||
int32_t CurrentMissionID;
|
||||
int32_t WarpLocationFlag;
|
||||
int64_t SkywayLocationFlag1;
|
||||
int64_t SkywayLocationFlag2;
|
||||
};
|
||||
struct DbQuest {
|
||||
int PlayerId;
|
||||
int32_t TaskId;
|
||||
int RemainingNPCCount1;
|
||||
int RemainingNPCCount2;
|
||||
int RemainingNPCCount3;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#pragma endregion DatabaseStructs
|
||||
|
||||
//handles migrations
|
||||
// handles migrations
|
||||
void open();
|
||||
//returns ID
|
||||
int getAccountsCount();
|
||||
int getPlayersCount();
|
||||
// returns ID
|
||||
int addAccount(std::string login, std::string password);
|
||||
void updateSelected(int accountId, int playerId);
|
||||
std::unique_ptr<Account> findAccount(std::string login);
|
||||
bool isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck);
|
||||
//called after chosing name, returns ID
|
||||
// called after chosing name, returns ID
|
||||
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
|
||||
//called after finishing creation
|
||||
// called after finishing creation
|
||||
void finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character);
|
||||
//called after tutorial
|
||||
// called after tutorial
|
||||
void finishTutorial(int PlayerID);
|
||||
//returns slot number
|
||||
int deleteCharacter(int characterID);
|
||||
// returns slot number
|
||||
int deleteCharacter(int characterID, int userID);
|
||||
std::vector <Player> getCharacters(int userID);
|
||||
//accepting/declining custom name
|
||||
enum class CUSTOMNAME {
|
||||
approve = 1,
|
||||
disapprove = 2
|
||||
// accepting/declining custom name
|
||||
enum class CustomName {
|
||||
APPROVE = 1,
|
||||
DISAPPROVE = 2
|
||||
};
|
||||
void evaluateCustomName(int characterID, CUSTOMNAME decision);
|
||||
void evaluateCustomName(int characterID, CustomName decision);
|
||||
void changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save);
|
||||
|
||||
//parsing DbPlayer
|
||||
DbPlayer playerToDb(Player player);
|
||||
// parsing DbPlayer
|
||||
DbPlayer playerToDb(Player *player);
|
||||
Player DbToPlayer(DbPlayer player);
|
||||
|
||||
//getting players
|
||||
// getting players
|
||||
DbPlayer getDbPlayerById(int id);
|
||||
Player getPlayer(int id);
|
||||
|
||||
void updatePlayer(Player *player);
|
||||
void updateInventory(Player *player);
|
||||
void updateNanos(Player *player);
|
||||
void updateQuests(Player* player);
|
||||
|
||||
void getInventory(Player* player);
|
||||
void removeExpiredVehicles(Player* player);
|
||||
void getNanos(Player* player);
|
||||
void getQuests(Player* player);
|
||||
|
||||
// parsing blobs
|
||||
void appendBlob(std::vector<char>*blob, int64_t input);
|
||||
int64_t blobToInt64(std::vector<char>::iterator it);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
* implementing just yet anyway.
|
||||
*/
|
||||
|
||||
// floats
|
||||
const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f;
|
||||
const float CN_EP_RANK_1 = 0.8f;
|
||||
const float CN_EP_RANK_2 = 0.7f;
|
||||
@@ -15,6 +16,86 @@ const float CN_EP_RANK_3 = 0.5f;
|
||||
const float CN_EP_RANK_4 = 0.3f;
|
||||
const float CN_EP_RANK_5 = 0.29f;
|
||||
|
||||
// NPC classes
|
||||
enum NPCClass {
|
||||
NPC_BASE = 0,
|
||||
NPC_MOB = 1,
|
||||
NPC_BUS = 2,
|
||||
NPC_EGG = 3
|
||||
};
|
||||
|
||||
// nano powers
|
||||
enum {
|
||||
EST_NONE = 0,
|
||||
EST_DAMAGE = 1,
|
||||
EST_HEAL_HP = 2,
|
||||
EST_KNOCKDOWN = 3,
|
||||
EST_SLEEP = 4,
|
||||
EST_SNARE = 5,
|
||||
EST_HEAL_STAMINA = 6,
|
||||
EST_STAMINA_SELF = 7,
|
||||
EST_STUN = 8,
|
||||
EST_WEAPONSLOW = 9,
|
||||
EST_JUMP = 10,
|
||||
EST_RUN = 11,
|
||||
EST_STEALTH = 12,
|
||||
EST_SWIM = 13,
|
||||
EST_MINIMAPENEMY = 14,
|
||||
EST_MINIMAPTRESURE = 15,
|
||||
EST_PHOENIX = 16,
|
||||
EST_PROTECTBATTERY = 17,
|
||||
EST_PROTECTINFECTION = 18,
|
||||
EST_REWARDBLOB = 19,
|
||||
EST_REWARDCASH = 20,
|
||||
EST_BATTERYDRAIN = 21,
|
||||
EST_CORRUPTIONATTACK = 22,
|
||||
EST_INFECTIONDAMAGE = 23,
|
||||
EST_KNOCKBACK = 24,
|
||||
EST_FREEDOM = 25,
|
||||
EST_PHOENIX_GROUP = 26,
|
||||
EST_RECALL = 27,
|
||||
EST_RECALL_GROUP = 28,
|
||||
EST_RETROROCKET_SELF = 29,
|
||||
EST_BLOODSUCKING = 30,
|
||||
EST_BOUNDINGBALL = 31,
|
||||
EST_INVULNERABLE = 32,
|
||||
EST_NANOSTIMPAK = 33,
|
||||
EST_RETURNHOMEHEAL = 34,
|
||||
EST_BUFFHEAL = 35,
|
||||
EST_EXTRABANK = 36,
|
||||
EST__END = 37,
|
||||
EST_CORRUPTIONATTACKWIN = 38,
|
||||
EST_CORRUPTIONATTACKLOSE = 39,
|
||||
|
||||
ECSB_NONE = 0,
|
||||
ECSB_UP_MOVE_SPEED = 1,
|
||||
ECSB_UP_SWIM_SPEED = 2,
|
||||
ECSB_UP_JUMP_HEIGHT = 3,
|
||||
ECSB_UP_STEALTH = 4,
|
||||
ECSB_PHOENIX = 5,
|
||||
ECSB_PROTECT_BATTERY = 6,
|
||||
ECSB_PROTECT_INFECTION = 7,
|
||||
ECSB_DN_MOVE_SPEED = 8,
|
||||
ECSB_DN_ATTACK_SPEED = 9,
|
||||
ECSB_STUN = 10,
|
||||
ECSB_MEZ = 11,
|
||||
ECSB_KNOCKDOWN = 12,
|
||||
ECSB_MINIMAP_ENEMY = 13,
|
||||
ECSB_MINIMAP_TRESURE = 14,
|
||||
ECSB_REWARD_BLOB = 15,
|
||||
ECSB_REWARD_CASH = 16,
|
||||
ECSB_INFECTION = 17,
|
||||
ECSB_FREEDOM = 18,
|
||||
ECSB_BOUNDINGBALL = 19,
|
||||
ECSB_INVULNERABLE = 20,
|
||||
ECSB_STIMPAKSLOT1 = 21,
|
||||
ECSB_STIMPAKSLOT2 = 22,
|
||||
ECSB_STIMPAKSLOT3 = 23,
|
||||
ECSB_HEAL = 24,
|
||||
ECSB_EXTRABANK = 25,
|
||||
ECSTB__END = 26,
|
||||
};
|
||||
|
||||
enum {
|
||||
SUCC = 1,
|
||||
FAIL = 0,
|
||||
|
376
src/GroupManager.cpp
Normal file
376
src/GroupManager.cpp
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
void GroupManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_JOIN, joinGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_LEAVE, leaveGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE, chatGroup);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE, menuChatGroup);
|
||||
}
|
||||
|
||||
void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->groupCnt >= 4 || otherPlr->groupCnt > 1) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_To);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp);
|
||||
|
||||
resp.iHostID = plr->iIDGroup;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
|
||||
}
|
||||
|
||||
void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf;
|
||||
|
||||
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
|
||||
|
||||
if (otherSock == nullptr)
|
||||
return;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp);
|
||||
|
||||
resp.iID_To = plr->iID;
|
||||
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
|
||||
}
|
||||
|
||||
void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_JOIN))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// fail if the group is full or the other player is already in a group
|
||||
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) {
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int bitFlagBefore = getGroupFlags(otherPlr);
|
||||
|
||||
plr->iIDGroup = otherPlr->iID;
|
||||
otherPlr->groupCnt += 1;
|
||||
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
|
||||
|
||||
resp->iID_NewMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt;
|
||||
|
||||
int bitFlag = getGroupFlags(otherPlr);
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
|
||||
}
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
}
|
||||
|
||||
void GroupManager::leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
groupKickPlayer(plr);
|
||||
}
|
||||
|
||||
void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::menuChatGroup(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (plr == nullptr || otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
|
||||
resp.iSendPCID = plr->iID;
|
||||
resp.iEmoteCode = chat->iEmoteCode;
|
||||
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
||||
|
||||
if (sock == nullptr)
|
||||
continue;
|
||||
|
||||
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
|
||||
Player* leavingPlr = PlayerManager::getPlayer(sock);
|
||||
leavingPlr->iIDGroup = leavingPlr->iID;
|
||||
}
|
||||
|
||||
sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupManager::groupTickInfo(Player* plr) {
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
||||
|
||||
resp->iID = plr->iID;
|
||||
resp->iMemberPCCnt = plr->groupCnt;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].iPC_ID = varPlr->iID;
|
||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i].iLv = varPlr->level;
|
||||
respdata[i].iHP = varPlr->HP;
|
||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
//respdata[i].iMapType = 0;
|
||||
//respdata[i].iMapNum = 0;
|
||||
respdata[i].iX = varPlr->x;
|
||||
respdata[i].iY = varPlr->y;
|
||||
respdata[i].iZ = varPlr->z;
|
||||
if (varPlr->activeNano > 0) {
|
||||
respdata[i].bNano = 1;
|
||||
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
|
||||
}
|
||||
}
|
||||
|
||||
sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
|
||||
}
|
||||
|
||||
void GroupManager::groupKickPlayer(Player* plr) {
|
||||
// if you are the group leader, destroy your own group and kick everybody
|
||||
if (plr->iID == plr->iIDGroup) {
|
||||
groupUnbuff(plr);
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
plr->groupCnt = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
|
||||
|
||||
resp->iID_LeaveMember = plr->iID;
|
||||
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
|
||||
|
||||
int bitFlagBefore = getGroupFlags(otherPlr);
|
||||
int bitFlag = bitFlagBefore & ~plr->iGroupConditionBitFlag;
|
||||
int moveDown = 0;
|
||||
|
||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
if (moveDown == 1)
|
||||
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
|
||||
|
||||
respdata[i-moveDown].iPC_ID = varPlr->iID;
|
||||
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
|
||||
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
|
||||
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
||||
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
||||
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
|
||||
respdata[i-moveDown].iLv = varPlr->level;
|
||||
respdata[i-moveDown].iHP = varPlr->HP;
|
||||
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
||||
// respdata[i-moveDown]].iMapType = 0;
|
||||
// respdata[i-moveDown]].iMapNum = 0;
|
||||
respdata[i-moveDown].iX = varPlr->x;
|
||||
respdata[i-moveDown].iY = varPlr->y;
|
||||
respdata[i-moveDown].iZ = varPlr->z;
|
||||
// client doesnt read nano data here
|
||||
|
||||
if (varPlr == plr) {
|
||||
moveDown = 1;
|
||||
otherPlr->groupIDs[i] = 0;
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, varPlr->iConditionBitFlag);
|
||||
} else
|
||||
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
|
||||
}
|
||||
|
||||
plr->iIDGroup = plr->iID;
|
||||
otherPlr->groupCnt -= 1;
|
||||
|
||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
|
||||
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
if (sock == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
}
|
||||
|
||||
void GroupManager::groupUnbuff(Player* plr) {
|
||||
int bitFlag = getGroupFlags(plr);
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
||||
|
||||
if (sock == nullptr)
|
||||
continue;
|
||||
|
||||
Player* otherPlr = PlayerManager::getPlayer(sock);
|
||||
NanoManager::nanoChangeBuff(sock, otherPlr, bitFlag | otherPlr->iConditionBitFlag, otherPlr->iConditionBitFlag);
|
||||
}
|
||||
}
|
||||
|
||||
int GroupManager::getGroupFlags(Player* plr) {
|
||||
int bitFlag = 0;
|
||||
|
||||
for (int i = 0; i < plr->groupCnt; i++) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
continue;
|
||||
|
||||
bitFlag |= otherPlr->iGroupConditionBitFlag;
|
||||
}
|
||||
|
||||
return bitFlag;
|
||||
}
|
25
src/GroupManager.hpp
Normal file
25
src/GroupManager.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace GroupManager {
|
||||
void init();
|
||||
|
||||
void requestGroup(CNSocket* sock, CNPacketData* data);
|
||||
void refuseGroup(CNSocket* sock, CNPacketData* data);
|
||||
void joinGroup(CNSocket* sock, CNPacketData* data);
|
||||
void leaveGroup(CNSocket* sock, CNPacketData* data);
|
||||
void chatGroup(CNSocket* sock, CNPacketData* data);
|
||||
void menuChatGroup(CNSocket* sock, CNPacketData* data);
|
||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(Player* plr);
|
||||
void groupKickPlayer(Player* plr);
|
||||
void groupUnbuff(Player* plr);
|
||||
int getGroupFlags(Player* plr);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,47 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
namespace ItemManager {
|
||||
void init();
|
||||
struct Item {
|
||||
bool tradeable, sellable;
|
||||
int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense, gender; // TODO: implement more as needed
|
||||
};
|
||||
struct VendorListing {
|
||||
int sort, type, iID;
|
||||
};
|
||||
struct CrocPotEntry {
|
||||
int multStats, multLooks;
|
||||
float base, rd0, rd1, rd2, rd3;
|
||||
};
|
||||
struct Crate {
|
||||
int rarityRatioId;
|
||||
std::vector<int> itemSets;
|
||||
};
|
||||
|
||||
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
|
||||
namespace ItemManager {
|
||||
enum class SlotType {
|
||||
EQUIP = 0,
|
||||
INVENTORY = 1,
|
||||
BANK = 3
|
||||
};
|
||||
// hopefully this is fine since it's never modified after load
|
||||
extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
|
||||
extern std::map<int32_t, std::vector<VendorListing>> VendorTables;
|
||||
extern std::map<int32_t, CrocPotEntry> CrocPotTable; // level gap -> entry
|
||||
extern std::map<int32_t, std::vector<int>> RarityRatios;
|
||||
extern std::map<int32_t, Crate> Crates;
|
||||
// pair <Itemset, Rarity> -> vector of pointers (map iterators) to records in ItemData (it looks a lot scarier than it is)
|
||||
extern std::map<std::pair<int32_t, int32_t>,
|
||||
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems;
|
||||
|
||||
void init();
|
||||
|
||||
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemGMGiveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemUseHandler(CNSocket* sock, CNPacketData* data);
|
||||
// Bank
|
||||
void itemBankOpenHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferHandler(CNSocket* sock, CNPacketData* data);
|
||||
//void itemTradeOfferCancel(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferAcceptHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeOfferRefusalHandler(CNSocket* sock, CNPacketData* data);
|
||||
void itemTradeConfirmHandler(CNSocket* sock, CNPacketData* data);
|
||||
@@ -21,5 +54,14 @@ namespace ItemManager {
|
||||
void itemTradeChatHandler(CNSocket* sock, CNPacketData* data);
|
||||
void chestOpenHandler(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// crate opening logic with all helper functions
|
||||
int getItemSetId(Crate& crate, int crateId);
|
||||
int getRarity(Crate& crate, int itemSetId);
|
||||
int getCrateItem(sItemBase& reward, int itemSetId, int rarity, int playerGender);
|
||||
|
||||
int findFreeSlot(Player *plr);
|
||||
Item* getItemData(int32_t id, int32_t type);
|
||||
void checkItemExpire(CNSocket* sock, Player* player);
|
||||
void setItemStats(Player* plr);
|
||||
void updateEquips(CNSocket* sock, Player* plr);
|
||||
}
|
||||
|
@@ -2,26 +2,98 @@
|
||||
#include "CNStructs.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
|
||||
#include "string.h"
|
||||
|
||||
std::map<int32_t, Reward*> MissionManager::Rewards;
|
||||
std::map<int32_t, TaskData*> MissionManager::Tasks;
|
||||
nlohmann::json MissionManager::AvatarGrowth[37];
|
||||
|
||||
void MissionManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, acceptMission);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, completeMission);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission);
|
||||
}
|
||||
|
||||
void MissionManager::acceptMission(CNSocket* sock, CNPacketData* data) {
|
||||
bool startTask(Player* plr, int TaskID, bool NanoMission) {
|
||||
if (MissionManager::Tasks.find(TaskID) == MissionManager::Tasks.end()) {
|
||||
std::cout << "[WARN] Player submitted unknown task!?" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// client freaks out if nano mission isn't sent first after reloging, so it's easiest to set it here
|
||||
if (NanoMission && plr->tasks[0] != 0) {
|
||||
// lets move task0 to different spot
|
||||
int moveToSlot = 1;
|
||||
for (; moveToSlot < ACTIVE_MISSION_COUNT; moveToSlot++)
|
||||
if (plr->tasks[moveToSlot] == 0)
|
||||
break;
|
||||
|
||||
plr->tasks[moveToSlot] = plr->tasks[0];
|
||||
plr->tasks[0] = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
plr->RemainingNPCCount[moveToSlot][i] = plr->RemainingNPCCount[0][i];
|
||||
plr->RemainingNPCCount[0][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
TaskData& task = *MissionManager::Tasks[TaskID];
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0) {
|
||||
plr->tasks[i] = TaskID;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != TaskID) {
|
||||
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_START))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_TASK_START* missionData = (sP_CL2FE_REQ_PC_TASK_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (!startTask(plr, missionData->iTaskNum, false)) {
|
||||
// TODO: TASK_FAIL?
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
return;
|
||||
}
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
|
||||
// HACK: auto-succeed Eduardo escort task
|
||||
// TODO: maybe check for iTaskType == 6 and skip all escort missions?
|
||||
if (missionData->iTaskNum == 576) {
|
||||
std::cout << "Sending Eduardo success packet" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||
|
||||
endTask(sock, 576);
|
||||
response.iTaskNum = 576;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
||||
}
|
||||
}
|
||||
|
||||
void MissionManager::completeMission(CNSocket* sock, CNPacketData* data) {
|
||||
void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_END))
|
||||
return; // malformed packet
|
||||
|
||||
@@ -29,17 +101,94 @@ void MissionManager::completeMission(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
|
||||
if (!endTask(sock, missionData->iTaskNum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
||||
}
|
||||
|
||||
bool MissionManager::endTask(CNSocket *sock, int32_t taskNum) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
if (Tasks.find(taskNum) == Tasks.end())
|
||||
return false;
|
||||
|
||||
// ugly pointer/reference juggling for the sake of operator overloading...
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// mission rewards
|
||||
if (Rewards.find(taskNum) != Rewards.end()) {
|
||||
if (giveMissionReward(sock, taskNum) == -1)
|
||||
return false; // we don't want to send anything
|
||||
}
|
||||
// don't take away quest items if we haven't finished the quest
|
||||
|
||||
/*
|
||||
* Give (or take away) quest items
|
||||
*
|
||||
* Some mission tasks give the player a quest item upon completion.
|
||||
* This is distinct from quest item mob drops.
|
||||
* They can be identified by a counter in the task indicator (ie. 1/1 Gravity Decelerator).
|
||||
* The server is responsible for dropping the correct item.
|
||||
* Yes, this is pretty stupid.
|
||||
*
|
||||
* iSUInstancename is the number of items to give. It is usually negative at the end of
|
||||
* a mission, to clean up its quest items.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (task["m_iSUItem"][i] != 0)
|
||||
dropQuestItem(sock, taskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
|
||||
|
||||
// update player
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
|
||||
}
|
||||
|
||||
// if it's the last task
|
||||
if (task["m_iSUOutgoingTask"] == 0) {
|
||||
// save completed mission on player
|
||||
saveMission(plr, (int)(task["m_iHMissionID"])-1);
|
||||
|
||||
// if it's a nano mission, reward the nano.
|
||||
if (task["m_iSTNanoID"] != 0)
|
||||
NanoManager::addNano(sock, task["m_iSTNanoID"], 0, true);
|
||||
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response);
|
||||
|
||||
response.iCurrentMissionID = missionData->iCurrentMissionID;
|
||||
plr->CurrentMissionID = missionData->iCurrentMissionID;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
|
||||
}
|
||||
|
||||
@@ -48,8 +197,333 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
|
||||
quitTask(sock, missionData->iTaskNum, true);
|
||||
}
|
||||
|
||||
response.iTaskNum = missionData->iTaskNum;
|
||||
void MissionManager::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// update player
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
||||
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
|
||||
}
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
|
||||
TaskData& task = *Tasks[taskNum];
|
||||
|
||||
// clean up quest items
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* It's ok to do this only server-side, because the server decides which
|
||||
* slot later items will be placed in.
|
||||
*/
|
||||
for (int j = 0; j < AQINVEN_COUNT; j++)
|
||||
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i])
|
||||
memset(&plr->QInven[j], 0, sizeof(sItemBase));
|
||||
}
|
||||
|
||||
if (!manual) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
|
||||
failResp.iErrorCode = 1;
|
||||
failResp.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
|
||||
response.iTaskNum = taskNum;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
|
||||
}
|
||||
}
|
||||
|
||||
int MissionManager::findQSlot(Player *plr, int id) {
|
||||
int i;
|
||||
|
||||
// two passes. we mustn't fail to find an existing stack.
|
||||
for (i = 0; i < AQINVEN_COUNT; i++)
|
||||
if (plr->QInven[i].iID == id)
|
||||
return i;
|
||||
|
||||
// no stack. start a new one.
|
||||
for (i = 0; i < AQINVEN_COUNT; i++)
|
||||
if (plr->QInven[i].iOpt == 0)
|
||||
return i;
|
||||
|
||||
// not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) {
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// find free quest item slot
|
||||
int slot = findQSlot(plr, id);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << 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 += count; // stacking
|
||||
}
|
||||
|
||||
// fully destory deleted items, for good measure
|
||||
if (plr->QInven[slot].iOpt <= 0)
|
||||
memset(&plr->QInven[slot], 0, sizeof(sItemBase));
|
||||
|
||||
// preserve stats
|
||||
reward->m_iCandy = plr->money;
|
||||
reward->m_iFusionMatter = plr->fusionmatter;
|
||||
reward->iFatigue = 100; // prevents warning message
|
||||
reward->iFatigue_Level = 1;
|
||||
reward->m_iBatteryN = plr->batteryN;
|
||||
reward->m_iBatteryW = plr->batteryW;
|
||||
|
||||
reward->iItemCnt = 1; // remember to update resplen if you change this
|
||||
reward->iTaskID = task;
|
||||
reward->iNPC_TypeID = mobid;
|
||||
|
||||
item->sItem = plr->QInven[slot];
|
||||
item->iSlotNum = slot;
|
||||
item->eIL = 2;
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
}
|
||||
|
||||
int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
||||
Reward *reward = Rewards[task];
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return -1;
|
||||
|
||||
int nrewards = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (reward->itemIds[i] != 0)
|
||||
nrewards++;
|
||||
}
|
||||
|
||||
int slots[4];
|
||||
for (int i = 0; i < nrewards; i++) {
|
||||
slots[i] = ItemManager::findFreeSlot(plr);
|
||||
if (slots[i] == -1) {
|
||||
std::cout << "Not enough room to complete task" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, fail);
|
||||
|
||||
fail.iTaskNum = task;
|
||||
fail.iErrorCode = 13; // inventory full
|
||||
|
||||
sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE);
|
||||
sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
|
||||
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
||||
|
||||
// don't forget to zero the buffer!
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
// update player
|
||||
plr->money += reward->money;
|
||||
updateFusionMatter(sock, reward->fusionmatter);
|
||||
|
||||
// simple rewards
|
||||
resp->m_iCandy = plr->money;
|
||||
resp->m_iFusionMatter = plr->fusionmatter;
|
||||
resp->iFatigue = 100; // prevents warning message
|
||||
resp->iFatigue_Level = 1;
|
||||
resp->iItemCnt = nrewards;
|
||||
resp->m_iBatteryN = plr->batteryN;
|
||||
resp->m_iBatteryW = plr->batteryW;
|
||||
|
||||
for (int i = 0; i < nrewards; i++) {
|
||||
item[i].sItem.iType = reward->itemTypes[i];
|
||||
item[i].sItem.iID = reward->itemIds[i];
|
||||
item[i].iSlotNum = slots[i];
|
||||
item[i].eIL = 1;
|
||||
|
||||
// update player
|
||||
plr->Inven[slots[i]] = item->sItem;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MissionManager::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
plr->fusionmatter += fusion;
|
||||
|
||||
// there's a much lower FM cap in the Future
|
||||
if (plr->fusionmatter > AvatarGrowth[plr->level]["m_iFMLimit"])
|
||||
plr->fusionmatter = AvatarGrowth[plr->level]["m_iFMLimit"];
|
||||
else if (plr->fusionmatter < 0) // if somehow lowered too far
|
||||
plr->fusionmatter = 0;
|
||||
|
||||
// check if it is enough for the nano mission
|
||||
if (plr->fusionmatter <= AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"])
|
||||
return;
|
||||
|
||||
// don't give the Blossom nano mission until the player's in the Past
|
||||
if (plr->level == 4 && plr->PCStyle2.iPayzoneFlag == 0)
|
||||
return;
|
||||
|
||||
// check if the nano task is already started
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
TaskData& task = *Tasks[plr->tasks[i]];
|
||||
if (task["m_iSTNanoID"] != 0)
|
||||
return; // nano mission was already started!
|
||||
}
|
||||
|
||||
// start the nano mission
|
||||
startTask(plr, AvatarGrowth[plr->level]["m_iNanoQuestTaskID"], true);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
||||
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));
|
||||
}
|
||||
|
||||
void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
bool missionmob = false;
|
||||
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0)
|
||||
continue;
|
||||
|
||||
// tasks[] should always have valid IDs
|
||||
TaskData& task = *Tasks[plr->tasks[i]];
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (task["m_iCSUEnemyID"][j] != mobid)
|
||||
continue;
|
||||
|
||||
// acknowledge killing of mission mob...
|
||||
if (task["m_iCSUNumToKill"][j] != 0) {
|
||||
missionmob = true;
|
||||
// sanity check
|
||||
if (plr->RemainingNPCCount[i][j] == 0) {
|
||||
std::cout << "[WARN] RemainingNPCCount tries to go below 0?!" << std::endl;
|
||||
} else {
|
||||
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 = rand() % 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);
|
||||
} else {
|
||||
// fail to drop (itemID == 0)
|
||||
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...but only once
|
||||
// XXX: is it actually necessary to do it this way?
|
||||
if (missionmob) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, kill);
|
||||
|
||||
kill.iNPCID = mobid;
|
||||
|
||||
sock->sendPacket((void*)&kill, P_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, sizeof(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC));
|
||||
}
|
||||
}
|
||||
|
||||
void MissionManager::saveMission(Player* player, int missionId) {
|
||||
// sanity check missionID so we don't get exceptions
|
||||
if (missionId < 0 || missionId>1023) {
|
||||
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Missions are stored in int64_t array
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
player->aQuestFlag[row] |= (1ULL << column);
|
||||
}
|
||||
|
||||
bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return true;
|
||||
|
||||
int slot = findQSlot(plr, itemId);
|
||||
if (slot == -1) {
|
||||
// this should never happen
|
||||
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (itemCount == plr->QInven[slot].iOpt);
|
||||
}
|
||||
|
||||
void MissionManager::failInstancedMissions(CNSocket* sock) {
|
||||
// loop through all tasks; if the required instance is being left, "fail" the task
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int taskNum = plr->tasks[i];
|
||||
if (MissionManager::Tasks.find(taskNum) == MissionManager::Tasks.end())
|
||||
continue; // sanity check
|
||||
|
||||
TaskData* task = MissionManager::Tasks[taskNum];
|
||||
if (task->task["m_iRequireInstanceID"] != 0) { // mission is instanced
|
||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||
if (failTaskID != 0) {
|
||||
MissionManager::quitTask(sock, taskNum, false);
|
||||
plr->tasks[i] = failTaskID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
struct Reward {
|
||||
int32_t id;
|
||||
int32_t itemTypes[4];
|
||||
int32_t itemIds[4];
|
||||
int32_t money;
|
||||
int32_t fusionmatter;
|
||||
|
||||
Reward(int32_t id, nlohmann::json types, nlohmann::json ids, int32_t m, int32_t fm) :
|
||||
id(id), money(m), fusionmatter(fm) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
itemTypes[i] = types[i];
|
||||
itemIds[i] = ids[i];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct TaskData {
|
||||
/*
|
||||
* TODO: We'll probably want to keep only the data the server actually needs,
|
||||
* but for now RE/development is much easier if we have everything at
|
||||
* our fingertips.
|
||||
*/
|
||||
nlohmann::json task;
|
||||
|
||||
TaskData(nlohmann::json t) : task(t) {}
|
||||
|
||||
// convenience
|
||||
auto operator[](std::string s) { return task[s]; }
|
||||
};
|
||||
|
||||
namespace MissionManager {
|
||||
extern std::map<int32_t, Reward*> Rewards;
|
||||
extern std::map<int32_t, TaskData*> Tasks;
|
||||
extern nlohmann::json AvatarGrowth[37];
|
||||
void init();
|
||||
|
||||
void acceptMission(CNSocket* sock, CNPacketData* data);
|
||||
void completeMission(CNSocket* sock, CNPacketData* data);
|
||||
void taskStart(CNSocket* sock, CNPacketData* data);
|
||||
void taskEnd(CNSocket* sock, CNPacketData* data);
|
||||
void setMission(CNSocket* sock, CNPacketData* data);
|
||||
void quitMission(CNSocket* sock, CNPacketData* data);
|
||||
}
|
||||
|
||||
int findQSlot(Player *plr, int id);
|
||||
void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid);
|
||||
// checks if player doesn't have n/n quest items
|
||||
bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount);
|
||||
int giveMissionReward(CNSocket *sock, int task);
|
||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid);
|
||||
|
||||
bool endTask(CNSocket *sock, int32_t taskNum);
|
||||
void saveMission(Player* player, int missionId);
|
||||
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
|
||||
|
||||
void failInstancedMissions(CNSocket* sock);
|
||||
}
|
||||
|
1085
src/MobManager.cpp
Normal file
1085
src/MobManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
138
src/MobManager.hpp
Normal file
138
src/MobManager.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPC.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
enum class MobState {
|
||||
INACTIVE,
|
||||
ROAMING,
|
||||
COMBAT,
|
||||
RETREAT,
|
||||
DEAD
|
||||
};
|
||||
|
||||
struct Mob : public BaseNPC {
|
||||
// general
|
||||
MobState state;
|
||||
int maxHealth;
|
||||
int spawnX;
|
||||
int spawnY;
|
||||
int spawnZ;
|
||||
int level;
|
||||
|
||||
// dead
|
||||
time_t killedTime = 0;
|
||||
time_t regenTime;
|
||||
bool summoned = false;
|
||||
bool despawned = false; // for the sake of death animations
|
||||
|
||||
// roaming
|
||||
int idleRange;
|
||||
time_t nextMovement = 0;
|
||||
bool staticPath = false;
|
||||
|
||||
// combat
|
||||
CNSocket *target = nullptr;
|
||||
time_t nextAttack = 0;
|
||||
int roamX, roamY, roamZ;
|
||||
|
||||
// drop
|
||||
int dropType;
|
||||
|
||||
// temporary; until we're sure what's what
|
||||
nlohmann::json data;
|
||||
|
||||
Mob(int x, int y, int z, int angle, uint64_t iID, int type, int hp, nlohmann::json d, int32_t id)
|
||||
: BaseNPC(x, y, z, angle, iID, type, id), maxHealth(hp) {
|
||||
state = MobState::ROAMING;
|
||||
|
||||
data = d;
|
||||
|
||||
regenTime = data["m_iRegenTime"];
|
||||
idleRange = (int)data["m_iIdleRange"] * 2; // TODO: tuning?
|
||||
dropType = data["m_iDropType"];
|
||||
level = data["m_iNpcLevel"];
|
||||
|
||||
roamX = spawnX = appearanceData.iX;
|
||||
roamY = spawnY = appearanceData.iY;
|
||||
roamZ = spawnZ = appearanceData.iZ;
|
||||
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
appearanceData.iHP = maxHealth;
|
||||
|
||||
npcClass = NPC_MOB;
|
||||
}
|
||||
|
||||
// constructor for /summon
|
||||
Mob(int x, int y, int z, uint64_t iID, int type, nlohmann::json d, int32_t id)
|
||||
: Mob(x, y, z, 0, iID, type, 0, d, id) {
|
||||
summoned = true; // will be despawned and deallocated when killed
|
||||
appearanceData.iHP = maxHealth = d["m_iHP"];
|
||||
}
|
||||
|
||||
~Mob() {}
|
||||
|
||||
auto operator[](std::string s) {
|
||||
return data[s];
|
||||
}
|
||||
};
|
||||
|
||||
struct MobDropChance {
|
||||
int dropChance;
|
||||
std::vector<int> cratesRatio;
|
||||
};
|
||||
|
||||
struct MobDrop {
|
||||
std::vector<int> crateIDs;
|
||||
int dropChanceType;
|
||||
int taros;
|
||||
int fm;
|
||||
int boosts;
|
||||
};
|
||||
|
||||
namespace MobManager {
|
||||
extern std::map<int32_t, Mob*> Mobs;
|
||||
extern std::queue<int32_t> RemovalQueue;
|
||||
extern std::map<int32_t, MobDropChance> MobDropChances;
|
||||
extern std::map<int32_t, MobDrop> MobDrops;
|
||||
extern bool simulateMobs;
|
||||
|
||||
void init();
|
||||
void step(CNServer*, time_t);
|
||||
void playerTick(CNServer*, time_t);
|
||||
|
||||
void deadStep(Mob*, time_t);
|
||||
void combatStep(Mob*, time_t);
|
||||
void retreatStep(Mob*, time_t);
|
||||
void roamingStep(Mob*, time_t);
|
||||
|
||||
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
|
||||
void combatBegin(CNSocket *sock, CNPacketData *data);
|
||||
void combatEnd(CNSocket *sock, CNPacketData *data);
|
||||
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
|
||||
void dealGooDamage(CNSocket *sock, int amount);
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
||||
void killMob(CNSocket *sock, Mob *mob);
|
||||
void giveReward(CNSocket *sock, Mob *mob);
|
||||
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance);
|
||||
void giveEventReward(CNSocket* sock, Player* player);
|
||||
|
||||
std::pair<int,int> lerp(int, int, int, int, int);
|
||||
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
|
||||
|
||||
void pcAttackChars(CNSocket *sock, CNPacketData *data);
|
||||
void resendMobHP(Mob *mob);
|
||||
void incNextMovement(Mob *mob, time_t currTime=0);
|
||||
bool aggroCheck(Mob *mob, time_t currTime);
|
||||
}
|
30
src/NPC.hpp
30
src/NPC.hpp
@@ -1,37 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNStructs.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
|
||||
class BaseNPC {
|
||||
public:
|
||||
sNPCAppearanceData appearanceData;
|
||||
NPCClass npcClass;
|
||||
uint64_t instanceID;
|
||||
std::tuple<int, int, uint64_t> chunkPos;
|
||||
std::vector<Chunk*> currentChunks;
|
||||
|
||||
BaseNPC() {};
|
||||
BaseNPC(int x, int y, int z, int type) {
|
||||
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id) {
|
||||
appearanceData.iX = x;
|
||||
appearanceData.iY = y;
|
||||
appearanceData.iZ = z;
|
||||
appearanceData.iNPCType = type;
|
||||
appearanceData.iHP = 400;
|
||||
appearanceData.iAngle = 0;
|
||||
appearanceData.iAngle = angle;
|
||||
appearanceData.iConditionBitFlag = 0;
|
||||
appearanceData.iBarkerType = 0;
|
||||
appearanceData.iNPC_ID = id;
|
||||
|
||||
// hopefully no collisions happen :eyes:
|
||||
appearanceData.iNPC_ID = (int32_t)rand();
|
||||
instanceID = iID;
|
||||
|
||||
chunkPos = std::make_tuple(0, 0, instanceID);
|
||||
};
|
||||
|
||||
BaseNPC(int x, int y, int z, int type, int hp, int cond, int angle, int barker) {
|
||||
appearanceData.iX = x;
|
||||
appearanceData.iY = y;
|
||||
appearanceData.iZ = z;
|
||||
appearanceData.iNPCType = type;
|
||||
appearanceData.iHP = hp;
|
||||
appearanceData.iAngle = angle;
|
||||
appearanceData.iConditionBitFlag = cond;
|
||||
appearanceData.iBarkerType = barker;
|
||||
|
||||
// hopefully no collisions happen :eyes:
|
||||
appearanceData.iNPC_ID = (int32_t)rand();
|
||||
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id, NPCClass classType) : BaseNPC(x, y, z, angle, iID, type, id) {
|
||||
npcClass = classType;
|
||||
}
|
||||
};
|
||||
|
@@ -1,171 +1,568 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
std::map<int32_t, BaseNPC> NPCManager::NPCs;
|
||||
std::map<int32_t, BaseNPC*> NPCManager::NPCs;
|
||||
std::map<int32_t, WarpLocation> NPCManager::Warps;
|
||||
std::vector<WarpLocation> NPCManager::RespawnPoints;
|
||||
nlohmann::json NPCManager::NPCData;
|
||||
|
||||
/*
|
||||
* Initialized at the end of TableData::init().
|
||||
* This allows us to summon and kill mobs in arbitrary order without
|
||||
* NPC ID collisions.
|
||||
*/
|
||||
int32_t NPCManager::nextId;
|
||||
|
||||
void NPCManager::init() {
|
||||
// load NPCs from NPCs.json into our NPC manager
|
||||
// Temporary fix, IDs will be pulled from json later
|
||||
int i = 0;
|
||||
|
||||
try {
|
||||
std::ifstream inFile(settings::NPCJSON);
|
||||
nlohmann::json npcData;
|
||||
|
||||
// read file into json
|
||||
inFile >> npcData;
|
||||
|
||||
for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) {
|
||||
BaseNPC tmp(npc.value()["x"], npc.value()["y"], npc.value()["z"], npc.value()["id"]);
|
||||
|
||||
// Temporary fix, IDs will be pulled from json later
|
||||
tmp.appearanceData.iNPC_ID = i;
|
||||
i++;
|
||||
|
||||
NPCs[tmp.appearanceData.iNPC_ID] = tmp;
|
||||
|
||||
if (npc.value()["id"] == 641 || npc.value()["id"] == 642)
|
||||
RespawnPoints.push_back({ npc.value()["x"], npc.value()["y"], ((int)npc.value()["z"]) + RESURRECT_HEIGHT });
|
||||
}
|
||||
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
|
||||
}
|
||||
|
||||
// load temporary mob dump
|
||||
try {
|
||||
std::ifstream inFile(settings::MOBJSON); // not in settings, since it's temp
|
||||
nlohmann::json npcData;
|
||||
|
||||
// read file into json
|
||||
inFile >> npcData;
|
||||
|
||||
for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) {
|
||||
BaseNPC tmp(npc.value()["iX"], npc.value()["iY"], npc.value()["iZ"], npc.value()["iNPCType"],
|
||||
npc.value()["iHP"], npc.value()["iConditionBitFlag"], npc.value()["iAngle"], npc.value()["iBarkerType"]);
|
||||
|
||||
// Temporary fix, IDs will be pulled from json later
|
||||
tmp.appearanceData.iNPC_ID = i;
|
||||
i++;
|
||||
|
||||
NPCs[tmp.appearanceData.iNPC_ID] = tmp;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] populated " << NPCs.size() << " NPCs" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl;
|
||||
}
|
||||
|
||||
try {
|
||||
std::ifstream infile(settings::WARPJSON);
|
||||
nlohmann::json warpData;
|
||||
|
||||
// read file into json
|
||||
infile >> warpData;
|
||||
|
||||
for (nlohmann::json::iterator warp = warpData.begin(); warp != warpData.end(); warp++) {
|
||||
WarpLocation warpLoc = { warp.value()["m_iToX"], warp.value()["m_iToY"], warp.value()["m_iToZ"] };
|
||||
int warpID = atoi(warp.key().c_str());
|
||||
Warps[warpID] = warpLoc;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] populated " << Warps.size() << " Warps" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[WARN] Malformed warps.json file! Reason:" << err.what() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_NPC, npcWarpHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TIME_TO_GO_WARP, npcWarpTimeMachine);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_SUMMON, npcSummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, npcVendorStart);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, npcVendorTable);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, npcVendorBuy);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, npcVendorSell);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems);
|
||||
}
|
||||
|
||||
void NPCManager::updatePlayerNPCS(CNSocket* sock, PlayerView& view) {
|
||||
std::list<int32_t> yesView;
|
||||
std::list<int32_t> noView;
|
||||
void NPCManager::removeNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
for (auto& pair : NPCs) {
|
||||
int diffX = abs(view.plr->x - pair.second.appearanceData.iX);
|
||||
int diffY = abs(view.plr->y - pair.second.appearanceData.iY);
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData);
|
||||
exitBusData.eTT = 3;
|
||||
exitBusData.iT_ID = id;
|
||||
|
||||
if (diffX < settings::NPCDISTANCE && diffY < settings::NPCDISTANCE) {
|
||||
yesView.push_back(pair.first);
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
|
||||
}
|
||||
}
|
||||
else {
|
||||
noView.push_back(pair.first);
|
||||
break;
|
||||
default:
|
||||
// create struct
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
|
||||
exitData.iNPC_ID = id;
|
||||
|
||||
// remove it from the clients
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::addNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData);
|
||||
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
|
||||
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// create struct
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
|
||||
enterData.NPCAppearanceData = npc->appearanceData;
|
||||
|
||||
for (Chunk* chunk : viewableChunks) {
|
||||
for (CNSocket* sock : chunk->players) {
|
||||
// send to socket
|
||||
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::destroyNPC(int32_t id) {
|
||||
// sanity check
|
||||
if (NPCs.find(id) == NPCs.end()) {
|
||||
std::cout << "npc not found : " << id << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
|
||||
std::list<int32_t>::iterator i = view.viewableNPCs.begin();
|
||||
while (i != view.viewableNPCs.end()) {
|
||||
int32_t id = *i;
|
||||
BaseNPC* entity = NPCs[id];
|
||||
|
||||
if (std::find(noView.begin(), noView.end(), id) != noView.end()) {
|
||||
// it shouldn't be visible, send NPC_EXIT
|
||||
|
||||
exitData.iNPC_ID = id;
|
||||
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
|
||||
// remove from view
|
||||
view.viewableNPCs.erase(i++);
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
// sanity check
|
||||
if (ChunkManager::chunks.find(entity->chunkPos) == ChunkManager::chunks.end()) {
|
||||
std::cout << "chunk not found!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
|
||||
for (int32_t id : yesView) {
|
||||
if (std::find(view.viewableNPCs.begin(), view.viewableNPCs.end(), id) == view.viewableNPCs.end()) {
|
||||
// needs to be added to viewableNPCs! send NPC_ENTER
|
||||
// remove NPC from the chunk
|
||||
Chunk* chunk = ChunkManager::chunks[entity->chunkPos];
|
||||
chunk->NPCs.erase(id);
|
||||
|
||||
enterData.NPCAppearanceData = NPCs[id].appearanceData;
|
||||
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
// remove from viewable chunks
|
||||
removeNPC(entity->currentChunks, id);
|
||||
|
||||
// add to viewable
|
||||
view.viewableNPCs.push_back(id);
|
||||
}
|
||||
// remove from mob manager
|
||||
if (MobManager::Mobs.find(id) != MobManager::Mobs.end())
|
||||
MobManager::Mobs.erase(id);
|
||||
|
||||
// finally, remove it from the map and free it
|
||||
NPCs.erase(id);
|
||||
delete entity;
|
||||
|
||||
std::cout << "npc removed!" << std::endl;
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, int angle) {
|
||||
NPCs[id]->appearanceData.iAngle = angle;
|
||||
updateNPCPosition(id, X, Y, Z);
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z) {
|
||||
BaseNPC* npc = NPCs[id];
|
||||
|
||||
npc->appearanceData.iX = X;
|
||||
npc->appearanceData.iY = Y;
|
||||
npc->appearanceData.iZ = Z;
|
||||
std::tuple<int, int, uint64_t> newPos = ChunkManager::grabChunk(X, Y, npc->instanceID);
|
||||
|
||||
// nothing to be done (but we should also update currentChunks to add/remove stale chunks)
|
||||
if (newPos == npc->chunkPos) {
|
||||
npc->currentChunks = ChunkManager::grabChunks(newPos);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerManager::players[sock].viewableNPCs = view.viewableNPCs;
|
||||
std::vector<Chunk*> allChunks = ChunkManager::grabChunks(newPos);
|
||||
|
||||
// send npc exit to stale chunks
|
||||
removeNPC(ChunkManager::getDeltaChunks(npc->currentChunks, allChunks), id);
|
||||
|
||||
// send npc enter to new chunks
|
||||
addNPC(ChunkManager::getDeltaChunks(allChunks, npc->currentChunks), id);
|
||||
|
||||
Chunk *chunk = nullptr;
|
||||
if (ChunkManager::checkChunk(npc->chunkPos))
|
||||
chunk = ChunkManager::chunks[npc->chunkPos];
|
||||
|
||||
if (ChunkManager::removeNPC(npc->chunkPos, id)) {
|
||||
// if the old chunk was deallocated, remove it
|
||||
allChunks.erase(std::remove(allChunks.begin(), allChunks.end(), chunk), allChunks.end());
|
||||
}
|
||||
|
||||
ChunkManager::addNPC(X, Y, npc->instanceID, id);
|
||||
|
||||
npc->chunkPos = newPos;
|
||||
npc->currentChunks = allChunks;
|
||||
}
|
||||
|
||||
void NPCManager::updateNPCInstance(int32_t npcID, uint64_t instanceID) {
|
||||
BaseNPC* npc = NPCs[npcID];
|
||||
npc->instanceID = instanceID;
|
||||
updateNPCPosition(npcID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ);
|
||||
}
|
||||
|
||||
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
|
||||
for (Chunk *chunk : npc->currentChunks) {
|
||||
for (CNSocket *s : chunk->players) {
|
||||
s->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
|
||||
|
||||
if (item == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl;
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
int itemCost = item->buyPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
|
||||
int slot = ItemManager::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
// if vehicle
|
||||
if (req->Item.iType == 10)
|
||||
// set time limit: current time + 7days
|
||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = req->Item;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) {
|
||||
std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* item = &plr->Inven[req->iInvenSlotNum];
|
||||
Item* itemData = ItemManager::getItemData(item->iID, item->iType);
|
||||
|
||||
if (itemData == nullptr || !itemData->sellable) { // sanity + sellable check
|
||||
std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase original;
|
||||
memcpy(&original, item, sizeof(sItemBase));
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
|
||||
|
||||
int sellValue = itemData->sellPrice * req->iItemCnt;
|
||||
|
||||
// increment taros
|
||||
plr->money = plr->money + sellValue;
|
||||
|
||||
// modify item
|
||||
if (plr->Inven[req->iInvenSlotNum].iOpt - req->iItemCnt > 0) { // selling part of a stack
|
||||
item->iOpt -= req->iItemCnt;
|
||||
original.iOpt = req->iItemCnt;
|
||||
} else { // selling entire slot
|
||||
item->iID = 0;
|
||||
item->iOpt = 0;
|
||||
item->iType = 0;
|
||||
item->iTimeLimit = 0;
|
||||
}
|
||||
|
||||
// response parameters
|
||||
resp.iInvenSlotNum = req->iInvenSlotNum;
|
||||
resp.iCandy = plr->money;
|
||||
resp.Item = original; // the item that gets sent to buyback
|
||||
resp.ItemStay = *item; // the void item that gets put in the slot
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuyback(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
|
||||
|
||||
if (item == nullptr) {
|
||||
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (rebuy)" << std::endl;
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
// sell price is used on rebuy. ternary identifies stacked items
|
||||
int itemCost = item->sellPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
|
||||
int slot = ItemManager::findFreeSlot(plr);
|
||||
if (itemCost > plr->money || slot == -1) {
|
||||
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
// possible item stacking?
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
|
||||
// response parameters
|
||||
resp.iCandy = plr->money;
|
||||
resp.iInvenSlotNum = slot;
|
||||
resp.Item = req->Item;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf;
|
||||
|
||||
if (req->iVendorID != req->iNPC_ID || ItemManager::VendorTables.find(req->iNPC_ID) == ItemManager::VendorTables.end())
|
||||
return;
|
||||
|
||||
std::vector<VendorListing> listings = ItemManager::VendorTables[req->iNPC_ID]; // maybe use iVendorID instead...?
|
||||
|
||||
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].iID;
|
||||
base.iOpt = 0;
|
||||
base.iTimeLimit = 0;
|
||||
base.iType = listings[i].type;
|
||||
|
||||
sItemVendor vItem;
|
||||
vItem.item = base;
|
||||
vItem.iSortNum = listings[i].sort;
|
||||
vItem.iVendorID = req->iVendorID;
|
||||
//vItem.fBuyCost = listings[i].price; // this value is not actually the one that is used
|
||||
|
||||
resp.item[i] = vItem;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorStart(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_START))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_START* req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp);
|
||||
|
||||
resp.iNPC_ID = req->iNPC_ID;
|
||||
resp.iVendorID = req->iVendorID;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_START_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_START_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int cost = req->Item.iOpt * 10;
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL));
|
||||
}
|
||||
|
||||
plr->money -= cost;
|
||||
plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 10 : 0;
|
||||
plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 10 : 0;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
resp.iBatteryW = plr->batteryW;
|
||||
resp.iBatteryN = plr->batteryN;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_ITEM_COMBINATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
|
||||
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
|
||||
failResp.iStatItemSlot = req->iStatItemSlot;
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
|
||||
std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sItemBase* itemStats = &plr->Inven[req->iStatItemSlot];
|
||||
sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot];
|
||||
Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType);
|
||||
Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType);
|
||||
|
||||
if (itemStatsDat == nullptr || itemLooksDat == nullptr
|
||||
|| ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { // sanity check 2
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
|
||||
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
|
||||
failResp.iStatItemSlot = req->iStatItemSlot;
|
||||
failResp.iErrorCode = 0;
|
||||
std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl;
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)];
|
||||
int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks;
|
||||
float successChance = recipe->base / 100.0f; // base success chance
|
||||
|
||||
// rarity gap multiplier
|
||||
switch(abs(itemStatsDat->rarity - itemLooksDat->rarity)) {
|
||||
case 0:
|
||||
successChance *= recipe->rd0;
|
||||
break;
|
||||
case 1:
|
||||
successChance *= recipe->rd1;
|
||||
break;
|
||||
case 2:
|
||||
successChance *= recipe->rd2;
|
||||
break;
|
||||
case 3:
|
||||
successChance *= recipe->rd3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100
|
||||
//std::cout << rolled << " vs " << successChance << std::endl;
|
||||
plr->money -= cost;
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
|
||||
if (rolled < successChance) {
|
||||
// success
|
||||
resp.iSuccessFlag = 1;
|
||||
|
||||
// modify the looks item with the new stats and set the appearance through iOpt
|
||||
itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16;
|
||||
itemLooks->iID = itemStats->iID;
|
||||
|
||||
// delete stats item
|
||||
itemStats->iID = 0;
|
||||
itemStats->iOpt = 0;
|
||||
itemStats->iTimeLimit = 0;
|
||||
itemStats->iType = 0;
|
||||
} else {
|
||||
// failure; don't do anything?
|
||||
resp.iSuccessFlag = 0;
|
||||
}
|
||||
resp.iCandy = plr->money;
|
||||
resp.iNewItemSlot = req->iCostumeItemSlot;
|
||||
resp.iStatItemSlot = req->iStatItemSlot;
|
||||
resp.sNewItem = *itemLooks;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC));
|
||||
}
|
||||
|
||||
void NPCManager::npcBarkHandler(CNSocket* sock, CNPacketData* data) {} // stubbed for now
|
||||
|
||||
void NPCManager::npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NPC_UNSUMMON))
|
||||
return; // malformed packet
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr || plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
sP_CL2FE_REQ_NPC_UNSUMMON* req = (sP_CL2FE_REQ_NPC_UNSUMMON*)data->buf;
|
||||
NPCManager::destroyNPC(req->iNPC_ID);
|
||||
}
|
||||
|
||||
void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NPC_SUMMON))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NPC_SUMMON* req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, resp);
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// permission & sanity check
|
||||
if (!plr->IsGM || req->iNPCType >= 3314)
|
||||
if (plr == nullptr || plr->accountLevel > 30 || req->iNPCType >= 3314 || req->iNPCCnt > 100)
|
||||
return;
|
||||
|
||||
resp.NPCAppearanceData.iNPC_ID = rand(); // cpunch-style
|
||||
resp.NPCAppearanceData.iNPCType = req->iNPCType;
|
||||
resp.NPCAppearanceData.iHP = 1000; // TODO: placeholder
|
||||
resp.NPCAppearanceData.iX = plr->x;
|
||||
resp.NPCAppearanceData.iY = plr->y;
|
||||
resp.NPCAppearanceData.iZ = plr->z;
|
||||
int team = NPCData[req->iNPCType]["m_iTeam"];
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
for (int i = 0; i < req->iNPCCnt; i++) {
|
||||
assert(nextId < INT32_MAX);
|
||||
int id = nextId++;
|
||||
|
||||
if (team == 2) {
|
||||
NPCs[id] = new Mob(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, NPCData[req->iNPCType], id);
|
||||
MobManager::Mobs[id] = (Mob*)NPCs[id];
|
||||
} else
|
||||
NPCs[id] = new BaseNPC(plr->x, plr->y, plr->z, 0, plr->instanceID, req->iNPCType, id);
|
||||
|
||||
updateNPCPosition(id, plr->x, plr->y, plr->z);
|
||||
}
|
||||
}
|
||||
|
||||
void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -173,21 +570,77 @@ void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_WARP_USE_NPC* warpNpc = (sP_CL2FE_REQ_PC_WARP_USE_NPC*)data->buf;
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
handleWarp(sock, warpNpc->iWarpID);
|
||||
}
|
||||
|
||||
void NPCManager::npcWarpTimeMachine(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_TIME_TO_GO_WARP))
|
||||
return; // malformed packet
|
||||
// this is just a warp request
|
||||
handleWarp(sock, 28);
|
||||
}
|
||||
|
||||
void NPCManager::handleWarp(CNSocket* sock, int32_t warpId) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
// sanity check
|
||||
if (Warps.find(warpNpc->iWarpID) == Warps.end())
|
||||
if (Warps.find(warpId) == Warps.end())
|
||||
return;
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp);
|
||||
resp.iX = Warps[warpNpc->iWarpID].x;
|
||||
resp.iY = Warps[warpNpc->iWarpID].y;
|
||||
resp.iZ = Warps[warpNpc->iWarpID].z;
|
||||
MissionManager::failInstancedMissions(sock); // fail any missions that require the player's current instance
|
||||
|
||||
// force player & NPC reload
|
||||
plrv.viewable.clear();
|
||||
plrv.viewableNPCs.clear();
|
||||
uint64_t fromInstance = plrv.plr->instanceID; // saved for post-warp
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
|
||||
}
|
||||
if (plrv.plr->instanceID == 0) {
|
||||
// save last uninstanced coords
|
||||
plrv.plr->lastX = plrv.plr->x;
|
||||
plrv.plr->lastY = plrv.plr->y;
|
||||
plrv.plr->lastZ = plrv.plr->z;
|
||||
plrv.plr->lastAngle = plrv.plr->angle;
|
||||
}
|
||||
|
||||
// std::cerr << "Warped to Map Num:" << Warps[warpId].instanceID << " NPC ID " << Warps[warpId].npcID << std::endl;
|
||||
if (Warps[warpId].isInstance) {
|
||||
uint64_t instanceID = Warps[warpId].instanceID;
|
||||
if (Warps[warpId].limitTaskID != 0) { // if warp requires you to be on a mission, it's gotta be a unique instance
|
||||
instanceID += ((uint64_t)plrv.plr->iIDGroup << 32); // upper 32 bits are leader ID
|
||||
ChunkManager::createInstance(instanceID);
|
||||
}
|
||||
|
||||
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp); // Can only be used for exiting instances because it sets the instance flag to false
|
||||
resp.iX = Warps[warpId].x;
|
||||
resp.iY = Warps[warpId].y;
|
||||
resp.iZ = Warps[warpId].z;
|
||||
resp.iCandy = plrv.plr->money;
|
||||
resp.eIL = 4; // do not take away any items
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
plrv.plr->instanceID = INSTANCE_OVERWORLD;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
|
||||
}
|
||||
|
||||
// post-warp: check if the source instance has no more players in it and delete it if so
|
||||
ChunkManager::destroyInstanceIfEmpty(fromInstance);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to get NPC closest to coordinates in specified chunks
|
||||
*/
|
||||
BaseNPC* NPCManager::getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z) {
|
||||
BaseNPC* npc = nullptr;
|
||||
int lastDist = INT_MAX;
|
||||
for (auto c = chunks.begin(); c != chunks.end(); c++) { // haha get it
|
||||
Chunk* chunk = *c;
|
||||
for (auto _npc = chunk->NPCs.begin(); _npc != chunk->NPCs.end(); _npc++) {
|
||||
BaseNPC* npcTemp = NPCs[*_npc];
|
||||
int distXY = std::hypot(X - npcTemp->appearanceData.iX, Y - npcTemp->appearanceData.iY);
|
||||
int dist = std::hypot(distXY, Z - npcTemp->appearanceData.iZ);
|
||||
if (dist < lastDist) {
|
||||
npc = npcTemp;
|
||||
lastDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
return npc;
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPC.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -11,18 +13,41 @@
|
||||
|
||||
// this should really be called vec3 or something...
|
||||
struct WarpLocation {
|
||||
int x, y, z;
|
||||
int x, y, z, instanceID, isInstance, limitTaskID, npcID;
|
||||
};
|
||||
|
||||
namespace NPCManager {
|
||||
extern std::map<int32_t, BaseNPC> NPCs;
|
||||
extern std::map<int32_t, BaseNPC*> NPCs;
|
||||
extern std::map<int32_t, WarpLocation> Warps;
|
||||
extern std::vector<WarpLocation> RespawnPoints;
|
||||
extern nlohmann::json NPCData;
|
||||
extern int32_t nextId;
|
||||
void init();
|
||||
|
||||
void addNPC(std::vector<Chunk*> viewableChunks, int32_t id);
|
||||
void removeNPC(std::vector<Chunk*> viewableChunks, int32_t id);
|
||||
void destroyNPC(int32_t);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z, int angle);
|
||||
void updateNPCPosition(int32_t, int X, int Y, int Z);
|
||||
void updateNPCInstance(int32_t, uint64_t instanceID);
|
||||
|
||||
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
|
||||
|
||||
void npcBarkHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcSummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcUnsummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcWarpHandler(CNSocket* sock, CNPacketData* data);
|
||||
void npcWarpTimeMachine(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void updatePlayerNPCS(CNSocket* sock, PlayerView& plr);
|
||||
void npcVendorStart(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorTable(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuy(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorSell(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuyback(CNSocket* sock, CNPacketData* data);
|
||||
void npcVendorBuyBattery(CNSocket* sock, CNPacketData* data);
|
||||
void npcCombineItems(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void handleWarp(CNSocket* sock, int32_t warpId);
|
||||
|
||||
BaseNPC* getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z);
|
||||
}
|
||||
|
@@ -2,6 +2,50 @@
|
||||
#include "CNStructs.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
|
||||
namespace NanoManager {
|
||||
|
||||
// active powers
|
||||
std::set<int> StunPowers = {1, 13, 42, 59, 78, 103};
|
||||
std::set<int> HealPowers = {7, 12, 38, 53, 92, 98};
|
||||
std::set<int> GroupHealPowers = {2, 61, 82};
|
||||
std::set<int> RecallPowers = {5, 25, 66, 69, 75, 87};
|
||||
std::set<int> DrainPowers = {10, 34, 37, 56, 93, 97};
|
||||
std::set<int> SnarePowers = {17, 18, 27, 41, 43, 47, 90, 96, 106};
|
||||
std::set<int> DamagePowers = {19, 21, 33, 45, 46, 52, 101, 105, 108};
|
||||
std::set<int> GroupRevivePowers = {20, 63, 91};
|
||||
std::set<int> LeechPowers = {24, 51, 89};
|
||||
std::set<int> SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94};
|
||||
|
||||
// passive powers
|
||||
std::set<int> ScavangePowers = {3, 50, 99};
|
||||
std::set<int> RunPowers = {4, 68, 86};
|
||||
std::set<int> GroupRunPowers = {8, 62, 73};
|
||||
std::set<int> BonusPowers = {6, 54, 104};
|
||||
std::set<int> GuardPowers = {9, 57, 76};
|
||||
std::set<int> RadarPowers = {11, 67, 95};
|
||||
std::set<int> AntidotePowers = {14, 58, 102};
|
||||
std::set<int> FreedomPowers = {31, 39, 107};
|
||||
std::set<int> GroupFreedomPowers = {15, 55, 77};
|
||||
std::set<int> JumpPowers = {16, 44, 88};
|
||||
std::set<int> GroupJumpPowers = {35, 60, 100};
|
||||
std::set<int> SelfRevivePowers = {22, 48, 83};
|
||||
std::set<int> SneakPowers = {29, 72, 80};
|
||||
std::set<int> GroupSneakPowers = {23, 65, 84};
|
||||
std::set<int> TreasureFinderPowers = {26, 40, 74};
|
||||
|
||||
/*
|
||||
* The active nano power table is down below activePower<>() and its
|
||||
* worker functions so we don't have to have unsightly function declarations.
|
||||
*/
|
||||
|
||||
}; // namespace
|
||||
|
||||
std::map<int32_t, NanoData> NanoManager::NanoTable;
|
||||
|
||||
void NanoManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_ACTIVE, nanoSummonHandler);
|
||||
@@ -9,7 +53,10 @@ void NanoManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_UNEQUIP, nanoUnEquipHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO, nanoGMGiveHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_TUNE, nanoSkillSetHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO_SKILL, nanoSkillSetGMHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_SKILL_USE, nanoSkillUseHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_WARP_USE_RECALL, nanoRecallHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_CHARGE_NANO_STAMINA, nanoPotionHandler);
|
||||
}
|
||||
|
||||
void NanoManager::nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -20,8 +67,8 @@ void NanoManager::nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp);
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
// sanity checks
|
||||
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
resp.iNanoID = nano->iNanoID;
|
||||
@@ -46,7 +93,7 @@ void NanoManager::nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity check
|
||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||
return;
|
||||
|
||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||
@@ -69,6 +116,9 @@ void NanoManager::nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_GIVE_NANO* nano = (sP_CL2FE_REQ_PC_GIVE_NANO*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// Add nano to player
|
||||
addNano(sock, nano->iNanoID, 0);
|
||||
|
||||
@@ -84,6 +134,9 @@ void NanoManager::nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_NANO_ACTIVE* pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
summonNano(sock, pkt->iNanoSlotNum);
|
||||
|
||||
// Send to client
|
||||
@@ -93,27 +146,40 @@ void NanoManager::nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_NANO_SKILL_USE))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_SKILL_USE* skill = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_NANO_SKILL_USE_SUCC, resp);
|
||||
resp.iArg1 = skill->iArg1;
|
||||
resp.iArg2 = skill->iArg2;
|
||||
resp.iArg3 = skill->iArg3;
|
||||
resp.iBulletID = skill->iBulletID;
|
||||
resp.iTargetCnt = skill->iTargetCnt;
|
||||
resp.iPC_ID = plr->iID;
|
||||
resp.iNanoStamina = 150; // Hardcoded for now
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_NANO_SKILL_USE_SUCC, sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
||||
int16_t nanoId = plr->activeNano;
|
||||
int16_t skillId = plr->Nanos[nanoId].iSkillID;
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
for (auto& pwr : ActivePowers)
|
||||
if (pwr.powers.count(skillId)) // std::set's contains method is C++20 only...
|
||||
pwr.handle(sock, data, nanoId, skillId);
|
||||
|
||||
// Group Revive is handled separately (XXX: move into table?)
|
||||
if (GroupRevivePowers.find(skillId) == GroupRevivePowers.end())
|
||||
return;
|
||||
|
||||
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < leader->groupCnt; i++) {
|
||||
Player* varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
|
||||
if (varPlr == nullptr)
|
||||
return;
|
||||
|
||||
if (varPlr->HP <= 0)
|
||||
revivePlayer(varPlr);
|
||||
}
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -124,51 +190,158 @@ void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
|
||||
setNanoSkill(sock, skill->iNanoID, skill->iTuneID);
|
||||
}
|
||||
|
||||
void NanoManager::nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_GIVE_NANO_SKILL))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_NANO_TUNE* skillGM = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
|
||||
setNanoSkill(sock, skillGM->iNanoID, skillGM->iTuneID);
|
||||
}
|
||||
|
||||
void NanoManager::nanoRecallHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_WARP_USE_RECALL))
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, resp);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL));
|
||||
// stubbed for now
|
||||
}
|
||||
|
||||
void NanoManager::nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_CHARGE_NANO_STAMINA))
|
||||
return;
|
||||
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
// sanity checks
|
||||
if (player == nullptr || player->activeNano == -1 || player->batteryN == 0)
|
||||
return;
|
||||
|
||||
sNano nano = player->Nanos[player->activeNano];
|
||||
int difference = 150 - nano.iStamina;
|
||||
if (player->batteryN < difference)
|
||||
difference = player->batteryN;
|
||||
|
||||
if (difference == 0)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_CHARGE_NANO_STAMINA, response);
|
||||
response.iNanoID = nano.iID;
|
||||
response.iNanoStamina = nano.iStamina + difference;
|
||||
response.iBatteryN = player->batteryN - difference;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_CHARGE_NANO_STAMINA, sizeof(sP_FE2CL_REP_CHARGE_NANO_STAMINA));
|
||||
// now update serverside
|
||||
player->batteryN -= difference;
|
||||
player->Nanos[nano.iID].iStamina += difference;
|
||||
|
||||
}
|
||||
|
||||
#pragma region Helper methods
|
||||
void NanoManager::addNano(CNSocket* sock, int16_t nanoId, int16_t slot) {
|
||||
void NanoManager::addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm) {
|
||||
if (nanoId > 36)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int level = nanoId < plr->level ? plr->level : nanoId;
|
||||
|
||||
/*
|
||||
* Spend the necessary Fusion Matter.
|
||||
* Note the use of the not-yet-incremented plr->level as opposed to level.
|
||||
* Doing it the other way always leaves the FM at 0. Jade totally called it.
|
||||
*/
|
||||
plr->level = level;
|
||||
|
||||
if (spendfm)
|
||||
MissionManager::updateFusionMatter(sock, -(int)MissionManager::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
|
||||
// Send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NANO_CREATE_SUCC, resp);
|
||||
resp.Nano.iID = nanoId;
|
||||
resp.Nano.iStamina = 150;
|
||||
resp.iQuestItemSlotNum = slot;
|
||||
resp.iPC_Level = level;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC, sizeof(sP_FE2CL_REP_PC_NANO_CREATE_SUCC));
|
||||
if (plr->activeNano > 0 && plr->activeNano == nanoId)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
// Update player
|
||||
plr->Nanos[nanoId] = resp.Nano;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC, sizeof(sP_FE2CL_REP_PC_NANO_CREATE_SUCC));
|
||||
|
||||
/*
|
||||
* iPC_Level in NANO_CREATE_SUCC sets the player's level.
|
||||
* Other players must be notified of the change as well. Both P_FE2CL_REP_PC_NANO_CREATE and
|
||||
* P_FE2CL_REP_PC_CHANGE_LEVEL appear to play the same animation, but only the latter affects
|
||||
* the other player's displayed level.
|
||||
*/
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp2);
|
||||
|
||||
resp2.iPC_ID = plr->iID;
|
||||
resp2.iPC_Level = level;
|
||||
|
||||
// Update other players' perception of the player's level
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
|
||||
}
|
||||
|
||||
void NanoManager::summonNano(CNSocket *sock, int slot) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||
resp.iActiveNanoSlotNum = slot;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_ACTIVE_SUCC, sizeof(sP_FE2CL_REP_NANO_ACTIVE_SUCC));
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::cout << "summon nano\n";
|
||||
|
||||
if (slot > 2 || slot < 0)
|
||||
return; //sanity check
|
||||
|
||||
int nanoId = plr->equippedNanos[slot];
|
||||
|
||||
if (nanoId > 36 || nanoId < 0)
|
||||
if (plr == nullptr || slot > 2 || slot < -1)
|
||||
return; // sanity check
|
||||
|
||||
int16_t nanoId = slot == -1 ? -1 : plr->equippedNanos[slot];
|
||||
|
||||
if (nanoId > 36 || nanoId < -1)
|
||||
return; // sanity check
|
||||
|
||||
int16_t skillId = 0;
|
||||
|
||||
if (plr->activeNano > 0)
|
||||
for (auto& pwr : PassivePowers)
|
||||
if (pwr.powers.count(plr->Nanos[plr->activeNano].iSkillID)) { // std::set's contains method is C++20 only...
|
||||
nanoUnbuff(sock, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
|
||||
plr->passiveNanoOut = false;
|
||||
}
|
||||
|
||||
sNano nano = plr->Nanos[nanoId];
|
||||
skillId = nano.iSkillID;
|
||||
|
||||
if (slot > -1) {
|
||||
plr->activeNano = nanoId;
|
||||
|
||||
for (auto& pwr : PassivePowers)
|
||||
if (pwr.powers.count(skillId)) { // std::set's contains method is C++20 only...
|
||||
resp.eCSTB___Add = 1;
|
||||
nanoBuff(sock, nanoId, skillId, pwr.eSkillType, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
|
||||
plr->passiveNanoOut = true;
|
||||
}
|
||||
} else
|
||||
plr->activeNano = 0;
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_ACTIVE_SUCC, sizeof(sP_FE2CL_REP_NANO_ACTIVE_SUCC));
|
||||
|
||||
// Send to other players
|
||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||
|
||||
pkt1.iPC_ID = plr->iID;
|
||||
pkt1.Nano = nano;
|
||||
|
||||
for (CNSocket* s : PlayerManager::players[sock].viewable)
|
||||
s->sendPacket((void*)&pkt1, P_FE2CL_NANO_ACTIVE, sizeof(sP_FE2CL_NANO_ACTIVE));
|
||||
if (nanoId == -1)
|
||||
memset(&pkt1.Nano, 0, sizeof(pkt1.Nano));
|
||||
else
|
||||
pkt1.Nano = plr->Nanos[nanoId];
|
||||
|
||||
PlayerManager::sendToViewable(sock, (void*)&pkt1, P_FE2CL_NANO_ACTIVE, sizeof(sP_FE2CL_NANO_ACTIVE));
|
||||
|
||||
// update player
|
||||
plr->activeNano = nanoId;
|
||||
@@ -179,6 +352,13 @@ void NanoManager::setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId)
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
if (plr->activeNano > 0 && plr->activeNano == nanoId)
|
||||
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
|
||||
|
||||
sNano nano = plr->Nanos[nanoId];
|
||||
|
||||
nano.iSkillID = skillId;
|
||||
@@ -188,6 +368,8 @@ void NanoManager::setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId)
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_TUNE_SUCC, resp);
|
||||
resp.iNanoID = nanoId;
|
||||
resp.iSkillID = skillId;
|
||||
resp.iPC_FusionMatter = plr->fusionmatter;
|
||||
resp.aItem[9] = plr->Inven[0]; // temp fix for a bug TODO: Use this for nano power changing later
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_TUNE_SUCC, sizeof(sP_FE2CL_REP_NANO_TUNE_SUCC));
|
||||
|
||||
@@ -201,6 +383,10 @@ void NanoManager::resetNanoSkill(CNSocket* sock, int16_t nanoId) {
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sNano nano = plr->Nanos[nanoId];
|
||||
|
||||
// 0 is reset
|
||||
@@ -208,3 +394,534 @@ void NanoManager::resetNanoSkill(CNSocket* sock, int16_t nanoId) {
|
||||
plr->Nanos[nanoId] = nano;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Active Powers
|
||||
namespace NanoManager {
|
||||
|
||||
bool doDebuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, amount);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = damage;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doBuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Buff *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoBuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second.plr->iID == pktdata[i]) {
|
||||
plr = pair.second.plr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
plr->HP += healedAmount;
|
||||
|
||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iHealHP = healedAmount;
|
||||
|
||||
std::cout << (int)plr->iID << " was healed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doGroupHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second.plr->iID == pktdata[0]) {
|
||||
plr = pair.second.plr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
Player *leader = PlayerManager::getPlayer(sock);
|
||||
|
||||
// player not found
|
||||
if (leader == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
leader->HP += healedAmount;
|
||||
|
||||
if (leader->HP > PC_MAXHEALTH(leader->level))
|
||||
leader->HP = PC_MAXHEALTH(leader->level);
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iHealHP = healedAmount;
|
||||
|
||||
std::cout << (int)plr->iID << " was healed" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doDamage(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage *respdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = damage;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
respdata[i].iHP = mob->appearanceData.iHP;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was damaged" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Leech is specially encoded.
|
||||
*
|
||||
* It manages to fit inside the activePower<>() mold with only a slight hack,
|
||||
* but it really is it's own thing. There is a hard assumption that players
|
||||
* will only every leech a single mob, and the sanity check that enforces that
|
||||
* assumption is critical.
|
||||
*/
|
||||
bool doLeech(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *healdata, int i, int32_t iCBFlag, int32_t amount) {
|
||||
// this sanity check is VERY important
|
||||
if (i != 0) {
|
||||
std::cout << "[WARN] Player attempted to leech more than one mob!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP));
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return false;
|
||||
|
||||
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
|
||||
|
||||
plr->HP += healedAmount;
|
||||
|
||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
healdata->eCT = 1;
|
||||
healdata->iID = plr->iID;
|
||||
healdata->iHP = plr->HP;
|
||||
healdata->iHealHP = healedAmount;
|
||||
|
||||
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] doLeech: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[pktdata[i]];
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
|
||||
|
||||
damagedata->eCT = 4;
|
||||
damagedata->iDamage = damage;
|
||||
damagedata->iID = mob->appearanceData.iNPC_ID;
|
||||
damagedata->iHP = mob->appearanceData.iHP;
|
||||
|
||||
std::cout << (int)mob->appearanceData.iNPC_ID << " was leeched" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// XXX: Special flags. This is still pretty dirty.
|
||||
enum {
|
||||
NONE,
|
||||
LEECH,
|
||||
GHEAL
|
||||
};
|
||||
|
||||
template<class sPAYLOAD,
|
||||
bool (*work)(CNSocket*,int32_t*,sPAYLOAD*,int,int32_t,int32_t),
|
||||
int specialCase=NONE>
|
||||
void activePower(CNSocket *sock, CNPacketData *data,
|
||||
int16_t nanoId, int16_t skillId, int16_t eSkillType,
|
||||
int32_t iCBFlag, int32_t amount) {
|
||||
|
||||
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||
|
||||
// validate request check
|
||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
||||
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE));
|
||||
|
||||
size_t resplen;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
Player *otherPlr = plr;
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
// special case since leech is atypically encoded
|
||||
if constexpr (specialCase == LEECH)
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage);
|
||||
else if constexpr (specialCase == GHEAL) {
|
||||
otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
|
||||
pkt->iTargetCnt = otherPlr->groupCnt;
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
|
||||
} else
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), pkt->iTargetCnt, sizeof(sPAYLOAD))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||
sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
||||
|
||||
plr->Nanos[plr->activeNano].iStamina -= 40;
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
|
||||
resp->iPC_ID = plr->iID;
|
||||
resp->iSkillID = skillId;
|
||||
resp->iNanoID = nanoId;
|
||||
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
resp->eST = eSkillType;
|
||||
resp->iTargetCnt = pkt->iTargetCnt;
|
||||
|
||||
CNSocket *workSock = sock;
|
||||
|
||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
||||
if constexpr (specialCase == GHEAL)
|
||||
workSock = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
||||
|
||||
if (!work(workSock, pktdata, respdata, i, iCBFlag, amount))
|
||||
return;
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
|
||||
// active nano power dispatch table
|
||||
std::vector<ActivePower> ActivePowers = {
|
||||
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 0),
|
||||
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 25),
|
||||
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 25),
|
||||
// TODO: Recall
|
||||
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 0),
|
||||
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 0),
|
||||
ActivePower(DamagePowers, activePower<sSkillResult_Damage, doDamage>, EST_DAMAGE, CSB_BIT_NONE, 12),
|
||||
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, LEECH>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
|
||||
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 0),
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Passive Powers
|
||||
void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int pktCnt = 1;
|
||||
Player *leader = plr;
|
||||
plr->iConditionBitFlag |= iCBFlag;
|
||||
|
||||
if (groupPower) {
|
||||
plr->iGroupConditionBitFlag |= iCBFlag;
|
||||
|
||||
if (plr->iID != plr->iIDGroup)
|
||||
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
pktCnt = leader->groupCnt;
|
||||
}
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE), pktCnt, sizeof(sSkillResult_Buff))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE) + pktCnt * sizeof(sSkillResult_Buff);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE *resp = (sP_FE2CL_NANO_SKILL_USE*)respbuf;
|
||||
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE));
|
||||
|
||||
resp->iPC_ID = plr->iID;
|
||||
resp->iSkillID = skillId;
|
||||
resp->iNanoID = nanoId;
|
||||
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
resp->eST = eSkillType;
|
||||
resp->iTargetCnt = pktCnt;
|
||||
|
||||
int bitFlag = GroupManager::getGroupFlags(leader);
|
||||
|
||||
for (int i = 0; i < pktCnt; i++) {
|
||||
Player* varPlr;
|
||||
CNSocket* sockTo;
|
||||
|
||||
if (plr->iID == leader->groupIDs[i]) {
|
||||
varPlr = plr;
|
||||
sockTo = sock;
|
||||
} else {
|
||||
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
}
|
||||
|
||||
if (varPlr == nullptr || sockTo == nullptr)
|
||||
continue;
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = varPlr->iID;
|
||||
respdata[i].iConditionBitFlag = iCBFlag;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
||||
pkt1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
|
||||
pkt1.eTBU = 1; // eTimeBuffUpdate
|
||||
pkt1.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
pkt1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
|
||||
|
||||
if (iValue > 0)
|
||||
pkt1.TimeBuff.iValue = iValue;
|
||||
|
||||
sockTo->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
|
||||
void NanoManager::nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
int pktCnt = 1;
|
||||
Player *leader = plr;
|
||||
plr->iConditionBitFlag &= ~iCBFlag;
|
||||
|
||||
if (groupPower) {
|
||||
plr->iGroupConditionBitFlag &= ~iCBFlag;
|
||||
|
||||
if (plr->iID != plr->iIDGroup)
|
||||
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
if (leader == nullptr)
|
||||
return;
|
||||
|
||||
pktCnt = leader->groupCnt;
|
||||
}
|
||||
|
||||
int bitFlag = GroupManager::getGroupFlags(leader);
|
||||
|
||||
for (int i = 0; i < pktCnt; i++) {
|
||||
Player* varPlr;
|
||||
CNSocket* sockTo;
|
||||
|
||||
if (plr->iID == leader->groupIDs[i]) {
|
||||
varPlr = plr;
|
||||
sockTo = sock;
|
||||
} else {
|
||||
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
|
||||
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp1);
|
||||
resp1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
|
||||
resp1.eTBU = 2; // eTimeBuffUpdate
|
||||
resp1.eTBT = 1; // eTimeBuffType 1 means nano
|
||||
resp1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
|
||||
|
||||
if (iValue > 0)
|
||||
resp1.TimeBuff.iValue = iValue;
|
||||
|
||||
sockTo->sendPacket((void*)&resp1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
}
|
||||
|
||||
// 0=A 1=B 2=C -1=Not found
|
||||
int NanoManager::nanoStyle(int nanoId) {
|
||||
if (nanoId < 1 || nanoId >= (int)NanoTable.size())
|
||||
return -1;
|
||||
return NanoTable[nanoId].style;
|
||||
}
|
||||
|
||||
namespace NanoManager {
|
||||
|
||||
std::vector<PassivePower> PassivePowers = {
|
||||
PassivePower(ScavangePowers, EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, 0, false),
|
||||
PassivePower(RunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, false),
|
||||
PassivePower(GroupRunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, true),
|
||||
PassivePower(BonusPowers, EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, 0, false),
|
||||
PassivePower(GuardPowers, EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, 0, false),
|
||||
PassivePower(RadarPowers, EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, 0, false),
|
||||
PassivePower(AntidotePowers, EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, 0, false),
|
||||
PassivePower(FreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, false),
|
||||
PassivePower(GroupFreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, true),
|
||||
PassivePower(JumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, false),
|
||||
PassivePower(GroupJumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, true),
|
||||
PassivePower(SelfRevivePowers, EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false),
|
||||
PassivePower(SneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, false),
|
||||
PassivePower(GroupSneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, true),
|
||||
PassivePower(TreasureFinderPowers, EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, 0, false),
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
|
||||
void NanoManager::revivePlayer(Player* plr) {
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
|
||||
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
|
||||
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
// Nanos
|
||||
int activeSlot = -1;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int nanoID = plr->equippedNanos[i];
|
||||
if (plr->activeNano == nanoID) {
|
||||
activeSlot = i;
|
||||
}
|
||||
response.PCRegenData.Nanos[i] = plr->Nanos[nanoID];
|
||||
}
|
||||
|
||||
// Response parameters
|
||||
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
|
||||
response.PCRegenData.iX = plr->x;
|
||||
response.PCRegenData.iY = plr->y;
|
||||
response.PCRegenData.iZ = plr->z;
|
||||
response.PCRegenData.iHP = plr->HP;
|
||||
response.iFusionMatter = plr->fusionmatter;
|
||||
response.bMoveLocation = 0;
|
||||
response.PCRegenData.iMapNum = 0;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
|
||||
|
||||
// Update other players
|
||||
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
|
||||
resp2.PCRegenDataForOtherPC.iX = plr->x;
|
||||
resp2.PCRegenDataForOtherPC.iY = plr->y;
|
||||
resp2.PCRegenDataForOtherPC.iZ = plr->z;
|
||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||
|
||||
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
|
||||
}
|
||||
|
||||
void NanoManager::nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo) {
|
||||
bool sentPacket = false;
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
||||
resp.eTBU = 3;
|
||||
resp.eTBT = 1;
|
||||
resp.iConditionBitFlag = cbTo;
|
||||
|
||||
if (!(cbFrom & CSB_BIT_UP_MOVE_SPEED) && (cbTo & CSB_BIT_UP_MOVE_SPEED)) {
|
||||
resp.eCSTB = ECSB_UP_MOVE_SPEED;
|
||||
resp.eTBU = 1;
|
||||
resp.TimeBuff.iValue = 200;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
} else if ((cbFrom & CSB_BIT_UP_MOVE_SPEED) && !(cbTo & CSB_BIT_UP_MOVE_SPEED)) {
|
||||
resp.eCSTB = ECSB_UP_MOVE_SPEED;
|
||||
resp.eTBU = 2;
|
||||
resp.TimeBuff.iValue = 200;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
}
|
||||
|
||||
if (!(cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && (cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
|
||||
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
|
||||
resp.eTBU = 1;
|
||||
resp.TimeBuff.iValue = 400;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
} else if ((cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && !(cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
|
||||
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
|
||||
resp.eTBU = 2;
|
||||
resp.TimeBuff.iValue = 400;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
sentPacket = true;
|
||||
}
|
||||
|
||||
if (!sentPacket)
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||
}
|
||||
#pragma endregion
|
||||
|
@@ -1,19 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
|
||||
typedef void (*ActivePowerHandler)(CNSocket*, CNPacketData*, int16_t, int16_t, int16_t, int32_t, int32_t);
|
||||
|
||||
struct ActivePower {
|
||||
std::set<int> powers;
|
||||
ActivePowerHandler handler;
|
||||
int16_t eSkillType;
|
||||
int32_t flag;
|
||||
int32_t amount;
|
||||
|
||||
ActivePower(std::set<int> p, ActivePowerHandler h, int16_t t, int32_t f, int32_t a) : powers(p), handler(h), eSkillType(t), flag(f), amount(a) {}
|
||||
|
||||
void handle(CNSocket *sock, CNPacketData *data, int16_t nanoId, int16_t skillId) {
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
handler(sock, data, nanoId, skillId, eSkillType, flag, amount);
|
||||
}
|
||||
};
|
||||
|
||||
struct PassivePower {
|
||||
std::set<int> powers;
|
||||
int16_t eSkillType;
|
||||
int32_t iCBFlag;
|
||||
int16_t eCharStatusTimeBuffID;
|
||||
int16_t iValue;
|
||||
bool groupPower;
|
||||
|
||||
PassivePower(std::set<int> p, int16_t t, int32_t f, int16_t b, int16_t a, bool g) : powers(p), eSkillType(t), iCBFlag(f), eCharStatusTimeBuffID(b), iValue(a), groupPower(g) {}
|
||||
};
|
||||
|
||||
struct NanoData {
|
||||
int style;
|
||||
};
|
||||
|
||||
namespace NanoManager {
|
||||
extern std::vector<ActivePower> ActivePowers;
|
||||
extern std::vector<PassivePower> PassivePowers;
|
||||
extern std::map<int32_t, NanoData> NanoTable;
|
||||
void init();
|
||||
|
||||
void nanoSummonHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoEquipHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillSetHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoRecallHandler(CNSocket* sock, CNPacketData* data);
|
||||
void nanoPotionHandler(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
// Helper methods
|
||||
void addNano(CNSocket* sock, int16_t nanoId, int16_t slot);
|
||||
void addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm=false);
|
||||
void summonNano(CNSocket* sock, int slot);
|
||||
void setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId);
|
||||
void resetNanoSkill(CNSocket* sock, int16_t nanoId);
|
||||
|
||||
void nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
|
||||
void nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
|
||||
|
||||
int nanoStyle(int nanoId);
|
||||
void revivePlayer(Player* plr);
|
||||
void nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo);
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
#include "Player.hpp"
|
@@ -6,30 +6,65 @@
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
|
||||
#define ACTIVE_MISSION_COUNT 6
|
||||
|
||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||
|
||||
struct Player {
|
||||
int accountId;
|
||||
int accountLevel; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
int64_t SerialKey;
|
||||
int32_t iID;
|
||||
uint64_t FEKey;
|
||||
time_t creationTime;
|
||||
|
||||
int level;
|
||||
int HP;
|
||||
int slot; // player slot, not nano slot
|
||||
int16_t mentor;
|
||||
int32_t money;
|
||||
int32_t fusionmatter;
|
||||
int32_t batteryW;
|
||||
int32_t batteryN;
|
||||
sPCStyle PCStyle;
|
||||
sPCStyle2 PCStyle2;
|
||||
sNano Nanos[37]; // acquired nanos
|
||||
int equippedNanos[3];
|
||||
int activeNano; // active nano (index into Nanos)
|
||||
int8_t iPCState;
|
||||
int32_t iWarpLocationFlag;
|
||||
int64_t aSkywayLocationFlag[2];
|
||||
int32_t iConditionBitFlag;
|
||||
int8_t iSpecialState;
|
||||
|
||||
int x, y, z, angle;
|
||||
int lastX, lastY, lastZ, lastAngle;
|
||||
uint64_t instanceID;
|
||||
sItemBase Equip[AEQUIP_COUNT];
|
||||
sItemBase Inven[AINVEN_COUNT];
|
||||
sItemBase Bank[ABANK_COUNT];
|
||||
sItemTrade Trade[12];
|
||||
int32_t moneyInTrade;
|
||||
bool isTrading;
|
||||
bool isTradeConfirm;
|
||||
bool IsGM;
|
||||
|
||||
bool inCombat;
|
||||
bool passiveNanoOut;
|
||||
|
||||
int pointDamage;
|
||||
int groupDamage;
|
||||
int defense;
|
||||
|
||||
int64_t aQuestFlag[16];
|
||||
int tasks[ACTIVE_MISSION_COUNT];
|
||||
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];
|
||||
sItemBase QInven[AQINVEN_COUNT];
|
||||
int32_t CurrentMissionID;
|
||||
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle;
|
||||
|
||||
int32_t iIDGroup;
|
||||
int groupCnt;
|
||||
int32_t groupIDs[4];
|
||||
int32_t iGroupConditionBitFlag;
|
||||
};
|
||||
|
@@ -3,9 +3,17 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "Database.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
@@ -23,6 +31,7 @@ void PlayerManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LAUNCHER, PlayerManager::launchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ZIPLINE, PlayerManager::ziplinePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVEPLATFORM, PlayerManager::movePlatformPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVETRANSPORTATION, PlayerManager::moveSliderPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SLOPE, PlayerManager::moveSlopePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, PlayerManager::gotoPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, PlayerManager::setSpecialPlayer);
|
||||
@@ -30,6 +39,7 @@ void PlayerManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_REGEN, PlayerManager::revivePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EXIT, PlayerManager::exitGame);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setSpecialSwitchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setGMSpecialSwitchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_ON, PlayerManager::enterPlayerVehicle);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, PlayerManager::exitPlayerVehicle);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, PlayerManager::changePlayerGuide);
|
||||
@@ -41,85 +51,105 @@ void PlayerManager::addPlayer(CNSocket* key, Player plr) {
|
||||
memcpy(p, &plr, sizeof(Player));
|
||||
|
||||
players[key] = PlayerView();
|
||||
players[key].viewable = std::list<CNSocket*>();
|
||||
players[key].chunkPos = std::make_tuple(0, 0, 0);
|
||||
players[key].currentChunks = std::vector<Chunk*>();
|
||||
players[key].plr = p;
|
||||
players[key].lastHeartbeat = 0;
|
||||
|
||||
key->plr = p;
|
||||
|
||||
std::cout << U16toU8(plr.PCStyle.szFirstName) << " " << U16toU8(plr.PCStyle.szLastName) << " has joined!" << std::endl;
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
}
|
||||
|
||||
void PlayerManager::removePlayer(CNSocket* key) {
|
||||
PlayerView cachedView = players[key];
|
||||
PlayerView& view = players[key];
|
||||
uint64_t fromInstance = view.plr->instanceID;
|
||||
|
||||
// if players have them in their viewable lists, remove it
|
||||
for (CNSocket* otherSock : players[key].viewable) {
|
||||
players[otherSock].viewable.remove(key); // gone
|
||||
GroupManager::groupKickPlayer(view.plr);
|
||||
|
||||
// now send PC_EXIT packet
|
||||
sP_FE2CL_PC_EXIT exitPacket;
|
||||
exitPacket.iID = players[key].plr->iID;
|
||||
// save player to DB
|
||||
Database::updatePlayer(view.plr);
|
||||
|
||||
otherSock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
|
||||
}
|
||||
// remove players from all chunks
|
||||
removePlayerFromChunks(view.currentChunks, key);
|
||||
|
||||
std::cout << U16toU8(cachedView.plr->PCStyle.szFirstName) << " " << U16toU8(cachedView.plr->PCStyle.szLastName) << " has left!" << std::endl;
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
// remove from chunk
|
||||
if (ChunkManager::chunks.find(view.chunkPos) != ChunkManager::chunks.end())
|
||||
ChunkManager::chunks[view.chunkPos]->players.erase(key);
|
||||
|
||||
delete cachedView.plr;
|
||||
std::cout << U16toU8(view.plr->PCStyle.szFirstName) << " " << U16toU8(view.plr->PCStyle.szLastName) << " (PlayerId = " << view.plr->iID << ") has left!" << std::endl;
|
||||
|
||||
key->plr = nullptr;
|
||||
delete view.plr;
|
||||
players.erase(key);
|
||||
|
||||
// if the player was in a lair, clean it up
|
||||
ChunkManager::destroyInstanceIfEmpty(fromInstance);
|
||||
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
}
|
||||
|
||||
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
|
||||
players[sock].plr->x = X;
|
||||
players[sock].plr->y = Y;
|
||||
players[sock].plr->z = Z;
|
||||
bool PlayerManager::removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket* sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_EXIT, exitPlayer);
|
||||
|
||||
std::vector<CNSocket*> noView;
|
||||
std::vector<CNSocket*> yesView;
|
||||
// for chunks that need the player to be removed from
|
||||
for (Chunk* chunk : chunks) {
|
||||
|
||||
// TODO: oh god this is sooooo perfomance heavy the more players you have
|
||||
for (auto pair : players) {
|
||||
if (pair.first == sock)
|
||||
continue; // ignore our own connection
|
||||
|
||||
int diffX = abs(pair.second.plr->x - X); // the map is like a grid, X and Y are your position on the map, Z is the height. very different from other games...
|
||||
int diffY = abs(pair.second.plr->y - Y);
|
||||
|
||||
if (diffX < settings::PLAYERDISTANCE && diffY < settings::PLAYERDISTANCE) {
|
||||
yesView.push_back(pair.first);
|
||||
// remove NPCs
|
||||
for (int32_t id : chunk->NPCs) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData);
|
||||
exitBusData.eTT = 3;
|
||||
exitBusData.iT_ID = id;
|
||||
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
|
||||
break;
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
|
||||
exitData.iNPC_ID = id;
|
||||
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
noView.push_back(pair.first);
|
||||
|
||||
// remove players from eachother
|
||||
for (CNSocket* otherSock : chunk->players) {
|
||||
exitPlayer.iID = players[sock].plr->iID;
|
||||
otherSock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
|
||||
exitPlayer.iID = players[otherSock].plr->iID;
|
||||
sock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_EXIT, exitPacket);
|
||||
std::list<CNSocket*>::iterator i = players[sock].viewable.begin();
|
||||
while (i != players[sock].viewable.end()) {
|
||||
CNSocket* otherSock = *i;
|
||||
if (std::find(noView.begin(), noView.end(), otherSock) != noView.end()) {
|
||||
|
||||
// sock shouldn't be visible, send PC_EXIT packet
|
||||
exitPacket.iID = players[sock].plr->iID;
|
||||
otherSock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
|
||||
exitPacket.iID = players[otherSock].plr->iID;
|
||||
sock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
|
||||
|
||||
// remove them from the viewable list
|
||||
players[sock].viewable.erase(i++);
|
||||
players[otherSock].viewable.remove(sock);
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
// remove us from that old stinky chunk
|
||||
return ChunkManager::removePlayer(players[sock].chunkPos, sock);
|
||||
}
|
||||
|
||||
void PlayerManager::addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_NEW, newPlayer);
|
||||
for (CNSocket* otherSock : yesView) {
|
||||
if (std::find(players[sock].viewable.begin(), players[sock].viewable.end(), otherSock) == players[sock].viewable.end()) {
|
||||
// this needs to be added to the viewable players, send PC_ENTER
|
||||
|
||||
for (Chunk* chunk : chunks) {
|
||||
// add npcs
|
||||
for (int32_t id : chunk->NPCs) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData);
|
||||
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
|
||||
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
|
||||
break;
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
|
||||
enterData.NPCAppearanceData = NPCManager::NPCs[id]->appearanceData;
|
||||
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add players
|
||||
for (CNSocket* otherSock : chunk->players) {
|
||||
Player *otherPlr = players[otherSock].plr;
|
||||
Player *plr = players[sock].plr;
|
||||
|
||||
@@ -133,6 +163,7 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
|
||||
newPlayer.PCAppearanceData.PCStyle = plr->PCStyle;
|
||||
newPlayer.PCAppearanceData.Nano = plr->Nanos[plr->activeNano];
|
||||
newPlayer.PCAppearanceData.iPCState = plr->iPCState;
|
||||
newPlayer.PCAppearanceData.iSpecialState = plr->iSpecialState;
|
||||
memcpy(newPlayer.PCAppearanceData.ItemEquip, plr->Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||
|
||||
otherSock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW));
|
||||
@@ -147,30 +178,98 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
|
||||
newPlayer.PCAppearanceData.PCStyle = otherPlr->PCStyle;
|
||||
newPlayer.PCAppearanceData.Nano = otherPlr->Nanos[otherPlr->activeNano];
|
||||
newPlayer.PCAppearanceData.iPCState = otherPlr->iPCState;
|
||||
newPlayer.PCAppearanceData.iSpecialState = otherPlr->iSpecialState;
|
||||
memcpy(newPlayer.PCAppearanceData.ItemEquip, otherPlr->Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||
|
||||
sock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW));
|
||||
|
||||
players[sock].viewable.push_back(otherSock);
|
||||
players[otherSock].viewable.push_back(sock);
|
||||
}
|
||||
}
|
||||
|
||||
NPCManager::updatePlayerNPCS(sock, players[sock]);
|
||||
}
|
||||
|
||||
std::list<CNSocket*> PlayerManager::getNearbyPlayers(int x, int y, int dist) {
|
||||
std::list<CNSocket*> plrs;
|
||||
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, int angle) {
|
||||
players[sock].plr->angle = angle;
|
||||
updatePlayerPosition(sock, X, Y, Z);
|
||||
}
|
||||
|
||||
for (auto pair : players) {
|
||||
int diffX = abs(pair.second.plr->x - x);
|
||||
int diffY = abs(pair.second.plr->x - x);
|
||||
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
|
||||
PlayerView& view = players[sock];
|
||||
view.plr->x = X;
|
||||
view.plr->y = Y;
|
||||
view.plr->z = Z;
|
||||
updatePlayerChunk(sock, X, Y, view.plr->instanceID);
|
||||
}
|
||||
|
||||
if (diffX < dist && diffY < dist)
|
||||
plrs.push_back(pair.first);
|
||||
void PlayerManager::updatePlayerChunk(CNSocket* sock, int X, int Y, uint64_t instanceID) {
|
||||
PlayerView& view = players[sock];
|
||||
std::tuple<int, int, uint64_t> newPos = ChunkManager::grabChunk(X, Y, view.plr->instanceID);
|
||||
|
||||
// nothing to be done
|
||||
if (newPos == view.chunkPos)
|
||||
return;
|
||||
|
||||
// add player to chunk
|
||||
std::vector<Chunk*> allChunks = ChunkManager::grabChunks(newPos);
|
||||
|
||||
Chunk *chunk = nullptr;
|
||||
if (ChunkManager::checkChunk(view.chunkPos))
|
||||
chunk = ChunkManager::chunks[view.chunkPos];
|
||||
|
||||
// first, remove all the old npcs & players from the old chunks
|
||||
if (removePlayerFromChunks(ChunkManager::getDeltaChunks(view.currentChunks, allChunks), sock)) {
|
||||
allChunks.erase(std::remove(allChunks.begin(), allChunks.end(), chunk), allChunks.end());
|
||||
}
|
||||
|
||||
return plrs;
|
||||
// now, add all the new npcs & players!
|
||||
addPlayerToChunks(ChunkManager::getDeltaChunks(allChunks, view.currentChunks), sock);
|
||||
|
||||
ChunkManager::addPlayer(X, Y, view.plr->instanceID, sock); // takes care of adding the player to the chunk if it exists or not
|
||||
view.chunkPos = newPos;
|
||||
view.currentChunks = allChunks;
|
||||
}
|
||||
|
||||
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
Player* plr = plrv.plr;
|
||||
|
||||
uint64_t fromInstance = plr->instanceID;
|
||||
|
||||
plr->instanceID = I;
|
||||
if (I != INSTANCE_OVERWORLD) {
|
||||
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
||||
pkt.iEP_ID = PLAYERID(I) == 0; // iEP_ID has to be positive for the map to be enabled
|
||||
pkt.iInstanceMapNum = (int32_t)MAPNUM(I); // lower 32 bits are mapnum
|
||||
sock->sendPacket((void*)&pkt, P_FE2CL_INSTANCE_MAP_INFO, sizeof(sP_FE2CL_INSTANCE_MAP_INFO));
|
||||
sendPlayerTo(sock, X, Y, Z);
|
||||
} else {
|
||||
// annoying but necessary to set the flag back
|
||||
MissionManager::failInstancedMissions(sock); // fail any instanced missions
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp);
|
||||
resp.iX = X;
|
||||
resp.iY = Y;
|
||||
resp.iZ = Z;
|
||||
resp.iCandy = plrv.plr->money;
|
||||
resp.eIL = 4; // do not take away any items
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
|
||||
}
|
||||
|
||||
ChunkManager::destroyInstanceIfEmpty(fromInstance);
|
||||
}
|
||||
|
||||
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) {
|
||||
PlayerManager::updatePlayerPosition(sock, X, Y, Z);
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt);
|
||||
pkt.iX = X;
|
||||
pkt.iY = Y;
|
||||
pkt.iZ = Z;
|
||||
|
||||
// force player & NPC reload
|
||||
PlayerView& plrv = players[sock];
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
|
||||
sock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
|
||||
}
|
||||
|
||||
void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -179,11 +278,13 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
sP_CL2FE_REQ_PC_ENTER* enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
|
||||
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
|
||||
|
||||
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
|
||||
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
|
||||
|
||||
plr.groupCnt = 1;
|
||||
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
|
||||
std::cout << "\tID: " << U16toU8(enter->szID) << std::endl;
|
||||
@@ -192,54 +293,86 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
|
||||
)
|
||||
|
||||
response.iID = rand();
|
||||
// check if account is already in use
|
||||
if (isAccountInUse(plr.accountId)) {
|
||||
// kick the other player
|
||||
exitDuplicate(plr.accountId);
|
||||
}
|
||||
|
||||
response.iID = plr.iID;
|
||||
response.uiSvrTime = getTime();
|
||||
response.PCLoadData2CL.iUserLevel = 1;
|
||||
response.PCLoadData2CL.iHP = 3625; //TODO: Check player levelupdata and get this right
|
||||
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
||||
response.PCLoadData2CL.iHP = plr.HP;
|
||||
response.PCLoadData2CL.iLevel = plr.level;
|
||||
response.PCLoadData2CL.iCandy = plr.money;
|
||||
response.PCLoadData2CL.iMentor = 1;
|
||||
response.PCLoadData2CL.iMentorCount = 4;
|
||||
response.PCLoadData2CL.iMapNum = 0;
|
||||
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 = 130;
|
||||
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.iActiveNanoSlotNum = -1;
|
||||
response.PCLoadData2CL.iFatigue = 50;
|
||||
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
||||
response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
||||
|
||||
// client doesnt read this, it gets it from charinfo
|
||||
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
||||
// inventory
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
||||
|
||||
for (int i = 0; i < AINVEN_COUNT; 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];
|
||||
// nanos
|
||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
||||
}
|
||||
for (int i = 0; i < 3; 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)
|
||||
break;
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
||||
TaskData &task = *MissionManager::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];
|
||||
/*
|
||||
* client doesn't care about NeededItem ID and Count,
|
||||
* it gets Count from Quest Inventory
|
||||
*
|
||||
* KillNPCCount sets RemainEnemyNum in the client
|
||||
* Yes, this is extraordinary stupid.
|
||||
*/
|
||||
}
|
||||
}
|
||||
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
||||
|
||||
// don't ask..
|
||||
for (int i = 1; i < 37; i++) {
|
||||
response.PCLoadData2CL.aNanoBank[i].iID = i;
|
||||
response.PCLoadData2CL.aNanoBank[i].iSkillID = 1;
|
||||
response.PCLoadData2CL.aNanoBank[i].iStamina = 150;
|
||||
// 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];
|
||||
}
|
||||
|
||||
// temporarily not add nanos for nano add test through commands
|
||||
//response.PCLoadData2CL.aNanoSlots[0] = 1;
|
||||
//response.PCLoadData2CL.aNanoSlots[1] = 2;
|
||||
//response.PCLoadData2CL.aNanoSlots[2] = 3;
|
||||
|
||||
response.PCLoadData2CL.aQuestFlag[0] = -1;
|
||||
|
||||
// shut computress up
|
||||
// shut Computress up
|
||||
response.PCLoadData2CL.iFirstUseFlag1 = UINT64_MAX;
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
||||
|
||||
plr.iID = response.iID;
|
||||
plr.SerialKey = enter->iEnterSerialKey;
|
||||
plr.HP = response.PCLoadData2CL.iHP;
|
||||
|
||||
motd.iType = 1;
|
||||
U8toU16(settings::MOTDSTRING, (char16_t*)motd.szSystemMsg);
|
||||
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(plr.FEKey);
|
||||
@@ -248,9 +381,27 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_ENTER_SUCC, sizeof(sP_FE2CL_REP_PC_ENTER_SUCC));
|
||||
|
||||
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
||||
sock->sendPacket((void*)&motd, P_FE2CL_PC_MOTD_LOGIN, sizeof(sP_FE2CL_PC_MOTD_LOGIN));
|
||||
ChatManager::sendServerMessage(sock, settings::MOTDSTRING);
|
||||
|
||||
addPlayer(sock, plr);
|
||||
// check if there is an expiring vehicle
|
||||
ItemManager::checkItemExpire(sock, getPlayer(sock));
|
||||
|
||||
// set player equip stats
|
||||
ItemManager::setItemStats(getPlayer(sock));
|
||||
|
||||
MissionManager::failInstancedMissions(sock);
|
||||
}
|
||||
|
||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
for (Chunk* chunk : players[sock].currentChunks) {
|
||||
for (CNSocket* otherSock : chunk->players) {
|
||||
if (otherSock == sock)
|
||||
continue;
|
||||
|
||||
otherSock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerManager::loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -269,7 +420,7 @@ void PlayerManager::loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
response.iPC_ID = complete->iPC_ID;
|
||||
|
||||
// reload players & NPCs
|
||||
updatePlayerPosition(sock, plr->x, plr->y, plr->z);
|
||||
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->angle);
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, sizeof(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC));
|
||||
}
|
||||
@@ -279,7 +430,7 @@ void PlayerManager::movePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_MOVE* moveData = (sP_CL2FE_REQ_PC_MOVE*)data->buf;
|
||||
updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ);
|
||||
updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ, moveData->iAngle);
|
||||
|
||||
players[sock].plr->angle = moveData->iAngle;
|
||||
uint64_t tm = getTime();
|
||||
@@ -301,9 +452,7 @@ void PlayerManager::movePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
moveResponse.iCliTime = moveData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
moveResponse.iSvrTime = tm;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&moveResponse, P_FE2CL_PC_MOVE, sizeof(sP_FE2CL_PC_MOVE));
|
||||
}
|
||||
sendToViewable(sock, (void*)&moveResponse, P_FE2CL_PC_MOVE, sizeof(sP_FE2CL_PC_MOVE));
|
||||
}
|
||||
|
||||
void PlayerManager::stopPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -326,9 +475,7 @@ void PlayerManager::stopPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
stopResponse.iCliTime = stopData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
stopResponse.iSvrTime = tm;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&stopResponse, P_FE2CL_PC_STOP, sizeof(sP_FE2CL_PC_STOP));
|
||||
}
|
||||
sendToViewable(sock, (void*)&stopResponse, P_FE2CL_PC_STOP, sizeof(sP_FE2CL_PC_STOP));
|
||||
}
|
||||
|
||||
void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -336,7 +483,7 @@ void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_JUMP* jumpData = (sP_CL2FE_REQ_PC_JUMP*)data->buf;
|
||||
updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ);
|
||||
updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ, jumpData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -357,9 +504,7 @@ void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
jumpResponse.iCliTime = jumpData->iCliTime; // maybe don't send this??? seems unneeded...
|
||||
jumpResponse.iSvrTime = tm;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&jumpResponse, P_FE2CL_PC_JUMP, sizeof(sP_FE2CL_PC_JUMP));
|
||||
}
|
||||
sendToViewable(sock, (void*)&jumpResponse, P_FE2CL_PC_JUMP, sizeof(sP_FE2CL_PC_JUMP));
|
||||
}
|
||||
|
||||
void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -367,7 +512,7 @@ void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_JUMPPAD* jumppadData = (sP_CL2FE_REQ_PC_JUMPPAD*)data->buf;
|
||||
updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ);
|
||||
updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ, jumppadData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -386,9 +531,7 @@ void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
jumppadResponse.iCliTime = jumppadData->iCliTime;
|
||||
jumppadResponse.iSvrTime = tm;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&jumppadResponse, P_FE2CL_PC_JUMPPAD, sizeof(sP_FE2CL_PC_JUMPPAD));
|
||||
}
|
||||
sendToViewable(sock, (void*)&jumppadResponse, P_FE2CL_PC_JUMPPAD, sizeof(sP_FE2CL_PC_JUMPPAD));
|
||||
}
|
||||
|
||||
void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -396,7 +539,7 @@ void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_LAUNCHER* launchData = (sP_CL2FE_REQ_PC_LAUNCHER*)data->buf;
|
||||
updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ);
|
||||
updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ, launchData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -416,9 +559,7 @@ void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
launchResponse.iCliTime = launchData->iCliTime;
|
||||
launchResponse.iSvrTime = tm;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&launchResponse, P_FE2CL_PC_LAUNCHER, sizeof(sP_FE2CL_PC_LAUNCHER));
|
||||
}
|
||||
sendToViewable(sock, (void*)&launchResponse, P_FE2CL_PC_LAUNCHER, sizeof(sP_FE2CL_PC_LAUNCHER));
|
||||
}
|
||||
|
||||
void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -426,7 +567,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_ZIPLINE* ziplineData = (sP_CL2FE_REQ_PC_ZIPLINE*)data->buf;
|
||||
updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ);
|
||||
updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ, ziplineData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -443,7 +584,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
ziplineResponse.fVZ = ziplineData->fVZ;
|
||||
ziplineResponse.fMovDistance = ziplineData->fMovDistance;
|
||||
ziplineResponse.fMaxDistance = ziplineData->fMaxDistance;
|
||||
ziplineResponse.fDummy = ziplineData->fDummy; //wtf is this for?
|
||||
ziplineResponse.fDummy = ziplineData->fDummy; // wtf is this for?
|
||||
ziplineResponse.iStX = ziplineData->iStX;
|
||||
ziplineResponse.iStY = ziplineData->iStY;
|
||||
ziplineResponse.iStZ = ziplineData->iStZ;
|
||||
@@ -453,9 +594,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
ziplineResponse.iRollMax = ziplineData->iRollMax;
|
||||
ziplineResponse.iRoll = ziplineData->iRoll;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&ziplineResponse, P_FE2CL_PC_ZIPLINE, sizeof(sP_FE2CL_PC_ZIPLINE));
|
||||
}
|
||||
sendToViewable(sock, (void*)&ziplineResponse, P_FE2CL_PC_ZIPLINE, sizeof(sP_FE2CL_PC_ZIPLINE));
|
||||
}
|
||||
|
||||
void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -463,7 +602,7 @@ void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_MOVEPLATFORM* platformData = (sP_CL2FE_REQ_PC_MOVEPLATFORM*)data->buf;
|
||||
updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ);
|
||||
updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ, platformData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -487,9 +626,38 @@ void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
platResponse.cKeyValue = platformData->cKeyValue;
|
||||
platResponse.iPlatformID = platformData->iPlatformID;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM));
|
||||
}
|
||||
sendToViewable(sock, (void*)&platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM));
|
||||
}
|
||||
|
||||
void PlayerManager::moveSliderPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_MOVETRANSPORTATION))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_MOVETRANSPORTATION* sliderData = (sP_CL2FE_REQ_PC_MOVETRANSPORTATION*)data->buf;
|
||||
updatePlayerPosition(sock, sliderData->iX, sliderData->iY, sliderData->iZ, sliderData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_MOVETRANSPORTATION, sliderResponse);
|
||||
|
||||
sliderResponse.iPC_ID = players[sock].plr->iID;
|
||||
sliderResponse.iCliTime = sliderData->iCliTime;
|
||||
sliderResponse.iSvrTime = tm;
|
||||
sliderResponse.iX = sliderData->iX;
|
||||
sliderResponse.iY = sliderData->iY;
|
||||
sliderResponse.iZ = sliderData->iZ;
|
||||
sliderResponse.iAngle = sliderData->iAngle;
|
||||
sliderResponse.fVX = sliderData->fVX;
|
||||
sliderResponse.fVY = sliderData->fVY;
|
||||
sliderResponse.fVZ = sliderData->fVZ;
|
||||
sliderResponse.iLcX = sliderData->iLcX;
|
||||
sliderResponse.iLcY = sliderData->iLcY;
|
||||
sliderResponse.iLcZ = sliderData->iLcZ;
|
||||
sliderResponse.iSpeed = sliderData->iSpeed;
|
||||
sliderResponse.cKeyValue = sliderData->cKeyValue;
|
||||
sliderResponse.iT_ID = sliderData->iT_ID;
|
||||
|
||||
sendToViewable(sock, (void*)&sliderResponse, P_FE2CL_PC_MOVETRANSPORTATION, sizeof(sP_FE2CL_PC_MOVETRANSPORTATION));
|
||||
}
|
||||
|
||||
void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -497,7 +665,7 @@ void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_SLOPE* slopeData = (sP_CL2FE_REQ_PC_SLOPE*)data->buf;
|
||||
updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ);
|
||||
updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ,slopeData->iAngle);
|
||||
|
||||
uint64_t tm = getTime();
|
||||
|
||||
@@ -517,9 +685,7 @@ void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
slopeResponse.cKeyValue = slopeData->cKeyValue;
|
||||
slopeResponse.iSlopeID = slopeData->iSlopeID;
|
||||
|
||||
for (CNSocket* otherSock : players[sock].viewable) {
|
||||
otherSock->sendPacket((void*)&slopeResponse, P_FE2CL_PC_SLOPE, sizeof(sP_FE2CL_PC_SLOPE));
|
||||
}
|
||||
sendToViewable(sock, (void*)&slopeResponse, P_FE2CL_PC_SLOPE, sizeof(sP_FE2CL_PC_SLOPE));
|
||||
}
|
||||
|
||||
void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -534,13 +700,10 @@ void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "\tX: " << gotoData->iToX << std::endl;
|
||||
std::cout << "\tY: " << gotoData->iToY << std::endl;
|
||||
std::cout << "\tZ: " << gotoData->iToZ << std::endl;
|
||||
)
|
||||
)
|
||||
|
||||
response.iX = gotoData->iToX;
|
||||
response.iY = gotoData->iToY;
|
||||
response.iZ = gotoData->iToZ;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
|
||||
MissionManager::failInstancedMissions(sock); // this ensures warping by command still fails instanced missions
|
||||
sendPlayerTo(sock, gotoData->iToX, gotoData->iToY, gotoData->iToZ, 0);
|
||||
}
|
||||
|
||||
void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -548,6 +711,11 @@ void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2FE_GM_REQ_PC_SET_VALUE* setData = (sP_CL2FE_GM_REQ_PC_SET_VALUE*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_SET_VALUE, response);
|
||||
|
||||
DEBUGLOG(
|
||||
@@ -557,6 +725,27 @@ void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "\tSetValue: " << setData->iSetValue << std::endl;
|
||||
)
|
||||
|
||||
// Handle serverside value-changes
|
||||
switch (setData->iSetValueType) {
|
||||
case 1:
|
||||
plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case 2:
|
||||
plr->batteryW = setData->iSetValue;
|
||||
break;
|
||||
case 3:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
break;
|
||||
case 4:
|
||||
plr->fusionmatter = setData->iSetValue;
|
||||
break;
|
||||
case 5:
|
||||
plr->money = setData->iSetValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iSetValue = setData->iSetValue;
|
||||
response.iSetValueType = setData->iSetValueType;
|
||||
@@ -586,70 +775,127 @@ void PlayerManager::revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
WarpLocation target = PlayerManager::getRespawnPoint(plr);
|
||||
|
||||
// players respawn at same spot they died at for now...
|
||||
sP_CL2FE_REQ_PC_REGEN* reviveData = (sP_CL2FE_REQ_PC_REGEN*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
|
||||
response.bMoveLocation = reviveData->eIL;
|
||||
response.PCRegenData.iMapNum = reviveData->iIndex;
|
||||
response.PCRegenData.iHP = 1000 * plr->level;
|
||||
response.PCRegenData.iX = target.x;
|
||||
response.PCRegenData.iY = target.y;
|
||||
response.PCRegenData.iZ = target.z;
|
||||
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
|
||||
|
||||
int activeSlot = -1;
|
||||
|
||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
||||
// nano revive
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
NanoManager::nanoUnbuff(sock, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false);
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
} else {
|
||||
plr->x = target.x;
|
||||
plr->y = target.y;
|
||||
plr->z = target.z;
|
||||
|
||||
if (reviveData->iRegenType != 5)
|
||||
plr->HP = PC_MAXHEALTH(plr->level);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int nanoID = plr->equippedNanos[i];
|
||||
|
||||
// halve nano health if respawning
|
||||
if (reviveData->iRegenType != 5)
|
||||
plr->Nanos[nanoID].iStamina = 75; // max is 150, so 75 is half
|
||||
response.PCRegenData.Nanos[i] = plr->Nanos[nanoID];
|
||||
if (plr->activeNano == nanoID)
|
||||
activeSlot = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Response parameters
|
||||
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
|
||||
response.PCRegenData.iX = plr->x;
|
||||
response.PCRegenData.iY = plr->y;
|
||||
response.PCRegenData.iZ = plr->z;
|
||||
response.PCRegenData.iHP = plr->HP;
|
||||
response.iFusionMatter = plr->fusionmatter;
|
||||
response.bMoveLocation = 0;
|
||||
response.PCRegenData.iMapNum = 0;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
|
||||
|
||||
// Update other players
|
||||
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
|
||||
resp2.PCRegenDataForOtherPC.iX = plr->x;
|
||||
resp2.PCRegenDataForOtherPC.iY = plr->y;
|
||||
resp2.PCRegenDataForOtherPC.iZ = plr->z;
|
||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag;
|
||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||
|
||||
sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
|
||||
}
|
||||
|
||||
void PlayerManager::enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
PlayerView& plr = PlayerManager::players[sock];
|
||||
|
||||
if (plr.plr->Equip[8].iID > 0) {
|
||||
|
||||
if (plr.plr->Equip[8].iID > 0 && plr.plr->Equip[8].iTimeLimit>getTimestamp()) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
|
||||
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_ON_SUCC));
|
||||
|
||||
//send to other players
|
||||
|
||||
// send to other players
|
||||
plr.plr->iPCState = 8;
|
||||
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
||||
response2.iPC_ID = plr.plr->iID;
|
||||
response2.iState = 8;
|
||||
|
||||
for (CNSocket* otherSock : plr.viewable) {
|
||||
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
||||
|
||||
for (Chunk* chunk : players[sock].currentChunks) {
|
||||
for (CNSocket* otherSock : chunk->players) {
|
||||
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
|
||||
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_FAIL, sizeof(sP_FE2CL_PC_VEHICLE_ON_FAIL));
|
||||
|
||||
// check if vehicle didn't expire
|
||||
if (plr.plr->Equip[8].iTimeLimit < getTimestamp()) {
|
||||
plr.plr->toRemoveVehicle.eIL = 0;
|
||||
plr.plr->toRemoveVehicle.iSlotNum = 8;
|
||||
ItemManager::checkItemExpire(sock, plr.plr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerManager::exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
||||
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_OFF_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_OFF_SUCC));
|
||||
|
||||
|
||||
PlayerView plr = PlayerManager::players[sock];
|
||||
|
||||
//send to other players
|
||||
// send to other players
|
||||
plr.plr->iPCState = 0;
|
||||
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
||||
response2.iPC_ID = plr.plr->iID;
|
||||
response2.iState = 0;
|
||||
|
||||
for (CNSocket* otherSock : plr.viewable) {
|
||||
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
||||
}
|
||||
|
||||
sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
||||
}
|
||||
|
||||
void PlayerManager::setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH* specialData = (sP_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, response);
|
||||
setSpecialState(sock, data);
|
||||
}
|
||||
|
||||
response.iPC_ID = specialData->iPC_ID;
|
||||
response.iReqSpecialStateFlag = specialData->iSpecialStateFlag;
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
|
||||
void PlayerManager::setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
if (getPlayer(sock)->accountLevel > 30)
|
||||
return;
|
||||
|
||||
setSpecialState(sock, data);
|
||||
}
|
||||
|
||||
void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
||||
@@ -665,11 +911,29 @@ void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
||||
resp.iFusionMatter = plr->fusionmatter; // no cost
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC));
|
||||
// if it's changed from computress
|
||||
if (plr->mentor == 5) {
|
||||
// we're warping to the past
|
||||
plr->PCStyle2.iPayzoneFlag = 1;
|
||||
// remove all active missions
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] != 0)
|
||||
MissionManager::quitTask(sock, plr->tasks[i], true);
|
||||
}
|
||||
|
||||
// start Blossom nano mission if applicable
|
||||
MissionManager::updateFusionMatter(sock, 0);
|
||||
}
|
||||
// save it on player
|
||||
plr->mentor = pkt->iMentor;
|
||||
}
|
||||
|
||||
#pragma region Helper methods
|
||||
Player *PlayerManager::getPlayer(CNSocket* key) {
|
||||
return players[key].plr;
|
||||
if (players.find(key) != players.end())
|
||||
return players[key].plr;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
|
||||
@@ -678,7 +942,7 @@ WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
|
||||
|
||||
for (auto targ : NPCManager::RespawnPoints) {
|
||||
curDist = sqrt(pow(plr->x - targ.x, 2) + pow(plr->y - targ.y, 2));
|
||||
if (curDist < bestDist) {
|
||||
if (curDist < bestDist && targ.instanceID == MAPNUM(plr->instanceID)) { // only mapNum needs to match
|
||||
best = targ;
|
||||
bestDist = curDist;
|
||||
}
|
||||
@@ -686,10 +950,10 @@ WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
bool PlayerManager::isAccountInUse(int accountId) {
|
||||
std::map<CNSocket*, PlayerView>::iterator it;
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++)
|
||||
{
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
||||
if (it->second.plr->accountId == accountId)
|
||||
return true;
|
||||
}
|
||||
@@ -698,16 +962,62 @@ bool PlayerManager::isAccountInUse(int accountId) {
|
||||
|
||||
void PlayerManager::exitDuplicate(int accountId) {
|
||||
std::map<CNSocket*, PlayerView>::iterator it;
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++)
|
||||
{
|
||||
if (it->second.plr->accountId == accountId)
|
||||
{
|
||||
|
||||
// disconnect any duplicate players
|
||||
for (it = players.begin(); it != players.end(); it++) {
|
||||
if (it->second.plr->accountId == accountId) {
|
||||
CNSocket* sock = it->first;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_DUPLICATE, resp);
|
||||
resp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_EXIT_DUPLICATE, sizeof(sP_FE2CL_REP_PC_EXIT_DUPLICATE));
|
||||
|
||||
sock->kill();
|
||||
CNShardServer::_killConnection(sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
Player *plr = getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH* setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
|
||||
|
||||
// HACK: work around the invisible weapon bug
|
||||
if (setData->iSpecialStateFlag == CN_SPECIAL_STATE_FLAG__FULL_UI)
|
||||
ItemManager::updateEquips(sock, plr);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_SPECIAL_STATE_CHANGE, response);
|
||||
|
||||
plr->iSpecialState ^= setData->iSpecialStateFlag;
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iReqSpecialStateFlag = setData->iSpecialStateFlag;
|
||||
response.iSpecialState = plr->iSpecialState;
|
||||
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
|
||||
sendToViewable(sock, (void*)&response, P_FE2CL_PC_SPECIAL_STATE_CHANGE, sizeof(sP_FE2CL_PC_SPECIAL_STATE_CHANGE));
|
||||
}
|
||||
|
||||
Player *PlayerManager::getPlayerFromID(int32_t iID) {
|
||||
for (auto& pair : PlayerManager::players)
|
||||
if (pair.second.plr->iID == iID)
|
||||
return pair.second.plr;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CNSocket *PlayerManager::getSockFromID(int32_t iID) {
|
||||
for (auto& pair : PlayerManager::players)
|
||||
if (pair.second.plr->iID == iID)
|
||||
return pair.first;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#pragma endregion
|
||||
|
@@ -4,17 +4,19 @@
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
struct PlayerView {
|
||||
std::list<CNSocket*> viewable;
|
||||
std::list<int32_t> viewableNPCs;
|
||||
std::tuple<int, int, uint64_t> chunkPos;
|
||||
std::vector<Chunk*> currentChunks;
|
||||
Player *plr;
|
||||
uint64_t lastHeartbeat;
|
||||
time_t lastHeartbeat;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,8 +27,17 @@ namespace PlayerManager {
|
||||
void addPlayer(CNSocket* key, Player plr);
|
||||
void removePlayer(CNSocket* key);
|
||||
|
||||
bool removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket* sock);
|
||||
void addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock);
|
||||
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z);
|
||||
std::list<CNSocket*> getNearbyPlayers(int X, int Y, int dist);
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, int angle);
|
||||
void updatePlayerChunk(CNSocket* sock, int X, int Y, uint64_t instanceID);
|
||||
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
|
||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
|
||||
|
||||
void sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size);
|
||||
|
||||
void enterPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void loadPlayer(CNSocket* sock, CNPacketData* data);
|
||||
@@ -37,6 +48,7 @@ namespace PlayerManager {
|
||||
void launchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void ziplinePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void movePlatformPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void moveSliderPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void moveSlopePlayer(CNSocket* sock, CNPacketData* data);
|
||||
void gotoPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void setSpecialPlayer(CNSocket* sock, CNPacketData* data);
|
||||
@@ -45,6 +57,7 @@ namespace PlayerManager {
|
||||
void exitGame(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
|
||||
void changePlayerGuide(CNSocket *sock, CNPacketData *data);
|
||||
|
||||
void enterPlayerVehicle(CNSocket* sock, CNPacketData* data);
|
||||
@@ -55,4 +68,7 @@ namespace PlayerManager {
|
||||
|
||||
bool isAccountInUse(int accountId);
|
||||
void exitDuplicate(int accountId);
|
||||
void setSpecialState(CNSocket* sock, CNPacketData* data);
|
||||
Player *getPlayerFromID(int32_t iID);
|
||||
CNSocket *getSockFromID(int32_t iID);
|
||||
}
|
||||
|
658
src/TableData.cpp
Normal file
658
src/TableData.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
#include "TableData.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
std::map<int32_t, std::vector<WarpLocation>> TableData::RunningSkywayRoutes;
|
||||
std::map<int32_t, int> TableData::RunningNPCRotations;
|
||||
std::map<int32_t, int> TableData::RunningNPCMapNumbers;
|
||||
std::map<int32_t, BaseNPC*> TableData::RunningMobs;
|
||||
|
||||
class TableException : public std::exception {
|
||||
public:
|
||||
std::string msg;
|
||||
|
||||
TableException(std::string m) : std::exception() { msg = m; }
|
||||
|
||||
const char *what() const throw() { return msg.c_str(); }
|
||||
};
|
||||
|
||||
void TableData::init() {
|
||||
int32_t nextId = 0;
|
||||
|
||||
// load NPCs from NPC.json
|
||||
try {
|
||||
std::ifstream inFile(settings::NPCJSON);
|
||||
nlohmann::json npcData;
|
||||
|
||||
// read file into json
|
||||
inFile >> npcData;
|
||||
for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
|
||||
auto npc = _npc.value();
|
||||
int instanceID = npc.find("mapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["mapNum"];
|
||||
BaseNPC *tmp = new BaseNPC(npc["x"], npc["y"], npc["z"], npc["angle"], instanceID, npc["id"], nextId);
|
||||
|
||||
NPCManager::NPCs[nextId] = tmp;
|
||||
NPCManager::updateNPCPosition(nextId, npc["x"], npc["y"], npc["z"]);
|
||||
nextId++;
|
||||
|
||||
if (npc["id"] == 641 || npc["id"] == 642)
|
||||
NPCManager::RespawnPoints.push_back({ npc["x"], npc["y"], ((int)npc["z"]) + RESURRECT_HEIGHT, instanceID });
|
||||
}
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
// load everything else from xdttable
|
||||
std::cout << "[INFO] Parsing xdt.json..." << std::endl;
|
||||
std::ifstream infile(settings::XDTJSON);
|
||||
nlohmann::json xdtData;
|
||||
|
||||
// read file into json
|
||||
infile >> xdtData;
|
||||
|
||||
// data we'll need for summoned mobs
|
||||
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
||||
|
||||
try {
|
||||
// load warps
|
||||
nlohmann::json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
||||
|
||||
for (nlohmann::json::iterator _warp = warpData.begin(); _warp != warpData.end(); _warp++) {
|
||||
auto warp = _warp.value();
|
||||
WarpLocation warpLoc = { warp["m_iToX"], warp["m_iToY"], warp["m_iToZ"], warp["m_iToMapNum"], warp["m_iIsInstance"], warp["m_iLimit_TaskID"], warp["m_iNpcNumber"] };
|
||||
int warpID = warp["m_iWarpNumber"];
|
||||
NPCManager::Warps[warpID] = warpLoc;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Populated " << NPCManager::Warps.size() << " Warps" << std::endl;
|
||||
|
||||
// load transport routes and locations
|
||||
nlohmann::json transRouteData = xdtData["m_pTransportationTable"]["m_pTransportationData"];
|
||||
nlohmann::json transLocData = xdtData["m_pTransportationTable"]["m_pTransportationWarpLocation"];
|
||||
|
||||
for (nlohmann::json::iterator _tLoc = transLocData.begin(); _tLoc != transLocData.end(); _tLoc++) {
|
||||
auto tLoc = _tLoc.value();
|
||||
TransportLocation transLoc = { tLoc["m_iNPCID"], tLoc["m_iXpos"], tLoc["m_iYpos"], tLoc["m_iZpos"] };
|
||||
TransportManager::Locations[tLoc["m_iLocationID"]] = transLoc;
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << TransportManager::Locations.size() << " S.C.A.M.P.E.R. locations" << std::endl;
|
||||
|
||||
for (nlohmann::json::iterator _tRoute = transRouteData.begin(); _tRoute != transRouteData.end(); _tRoute++) {
|
||||
auto tRoute = _tRoute.value();
|
||||
TransportRoute transRoute = { tRoute["m_iMoveType"], tRoute["m_iStartLocation"], tRoute["m_iEndLocation"],
|
||||
tRoute["m_iCost"] , tRoute["m_iSpeed"], tRoute["m_iRouteNum"] };
|
||||
TransportManager::Routes[tRoute["m_iVehicleID"]] = transRoute;
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << TransportManager::Routes.size() << " transportation routes" << std::endl;
|
||||
|
||||
// load mission-related data
|
||||
nlohmann::json tasks = xdtData["m_pMissionTable"]["m_pMissionData"];
|
||||
|
||||
for (auto _task = tasks.begin(); _task != tasks.end(); _task++) {
|
||||
auto task = _task.value();
|
||||
|
||||
// rewards
|
||||
if (task["m_iSUReward"] != 0) {
|
||||
auto _rew = xdtData["m_pMissionTable"]["m_pRewardData"][(int)task["m_iSUReward"]];
|
||||
Reward *rew = new Reward(_rew["m_iMissionRewardID"], _rew["m_iMissionRewarItemType"],
|
||||
_rew["m_iMissionRewardItemID"], _rew["m_iCash"], _rew["m_iFusionMatter"]);
|
||||
|
||||
MissionManager::Rewards[task["m_iHTaskID"]] = rew;
|
||||
}
|
||||
|
||||
// everything else lol. see TaskData comment.
|
||||
MissionManager::Tasks[task["m_iHTaskID"]] = new TaskData(task);
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded mission-related data" << std::endl;
|
||||
|
||||
// load all item data. i'm sorry. it has to be done
|
||||
const char* setNames[12] = { "m_pBackItemTable", "m_pFaceItemTable", "m_pGlassItemTable", "m_pHatItemTable",
|
||||
"m_pHeadItemTable", "m_pPantsItemTable", "m_pShirtsItemTable", "m_pShoesItemTable", "m_pWeaponItemTable",
|
||||
"m_pVehicleItemTable", "m_pGeneralItemTable", "m_pChestItemTable" };
|
||||
nlohmann::json itemSet;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
itemSet = xdtData[setNames[i]]["m_pItemData"];
|
||||
for (nlohmann::json::iterator _item = itemSet.begin(); _item != itemSet.end(); _item++) {
|
||||
auto item = _item.value();
|
||||
int typeOverride = getItemType(i); // used for special cases where iEquipLoc doesn't indicate item type
|
||||
ItemManager::ItemData[std::pair<int32_t, int32_t>(item["m_iItemNumber"], typeOverride != -1 ? typeOverride : (int)item["m_iEquipLoc"])]
|
||||
= { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"], i > 9 ? 1 : (int)item["m_iRarity"], i > 9 ? 0 : (int)item["m_iPointRat"], i > 9 ? 0 : (int)item["m_iGroupRat"], i > 9 ? 0 : (int)item["m_iDefenseRat"], i > 9 ? 0 : (int)item["m_iReqSex"] };
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl;
|
||||
|
||||
// load player limits from m_pAvatarTable.m_pAvatarGrowData
|
||||
|
||||
nlohmann::json growth = xdtData["m_pAvatarTable"]["m_pAvatarGrowData"];
|
||||
|
||||
for (int i = 0; i < 37; i++) {
|
||||
MissionManager::AvatarGrowth[i] = growth[i];
|
||||
}
|
||||
|
||||
// load vendor listings
|
||||
nlohmann::json listings = xdtData["m_pVendorTable"]["m_pItemData"];
|
||||
|
||||
for (nlohmann::json::iterator _lst = listings.begin(); _lst != listings.end(); _lst++) {
|
||||
auto lst = _lst.value();
|
||||
VendorListing vListing = { lst["m_iSortNumber"], lst["m_iItemType"], lst["m_iitemID"] };
|
||||
ItemManager::VendorTables[lst["m_iNpcNumber"]].push_back(vListing);
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << ItemManager::VendorTables.size() << " vendor tables" << std::endl;
|
||||
|
||||
// load crocpot entries
|
||||
nlohmann::json crocs = xdtData["m_pCombiningTable"]["m_pCombiningData"];
|
||||
|
||||
for (nlohmann::json::iterator croc = crocs.begin(); croc != crocs.end(); croc++) {
|
||||
CrocPotEntry crocEntry = { croc.value()["m_iStatConstant"], croc.value()["m_iLookConstant"], croc.value()["m_fLevelGapStandard"],
|
||||
croc.value()["m_fSameGrade"], croc.value()["m_fOneGrade"], croc.value()["m_fTwoGrade"], croc.value()["m_fThreeGrade"] };
|
||||
ItemManager::CrocPotTable[croc.value()["m_iLevelGap"]] = crocEntry;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << ItemManager::CrocPotTable.size() << " croc pot value sets" << std::endl;
|
||||
|
||||
// load nano info
|
||||
nlohmann::json nanoInfo = xdtData["m_pNanoTable"]["m_pNanoData"];
|
||||
for (nlohmann::json::iterator _nano = nanoInfo.begin(); _nano != nanoInfo.end(); _nano++) {
|
||||
auto nano = _nano.value();
|
||||
NanoData nanoData;
|
||||
nanoData.style = nano["m_iStyle"];
|
||||
NanoManager::NanoTable[nano["m_iNanoNumber"]] = nanoData;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << NanoManager::NanoTable.size() << " nanos" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed xdt.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
// load temporary mob dump
|
||||
try {
|
||||
std::ifstream inFile(settings::MOBJSON);
|
||||
nlohmann::json npcData;
|
||||
|
||||
// read file into json
|
||||
inFile >> npcData;
|
||||
|
||||
for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
|
||||
auto npc = _npc.value();
|
||||
auto td = NPCManager::NPCData[(int)npc["iNPCType"]];
|
||||
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||
Mob *tmp = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], npc["iHP"], td, nextId);
|
||||
|
||||
NPCManager::NPCs[nextId] = tmp;
|
||||
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
NPCManager::updateNPCPosition(nextId, npc["iX"], npc["iY"], npc["iZ"]);
|
||||
|
||||
nextId++;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Populated " << NPCManager::NPCs.size() << " NPCs" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed mobs.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
loadDrops();
|
||||
|
||||
loadPaths(&nextId); // load paths
|
||||
|
||||
loadGruntwork(&nextId);
|
||||
|
||||
NPCManager::nextId = nextId;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some item categories either don't possess iEquipLoc or use a different value for item type.
|
||||
*/
|
||||
int TableData::getItemType(int itemSet) {
|
||||
int overriden;
|
||||
switch (itemSet) {
|
||||
case 11: // Chest items don't have iEquipLoc and are type 9.
|
||||
overriden = 9;
|
||||
break;
|
||||
case 10: // General items don't have iEquipLoc and are type 7.
|
||||
overriden = 7;
|
||||
break;
|
||||
case 9: // Vehicles have iEquipLoc 8, but type 10.
|
||||
overriden = 10;
|
||||
break;
|
||||
default:
|
||||
overriden = -1;
|
||||
}
|
||||
return overriden;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load paths from paths JSON.
|
||||
*/
|
||||
void TableData::loadPaths(int* nextId) {
|
||||
try {
|
||||
std::ifstream inFile(settings::PATHJSON);
|
||||
nlohmann::json pathData;
|
||||
|
||||
// read file into json
|
||||
inFile >> pathData;
|
||||
|
||||
// skyway paths
|
||||
nlohmann::json pathDataSkyway = pathData["skyway"];
|
||||
for (nlohmann::json::iterator skywayPath = pathDataSkyway.begin(); skywayPath != pathDataSkyway.end(); skywayPath++) {
|
||||
constructPathSkyway(skywayPath);
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << TransportManager::SkywayPaths.size() << " skyway paths" << std::endl;
|
||||
|
||||
// slider circuit
|
||||
int sliders = 0;
|
||||
nlohmann::json pathDataSlider = pathData["slider"];
|
||||
for (nlohmann::json::iterator _sliderPoint = pathDataSlider.begin(); _sliderPoint != pathDataSlider.end(); _sliderPoint++) {
|
||||
auto sliderPoint = _sliderPoint.value();
|
||||
if (sliderPoint["stop"] && sliders % 2 == 0) { // check if this point in the circuit is a stop
|
||||
// spawn a slider
|
||||
BaseNPC* slider = new BaseNPC(sliderPoint["iX"], sliderPoint["iY"], sliderPoint["iZ"], 0, INSTANCE_OVERWORLD, 1, (*nextId)++, NPC_BUS);
|
||||
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider;
|
||||
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->appearanceData.iX, slider->appearanceData.iY, slider->appearanceData.iZ);
|
||||
// set slider path to a rotation of the circuit
|
||||
constructPathSlider(pathDataSlider, 0, slider->appearanceData.iNPC_ID);
|
||||
sliders++;
|
||||
}
|
||||
}
|
||||
|
||||
// npc paths
|
||||
nlohmann::json pathDataNPC = pathData["npc"];
|
||||
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
||||
constructPathNPC(npcPath);
|
||||
}
|
||||
|
||||
// mob paths
|
||||
pathDataNPC = pathData["mob"];
|
||||
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
||||
for (auto& pair : MobManager::Mobs) {
|
||||
if (pair.second->appearanceData.iNPCType == npcPath.value()["iNPCType"]) {
|
||||
std::cout << "[INFO] Using static path for mob " << pair.second->appearanceData.iNPCType << " with ID " << pair.first << std::endl;
|
||||
|
||||
auto firstPoint = npcPath.value()["points"][0];
|
||||
if (firstPoint["iX"] != pair.second->spawnX || firstPoint["iY"] != pair.second->spawnY) {
|
||||
std::cout << "[FATAL] The first point of the route for mob " << pair.first <<
|
||||
" (type " << pair.second->appearanceData.iNPCType << ") does not correspond with its spawn point." << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
constructPathNPC(npcPath, pair.first);
|
||||
pair.second->staticPath = true;
|
||||
break; // only one NPC per path
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << TransportManager::NPCQueues.size() << " NPC paths" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load drops data from JSON.
|
||||
* This has to be called after reading xdt because it reffers to ItemData!!!
|
||||
*/
|
||||
void TableData::loadDrops() {
|
||||
try {
|
||||
std::ifstream inFile(settings::DROPSJSON);
|
||||
nlohmann::json dropData;
|
||||
|
||||
// read file into json
|
||||
inFile >> dropData;
|
||||
|
||||
// MobDropChances
|
||||
nlohmann::json mobDropChances = dropData["MobDropChances"];
|
||||
for (nlohmann::json::iterator _dropChance = mobDropChances.begin(); _dropChance != mobDropChances.end(); _dropChance++) {
|
||||
auto dropChance = _dropChance.value();
|
||||
MobDropChance toAdd = {};
|
||||
toAdd.dropChance = (int)dropChance["DropChance"];
|
||||
for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) {
|
||||
toAdd.cratesRatio.push_back((int)_cratesRatio.value());
|
||||
}
|
||||
MobManager::MobDropChances[(int)dropChance["Type"]] = toAdd;
|
||||
}
|
||||
|
||||
// MobDrops
|
||||
nlohmann::json mobDrops = dropData["MobDrops"];
|
||||
for (nlohmann::json::iterator _drop = mobDrops.begin(); _drop != mobDrops.end(); _drop++) {
|
||||
auto drop = _drop.value();
|
||||
MobDrop toAdd = {};
|
||||
for (nlohmann::json::iterator _crates = drop["CrateIDs"].begin(); _crates != drop["CrateIDs"].end(); _crates++) {
|
||||
toAdd.crateIDs.push_back((int)_crates.value());
|
||||
}
|
||||
|
||||
toAdd.dropChanceType = (int)drop["DropChance"];
|
||||
// Check if DropChance exists
|
||||
if (MobManager::MobDropChances.find(toAdd.dropChanceType) == MobManager::MobDropChances.end()) {
|
||||
throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType)));
|
||||
}
|
||||
// Check if number of crates is correct
|
||||
if (!(MobManager::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) {
|
||||
throw TableException(" DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates");
|
||||
}
|
||||
|
||||
toAdd.taros = (int)drop["Taros"];
|
||||
toAdd.fm = (int)drop["FM"];
|
||||
toAdd.boosts = (int)drop["Boosts"];
|
||||
MobManager::MobDrops[(int)drop["DropType"]] = toAdd;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << MobManager::MobDrops.size() << " Mob Drop Types"<< std::endl;
|
||||
|
||||
// Rarity Ratios
|
||||
nlohmann::json rarities = dropData["RarityRatios"];
|
||||
for (nlohmann::json::iterator _rarity = rarities.begin(); _rarity != rarities.end(); _rarity++) {
|
||||
auto rarity = _rarity.value();
|
||||
std::vector<int> toAdd;
|
||||
for (nlohmann::json::iterator _ratio = rarity["Ratio"].begin(); _ratio != rarity["Ratio"].end(); _ratio++){
|
||||
toAdd.push_back((int)_ratio.value());
|
||||
}
|
||||
ItemManager::RarityRatios[(int)rarity["Type"]] = toAdd;
|
||||
}
|
||||
|
||||
// Crates
|
||||
nlohmann::json crates = dropData["Crates"];
|
||||
for (nlohmann::json::iterator _crate = crates.begin(); _crate != crates.end(); _crate++) {
|
||||
auto crate = _crate.value();
|
||||
Crate toAdd;
|
||||
toAdd.rarityRatioId = (int)crate["RarityRatio"];
|
||||
for (nlohmann::json::iterator _itemSet = crate["ItemSets"].begin(); _itemSet != crate["ItemSets"].end(); _itemSet++) {
|
||||
toAdd.itemSets.push_back((int)_itemSet.value());
|
||||
}
|
||||
ItemManager::Crates[(int)crate["Id"]] = toAdd;
|
||||
}
|
||||
|
||||
// Crate Items
|
||||
nlohmann::json items = dropData["Items"];
|
||||
int itemCount = 0;
|
||||
for (nlohmann::json::iterator _item = items.begin(); _item != items.end(); _item++) {
|
||||
auto item = _item.value();
|
||||
std::pair<int32_t, int32_t> itemSetkey = std::make_pair((int)item["ItemSet"], (int)item["Rarity"]);
|
||||
std::pair<int32_t, int32_t> itemDataKey = std::make_pair((int)item["Id"], (int)item["Type"]);
|
||||
|
||||
if (ItemManager::ItemData.find(itemDataKey) == ItemManager::ItemData.end()) {
|
||||
char buff[255];
|
||||
sprintf(buff, "Unknown item with Id %d and Type %d", (int)item["Id"], (int)item["Type"]);
|
||||
throw TableException(std::string(buff));
|
||||
}
|
||||
|
||||
std::map<std::pair<int32_t, int32_t>, Item>::iterator toAdd = ItemManager::ItemData.find(itemDataKey);
|
||||
|
||||
// if item collection doesn't exist, start a new one
|
||||
if (ItemManager::CrateItems.find(itemSetkey) == ItemManager::CrateItems.end()) {
|
||||
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator> vector;
|
||||
vector.push_back(toAdd);
|
||||
ItemManager::CrateItems[itemSetkey] = vector;
|
||||
} else // else add a new element to existing collection
|
||||
ItemManager::CrateItems[itemSetkey].push_back(toAdd);
|
||||
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << ItemManager::Crates.size() << " Crates containing "
|
||||
<< itemCount << " items" << std::endl;
|
||||
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed drops.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a full and properly-paced path by interpolating between keyframes.
|
||||
*/
|
||||
void TableData::constructPathSkyway(nlohmann::json::iterator _pathData) {
|
||||
auto pathData = _pathData.value();
|
||||
// Interpolate
|
||||
nlohmann::json pathPoints = pathData["points"];
|
||||
std::queue<WarpLocation> points;
|
||||
nlohmann::json::iterator _point = pathPoints.begin();
|
||||
auto point = _point.value();
|
||||
WarpLocation last = { point["iX"] , point["iY"] , point["iZ"] }; // start pos
|
||||
// use some for loop trickery; start position should not be a point
|
||||
for (_point++; _point != pathPoints.end(); _point++) {
|
||||
point = _point.value();
|
||||
WarpLocation coords = { point["iX"] , point["iY"] , point["iZ"] };
|
||||
TransportManager::lerp(&points, last, coords, pathData["iMonkeySpeed"]);
|
||||
points.push(coords); // add keyframe to the queue
|
||||
last = coords; // update start pos
|
||||
}
|
||||
TransportManager::SkywayPaths[pathData["iRouteID"]] = points;
|
||||
}
|
||||
|
||||
void TableData::constructPathSlider(nlohmann::json points, int rotations, int sliderID) {
|
||||
std::queue<WarpLocation> route;
|
||||
std::rotate(points.begin(), points.begin() + rotations, points.end()); // rotate points
|
||||
nlohmann::json::iterator _point = points.begin(); // iterator
|
||||
auto point = _point.value();
|
||||
WarpLocation from = { point["iX"] , point["iY"] , point["iZ"] }; // point A coords
|
||||
int stopTime = point["stop"] ? SLIDER_STOP_TICKS : 0; // arbitrary stop length
|
||||
for (_point++; _point != points.end(); _point++) { // loop through all point Bs
|
||||
point = _point.value();
|
||||
for (int i = 0; i < stopTime + 1; i++) // repeat point if it's a stop
|
||||
route.push(from); // add point A to the queue
|
||||
WarpLocation to = { point["iX"] , point["iY"] , point["iZ"] }; // point B coords
|
||||
float curve = 1;
|
||||
if (stopTime > 0) { // point A is a stop
|
||||
curve = 2.0f;
|
||||
} else if (point["stop"]) { // point B is a stop
|
||||
curve = 0.35f;
|
||||
}
|
||||
TransportManager::lerp(&route, from, to, SLIDER_SPEED, curve); // lerp from A to B (arbitrary speed)
|
||||
from = to; // update point A
|
||||
stopTime = point["stop"] ? SLIDER_STOP_TICKS : 0;
|
||||
}
|
||||
std::rotate(points.rbegin(), points.rbegin() + rotations, points.rend()); // undo rotation
|
||||
TransportManager::NPCQueues[sliderID] = route;
|
||||
}
|
||||
|
||||
void TableData::constructPathNPC(nlohmann::json::iterator _pathData, int32_t id) {
|
||||
auto pathData = _pathData.value();
|
||||
// Interpolate
|
||||
nlohmann::json pathPoints = pathData["points"];
|
||||
std::queue<WarpLocation> points;
|
||||
nlohmann::json::iterator _point = pathPoints.begin();
|
||||
auto point = _point.value();
|
||||
WarpLocation from = { point["iX"] , point["iY"] , point["iZ"] }; // point A coords
|
||||
int stopTime = point["stop"];
|
||||
for (_point++; _point != pathPoints.end(); _point++) { // loop through all point Bs
|
||||
point = _point.value();
|
||||
for(int i = 0; i < stopTime + 1; i++) // repeat point if it's a stop
|
||||
points.push(from); // add point A to the queue
|
||||
WarpLocation to = { point["iX"] , point["iY"] , point["iZ"] }; // point B coords
|
||||
TransportManager::lerp(&points, from, to, pathData["iBaseSpeed"]); // lerp from A to B
|
||||
from = to; // update point A
|
||||
stopTime = point["stop"];
|
||||
}
|
||||
|
||||
if (id == 0)
|
||||
id = pathData["iNPCID"];
|
||||
|
||||
TransportManager::NPCQueues[id] = points;
|
||||
}
|
||||
|
||||
// load gruntwork output; if it exists
|
||||
void TableData::loadGruntwork(int32_t *nextId) {
|
||||
try {
|
||||
std::ifstream inFile(settings::GRUNTWORKJSON);
|
||||
nlohmann::json gruntwork;
|
||||
|
||||
// skip if there's no gruntwork to load
|
||||
if (inFile.fail())
|
||||
return;
|
||||
|
||||
inFile >> gruntwork;
|
||||
|
||||
// skyway paths
|
||||
auto skyway = gruntwork["skyway"];
|
||||
for (auto _route = skyway.begin(); _route != skyway.end(); _route++) {
|
||||
auto route = _route.value();
|
||||
std::vector<WarpLocation> points;
|
||||
|
||||
for (auto _point = route["points"].begin(); _point != route["points"].end(); _point++) {
|
||||
auto point = _point.value();
|
||||
points.push_back(WarpLocation{point["x"], point["y"], point["z"]});
|
||||
}
|
||||
|
||||
RunningSkywayRoutes[(int)route["iRouteID"]] = points;
|
||||
}
|
||||
|
||||
// npc rotations
|
||||
auto npcRot = gruntwork["rotations"];
|
||||
for (auto _rot = npcRot.begin(); _rot != npcRot.end(); _rot++) {
|
||||
int32_t npcID = _rot.value()["iNPCID"];
|
||||
int angle = _rot.value()["iAngle"];
|
||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||
continue; // NPC not found
|
||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||
npc->appearanceData.iAngle = angle;
|
||||
|
||||
RunningNPCRotations[npcID] = angle;
|
||||
}
|
||||
|
||||
// npc map numbers
|
||||
auto npcMap = gruntwork["instances"];
|
||||
for (auto _map = npcMap.begin(); _map != npcMap.end(); _map++) {
|
||||
int32_t npcID = _map.value()["iNPCID"];
|
||||
uint64_t instanceID = _map.value()["iMapNum"];
|
||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||
continue; // NPC not found
|
||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||
NPCManager::updateNPCInstance(npc->appearanceData.iNPC_ID, instanceID);
|
||||
|
||||
RunningNPCMapNumbers[npcID] = instanceID;
|
||||
}
|
||||
|
||||
// mobs
|
||||
auto mobs = gruntwork["mobs"];
|
||||
for (auto _mob = mobs.begin(); _mob != mobs.end(); _mob++) {
|
||||
auto mob = _mob.value();
|
||||
BaseNPC *npc;
|
||||
int id = (*nextId)++;
|
||||
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
||||
|
||||
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
||||
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
||||
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
||||
|
||||
// re-enable respawning
|
||||
((Mob*)npc)->summoned = false;
|
||||
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
} else {
|
||||
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
|
||||
}
|
||||
|
||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc;
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"]);
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded gruntwork.json" << std::endl;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed gruntwork.json file! Reason:" << err.what() << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
}
|
||||
|
||||
// write gruntwork output to file
|
||||
void TableData::flush() {
|
||||
std::ofstream file(settings::GRUNTWORKJSON);
|
||||
nlohmann::json gruntwork;
|
||||
|
||||
for (auto& pair : RunningSkywayRoutes) {
|
||||
nlohmann::json route;
|
||||
|
||||
route["iRouteID"] = (int)pair.first;
|
||||
route["iMonkeySpeed"] = 1500;
|
||||
|
||||
std::cout << "serializing mss route " << (int)pair.first << std::endl;
|
||||
for (WarpLocation& point : pair.second) {
|
||||
nlohmann::json tmp;
|
||||
|
||||
tmp["x"] = point.x;
|
||||
tmp["y"] = point.y;
|
||||
tmp["z"] = point.z;
|
||||
|
||||
route["points"].push_back(tmp);
|
||||
}
|
||||
|
||||
gruntwork["skyway"].push_back(route);
|
||||
}
|
||||
|
||||
for (auto& pair : RunningNPCRotations) {
|
||||
nlohmann::json rotation;
|
||||
|
||||
rotation["iNPCID"] = (int)pair.first;
|
||||
rotation["iAngle"] = pair.second;
|
||||
|
||||
gruntwork["rotations"].push_back(rotation);
|
||||
}
|
||||
|
||||
for (auto& pair : RunningNPCMapNumbers) {
|
||||
nlohmann::json mapNumber;
|
||||
|
||||
mapNumber["iNPCID"] = (int)pair.first;
|
||||
mapNumber["iMapNum"] = pair.second;
|
||||
|
||||
gruntwork["instances"].push_back(mapNumber);
|
||||
}
|
||||
|
||||
for (auto& pair : RunningMobs) {
|
||||
nlohmann::json mob;
|
||||
BaseNPC *npc = pair.second;
|
||||
|
||||
if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end())
|
||||
continue;
|
||||
|
||||
int x, y, z, hp;
|
||||
if (npc->npcClass == NPC_MOB) {
|
||||
Mob *m = (Mob*)npc;
|
||||
x = m->spawnX;
|
||||
y = m->spawnY;
|
||||
z = m->spawnZ;
|
||||
hp = m->maxHealth;
|
||||
} else {
|
||||
x = npc->appearanceData.iX;
|
||||
y = npc->appearanceData.iY;
|
||||
z = npc->appearanceData.iZ;
|
||||
hp = npc->appearanceData.iHP;
|
||||
}
|
||||
|
||||
// NOTE: this format deviates slightly from the one in mobs.json
|
||||
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
|
||||
mob["iHP"] = hp;
|
||||
mob["iX"] = x;
|
||||
mob["iY"] = y;
|
||||
mob["iZ"] = z;
|
||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
||||
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
|
||||
mob["iAngle"] = npc->appearanceData.iAngle;
|
||||
|
||||
// it's called mobs, but really it's everything
|
||||
gruntwork["mobs"].push_back(mob);
|
||||
}
|
||||
|
||||
file << gruntwork << std::endl;
|
||||
}
|
24
src/TableData.hpp
Normal file
24
src/TableData.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "contrib/JSON.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
namespace TableData {
|
||||
extern std::map<int32_t, std::vector<WarpLocation>> RunningSkywayRoutes;
|
||||
extern std::map<int32_t, int> RunningNPCRotations;
|
||||
extern std::map<int32_t, int> RunningNPCMapNumbers;
|
||||
extern std::map<int32_t, BaseNPC*> RunningMobs;
|
||||
|
||||
void init();
|
||||
void cleanup();
|
||||
void loadGruntwork(int32_t*);
|
||||
void flush();
|
||||
|
||||
int getItemType(int);
|
||||
void loadPaths(int*);
|
||||
void loadDrops();
|
||||
void constructPathSkyway(nlohmann::json::iterator);
|
||||
void constructPathSlider(nlohmann::json, int, int);
|
||||
void constructPathNPC(nlohmann::json::iterator, int id=0);
|
||||
}
|
@@ -1,18 +1,367 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNStructs.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "MobManager.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cmath>
|
||||
|
||||
std::map<int32_t, TransportRoute> TransportManager::Routes;
|
||||
std::map<int32_t, TransportLocation> TransportManager::Locations;
|
||||
std::map<int32_t, std::queue<WarpLocation>> TransportManager::SkywayPaths;
|
||||
std::unordered_map<CNSocket*, std::queue<WarpLocation>> TransportManager::SkywayQueues;
|
||||
std::unordered_map<int32_t, std::queue<WarpLocation>> TransportManager::NPCQueues;
|
||||
|
||||
void TransportManager::init() {
|
||||
REGISTER_SHARD_TIMER(tickTransportationSystem, 1000);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler);
|
||||
}
|
||||
|
||||
void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION* transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
bool newReg = false; // this is a new registration
|
||||
//std::cout << "request to register transport, eTT " << transport->eTT << ", locID " << transport->iLocationID << ", npc " << transport->iNPC_ID << std::endl;
|
||||
if (transport->eTT == 1) { // S.C.A.M.P.E.R.
|
||||
if (transport->iLocationID < 1 || transport->iLocationID > 31) { // sanity check
|
||||
std::cout << "[WARN] S.C.A.M.P.E.R. location ID " << transport->iLocationID << " is out of bounds" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
||||
|
||||
failResp.eTT = transport->eTT;
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
// update registration bitfield using bitmask
|
||||
uint32_t newScamperFlag = plr->iWarpLocationFlag | (plr->accountLevel <= 40 ? INT32_MAX : (1UL << (transport->iLocationID - 1)));
|
||||
if (newScamperFlag != plr->iWarpLocationFlag) {
|
||||
plr->iWarpLocationFlag = newScamperFlag;
|
||||
newReg = true;
|
||||
}
|
||||
} else if (transport->eTT == 2) { // Monkey Skyway System
|
||||
if (transport->iLocationID < 1 || transport->iLocationID > 127) { // sanity check
|
||||
std::cout << "[WARN] Skyway location ID " << transport->iLocationID << " is out of bounds" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
||||
|
||||
failResp.eTT = transport->eTT;
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* assuming the two bitfields are just stuck together to make a longer one, do a similar operation
|
||||
*/
|
||||
if (plr->accountLevel <= 40) {
|
||||
plr->aSkywayLocationFlag[0] = INT64_MAX;
|
||||
plr->aSkywayLocationFlag[1] = INT64_MAX;
|
||||
newReg = true;
|
||||
} else {
|
||||
int index = transport->iLocationID > 64 ? 1 : 0;
|
||||
uint64_t newMonkeyFlag = plr->aSkywayLocationFlag[index] | (1ULL << (index ? transport->iLocationID - 65 : transport->iLocationID - 1));
|
||||
if (newMonkeyFlag != plr->aSkywayLocationFlag[index]) {
|
||||
plr->aSkywayLocationFlag[index] = newMonkeyFlag;
|
||||
newReg = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << "[WARN] Unknown mode of transport; eTT = " << transport->eTT << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
||||
|
||||
failResp.eTT = transport->eTT;
|
||||
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
||||
failResp.iLocationID = transport->iLocationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newReg)
|
||||
return; // don't send new registration message
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, resp);
|
||||
// response parameters
|
||||
resp.eTT = transport->eTT;
|
||||
resp.iLocationID = transport->iLocationID;
|
||||
resp.iWarpLocationFlag = plr->iWarpLocationFlag;
|
||||
resp.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||
resp.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC));
|
||||
}
|
||||
|
||||
void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION))
|
||||
return; // malformed packet
|
||||
|
||||
sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION* req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (plr == nullptr)
|
||||
return;
|
||||
|
||||
/*
|
||||
* req:
|
||||
* eIL -- inventory type
|
||||
* iNPC_ID -- the ID of the NPC who is warping you
|
||||
* iTransporationID -- iVehicleID
|
||||
* iSlotNum -- inventory slot number
|
||||
*/
|
||||
|
||||
if (Routes.find(req->iTransporationID) == Routes.end() || Routes[req->iTransporationID].cost > plr->money) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, failResp);
|
||||
|
||||
failResp.iErrorCode = 0; // TODO: error code
|
||||
failResp.iTransportationID = req->iTransporationID;
|
||||
|
||||
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL));
|
||||
return;
|
||||
}
|
||||
|
||||
TransportRoute route = Routes[req->iTransporationID];
|
||||
plr->money -= route.cost;
|
||||
|
||||
TransportLocation target;
|
||||
PlayerView& plrv = PlayerManager::players[sock];
|
||||
switch (route.type) {
|
||||
case 1: // S.C.A.M.P.E.R.
|
||||
target = Locations[route.end];
|
||||
plr->x = target.x;
|
||||
plr->y = target.y;
|
||||
plr->z = target.z;
|
||||
/*
|
||||
* Not strictly necessary since there isn't a valid SCAMPER that puts you in the
|
||||
* same map tile you were already in, but we might as well force an NPC reload.
|
||||
*/
|
||||
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
|
||||
plrv.currentChunks.clear();
|
||||
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
|
||||
break;
|
||||
case 2: // Monkey Skyway
|
||||
if (SkywayPaths.find(route.mssRouteNum) != SkywayPaths.end()) { // check if route exists
|
||||
NanoManager::summonNano(sock, -1); // make sure that no nano is active during the ride
|
||||
SkywayQueues[sock] = SkywayPaths[route.mssRouteNum]; // set socket point queue to route
|
||||
break;
|
||||
} else if (TableData::RunningSkywayRoutes.find(route.mssRouteNum) != TableData::RunningSkywayRoutes.end()) {
|
||||
std::vector<WarpLocation>* _route = &TableData::RunningSkywayRoutes[route.mssRouteNum];
|
||||
|
||||
NanoManager::summonNano(sock, -1);
|
||||
testMssRoute(sock, _route);
|
||||
break;
|
||||
}
|
||||
|
||||
// refund and send alert packet
|
||||
plr->money += route.cost;
|
||||
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
|
||||
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
|
||||
alert.iDuringTime = 3;
|
||||
U8toU16("Skyway route " + std::to_string(route.mssRouteNum) + " isn't pathed yet. You will not be charged any taros.", (char16_t*)alert.szAnnounceMsg, sizeof(alert.szAnnounceMsg));
|
||||
sock->sendPacket((void*)&alert, P_FE2CL_ANNOUNCE_MSG, sizeof(sP_FE2CL_ANNOUNCE_MSG));
|
||||
|
||||
std::cout << "[WARN] MSS route " << route.mssRouteNum << " not pathed" << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] Unknown tranportation type " << route.type << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, resp);
|
||||
// response parameters
|
||||
resp.eTT = route.type;
|
||||
resp.iCandy = plr->money;
|
||||
resp.iX = plr->x;
|
||||
resp.iY = plr->y;
|
||||
resp.iZ = plr->z;
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC));
|
||||
}
|
||||
|
||||
void TransportManager::testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route) {
|
||||
int speed = 1500; // TODO: make this adjustable
|
||||
std::queue<WarpLocation> path;
|
||||
WarpLocation last = route->front(); // start pos
|
||||
|
||||
for (int i = 1; i < route->size(); i++) {
|
||||
WarpLocation coords = route->at(i);
|
||||
TransportManager::lerp(&path, last, coords, speed);
|
||||
path.push(coords); // add keyframe to the queue
|
||||
last = coords; // update start pos
|
||||
}
|
||||
|
||||
SkywayQueues[sock] = path;
|
||||
}
|
||||
|
||||
void TransportManager::tickTransportationSystem(CNServer* serv, time_t currTime) {
|
||||
stepNPCPathing();
|
||||
stepSkywaySystem();
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through every socket that has broomstick points queued up, and advance to the next point.
|
||||
* If the player has disconnected or finished the route, clean up and remove them from the queue.
|
||||
*/
|
||||
void TransportManager::stepSkywaySystem() {
|
||||
|
||||
// using an unordered map so we can remove finished players in one iteration
|
||||
std::unordered_map<CNSocket*, std::queue<WarpLocation>>::iterator it = SkywayQueues.begin();
|
||||
while (it != SkywayQueues.end()) {
|
||||
|
||||
std::queue<WarpLocation>* queue = &it->second;
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(it->first);
|
||||
|
||||
if (plr == nullptr) {
|
||||
// pluck out dead socket + update iterator
|
||||
it = SkywayQueues.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (queue->empty()) {
|
||||
// send dismount packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_RIDING_SUCC, rideSucc);
|
||||
INITSTRUCT(sP_FE2CL_PC_RIDING, rideBroadcast);
|
||||
rideSucc.iPC_ID = plr->iID;
|
||||
rideSucc.eRT = 0;
|
||||
rideBroadcast.iPC_ID = plr->iID;
|
||||
rideBroadcast.eRT = 0;
|
||||
it->first->sendPacket((void*)&rideSucc, P_FE2CL_REP_PC_RIDING_SUCC, sizeof(sP_FE2CL_REP_PC_RIDING_SUCC));
|
||||
// send packet to players in view
|
||||
PlayerManager::sendToViewable(it->first, (void*)&rideBroadcast, P_FE2CL_PC_RIDING, sizeof(sP_FE2CL_PC_RIDING));
|
||||
it = SkywayQueues.erase(it); // remove player from tracking map + update iterator
|
||||
} else {
|
||||
WarpLocation point = queue->front(); // get point
|
||||
queue->pop(); // remove point from front of queue
|
||||
|
||||
INITSTRUCT(sP_FE2CL_PC_BROOMSTICK_MOVE, bmstk);
|
||||
bmstk.iPC_ID = plr->iID;
|
||||
bmstk.iToX = point.x;
|
||||
bmstk.iToY = point.y;
|
||||
bmstk.iToZ = point.z;
|
||||
it->first->sendPacket((void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
|
||||
// set player location to point to update viewables
|
||||
PlayerManager::updatePlayerChunk(it->first, point.x, point.y, plr->instanceID);
|
||||
// send packet to players in view
|
||||
PlayerManager::sendToViewable(it->first, (void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
|
||||
|
||||
it++; // go to next entry in map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransportManager::stepNPCPathing() {
|
||||
|
||||
// all NPC pathing queues
|
||||
std::unordered_map<int32_t, std::queue<WarpLocation>>::iterator it = NPCQueues.begin();
|
||||
while (it != NPCQueues.end()) {
|
||||
|
||||
std::queue<WarpLocation>* queue = &it->second;
|
||||
|
||||
BaseNPC* npc = nullptr;
|
||||
if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end())
|
||||
npc = NPCManager::NPCs[it->first];
|
||||
|
||||
if (npc == nullptr || queue->empty()) {
|
||||
// pluck out dead path + update iterator
|
||||
it = NPCQueues.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if not simulating mobs
|
||||
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not roam if not roaming
|
||||
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
WarpLocation point = queue->front(); // get point
|
||||
queue->pop(); // remove point from front of queue
|
||||
|
||||
// calculate displacement
|
||||
int dXY = hypot(point.x - npc->appearanceData.iX, point.y - npc->appearanceData.iY); // XY plane distance
|
||||
int distanceBetween = hypot(dXY, point.z - npc->appearanceData.iZ); // total distance
|
||||
|
||||
// update NPC location to update viewables
|
||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z);
|
||||
|
||||
switch (npc->npcClass) {
|
||||
case NPC_BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||
busMove.eTT = 3;
|
||||
busMove.iT_ID = npc->appearanceData.iNPC_ID;
|
||||
busMove.iMoveStyle = 0; // ???
|
||||
busMove.iToX = point.x;
|
||||
busMove.iToY = point.y;
|
||||
busMove.iToZ = point.z;
|
||||
busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work
|
||||
|
||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||
break;
|
||||
case NPC_MOB:
|
||||
MobManager::incNextMovement((Mob*)npc);
|
||||
/* fallthrough */
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||
move.iNPC_ID = npc->appearanceData.iNPC_ID;
|
||||
move.iMoveStyle = 0; // ???
|
||||
move.iToX = point.x;
|
||||
move.iToY = point.y;
|
||||
move.iToZ = point.z;
|
||||
move.iSpeed = distanceBetween;
|
||||
|
||||
NPCManager::sendToViewable(npc, &move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move processed point to the back to maintain cycle, unless this is a
|
||||
* dynamically calculated mob route.
|
||||
*/
|
||||
if (!(npc->npcClass == NPC_MOB && !((Mob*)npc)->staticPath))
|
||||
queue->push(point);
|
||||
|
||||
it++; // go to next entry in map
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Linearly interpolate between two points and insert the results into a queue.
|
||||
*/
|
||||
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize, float curve) {
|
||||
int dXY = hypot(end.x - start.x, end.y - start.y); // XY plane distance
|
||||
int distanceBetween = hypot(dXY, end.z - start.z); // total distance
|
||||
int lerps = distanceBetween / gapSize; // number of intermediate points to add
|
||||
for (int i = 1; i <= lerps; i++) {
|
||||
WarpLocation lerp;
|
||||
// lerp math
|
||||
//float frac = i / (lerps + 1);
|
||||
float frac = powf(i, curve) / powf(lerps + 1, curve);
|
||||
lerp.x = (start.x * (1.0f - frac)) + (end.x * frac);
|
||||
lerp.y = (start.y * (1.0f - frac)) + (end.y * frac);
|
||||
lerp.z = (start.z * (1.0f - frac)) + (end.z * frac);
|
||||
queue->push(lerp); // add lerp'd point
|
||||
}
|
||||
}
|
||||
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize) {
|
||||
lerp(queue, start, end, gapSize, 1);
|
||||
}
|
||||
|
@@ -1,9 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
const int SLIDER_SPEED = 1200;
|
||||
const int SLIDER_STOP_TICKS = 8;
|
||||
|
||||
struct WarpLocation;
|
||||
|
||||
struct TransportRoute {
|
||||
int type, start, end, cost, mssSpeed, mssRouteNum;
|
||||
};
|
||||
|
||||
struct TransportLocation {
|
||||
int npcID, x, y, z;
|
||||
};
|
||||
|
||||
namespace TransportManager {
|
||||
extern std::map<int32_t, TransportRoute> Routes;
|
||||
extern std::map<int32_t, TransportLocation> Locations;
|
||||
extern std::map<int32_t, std::queue<WarpLocation>> SkywayPaths; // predefined skyway paths with points
|
||||
extern std::unordered_map<CNSocket*, std::queue<WarpLocation>> SkywayQueues; // player sockets with queued broomstick points
|
||||
extern std::unordered_map<int32_t, std::queue<WarpLocation>> NPCQueues; // NPC ids with queued pathing points
|
||||
|
||||
void init();
|
||||
|
||||
void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data);
|
||||
void transportRegisterLocationHandler(CNSocket*, CNPacketData*);
|
||||
void transportWarpHandler(CNSocket*, CNPacketData*);
|
||||
|
||||
void testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route);
|
||||
|
||||
void tickTransportationSystem(CNServer*, time_t);
|
||||
void stepNPCPathing();
|
||||
void stepSkywaySystem();
|
||||
|
||||
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int, float);
|
||||
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int);
|
||||
}
|
||||
|
94
src/main.cpp
94
src/main.cpp
@@ -2,40 +2,58 @@
|
||||
#include "CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "CombatManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "BuddyManager.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "../version.h"
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
||||
#include "mingw/mingw.thread.h"
|
||||
#else
|
||||
#else
|
||||
#include <thread>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <signal.h>
|
||||
|
||||
CNShardServer *shardServer;
|
||||
std::thread *shardThread;
|
||||
// HACK
|
||||
#ifdef __has_feature
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define __SANITIZE_ADDRESS__ 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
CNShardServer *shardServer = nullptr;
|
||||
std::thread *shardThread = nullptr;
|
||||
|
||||
void startShard(CNShardServer* server) {
|
||||
server->start();
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// terminate gracefully on SIGINT (for gprof)
|
||||
void terminate(int arg) {
|
||||
std::cout << "OpenFusion: terminating." << std::endl;
|
||||
shardServer->kill();
|
||||
shardThread->join();
|
||||
|
||||
if (shardServer != nullptr && shardThread != nullptr) {
|
||||
shardServer->kill();
|
||||
shardThread->join();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
void initsignals() {
|
||||
struct sigaction act;
|
||||
|
||||
@@ -67,20 +85,35 @@ int main() {
|
||||
#else
|
||||
initsignals();
|
||||
#endif
|
||||
srand(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;
|
||||
TableData::init();
|
||||
PlayerManager::init();
|
||||
ChatManager::init();
|
||||
CombatManager::init();
|
||||
MobManager::init();
|
||||
ItemManager::init();
|
||||
MissionManager::init();
|
||||
NanoManager::init();
|
||||
NPCManager::init();
|
||||
TransportManager::init();
|
||||
|
||||
// BuddyManager::init(); // stubbed until we have database integration + lots of bug fixes
|
||||
GroupManager::init();
|
||||
Database::open();
|
||||
|
||||
switch (settings::EVENTMODE) {
|
||||
case 0: break; // no event
|
||||
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
|
||||
case 2: std::cout << "[INFO] Event active. Wishing you a spook-tacular Halloween!" << std::endl; break;
|
||||
case 3: std::cout << "[INFO] Event active. Have a very hoppy Easter!" << std::endl; break;
|
||||
default:
|
||||
std::cout << "[FATAL] Unknown event set in config file." << std::endl;
|
||||
terminate(0);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Starting Server Threads..." << std::endl;
|
||||
CNLoginServer loginServer(settings::LOGINPORT);
|
||||
shardServer = new CNShardServer(settings::SHARDPORT);
|
||||
@@ -97,3 +130,46 @@ int main() {
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
std::string U16toU8(char16_t* src) {
|
||||
try {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
return convert.to_bytes(src);
|
||||
} catch(const std::exception& e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// returns number of char16_t that was written at des
|
||||
size_t U8toU16(std::string src, char16_t* des, size_t max) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
std::u16string tmp = convert.from_bytes(src);
|
||||
|
||||
// copy utf16 string to buffer
|
||||
if (sizeof(char16_t) * tmp.length() > max) // make sure we don't write outside the buffer
|
||||
memcpy(des, tmp.c_str(), sizeof(char16_t) * max);
|
||||
else
|
||||
memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length());
|
||||
des[tmp.length()] = '\0';
|
||||
|
||||
return tmp.length();
|
||||
}
|
||||
|
||||
time_t getTime() {
|
||||
using namespace std::chrono;
|
||||
|
||||
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(high_resolution_clock::now())).time_since_epoch());
|
||||
|
||||
return (time_t)value.count();
|
||||
}
|
||||
|
||||
// returns system time in seconds
|
||||
time_t getTimestamp() {
|
||||
using namespace std::chrono;
|
||||
|
||||
seconds value = duration_cast<seconds>((time_point_cast<seconds>(system_clock::now())).time_since_epoch());
|
||||
|
||||
return (time_t)value.count();
|
||||
}
|
||||
|
@@ -6,24 +6,32 @@
|
||||
int settings::VERBOSITY = 1;
|
||||
|
||||
int settings::LOGINPORT = 8001;
|
||||
bool settings::LOGINRANDCHARACTERS = false;
|
||||
bool settings::APPROVEALLNAMES = true;
|
||||
int settings::DBSAVEINTERVAL = 240;
|
||||
|
||||
int settings::SHARDPORT = 8002;
|
||||
std::string settings::SHARDSERVERIP = "127.0.0.1";
|
||||
int settings::PLAYERDISTANCE = 20000;
|
||||
int settings::NPCDISTANCE = 16000;
|
||||
time_t settings::TIMEOUT = 60000;
|
||||
int settings::VIEWDISTANCE = 40000;
|
||||
bool settings::SIMULATEMOBS = true;
|
||||
|
||||
// default spawn point is city hall
|
||||
int settings::SPAWN_X = 179213;
|
||||
int settings::SPAWN_Y = 268451;
|
||||
int settings::SPAWN_Z = -4210;
|
||||
std::string settings::GMPASS = "pass";
|
||||
std::string settings::NPCJSON = "data/NPCs.json";
|
||||
std::string settings::WARPJSON = "data/warps.json";
|
||||
std::string settings::MOBJSON = "data/mobs.json";
|
||||
// default spawn point is Sector V (future)
|
||||
int settings::SPAWN_X = 632032;
|
||||
int settings::SPAWN_Y = 187177;
|
||||
int settings::SPAWN_Z = -5500;
|
||||
int settings::SPAWN_ANGLE = 130;
|
||||
std::string settings::NPCJSON = "tdata/NPCs.json";
|
||||
std::string settings::XDTJSON = "tdata/xdt.json";
|
||||
std::string settings::MOBJSON = "tdata/mobs.json";
|
||||
std::string settings::PATHJSON = "tdata/paths.json";
|
||||
std::string settings::DROPSJSON = "tdata/drops.json";
|
||||
std::string settings::GRUNTWORKJSON = "tdata/gruntwork.json";
|
||||
std::string settings::MOTDSTRING = "Welcome to OpenFusion!";
|
||||
bool settings::GM = false;
|
||||
int settings::ACCLEVEL = 1;
|
||||
|
||||
// event mode settings
|
||||
int settings::EVENTMODE = 0;
|
||||
int settings::EVENTCRATECHANCE = 10;
|
||||
|
||||
void settings::init() {
|
||||
INIReader reader("config.ini");
|
||||
@@ -31,7 +39,7 @@ void settings::init() {
|
||||
if (reader.ParseError() != 0) {
|
||||
if (reader.ParseError() == -1)
|
||||
std::cerr << "[WARN] Settings: missing config.ini file!" << std::endl;
|
||||
else
|
||||
else
|
||||
std::cerr << "[WARN] Settings: invalid config.ini syntax at line " << reader.ParseError() << std::endl;
|
||||
|
||||
return;
|
||||
@@ -40,18 +48,24 @@ void settings::init() {
|
||||
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
|
||||
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
|
||||
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
||||
LOGINRANDCHARACTERS = reader.GetBoolean("login", "randomcharacters", LOGINRANDCHARACTERS);
|
||||
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
|
||||
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
|
||||
PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE);
|
||||
NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE);
|
||||
DBSAVEINTERVAL = reader.GetInteger("shard", "dbsaveinterval", DBSAVEINTERVAL);
|
||||
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
|
||||
VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE);
|
||||
SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);
|
||||
SPAWN_X = reader.GetInteger("shard", "spawnx", SPAWN_X);
|
||||
SPAWN_Y = reader.GetInteger("shard", "spawny", SPAWN_Y);
|
||||
SPAWN_Z = reader.GetInteger("shard", "spawnz", SPAWN_Z);
|
||||
GMPASS = reader.Get("login", "pass", GMPASS);
|
||||
SPAWN_ANGLE = reader.GetInteger("shard", "spawnangle", SPAWN_ANGLE);
|
||||
NPCJSON = reader.Get("shard", "npcdata", NPCJSON);
|
||||
WARPJSON = reader.Get("shard", "warpdata", WARPJSON);
|
||||
XDTJSON = reader.Get("shard", "xdtdata", XDTJSON);
|
||||
MOBJSON = reader.Get("shard", "mobdata", MOBJSON);
|
||||
DROPSJSON = reader.Get("shard", "dropdata", DROPSJSON);
|
||||
PATHJSON = reader.Get("shard", "pathdata", PATHJSON);
|
||||
GRUNTWORKJSON = reader.Get("shard", "gruntwork", GRUNTWORKJSON);
|
||||
MOTDSTRING = reader.Get("shard", "motd", MOTDSTRING);
|
||||
GM = reader.GetBoolean("shard", "gm", GM);
|
||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||
EVENTCRATECHANCE = reader.GetInteger("shard", "eventcratechance", EVENTCRATECHANCE);
|
||||
}
|
||||
|
@@ -3,21 +3,27 @@
|
||||
namespace settings {
|
||||
extern int VERBOSITY;
|
||||
extern int LOGINPORT;
|
||||
extern bool LOGINRANDCHARACTERS;
|
||||
extern bool APPROVEALLNAMES;
|
||||
extern int DBSAVEINTERVAL;
|
||||
extern int SHARDPORT;
|
||||
extern std::string SHARDSERVERIP;
|
||||
extern int PLAYERDISTANCE;
|
||||
extern int NPCDISTANCE;
|
||||
extern time_t TIMEOUT;
|
||||
extern int VIEWDISTANCE;
|
||||
extern bool SIMULATEMOBS;
|
||||
extern int SPAWN_X;
|
||||
extern int SPAWN_Y;
|
||||
extern int SPAWN_Z;
|
||||
extern int SPAWN_ANGLE;
|
||||
extern int ACCLEVEL;
|
||||
extern std::string MOTDSTRING;
|
||||
extern std::string NPCJSON;
|
||||
extern std::string WARPJSON;
|
||||
extern std::string XDTJSON;
|
||||
extern std::string MOBJSON;
|
||||
extern std::string GMPASS;
|
||||
extern bool GM;
|
||||
extern std::string PATHJSON;
|
||||
extern std::string DROPSJSON;
|
||||
extern std::string GRUNTWORKJSON;
|
||||
extern int EVENTMODE;
|
||||
extern int EVENTCRATECHANCE;
|
||||
|
||||
void init();
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
#define AEQUIP_COUNT 9
|
||||
#define AINVEN_COUNT 50
|
||||
#define AQINVEN_COUNT 50
|
||||
#define ABANK_COUNT 119
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
#define AEQUIP_COUNT 12
|
||||
#define AINVEN_COUNT 50
|
||||
#define AQINVEN_COUNT 50
|
||||
#define ABANK_COUNT 200
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
|
8
suppr.txt
Normal file
8
suppr.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
leak:TableData::init
|
||||
leak:ChunkManager::addPlayer
|
||||
leak:ChunkManager::addNPC
|
||||
leak:NPCManager::updateNPCPosition
|
||||
leak:NPCManager::npcSummonHandler
|
||||
leak:summonWCommand
|
||||
leak:TableData::loadGruntwork
|
||||
leak:nlohmann::basic_json
|
1
tdata
Submodule
1
tdata
Submodule
Submodule tdata added at aa4338202e
1
version.h.in
Normal file
1
version.h.in
Normal file
@@ -0,0 +1 @@
|
||||
#define GIT_VERSION "@GIT_VERSION@"
|
Reference in New Issue
Block a user