mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
Compare commits
88 Commits
bbaaa53df2
...
cd908666af
Author | SHA1 | Date | |
---|---|---|---|
|
cd908666af | ||
|
74588f2c77 | ||
|
829f75112c | ||
|
343668fbcd | ||
|
2e572169c0 | ||
|
b3e28ddea3 | ||
|
1670dfd830 | ||
|
e768ebcabe | ||
|
9bc7e8de62 | ||
|
f249599ab5 | ||
|
2901f5f285 | ||
|
36f329c302 | ||
|
eb7daf8eaa | ||
|
0c5a9400ce | ||
|
f150595f70 | ||
|
c6528eb2ac | ||
|
d631ca1aa1 | ||
|
a94fb0ed6d | ||
|
d48aa21135 | ||
|
c60c4dac38 | ||
|
215da0130d | ||
|
0e8a4742eb | ||
|
85bb4d163e | ||
|
09b74a5711 | ||
|
90819bea8e | ||
|
3cc5c09a91 | ||
|
536d5fbcfa | ||
|
4ec3a3acb7 | ||
|
89ed0b99a3 | ||
|
db73b85bc8 | ||
|
6cab203401 | ||
|
92846e0eac | ||
|
5963ea06be | ||
|
c28970f2e1 | ||
|
8be853c2dc | ||
|
a811e73fed | ||
|
a58971c270 | ||
|
07429a0e51 | ||
|
7c9038cf10 | ||
|
c1e391d86a | ||
|
e23af08838 | ||
|
8a26ae2f01 | ||
|
d17694e12e | ||
|
4b612f35d2 | ||
|
d9e0a4a281 | ||
|
dd9891f668 | ||
|
962141e54f | ||
bea41132b4 | |||
f305d7252d | |||
ff62129eec | |||
55f3ab8bad | |||
|
a732cde117 | ||
|
703eaff2b4 | ||
|
f4f5f2e0bd | ||
|
1858938280 | ||
|
0af8f7e91d | ||
|
168a85e8ff | ||
|
b12aecad63 | ||
|
5bf0c8f3ea | ||
|
2ddc956c9b | ||
|
4f0ae027a5 | ||
23ab908366 | |||
be6a4c0a5d | |||
8eb1af20c8 | |||
e73daa0865 | |||
743a39c125 | |||
a9af8713bc | |||
4825267537 | |||
a92cfaff25 | |||
abcfa3445b | |||
2bf14200f7 | |||
876a9c82cd | |||
fb5b0eeeb9 | |||
7aabc507e7 | |||
2914b95cff | |||
dbd2ec2270 | |||
50e00a6772 | |||
7471bcbf38 | |||
100b4605ec | |||
741b898230 | |||
3f44f53f97 | |||
d92b407349 | |||
9b3e856a05 | |||
eb8e54c1f0 | |||
1ba0f5e14a | |||
12dde394c0 | |||
b1eea6d4fe | |||
f126b88781 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ build/
|
|||||||
version.h
|
version.h
|
||||||
infer-out
|
infer-out
|
||||||
gmon.out
|
gmon.out
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug (Linux)",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/fusion",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug (Windows)",
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Release (Windows)",
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -3,7 +3,8 @@ project(OpenFusion)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
|
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
# OpenFusion supports multiple packet/struct versions
|
# OpenFusion supports multiple packet/struct versions
|
||||||
# 104 is the default version to build which can be changed
|
# 104 is the default version to build which can be changed
|
||||||
@ -44,7 +45,7 @@ add_executable(openfusion ${SOURCES})
|
|||||||
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||||
|
|
||||||
# find sqlite3 and use it
|
# find sqlite3 and use it
|
||||||
find_package(sqlite3 REQUIRED)
|
find_package(SQLite3 REQUIRED)
|
||||||
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
||||||
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
||||||
|
|
||||||
@ -56,5 +57,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S
|
|||||||
# It's not something you should do, but it's there if you need it...
|
# It's not something you should do, but it's there if you need it...
|
||||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
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)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(openfusion pthread)
|
target_link_libraries(openfusion PRIVATE pthread)
|
||||||
endif()
|
endif()
|
||||||
|
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM debian:latest
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
RUN apt-get -y update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
clang \
|
||||||
|
make \
|
||||||
|
libsqlite3-dev
|
||||||
|
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
RUN make -j8
|
||||||
|
|
||||||
|
# tabledata should be copied from the host;
|
||||||
|
# clone it there before building the container
|
||||||
|
#RUN git submodule update --init --recursive
|
||||||
|
|
||||||
|
CMD ["./bin/fusion"]
|
||||||
|
|
||||||
|
LABEL Name=openfusion Version=0.0.1
|
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2022 OpenFusion Contributors
|
Copyright (c) 2020-2023 OpenFusion Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
2
Makefile
2
Makefile
@ -50,6 +50,7 @@ CXXSRC=\
|
|||||||
src/db/email.cpp\
|
src/db/email.cpp\
|
||||||
src/sandbox/seccomp.cpp\
|
src/sandbox/seccomp.cpp\
|
||||||
src/sandbox/openbsd.cpp\
|
src/sandbox/openbsd.cpp\
|
||||||
|
src/Buffs.cpp\
|
||||||
src/Chat.cpp\
|
src/Chat.cpp\
|
||||||
src/CustomCommands.cpp\
|
src/CustomCommands.cpp\
|
||||||
src/Entities.cpp\
|
src/Entities.cpp\
|
||||||
@ -96,6 +97,7 @@ CXXHDR=\
|
|||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
vendor/INIReader.hpp\
|
vendor/INIReader.hpp\
|
||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
|
src/Buffs.hpp\
|
||||||
src/Chat.hpp\
|
src/Chat.hpp\
|
||||||
src/CustomCommands.hpp\
|
src/CustomCommands.hpp\
|
||||||
src/Entities.hpp\
|
src/Entities.hpp\
|
||||||
|
@ -25,6 +25,8 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
|||||||
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||||
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
||||||
|
|
||||||
|
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
|
||||||
|
|
||||||
### Hosting a server
|
### Hosting a server
|
||||||
|
|
||||||
1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
|
1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
|
||||||
@ -35,7 +37,7 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
|||||||
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
||||||
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||||
|
|
||||||
If you want, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||||
|
|
||||||
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ This just works if you're all under the same LAN, but if you want to play over t
|
|||||||
|
|
||||||
## Compiling
|
## Compiling
|
||||||
|
|
||||||
OpenFusion has one external dependency: SQLite. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||||
|
|
||||||
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.
|
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.
|
||||||
|
|
||||||
|
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
openfusion:
|
||||||
|
image: openfusion
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
ports:
|
||||||
|
- "23000:23000"
|
||||||
|
- "23001:23001"
|
||||||
|
- "8003:8003"
|
1090
src/Abilities.cpp
1090
src/Abilities.cpp
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,68 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Combat.hpp"
|
|
||||||
|
|
||||||
typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
#include "Entities.hpp"
|
||||||
|
#include "Player.hpp"
|
||||||
|
|
||||||
struct NanoPower {
|
#include <map>
|
||||||
int16_t skillType;
|
#include <vector>
|
||||||
int32_t bitFlag;
|
|
||||||
int16_t timeBuffID;
|
|
||||||
PowerHandler handler;
|
|
||||||
|
|
||||||
NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {}
|
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||||
|
|
||||||
void handle(CNSocket *sock, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) {
|
enum class SkillEffectTarget {
|
||||||
if (handler == nullptr)
|
POINT = 1,
|
||||||
return;
|
SELF = 2,
|
||||||
|
CONE = 3,
|
||||||
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
WEAPON = 4,
|
||||||
}
|
AREA_SELF = 5,
|
||||||
|
AREA_TARGET = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*MobPowerHandler)(Mob*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
enum class SkillTargetType {
|
||||||
|
MOBS = 1,
|
||||||
|
PLAYERS = 2,
|
||||||
|
GROUP = 3
|
||||||
|
};
|
||||||
|
|
||||||
struct MobPower {
|
enum class SkillDrainType {
|
||||||
int16_t skillType;
|
ACTIVE = 1,
|
||||||
int32_t bitFlag;
|
PASSIVE = 2
|
||||||
int16_t timeBuffID;
|
};
|
||||||
MobPowerHandler handler;
|
|
||||||
|
|
||||||
MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {}
|
struct SkillResult {
|
||||||
|
size_t size;
|
||||||
void handle(Mob *mob, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) {
|
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||||
if (handler == nullptr)
|
SkillResult(size_t len, void* dat) {
|
||||||
return;
|
size = len;
|
||||||
|
memcpy(payload, dat, len);
|
||||||
handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
}
|
||||||
|
SkillResult() {
|
||||||
|
size = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SkillData {
|
struct SkillData {
|
||||||
int skillType;
|
int skillType; // eST
|
||||||
int targetType;
|
SkillEffectTarget effectTarget;
|
||||||
int drainType;
|
int effectType; // always 1?
|
||||||
|
SkillTargetType targetType;
|
||||||
|
SkillDrainType drainType;
|
||||||
int effectArea;
|
int effectArea;
|
||||||
|
|
||||||
int batteryUse[4];
|
int batteryUse[4];
|
||||||
int durationTime[4];
|
int durationTime[4];
|
||||||
int powerIntensity[4];
|
|
||||||
|
int valueTypes[3];
|
||||||
|
int values[3][4];
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Nanos {
|
namespace Abilities {
|
||||||
extern std::vector<NanoPower> NanoPowers;
|
|
||||||
extern std::map<int32_t, SkillData> SkillTable;
|
extern std::map<int32_t, SkillData> SkillTable;
|
||||||
|
|
||||||
void nanoUnbuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower);
|
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||||
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags);
|
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||||
|
|
||||||
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr);
|
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||||
}
|
int getCSTBFromST(int eSkillType);
|
||||||
|
|
||||||
namespace Combat {
|
|
||||||
extern std::vector<MobPower> MobPowers;
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Buddies.hpp"
|
#include "Buddies.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Buddies.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include "db/Database.hpp"
|
||||||
#include <chrono>
|
#include "servers/CNShardServer.hpp"
|
||||||
#include <algorithm>
|
|
||||||
#include <thread>
|
#include "Player.hpp"
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
|
||||||
using namespace Buddies;
|
using namespace Buddies;
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "core/Core.hpp"
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
namespace Buddies {
|
namespace Buddies {
|
||||||
|
151
src/Buffs.cpp
Normal file
151
src/Buffs.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
|
|
||||||
|
using namespace Buffs;
|
||||||
|
|
||||||
|
void Buff::tick(time_t currTime) {
|
||||||
|
auto it = stacks.begin();
|
||||||
|
while(it != stacks.end()) {
|
||||||
|
BuffStack& stack = *it;
|
||||||
|
//if(onTick) onTick(self, this, currTime);
|
||||||
|
|
||||||
|
if(stack.durationTicks == 0) {
|
||||||
|
BuffStack deadStack = stack;
|
||||||
|
it = stacks.erase(it);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||||
|
} else {
|
||||||
|
if(stack.durationTicks > 0) stack.durationTicks--;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::combatTick(time_t currTime) {
|
||||||
|
if(onCombatTick) onCombatTick(self, this, currTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::clear() {
|
||||||
|
while(!stacks.empty()) {
|
||||||
|
BuffStack stack = stacks.back();
|
||||||
|
stacks.pop_back();
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::clear(BuffClass buffClass) {
|
||||||
|
auto it = stacks.begin();
|
||||||
|
while(it != stacks.end()) {
|
||||||
|
BuffStack& stack = *it;
|
||||||
|
if(stack.buffStackClass == buffClass) {
|
||||||
|
BuffStack deadStack = stack;
|
||||||
|
it = stacks.erase(it);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||||
|
} else it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::addStack(BuffStack* stack) {
|
||||||
|
stacks.push_back(*stack);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buff::hasClass(BuffClass buffClass) {
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
if(stack.buffStackClass == buffClass)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuffClass Buff::maxClass() {
|
||||||
|
BuffClass buffClass = BuffClass::NONE;
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
if(stack.buffStackClass > buffClass)
|
||||||
|
buffClass = stack.buffStackClass;
|
||||||
|
}
|
||||||
|
return buffClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Buff::getValue(BuffValueSelector selector) {
|
||||||
|
if(isStale()) return 0;
|
||||||
|
|
||||||
|
int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value;
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
switch(selector)
|
||||||
|
{
|
||||||
|
case BuffValueSelector::NET_TOTAL:
|
||||||
|
value += stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MIN_VALUE:
|
||||||
|
if(stack.value < value) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MAX_VALUE:
|
||||||
|
if(stack.value > value) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MIN_MAGNITUDE:
|
||||||
|
if(abs(stack.value) < abs(value)) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MAX_MAGNITUDE:
|
||||||
|
default:
|
||||||
|
if(abs(stack.value) > abs(value)) value = stack.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buff::isStale() {
|
||||||
|
return stacks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This will practically never do anything important, but it's here just in case */
|
||||||
|
void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) {
|
||||||
|
if(!onUpdate) onUpdate = fOnUpdate;
|
||||||
|
if(!onCombatTick) onCombatTick = fOnCombatTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma region Handlers
|
||||||
|
void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
|
||||||
|
if(self.kind != EntityKind::PLAYER)
|
||||||
|
return; // not implemented
|
||||||
|
|
||||||
|
Player* plr = (Player*)self.getEntity();
|
||||||
|
if(plr == nullptr)
|
||||||
|
return; // sanity check
|
||||||
|
|
||||||
|
if(status == ETBU_DEL && !buff->isStale())
|
||||||
|
return; // no premature effect deletion
|
||||||
|
|
||||||
|
int cbf = plr->getCompositeCondition();
|
||||||
|
sTimeBuff payload{};
|
||||||
|
if(status == ETBU_ADD) {
|
||||||
|
payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE);
|
||||||
|
// we need to explicitly add the ECSB for this buff,
|
||||||
|
// in case this is the first stack in and the entry
|
||||||
|
// in the buff map doesn't yet exist
|
||||||
|
if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||||
|
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
||||||
|
pkt.eTBU = status; // eTimeBuffUpdate
|
||||||
|
pkt.eTBT = (int)stack->buffStackClass;
|
||||||
|
pkt.iConditionBitFlag = cbf;
|
||||||
|
pkt.TimeBuff = payload;
|
||||||
|
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||||
|
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not a combatant
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||||
|
pkt.eCT = combatant->getCharType();
|
||||||
|
pkt.iID = combatant->getID();
|
||||||
|
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||||
|
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
|
}
|
||||||
|
#pragma endregion
|
90
src/Buffs.hpp
Normal file
90
src/Buffs.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
template<class... Types>
|
||||||
|
using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>;
|
||||||
|
|
||||||
|
#define CSB_FROM_ECSB(x) (1 << (x - 1))
|
||||||
|
|
||||||
|
enum class BuffClass {
|
||||||
|
NONE = ETBT_NONE,
|
||||||
|
NANO = ETBT_NANO,
|
||||||
|
GROUP_NANO = ETBT_GROUPNANO,
|
||||||
|
EGG = ETBT_SHINY,
|
||||||
|
ENVIRONMENT = ETBT_LANDEFFECT,
|
||||||
|
ITEM = ETBT_ITEM,
|
||||||
|
CASH_ITEM = ETBT_CASHITEM
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BuffValueSelector {
|
||||||
|
MAX_VALUE,
|
||||||
|
MIN_VALUE,
|
||||||
|
MAX_MAGNITUDE,
|
||||||
|
MIN_MAGNITUDE,
|
||||||
|
NET_TOTAL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuffStack {
|
||||||
|
int durationTicks;
|
||||||
|
int value;
|
||||||
|
EntityRef source;
|
||||||
|
BuffClass buffStackClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Buff {
|
||||||
|
private:
|
||||||
|
EntityRef self;
|
||||||
|
std::vector<BuffStack> stacks;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int id;
|
||||||
|
/* called just after a stack is added or removed */
|
||||||
|
BuffCallback<int, BuffStack*> onUpdate;
|
||||||
|
/* called when the buff is combat-ticked */
|
||||||
|
BuffCallback<time_t> onCombatTick;
|
||||||
|
|
||||||
|
void tick(time_t);
|
||||||
|
void combatTick(time_t);
|
||||||
|
void clear();
|
||||||
|
void clear(BuffClass buffClass);
|
||||||
|
void addStack(BuffStack* stack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sometimes we need to determine if a buff
|
||||||
|
* is covered by a certain class, ex: nano
|
||||||
|
* vs. coco egg in the case of infection protection
|
||||||
|
*/
|
||||||
|
bool hasClass(BuffClass buffClass);
|
||||||
|
BuffClass maxClass();
|
||||||
|
|
||||||
|
int getValue(BuffValueSelector selector);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In general, a Buff object won't exist
|
||||||
|
* unless it has stacks. However, when
|
||||||
|
* popping stacks during iteration (onExpire),
|
||||||
|
* stacks will be empty for a brief moment
|
||||||
|
* when the last stack is popped.
|
||||||
|
*/
|
||||||
|
bool isStale();
|
||||||
|
|
||||||
|
void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick);
|
||||||
|
|
||||||
|
Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack)
|
||||||
|
: self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) {
|
||||||
|
addStack(firstStack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Buffs {
|
||||||
|
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||||
|
void timeBuffTimeout(EntityRef self);
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
#include "BuiltinCommands.hpp"
|
#include "BuiltinCommands.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Chat.hpp"
|
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Chat.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Rand.hpp"
|
|
||||||
|
|
||||||
// helper function, not a packet handler
|
// helper function, not a packet handler
|
||||||
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
||||||
@ -247,17 +250,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
|||||||
uint64_t instance = plr->instanceID;
|
uint64_t instance = plr->instanceID;
|
||||||
const int unstickRange = 400;
|
const int unstickRange = 400;
|
||||||
|
|
||||||
switch (req->eTeleportType) {
|
switch ((eCN_GM_TeleportType)req->eTeleportType) {
|
||||||
case eCN_GM_TeleportMapType__MyLocation:
|
case eCN_GM_TeleportType::MyLocation:
|
||||||
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__MapXYZ:
|
case eCN_GM_TeleportType::MapXYZ:
|
||||||
instance = req->iToMap;
|
instance = req->iToMap;
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case eCN_GM_TeleportMapType__XYZ:
|
case eCN_GM_TeleportType::XYZ:
|
||||||
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__SomeoneLocation:
|
case eCN_GM_TeleportType::SomeoneLocation:
|
||||||
// player to teleport to
|
// player to teleport to
|
||||||
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
||||||
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
||||||
@ -269,7 +272,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__Unstick:
|
case eCN_GM_TeleportType::Unstick:
|
||||||
targetPlr = PlayerManager::getPlayer(targetSock);
|
targetPlr = PlayerManager::getPlayer(targetSock);
|
||||||
|
|
||||||
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),
|
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),
|
||||||
|
22
src/Chat.cpp
22
src/Chat.cpp
@ -1,6 +1,9 @@
|
|||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "CustomCommands.hpp"
|
#include "CustomCommands.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -225,10 +228,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
|
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* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||||
|
|
||||||
@ -251,16 +250,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp.iSendPCID = plr->iID;
|
resp.iSendPCID = plr->iID;
|
||||||
resp.iEmoteCode = chat->iEmoteCode;
|
resp.iEmoteCode = chat->iEmoteCode;
|
||||||
|
|
||||||
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
if (plr->group == nullptr)
|
||||||
|
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||||
|
else
|
||||||
|
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
|
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* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||||
@ -275,7 +273,9 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp.iSendPCID = plr->iID;
|
resp.iSendPCID = plr->iID;
|
||||||
resp.iEmoteCode = chat->iEmoteCode;
|
resp.iEmoteCode = chat->iEmoteCode;
|
||||||
|
|
||||||
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
if (plr->group == nullptr)
|
||||||
|
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||||
|
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only allow plain ascii, at least for now
|
// we only allow plain ascii, at least for now
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
#define CMD_PREFIX '/'
|
#define CMD_PREFIX '/'
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Chat {
|
namespace Chat {
|
||||||
extern std::vector<std::string> dump;
|
extern std::vector<std::string> dump;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include "Chunking.hpp"
|
#include "Chunking.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
|
#include "MobAI.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "settings.hpp"
|
|
||||||
#include "Combat.hpp"
|
#include <assert.h>
|
||||||
#include "Eggs.hpp"
|
|
||||||
|
|
||||||
using namespace Chunking;
|
using namespace Chunking;
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ static void newChunk(ChunkPos pos) {
|
|||||||
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
||||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||||
for (Chunk* c : surroundings)
|
for (Chunk* c : surroundings)
|
||||||
for (const EntityRef& ref : c->entities)
|
for (const EntityRef ref : c->entities)
|
||||||
ref.getEntity()->viewableChunks.insert(chunk);
|
ref.getEntity()->viewableChunks.insert(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,24 +41,24 @@ static void deleteChunk(ChunkPos pos) {
|
|||||||
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
||||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||||
for(Chunk* c : surroundings)
|
for(Chunk* c : surroundings)
|
||||||
for (const EntityRef& ref : c->entities)
|
for (const EntityRef ref : c->entities)
|
||||||
ref.getEntity()->viewableChunks.erase(chunk);
|
ref.getEntity()->viewableChunks.erase(chunk);
|
||||||
|
|
||||||
chunks.erase(pos); // remove from map
|
chunks.erase(pos); // remove from map
|
||||||
delete chunk; // free from memory
|
delete chunk; // free from memory
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||||
if (!chunkExists(chunkPos))
|
if (!chunkExists(chunkPos))
|
||||||
return; // shouldn't happen
|
return; // shouldn't happen
|
||||||
|
|
||||||
chunks[chunkPos]->entities.insert(ref);
|
chunks[chunkPos]->entities.insert(ref);
|
||||||
|
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
chunks[chunkPos]->nplayers++;
|
chunks[chunkPos]->nplayers++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||||
if (!chunkExists(chunkPos))
|
if (!chunkExists(chunkPos))
|
||||||
return; // do nothing if chunk doesn't even exist
|
return; // do nothing if chunk doesn't even exist
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
|||||||
|
|
||||||
chunk->entities.erase(ref); // gone
|
chunk->entities.erase(ref); // gone
|
||||||
|
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
chunks[chunkPos]->nplayers--;
|
chunks[chunkPos]->nplayers--;
|
||||||
assert(chunks[chunkPos]->nplayers >= 0);
|
assert(chunks[chunkPos]->nplayers >= 0);
|
||||||
|
|
||||||
@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
|||||||
deleteChunk(chunkPos);
|
deleteChunk(chunkPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||||
Entity *ent = ref.getEntity();
|
Entity *ent = ref.getEntity();
|
||||||
bool alive = ent->isAlive();
|
bool alive = ent->isExtant();
|
||||||
|
|
||||||
// TODO: maybe optimize this, potentially using AROUND packets?
|
// TODO: maybe optimize this, potentially using AROUND packets?
|
||||||
for (Chunk *chunk : chnks) {
|
for (Chunk *chunk : chnks) {
|
||||||
for (const EntityRef& otherRef : chunk->entities) {
|
for (const EntityRef otherRef : chunk->entities) {
|
||||||
// skip oneself
|
// skip oneself
|
||||||
if (ref == otherRef)
|
if (ref == otherRef)
|
||||||
continue;
|
continue;
|
||||||
@ -89,31 +89,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
|||||||
Entity *other = otherRef.getEntity();
|
Entity *other = otherRef.getEntity();
|
||||||
|
|
||||||
// notify all visible players of the existence of this Entity
|
// notify all visible players of the existence of this Entity
|
||||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||||
ent->enterIntoViewOf(otherRef.sock);
|
ent->enterIntoViewOf(otherRef.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify this *player* of the existence of all visible Entities
|
// notify this *player* of the existence of all visible Entities
|
||||||
if (ref.type == EntityType::PLAYER && other->isAlive()) {
|
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||||
other->enterIntoViewOf(ref.sock);
|
other->enterIntoViewOf(ref.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for mobs, increment playersInView
|
// for mobs, increment playersInView
|
||||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||||
((Mob*)ent)->playersInView++;
|
((Mob*)ent)->playersInView++;
|
||||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||||
((Mob*)other)->playersInView++;
|
((Mob*)other)->playersInView++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
|
||||||
Entity *ent = ref.getEntity();
|
Entity *ent = ref.getEntity();
|
||||||
bool alive = ent->isAlive();
|
bool alive = ent->isExtant();
|
||||||
|
|
||||||
// TODO: same as above
|
// TODO: same as above
|
||||||
for (Chunk *chunk : chnks) {
|
for (Chunk *chunk : chnks) {
|
||||||
for (const EntityRef& otherRef : chunk->entities) {
|
for (const EntityRef otherRef : chunk->entities) {
|
||||||
// skip oneself
|
// skip oneself
|
||||||
if (ref == otherRef)
|
if (ref == otherRef)
|
||||||
continue;
|
continue;
|
||||||
@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r
|
|||||||
Entity *other = otherRef.getEntity();
|
Entity *other = otherRef.getEntity();
|
||||||
|
|
||||||
// notify all visible players of the departure of this Entity
|
// notify all visible players of the departure of this Entity
|
||||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||||
ent->disappearFromViewOf(otherRef.sock);
|
ent->disappearFromViewOf(otherRef.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify this *player* of the departure of all visible Entities
|
// notify this *player* of the departure of all visible Entities
|
||||||
if (ref.type == EntityType::PLAYER && other->isAlive()) {
|
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||||
other->disappearFromViewOf(ref.sock);
|
other->disappearFromViewOf(ref.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for mobs, decrement playersInView
|
// for mobs, decrement playersInView
|
||||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||||
((Mob*)ent)->playersInView--;
|
((Mob*)ent)->playersInView--;
|
||||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||||
((Mob*)other)->playersInView--;
|
((Mob*)other)->playersInView--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,8 +154,8 @@ static void emptyChunk(ChunkPos chunkPos) {
|
|||||||
|
|
||||||
// unspawn all of the mobs/npcs
|
// unspawn all of the mobs/npcs
|
||||||
std::set refs(chunk->entities);
|
std::set refs(chunk->entities);
|
||||||
for (const EntityRef& ref : refs) {
|
for (const EntityRef ref : refs) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
assert(0);
|
assert(0);
|
||||||
|
|
||||||
// every call of this will check if the chunk is empty and delete it if so
|
// every call of this will check if the chunk is empty and delete it if so
|
||||||
@ -163,7 +163,7 @@ static void emptyChunk(ChunkPos chunkPos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) {
|
void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) {
|
||||||
Entity* ent = ref.getEntity();
|
Entity* ent = ref.getEntity();
|
||||||
|
|
||||||
// move to other chunk's player set
|
// move to other chunk's player set
|
||||||
@ -267,54 +267,53 @@ void Chunking::createInstance(uint64_t instanceID) {
|
|||||||
|
|
||||||
std::cout << "Creating instance " << instanceID << std::endl;
|
std::cout << "Creating instance " << instanceID << std::endl;
|
||||||
for (ChunkPos &coords : templateChunks) {
|
for (ChunkPos &coords : templateChunks) {
|
||||||
for (const EntityRef& ref : chunks[coords]->entities) {
|
for (const EntityRef ref : chunks[coords]->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int npcID = ref.id;
|
int npcID = ref.id;
|
||||||
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
||||||
|
|
||||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||||
if (baseNPC->type == EntityType::MOB) {
|
if (baseNPC->kind == EntityKind::MOB) {
|
||||||
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
||||||
continue; // follower; don't copy individually
|
continue; // follower; don't copy individually
|
||||||
|
|
||||||
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
|
||||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--);
|
instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
|
||||||
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob;
|
NPCManager::NPCs[newMob->id] = newMob;
|
||||||
|
|
||||||
// if in a group, copy over group members as well
|
// if in a group, copy over group members as well
|
||||||
if (((Mob*)baseNPC)->groupLeader != 0) {
|
if (((Mob*)baseNPC)->groupLeader != 0) {
|
||||||
newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader
|
newMob->groupLeader = newMob->id; // set leader ID for new leader
|
||||||
Mob* mobData = (Mob*)baseNPC;
|
Mob* mobData = (Mob*)baseNPC;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (mobData->groupMember[i] != 0) {
|
if (mobData->groupMember[i] != 0) {
|
||||||
int followerID = NPCManager::nextId--; // id for follower
|
int followerID = NPCManager::nextId--; // id for follower
|
||||||
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
||||||
// new follower instance
|
// new follower instance
|
||||||
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle,
|
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
|
||||||
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID);
|
instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
|
||||||
// add follower to NPC maps
|
// add follower to NPC maps
|
||||||
NPCManager::NPCs[followerID] = newMobFollower;
|
NPCManager::NPCs[followerID] = newMobFollower;
|
||||||
// set follower-specific properties
|
// set follower-specific properties
|
||||||
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID;
|
newMobFollower->groupLeader = newMob->id;
|
||||||
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
||||||
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
||||||
// add follower copy to leader copy
|
// add follower copy to leader copy
|
||||||
newMob->groupMember[i] = followerID;
|
newMob->groupMember[i] = followerID;
|
||||||
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
||||||
instanceID, baseFollower->appearanceData.iAngle);
|
instanceID, baseFollower->angle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z,
|
NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||||
instanceID, baseNPC->appearanceData.iAngle);
|
instanceID, baseNPC->angle);
|
||||||
} else {
|
} else {
|
||||||
BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
|
||||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--);
|
NPCManager::NPCs[newNPC->id] = newNPC;
|
||||||
NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC;
|
NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||||
NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z,
|
instanceID, baseNPC->angle);
|
||||||
instanceID, baseNPC->appearanceData.iAngle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <tuple>
|
#include <vector>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
struct EntityRef;
|
|
||||||
|
|
||||||
class Chunk {
|
class Chunk {
|
||||||
public:
|
public:
|
||||||
@ -36,13 +32,13 @@ namespace Chunking {
|
|||||||
|
|
||||||
extern const ChunkPos INVALID_CHUNK;
|
extern const ChunkPos INVALID_CHUNK;
|
||||||
|
|
||||||
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to);
|
void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to);
|
||||||
|
|
||||||
void trackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
void trackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||||
void untrackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||||
|
|
||||||
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref);
|
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||||
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref);
|
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||||
|
|
||||||
bool chunkExists(ChunkPos chunk);
|
bool chunkExists(ChunkPos chunk);
|
||||||
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
||||||
|
663
src/Combat.cpp
663
src/Combat.cpp
@ -1,22 +1,270 @@
|
|||||||
#include "Combat.hpp"
|
#include "Combat.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Nanos.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Missions.hpp"
|
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Racing.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
#include "Player.hpp"
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
|
#include "Nanos.hpp"
|
||||||
|
#include "Abilities.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
using namespace Combat;
|
using namespace Combat;
|
||||||
|
|
||||||
/// Player Id -> Bullet Id -> Bullet
|
/// Player Id -> Bullet Id -> Bullet
|
||||||
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
||||||
|
|
||||||
|
#pragma region Player
|
||||||
|
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||||
|
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||||
|
|
||||||
|
if(!hasBuff(buffId)) {
|
||||||
|
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||||
|
buffs[buffId]->addStack(stack);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buff* Player::getBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
return buffs[buffId];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::removeBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear();
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::removeBuff(int buffId, int buffClass) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear((BuffClass)buffClass);
|
||||||
|
if(buffs[buffId]->isStale()) {
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Player::hasBuff(int buffId) {
|
||||||
|
auto buff = buffs.find(buffId);
|
||||||
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getCompositeCondition() {
|
||||||
|
int conditionBitFlag = 0;
|
||||||
|
for(auto buff : buffs) {
|
||||||
|
if(!buff.second->isStale() && buff.second->id > 0)
|
||||||
|
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
||||||
|
}
|
||||||
|
return conditionBitFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::takeDamage(EntityRef src, int amt) {
|
||||||
|
int dmg = amt;
|
||||||
|
if(HP - dmg < 0) dmg = HP;
|
||||||
|
HP -= dmg;
|
||||||
|
|
||||||
|
return dmg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::heal(EntityRef src, int amt) {
|
||||||
|
int heal = amt;
|
||||||
|
if(HP + heal > getMaxHP()) heal = getMaxHP() - HP;
|
||||||
|
HP += heal;
|
||||||
|
|
||||||
|
return heal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Player::isAlive() {
|
||||||
|
return HP > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getCurrentHP() {
|
||||||
|
return HP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getMaxHP() {
|
||||||
|
return PC_MAXHEALTH(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EntityRef> Player::getGroupMembers() {
|
||||||
|
std::vector<EntityRef> members;
|
||||||
|
if(group != nullptr)
|
||||||
|
members = group->members;
|
||||||
|
else
|
||||||
|
members.push_back(PlayerManager::getSockFromID(iID));
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Player::getCharType() {
|
||||||
|
return 1; // eCharType (eCT_PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Player::getID() {
|
||||||
|
return iID;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityRef Player::getRef() {
|
||||||
|
return EntityRef(PlayerManager::getSockFromID(iID));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::step(time_t currTime) {
|
||||||
|
CNSocket* sock = getRef().sock;
|
||||||
|
|
||||||
|
// nanos
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (activeNano != 0 && equippedNanos[i] == activeNano) { // tick active nano
|
||||||
|
sNano& nano = Nanos[activeNano];
|
||||||
|
int drainRate = 0;
|
||||||
|
|
||||||
|
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
|
||||||
|
// nano has skill data
|
||||||
|
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
|
||||||
|
int boost = Nanos::getNanoBoost(this);
|
||||||
|
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||||
|
drainRate = skill->batteryUse[boost * 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
nano.iStamina -= 1 + drainRate / 5;
|
||||||
|
if (nano.iStamina <= 0)
|
||||||
|
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
||||||
|
|
||||||
|
} else if (Nanos[equippedNanos[i]].iStamina < 150) { // tick resting nano
|
||||||
|
sNano& nano = Nanos[equippedNanos[i]];
|
||||||
|
if (nano.iStamina < 150)
|
||||||
|
nano.iStamina += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffs
|
||||||
|
for(auto buffEntry : buffs) {
|
||||||
|
buffEntry.second->combatTick(currTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region CombatNPC
|
||||||
|
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
||||||
|
|
||||||
|
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getCompositeCondition() { /* stubbed */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||||
|
int dmg = amt;
|
||||||
|
if(hp - dmg < 0) dmg = hp;
|
||||||
|
hp -= dmg;
|
||||||
|
|
||||||
|
if(hp <= 0) transition(AIState::DEAD, src);
|
||||||
|
|
||||||
|
return dmg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::heal(EntityRef src, int amt) {
|
||||||
|
int heal = amt;
|
||||||
|
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
|
||||||
|
hp += heal;
|
||||||
|
|
||||||
|
return heal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CombatNPC::isAlive() {
|
||||||
|
return hp > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getCurrentHP() {
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getMaxHP() {
|
||||||
|
return maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EntityRef> CombatNPC::getGroupMembers() {
|
||||||
|
std::vector<EntityRef> members;
|
||||||
|
if(group != nullptr)
|
||||||
|
members = group->members;
|
||||||
|
else
|
||||||
|
members.push_back(id);
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CombatNPC::getCharType() {
|
||||||
|
if(kind == EntityKind::MOB)
|
||||||
|
return 4; // eCharType (eCT_MOB)
|
||||||
|
return 2; // eCharType (eCT_NPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CombatNPC::getID() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityRef CombatNPC::getRef() {
|
||||||
|
return EntityRef(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::step(time_t currTime) {
|
||||||
|
|
||||||
|
if(stateHandlers.find(state) != stateHandlers.end())
|
||||||
|
stateHandlers[state](this, currTime);
|
||||||
|
else {
|
||||||
|
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
|
||||||
|
transition(AIState::INACTIVE, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||||
|
|
||||||
|
state = newState;
|
||||||
|
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
||||||
|
transitionHandlers[newState](this, src);
|
||||||
|
else {
|
||||||
|
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
||||||
|
transition(AIState::INACTIVE, id);
|
||||||
|
}
|
||||||
|
/* TODO: fire any triggered events
|
||||||
|
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||||
|
if (event.trigger == ON_KILLED && event.npcType == type)
|
||||||
|
event.handler(src, this);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||||
bool batteryBoost, int attackerStyle,
|
bool batteryBoost, int attackerStyle,
|
||||||
int defenderStyle, int difficulty) {
|
int defenderStyle, int difficulty) {
|
||||||
@ -56,14 +304,10 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
||||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
auto targets = (int32_t*)data->trailers;
|
|
||||||
|
|
||||||
// rapid fire anti-cheat
|
|
||||||
// TODO: move this out of here, when generalizing packet frequency validation
|
|
||||||
time_t currTime = getTime();
|
time_t currTime = getTime();
|
||||||
|
|
||||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
if (currTime - plr->lastShot < plr->fireRate * 80)
|
||||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
||||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
||||||
@ -71,15 +315,28 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
plr->lastShot = currTime;
|
plr->lastShot = currTime;
|
||||||
|
|
||||||
if (pkt->iNPCCnt > 3) // 3+ targets should never be possible
|
// 3+ targets should never be possible
|
||||||
plr->suspicionRating += 10000;
|
if (targetCount > 3)
|
||||||
|
plr->suspicionRating += 10001;
|
||||||
|
|
||||||
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
|
// kill the socket when the player is too suspicious
|
||||||
|
if (plr->suspicionRating > 10000) {
|
||||||
sock->kill();
|
sock->kill();
|
||||||
CNShardServer::_killConnection(sock);
|
CNShardServer::_killConnection(sock);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||||
|
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
auto targets = (int32_t*)data->trailers;
|
||||||
|
|
||||||
|
// kick the player if firing too rapidly
|
||||||
|
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
|
||||||
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IMPORTANT: This validates memory safety in addition to preventing
|
* IMPORTANT: This validates memory safety in addition to preventing
|
||||||
@ -105,7 +362,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
||||||
if (npc->type != EntityType::MOB) {
|
if (npc->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,11 +385,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
else
|
else
|
||||||
plr->batteryW = 0;
|
plr->batteryW = 0;
|
||||||
|
|
||||||
damage.first = hitMob(sock, mob, damage.first);
|
damage.first = mob->takeDamage(sock, damage.first);
|
||||||
|
|
||||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
respdata[i].iID = mob->id;
|
||||||
respdata[i].iDamage = damage.first;
|
respdata[i].iDamage = damage.first;
|
||||||
respdata[i].iHP = mob->appearanceData.iHP;
|
respdata[i].iHP = mob->hp;
|
||||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +416,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
|
|||||||
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||||
plr->HP -= damage.first;
|
plr->HP -= damage.first;
|
||||||
|
|
||||||
pkt->iNPC_ID = mob->appearanceData.iNPC_ID;
|
pkt->iNPC_ID = mob->id;
|
||||||
pkt->iPCCnt = 1;
|
pkt->iPCCnt = 1;
|
||||||
|
|
||||||
atk->iID = plr->iID;
|
atk->iID = plr->iID;
|
||||||
@ -171,51 +428,10 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
|
|||||||
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
||||||
|
|
||||||
if (plr->HP <= 0) {
|
if (plr->HP <= 0) {
|
||||||
mob->target = nullptr;
|
if (!MobAI::aggroCheck(mob, getTime()))
|
||||||
mob->state = MobState::RETREAT;
|
mob->transition(AIState::RETREAT, mob->target);
|
||||||
if (!MobAI::aggroCheck(mob, currTime)) {
|
|
||||||
MobAI::clearDebuff(mob);
|
|
||||||
if (mob->groupLeader != 0)
|
|
||||||
MobAI::groupRetreat(mob);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
|
||||||
// cannot kill mobs multiple times; cannot harm retreating mobs
|
|
||||||
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) {
|
|
||||||
return 0; // no damage
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mob->skillStyle >= 0)
|
|
||||||
return 0; // don't hurt a mob casting corruption
|
|
||||||
|
|
||||||
if (mob->state == MobState::ROAMING) {
|
|
||||||
assert(mob->target == nullptr);
|
|
||||||
MobAI::enterCombat(sock, mob);
|
|
||||||
|
|
||||||
if (mob->groupLeader != 0)
|
|
||||||
MobAI::followToCombat(mob);
|
|
||||||
}
|
|
||||||
|
|
||||||
mob->appearanceData.iHP -= damage;
|
|
||||||
|
|
||||||
// wake up sleeping monster
|
|
||||||
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
|
|
||||||
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
|
||||||
pkt1.eCT = 2;
|
|
||||||
pkt1.iID = mob->appearanceData.iNPC_ID;
|
|
||||||
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
|
|
||||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mob->appearanceData.iHP <= 0)
|
|
||||||
killMob(mob->target, mob);
|
|
||||||
|
|
||||||
return damage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When a group of players is doing missions together, we want them to all get
|
* When a group of players is doing missions together, we want them to all get
|
||||||
@ -224,96 +440,16 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
|||||||
* single RNG roll per mission task, and every group member shares that same
|
* single RNG roll per mission task, and every group member shares that same
|
||||||
* set of rolls.
|
* set of rolls.
|
||||||
*/
|
*/
|
||||||
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) {
|
void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) {
|
||||||
for (int i = 0; i < leader->groupCnt; i++) {
|
for (int i = 0; i < players.size(); i++) {
|
||||||
if (leader->groupIDs[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
|
||||||
if (otherSock == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Player *member = PlayerManager::getPlayer(otherSock);
|
|
||||||
|
|
||||||
|
Player* member = players[i];
|
||||||
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
||||||
if (member->tasks[j] != 0)
|
if (member->tasks[j] != 0)
|
||||||
rolls[member->tasks[j]] = Rand::rand();
|
rolls[member->tasks[j]] = Rand::rand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Combat::killMob(CNSocket *sock, Mob *mob) {
|
|
||||||
mob->state = MobState::DEAD;
|
|
||||||
mob->target = nullptr;
|
|
||||||
mob->appearanceData.iConditionBitFlag = 0;
|
|
||||||
mob->skillStyle = -1;
|
|
||||||
mob->unbuffTimes.clear();
|
|
||||||
mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
|
||||||
|
|
||||||
// check for the edge case where hitting the mob did not aggro it
|
|
||||||
if (sock != nullptr) {
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
Items::DropRoll rolled;
|
|
||||||
Items::DropRoll eventRolled;
|
|
||||||
std::map<int, int> qitemRolls;
|
|
||||||
|
|
||||||
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
assert(leader != nullptr); // should never happen
|
|
||||||
|
|
||||||
genQItemRolls(leader, qitemRolls);
|
|
||||||
|
|
||||||
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
|
||||||
Items::giveMobDrop(sock, mob, rolled, eventRolled);
|
|
||||||
Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < leader->groupCnt; i++) {
|
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
|
||||||
if (sockTo == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Player *otherPlr = PlayerManager::getPlayer(sockTo);
|
|
||||||
|
|
||||||
// only contribute to group members' kills if they're close enough
|
|
||||||
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
|
|
||||||
if (dist > 5000)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
|
|
||||||
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delay the despawn animation
|
|
||||||
mob->despawned = false;
|
|
||||||
|
|
||||||
// fire any triggered events
|
|
||||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
|
||||||
if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType)
|
|
||||||
event.handler(sock, mob);
|
|
||||||
|
|
||||||
auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID);
|
|
||||||
if (it == Transport::NPCQueues.end() || it->second.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// rewind or empty the movement queue
|
|
||||||
if (mob->staticPath) {
|
|
||||||
/*
|
|
||||||
* This is inelegant, but we wind forward in the path until we find the point that
|
|
||||||
* corresponds with the Mob's spawn point.
|
|
||||||
*
|
|
||||||
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
|
|
||||||
*/
|
|
||||||
auto& queue = it->second;
|
|
||||||
for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) {
|
|
||||||
queue.pop();
|
|
||||||
queue.push(point);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void combatBegin(CNSocket *sock, CNPacketData *data) {
|
static void combatBegin(CNSocket *sock, CNPacketData *data) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
@ -336,40 +472,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
|||||||
plr->healCooldown = 4000;
|
plr->healCooldown = 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
static void dealGooDamage(CNSocket *sock) {
|
||||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
|
||||||
|
return; // ignore completely
|
||||||
|
|
||||||
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
|
|
||||||
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
|
||||||
|
|
||||||
pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID
|
|
||||||
pkt1.eTBU = 1; // eTimeBuffUpdate
|
|
||||||
pkt1.eTBT = 0; // eTimeBuffType 1 means nano
|
|
||||||
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
|
|
||||||
|
|
||||||
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dealGooDamage(CNSocket *sock, int amount) {
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||||
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) {
|
int amount = PC_MAXHEALTH(plr->level) * 3 / 20;
|
||||||
|
Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
|
||||||
|
if (protectionBuff != nullptr) {
|
||||||
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
||||||
dmg->bProtected = 1;
|
dmg->bProtected = 1;
|
||||||
|
|
||||||
// eggs allow protection without nanos
|
// eggs allow protection without nanos
|
||||||
if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION))
|
if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1)
|
||||||
plr->Nanos[plr->activeNano].iStamina -= 3;
|
plr->Nanos[plr->activeNano].iStamina -= 3;
|
||||||
} else {
|
} else {
|
||||||
plr->HP -= amount;
|
plr->HP -= amount;
|
||||||
@ -393,12 +516,39 @@ static void dealGooDamage(CNSocket *sock, int amount) {
|
|||||||
dmg->iID = plr->iID;
|
dmg->iID = plr->iID;
|
||||||
dmg->iDamage = amount;
|
dmg->iDamage = amount;
|
||||||
dmg->iHP = plr->HP;
|
dmg->iHP = plr->HP;
|
||||||
dmg->iConditionBitFlag = plr->iConditionBitFlag;
|
dmg->iConditionBitFlag = plr->getCompositeCondition();
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
||||||
|
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
|
// infection debuff toggles as the client asks it to,
|
||||||
|
// so we add and remove a permanent debuff
|
||||||
|
if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) {
|
||||||
|
BuffStack infection = {
|
||||||
|
-1, // infinite
|
||||||
|
0, // no value
|
||||||
|
sock, // self-inflicted
|
||||||
|
BuffClass::ENVIRONMENT
|
||||||
|
};
|
||||||
|
plr->addBuff(ECSB_INFECTION,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
if(self.kind == EntityKind::PLAYER)
|
||||||
|
dealGooDamage(self.sock);
|
||||||
|
},
|
||||||
|
&infection);
|
||||||
|
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
||||||
|
plr->removeBuff(ECSB_INFECTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
@ -408,12 +558,12 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
|
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
|
||||||
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) {
|
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) {
|
||||||
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
|
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
|
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
|
||||||
@ -432,11 +582,19 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
resp->iTargetCnt = pkt->iTargetCnt;
|
resp->iTargetCnt = pkt->iTargetCnt;
|
||||||
|
|
||||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
||||||
if (pktdata[i*2+1] == 1) { // eCT == 1; attack player
|
|
||||||
Player *target = nullptr;
|
ICombatant* target = nullptr;
|
||||||
|
std::pair<int, int> damage;
|
||||||
|
|
||||||
|
if (pkt->iTargetCnt > 1)
|
||||||
|
damage.first = plr->groupDamage;
|
||||||
|
else
|
||||||
|
damage.first = plr->pointDamage;
|
||||||
|
|
||||||
|
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
|
||||||
|
|
||||||
for (auto& pair : PlayerManager::players) {
|
for (auto& pair : PlayerManager::players) {
|
||||||
if (pair.second->iID == pktdata[i*2]) {
|
if (pair.second->iID == pktdata[i].iID) {
|
||||||
target = pair.second;
|
target = pair.second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -448,67 +606,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int,int> damage;
|
damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
|
||||||
|
|
||||||
if (pkt->iTargetCnt > 1)
|
} else { // eCT == 4; attack mob
|
||||||
damage.first = plr->groupDamage;
|
|
||||||
else
|
|
||||||
damage.first = plr->pointDamage;
|
|
||||||
|
|
||||||
damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
|
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
|
||||||
|
// not sure how to best handle this
|
||||||
|
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
||||||
|
if (npc->kind != EntityKind::MOB) {
|
||||||
|
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mob* mob = (Mob*)npc;
|
||||||
|
target = mob;
|
||||||
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||||
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||||
|
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
if (plr->batteryW >= 6 + plr->level)
|
if (plr->batteryW >= 6 + plr->level)
|
||||||
plr->batteryW -= 6 + plr->level;
|
plr->batteryW -= 6 + plr->level;
|
||||||
else
|
else
|
||||||
plr->batteryW = 0;
|
plr->batteryW = 0;
|
||||||
|
|
||||||
target->HP -= damage.first;
|
damage.first = target->takeDamage(sock, damage.first);
|
||||||
|
|
||||||
respdata[i].eCT = pktdata[i*2+1];
|
respdata[i].eCT = pktdata[i].eCT;
|
||||||
respdata[i].iID = target->iID;
|
respdata[i].iID = target->getID();
|
||||||
respdata[i].iDamage = damage.first;
|
respdata[i].iDamage = damage.first;
|
||||||
respdata[i].iHP = target->HP;
|
respdata[i].iHP = target->getCurrentHP();
|
||||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||||
} else { // eCT == 4; attack mob
|
|
||||||
if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) {
|
|
||||||
// not sure how to best handle this
|
|
||||||
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]];
|
|
||||||
if (npc->type != EntityType::MOB) {
|
|
||||||
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mob* mob = (Mob*)npc;
|
|
||||||
|
|
||||||
std::pair<int,int> damage;
|
|
||||||
|
|
||||||
if (pkt->iTargetCnt > 1)
|
|
||||||
damage.first = plr->groupDamage;
|
|
||||||
else
|
|
||||||
damage.first = plr->pointDamage;
|
|
||||||
|
|
||||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
|
||||||
|
|
||||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
|
||||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
|
||||||
|
|
||||||
if (plr->batteryW >= 6 + difficulty)
|
|
||||||
plr->batteryW -= 6 + difficulty;
|
|
||||||
else
|
|
||||||
plr->batteryW = 0;
|
|
||||||
|
|
||||||
damage.first = hitMob(sock, mob, damage.first);
|
|
||||||
|
|
||||||
respdata[i].eCT = pktdata[i*2+1];
|
|
||||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
|
||||||
respdata[i].iDamage = damage.first;
|
|
||||||
respdata[i].iHP = mob->appearanceData.iHP;
|
|
||||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
||||||
@ -644,21 +776,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rapid fire anti-cheat
|
|
||||||
time_t currTime = getTime();
|
|
||||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
|
||||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
|
||||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
|
||||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
|
|
||||||
|
|
||||||
plr->lastShot = currTime;
|
|
||||||
|
|
||||||
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
|
|
||||||
sock->kill();
|
|
||||||
CNShardServer::_killConnection(sock);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* initialize response struct
|
* initialize response struct
|
||||||
* rocket style hit doesn't work properly, so we're always sending this one
|
* rocket style hit doesn't work properly, so we're always sending this one
|
||||||
@ -687,7 +804,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
||||||
if (npc->type != EntityType::MOB) {
|
if (npc->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -700,11 +817,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||||
|
|
||||||
damage.first = hitMob(sock, mob, damage.first);
|
damage.first = mob->takeDamage(sock, damage.first);
|
||||||
|
|
||||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
respdata[i].iID = mob->id;
|
||||||
respdata[i].iDamage = damage.first;
|
respdata[i].iDamage = damage.first;
|
||||||
respdata[i].iHP = mob->appearanceData.iHP;
|
respdata[i].iHP = mob->hp;
|
||||||
respdata[i].iHitFlag = damage.second;
|
respdata[i].iHitFlag = damage.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,6 +836,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
static void playerTick(CNServer *serv, time_t currTime) {
|
static void playerTick(CNServer *serv, time_t currTime) {
|
||||||
static time_t lastHealTime = 0;
|
static time_t lastHealTime = 0;
|
||||||
|
static time_t lastCombatTIme = 0;
|
||||||
|
|
||||||
for (auto& pair : PlayerManager::players) {
|
for (auto& pair : PlayerManager::players) {
|
||||||
CNSocket *sock = pair.first;
|
CNSocket *sock = pair.first;
|
||||||
@ -726,18 +844,13 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
bool transmit = false;
|
bool transmit = false;
|
||||||
|
|
||||||
// group ticks
|
// group ticks
|
||||||
if (plr->groupCnt > 1)
|
if (plr->group != nullptr)
|
||||||
Groups::groupTickInfo(plr);
|
Groups::groupTickInfo(sock);
|
||||||
|
|
||||||
// do not tick dead players
|
// do not tick dead players
|
||||||
if (plr->HP <= 0)
|
if (plr->HP <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// fm patch/lake damage
|
|
||||||
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION)
|
|
||||||
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
|
||||||
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
|
|
||||||
|
|
||||||
// heal
|
// heal
|
||||||
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
||||||
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
||||||
@ -749,22 +862,21 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
plr->healCooldown -= 4000;
|
plr->healCooldown -= 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
// combat tick
|
||||||
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina
|
if(currTime - lastCombatTIme >= 2000) {
|
||||||
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5;
|
plr->step(currTime);
|
||||||
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
|
||||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
|
||||||
|
|
||||||
transmit = true;
|
transmit = true;
|
||||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
|
}
|
||||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
|
||||||
nano.iStamina += 1;
|
|
||||||
|
|
||||||
if (nano.iStamina > 150)
|
// nanos
|
||||||
nano.iStamina = 150;
|
if (plr->activeNano != 0) { // tick active nano
|
||||||
|
sNano* nano = plr->getActiveNano();
|
||||||
transmit = true;
|
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||||
|
// nano has skill data
|
||||||
|
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||||
|
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||||
|
Nanos::applyNanoBuff(skill, plr);
|
||||||
|
// ^ composite condition calculation is separate from combat for responsiveness
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,6 +892,19 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
|
PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process buffsets
|
||||||
|
auto it = plr->buffs.begin();
|
||||||
|
while(it != plr->buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
buff->tick(currTime);
|
||||||
|
if(buff->isStale()) {
|
||||||
|
// garbage collect
|
||||||
|
it = plr->buffs.erase(it);
|
||||||
|
delete buff;
|
||||||
|
}
|
||||||
|
else it++;
|
||||||
|
}
|
||||||
|
|
||||||
if (transmit) {
|
if (transmit) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||||
|
|
||||||
@ -794,13 +919,15 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this was a heal tick, update the counter outside of the loop
|
// if this was a heal/combat tick, update the counters outside of the loop
|
||||||
if (currTime - lastHealTime >= 4000)
|
if (currTime - lastHealTime >= 4000)
|
||||||
lastHealTime = currTime;
|
lastHealTime = currTime;
|
||||||
|
if(currTime - lastCombatTIme >= 2000)
|
||||||
|
lastCombatTIme = currTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Combat::init() {
|
void Combat::init() {
|
||||||
REGISTER_SHARD_TIMER(playerTick, 2000);
|
REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK);
|
||||||
|
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
||||||
|
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "Player.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "NPC.hpp"
|
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <vector>
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
struct Bullet {
|
struct Bullet {
|
||||||
int pointDamage;
|
int pointDamage;
|
||||||
@ -24,6 +19,5 @@ namespace Combat {
|
|||||||
void init();
|
void init();
|
||||||
|
|
||||||
void npcAttackPc(Mob *mob, time_t currTime);
|
void npcAttackPc(Mob *mob, time_t currTime);
|
||||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
|
||||||
void killMob(CNSocket *sock, Mob *mob);
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
#include "CustomCommands.hpp"
|
#include "CustomCommands.hpp"
|
||||||
#include "Chat.hpp"
|
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Chat.hpp"
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Eggs.hpp"
|
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "Items.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Eggs.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iterator>
|
|
||||||
#include <math.h>
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
||||||
@ -243,20 +244,20 @@ static void summonWCommand(std::string full, std::vector<std::string>& args, CNS
|
|||||||
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
|
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
|
||||||
|
|
||||||
// update angle
|
// update angle
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle);
|
||||||
|
|
||||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
// 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) {
|
if (PLAYERID(plr->instanceID) != 0) {
|
||||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
|
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
|
TableData::RunningMobs[npc->id] = npc; // only record the one in the template
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@ -269,24 +270,24 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) {
|
if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) {
|
||||||
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID);
|
TableData::RunningEggs.erase(npc->id);
|
||||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
NPCManager::destroyNPC(npc->id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()
|
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()
|
||||||
&& TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) {
|
&& TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) {
|
||||||
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) {
|
if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) {
|
||||||
int leadId = ((Mob*)npc)->groupLeader;
|
int leadId = ((Mob*)npc)->groupLeader;
|
||||||
if (leadId != 0) {
|
if (leadId != 0) {
|
||||||
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) {
|
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
||||||
}
|
}
|
||||||
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
|
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
|
||||||
@ -294,7 +295,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
if (leadNpc->groupMember[i] == 0)
|
if (leadNpc->groupMember[i] == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) {
|
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -308,12 +309,12 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
|
|
||||||
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID);
|
TableData::RunningMobs.erase(npc->id);
|
||||||
|
|
||||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
NPCManager::destroyNPC(npc->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@ -324,11 +325,11 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
|
|||||||
|
|
||||||
// return all mobs to their spawn points
|
// return all mobs to their spawn points
|
||||||
for (auto& pair : NPCManager::NPCs) {
|
for (auto& pair : NPCManager::NPCs) {
|
||||||
if (pair.second->type != EntityType::MOB)
|
if (pair.second->kind != EntityKind::MOB)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Mob* mob = (Mob*)pair.second;
|
Mob* mob = (Mob*)pair.second;
|
||||||
mob->state = MobState::RETREAT;
|
mob->state = AIState::RETREAT;
|
||||||
mob->target = nullptr;
|
mob->target = nullptr;
|
||||||
mob->nextMovement = getTime();
|
mob->nextMovement = getTime();
|
||||||
|
|
||||||
@ -356,24 +357,24 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
int angle = (plr->angle + 180) % 360;
|
int angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||||
|
|
||||||
// if it's a gruntwork NPC, rotate in-place
|
// if it's a gruntwork NPC, rotate in-place
|
||||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
|
if (TableData::RunningMobs.find(npc->id) != TableData::RunningMobs.end()) {
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
||||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
+ std::to_string(npc->id));
|
||||||
} else {
|
} else {
|
||||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
TableData::RunningNPCRotations[npc->id] = angle;
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
||||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
+ std::to_string(npc->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update rotation clientside
|
// update rotation clientside
|
||||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||||
pkt.NPCAppearanceData = npc->appearanceData;
|
pkt.NPCAppearanceData = npc->getAppearanceData();
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,9 +444,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
|
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance));
|
||||||
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
|
TableData::RunningNPCMapNumbers[npc->id] = instance;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@ -501,9 +502,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
if (*tmp)
|
if (*tmp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0)
|
if (Abilities::SkillTable.count(skillId) == 0) {
|
||||||
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eggs::eggBuffPlayer(sock, skillId, 0, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@ -532,7 +536,7 @@ static void eggCommand(std::string full, std::vector<std::string>& args, CNSocke
|
|||||||
int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
|
int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
|
||||||
int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI);
|
int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI);
|
||||||
|
|
||||||
Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
|
Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
|
||||||
NPCManager::NPCs[id] = egg;
|
NPCManager::NPCs[id] = egg;
|
||||||
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
|
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
|
||||||
|
|
||||||
@ -609,40 +613,40 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
|||||||
}
|
}
|
||||||
|
|
||||||
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
||||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
leadNpc->groupMember[i-1] = npc->id;
|
||||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
mob->groupLeader = leadNpc->id;
|
||||||
mob->offsetX = x - plr->x;
|
mob->offsetX = x - plr->x;
|
||||||
mob->offsetY = y - plr->y;
|
mob->offsetY = y - plr->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
|
||||||
|
|
||||||
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
|
// 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) {
|
if (PLAYERID(plr->instanceID) != 0) {
|
||||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
||||||
|
|
||||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
leadNpc->groupMember[i-1] = npc->id;
|
||||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
mob->groupLeader = leadNpc->id;
|
||||||
mob->offsetX = x - plr->x;
|
mob->offsetX = x - plr->x;
|
||||||
mob->offsetY = y - plr->y;
|
mob->offsetY = y - plr->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
|
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
|
|
||||||
if (i == 0 && team == 2 && npc->type == EntityType::MOB) {
|
if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) {
|
||||||
type = type2;
|
type = type2;
|
||||||
leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
leadNpc = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
leadNpc->groupLeader = leadNpc->id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,7 +658,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader
|
TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader
|
||||||
}
|
}
|
||||||
|
|
||||||
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@ -671,15 +675,15 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID));
|
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType));
|
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP));
|
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag));
|
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type));
|
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
|
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle));
|
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle));
|
||||||
std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos));
|
std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}");
|
Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}");
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
|
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
|
||||||
@ -694,7 +698,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
|
|||||||
int lastDist = INT_MAX;
|
int lastDist = INT_MAX;
|
||||||
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
||||||
for (const EntityRef& ref : chnk->entities) {
|
for (const EntityRef& ref : chnk->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||||
@ -705,7 +709,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
|
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
|
||||||
if (it->second.npcID == npc->appearanceData.iNPCType) {
|
if (it->second.npcID == npc->type) {
|
||||||
taskID = it->second.limitTaskID;
|
taskID = it->second.limitTaskID;
|
||||||
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
||||||
lastDist = dist;
|
lastDist = dist;
|
||||||
@ -977,11 +981,17 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
|
|
||||||
// add first point at NPC's current location
|
// add first point at NPC's current location
|
||||||
std::vector<BaseNPC*> pathPoints;
|
std::vector<BaseNPC*> pathPoints;
|
||||||
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--);
|
BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||||
|
|
||||||
|
// assign coords manually, since we aren't actually adding markers to the world
|
||||||
|
marker->x = npc->x;
|
||||||
|
marker->y = npc->y;
|
||||||
|
marker->z = npc->z;
|
||||||
|
|
||||||
pathPoints.push_back(marker);
|
pathPoints.push_back(marker);
|
||||||
// map from player
|
// map from player
|
||||||
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
|
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
|
||||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you");
|
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you");
|
||||||
updatePathMarkers(sock);
|
updatePathMarkers(sock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -998,7 +1008,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
|
|
||||||
// /path kf
|
// /path kf
|
||||||
if (args[1] == "kf") {
|
if (args[1] == "kf") {
|
||||||
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--);
|
BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
|
||||||
|
|
||||||
|
marker->x = npc->x;
|
||||||
|
marker->y = npc->y;
|
||||||
|
marker->z = npc->z;
|
||||||
|
|
||||||
entry->second.push_back(marker);
|
entry->second.push_back(marker);
|
||||||
Chat::sendServerMessage(sock, "[PATH] Added keyframe");
|
Chat::sendServerMessage(sock, "[PATH] Added keyframe");
|
||||||
updatePathMarkers(sock);
|
updatePathMarkers(sock);
|
||||||
@ -1008,8 +1023,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// /path here
|
// /path here
|
||||||
if (args[1] == "here") {
|
if (args[1] == "here") {
|
||||||
// bring the NPC to where the player is standing
|
// bring the NPC to where the player is standing
|
||||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
||||||
npc->disappearFromViewOf(sock);
|
npc->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
Chat::sendServerMessage(sock, "[PATH] Come here");
|
Chat::sendServerMessage(sock, "[PATH] Come here");
|
||||||
@ -1047,9 +1062,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
speed = speedArg;
|
speed = speedArg;
|
||||||
}
|
}
|
||||||
// return NPC to home
|
// return NPC to home
|
||||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||||
BaseNPC* home = entry->second[0];
|
BaseNPC* home = entry->second[0];
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||||
npc->disappearFromViewOf(sock);
|
npc->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
|
|
||||||
@ -1065,7 +1080,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||||
from = to; // update point A
|
from = to; // update point A
|
||||||
}
|
}
|
||||||
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes;
|
Transport::NPCQueues[npc->id] = keyframes;
|
||||||
entry->second.pop_back(); // remove temp end point
|
entry->second.pop_back(); // remove temp end point
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
|
Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
|
||||||
@ -1075,9 +1090,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// /path cancel
|
// /path cancel
|
||||||
if (args[1] == "cancel") {
|
if (args[1] == "cancel") {
|
||||||
// return NPC to home
|
// return NPC to home
|
||||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||||
BaseNPC* home = entry->second[0];
|
BaseNPC* home = entry->second[0];
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||||
npc->disappearFromViewOf(sock);
|
npc->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
// deallocate markers
|
// deallocate markers
|
||||||
@ -1087,7 +1102,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
}
|
}
|
||||||
// unmap
|
// unmap
|
||||||
TableData::RunningNPCPaths.erase(plr->iID);
|
TableData::RunningNPCPaths.erase(plr->iID);
|
||||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you");
|
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1115,9 +1130,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return NPC to home and set path to repeat
|
// return NPC to home and set path to repeat
|
||||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||||
BaseNPC* home = entry->second[0];
|
BaseNPC* home = entry->second[0];
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0);
|
NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
|
||||||
npc->disappearFromViewOf(sock);
|
npc->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
npc->loopingPath = true;
|
npc->loopingPath = true;
|
||||||
@ -1134,7 +1149,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||||
from = to; // update point A
|
from = to; // update point A
|
||||||
}
|
}
|
||||||
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes;
|
Transport::NPCQueues[npc->id] = keyframes;
|
||||||
entry->second.pop_back(); // remove temp end point
|
entry->second.pop_back(); // remove temp end point
|
||||||
|
|
||||||
// save to gruntwork
|
// save to gruntwork
|
||||||
@ -1161,7 +1176,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
finishedPath.isLoop = true;
|
finishedPath.isLoop = true;
|
||||||
finishedPath.speed = speed;
|
finishedPath.speed = speed;
|
||||||
finishedPath.points = finalPoints;
|
finishedPath.points = finalPoints;
|
||||||
finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID);
|
finishedPath.targetIDs.push_back(npc->id);
|
||||||
|
|
||||||
TableData::FinishedNPCPaths.push_back(finishedPath);
|
TableData::FinishedNPCPaths.push_back(finishedPath);
|
||||||
|
|
||||||
@ -1173,7 +1188,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// unmap
|
// unmap
|
||||||
TableData::RunningNPCPaths.erase(plr->iID);
|
TableData::RunningNPCPaths.erase(plr->iID);
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you");
|
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
|
||||||
|
|
||||||
TableData::flush();
|
TableData::flush();
|
||||||
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");
|
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace CustomCommands {
|
namespace CustomCommands {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
|
162
src/Eggs.cpp
162
src/Eggs.cpp
@ -1,141 +1,78 @@
|
|||||||
#include "core/Core.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Groups.hpp"
|
#include "NPCManager.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
using namespace Eggs;
|
using namespace Eggs;
|
||||||
|
|
||||||
/// sock, CBFlag -> until
|
|
||||||
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
|
|
||||||
std::unordered_map<int, EggType> Eggs::EggTypes;
|
std::unordered_map<int, EggType> Eggs::EggTypes;
|
||||||
|
|
||||||
int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
// eggId might be 0 if the buff is made by the /buff command
|
||||||
int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag);
|
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
||||||
|
|
||||||
size_t resplen;
|
if(Abilities::SkillTable.count(skillId) == 0) {
|
||||||
|
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
||||||
if (skillId == 183) {
|
return;
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage);
|
|
||||||
} else if (skillId == 150) {
|
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP);
|
|
||||||
} else {
|
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff);
|
|
||||||
}
|
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
|
||||||
// we know it's only one trailing struct, so we can skip full validation
|
|
||||||
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
|
||||||
|
|
||||||
if (skillId == 183) { // damage egg
|
|
||||||
auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
|
|
||||||
plr->HP -= skill->iDamage;
|
|
||||||
if (plr->HP < 0)
|
|
||||||
plr->HP = 0;
|
|
||||||
skill->iHP = plr->HP;
|
|
||||||
} else if (skillId == 150) { // heal egg
|
|
||||||
auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iHealHP = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
|
|
||||||
plr->HP += skill->iHealHP;
|
|
||||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
|
||||||
plr->HP = PC_MAXHEALTH(plr->level);
|
|
||||||
skill->iHP = plr->HP;
|
|
||||||
} else { // regular buff egg
|
|
||||||
auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iConditionBitFlag = plr->iConditionBitFlag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
skillUse->iNPC_ID = eggId;
|
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||||
skillUse->iSkillID = skillId;
|
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
skillUse->eST = Nanos::SkillTable[skillId].skillType;
|
// apply buff
|
||||||
skillUse->iTargetCnt = 1;
|
if(skill->targetType != SkillTargetType::PLAYERS) {
|
||||||
|
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
int value = skill->values[0][0];
|
||||||
|
BuffStack eggBuff = {
|
||||||
|
duration * 1000 / MS_PER_PLAYER_TICK,
|
||||||
|
value,
|
||||||
|
src,
|
||||||
|
BuffClass::EGG
|
||||||
|
};
|
||||||
|
plr->addBuff(timeBuffId,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&eggBuff);
|
||||||
|
}
|
||||||
|
|
||||||
if (CBFlag == 0)
|
// use skill
|
||||||
return -1;
|
std::vector<ICombatant*> targets;
|
||||||
|
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag);
|
Abilities::useNPCSkill(src, skillId, targets);
|
||||||
|
|
||||||
// save the buff serverside;
|
|
||||||
// if you get the same buff again, new duration will override the previous one
|
|
||||||
time_t until = getTime() + (time_t)duration * 1000;
|
|
||||||
EggBuffs[key] = until;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggStep(CNServer* serv, time_t currTime) {
|
static void eggStep(CNServer* serv, time_t currTime) {
|
||||||
// tick buffs
|
|
||||||
time_t timeStamp = currTime;
|
|
||||||
auto it = EggBuffs.begin();
|
|
||||||
while (it != EggBuffs.end()) {
|
|
||||||
// check remaining time
|
|
||||||
if (it->second > timeStamp) {
|
|
||||||
it++;
|
|
||||||
} else { // if time reached 0
|
|
||||||
CNSocket* sock = it->first.first;
|
|
||||||
int32_t CBFlag = it->first.second;
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
int groupFlags = Groups::getGroupFlags(otherPlr);
|
|
||||||
for (auto& pwr : Nanos::NanoPowers) {
|
|
||||||
if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
|
||||||
resp.eCSTB = pwr.timeBuffID;
|
|
||||||
resp.eTBU = 2;
|
|
||||||
resp.eTBT = 3; // for egg buffs
|
|
||||||
plr->iConditionBitFlag &= ~CBFlag;
|
|
||||||
resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
|
||||||
sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
|
|
||||||
resp2.eCT = 1;
|
|
||||||
resp2.iID = plr->iID;
|
|
||||||
resp2.iConditionBitFlag = plr->iConditionBitFlag;
|
|
||||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove buff from the map
|
|
||||||
it = EggBuffs.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check dead eggs and eggs in inactive chunks
|
// check dead eggs and eggs in inactive chunks
|
||||||
for (auto npc : NPCManager::NPCs) {
|
for (auto npc : NPCManager::NPCs) {
|
||||||
if (npc.second->type != EntityType::EGG)
|
if (npc.second->kind != EntityKind::EGG)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto egg = (Egg*)npc.second;
|
auto egg = (Egg*)npc.second;
|
||||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (egg->deadUntil <= timeStamp) {
|
if (egg->deadUntil <= currTime) {
|
||||||
// respawn it
|
// respawn it
|
||||||
egg->dead = false;
|
egg->dead = false;
|
||||||
egg->deadUntil = 0;
|
egg->deadUntil = 0;
|
||||||
egg->appearanceData.iHP = 400;
|
egg->hp = 400;
|
||||||
|
|
||||||
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
||||||
}
|
}
|
||||||
@ -163,7 +100,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto egg = (Egg*)eggRef.getEntity();
|
auto egg = (Egg*)eggRef.getEntity();
|
||||||
if (egg->type != EntityType::EGG) {
|
if (egg->kind != EntityKind::EGG) {
|
||||||
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -180,7 +117,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int typeId = egg->appearanceData.iNPCType;
|
int typeId = egg->type;
|
||||||
if (EggTypes.find(typeId) == EggTypes.end()) {
|
if (EggTypes.find(typeId) == EggTypes.end()) {
|
||||||
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
||||||
return;
|
return;
|
||||||
@ -188,16 +125,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
EggType* type = &EggTypes[typeId];
|
EggType* type = &EggTypes[typeId];
|
||||||
|
|
||||||
// buff the player
|
|
||||||
if (type->effectId != 0)
|
|
||||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
||||||
* (buff icon pops up in the bottom of the screen)
|
* (buff icon pops up in the bottom of the screen)
|
||||||
* so we don't send it for non-effect
|
* so we don't send it for non-effect
|
||||||
*/
|
*/
|
||||||
if (type->effectId != 0) {
|
if (type->effectId != 0) {
|
||||||
|
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||||
resp.iSkillID = type->effectId;
|
resp.iSkillID = type->effectId;
|
||||||
|
|
||||||
@ -255,7 +189,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
||||||
egg->dead = true;
|
egg->dead = true;
|
||||||
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
||||||
egg->appearanceData.iHP = 0;
|
egg->hp = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Entities.hpp"
|
|
||||||
|
|
||||||
struct EggType {
|
struct EggType {
|
||||||
int dropCrateId;
|
int dropCrateId;
|
||||||
@ -11,12 +10,10 @@ struct EggType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace Eggs {
|
namespace Eggs {
|
||||||
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
|
|
||||||
extern std::unordered_map<int, EggType> EggTypes;
|
extern std::unordered_map<int, EggType> EggTypes;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
/// returns -1 on fail
|
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||||
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
|
||||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include "Email.hpp"
|
#include "Email.hpp"
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
@ -93,7 +93,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
|
|||||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
|
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
|
|
||||||
// get email item from db and delete it
|
// get email item from db and delete it
|
||||||
@ -252,11 +252,26 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (attachment.ItemInven.iID == 0)
|
if (attachment.ItemInven.iID == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||||
|
sItemBase* real = &plr->Inven[attachment.iSlotNum];
|
||||||
|
|
||||||
resp.aItem[i] = attachment;
|
resp.aItem[i] = attachment;
|
||||||
attachments.push_back(attachment.ItemInven);
|
attachments.push_back(attachment.ItemInven);
|
||||||
attSlots.push_back(attachment.iSlotNum);
|
attSlots.push_back(attachment.iSlotNum);
|
||||||
// delete item
|
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack)
|
||||||
plr->Inven[attachment.iSlotNum] = { 0, 0, 0, 0 };
|
*real = { 0, 0, 0, 0 };
|
||||||
|
else // otherwise, decrement the item
|
||||||
|
real->iOpt -= item->iOpt;
|
||||||
|
|
||||||
|
// HACK: update the slot
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
|
||||||
|
itemResp.iFromSlotNum = attachment.iSlotNum;
|
||||||
|
itemResp.iToSlotNum = attachment.iSlotNum;
|
||||||
|
itemResp.FromSlotItem = *real;
|
||||||
|
itemResp.ToSlotItem = *real;
|
||||||
|
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
|
||||||
|
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
|
||||||
|
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||||
@ -276,7 +291,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
0 // DeleteTime (unimplemented)
|
0 // DeleteTime (unimplemented)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!Database::sendEmail(&email, attachments)) {
|
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||||
plr->money += cost; // give money back
|
plr->money += cost; // give money back
|
||||||
// give items back
|
// give items back
|
||||||
while (!attachments.empty()) {
|
while (!attachments.empty()) {
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
#include "core/Core.hpp"
|
|
||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
#include "Chunking.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Eggs.hpp"
|
|
||||||
#include "MobAI.hpp"
|
|
||||||
|
|
||||||
#include <type_traits>
|
#include "NPCManager.hpp"
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
static_assert(std::is_standard_layout<EntityRef>::value);
|
static_assert(std::is_standard_layout<EntityRef>::value);
|
||||||
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
||||||
|
|
||||||
EntityRef::EntityRef(CNSocket *s) {
|
EntityRef::EntityRef(CNSocket *s) {
|
||||||
type = EntityType::PLAYER;
|
kind = EntityKind::PLAYER;
|
||||||
sock = s;
|
sock = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,11 +17,11 @@ EntityRef::EntityRef(int32_t i) {
|
|||||||
id = i;
|
id = i;
|
||||||
|
|
||||||
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
||||||
type = NPCManager::NPCs[id]->type;
|
kind = NPCManager::NPCs[id]->kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityRef::isValid() const {
|
bool EntityRef::isValid() const {
|
||||||
if (type == EntityType::PLAYER)
|
if (kind == EntityKind::PLAYER)
|
||||||
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
||||||
|
|
||||||
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
||||||
@ -33,21 +30,32 @@ bool EntityRef::isValid() const {
|
|||||||
Entity *EntityRef::getEntity() const {
|
Entity *EntityRef::getEntity() const {
|
||||||
assert(isValid());
|
assert(isValid());
|
||||||
|
|
||||||
if (type == EntityType::PLAYER)
|
if (kind == EntityKind::PLAYER)
|
||||||
return PlayerManager::getPlayer(sock);
|
return PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
return NPCManager::NPCs[id];
|
return NPCManager::NPCs[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sNPCAppearanceData BaseNPC::getAppearanceData() {
|
||||||
|
sNPCAppearanceData data = {};
|
||||||
|
data.iAngle = angle;
|
||||||
|
data.iBarkerType = 0; // unused?
|
||||||
|
data.iConditionBitFlag = cbf;
|
||||||
|
data.iHP = hp;
|
||||||
|
data.iNPCType = type;
|
||||||
|
data.iNPC_ID = id;
|
||||||
|
data.iX = x;
|
||||||
|
data.iY = y;
|
||||||
|
data.iZ = z;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Entity coming into view.
|
* Entity coming into view.
|
||||||
*/
|
*/
|
||||||
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||||
pkt.NPCAppearanceData = appearanceData;
|
pkt.NPCAppearanceData = getAppearanceData();
|
||||||
pkt.NPCAppearanceData.iX = x;
|
|
||||||
pkt.NPCAppearanceData.iY = y;
|
|
||||||
pkt.NPCAppearanceData.iZ = z;
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +64,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
|||||||
|
|
||||||
// TODO: Potentially decouple this from BaseNPC?
|
// TODO: Potentially decouple this from BaseNPC?
|
||||||
pkt.AppearanceData = {
|
pkt.AppearanceData = {
|
||||||
3, appearanceData.iNPC_ID, appearanceData.iNPCType,
|
3, id, type,
|
||||||
x, y, z
|
x, y, z
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,28 +74,40 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
|||||||
void Egg::enterIntoViewOf(CNSocket *sock) {
|
void Egg::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
|
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
|
||||||
|
|
||||||
Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData);
|
// TODO: Potentially decouple this from BaseNPC?
|
||||||
|
pkt.ShinyAppearanceData = {
|
||||||
|
id, type, 0, // client doesn't care about map num
|
||||||
|
x, y, z
|
||||||
|
};
|
||||||
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sNano* Player::getActiveNano() {
|
||||||
|
return &Nanos[activeNano];
|
||||||
|
}
|
||||||
|
|
||||||
|
sPCAppearanceData Player::getAppearanceData() {
|
||||||
|
sPCAppearanceData data = {};
|
||||||
|
data.iID = iID;
|
||||||
|
data.iHP = HP;
|
||||||
|
data.iLv = level;
|
||||||
|
data.iX = x;
|
||||||
|
data.iY = y;
|
||||||
|
data.iZ = z;
|
||||||
|
data.iAngle = angle;
|
||||||
|
data.PCStyle = PCStyle;
|
||||||
|
data.Nano = Nanos[activeNano];
|
||||||
|
data.iPCState = iPCState;
|
||||||
|
data.iSpecialState = iSpecialState;
|
||||||
|
memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this is less effiecient than it was, because of memset()
|
// TODO: this is less effiecient than it was, because of memset()
|
||||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||||
|
pkt.PCAppearanceData = getAppearanceData();
|
||||||
pkt.PCAppearanceData.iID = iID;
|
|
||||||
pkt.PCAppearanceData.iHP = HP;
|
|
||||||
pkt.PCAppearanceData.iLv = level;
|
|
||||||
pkt.PCAppearanceData.iX = x;
|
|
||||||
pkt.PCAppearanceData.iY = y;
|
|
||||||
pkt.PCAppearanceData.iZ = z;
|
|
||||||
pkt.PCAppearanceData.iAngle = angle;
|
|
||||||
pkt.PCAppearanceData.PCStyle = PCStyle;
|
|
||||||
pkt.PCAppearanceData.Nano = Nanos[activeNano];
|
|
||||||
pkt.PCAppearanceData.iPCState = iPCState;
|
|
||||||
pkt.PCAppearanceData.iSpecialState = iSpecialState;
|
|
||||||
memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
|
|
||||||
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,20 +116,20 @@ void Player::enterIntoViewOf(CNSocket *sock) {
|
|||||||
*/
|
*/
|
||||||
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||||
pkt.iNPC_ID = appearanceData.iNPC_ID;
|
pkt.iNPC_ID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bus::disappearFromViewOf(CNSocket *sock) {
|
void Bus::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
||||||
pkt.eTT = 3;
|
pkt.eTT = 3;
|
||||||
pkt.iT_ID = appearanceData.iNPC_ID;
|
pkt.iT_ID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Egg::disappearFromViewOf(CNSocket *sock) {
|
void Egg::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
||||||
pkt.iShinyID = appearanceData.iNPC_ID;
|
pkt.iShinyID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
170
src/Entities.hpp
170
src/Entities.hpp
@ -1,23 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include "EntityRef.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
#include "Chunking.hpp"
|
#include "Chunking.hpp"
|
||||||
|
#include "Groups.hpp"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
enum class EntityType : uint8_t {
|
enum class AIState {
|
||||||
INVALID,
|
INACTIVE,
|
||||||
PLAYER,
|
ROAMING,
|
||||||
SIMPLE_NPC,
|
COMBAT,
|
||||||
COMBAT_NPC,
|
RETREAT,
|
||||||
MOB,
|
DEAD
|
||||||
EGG,
|
|
||||||
BUS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
EntityType type = EntityType::INVALID;
|
EntityKind kind = EntityKind::INVALID;
|
||||||
int x = 0, y = 0, z = 0;
|
int x = 0, y = 0, z = 0;
|
||||||
uint64_t instanceID = 0;
|
uint64_t instanceID = 0;
|
||||||
ChunkPos chunkPos = {};
|
ChunkPos chunkPos = {};
|
||||||
@ -26,47 +29,38 @@ struct Entity {
|
|||||||
// destructor must be virtual, apparently
|
// destructor must be virtual, apparently
|
||||||
virtual ~Entity() {}
|
virtual ~Entity() {}
|
||||||
|
|
||||||
virtual bool isAlive() { return true; }
|
virtual bool isExtant() { return true; }
|
||||||
|
|
||||||
// stubs
|
// stubs
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EntityRef {
|
/*
|
||||||
EntityType type;
|
* Interfaces
|
||||||
union {
|
*/
|
||||||
CNSocket *sock;
|
class ICombatant {
|
||||||
int32_t id;
|
public:
|
||||||
};
|
ICombatant() {}
|
||||||
|
virtual ~ICombatant() {}
|
||||||
|
|
||||||
EntityRef(CNSocket *s);
|
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||||
EntityRef(int32_t i);
|
virtual Buff* getBuff(int) = 0;
|
||||||
|
virtual void removeBuff(int) = 0;
|
||||||
bool isValid() const;
|
virtual void removeBuff(int, int) = 0;
|
||||||
Entity *getEntity() const;
|
virtual bool hasBuff(int) = 0;
|
||||||
|
virtual int getCompositeCondition() = 0;
|
||||||
bool operator==(const EntityRef& other) const {
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
if (type != other.type)
|
virtual int heal(EntityRef, int) = 0;
|
||||||
return false;
|
virtual bool isAlive() = 0;
|
||||||
|
virtual int getCurrentHP() = 0;
|
||||||
if (type == EntityType::PLAYER)
|
virtual int getMaxHP() = 0;
|
||||||
return sock == other.sock;
|
virtual int getLevel() = 0;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||||
return id == other.id;
|
virtual int32_t getCharType() = 0;
|
||||||
}
|
virtual int32_t getID() = 0;
|
||||||
|
virtual EntityRef getRef() = 0;
|
||||||
// arbitrary ordering
|
virtual void step(time_t currTime) = 0;
|
||||||
bool operator<(const EntityRef& other) const {
|
|
||||||
if (type == other.type) {
|
|
||||||
if (type == EntityType::PLAYER)
|
|
||||||
return sock < other.sock;
|
|
||||||
else
|
|
||||||
return id < other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return type < other.type;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -74,75 +68,101 @@ struct EntityRef {
|
|||||||
*/
|
*/
|
||||||
class BaseNPC : public Entity {
|
class BaseNPC : public Entity {
|
||||||
public:
|
public:
|
||||||
sNPCAppearanceData appearanceData = {};
|
int id;
|
||||||
|
int type;
|
||||||
|
int hp;
|
||||||
|
int angle;
|
||||||
|
int cbf;
|
||||||
bool loopingPath = false;
|
bool loopingPath = false;
|
||||||
|
|
||||||
BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX
|
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||||
x = _X;
|
kind = EntityKind::SIMPLE_NPC;
|
||||||
y = _Y;
|
type = t;
|
||||||
z = _Z;
|
hp = 400;
|
||||||
appearanceData.iNPCType = t;
|
angle = _A;
|
||||||
appearanceData.iHP = 400;
|
cbf = 0;
|
||||||
appearanceData.iAngle = angle;
|
id = _id;
|
||||||
appearanceData.iConditionBitFlag = 0;
|
|
||||||
appearanceData.iBarkerType = 0;
|
|
||||||
appearanceData.iNPC_ID = id;
|
|
||||||
|
|
||||||
instanceID = iID;
|
instanceID = iID;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
|
|
||||||
|
sNPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CombatNPC : public BaseNPC {
|
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||||
int maxHealth = 0;
|
int maxHealth = 0;
|
||||||
int spawnX = 0;
|
int spawnX = 0;
|
||||||
int spawnY = 0;
|
int spawnY = 0;
|
||||||
int spawnZ = 0;
|
int spawnZ = 0;
|
||||||
int level = 0;
|
int level = 0;
|
||||||
int speed = 300;
|
int speed = 300;
|
||||||
|
AIState state = AIState::INACTIVE;
|
||||||
|
Group* group = nullptr;
|
||||||
|
int playersInView = 0; // for optimizing away AI in empty chunks
|
||||||
|
|
||||||
void (*_stepAI)(CombatNPC*, time_t) = nullptr;
|
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||||
|
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||||
|
|
||||||
// XXX
|
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||||
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) :
|
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||||
BaseNPC(x, y, z, angle, iID, t, id),
|
spawnX = x;
|
||||||
maxHealth(maxHP) {}
|
spawnY = y;
|
||||||
|
spawnZ = z;
|
||||||
|
|
||||||
virtual void stepAI(time_t currTime) {
|
kind = EntityKind::COMBAT_NPC;
|
||||||
if (_stepAI != nullptr)
|
|
||||||
_stepAI(this, currTime);
|
stateHandlers[AIState::INACTIVE] = {};
|
||||||
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isAlive() override { return appearanceData.iHP > 0; }
|
virtual bool isExtant() override { return hp > 0; }
|
||||||
|
|
||||||
|
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
|
virtual Buff* getBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
|
virtual bool hasBuff(int buffId) override;
|
||||||
|
virtual int getCompositeCondition() override;
|
||||||
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
virtual int heal(EntityRef src, int amt) override;
|
||||||
|
virtual bool isAlive() override;
|
||||||
|
virtual int getCurrentHP() override;
|
||||||
|
virtual int getMaxHP() override;
|
||||||
|
virtual int getLevel() override;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||||
|
virtual int32_t getCharType() override;
|
||||||
|
virtual int32_t getID() override;
|
||||||
|
virtual EntityRef getRef() override;
|
||||||
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
|
virtual void transition(AIState newState, EntityRef src);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mob is in MobAI.hpp, Player is in Player.hpp
|
// Mob is in MobAI.hpp, Player is in Player.hpp
|
||||||
|
|
||||||
// TODO: decouple from BaseNPC
|
|
||||||
struct Egg : public BaseNPC {
|
struct Egg : public BaseNPC {
|
||||||
bool summoned = false;
|
bool summoned = false;
|
||||||
bool dead = false;
|
bool dead = false;
|
||||||
time_t deadUntil;
|
time_t deadUntil;
|
||||||
|
|
||||||
Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon)
|
Egg(uint64_t iID, int t, int32_t id, bool summon)
|
||||||
: BaseNPC(x, y, z, 0, iID, t, id) {
|
: BaseNPC(0, iID, t, id) {
|
||||||
summoned = summon;
|
summoned = summon;
|
||||||
type = EntityType::EGG;
|
kind = EntityKind::EGG;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isAlive() override { return !dead; }
|
virtual bool isExtant() override { return !dead; }
|
||||||
|
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: decouple from BaseNPC
|
|
||||||
struct Bus : public BaseNPC {
|
struct Bus : public BaseNPC {
|
||||||
Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) :
|
Bus(int angle, uint64_t iID, int t, int id) :
|
||||||
BaseNPC(x, y, z, angle, iID, t, id) {
|
BaseNPC(angle, iID, t, id) {
|
||||||
type = EntityType::BUS;
|
kind = EntityKind::BUS;
|
||||||
loopingPath = true;
|
loopingPath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
src/EntityRef.hpp
Normal file
56
src/EntityRef.hpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
struct Entity;
|
||||||
|
|
||||||
|
enum EntityKind {
|
||||||
|
INVALID,
|
||||||
|
PLAYER,
|
||||||
|
SIMPLE_NPC,
|
||||||
|
COMBAT_NPC,
|
||||||
|
MOB,
|
||||||
|
EGG,
|
||||||
|
BUS
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntityRef {
|
||||||
|
EntityKind kind;
|
||||||
|
union {
|
||||||
|
CNSocket *sock;
|
||||||
|
int32_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
EntityRef(CNSocket *s);
|
||||||
|
EntityRef(int32_t i);
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
Entity *getEntity() const;
|
||||||
|
|
||||||
|
bool operator==(const EntityRef& other) const {
|
||||||
|
if (kind != other.kind)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock == other.sock;
|
||||||
|
|
||||||
|
return id == other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const EntityRef& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// arbitrary ordering
|
||||||
|
bool operator<(const EntityRef& other) const {
|
||||||
|
if (kind == other.kind) {
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock < other.sock;
|
||||||
|
else
|
||||||
|
return id < other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kind < other.kind;
|
||||||
|
}
|
||||||
|
};
|
441
src/Groups.cpp
441
src/Groups.cpp
@ -1,13 +1,10 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Groups.hpp"
|
#include "Groups.hpp"
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include "servers/CNShardServer.hpp"
|
||||||
#include <chrono>
|
|
||||||
#include <algorithm>
|
#include "Player.hpp"
|
||||||
#include <thread>
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: Variadic response packets that list group members are technically
|
* NOTE: Variadic response packets that list group members are technically
|
||||||
@ -19,22 +16,177 @@
|
|||||||
|
|
||||||
using namespace Groups;
|
using namespace Groups;
|
||||||
|
|
||||||
|
Group::Group(EntityRef leader) {
|
||||||
|
addToGroup(this, leader);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) {
|
||||||
|
for(EntityRef pcRef : pcs) {
|
||||||
|
sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot;
|
||||||
|
|
||||||
|
Player* plr = PlayerManager::getPlayer(pcRef.sock);
|
||||||
|
info->iPC_ID = plr->iID;
|
||||||
|
info->iPCUID = plr->PCStyle.iPC_UID;
|
||||||
|
info->iNameCheck = plr->PCStyle.iNameCheck;
|
||||||
|
memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||||
|
memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||||
|
info->iSpecialState = plr->iSpecialState;
|
||||||
|
info->iLv = plr->level;
|
||||||
|
info->iHP = plr->HP;
|
||||||
|
info->iMaxHP = PC_MAXHEALTH(plr->level);
|
||||||
|
// info->iMapType = 0;
|
||||||
|
// info->iMapNum = 0;
|
||||||
|
info->iX = plr->x;
|
||||||
|
info->iY = plr->y;
|
||||||
|
info->iZ = plr->z;
|
||||||
|
if(plr->activeNano > 0) {
|
||||||
|
info->Nano = *plr->getActiveNano();
|
||||||
|
info->bNano = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot = (uint8_t*)(info + 1);
|
||||||
|
}
|
||||||
|
for(EntityRef npcRef : npcs) {
|
||||||
|
sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot;
|
||||||
|
|
||||||
|
// probably should not assume that the combatant is an
|
||||||
|
// entity, but it works for now
|
||||||
|
BaseNPC* npc = (BaseNPC*)npcRef.getEntity();
|
||||||
|
info->iNPC_ID = npcRef.id;
|
||||||
|
info->iNPC_Type = npc->type;
|
||||||
|
info->iHP = npc->hp;
|
||||||
|
info->iX = npc->x;
|
||||||
|
info->iY = npc->y;
|
||||||
|
info->iZ = npc->z;
|
||||||
|
|
||||||
|
pivot = (uint8_t*)(info + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groups::addToGroup(Group* group, EntityRef member) {
|
||||||
|
if (member.kind == EntityKind::PLAYER) {
|
||||||
|
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||||
|
plr->group = group;
|
||||||
|
}
|
||||||
|
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||||
|
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||||
|
npc->group = group;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
group->members.push_back(member);
|
||||||
|
|
||||||
|
if(member.kind == EntityKind::PLAYER) {
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||||
|
|
||||||
|
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl;
|
||||||
|
} else {
|
||||||
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
|
// PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs
|
||||||
|
// (and the client does care!) so we need to send one to the new member
|
||||||
|
// and the other to the rest
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo);
|
||||||
|
member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen);
|
||||||
|
sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Groups::removeFromGroup(Group* group, EntityRef member) {
|
||||||
|
if (member.kind == EntityKind::PLAYER) {
|
||||||
|
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||||
|
plr->group = nullptr; // no dangling pointers here muahaahahah
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt);
|
||||||
|
member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC);
|
||||||
|
}
|
||||||
|
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||||
|
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||||
|
npc->group = nullptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::find(group->members.begin(), group->members.end(), member);
|
||||||
|
if (it == group->members.end()) {
|
||||||
|
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
|
||||||
|
} else {
|
||||||
|
group->members.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(member.kind == EntityKind::PLAYER) {
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||||
|
|
||||||
|
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl;
|
||||||
|
} else {
|
||||||
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
|
sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE,
|
||||||
|
sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->members.size() == 1) {
|
||||||
|
return removeFromGroup(group, group->members.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->members.empty()) {
|
||||||
|
delete group; // cleanup memory
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groups::disbandGroup(Group* group) {
|
||||||
|
// remove everyone from the group!!
|
||||||
|
bool done = false;
|
||||||
|
while(!done) {
|
||||||
|
EntityRef back = group->members.back();
|
||||||
|
done = removeFromGroup(group, back);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void requestGroup(CNSocket* sock, CNPacketData* data) {
|
static void requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
if (otherPlr == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// fail if the group is full or the other player is already in a group
|
// fail if the group is full or the other player is already in a group
|
||||||
if (plr->groupCnt >= 4 || otherPlr->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) {
|
if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) {
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
|
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));
|
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||||
return;
|
return;
|
||||||
@ -75,255 +227,80 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
|||||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
if (otherPlr == nullptr)
|
||||||
return;
|
return; // disconnect or something
|
||||||
|
|
||||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// fail if the group is full or the other player is already in a group
|
// 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) {
|
if (plr->group != nullptr || size + 1 > 4) {
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
|
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));
|
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
|
if (otherPlr->group == nullptr) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
// create group
|
||||||
return;
|
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||||
|
otherPlr->group = new Group(otherPlrRef);
|
||||||
}
|
}
|
||||||
|
addToGroup(otherPlr->group, sock);
|
||||||
plr->iIDGroup = otherPlr->iID;
|
|
||||||
otherPlr->groupCnt += 1;
|
|
||||||
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
|
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
|
|
||||||
|
|
||||||
resp->iID_NewMember = plr->iID;
|
|
||||||
resp->iMemberPCCnt = otherPlr->groupCnt;
|
|
||||||
|
|
||||||
int bitFlag = getGroupFlags(otherPlr);
|
|
||||||
|
|
||||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
|
||||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
|
||||||
|
|
||||||
if (varPlr == nullptr || sockTo == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
respdata[i].iPC_ID = varPlr->iID;
|
|
||||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
|
||||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
|
||||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
|
||||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
|
||||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
|
||||||
respdata[i].iLv = varPlr->level;
|
|
||||||
respdata[i].iHP = varPlr->HP;
|
|
||||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
|
||||||
//respdata[i].iMapType = 0;
|
|
||||||
//respdata[i].iMapNum = 0;
|
|
||||||
respdata[i].iX = varPlr->x;
|
|
||||||
respdata[i].iY = varPlr->y;
|
|
||||||
respdata[i].iZ = varPlr->z;
|
|
||||||
// client doesnt read nano data here
|
|
||||||
|
|
||||||
if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member
|
|
||||||
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
|
|
||||||
if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
groupKickPlayer(plr);
|
groupKick(plr->group, sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
|
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
for (EntityRef ref : players) {
|
||||||
|
ref.sock->sendPacket(buf, type, size);
|
||||||
if (sock == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
|
|
||||||
Player* leavingPlr = PlayerManager::getPlayer(sock);
|
|
||||||
leavingPlr->iIDGroup = leavingPlr->iID;
|
|
||||||
}
|
|
||||||
|
|
||||||
sock->sendPacket(buf, type, size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::groupTickInfo(Player* plr) {
|
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
for (EntityRef ref : players) {
|
||||||
return;
|
if(ref != excluded) ref.sock->sendPacket(buf, type, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
|
void Groups::groupTickInfo(CNSocket* sock) {
|
||||||
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
Group* group = plr->group;
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
pkt->iID = plr->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl;
|
||||||
resp->iID = plr->iID;
|
} else {
|
||||||
resp->iMemberPCCnt = plr->groupCnt;
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||||
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||||
|
|
||||||
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 Groups::groupKick(Group* group, EntityRef ref) {
|
||||||
}
|
|
||||||
|
|
||||||
static void groupUnbuff(Player* plr) {
|
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
|
||||||
for (int n = 0; n < plr->groupCnt; n++) {
|
|
||||||
if (i == n)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
|
||||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]);
|
|
||||||
|
|
||||||
Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Groups::groupKickPlayer(Player* plr) {
|
|
||||||
// if you are the group leader, destroy your own group and kick everybody
|
// if you are the group leader, destroy your own group and kick everybody
|
||||||
if (plr->iID == plr->iIDGroup) {
|
if (group->members[0] == ref) {
|
||||||
groupUnbuff(plr);
|
disbandGroup(group);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
removeFromGroup(group, ref);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
|
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
|
|
||||||
|
|
||||||
resp->iID_LeaveMember = plr->iID;
|
|
||||||
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
|
|
||||||
|
|
||||||
int bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
|
|
||||||
int moveDown = 0;
|
|
||||||
|
|
||||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
|
||||||
|
|
||||||
if (sock == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < otherPlr->groupCnt; i++) {
|
|
||||||
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
|
|
||||||
|
|
||||||
if (varPlr == nullptr || sockTo == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (moveDown == 1)
|
|
||||||
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
|
|
||||||
|
|
||||||
respdata[i-moveDown].iPC_ID = varPlr->iID;
|
|
||||||
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
|
|
||||||
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
|
|
||||||
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
|
||||||
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
|
||||||
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
|
|
||||||
respdata[i-moveDown].iLv = varPlr->level;
|
|
||||||
respdata[i-moveDown].iHP = varPlr->HP;
|
|
||||||
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
|
||||||
// respdata[i-moveDown]].iMapType = 0;
|
|
||||||
// respdata[i-moveDown]].iMapNum = 0;
|
|
||||||
respdata[i-moveDown].iX = varPlr->x;
|
|
||||||
respdata[i-moveDown].iY = varPlr->y;
|
|
||||||
respdata[i-moveDown].iZ = varPlr->z;
|
|
||||||
// client doesnt read nano data here
|
|
||||||
|
|
||||||
if (varPlr == plr) {
|
|
||||||
moveDown = 1;
|
|
||||||
otherPlr->groupIDs[i] = 0;
|
|
||||||
} else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
|
|
||||||
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
|
|
||||||
if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plr->iIDGroup = plr->iID;
|
|
||||||
otherPlr->groupCnt -= 1;
|
|
||||||
|
|
||||||
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
|
||||||
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
|
||||||
}
|
|
||||||
|
|
||||||
int Groups::getGroupFlags(Player* plr) {
|
|
||||||
int bitFlag = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bitFlag |= otherPlr->iGroupConditionBitFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitFlag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::init() {
|
void Groups::init() {
|
||||||
|
@ -1,17 +1,32 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "EntityRef.hpp"
|
||||||
#include "core/Core.hpp"
|
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
#include <vector>
|
||||||
#include <list>
|
|
||||||
|
struct Group {
|
||||||
|
std::vector<EntityRef> members;
|
||||||
|
|
||||||
|
std::vector<EntityRef> filter(EntityKind kind) {
|
||||||
|
std::vector<EntityRef> filtered;
|
||||||
|
std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) {
|
||||||
|
return e.kind == kind;
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
Group(EntityRef leader);
|
||||||
|
};
|
||||||
|
|
||||||
namespace Groups {
|
namespace Groups {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||||
void groupTickInfo(Player* plr);
|
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||||
void groupKickPlayer(Player* plr);
|
void groupTickInfo(CNSocket* sock);
|
||||||
int getGroupFlags(Player* plr);
|
|
||||||
|
void groupKick(Group* group, EntityRef ref);
|
||||||
|
void addToGroup(Group* group, EntityRef member);
|
||||||
|
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
|
||||||
|
void disbandGroup(Group* group);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Missions.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
#include "Rand.hpp"
|
#include "MobAI.hpp"
|
||||||
|
#include "Missions.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <string.h> // for memset()
|
#include <string.h> // for memset()
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
using namespace Items;
|
using namespace Items;
|
||||||
|
|
||||||
@ -482,27 +485,31 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp->eST = EST_NANOSTIMPAK;
|
resp->eST = EST_NANOSTIMPAK;
|
||||||
resp->iSkillID = 144;
|
resp->iSkillID = 144;
|
||||||
|
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot;
|
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||||
int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
|
||||||
|
|
||||||
respdata->eCT = 1;
|
respdata->eCT = 1;
|
||||||
respdata->iID = player->iID;
|
respdata->iID = player->iID;
|
||||||
respdata->iConditionBitFlag = value1;
|
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
||||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
BuffStack gumballBuff = {
|
||||||
pkt.eTBU = 1; // eTimeBuffUpdate
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
0,
|
||||||
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1;
|
sock,
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
BuffClass::CASH_ITEM // or BuffClass::ITEM?
|
||||||
|
};
|
||||||
|
player->addBuff(eCSB,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&gumballBuff);
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
||||||
// update inventory serverside
|
// update inventory serverside
|
||||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
||||||
|
|
||||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, value1);
|
|
||||||
time_t until = getTime() + (time_t)Nanos::SkillTable[144].durationTime[0] * 100;
|
|
||||||
Eggs::EggBuffs[key] = until;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
@ -755,7 +762,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||||
plr->money += miscDropType.taroAmount;
|
plr->money += miscDropType.taroAmount;
|
||||||
// money nano boost
|
// money nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
|
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
@ -770,7 +777,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
if (levelDifference > 0)
|
if (levelDifference > 0)
|
||||||
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
||||||
// scavenger nano boost
|
// scavenger nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
@ -822,12 +829,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
|
|
||||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
||||||
// sanity check
|
// sanity check
|
||||||
if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) {
|
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||||
std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl;
|
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// find mob drop id
|
// find mob drop id
|
||||||
int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType];
|
int mobDropId = Items::MobToDropMap[mob->type];
|
||||||
|
|
||||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
#include "MobAI.hpp"
|
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
struct CrocPotEntry {
|
struct CrocPotEntry {
|
||||||
int multStats, multLooks;
|
int multStats, multLooks;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
|
|
||||||
#include "string.h"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
#include "Nanos.hpp"
|
||||||
|
|
||||||
using namespace Missions;
|
using namespace Missions;
|
||||||
|
|
||||||
@ -164,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
|||||||
|
|
||||||
// update player
|
// update player
|
||||||
plr->money += reward->money;
|
plr->money += reward->money;
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros
|
if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
plr->money += reward->money * (5 + boost) / 25;
|
plr->money += reward->money * (5 + boost) / 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
@ -367,15 +366,15 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
|
|||||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||||
|
|
||||||
// if escort task, assign matching paths to all nearby NPCs
|
// if escort task, assign matching paths to all nearby NPCs
|
||||||
if (task["m_iHTaskType"] == 6) {
|
if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) {
|
||||||
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
||||||
Chunk* chunk = Chunking::chunks[chunkPos];
|
Chunk* chunk = Chunking::chunks[chunkPos];
|
||||||
for (EntityRef ref : chunk->entities) {
|
for (EntityRef ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER) {
|
if (ref.kind != EntityKind::PLAYER) {
|
||||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||||
NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum);
|
NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
|
||||||
if (path != nullptr) {
|
if (path != nullptr) {
|
||||||
Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path);
|
Transport::constructPathNPC(npc->id, path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,7 +398,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
* once we comb over mission logic more throughly
|
* once we comb over mission logic more throughly
|
||||||
*/
|
*/
|
||||||
bool mobsAreKilled = false;
|
bool mobsAreKilled = false;
|
||||||
if (task->task["m_iHTaskType"] == 5) {
|
if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
|
||||||
mobsAreKilled = true;
|
mobsAreKilled = true;
|
||||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
if (plr->tasks[i] == missionData->iTaskNum) {
|
if (plr->tasks[i] == missionData->iTaskNum) {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "JSON.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
#include <map>
|
||||||
|
|
||||||
struct Reward {
|
struct Reward {
|
||||||
int32_t id;
|
int32_t id;
|
||||||
|
802
src/MobAI.cpp
802
src/MobAI.cpp
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "JSON.hpp"
|
||||||
|
|
||||||
enum class MobState {
|
#include "Entities.hpp"
|
||||||
INACTIVE,
|
|
||||||
ROAMING,
|
#include <unordered_map>
|
||||||
COMBAT,
|
#include <string>
|
||||||
RETREAT,
|
|
||||||
DEAD
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace MobAI {
|
namespace MobAI {
|
||||||
// needs to be declared before Mob's constructor
|
void deadStep(CombatNPC* self, time_t currTime);
|
||||||
void step(CombatNPC*, time_t);
|
void combatStep(CombatNPC* self, time_t currTime);
|
||||||
};
|
void roamingStep(CombatNPC* self, time_t currTime);
|
||||||
|
void retreatStep(CombatNPC* self, time_t currTime);
|
||||||
|
|
||||||
|
void onRoamStart(CombatNPC* self, EntityRef src);
|
||||||
|
void onCombatStart(CombatNPC* self, EntityRef src);
|
||||||
|
void onRetreat(CombatNPC* self, EntityRef src);
|
||||||
|
void onDeath(CombatNPC* self, EntityRef src);
|
||||||
|
}
|
||||||
|
|
||||||
struct Mob : public CombatNPC {
|
struct Mob : public CombatNPC {
|
||||||
// general
|
// general
|
||||||
MobState state = MobState::INACTIVE;
|
|
||||||
|
|
||||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
@ -47,16 +49,13 @@ struct Mob : public CombatNPC {
|
|||||||
int offsetX = 0, offsetY = 0;
|
int offsetX = 0, offsetY = 0;
|
||||||
int groupMember[4] = {};
|
int groupMember[4] = {};
|
||||||
|
|
||||||
// for optimizing away AI in empty chunks
|
|
||||||
int playersInView = 0;
|
|
||||||
|
|
||||||
// temporary; until we're sure what's what
|
// temporary; until we're sure what's what
|
||||||
nlohmann::json data = {};
|
nlohmann::json data = {};
|
||||||
|
|
||||||
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||||
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
||||||
sightRange(d["m_iSightRange"]) {
|
sightRange(d["m_iSightRange"]) {
|
||||||
state = MobState::ROAMING;
|
state = AIState::ROAMING;
|
||||||
|
|
||||||
data = d;
|
data = d;
|
||||||
|
|
||||||
@ -65,20 +64,30 @@ struct Mob : public CombatNPC {
|
|||||||
idleRange = (int)data["m_iIdleRange"];
|
idleRange = (int)data["m_iIdleRange"];
|
||||||
level = data["m_iNpcLevel"];
|
level = data["m_iNpcLevel"];
|
||||||
|
|
||||||
roamX = spawnX = x;
|
roamX = x;
|
||||||
roamY = spawnY = y;
|
roamY = y;
|
||||||
roamZ = spawnZ = z;
|
roamZ = z;
|
||||||
|
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
|
|
||||||
appearanceData.iConditionBitFlag = 0;
|
cbf = 0;
|
||||||
|
|
||||||
// NOTE: there appear to be discrepancies in the dump
|
// NOTE: there appear to be discrepancies in the dump
|
||||||
appearanceData.iHP = maxHealth;
|
hp = maxHealth;
|
||||||
|
|
||||||
type = EntityType::MOB;
|
kind = EntityKind::MOB;
|
||||||
_stepAI = MobAI::step;
|
|
||||||
|
// AI
|
||||||
|
stateHandlers[AIState::DEAD] = MobAI::deadStep;
|
||||||
|
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
|
||||||
|
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
|
||||||
|
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
|
||||||
|
|
||||||
|
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
|
||||||
|
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
|
||||||
|
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
|
||||||
|
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
|
||||||
}
|
}
|
||||||
|
|
||||||
// constructor for /summon
|
// constructor for /summon
|
||||||
@ -89,6 +98,9 @@ struct Mob : public CombatNPC {
|
|||||||
|
|
||||||
~Mob() {}
|
~Mob() {}
|
||||||
|
|
||||||
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
auto operator[](std::string s) {
|
auto operator[](std::string s) {
|
||||||
return data[s];
|
return data[s];
|
||||||
}
|
}
|
||||||
@ -103,5 +115,4 @@ namespace MobAI {
|
|||||||
void clearDebuff(Mob *mob);
|
void clearDebuff(Mob *mob);
|
||||||
void followToCombat(Mob *mob);
|
void followToCombat(Mob *mob);
|
||||||
void groupRetreat(Mob *mob);
|
void groupRetreat(Mob *mob);
|
||||||
void enterCombat(CNSocket *sock, Mob *mob);
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Chunking.hpp"
|
|
||||||
#include "Entities.hpp"
|
|
@ -1,4 +1,8 @@
|
|||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include "Combat.hpp"
|
#include "Combat.hpp"
|
||||||
@ -20,8 +24,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
using namespace NPCManager;
|
using namespace NPCManager;
|
||||||
|
|
||||||
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
|
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
|
||||||
@ -67,7 +69,7 @@ void NPCManager::destroyNPC(int32_t id) {
|
|||||||
|
|
||||||
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
|
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
|
||||||
BaseNPC* npc = NPCs[id];
|
BaseNPC* npc = NPCs[id];
|
||||||
npc->appearanceData.iAngle = angle;
|
npc->angle = angle;
|
||||||
ChunkPos oldChunk = npc->chunkPos;
|
ChunkPos oldChunk = npc->chunkPos;
|
||||||
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
||||||
npc->x = X;
|
npc->x = X;
|
||||||
@ -79,11 +81,11 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I,
|
|||||||
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
|
void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) {
|
||||||
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
ref.sock->sendPacket(buf, type, size);
|
ref.sock->sendPacket(buf, type, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +124,6 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
// type must already be checked and updateNPCPosition() must be called on the result
|
// type must already be checked and updateNPCPosition() must be called on the result
|
||||||
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||||
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||||
#define EXTRA_HEIGHT 0
|
|
||||||
|
|
||||||
//assert(nextId < INT32_MAX);
|
//assert(nextId < INT32_MAX);
|
||||||
int id = nextId--;
|
int id = nextId--;
|
||||||
@ -130,12 +131,12 @@ BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type,
|
|||||||
BaseNPC *npc = nullptr;
|
BaseNPC *npc = nullptr;
|
||||||
|
|
||||||
if (team == 2) {
|
if (team == 2) {
|
||||||
npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id);
|
npc = new Mob(x, y, z, inst, type, NPCData[type], id);
|
||||||
|
|
||||||
// re-enable respawning, if desired
|
// re-enable respawning, if desired
|
||||||
((Mob*)npc)->summoned = !respawn;
|
((Mob*)npc)->summoned = !respawn;
|
||||||
} else
|
} else
|
||||||
npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id);
|
npc = new BaseNPC(0, inst, type, id);
|
||||||
|
|
||||||
NPCs[id] = npc;
|
NPCs[id] = npc;
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
for (int i = 0; i < req->iNPCCnt; i++) {
|
for (int i = 0; i < req->iNPCCnt; i++) {
|
||||||
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
|
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
|
||||||
updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0);
|
updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,9 +184,12 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
|||||||
if (Warps[warpId].isInstance) {
|
if (Warps[warpId].isInstance) {
|
||||||
uint64_t instanceID = Warps[warpId].instanceID;
|
uint64_t instanceID = Warps[warpId].instanceID;
|
||||||
|
|
||||||
|
Player* leader = plr;
|
||||||
|
if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock);
|
||||||
|
|
||||||
// if warp requires you to be on a mission, it's gotta be a unique instance
|
// if warp requires you to be on a mission, it's gotta be a unique instance
|
||||||
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
|
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
|
||||||
instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID
|
instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID
|
||||||
Chunking::createInstance(instanceID);
|
Chunking::createInstance(instanceID);
|
||||||
|
|
||||||
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
||||||
@ -195,14 +199,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
|||||||
plr->recallInstance = instanceID;
|
plr->recallInstance = instanceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plr->iID == plr->iIDGroup && plr->groupCnt == 1)
|
if (plr->group == nullptr)
|
||||||
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||||
else {
|
else {
|
||||||
Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||||
|
for (int i = 0; i < players.size(); i++) {
|
||||||
for (int i = 0; i < leaderPlr->groupCnt; i++) {
|
CNSocket* sockTo = players[i].sock;
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]);
|
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr || sockTo == nullptr)
|
if (otherPlr == nullptr || sockTo == nullptr)
|
||||||
continue;
|
continue;
|
||||||
@ -276,7 +279,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
|||||||
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
||||||
Chunk* chunk = *c;
|
Chunk* chunk = *c;
|
||||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||||
if (ent->type == EntityType::PLAYER)
|
if (ent->kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
||||||
@ -301,20 +304,20 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
|
|||||||
|
|
||||||
std::cout << "Lord Fuse stage two" << std::endl;
|
std::cout << "Lord Fuse stage two" << std::endl;
|
||||||
|
|
||||||
// Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData*
|
// Fuse doesn't move
|
||||||
// Blastons, Heal
|
// Blastons, Heal
|
||||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467);
|
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
|
||||||
|
|
||||||
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
plr->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// right arm, Adaptium, Stun
|
// right arm, Adaptium, Stun
|
||||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469);
|
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
|
||||||
|
|
||||||
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
plr->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// summon left arm and stage 3 body
|
// summon left arm and stage 3 body
|
||||||
@ -325,18 +328,18 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
|||||||
std::cout << "Lord Fuse stage three" << std::endl;
|
std::cout << "Lord Fuse stage three" << std::endl;
|
||||||
|
|
||||||
// Cosmix, Damage Point
|
// Cosmix, Damage Point
|
||||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468);
|
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
|
||||||
|
|
||||||
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
plr->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// Blastons, Heal
|
// Blastons, Heal
|
||||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470);
|
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
|
||||||
|
|
||||||
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
plr->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||||
@ -352,11 +355,11 @@ void NPCManager::queueNPCRemoval(int32_t id) {
|
|||||||
|
|
||||||
static void step(CNServer *serv, time_t currTime) {
|
static void step(CNServer *serv, time_t currTime) {
|
||||||
for (auto& pair : NPCs) {
|
for (auto& pair : NPCs) {
|
||||||
if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB)
|
if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
|
||||||
continue;
|
continue;
|
||||||
auto npc = (CombatNPC*)pair.second;
|
auto npc = (CombatNPC*)pair.second;
|
||||||
|
|
||||||
npc->stepAI(currTime);
|
npc->step(currTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocate all NPCs queued for removal
|
// deallocate all NPCs queued for removal
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "NPC.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
|
|
||||||
#include "JSON.hpp"
|
#include "JSON.hpp"
|
||||||
|
|
||||||
|
#include "Transport.hpp"
|
||||||
|
#include "Chunking.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#define RESURRECT_HEIGHT 400
|
#define RESURRECT_HEIGHT 400
|
||||||
|
|
||||||
@ -29,8 +30,6 @@ struct NPCEvent {
|
|||||||
: npcType(t), trigger(tr), handler(hndlr) {}
|
: npcType(t), trigger(tr), handler(hndlr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WarpLocation;
|
|
||||||
|
|
||||||
namespace NPCManager {
|
namespace NPCManager {
|
||||||
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
||||||
extern std::map<int32_t, WarpLocation> Warps;
|
extern std::map<int32_t, WarpLocation> Warps;
|
||||||
@ -44,7 +43,7 @@ namespace NPCManager {
|
|||||||
void destroyNPC(int32_t);
|
void destroyNPC(int32_t);
|
||||||
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
||||||
|
|
||||||
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
|
void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size);
|
||||||
|
|
||||||
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
||||||
|
|
||||||
|
126
src/Nanos.cpp
126
src/Nanos.cpp
@ -1,11 +1,11 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Combat.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@ -69,6 +69,43 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
|||||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
|
||||||
|
assert(skill->drainType == SkillDrainType::PASSIVE);
|
||||||
|
|
||||||
|
EntityRef self = PlayerManager::getSockFromID(plr->iID);
|
||||||
|
|
||||||
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
|
int boost = Nanos::getNanoBoost(plr) ? 3 : 0;
|
||||||
|
int value = skill->values[0][boost];
|
||||||
|
|
||||||
|
BuffStack passiveBuff = {
|
||||||
|
1, // passive nano buffs refreshed every tick
|
||||||
|
value,
|
||||||
|
self,
|
||||||
|
BuffClass::NONE, // overwritten per target
|
||||||
|
};
|
||||||
|
|
||||||
|
// for passive skills, using just the player as a target is fine
|
||||||
|
// this is because the group skill type will ignore the count,
|
||||||
|
// and the other option is single-target
|
||||||
|
std::vector<ICombatant*> targets = Abilities::matchTargets(dynamic_cast<ICombatant*>(plr), skill, 1, &plr->iID);
|
||||||
|
std::vector<ICombatant*> affected;
|
||||||
|
for (ICombatant* target : targets) {
|
||||||
|
|
||||||
|
passiveBuff.buffStackClass = target == plr ? BuffClass::NANO : BuffClass::GROUP_NANO;
|
||||||
|
if(target->addBuff(timeBuffId,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&passiveBuff)) affected.push_back(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected;
|
||||||
|
}
|
||||||
|
|
||||||
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||||
resp.iActiveNanoSlotNum = slot;
|
resp.iActiveNanoSlotNum = slot;
|
||||||
@ -82,40 +119,19 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
||||||
return; // prevent powerless nanos from summoning
|
return; // prevent powerless nanos from summoning
|
||||||
|
|
||||||
plr->nanoDrainRate = 0;
|
|
||||||
int16_t skillID = plr->Nanos[plr->activeNano].iSkillID;
|
|
||||||
|
|
||||||
// passive nano unbuffing
|
|
||||||
if (SkillTable[skillID].drainType == 2) {
|
|
||||||
std::vector<int> targetData = findTargets(plr, skillID);
|
|
||||||
|
|
||||||
for (auto& pwr : NanoPowers)
|
|
||||||
if (pwr.skillType == SkillTable[skillID].skillType)
|
|
||||||
nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nanoID >= NANO_COUNT || nanoID < 0)
|
if (nanoID >= NANO_COUNT || nanoID < 0)
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
|
|
||||||
plr->activeNano = nanoID;
|
plr->activeNano = nanoID;
|
||||||
skillID = plr->Nanos[nanoID].iSkillID;
|
sNano& nano = plr->Nanos[nanoID];
|
||||||
|
|
||||||
// passive nano buffing
|
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||||
if (SkillTable[skillID].drainType == 2) {
|
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||||
std::vector<int> targetData = findTargets(plr, skillID);
|
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
|
// passive buff effect
|
||||||
int boost = 0;
|
resp.eCSTB___Add = 1;
|
||||||
if (getNanoBoost(plr))
|
std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
|
||||||
boost = 1;
|
if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||||
|
|
||||||
for (auto& pwr : NanoPowers) {
|
|
||||||
if (pwr.skillType == SkillTable[skillID].skillType) {
|
|
||||||
resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
|
|
||||||
plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3];
|
|
||||||
|
|
||||||
pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent) // silent nano death but only for the summoning player
|
if (!silent) // silent nano death but only for the summoning player
|
||||||
@ -124,7 +140,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
||||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||||
pkt1.iPC_ID = plr->iID;
|
pkt1.iPC_ID = plr->iID;
|
||||||
pkt1.Nano = plr->Nanos[nanoID];
|
pkt1.Nano = nano;
|
||||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +227,7 @@ int Nanos::nanoStyle(int nanoID) {
|
|||||||
bool Nanos::getNanoBoost(Player* plr) {
|
bool Nanos::getNanoBoost(Player* plr) {
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
if (plr->equippedNanos[i] == plr->activeNano)
|
if (plr->equippedNanos[i] == plr->activeNano)
|
||||||
if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i))
|
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -235,18 +251,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
// Update player
|
// Update player
|
||||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||||
|
|
||||||
// Unbuff gumballs
|
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum;
|
|
||||||
if (plr->iConditionBitFlag & value1) {
|
|
||||||
int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum;
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
|
||||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
|
||||||
pkt.eTBU = 2; // eTimeBuffUpdate
|
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
|
||||||
pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1;
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsummon nano if replaced
|
// unsummon nano if replaced
|
||||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
@ -289,26 +293,24 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
int16_t nanoID = plr->activeNano;
|
// validate request check
|
||||||
int16_t skillID = plr->Nanos[nanoID].iSkillID;
|
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
||||||
|
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
||||||
|
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sNano& nano = plr->Nanos[plr->activeNano];
|
||||||
|
int16_t skillID = nano.iSkillID;
|
||||||
|
SkillData* skillData = &Abilities::SkillTable[skillID];
|
||||||
|
|
||||||
DEBUGLOG(
|
DEBUGLOG(
|
||||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||||
)
|
)
|
||||||
|
|
||||||
std::vector<int> targetData = findTargets(plr, skillID, data);
|
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
|
||||||
|
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||||
int boost = 0;
|
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||||
if (getNanoBoost(plr))
|
|
||||||
boost = 1;
|
|
||||||
|
|
||||||
plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3];
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
|
||||||
|
|
||||||
for (auto& pwr : NanoPowers)
|
|
||||||
if (pwr.skillType == SkillTable[skillID].skillType)
|
|
||||||
pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]);
|
|
||||||
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include "core/Core.hpp"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
struct NanoData {
|
struct NanoData {
|
||||||
int style;
|
int style;
|
||||||
@ -25,4 +26,5 @@ namespace Nanos {
|
|||||||
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
||||||
int nanoStyle(int nanoID);
|
int nanoStyle(int nanoID);
|
||||||
bool getNanoBoost(Player* plr);
|
bool getNanoBoost(Player* plr);
|
||||||
|
std::vector<ICombatant*> applyNanoBuff(SkillData* skill, Player* plr);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Chunking.hpp"
|
|
||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "Groups.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
struct BuffStack;
|
||||||
|
|
||||||
#define ACTIVE_MISSION_COUNT 6
|
#define ACTIVE_MISSION_COUNT 6
|
||||||
|
|
||||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||||
|
|
||||||
struct Player : public Entity {
|
struct Player : public Entity, public ICombatant {
|
||||||
int accountId = 0;
|
int accountId = 0;
|
||||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||||
int32_t iID = 0;
|
int32_t iID = 0;
|
||||||
@ -32,9 +36,8 @@ struct Player : public Entity {
|
|||||||
int8_t iPCState = 0;
|
int8_t iPCState = 0;
|
||||||
int32_t iWarpLocationFlag = 0;
|
int32_t iWarpLocationFlag = 0;
|
||||||
int64_t aSkywayLocationFlag[2] = {};
|
int64_t aSkywayLocationFlag[2] = {};
|
||||||
int32_t iConditionBitFlag = 0;
|
|
||||||
int32_t iSelfConditionBitFlag = 0;
|
|
||||||
int8_t iSpecialState = 0;
|
int8_t iSpecialState = 0;
|
||||||
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
|
|
||||||
int angle = 0;
|
int angle = 0;
|
||||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||||
@ -49,7 +52,6 @@ struct Player : public Entity {
|
|||||||
|
|
||||||
bool inCombat = false;
|
bool inCombat = false;
|
||||||
bool onMonkey = false;
|
bool onMonkey = false;
|
||||||
int nanoDrainRate = 0;
|
|
||||||
int healCooldown = 0;
|
int healCooldown = 0;
|
||||||
|
|
||||||
int pointDamage = 0;
|
int pointDamage = 0;
|
||||||
@ -65,10 +67,7 @@ struct Player : public Entity {
|
|||||||
|
|
||||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||||
|
|
||||||
int32_t iIDGroup = 0;
|
Group* group = nullptr;
|
||||||
int groupCnt = 0;
|
|
||||||
int32_t groupIDs[4] = {};
|
|
||||||
int32_t iGroupConditionBitFlag = 0;
|
|
||||||
|
|
||||||
bool notify = false;
|
bool notify = false;
|
||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
@ -85,8 +84,30 @@ struct Player : public Entity {
|
|||||||
time_t lastShot = 0;
|
time_t lastShot = 0;
|
||||||
std::vector<sItemBase> buyback = {};
|
std::vector<sItemBase> buyback = {};
|
||||||
|
|
||||||
Player() { type = EntityType::PLAYER; }
|
Player() { kind = EntityKind::PLAYER; }
|
||||||
|
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
|
|
||||||
|
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
|
virtual Buff* getBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
|
virtual bool hasBuff(int buffId) override;
|
||||||
|
virtual int getCompositeCondition() override;
|
||||||
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
virtual int heal(EntityRef src, int amt) override;
|
||||||
|
virtual bool isAlive() override;
|
||||||
|
virtual int getCurrentHP() override;
|
||||||
|
virtual int getMaxHP() override;
|
||||||
|
virtual int getLevel() override;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||||
|
virtual int32_t getCharType() override;
|
||||||
|
virtual int32_t getID() override;
|
||||||
|
virtual EntityRef getRef() override;
|
||||||
|
|
||||||
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
|
sNano* getActiveNano();
|
||||||
|
sPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
#include "core/Core.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
|
||||||
|
#include "db/Database.hpp"
|
||||||
#include "core/CNShared.hpp"
|
#include "core/CNShared.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Missions.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "Chat.hpp"
|
|
||||||
#include "Buddies.hpp"
|
|
||||||
#include "Combat.hpp"
|
#include "Combat.hpp"
|
||||||
#include "Racing.hpp"
|
#include "Racing.hpp"
|
||||||
#include "BuiltinCommands.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
#include "Missions.hpp"
|
||||||
#include "settings.hpp"
|
#include "Chat.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
#include "Buddies.hpp"
|
||||||
|
#include "BuiltinCommands.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -28,17 +23,12 @@ using namespace PlayerManager;
|
|||||||
|
|
||||||
std::map<CNSocket*, Player*> PlayerManager::players;
|
std::map<CNSocket*, Player*> PlayerManager::players;
|
||||||
|
|
||||||
static void addPlayer(CNSocket* key, Player& plr) {
|
static void addPlayer(CNSocket* key, Player *plr) {
|
||||||
Player *p = new Player();
|
players[key] = plr;
|
||||||
|
plr->chunkPos = Chunking::INVALID_CHUNK;
|
||||||
|
plr->lastHeartbeat = 0;
|
||||||
|
|
||||||
// copy object into heap memory
|
std::cout << getPlayerName(plr) << " has joined!" << std::endl;
|
||||||
*p = plr;
|
|
||||||
|
|
||||||
players[key] = p;
|
|
||||||
p->chunkPos = Chunking::INVALID_CHUNK;
|
|
||||||
p->lastHeartbeat = 0;
|
|
||||||
|
|
||||||
std::cout << getPlayerName(p) << " has joined!" << std::endl;
|
|
||||||
std::cout << players.size() << " players" << std::endl;
|
std::cout << players.size() << " players" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +36,13 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
Player* plr = getPlayer(key);
|
Player* plr = getPlayer(key);
|
||||||
uint64_t fromInstance = plr->instanceID;
|
uint64_t fromInstance = plr->instanceID;
|
||||||
|
|
||||||
Groups::groupKickPlayer(plr);
|
// free buff memory
|
||||||
|
for(auto buffEntry : plr->buffs)
|
||||||
|
delete buffEntry.second;
|
||||||
|
|
||||||
|
// leave group
|
||||||
|
if(plr->group != nullptr)
|
||||||
|
Groups::groupKick(plr->group, key);
|
||||||
|
|
||||||
// remove player's bullets
|
// remove player's bullets
|
||||||
Combat::Bullets.erase(plr->iID);
|
Combat::Bullets.erase(plr->iID);
|
||||||
@ -70,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
// if the player was in a lair, clean it up
|
// if the player was in a lair, clean it up
|
||||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||||
|
|
||||||
// remove player's buffs from the server
|
|
||||||
auto it = Eggs::EggBuffs.begin();
|
|
||||||
while (it != Eggs::EggBuffs.end()) {
|
|
||||||
if (it->first.first == key) {
|
|
||||||
it = Eggs::EggBuffs.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << players.size() << " players" << std::endl;
|
std::cout << players.size() << " players" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,69 +210,72 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for convenience
|
Player *plr = new Player();
|
||||||
Player& plr = lm->plr;
|
Database::getPlayer(plr, lm->playerId);
|
||||||
|
|
||||||
plr.groupCnt = 1;
|
|
||||||
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
|
|
||||||
|
|
||||||
// check if account is already in use
|
// check if account is already in use
|
||||||
if (isAccountInUse(plr.accountId)) {
|
if (isAccountInUse(plr->accountId)) {
|
||||||
// kick the other player
|
// kick the other player
|
||||||
exitDuplicate(plr.accountId);
|
exitDuplicate(plr->accountId);
|
||||||
|
|
||||||
|
// re-read the player from disk, in case it was just flushed
|
||||||
|
*plr = {};
|
||||||
|
Database::getPlayer(plr, lm->playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.iID = plr.iID;
|
plr->group = nullptr;
|
||||||
|
|
||||||
|
response.iID = plr->iID;
|
||||||
response.uiSvrTime = getTime();
|
response.uiSvrTime = getTime();
|
||||||
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
response.PCLoadData2CL.iUserLevel = plr->accountLevel;
|
||||||
response.PCLoadData2CL.iHP = plr.HP;
|
response.PCLoadData2CL.iHP = plr->HP;
|
||||||
response.PCLoadData2CL.iLevel = plr.level;
|
response.PCLoadData2CL.iLevel = plr->level;
|
||||||
response.PCLoadData2CL.iCandy = plr.money;
|
response.PCLoadData2CL.iCandy = plr->money;
|
||||||
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
|
response.PCLoadData2CL.iFusionMatter = plr->fusionmatter;
|
||||||
response.PCLoadData2CL.iMentor = plr.mentor;
|
response.PCLoadData2CL.iMentor = plr->mentor;
|
||||||
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
||||||
response.PCLoadData2CL.iX = plr.x;
|
response.PCLoadData2CL.iX = plr->x;
|
||||||
response.PCLoadData2CL.iY = plr.y;
|
response.PCLoadData2CL.iY = plr->y;
|
||||||
response.PCLoadData2CL.iZ = plr.z;
|
response.PCLoadData2CL.iZ = plr->z;
|
||||||
response.PCLoadData2CL.iAngle = plr.angle;
|
response.PCLoadData2CL.iAngle = plr->angle;
|
||||||
response.PCLoadData2CL.iBatteryN = plr.batteryN;
|
response.PCLoadData2CL.iBatteryN = plr->batteryN;
|
||||||
response.PCLoadData2CL.iBatteryW = plr.batteryW;
|
response.PCLoadData2CL.iBatteryW = plr->batteryW;
|
||||||
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
||||||
|
|
||||||
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
|
response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag;
|
||||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
|
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
|
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||||
|
|
||||||
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
||||||
response.PCLoadData2CL.iFatigue = 50;
|
response.PCLoadData2CL.iFatigue = 50;
|
||||||
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
response.PCLoadData2CL.PCStyle = plr->PCStyle;
|
||||||
|
|
||||||
// client doesnt read this, it gets it from charinfo
|
// client doesnt read this, it gets it from charinfo
|
||||||
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||||
// inventory
|
// inventory
|
||||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||||
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
|
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||||
// quest inventory
|
// quest inventory
|
||||||
for (int i = 0; i < AQINVEN_COUNT; i++)
|
for (int i = 0; i < AQINVEN_COUNT; i++)
|
||||||
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
|
response.PCLoadData2CL.aQInven[i] = plr->QInven[i];
|
||||||
// nanos
|
// nanos
|
||||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||||
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i];
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
|
response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i];
|
||||||
}
|
}
|
||||||
// missions in progress
|
// missions in progress
|
||||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
if (plr.tasks[i] == 0)
|
if (plr->tasks[i] == 0)
|
||||||
break;
|
break;
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i];
|
||||||
TaskData &task = *Missions::Tasks[plr.tasks[i]];
|
TaskData &task = *Missions::Tasks[plr->tasks[i]];
|
||||||
for (int j = 0; j < 3; j++) {
|
for (int j = 0; j < 3; j++) {
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j];
|
||||||
/*
|
/*
|
||||||
* client doesn't care about NeededItem ID and Count,
|
* client doesn't care about NeededItem ID and Count,
|
||||||
* it gets Count from Quest Inventory
|
* it gets Count from Quest Inventory
|
||||||
@ -296,12 +285,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID;
|
||||||
|
|
||||||
// completed missions
|
// completed missions
|
||||||
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computress tips
|
// Computress tips
|
||||||
@ -310,11 +299,11 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
|
response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0];
|
||||||
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
|
response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
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->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
||||||
sock->setFEKey(lm->FEKey);
|
sock->setFEKey(lm->FEKey);
|
||||||
@ -325,14 +314,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
||||||
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
||||||
|
|
||||||
// copy Player object into the shard
|
// transfer ownership of Player object into the shard (still valid in this function though)
|
||||||
addPlayer(sock, plr);
|
addPlayer(sock, plr);
|
||||||
|
|
||||||
// check if there is an expiring vehicle
|
// check if there is an expiring vehicle
|
||||||
Items::checkItemExpire(sock, getPlayer(sock));
|
Items::checkItemExpire(sock, plr);
|
||||||
|
|
||||||
// set player equip stats
|
// set player equip stats
|
||||||
Items::setItemStats(getPlayer(sock));
|
Items::setItemStats(plr);
|
||||||
|
|
||||||
Missions::failInstancedMissions(sock);
|
Missions::failInstancedMissions(sock);
|
||||||
|
|
||||||
@ -343,9 +332,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
for (auto& pair : players)
|
for (auto& pair : players)
|
||||||
if (pair.second->notify)
|
if (pair.second->notify)
|
||||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
|
||||||
|
|
||||||
// deallocate lm (and therefore the plr object)
|
// deallocate lm
|
||||||
delete lm;
|
delete lm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +343,7 @@ void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, siz
|
|||||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ref.sock->sendPacket(buf, type, size);
|
ref.sock->sendPacket(buf, type, size);
|
||||||
@ -407,21 +396,22 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
int activeSlot = -1;
|
int activeSlot = -1;
|
||||||
bool move = false;
|
bool move = false;
|
||||||
|
|
||||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
switch ((ePCRegenType)reviveData->iRegenType) {
|
||||||
// nano revive
|
case ePCRegenType::HereByPhoenix: // nano revive
|
||||||
|
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||||
|
return; // sanity check
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
|
// fallthrough
|
||||||
|
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
break;
|
||||||
} else if (reviveData->iRegenType == 4) {
|
|
||||||
// revived by group member's nano
|
default: // plain respawn
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
} else if (reviveData->iRegenType == 5) {
|
// fallthrough
|
||||||
// warp away
|
case ePCRegenType::Unstick: // warp away
|
||||||
move = true;
|
move = true;
|
||||||
} else {
|
break;
|
||||||
// plain respawn
|
|
||||||
move = true;
|
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@ -473,11 +463,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||||
|
|
||||||
Player *otherPlr = getPlayerFromID(plr->iIDGroup);
|
if (plr->group != nullptr) {
|
||||||
if (otherPlr != nullptr) {
|
|
||||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
|
||||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
|
||||||
|
|
||||||
|
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||||
@ -668,16 +656,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last
|
|||||||
}
|
}
|
||||||
|
|
||||||
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
|
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
|
||||||
switch (by) {
|
switch ((eCN_GM_TargetSearchBy)by) {
|
||||||
case eCN_GM_TargetSearchBy__PC_ID:
|
case eCN_GM_TargetSearchBy::PC_ID:
|
||||||
assert(id != 0);
|
assert(id != 0);
|
||||||
return getSockFromID(id);
|
return getSockFromID(id);
|
||||||
case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id
|
case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id
|
||||||
assert(uid != 0);
|
assert(uid != 0);
|
||||||
for (auto& pair : players)
|
for (auto& pair : players)
|
||||||
if (pair.second->accountId == uid)
|
if (pair.second->accountId == uid)
|
||||||
return pair.first;
|
return pair.first;
|
||||||
case eCN_GM_TargetSearchBy__PC_Name:
|
case eCN_GM_TargetSearchBy::PC_Name:
|
||||||
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
|
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
|
||||||
return getSockFromName(firstname, lastname);
|
return getSockFromName(firstname, lastname);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Chunking.hpp"
|
|
||||||
|
|
||||||
#include <utility>
|
#include "Player.hpp"
|
||||||
|
#include "Chunking.hpp"
|
||||||
|
#include "Transport.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
struct WarpLocation;
|
|
||||||
|
|
||||||
namespace PlayerManager {
|
namespace PlayerManager {
|
||||||
extern std::map<CNSocket*, Player*> players;
|
extern std::map<CNSocket*, Player*> players;
|
||||||
void init();
|
void init();
|
||||||
@ -43,7 +42,7 @@ namespace PlayerManager {
|
|||||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ref.sock->sendPacket(pkt, type);
|
ref.sock->sendPacket(pkt, type);
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#include "PlayerMovement.hpp"
|
#include "PlayerMovement.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
@ -33,7 +36,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
// [gruntwork] check if player has a follower and move it
|
// [gruntwork] check if player has a follower and move it
|
||||||
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
||||||
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
|
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
|
||||||
Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points
|
Transport::NPCQueues.erase(follower->id); // erase existing points
|
||||||
std::queue<Vec3> queue;
|
std::queue<Vec3> queue;
|
||||||
Vec3 from = { follower->x, follower->y, follower->z };
|
Vec3 from = { follower->x, follower->y, follower->z };
|
||||||
float drag = 0.95f; // this ensures that they don't bump into the player
|
float drag = 0.95f; // this ensures that they don't bump into the player
|
||||||
@ -45,7 +48,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||||
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
|
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
|
||||||
Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue;
|
Transport::NPCQueues[follower->id] = queue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Racing.hpp"
|
#include "Racing.hpp"
|
||||||
|
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "NPCManager.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "NPCManager.hpp"
|
|
||||||
|
|
||||||
using namespace Racing;
|
using namespace Racing;
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
struct EPInfo {
|
struct EPInfo {
|
||||||
int zoneX, zoneY, EPID, maxScore, maxTime;
|
int zoneX, zoneY, EPID, maxScore, maxTime;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Rand {
|
namespace Rand {
|
||||||
extern std::unique_ptr<std::mt19937> generator;
|
extern std::unique_ptr<std::mt19937> generator;
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "settings.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Chunking.hpp"
|
#include "Items.hpp"
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Racing.hpp"
|
|
||||||
#include "Vendors.hpp"
|
#include "Vendors.hpp"
|
||||||
|
#include "Racing.hpp"
|
||||||
|
#include "Nanos.hpp"
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -37,6 +33,22 @@ public:
|
|||||||
const char *what() const throw() { return msg.c_str(); }
|
const char *what() const throw() { return msg.c_str(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
|
||||||
|
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
|
||||||
|
* silently dropped from the gruntwork file, which would be confusing in situations
|
||||||
|
* where a gruntwork file for the wrong game build was accidentally loaded.
|
||||||
|
*/
|
||||||
|
static void ensureValidNPCType(int type, std::string filename) {
|
||||||
|
// last known NPC type
|
||||||
|
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
|
||||||
|
|
||||||
|
if (type > npcLimit) {
|
||||||
|
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a full and properly-paced path by interpolating between keyframes.
|
* Create a full and properly-paced path by interpolating between keyframes.
|
||||||
*/
|
*/
|
||||||
@ -215,18 +227,34 @@ static void loadXDT(json& xdtData) {
|
|||||||
// load nano powers
|
// load nano powers
|
||||||
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
||||||
|
|
||||||
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) {
|
for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) {
|
||||||
auto skills = _skills.value();
|
auto skill = _skill.value();
|
||||||
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] };
|
SkillData skillData = {
|
||||||
|
skill["m_iSkillType"],
|
||||||
|
skill["m_iEffectTarget"],
|
||||||
|
skill["m_iEffectType"],
|
||||||
|
skill["m_iTargetType"],
|
||||||
|
skill["m_iBatteryDrainType"],
|
||||||
|
skill["m_iEffectArea"]
|
||||||
|
};
|
||||||
|
|
||||||
|
skillData.valueTypes[0] = skill["m_iValueA_Type"];
|
||||||
|
skillData.valueTypes[1] = skill["m_iValueB_Type"];
|
||||||
|
skillData.valueTypes[2] = skill["m_iValueC_Type"];
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i];
|
skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i];
|
||||||
skillData.durationTime[i] = skills["m_iDurationTime"][i];
|
skillData.durationTime[i] = skill["m_iDurationTime"][i];
|
||||||
skillData.powerIntensity[i] = skills["m_iValueA"][i];
|
|
||||||
}
|
skillData.values[0][i] = skill["m_iValueA"][i];
|
||||||
Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData;
|
skillData.values[1][i] = skill["m_iValueB"][i];
|
||||||
|
skillData.values[2][i] = skill["m_iValueC"][i];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl;
|
Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl;
|
||||||
|
|
||||||
// load EP data
|
// load EP data
|
||||||
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
|
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
|
||||||
@ -298,10 +326,10 @@ static void loadPaths(json& pathData, int32_t* nextId) {
|
|||||||
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
|
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
|
||||||
passedDistance -= SLIDER_GAP_SIZE; // step down
|
passedDistance -= SLIDER_GAP_SIZE; // step down
|
||||||
// spawn a slider
|
// spawn a slider
|
||||||
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
||||||
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider;
|
NPCManager::NPCs[slider->id] = slider;
|
||||||
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0);
|
NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0);
|
||||||
Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route;
|
Transport::NPCQueues[slider->id] = route;
|
||||||
}
|
}
|
||||||
// rotate
|
// rotate
|
||||||
route.pop();
|
route.pop();
|
||||||
@ -643,7 +671,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
||||||
|
|
||||||
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
|
||||||
NPCManager::NPCs[id] = addEgg;
|
NPCManager::NPCs[id] = addEgg;
|
||||||
eggCount++;
|
eggCount++;
|
||||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||||
@ -662,6 +690,8 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
|||||||
* Load gruntwork output, if it exists
|
* Load gruntwork output, if it exists
|
||||||
*/
|
*/
|
||||||
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||||
|
if (gruntwork.is_null())
|
||||||
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto paths = gruntwork["paths"];
|
auto paths = gruntwork["paths"];
|
||||||
@ -711,8 +741,8 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||||
|
if (gruntwork.is_null())
|
||||||
if (gruntwork.is_null()) return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// skyway paths
|
// skyway paths
|
||||||
@ -737,7 +767,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||||
continue; // NPC not found
|
continue; // NPC not found
|
||||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||||
npc->appearanceData.iAngle = angle;
|
npc->angle = angle;
|
||||||
|
|
||||||
RunningNPCRotations[npcID] = angle;
|
RunningNPCRotations[npcID] = angle;
|
||||||
}
|
}
|
||||||
@ -750,8 +780,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||||
continue; // NPC not found
|
continue; // NPC not found
|
||||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y,
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
|
||||||
npc->z, instanceID, npc->appearanceData.iAngle);
|
npc->z, instanceID, npc->angle);
|
||||||
|
|
||||||
RunningNPCMapNumbers[npcID] = instanceID;
|
RunningNPCMapNumbers[npcID] = instanceID;
|
||||||
}
|
}
|
||||||
@ -764,6 +794,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
||||||
|
|
||||||
|
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
||||||
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
||||||
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
||||||
@ -771,18 +803,21 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
// re-enable respawning
|
// re-enable respawning
|
||||||
((Mob*)npc)->summoned = false;
|
((Mob*)npc)->summoned = false;
|
||||||
} else {
|
} else {
|
||||||
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
|
npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id);
|
||||||
}
|
}
|
||||||
|
|
||||||
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
|
NPCManager::NPCs[npc->id] = npc;
|
||||||
RunningMobs[npc->appearanceData.iNPC_ID] = npc;
|
RunningMobs[npc->id] = npc;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mob groups
|
// mob groups
|
||||||
auto groups = gruntwork["groups"];
|
auto groups = gruntwork["groups"];
|
||||||
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
||||||
auto leader = _group.value();
|
auto leader = _group.value();
|
||||||
|
|
||||||
|
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||||
|
|
||||||
@ -803,6 +838,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int followerCount = 0;
|
int followerCount = 0;
|
||||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||||
auto follower = _fol.value();
|
auto follower = _fol.value();
|
||||||
|
|
||||||
|
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||||
|
|
||||||
@ -814,7 +852,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
|
|
||||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
||||||
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID;
|
tmpFol->groupLeader = tmp->id;
|
||||||
tmp->groupMember[followerCount++] = *nextId;
|
tmp->groupMember[followerCount++] = *nextId;
|
||||||
|
|
||||||
(*nextId)--;
|
(*nextId)--;
|
||||||
@ -824,7 +862,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
|
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running
|
RunningGroups[tmp->id] = tmp; // store as running
|
||||||
}
|
}
|
||||||
|
|
||||||
auto eggs = gruntwork["eggs"];
|
auto eggs = gruntwork["eggs"];
|
||||||
@ -833,7 +871,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
||||||
|
|
||||||
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
|
||||||
NPCManager::NPCs[id] = addEgg;
|
NPCManager::NPCs[id] = addEgg;
|
||||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||||
RunningEggs[id] = addEgg;
|
RunningEggs[id] = addEgg;
|
||||||
@ -859,16 +897,15 @@ static void loadNPCs(json& npcData) {
|
|||||||
npcID += NPC_ID_OFFSET;
|
npcID += NPC_ID_OFFSET;
|
||||||
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||||
int type = (int)npc["iNPCType"];
|
int type = (int)npc["iNPCType"];
|
||||||
if (NPCManager::NPCData[type].is_null()) {
|
|
||||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
ensureValidNPCType(type, settings::NPCJSON);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#ifdef ACADEMY
|
#ifdef ACADEMY
|
||||||
// do not spawn NPCs in the future
|
// do not spawn NPCs in the future
|
||||||
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
||||||
continue;
|
continue;
|
||||||
#endif
|
#endif
|
||||||
BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID);
|
BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID);
|
||||||
|
|
||||||
NPCManager::NPCs[npcID] = tmp;
|
NPCManager::NPCs[npcID] = tmp;
|
||||||
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
||||||
@ -904,10 +941,9 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||||
npcID += MOB_ID_OFFSET;
|
npcID += MOB_ID_OFFSET;
|
||||||
int type = (int)npc["iNPCType"];
|
int type = (int)npc["iNPCType"];
|
||||||
if (NPCManager::NPCData[type].is_null()) {
|
|
||||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
ensureValidNPCType(type, settings::MOBJSON);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto td = NPCManager::NPCData[type];
|
auto td = NPCManager::NPCData[type];
|
||||||
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||||
|
|
||||||
@ -935,7 +971,10 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
||||||
auto leader = _group.value();
|
auto leader = _group.value();
|
||||||
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||||
|
|
||||||
leadID += MOB_GROUP_ID_OFFSET;
|
leadID += MOB_GROUP_ID_OFFSET;
|
||||||
|
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
|
||||||
|
|
||||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||||
auto followers = leader["aFollowers"];
|
auto followers = leader["aFollowers"];
|
||||||
@ -965,6 +1004,9 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
int followerCount = 0;
|
int followerCount = 0;
|
||||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||||
auto follower = _fol.value();
|
auto follower = _fol.value();
|
||||||
|
|
||||||
|
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
|
||||||
|
|
||||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||||
|
|
||||||
@ -973,7 +1015,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
|
|
||||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
||||||
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID;
|
tmpFol->groupLeader = tmp->id;
|
||||||
tmp->groupMember[followerCount++] = *nextId;
|
tmp->groupMember[followerCount++] = *nextId;
|
||||||
|
|
||||||
(*nextId)--;
|
(*nextId)--;
|
||||||
@ -1070,19 +1112,39 @@ void TableData::init() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// load JSON data into tables
|
// load JSON data into tables
|
||||||
std::ifstream fstream;
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
std::pair<json*, std::string>& table = tables[i];
|
std::pair<json*, std::string>& table = tables[i];
|
||||||
|
|
||||||
|
// scope for fstream
|
||||||
|
{
|
||||||
|
std::ifstream fstream;
|
||||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||||
if (!fstream.fail()) {
|
|
||||||
fstream >> *table.first; // load file contents into table
|
// did we fail to open the file?
|
||||||
} else {
|
if (fstream.fail()) {
|
||||||
if (table.first != &gruntwork) { // gruntwork isn't critical
|
// gruntwork isn't critical
|
||||||
|
if (table.first == &gruntwork)
|
||||||
|
continue;
|
||||||
|
|
||||||
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is the file empty?
|
||||||
|
if (fstream.peek() == std::ifstream::traits_type::eof()) {
|
||||||
|
// tolerate empty gruntwork file
|
||||||
|
if (table.first == &gruntwork) {
|
||||||
|
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load file contents into table
|
||||||
|
fstream >> *table.first;
|
||||||
}
|
}
|
||||||
fstream.close();
|
|
||||||
|
|
||||||
// patching: load each patch directory specified in the config file
|
// patching: load each patch directory specified in the config file
|
||||||
|
|
||||||
@ -1097,11 +1159,11 @@ void TableData::init() {
|
|||||||
std::string patchModuleName = *it;
|
std::string patchModuleName = *it;
|
||||||
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
||||||
try {
|
try {
|
||||||
|
std::ifstream fstream;
|
||||||
fstream.open(patchFile);
|
fstream.open(patchFile);
|
||||||
fstream >> patch; // load into temporary json object
|
fstream >> patch; // load into temporary json object
|
||||||
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
||||||
patchJSON(table.first, &patch); // patch
|
patchJSON(table.first, &patch); // patch
|
||||||
fstream.close();
|
|
||||||
} catch (const std::exception& err) {
|
} catch (const std::exception& err) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
@ -1176,7 +1238,7 @@ void TableData::flush() {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
if (npc->type == EntityType::MOB) {
|
if (npc->kind == EntityKind::MOB) {
|
||||||
Mob *m = (Mob*)npc;
|
Mob *m = (Mob*)npc;
|
||||||
x = m->spawnX;
|
x = m->spawnX;
|
||||||
y = m->spawnY;
|
y = m->spawnY;
|
||||||
@ -1188,13 +1250,13 @@ void TableData::flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this format deviates slightly from the one in mobs.json
|
// NOTE: this format deviates slightly from the one in mobs.json
|
||||||
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
|
mob["iNPCType"] = (int)npc->type;
|
||||||
mob["iX"] = x;
|
mob["iX"] = x;
|
||||||
mob["iY"] = y;
|
mob["iY"] = y;
|
||||||
mob["iZ"] = z;
|
mob["iZ"] = z;
|
||||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
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
|
// 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;
|
mob["iAngle"] = npc->angle;
|
||||||
|
|
||||||
// it's called mobs, but really it's everything
|
// it's called mobs, but really it's everything
|
||||||
gruntwork["mobs"].push_back(mob);
|
gruntwork["mobs"].push_back(mob);
|
||||||
@ -1209,19 +1271,19 @@ void TableData::flush() {
|
|||||||
|
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
std::vector<Mob*> followers;
|
std::vector<Mob*> followers;
|
||||||
if (npc->type == EntityType::MOB) {
|
if (npc->kind == EntityKind::MOB) {
|
||||||
Mob* m = (Mob*)npc;
|
Mob* m = (Mob*)npc;
|
||||||
x = m->spawnX;
|
x = m->spawnX;
|
||||||
y = m->spawnY;
|
y = m->spawnY;
|
||||||
z = m->spawnZ;
|
z = m->spawnZ;
|
||||||
if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader
|
if (m->groupLeader != m->id) { // make sure this is a leader
|
||||||
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
|
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add follower data to vector; go until OOB or until follower ID is 0
|
// add follower data to vector; go until OOB or until follower ID is 0
|
||||||
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
|
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
|
||||||
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) {
|
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1235,13 +1297,13 @@ void TableData::flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this format deviates slightly from the one in mobs.json
|
// NOTE: this format deviates slightly from the one in mobs.json
|
||||||
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
|
mob["iNPCType"] = (int)npc->type;
|
||||||
mob["iX"] = x;
|
mob["iX"] = x;
|
||||||
mob["iY"] = y;
|
mob["iY"] = y;
|
||||||
mob["iZ"] = z;
|
mob["iZ"] = z;
|
||||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
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
|
// 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;
|
mob["iAngle"] = npc->angle;
|
||||||
|
|
||||||
// followers
|
// followers
|
||||||
while (followers.size() > 0) {
|
while (followers.size() > 0) {
|
||||||
@ -1250,7 +1312,7 @@ void TableData::flush() {
|
|||||||
|
|
||||||
// populate JSON entry
|
// populate JSON entry
|
||||||
json fol;
|
json fol;
|
||||||
fol["iNPCType"] = follower->appearanceData.iNPCType;
|
fol["iNPCType"] = follower->type;
|
||||||
fol["iOffsetX"] = follower->offsetX;
|
fol["iOffsetX"] = follower->offsetX;
|
||||||
fol["iOffsetY"] = follower->offsetY;
|
fol["iOffsetY"] = follower->offsetY;
|
||||||
|
|
||||||
@ -1275,7 +1337,7 @@ void TableData::flush() {
|
|||||||
int mapnum = MAPNUM(npc->instanceID);
|
int mapnum = MAPNUM(npc->instanceID);
|
||||||
if (mapnum != 0)
|
if (mapnum != 0)
|
||||||
egg["iMapNum"] = mapnum;
|
egg["iMapNum"] = mapnum;
|
||||||
egg["iType"] = npc->appearanceData.iNPCType;
|
egg["iType"] = npc->type;
|
||||||
|
|
||||||
gruntwork["eggs"].push_back(egg);
|
gruntwork["eggs"].push_back(egg);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
#include "JSON.hpp"
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "Transport.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// these are added to the NPC's static key to avoid collisions
|
// these are added to the NPC's static key to avoid collisions
|
||||||
const int NPC_ID_OFFSET = 1;
|
const int NPC_ID_OFFSET = 1;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
#include "Trading.hpp"
|
#include "Trading.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
using namespace Trading;
|
using namespace Trading;
|
||||||
|
|
||||||
@ -269,6 +273,8 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
|||||||
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Database::commitTrade(plr, plr2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Items.hpp"
|
|
||||||
|
|
||||||
namespace Trading {
|
namespace Trading {
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
@ -1,9 +1,12 @@
|
|||||||
|
#include "Transport.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "Combat.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -257,13 +260,13 @@ static void stepNPCPathing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip if not simulating mobs
|
// skip if not simulating mobs
|
||||||
if (npc->type == EntityType::MOB && !MobAI::simulateMobs) {
|
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
||||||
it++;
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not roam if not roaming
|
// do not roam if not roaming
|
||||||
if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
||||||
it++;
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -276,15 +279,15 @@ static void stepNPCPathing() {
|
|||||||
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
||||||
|
|
||||||
// update NPC location to update viewables
|
// update NPC location to update viewables
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
|
||||||
|
|
||||||
// TODO: move walking logic into Entity stack
|
// TODO: move walking logic into Entity stack
|
||||||
switch (npc->type) {
|
switch (npc->kind) {
|
||||||
case EntityType::BUS:
|
case EntityKind::BUS:
|
||||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||||
|
|
||||||
busMove.eTT = 3;
|
busMove.eTT = 3;
|
||||||
busMove.iT_ID = npc->appearanceData.iNPC_ID;
|
busMove.iT_ID = npc->id;
|
||||||
busMove.iMoveStyle = 0; // ???
|
busMove.iMoveStyle = 0; // ???
|
||||||
busMove.iToX = point.x;
|
busMove.iToX = point.x;
|
||||||
busMove.iToY = point.y;
|
busMove.iToY = point.y;
|
||||||
@ -293,12 +296,12 @@ static void stepNPCPathing() {
|
|||||||
|
|
||||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||||
break;
|
break;
|
||||||
case EntityType::MOB:
|
case EntityKind::MOB:
|
||||||
MobAI::incNextMovement((Mob*)npc);
|
MobAI::incNextMovement((Mob*)npc);
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||||
move.iNPC_ID = npc->appearanceData.iNPC_ID;
|
move.iNPC_ID = npc->id;
|
||||||
move.iMoveStyle = 0; // ???
|
move.iMoveStyle = 0; // ???
|
||||||
move.iToX = point.x;
|
move.iToX = point.x;
|
||||||
move.iToY = point.y;
|
move.iToY = point.y;
|
||||||
@ -385,7 +388,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
|
|||||||
|
|
||||||
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
||||||
BaseNPC* npc = NPCManager::NPCs[id];
|
BaseNPC* npc = NPCManager::NPCs[id];
|
||||||
if (npc->type == EntityType::MOB)
|
if (npc->kind == EntityKind::MOB)
|
||||||
((Mob*)(npc))->staticPath = true;
|
((Mob*)(npc))->staticPath = true;
|
||||||
npc->loopingPath = path->isLoop;
|
npc->loopingPath = path->isLoop;
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
const int SLIDER_SPEED = 1200;
|
const int SLIDER_SPEED = 1200;
|
||||||
const int SLIDER_STOP_TICKS = 16;
|
const int SLIDER_STOP_TICKS = 16;
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
#include "Vendors.hpp"
|
#include "Vendors.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
|
||||||
|
// 7 days
|
||||||
|
#define VEHICLE_EXPIRY_DURATION 604800
|
||||||
|
|
||||||
using namespace Vendors;
|
using namespace Vendors;
|
||||||
|
|
||||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||||
@ -53,8 +61,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
// if vehicle
|
// if vehicle
|
||||||
if (req->Item.iType == 10) {
|
if (req->Item.iType == 10) {
|
||||||
// set time limit: current time + 7days
|
// set time limit: current time + expiry duration
|
||||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slot != req->iInvenSlotNum) {
|
if (slot != req->iInvenSlotNum) {
|
||||||
@ -224,12 +232,20 @@ static void vendorTable(CNSocket* sock, CNPacketData* data) {
|
|||||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
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
|
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||||
sItemBase base;
|
sItemBase base = {};
|
||||||
base.iID = listings[i].id;
|
base.iID = listings[i].id;
|
||||||
base.iOpt = 0;
|
|
||||||
base.iTimeLimit = 0;
|
|
||||||
base.iType = listings[i].type;
|
base.iType = listings[i].type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set vehicle expiry value.
|
||||||
|
*
|
||||||
|
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
|
||||||
|
* a duration, unlike in most other contexts where it contains the
|
||||||
|
* expiration timestamp.
|
||||||
|
*/
|
||||||
|
if (listings[i].type == 10)
|
||||||
|
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
|
||||||
|
|
||||||
sItemVendor vItem;
|
sItemVendor vItem;
|
||||||
vItem.item = base;
|
vItem.item = base;
|
||||||
vItem.iSortNum = listings[i].sort;
|
vItem.iSortNum = listings[i].sort;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include "Items.hpp"
|
#include <vector>
|
||||||
#include "PlayerManager.hpp"
|
#include <map>
|
||||||
|
|
||||||
struct VendorListing {
|
struct VendorListing {
|
||||||
int sort, type, id;
|
int sort, type, id;
|
||||||
|
@ -41,7 +41,8 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
|||||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||||
uint64_t num = (uint64_t)(iv1 + 1);
|
uint64_t num = (uint64_t)(iv1 + 1);
|
||||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||||
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
uint64_t dEKey;
|
||||||
|
memcpy(&dEKey, defaultKey, sizeof(dEKey));
|
||||||
return dEKey * (uTime * num * num2);
|
return dEKey * (uTime * num * num2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
|
|||||||
// ========================================================[[ CNSocket ]]========================================================
|
// ========================================================[[ CNSocket ]]========================================================
|
||||||
|
|
||||||
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
||||||
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||||
@ -109,7 +110,11 @@ bool CNSocket::isAlive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CNSocket::kill() {
|
void CNSocket::kill() {
|
||||||
|
if (!alive)
|
||||||
|
return;
|
||||||
|
|
||||||
alive = false;
|
alive = false;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
shutdown(sock, SD_BOTH);
|
shutdown(sock, SD_BOTH);
|
||||||
closesocket(sock);
|
closesocket(sock);
|
||||||
@ -241,9 +246,10 @@ void CNSocket::step() {
|
|||||||
if (readSize <= 0) {
|
if (readSize <= 0) {
|
||||||
// we aren't reading a packet yet, try to start looking for one
|
// we aren't reading a packet yet, try to start looking for one
|
||||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||||
if (recved == 0) {
|
if (recved >= 0 && recved < sizeof(int32_t)) {
|
||||||
// the socket was closed normally
|
// too little data for readSize or the socket was closed normally (when 0 bytes were read)
|
||||||
kill();
|
kill();
|
||||||
|
return;
|
||||||
} else if (!SOCKETERROR(recved)) {
|
} else if (!SOCKETERROR(recved)) {
|
||||||
// we got our packet size!!!!
|
// we got our packet size!!!!
|
||||||
readSize = *((int32_t*)readBuffer);
|
readSize = *((int32_t*)readBuffer);
|
||||||
@ -264,11 +270,12 @@ void CNSocket::step() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (readSize > 0 && readBufferIndex < readSize) {
|
if (readSize > 0 && readBufferIndex < readSize) {
|
||||||
// read until the end of the packet! (or at least try too)
|
// read until the end of the packet (or at least try to)
|
||||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||||
if (recved == 0) {
|
if (recved == 0) {
|
||||||
// the socket was closed normally
|
// the socket was closed normally
|
||||||
kill();
|
kill();
|
||||||
|
return;
|
||||||
} else if (!SOCKETERROR(recved))
|
} else if (!SOCKETERROR(recved))
|
||||||
readBufferIndex += recved;
|
readBufferIndex += recved;
|
||||||
else if (OF_ERRNO != OF_EWOULD) {
|
else if (OF_ERRNO != OF_EWOULD) {
|
||||||
@ -411,9 +418,9 @@ void CNServer::addPollFD(SOCKET s) {
|
|||||||
fds.push_back({s, POLLIN});
|
fds.push_back({s, POLLIN});
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNServer::removePollFD(int i) {
|
void CNServer::removePollFD(int fd) {
|
||||||
auto it = fds.begin();
|
auto it = fds.begin();
|
||||||
while (it != fds.end() && it->fd != fds[i].fd)
|
while (it != fds.end() && it->fd != fd)
|
||||||
it++;
|
it++;
|
||||||
assert(it != fds.end());
|
assert(it != fds.end());
|
||||||
|
|
||||||
@ -458,7 +465,7 @@ void CNServer::start() {
|
|||||||
if (!setSockNonblocking(sock, newConnectionSocket))
|
if (!setSockNonblocking(sock, newConnectionSocket))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||||
|
|
||||||
addPollFD(newConnectionSocket);
|
addPollFD(newConnectionSocket);
|
||||||
|
|
||||||
@ -473,10 +480,15 @@ void CNServer::start() {
|
|||||||
} else {
|
} else {
|
||||||
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
||||||
|
|
||||||
|
// halt packet handling if server is shutting down
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
// player sockets
|
// player sockets
|
||||||
if (connections.find(fds[i].fd) == connections.end()) {
|
if (connections.find(fds[i].fd) == connections.end()) {
|
||||||
std::cout << "[WARN] Event on non-existant socket?" << std::endl;
|
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl;
|
||||||
continue; // just to be safe
|
assert(0);
|
||||||
|
/* not reached */
|
||||||
}
|
}
|
||||||
|
|
||||||
CNSocket* cSock = connections[fds[i].fd];
|
CNSocket* cSock = connections[fds[i].fd];
|
||||||
@ -485,22 +497,29 @@ void CNServer::start() {
|
|||||||
if (fds[i].revents & ~POLLIN)
|
if (fds[i].revents & ~POLLIN)
|
||||||
cSock->kill();
|
cSock->kill();
|
||||||
|
|
||||||
if (cSock->isAlive()) {
|
if (cSock->isAlive())
|
||||||
cSock->step();
|
cSock->step();
|
||||||
} else {
|
|
||||||
killConnection(cSock);
|
|
||||||
connections.erase(fds[i].fd);
|
|
||||||
delete cSock;
|
|
||||||
|
|
||||||
removePollFD(i);
|
|
||||||
|
|
||||||
// a new entry was moved to this position, so we check it again
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onStep();
|
onStep();
|
||||||
|
|
||||||
|
// clean up dead connection sockets
|
||||||
|
auto it = connections.begin();
|
||||||
|
while (it != connections.end()) {
|
||||||
|
CNSocket *cSock = it->second;
|
||||||
|
|
||||||
|
if (!cSock->isAlive()) {
|
||||||
|
killConnection(cSock);
|
||||||
|
it = connections.erase(it);
|
||||||
|
|
||||||
|
removePollFD(cSock->sock);
|
||||||
|
|
||||||
|
delete cSock;
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
|
|||||||
|
|
||||||
// overflow-safe validation of variable-length packets
|
// overflow-safe validation of variable-length packets
|
||||||
// for outbound packets
|
// for outbound packets
|
||||||
inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
|
||||||
// check for multiplication overflow
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for inbound packets
|
// for inbound packets
|
||||||
inline constexpr bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
|
inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
|
||||||
// check for multiplication overflow
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
@ -230,6 +230,7 @@ protected:
|
|||||||
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
||||||
std::vector<PollFD> fds;
|
std::vector<PollFD> fds;
|
||||||
|
|
||||||
|
std::string serverType = "invalid";
|
||||||
SOCKET sock;
|
SOCKET sock;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
socklen_t addressSize;
|
socklen_t addressSize;
|
||||||
|
@ -32,7 +32,7 @@ void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
|
|||||||
auto& sk = it->first;
|
auto& sk = it->first;
|
||||||
auto& lm = it->second;
|
auto& lm = it->second;
|
||||||
|
|
||||||
if (lm->timestamp + CNSHARED_TIMEOUT > currTime) {
|
if (currTime > lm->timestamp + CNSHARED_TIMEOUT) {
|
||||||
std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
|
std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
|
||||||
|
|
||||||
// deallocate object and remove map entry
|
// deallocate object and remove map entry
|
||||||
|
@ -11,14 +11,14 @@
|
|||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Connecions time out after 15 minutes, checked every 30 seconds.
|
* Connecions time out after 5 minutes, checked every 30 seconds.
|
||||||
*/
|
*/
|
||||||
#define CNSHARED_TIMEOUT 900000
|
#define CNSHARED_TIMEOUT 300000
|
||||||
#define CNSHARED_PERIOD 30000
|
#define CNSHARED_PERIOD 30000
|
||||||
|
|
||||||
struct LoginMetadata {
|
struct LoginMetadata {
|
||||||
uint64_t FEKey;
|
uint64_t FEKey;
|
||||||
Player plr;
|
int32_t playerId;
|
||||||
time_t timestamp;
|
time_t timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
|
// 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; \
|
#define INITSTRUCT(T, x) T x; \
|
||||||
|
@ -10,18 +10,40 @@ const float CN_EP_RANK_4 = 0.3f;
|
|||||||
const float CN_EP_RANK_5 = 0.29f;
|
const float CN_EP_RANK_5 = 0.29f;
|
||||||
|
|
||||||
// methods of finding players for GM commands
|
// methods of finding players for GM commands
|
||||||
enum eCN_GM_TargetSearchBy {
|
enum class eCN_GM_TargetSearchBy {
|
||||||
eCN_GM_TargetSearchBy__PC_ID, // player id
|
PC_ID, // player id
|
||||||
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname
|
PC_Name, // firstname, lastname
|
||||||
eCN_GM_TargetSearchBy__PC_UID // account id
|
PC_UID // account id
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eCN_GM_TeleportType {
|
enum class eCN_GM_TeleportType {
|
||||||
eCN_GM_TeleportMapType__XYZ,
|
XYZ,
|
||||||
eCN_GM_TeleportMapType__MapXYZ,
|
MapXYZ,
|
||||||
eCN_GM_TeleportMapType__MyLocation,
|
MyLocation,
|
||||||
eCN_GM_TeleportMapType__SomeoneLocation,
|
SomeoneLocation,
|
||||||
eCN_GM_TeleportMapType__Unstick
|
Unstick
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class eTaskTypeProperty {
|
||||||
|
None = -1,
|
||||||
|
Talk = 1,
|
||||||
|
GotoLocation = 2,
|
||||||
|
UseItems = 3,
|
||||||
|
Delivery = 4,
|
||||||
|
Defeat = 5,
|
||||||
|
EscortDefence = 6,
|
||||||
|
Max = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ePCRegenType {
|
||||||
|
None,
|
||||||
|
Xcom,
|
||||||
|
Here,
|
||||||
|
HereByPhoenix,
|
||||||
|
HereByPhoenixGroup,
|
||||||
|
Unstick,
|
||||||
|
HereByPhoenixItem,
|
||||||
|
End
|
||||||
};
|
};
|
||||||
|
|
||||||
// nano powers
|
// nano powers
|
||||||
@ -96,6 +118,27 @@ enum {
|
|||||||
ECSTB__END = 26,
|
ECSTB__END = 26,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ETBU_NONE = 0,
|
||||||
|
ETBU_ADD = 1,
|
||||||
|
ETBU_DEL = 2,
|
||||||
|
ETBU_CHANGE = 3,
|
||||||
|
ETBU__END = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ETBT_NONE = 0,
|
||||||
|
ETBT_NANO = 1,
|
||||||
|
ETBT_GROUPNANO = 2,
|
||||||
|
ETBT_SHINY = 3,
|
||||||
|
ETBT_LANDEFFECT = 4,
|
||||||
|
ETBT_ITEM = 5,
|
||||||
|
ETBT_CASHITEM = 6,
|
||||||
|
ETBT__END = 7,
|
||||||
|
ETBT_SKILL = 1,
|
||||||
|
ETBT_GROUPSKILL = 2
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SUCC = 1,
|
SUCC = 1,
|
||||||
FAIL = 0,
|
FAIL = 0,
|
||||||
|
@ -47,8 +47,8 @@ struct PacketDesc {
|
|||||||
* really should.
|
* really should.
|
||||||
*/
|
*/
|
||||||
struct sGM_PVPTarget {
|
struct sGM_PVPTarget {
|
||||||
uint32_t eCT;
|
|
||||||
uint32_t iID;
|
uint32_t iID;
|
||||||
|
uint32_t eCT;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sSkillResult_Leech {
|
struct sSkillResult_Leech {
|
||||||
|
@ -41,6 +41,7 @@ namespace Database {
|
|||||||
uint64_t Timestamp;
|
uint64_t Timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void init();
|
||||||
void open();
|
void open();
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@ -52,7 +53,8 @@ namespace Database {
|
|||||||
bool banPlayer(int playerId, std::string& reason);
|
bool banPlayer(int playerId, std::string& reason);
|
||||||
bool unbanPlayer(int playerId);
|
bool unbanPlayer(int playerId);
|
||||||
|
|
||||||
void updateSelected(int accountId, int playerId);
|
void updateSelected(int accountId, int slot);
|
||||||
|
void updateSelectedByPlayerId(int accountId, int playerId);
|
||||||
|
|
||||||
bool validateCharacter(int characterID, int userID);
|
bool validateCharacter(int characterID, int userID);
|
||||||
bool isNameFree(std::string firstName, std::string lastName);
|
bool isNameFree(std::string firstName, std::string lastName);
|
||||||
@ -78,7 +80,9 @@ namespace Database {
|
|||||||
|
|
||||||
// getting players
|
// getting players
|
||||||
void getPlayer(Player* plr, int id);
|
void getPlayer(Player* plr, int id);
|
||||||
|
bool _updatePlayer(Player *player);
|
||||||
void updatePlayer(Player *player);
|
void updatePlayer(Player *player);
|
||||||
|
void commitTrade(Player *plr1, Player *plr2);
|
||||||
|
|
||||||
// buddies
|
// buddies
|
||||||
int getNumBuddies(Player* player);
|
int getNumBuddies(Player* player);
|
||||||
@ -98,7 +102,7 @@ namespace Database {
|
|||||||
void deleteEmailAttachments(int playerID, int index, int slot);
|
void deleteEmailAttachments(int playerID, int index, int slot);
|
||||||
void deleteEmails(int playerID, int64_t* indices);
|
void deleteEmails(int playerID, int64_t* indices);
|
||||||
int getNextEmailIndex(int playerID);
|
int getNextEmailIndex(int playerID);
|
||||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
|
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender);
|
||||||
|
|
||||||
// racing
|
// racing
|
||||||
RaceRanking getTopRaceRanking(int epID, int playerID);
|
RaceRanking getTopRaceRanking(int epID, int playerID);
|
||||||
|
@ -152,7 +152,8 @@ void Database::updateEmailContent(EmailData* data) {
|
|||||||
sqlite3_step(stmt);
|
sqlite3_step(stmt);
|
||||||
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
||||||
|
|
||||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
|
// set attachment flag dynamically
|
||||||
|
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
@ -265,7 +266,7 @@ int Database::getNextEmailIndex(int playerID) {
|
|||||||
return (index > 0 ? index + 1 : 1);
|
return (index > 0 ? index + 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
@ -330,6 +331,13 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
|||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
if (!_updatePlayer(sender)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,20 @@ static int getTableSize(std::string tableName) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Database::init() {
|
||||||
|
std::cout << "[INFO] Built with libsqlite " SQLITE_VERSION << std::endl;
|
||||||
|
|
||||||
|
if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER)
|
||||||
|
std::cout << "[INFO] Using libsqlite " << std::string(sqlite3_libversion()) << std::endl;
|
||||||
|
|
||||||
|
if (sqlite3_libversion_number() < MIN_SUPPORTED_SQLITE_NUMBER) {
|
||||||
|
std::cerr << "[FATAL] Runtime sqlite version too old. Minimum compatible version: " MIN_SUPPORTED_SQLITE << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Database::open() {
|
void Database::open() {
|
||||||
|
|
||||||
// XXX: move locks here
|
// XXX: move locks here
|
||||||
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#define MIN_SUPPORTED_SQLITE_NUMBER 3033000
|
||||||
|
#define MIN_SUPPORTED_SQLITE "3.33.0"
|
||||||
|
// we can't use this in #error, since it doesn't expand macros
|
||||||
|
|
||||||
|
// Compile-time libsqlite version check
|
||||||
|
#if SQLITE_VERSION_NUMBER < MIN_SUPPORTED_SQLITE_NUMBER
|
||||||
|
#error libsqlite version too old. Minimum compatible version: 3.33.0
|
||||||
|
#endif
|
||||||
|
|
||||||
extern std::mutex dbCrit;
|
extern std::mutex dbCrit;
|
||||||
extern sqlite3 *db;
|
extern sqlite3 *db;
|
||||||
|
|
||||||
|
@ -79,6 +79,29 @@ void Database::updateSelected(int accountId, int slot) {
|
|||||||
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
const char* sql = R"(
|
||||||
|
UPDATE Accounts SET
|
||||||
|
Selected = p.Slot,
|
||||||
|
LastLogin = (strftime('%s', 'now'))
|
||||||
|
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
|
||||||
|
WHERE AccountID = ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
sqlite3_bind_int(stmt, 1, playerId);
|
||||||
|
sqlite3_bind_int(stmt, 2, accountId);
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
if (rc != SQLITE_DONE)
|
||||||
|
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::validateCharacter(int characterID, int userID) {
|
bool Database::validateCharacter(int characterID, int userID) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
@ -285,11 +285,13 @@ void Database::getPlayer(Player* plr, int id) {
|
|||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::updatePlayer(Player *player) {
|
/*
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
* Low-level function to save a player to DB.
|
||||||
|
* Must be run in a SQL transaction and with dbCrit locked.
|
||||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
* The caller manages the transacstion, so if this function returns false,
|
||||||
|
* the caller must roll it back.
|
||||||
|
*/
|
||||||
|
bool Database::_updatePlayer(Player *player) {
|
||||||
const char* sql = R"(
|
const char* sql = R"(
|
||||||
UPDATE Players
|
UPDATE Players
|
||||||
SET
|
SET
|
||||||
@ -336,10 +338,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 21, player->iID);
|
sqlite3_bind_int(stmt, 21, player->iID);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@ -375,10 +375,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
rc = sqlite3_step(stmt);
|
rc = sqlite3_step(stmt);
|
||||||
|
|
||||||
if (rc != SQLITE_DONE) {
|
if (rc != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@ -395,10 +393,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@ -415,10 +411,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@ -451,10 +445,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@ -487,10 +479,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@ -524,14 +514,47 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::updatePlayer(Player *player) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (!_updatePlayer(player)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::commitTrade(Player *plr1, Player *plr2) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (!_updatePlayer(plr1)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_updatePlayer(plr2)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
16
src/main.cpp
16
src/main.cpp
@ -98,6 +98,9 @@ void initsignals() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||||
|
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
||||||
@ -105,15 +108,15 @@ int main() {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
initsignals();
|
initsignals();
|
||||||
settings::init();
|
settings::init();
|
||||||
|
Database::init();
|
||||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
|
||||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
|
||||||
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
|
||||||
|
|
||||||
Rand::init(getTime());
|
Rand::init(getTime());
|
||||||
TableData::init();
|
TableData::init();
|
||||||
|
|
||||||
|
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
||||||
|
|
||||||
PlayerManager::init();
|
PlayerManager::init();
|
||||||
PlayerMovement::init();
|
PlayerMovement::init();
|
||||||
BuiltinCommands::init();
|
BuiltinCommands::init();
|
||||||
@ -132,9 +135,10 @@ int main() {
|
|||||||
Email::init();
|
Email::init();
|
||||||
Groups::init();
|
Groups::init();
|
||||||
Racing::init();
|
Racing::init();
|
||||||
Database::open();
|
|
||||||
Trading::init();
|
Trading::init();
|
||||||
|
|
||||||
|
Database::open();
|
||||||
|
|
||||||
switch (settings::EVENTMODE) {
|
switch (settings::EVENTMODE) {
|
||||||
case 0: break; // no event
|
case 0: break; // no event
|
||||||
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
|
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
|
||||||
|
@ -153,15 +153,18 @@ static sock_filter filter[] = {
|
|||||||
ALLOW_SYSCALL(read),
|
ALLOW_SYSCALL(read),
|
||||||
ALLOW_SYSCALL(write),
|
ALLOW_SYSCALL(write),
|
||||||
ALLOW_SYSCALL(close),
|
ALLOW_SYSCALL(close),
|
||||||
#if __NR_stat
|
#ifdef __NR_stat
|
||||||
ALLOW_SYSCALL(stat),
|
ALLOW_SYSCALL(stat),
|
||||||
#endif
|
#endif
|
||||||
ALLOW_SYSCALL(fstat),
|
ALLOW_SYSCALL(fstat),
|
||||||
|
#ifdef __NR_newfstatat
|
||||||
|
ALLOW_SYSCALL(newfstatat),
|
||||||
|
#endif
|
||||||
ALLOW_SYSCALL(fsync), // maybe
|
ALLOW_SYSCALL(fsync), // maybe
|
||||||
#if __NR_creat
|
#ifdef __NR_creat
|
||||||
ALLOW_SYSCALL(creat), // maybe; for DB journal
|
ALLOW_SYSCALL(creat), // maybe; for DB journal
|
||||||
#endif
|
#endif
|
||||||
#if __NR_unlink
|
#ifdef __NR_unlink
|
||||||
ALLOW_SYSCALL(unlink), // for DB journal
|
ALLOW_SYSCALL(unlink), // for DB journal
|
||||||
#endif
|
#endif
|
||||||
ALLOW_SYSCALL(lseek), // musl-libc; alt DB
|
ALLOW_SYSCALL(lseek), // musl-libc; alt DB
|
||||||
@ -192,6 +195,9 @@ static sock_filter filter[] = {
|
|||||||
ALLOW_SYSCALL(exit_group),
|
ALLOW_SYSCALL(exit_group),
|
||||||
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
|
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
|
||||||
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
|
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
|
||||||
|
#ifdef __NR_rseq
|
||||||
|
ALLOW_SYSCALL(rseq),
|
||||||
|
#endif
|
||||||
|
|
||||||
// to crash properly on SIGSEGV
|
// to crash properly on SIGSEGV
|
||||||
DENY_SYSCALL_ERRNO(tgkill, EPERM),
|
DENY_SYSCALL_ERRNO(tgkill, EPERM),
|
||||||
@ -274,7 +280,7 @@ static sock_filter filter[] = {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// AArch64 (ARM64)
|
// AArch64 (ARM64)
|
||||||
#if __NR_unlinkat
|
#ifdef __NR_unlinkat
|
||||||
ALLOW_SYSCALL(unlinkat),
|
ALLOW_SYSCALL(unlinkat),
|
||||||
#endif
|
#endif
|
||||||
#ifdef __NR_fstatat64
|
#ifdef __NR_fstatat64
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
#include "servers/CNLoginServer.hpp"
|
#include "servers/CNLoginServer.hpp"
|
||||||
|
|
||||||
#include "core/CNShared.hpp"
|
#include "core/CNShared.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include <regex>
|
|
||||||
#include "bcrypt/BCrypt.hpp"
|
#include "bcrypt/BCrypt.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||||
|
|
||||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||||
|
serverType = "login";
|
||||||
port = p;
|
port = p;
|
||||||
pHandler = &CNLoginServer::handlePacket;
|
pHandler = &CNLoginServer::handlePacket;
|
||||||
init();
|
init();
|
||||||
@ -208,9 +211,12 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
|||||||
// send the resp in with original key
|
// send the resp in with original key
|
||||||
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
||||||
|
|
||||||
|
uint64_t defaultKey;
|
||||||
|
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
|
||||||
|
|
||||||
// update keys
|
// update keys
|
||||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
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));
|
sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1));
|
||||||
|
|
||||||
DEBUGLOG(
|
DEBUGLOG(
|
||||||
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||||
@ -471,11 +477,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
|||||||
LoginMetadata *lm = new LoginMetadata();
|
LoginMetadata *lm = new LoginMetadata();
|
||||||
lm->FEKey = sock->getFEKey();
|
lm->FEKey = sock->getFEKey();
|
||||||
lm->timestamp = getTime();
|
lm->timestamp = getTime();
|
||||||
|
lm->playerId = selection->iPC_UID;
|
||||||
Database::getPlayer(&lm->plr, selection->iPC_UID);
|
|
||||||
// this should never happen but for extra safety
|
|
||||||
if (lm->plr.iID == 0)
|
|
||||||
return invalidCharacter(sock);
|
|
||||||
|
|
||||||
resp.iEnterSerialKey = Rand::cryptoRand();
|
resp.iEnterSerialKey = Rand::cryptoRand();
|
||||||
|
|
||||||
@ -485,7 +487,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
|||||||
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
||||||
|
|
||||||
// update current slot in DB
|
// update current slot in DB
|
||||||
Database::updateSelected(loginSessions[sock].userID, lm->plr.slot);
|
Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "core/CNShared.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "servers/Monitor.hpp"
|
#include "servers/Monitor.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "core/CNShared.hpp"
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include "TableData.hpp" // for flush()
|
#include "TableData.hpp" // for flush()
|
||||||
|
|
||||||
@ -16,6 +18,7 @@ std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
|
|||||||
std::list<TimerEvent> CNShardServer::Timers;
|
std::list<TimerEvent> CNShardServer::Timers;
|
||||||
|
|
||||||
CNShardServer::CNShardServer(uint16_t p) {
|
CNShardServer::CNShardServer(uint16_t p) {
|
||||||
|
serverType = "shard";
|
||||||
port = p;
|
port = p;
|
||||||
pHandler = &CNShardServer::handlePacket;
|
pHandler = &CNShardServer::handlePacket;
|
||||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||||
@ -115,6 +118,10 @@ void CNShardServer::kill() {
|
|||||||
void CNShardServer::onStep() {
|
void CNShardServer::onStep() {
|
||||||
time_t currTime = getTime();
|
time_t currTime = getTime();
|
||||||
|
|
||||||
|
// do not evaluate timers if the server is shutting down
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
for (TimerEvent& event : Timers) {
|
for (TimerEvent& event : Timers) {
|
||||||
if (event.scheduledEvent == 0) {
|
if (event.scheduledEvent == 0) {
|
||||||
// event hasn't been queued yet, go ahead and do that
|
// event hasn't been queued yet, go ahead and do that
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
||||||
|
#define MS_PER_PLAYER_TICK 500
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
#include "servers/Monitor.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
#include "Email.hpp"
|
#include "Email.hpp"
|
||||||
#include "servers/Monitor.hpp"
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace Monitor {
|
namespace Monitor {
|
||||||
SOCKET init();
|
SOCKET init();
|
||||||
bool acceptConnection(SOCKET, uint16_t);
|
bool acceptConnection(SOCKET, uint16_t);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
#include <iostream>
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include "core/CNStructs.hpp" // so we get the ACADEMY definition
|
||||||
#include "INIReader.hpp"
|
#include "INIReader.hpp"
|
||||||
|
|
||||||
// so we get the ACADEMY definition
|
#include <iostream>
|
||||||
#include "core/CNStructs.hpp"
|
|
||||||
|
|
||||||
// defaults :)
|
// defaults :)
|
||||||
int settings::VERBOSITY = 1;
|
int settings::VERBOSITY = 1;
|
||||||
@ -20,6 +21,7 @@ bool settings::LOCALHOSTWORKAROUND = true;
|
|||||||
time_t settings::TIMEOUT = 60000;
|
time_t settings::TIMEOUT = 60000;
|
||||||
int settings::VIEWDISTANCE = 25600;
|
int settings::VIEWDISTANCE = 25600;
|
||||||
bool settings::SIMULATEMOBS = true;
|
bool settings::SIMULATEMOBS = true;
|
||||||
|
bool settings::ANTICHEAT = true;
|
||||||
|
|
||||||
// default spawn point
|
// default spawn point
|
||||||
#ifndef ACADEMY
|
#ifndef ACADEMY
|
||||||
@ -108,6 +110,7 @@ void settings::init() {
|
|||||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||||
|
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||||
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace settings {
|
namespace settings {
|
||||||
extern int VERBOSITY;
|
extern int VERBOSITY;
|
||||||
extern bool SANDBOX;
|
extern bool SANDBOX;
|
||||||
@ -10,6 +12,7 @@ namespace settings {
|
|||||||
extern int SHARDPORT;
|
extern int SHARDPORT;
|
||||||
extern std::string SHARDSERVERIP;
|
extern std::string SHARDSERVERIP;
|
||||||
extern bool LOCALHOSTWORKAROUND;
|
extern bool LOCALHOSTWORKAROUND;
|
||||||
|
extern bool ANTICHEAT;
|
||||||
extern time_t TIMEOUT;
|
extern time_t TIMEOUT;
|
||||||
extern int VIEWDISTANCE;
|
extern int VIEWDISTANCE;
|
||||||
extern bool SIMULATEMOBS;
|
extern bool SIMULATEMOBS;
|
||||||
|
2
tdata
2
tdata
@ -1 +1 @@
|
|||||||
Subproject commit 8230fb8649d6da8de71e918c24efbb55d1c08a88
|
Subproject commit cc65dbb402b5baa2b604ed66132edd88cc82a52a
|
2
vendor/JSON.hpp
vendored
2
vendor/JSON.hpp
vendored
@ -27,6 +27,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user