mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2026-01-05 00:40:07 +00:00
Compare commits
63 Commits
bbaaa53df2
...
refactor
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b2a65f8fd | |||
| 6a69388822 | |||
| 2924a27eb4 | |||
| ba20f5a401 | |||
|
|
eb88fa05cb | ||
|
|
0b73cef187 | ||
|
|
7af39b3d04 | ||
|
|
33206b1207 | ||
|
|
e325f7a40b | ||
|
|
82bee2051a | ||
|
|
4ece1bb89b | ||
|
|
31677e2638 | ||
|
|
d32827b692 | ||
|
|
13c009b448 | ||
|
|
a032497bed | ||
|
|
3b6b61d087 | ||
|
|
6d760f5bce | ||
|
|
2a622f901c | ||
|
|
03d28bf4e4 | ||
|
|
4b834579c5 | ||
|
|
07fe8ca367 | ||
|
|
2f3f8a3951 | ||
| 4f890a9c07 | |||
| 8517e0c7de | |||
| 5fb0cbbcf7 | |||
| 55e9f6531d | |||
|
|
7726357fbe | ||
|
|
564c275d51 | ||
|
|
3ce9ae5f77 | ||
|
|
7c5b9a8105 | ||
|
|
258ff35e20 | ||
|
|
650f947451 | ||
|
|
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 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
version.h
|
||||||
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}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -45,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})
|
||||||
|
|
||||||
@@ -57,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()
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra
|
|||||||
|
|
||||||
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
||||||
|
|
||||||
## Dirty pull requests
|
### Dirty pull requests
|
||||||
|
|
||||||
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||||
These are generally either:
|
These are generally either:
|
||||||
@@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut
|
|||||||
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
||||||
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
||||||
|
|
||||||
## The details
|
### The details
|
||||||
|
|
||||||
A git commit is uniquely identified by its SHA1 hash.
|
A git commit is uniquely identified by its SHA1 hash.
|
||||||
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
||||||
@@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c
|
|||||||
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
||||||
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
||||||
|
|
||||||
## The solution
|
### The solution
|
||||||
|
|
||||||
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you
|
|||||||
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
||||||
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
||||||
|
|
||||||
## Avoiding the problem
|
### Avoiding the problem
|
||||||
|
|
||||||
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
||||||
Create a work branch just for your changeset with `git checkout -b work`.
|
Create a work branch just for your changeset with `git checkout -b work`.
|
||||||
@@ -81,3 +81,34 @@ That way you can always keep master in sync with upstream with `git pull --ff-on
|
|||||||
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
||||||
|
|
||||||
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
||||||
|
|
||||||
|
## Code guidelines
|
||||||
|
|
||||||
|
Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow:
|
||||||
|
|
||||||
|
### Match the styling
|
||||||
|
|
||||||
|
Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc.
|
||||||
|
|
||||||
|
### Prefer short-circuiting
|
||||||
|
|
||||||
|
To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement.
|
||||||
|
|
||||||
|
### Follow the include convention
|
||||||
|
|
||||||
|
This one matters a lot as it can cause cyclic dependencies and other code-breaking issues.
|
||||||
|
|
||||||
|
FOR HEADER FILES (.hpp):
|
||||||
|
- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp
|
||||||
|
- you may NOT include ANYTHING ELSE
|
||||||
|
|
||||||
|
FOR SOURCE FILES (.cpp):
|
||||||
|
- you can #include whatever you want as long as the partner header is included first
|
||||||
|
- anything that gets included by another include is fair game
|
||||||
|
- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean.
|
||||||
|
|
||||||
|
The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues.
|
||||||
|
|
||||||
|
## When in doubt, ask
|
||||||
|
|
||||||
|
If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub.
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Buffs.hpp"
|
#include "Buffs.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
|
#include "MobAI.hpp"
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
using namespace Abilities;
|
using namespace Abilities;
|
||||||
|
|
||||||
@@ -47,16 +48,107 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
// TODO abilities
|
// take aggro
|
||||||
|
target->takeDamage(source->getRef(), 0);
|
||||||
|
|
||||||
|
int duration = 0;
|
||||||
|
int strength = 0;
|
||||||
|
bool blocked = target->hasBuff(ECSB_FREEDOM);
|
||||||
|
if(!blocked) {
|
||||||
|
duration = skill->durationTime[power];
|
||||||
|
strength = skill->values[0][power];
|
||||||
|
BuffStack debuff = {
|
||||||
|
(duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||||
|
strength, // value
|
||||||
|
source->getRef(), // source
|
||||||
|
BuffClass::NANO, // buff class
|
||||||
|
};
|
||||||
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
|
target->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) {
|
||||||
|
Buffs::timeBuffTick(self, buff);
|
||||||
|
},
|
||||||
|
&debuff);
|
||||||
|
}
|
||||||
|
|
||||||
sSkillResult_Damage_N_Debuff result{};
|
sSkillResult_Damage_N_Debuff result{};
|
||||||
|
|
||||||
|
result.iDamage = duration / 10; // we use the duration as the damage number (why?)
|
||||||
|
result.iHP = target->getCurrentHP();
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
result.bProtected = false;
|
result.bProtected = blocked;
|
||||||
result.iConditionBitFlag = target->getCompositeCondition();
|
result.iConditionBitFlag = target->getCompositeCondition();
|
||||||
|
|
||||||
|
// for player targets, make sure to update Nano stamina
|
||||||
|
if (target->getCharType() == 1) {
|
||||||
|
Player *plr = dynamic_cast<Player*>(target);
|
||||||
|
result.iStamina = plr->getActiveNano()->iStamina;
|
||||||
|
}
|
||||||
|
|
||||||
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
EntityRef sourceRef = source->getRef();
|
||||||
|
int heal = skill->values[0][power];
|
||||||
|
int healed = source->heal(sourceRef, heal);
|
||||||
|
int damage = heal * 2;
|
||||||
|
int dealt = target->takeDamage(sourceRef, damage);
|
||||||
|
|
||||||
|
sSkillResult_Leech result{};
|
||||||
|
|
||||||
|
result.Damage.eCT = target->getCharType();
|
||||||
|
result.Damage.iID = target->getID();
|
||||||
|
result.Damage.bProtected = dealt <= 0;
|
||||||
|
result.Damage.iDamage = dealt;
|
||||||
|
result.Damage.iHP = target->getCurrentHP();
|
||||||
|
|
||||||
|
result.Heal.eCT = result.Damage.eCT;
|
||||||
|
result.Heal.iID = result.Damage.iID;
|
||||||
|
result.Heal.iHealHP = healed;
|
||||||
|
result.Heal.iHP = source->getCurrentHP();
|
||||||
|
|
||||||
|
return SkillResult(sizeof(sSkillResult_Leech), &result);
|
||||||
|
}
|
||||||
|
|
||||||
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
int duration = skill->durationTime[power];
|
||||||
|
int strength = skill->values[0][power];
|
||||||
|
BuffStack passiveBuff = {
|
||||||
|
// if the duration is 0, it needs to be recast every tick
|
||||||
|
duration == 0 ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||||
|
strength, // value
|
||||||
|
source->getRef(), // source
|
||||||
|
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
|
||||||
|
};
|
||||||
|
|
||||||
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
|
SkillDrainType drainType = skill->drainType;
|
||||||
|
int combatLifetime = 0;
|
||||||
|
if(!target->addBuff(timeBuffId,
|
||||||
|
[drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
if(buff->id == ECSB_BOUNDINGBALL && status == ETBU_ADD) {
|
||||||
|
// drain
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
|
||||||
|
combatant->takeDamage(buff->getLastSource(), 0); // aggro
|
||||||
|
}
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL)
|
||||||
|
Buffs::timeBuffTimeout(self);
|
||||||
|
},
|
||||||
|
[combatLifetime](EntityRef self, Buff* buff, time_t currTime) mutable {
|
||||||
|
if(buff->id == ECSB_BOUNDINGBALL &&
|
||||||
|
combatLifetime % COMBAT_TICKS_PER_DRAIN_PROC == 0)
|
||||||
|
Buffs::tickDrain(self, buff, COMBAT_TICKS_PER_DRAIN_PROC); // drain
|
||||||
|
combatLifetime++;
|
||||||
|
},
|
||||||
|
&passiveBuff)) return SkillResult();
|
||||||
|
|
||||||
sSkillResult_Buff result{};
|
sSkillResult_Buff result{};
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
@@ -71,19 +163,24 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
|||||||
Player* plr = dynamic_cast<Player*>(target);
|
Player* plr = dynamic_cast<Player*>(target);
|
||||||
|
|
||||||
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||||
|
const bool blocked = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||||
|
|
||||||
int boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
int boostDrain = 0;
|
||||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
int potionDrain = 0;
|
||||||
plr->batteryW -= boostDrain;
|
if(!blocked) {
|
||||||
|
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||||
|
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||||
|
plr->batteryW -= boostDrain;
|
||||||
|
|
||||||
int potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||||
plr->batteryN -= potionDrain;
|
plr->batteryN -= potionDrain;
|
||||||
|
}
|
||||||
|
|
||||||
sSkillResult_BatteryDrain result{};
|
sSkillResult_BatteryDrain result{};
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
|
result.bProtected = blocked;
|
||||||
result.iDrainW = boostDrain;
|
result.iDrainW = boostDrain;
|
||||||
result.iBatteryW = plr->batteryW;
|
result.iBatteryW = plr->batteryW;
|
||||||
result.iDrainN = potionDrain;
|
result.iDrainN = potionDrain;
|
||||||
@@ -97,8 +194,13 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
|||||||
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
if(source->getCharType() != 1)
|
if(source->getCharType() != 1)
|
||||||
return SkillResult(); // only Players are valid sources for recall
|
return SkillResult(); // only Players are valid sources for recall
|
||||||
|
|
||||||
Player* plr = dynamic_cast<Player*>(source);
|
Player* plr = dynamic_cast<Player*>(source);
|
||||||
PlayerManager::sendPlayerTo(source->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
if(source == target) {
|
||||||
|
// no trailing struct for self
|
||||||
|
PlayerManager::sendPlayerTo(target->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
||||||
|
return SkillResult();
|
||||||
|
}
|
||||||
|
|
||||||
sSkillResult_Move result{};
|
sSkillResult_Move result{};
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
@@ -120,56 +222,66 @@ static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant*
|
|||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
||||||
size_t resultSize = 0;
|
|
||||||
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
||||||
std::vector<SkillResult> results;
|
std::vector<SkillResult> results;
|
||||||
|
|
||||||
switch(skill->skillType)
|
switch(skill->skillType)
|
||||||
{
|
{
|
||||||
case EST_DAMAGE:
|
case SkillType::CORRUPTIONATTACK:
|
||||||
resultSize = sizeof(sSkillResult_Damage);
|
case SkillType::CORRUPTIONATTACKLOSE:
|
||||||
|
case SkillType::CORRUPTIONATTACKWIN:
|
||||||
|
// skillHandler = handleSkillCorruptionReflect;
|
||||||
|
// break;
|
||||||
|
case SkillType::DAMAGE:
|
||||||
skillHandler = handleSkillDamage;
|
skillHandler = handleSkillDamage;
|
||||||
break;
|
break;
|
||||||
case EST_HEAL_HP:
|
case SkillType::HEAL_HP:
|
||||||
case EST_RETURNHOMEHEAL:
|
case SkillType::RETURNHOMEHEAL:
|
||||||
resultSize = sizeof(sSkillResult_Heal_HP);
|
|
||||||
skillHandler = handleSkillHealHP;
|
skillHandler = handleSkillHealHP;
|
||||||
break;
|
break;
|
||||||
case EST_JUMP:
|
case SkillType::KNOCKDOWN:
|
||||||
case EST_RUN:
|
case SkillType::SLEEP:
|
||||||
case EST_FREEDOM:
|
case SkillType::SNARE:
|
||||||
case EST_PHOENIX:
|
case SkillType::STUN:
|
||||||
case EST_INVULNERABLE:
|
skillHandler = handleSkillDamageNDebuff;
|
||||||
case EST_MINIMAPENEMY:
|
break;
|
||||||
case EST_MINIMAPTRESURE:
|
case SkillType::JUMP:
|
||||||
case EST_NANOSTIMPAK:
|
case SkillType::RUN:
|
||||||
case EST_PROTECTBATTERY:
|
case SkillType::STEALTH:
|
||||||
case EST_PROTECTINFECTION:
|
case SkillType::MINIMAPENEMY:
|
||||||
case EST_REWARDBLOB:
|
case SkillType::MINIMAPTRESURE:
|
||||||
case EST_REWARDCASH:
|
case SkillType::PHOENIX:
|
||||||
case EST_STAMINA_SELF:
|
case SkillType::PROTECTBATTERY:
|
||||||
case EST_STEALTH:
|
case SkillType::PROTECTINFECTION:
|
||||||
resultSize = sizeof(sSkillResult_Buff);
|
case SkillType::REWARDBLOB:
|
||||||
|
case SkillType::REWARDCASH:
|
||||||
|
// case SkillType::INFECTIONDAMAGE:
|
||||||
|
case SkillType::FREEDOM:
|
||||||
|
case SkillType::BOUNDINGBALL:
|
||||||
|
case SkillType::INVULNERABLE:
|
||||||
|
case SkillType::STAMINA_SELF:
|
||||||
|
case SkillType::NANOSTIMPAK:
|
||||||
|
case SkillType::BUFFHEAL:
|
||||||
skillHandler = handleSkillBuff;
|
skillHandler = handleSkillBuff;
|
||||||
break;
|
break;
|
||||||
case EST_BATTERYDRAIN:
|
case SkillType::BLOODSUCKING:
|
||||||
resultSize = sizeof(sSkillResult_BatteryDrain);
|
skillHandler = handleSkillLeech;
|
||||||
skillHandler = handleSkillBatteryDrain;
|
|
||||||
break;
|
break;
|
||||||
case EST_RECALL: // still soft lock
|
case SkillType::RETROROCKET_SELF:
|
||||||
case EST_RECALL_GROUP: // works for player who uses it
|
|
||||||
resultSize = sizeof(sSkillResult_Move);
|
|
||||||
skillHandler = handleSkillMove;
|
|
||||||
break;
|
|
||||||
case EST_PHOENIX_GROUP: // broken
|
|
||||||
resultSize = sizeof(sSkillResult_Resurrect);
|
|
||||||
skillHandler = handleSkillResurrect;
|
|
||||||
break;
|
|
||||||
case EST_RETROROCKET_SELF:
|
|
||||||
// no-op
|
// no-op
|
||||||
return results;
|
return results;
|
||||||
|
case SkillType::PHOENIX_GROUP:
|
||||||
|
skillHandler = handleSkillResurrect;
|
||||||
|
break;
|
||||||
|
case SkillType::RECALL:
|
||||||
|
case SkillType::RECALL_GROUP:
|
||||||
|
skillHandler = handleSkillMove;
|
||||||
|
break;
|
||||||
|
case SkillType::BATTERYDRAIN:
|
||||||
|
skillHandler = handleSkillBatteryDrain;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl;
|
std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl;
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,8 +289,8 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
|||||||
assert(target != nullptr);
|
assert(target != nullptr);
|
||||||
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
||||||
if(result.size == 0) continue; // skill not applicable
|
if(result.size == 0) continue; // skill not applicable
|
||||||
if(result.size != resultSize) {
|
if(result.size > MAX_SKILLRESULT_SIZE) {
|
||||||
std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
results.push_back(result);
|
results.push_back(result);
|
||||||
@@ -186,36 +298,42 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
|
static void attachSkillResults(std::vector<SkillResult> results, uint8_t* pivot) {
|
||||||
for(SkillResult& result : results) {
|
for(SkillResult& result : results) {
|
||||||
memcpy(pivot, result.payload, resultSize);
|
size_t sz = result.size;
|
||||||
pivot += resultSize;
|
memcpy(pivot, result.payload, sz);
|
||||||
|
pivot += sz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(plr);
|
||||||
|
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr))
|
if (Nanos::getNanoBoost(plr))
|
||||||
boost = 3;
|
boost = 3;
|
||||||
|
|
||||||
nano.iStamina -= skill->batteryUse[boost];
|
if(skill->drainType == SkillDrainType::ACTIVE) {
|
||||||
if (nano.iStamina < 0)
|
nano.iStamina -= skill->batteryUse[boost];
|
||||||
nano.iStamina = 0;
|
if (nano.iStamina <= 0)
|
||||||
|
nano.iStamina = 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<SkillResult> results = handleSkill(skill, boost, plr, affected);
|
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected);
|
||||||
size_t resultSize = 0; // guaranteed to be the same for every item
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
if (!results.empty()) resultSize = results.back().size;
|
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
|
// lazy validation since skill results might be different sizes
|
||||||
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
|
||||||
|
for(SkillResult& sr : results)
|
||||||
|
resplen += sr.size;
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
@@ -225,15 +343,17 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
|||||||
pkt->iSkillID = nano.iSkillID;
|
pkt->iSkillID = nano.iSkillID;
|
||||||
pkt->iNanoStamina = nano.iStamina;
|
pkt->iNanoStamina = nano.iStamina;
|
||||||
pkt->bNanoDeactive = nano.iStamina <= 0;
|
pkt->bNanoDeactive = nano.iStamina <= 0;
|
||||||
pkt->eST = skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
|
||||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||||
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||||
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
|
||||||
|
|
||||||
if (nano.iStamina <= 0)
|
if(skill->skillType == SkillType::RECALL_GROUP)
|
||||||
Nanos::summonNano(sock, -1);
|
// group recall packet is sent only to group members
|
||||||
|
PlayerManager::sendToGroup(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||||
|
else
|
||||||
|
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
||||||
@@ -248,32 +368,59 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
|||||||
SkillData* skill = &SkillTable[skillID];
|
SkillData* skill = &SkillTable[skillID];
|
||||||
|
|
||||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||||
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
|
// lazy validation since skill results might be different sizes
|
||||||
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
|
||||||
|
for(SkillResult& sr : results)
|
||||||
|
resplen += sr.size;
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||||
pkt->iNPC_ID = npc.id;
|
pkt->iNPC_ID = npc.id;
|
||||||
pkt->iSkillID = skillID;
|
pkt->iSkillID = skillID;
|
||||||
pkt->eST = skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
if(npc.kind == EntityKind::MOB) {
|
||||||
|
Mob* mob = dynamic_cast<Mob*>(entity);
|
||||||
|
pkt->iValue1 = mob->hitX;
|
||||||
|
pkt->iValue2 = mob->hitY;
|
||||||
|
pkt->iValue3 = mob->hitZ;
|
||||||
|
}
|
||||||
|
|
||||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||||
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
|
static std::vector<ICombatant*> entityRefsToCombatants(std::vector<EntityRef> refs) {
|
||||||
|
std::vector<ICombatant*> combatants;
|
||||||
|
for(EntityRef ref : refs) {
|
||||||
|
if(ref.kind == EntityKind::PLAYER)
|
||||||
|
combatants.push_back(dynamic_cast<ICombatant*>(PlayerManager::getPlayer(ref.sock)));
|
||||||
|
else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB)
|
||||||
|
combatants.push_back(dynamic_cast<ICombatant*>(ref.getEntity()));
|
||||||
|
}
|
||||||
|
return combatants;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) {
|
||||||
|
|
||||||
|
if(skill->targetType == SkillTargetType::GROUP)
|
||||||
|
return entityRefsToCombatants(src->getGroupMembers());
|
||||||
|
|
||||||
|
// this check *has* to happen after the group check above due to cases like group recall that use both
|
||||||
|
if(skill->effectTarget == SkillEffectTarget::SELF)
|
||||||
|
return {src}; // client sends 0 targets for certain self-targeting skills (recall)
|
||||||
|
|
||||||
|
// individuals
|
||||||
std::vector<ICombatant*> targets;
|
std::vector<ICombatant*> targets;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int32_t id = ids[i];
|
int32_t id = ids[i];
|
||||||
if (skill->targetType == SkillTargetType::MOBS) {
|
if (skill->targetType == SkillTargetType::MOBS) {
|
||||||
@@ -286,8 +433,8 @@ std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n";
|
std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n";
|
||||||
} else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) {
|
} else if(skill->targetType == SkillTargetType::PLAYERS) {
|
||||||
// players (?)
|
// player
|
||||||
Player* plr = PlayerManager::getPlayerFromID(id);
|
Player* plr = PlayerManager::getPlayerFromID(id);
|
||||||
if (plr != nullptr) {
|
if (plr != nullptr) {
|
||||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||||
@@ -301,63 +448,69 @@ std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ripped from client (enums emplaced) */
|
/* ripped from client (enums emplaced) */
|
||||||
int Abilities::getCSTBFromST(int eSkillType) {
|
int Abilities::getCSTBFromST(SkillType skillType) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
switch (eSkillType)
|
switch (skillType)
|
||||||
{
|
{
|
||||||
case EST_RUN:
|
case SkillType::RUN:
|
||||||
result = ECSB_UP_MOVE_SPEED;
|
result = ECSB_UP_MOVE_SPEED;
|
||||||
break;
|
break;
|
||||||
case EST_JUMP:
|
case SkillType::JUMP:
|
||||||
result = ECSB_UP_JUMP_HEIGHT;
|
result = ECSB_UP_JUMP_HEIGHT;
|
||||||
break;
|
break;
|
||||||
case EST_STEALTH:
|
case SkillType::STEALTH:
|
||||||
result = ECSB_UP_STEALTH;
|
result = ECSB_UP_STEALTH;
|
||||||
break;
|
break;
|
||||||
case EST_PHOENIX:
|
case SkillType::PHOENIX:
|
||||||
result = ECSB_PHOENIX;
|
result = ECSB_PHOENIX;
|
||||||
break;
|
break;
|
||||||
case EST_PROTECTBATTERY:
|
case SkillType::PROTECTBATTERY:
|
||||||
result = ECSB_PROTECT_BATTERY;
|
result = ECSB_PROTECT_BATTERY;
|
||||||
break;
|
break;
|
||||||
case EST_PROTECTINFECTION:
|
case SkillType::PROTECTINFECTION:
|
||||||
result = ECSB_PROTECT_INFECTION;
|
result = ECSB_PROTECT_INFECTION;
|
||||||
break;
|
break;
|
||||||
case EST_SNARE:
|
case SkillType::MINIMAPENEMY:
|
||||||
result = ECSB_DN_MOVE_SPEED;
|
|
||||||
break;
|
|
||||||
case EST_SLEEP:
|
|
||||||
result = ECSB_MEZ;
|
|
||||||
break;
|
|
||||||
case EST_MINIMAPENEMY:
|
|
||||||
result = ECSB_MINIMAP_ENEMY;
|
result = ECSB_MINIMAP_ENEMY;
|
||||||
break;
|
break;
|
||||||
case EST_MINIMAPTRESURE:
|
case SkillType::MINIMAPTRESURE:
|
||||||
result = ECSB_MINIMAP_TRESURE;
|
result = ECSB_MINIMAP_TRESURE;
|
||||||
break;
|
break;
|
||||||
case EST_REWARDBLOB:
|
case SkillType::REWARDBLOB:
|
||||||
result = ECSB_REWARD_BLOB;
|
result = ECSB_REWARD_BLOB;
|
||||||
break;
|
break;
|
||||||
case EST_REWARDCASH:
|
case SkillType::REWARDCASH:
|
||||||
result = ECSB_REWARD_CASH;
|
result = ECSB_REWARD_CASH;
|
||||||
break;
|
break;
|
||||||
case EST_INFECTIONDAMAGE:
|
case SkillType::FREEDOM:
|
||||||
result = ECSB_INFECTION;
|
|
||||||
break;
|
|
||||||
case EST_FREEDOM:
|
|
||||||
result = ECSB_FREEDOM;
|
result = ECSB_FREEDOM;
|
||||||
break;
|
break;
|
||||||
case EST_BOUNDINGBALL:
|
case SkillType::INVULNERABLE:
|
||||||
result = ECSB_BOUNDINGBALL;
|
|
||||||
break;
|
|
||||||
case EST_INVULNERABLE:
|
|
||||||
result = ECSB_INVULNERABLE;
|
result = ECSB_INVULNERABLE;
|
||||||
break;
|
break;
|
||||||
case EST_BUFFHEAL:
|
case SkillType::BUFFHEAL:
|
||||||
result = ECSB_HEAL;
|
result = ECSB_HEAL;
|
||||||
break;
|
break;
|
||||||
case EST_NANOSTIMPAK:
|
case SkillType::NANOSTIMPAK:
|
||||||
result = ECSB_STIMPAKSLOT1;
|
result = ECSB_STIMPAKSLOT1;
|
||||||
|
// shift as necessary
|
||||||
|
break;
|
||||||
|
case SkillType::SNARE:
|
||||||
|
result = ECSB_DN_MOVE_SPEED;
|
||||||
|
break;
|
||||||
|
case SkillType::STUN:
|
||||||
|
result = ECSB_STUN;
|
||||||
|
break;
|
||||||
|
case SkillType::SLEEP:
|
||||||
|
result = ECSB_MEZ;
|
||||||
|
break;
|
||||||
|
case SkillType::INFECTIONDAMAGE:
|
||||||
|
result = ECSB_INFECTION;
|
||||||
|
break;
|
||||||
|
case SkillType::BOUNDINGBALL:
|
||||||
|
result = ECSB_BOUNDINGBALL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -7,9 +7,52 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
const int COMBAT_TICKS_PER_DRAIN_PROC = 2;
|
||||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||||
|
|
||||||
|
enum class SkillType {
|
||||||
|
DAMAGE = 1,
|
||||||
|
HEAL_HP = 2,
|
||||||
|
KNOCKDOWN = 3, // uses DamageNDebuff
|
||||||
|
SLEEP = 4, // uses DamageNDebuff
|
||||||
|
SNARE = 5, // uses DamageNDebuff
|
||||||
|
HEAL_STAMINA = 6,
|
||||||
|
STAMINA_SELF = 7,
|
||||||
|
STUN = 8, // uses DamageNDebuff
|
||||||
|
WEAPONSLOW = 9,
|
||||||
|
JUMP = 10,
|
||||||
|
RUN = 11,
|
||||||
|
STEALTH = 12,
|
||||||
|
SWIM = 13,
|
||||||
|
MINIMAPENEMY = 14,
|
||||||
|
MINIMAPTRESURE = 15,
|
||||||
|
PHOENIX = 16,
|
||||||
|
PROTECTBATTERY = 17,
|
||||||
|
PROTECTINFECTION = 18,
|
||||||
|
REWARDBLOB = 19,
|
||||||
|
REWARDCASH = 20,
|
||||||
|
BATTERYDRAIN = 21,
|
||||||
|
CORRUPTIONATTACK = 22,
|
||||||
|
INFECTIONDAMAGE = 23,
|
||||||
|
KNOCKBACK = 24,
|
||||||
|
FREEDOM = 25,
|
||||||
|
PHOENIX_GROUP = 26,
|
||||||
|
RECALL = 27,
|
||||||
|
RECALL_GROUP = 28,
|
||||||
|
RETROROCKET_SELF = 29,
|
||||||
|
BLOODSUCKING = 30,
|
||||||
|
BOUNDINGBALL = 31,
|
||||||
|
INVULNERABLE = 32,
|
||||||
|
NANOSTIMPAK = 33,
|
||||||
|
RETURNHOMEHEAL = 34,
|
||||||
|
BUFFHEAL = 35,
|
||||||
|
EXTRABANK = 36,
|
||||||
|
CORRUPTIONATTACKWIN = 38,
|
||||||
|
CORRUPTIONATTACKLOSE = 39,
|
||||||
|
};
|
||||||
|
|
||||||
enum class SkillEffectTarget {
|
enum class SkillEffectTarget {
|
||||||
POINT = 1,
|
POINT = 1,
|
||||||
SELF = 2,
|
SELF = 2,
|
||||||
@@ -21,7 +64,7 @@ enum class SkillEffectTarget {
|
|||||||
|
|
||||||
enum class SkillTargetType {
|
enum class SkillTargetType {
|
||||||
MOBS = 1,
|
MOBS = 1,
|
||||||
SELF = 2,
|
PLAYERS = 2,
|
||||||
GROUP = 3
|
GROUP = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +77,7 @@ struct SkillResult {
|
|||||||
size_t size;
|
size_t size;
|
||||||
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||||
SkillResult(size_t len, void* dat) {
|
SkillResult(size_t len, void* dat) {
|
||||||
|
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||||
size = len;
|
size = len;
|
||||||
memcpy(payload, dat, len);
|
memcpy(payload, dat, len);
|
||||||
}
|
}
|
||||||
@@ -43,7 +87,7 @@ struct SkillResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SkillData {
|
struct SkillData {
|
||||||
int skillType; // eST
|
SkillType skillType; // eST
|
||||||
SkillEffectTarget effectTarget;
|
SkillEffectTarget effectTarget;
|
||||||
int effectType; // always 1?
|
int effectType; // always 1?
|
||||||
SkillTargetType targetType;
|
SkillTargetType targetType;
|
||||||
@@ -63,6 +107,6 @@ namespace Abilities {
|
|||||||
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||||
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||||
|
|
||||||
std::vector<ICombatant*> matchTargets(SkillData*, int, int32_t*);
|
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||||
int getCSTBFromST(int eSkillType);
|
int getCSTBFromST(SkillType skillType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ int Buff::getValue(BuffValueSelector selector) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityRef Buff::getLastSource() {
|
||||||
|
if(stacks.empty())
|
||||||
|
return self;
|
||||||
|
return stacks.back().source;
|
||||||
|
}
|
||||||
|
|
||||||
bool Buff::isStale() {
|
bool Buff::isStale() {
|
||||||
return stacks.empty();
|
return stacks.empty();
|
||||||
}
|
}
|
||||||
@@ -137,15 +143,56 @@ void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* st
|
|||||||
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Buffs::timeBuffTick(EntityRef self, Buff* buff) {
|
||||||
|
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not implemented
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt);
|
||||||
|
pkt.eCT = combatant->getCharType();
|
||||||
|
pkt.iID = combatant->getID();
|
||||||
|
pkt.iTB_ID = buff->id;
|
||||||
|
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
}
|
||||||
|
|
||||||
void Buffs::timeBuffTimeout(EntityRef self) {
|
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||||
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
return; // not a combatant
|
return; // not a combatant
|
||||||
Entity* entity = self.getEntity();
|
Entity* entity = self.getEntity();
|
||||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||||
pkt.eCT = combatant->getCharType();
|
int32_t eCharType = combatant->getCharType();
|
||||||
|
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
|
||||||
pkt.iID = combatant->getID();
|
pkt.iID = combatant->getID();
|
||||||
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) {
|
||||||
|
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not implemented
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
int damage = combatant->getMaxHP() / 100 * mult;
|
||||||
|
int dealt = combatant->takeDamage(buff->getLastSource(), damage);
|
||||||
|
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||||
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
|
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||||
|
pkt->iID = self.id;
|
||||||
|
pkt->eCT = combatant->getCharType();
|
||||||
|
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||||
|
|
||||||
|
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
drain->iDamage = dealt;
|
||||||
|
drain->iHP = combatant->getCurrentHP();
|
||||||
|
drain->eCT = pkt->eCT;
|
||||||
|
drain->iID = pkt->iID;
|
||||||
|
|
||||||
|
NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||||
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ public:
|
|||||||
BuffClass maxClass();
|
BuffClass maxClass();
|
||||||
|
|
||||||
int getValue(BuffValueSelector selector);
|
int getValue(BuffValueSelector selector);
|
||||||
|
EntityRef getLastSource();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In general, a Buff object won't exist
|
* In general, a Buff object won't exist
|
||||||
@@ -86,5 +87,7 @@ public:
|
|||||||
|
|
||||||
namespace Buffs {
|
namespace Buffs {
|
||||||
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||||
|
void timeBuffTick(EntityRef self, Buff* buff);
|
||||||
void timeBuffTimeout(EntityRef self);
|
void timeBuffTimeout(EntityRef self);
|
||||||
|
void tickDrain(EntityRef self, Buff* buff, int mult);
|
||||||
}
|
}
|
||||||
|
|||||||
140
src/Combat.cpp
140
src/Combat.cpp
@@ -21,13 +21,14 @@ std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
|||||||
|
|
||||||
#pragma region Player
|
#pragma region Player
|
||||||
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||||
EntityRef self = PlayerManager::getSockFromID(iID);
|
if(!isAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
if(!hasBuff(buffId)) {
|
if(!hasBuff(buffId)) {
|
||||||
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||||
buffs[buffId]->addStack(stack);
|
buffs[buffId]->addStack(stack);
|
||||||
return false;
|
return false;
|
||||||
@@ -48,9 +49,10 @@ void Player::removeBuff(int buffId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::removeBuff(int buffId, int buffClass) {
|
void Player::removeBuff(int buffId, BuffClass buffClass) {
|
||||||
if(hasBuff(buffId)) {
|
if(hasBuff(buffId)) {
|
||||||
buffs[buffId]->clear((BuffClass)buffClass);
|
buffs[buffId]->clear(buffClass);
|
||||||
|
// buff might not be stale since another buff class might remain
|
||||||
if(buffs[buffId]->isStale()) {
|
if(buffs[buffId]->isStale()) {
|
||||||
delete buffs[buffId];
|
delete buffs[buffId];
|
||||||
buffs.erase(buffId);
|
buffs.erase(buffId);
|
||||||
@@ -58,6 +60,16 @@ void Player::removeBuff(int buffId, int buffClass) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::clearBuffs(bool force) {
|
||||||
|
auto it = buffs.begin();
|
||||||
|
while(it != buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
if(!force) buff->clear();
|
||||||
|
delete buff;
|
||||||
|
it = buffs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Player::hasBuff(int buffId) {
|
bool Player::hasBuff(int buffId) {
|
||||||
auto buff = buffs.find(buffId);
|
auto buff = buffs.find(buffId);
|
||||||
return buff != buffs.end() && !buff->second->isStale();
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
@@ -156,29 +168,78 @@ void Player::step(time_t currTime) {
|
|||||||
// buffs
|
// buffs
|
||||||
for(auto buffEntry : buffs) {
|
for(auto buffEntry : buffs) {
|
||||||
buffEntry.second->combatTick(currTime);
|
buffEntry.second->combatTick(currTime);
|
||||||
|
if(!isAlive())
|
||||||
|
break; // unsafe to keep ticking if we're dead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region CombatNPC
|
#pragma region CombatNPC
|
||||||
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||||
|
if(!isAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (this->state != AIState::COMBAT && this->state != AIState::ROAMING)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!hasBuff(buffId)) {
|
||||||
|
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||||
|
buffs[buffId]->addStack(stack);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
Buff* CombatNPC::getBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
return buffs[buffId];
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
void CombatNPC::removeBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
buffs[buffId]->clear();
|
||||||
|
delete buffs[buffId];
|
||||||
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
buffs.erase(buffId);
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CombatNPC::getCompositeCondition() { /* stubbed */
|
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
|
||||||
return 0;
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear(buffClass);
|
||||||
|
// buff might not be stale since another buff class might remain
|
||||||
|
if(buffs[buffId]->isStale()) {
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::clearBuffs(bool force) {
|
||||||
|
auto it = buffs.begin();
|
||||||
|
while(it != buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
if(!force) buff->clear();
|
||||||
|
delete buff;
|
||||||
|
it = buffs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CombatNPC::hasBuff(int buffId) {
|
||||||
|
auto buff = buffs.find(buffId);
|
||||||
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getCompositeCondition() {
|
||||||
|
int conditionBitFlag = 0;
|
||||||
|
for(auto buff : buffs) {
|
||||||
|
if(!buff.second->isStale() && buff.second->id > 0)
|
||||||
|
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
||||||
|
}
|
||||||
|
return conditionBitFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||||
@@ -249,19 +310,19 @@ void CombatNPC::step(time_t currTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::transition(AIState newState, EntityRef src) {
|
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||||
|
|
||||||
state = newState;
|
state = newState;
|
||||||
|
|
||||||
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
||||||
transitionHandlers[newState](this, src);
|
transitionHandlers[newState](this, src);
|
||||||
else {
|
else {
|
||||||
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
||||||
transition(AIState::INACTIVE, id);
|
transition(AIState::INACTIVE, id);
|
||||||
}
|
}
|
||||||
/* TODO: fire any triggered events
|
|
||||||
|
// trigger special NPCEvents, if applicable
|
||||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||||
if (event.trigger == ON_KILLED && event.npcType == type)
|
if (event.triggerState == newState && event.npcType == type)
|
||||||
event.handler(src, this);
|
event.handler(this);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
@@ -304,11 +365,40 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 3+ targets should never be possible
|
||||||
|
if (targetCount > 3)
|
||||||
|
plr->suspicionRating += 10001;
|
||||||
|
|
||||||
|
// kill the socket when the player is too suspicious
|
||||||
|
if (plr->suspicionRating > 10000) {
|
||||||
|
sock->kill();
|
||||||
|
CNShardServer::_killConnection(sock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
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;
|
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
|
||||||
* ordinary cheating. If the client sends a very large number of trailing
|
* ordinary cheating. If the client sends a very large number of trailing
|
||||||
@@ -845,9 +935,12 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||||
// nano has skill data
|
// nano has skill data
|
||||||
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||||
if (skill->drainType == SkillDrainType::PASSIVE)
|
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
Nanos::applyNanoBuff(skill, plr);
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
// ^ composite condition calculation is separate from combat for responsiveness
|
int32_t targets[] = { plr->iID };
|
||||||
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
|
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,6 +960,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
auto it = plr->buffs.begin();
|
auto it = plr->buffs.begin();
|
||||||
while(it != plr->buffs.end()) {
|
while(it != plr->buffs.end()) {
|
||||||
Buff* buff = (*it).second;
|
Buff* buff = (*it).second;
|
||||||
|
//buff->combatTick() gets called in Player::step
|
||||||
buff->tick(currTime);
|
buff->tick(currTime);
|
||||||
if(buff->isStale()) {
|
if(buff->isStale()) {
|
||||||
// garbage collect
|
// garbage collect
|
||||||
|
|||||||
@@ -678,7 +678,6 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
|||||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf));
|
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
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));
|
||||||
|
|||||||
56
src/Eggs.cpp
56
src/Eggs.cpp
@@ -26,10 +26,11 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkillResult result = SkillResult();
|
||||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
// apply buff
|
// apply buff
|
||||||
if(skill->targetType != SkillTargetType::SELF) {
|
if(skill->targetType != SkillTargetType::PLAYERS) {
|
||||||
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,12 +51,57 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|||||||
// no-op
|
// no-op
|
||||||
},
|
},
|
||||||
&eggBuff);
|
&eggBuff);
|
||||||
|
|
||||||
|
sSkillResult_Buff resultBuff{};
|
||||||
|
resultBuff.eCT = plr->getCharType();
|
||||||
|
resultBuff.iID = plr->getID();
|
||||||
|
resultBuff.bProtected = false;
|
||||||
|
resultBuff.iConditionBitFlag = plr->getCompositeCondition();
|
||||||
|
result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff);
|
||||||
|
} else {
|
||||||
|
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
||||||
|
sSkillResult_Damage resultDamage{};
|
||||||
|
sSkillResult_Heal_HP resultHeal{};
|
||||||
|
switch(skill->skillType)
|
||||||
|
{
|
||||||
|
case SkillType::DAMAGE:
|
||||||
|
resultDamage.bProtected = false;
|
||||||
|
resultDamage.eCT = plr->getCharType();
|
||||||
|
resultDamage.iID = plr->getID();
|
||||||
|
resultDamage.iDamage = plr->takeDamage(src, value);
|
||||||
|
resultDamage.iHP = plr->getCurrentHP();
|
||||||
|
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
||||||
|
break;
|
||||||
|
case SkillType::HEAL_HP:
|
||||||
|
resultHeal.eCT = plr->getCharType();
|
||||||
|
resultHeal.iID = plr->getID();
|
||||||
|
resultHeal.iHealHP = plr->heal(src, value);
|
||||||
|
resultHeal.iHP = plr->getCurrentHP();
|
||||||
|
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// use skill
|
// initialize response struct
|
||||||
std::vector<ICombatant*> targets;
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
||||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
Abilities::useNPCSkill(src, skillId, targets);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
|
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||||
|
pkt->iNPC_ID = eggId;
|
||||||
|
pkt->iSkillID = skillId;
|
||||||
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
|
pkt->iTargetCnt = 1;
|
||||||
|
|
||||||
|
if(result.size > 0) {
|
||||||
|
void* attached = (void*)(pkt + 1);
|
||||||
|
memcpy(attached, result.payload, result.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggStep(CNServer* serv, time_t currTime) {
|
static void eggStep(CNServer* serv, time_t currTime) {
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
sNPCAppearanceData data = {};
|
sNPCAppearanceData data = {};
|
||||||
data.iAngle = angle;
|
data.iAngle = angle;
|
||||||
data.iBarkerType = 0; // unused?
|
data.iBarkerType = 0; // unused?
|
||||||
data.iConditionBitFlag = cbf;
|
data.iConditionBitFlag = 0;
|
||||||
data.iHP = hp;
|
data.iHP = hp;
|
||||||
data.iNPCType = type;
|
data.iNPCType = type;
|
||||||
data.iNPC_ID = id;
|
data.iNPC_ID = id;
|
||||||
@@ -50,6 +50,12 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||||
|
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||||
|
data.iConditionBitFlag = getCompositeCondition();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Entity coming into view.
|
* Entity coming into view.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ public:
|
|||||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||||
virtual Buff* getBuff(int) = 0;
|
virtual Buff* getBuff(int) = 0;
|
||||||
virtual void removeBuff(int) = 0;
|
virtual void removeBuff(int) = 0;
|
||||||
virtual void removeBuff(int, int) = 0;
|
virtual void removeBuff(int, BuffClass) = 0;
|
||||||
|
virtual void clearBuffs(bool) = 0;
|
||||||
virtual bool hasBuff(int) = 0;
|
virtual bool hasBuff(int) = 0;
|
||||||
virtual int getCompositeCondition() = 0;
|
virtual int getCompositeCondition() = 0;
|
||||||
virtual int takeDamage(EntityRef, int) = 0;
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
@@ -72,7 +73,6 @@ public:
|
|||||||
int type;
|
int type;
|
||||||
int hp;
|
int hp;
|
||||||
int angle;
|
int angle;
|
||||||
int cbf;
|
|
||||||
bool loopingPath = false;
|
bool loopingPath = false;
|
||||||
|
|
||||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||||
@@ -80,7 +80,6 @@ public:
|
|||||||
type = t;
|
type = t;
|
||||||
hp = 400;
|
hp = 400;
|
||||||
angle = _A;
|
angle = _A;
|
||||||
cbf = 0;
|
|
||||||
id = _id;
|
id = _id;
|
||||||
instanceID = iID;
|
instanceID = iID;
|
||||||
};
|
};
|
||||||
@@ -88,7 +87,7 @@ public:
|
|||||||
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();
|
virtual sNPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||||
@@ -105,11 +104,13 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||||
|
|
||||||
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
|
|
||||||
|
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||||
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||||
spawnX = x;
|
this->spawnX = spawnX;
|
||||||
spawnY = y;
|
this->spawnY = spawnY;
|
||||||
spawnZ = z;
|
this->spawnZ = spawnZ;
|
||||||
|
|
||||||
kind = EntityKind::COMBAT_NPC;
|
kind = EntityKind::COMBAT_NPC;
|
||||||
|
|
||||||
@@ -117,12 +118,15 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
transitionHandlers[AIState::INACTIVE] = {};
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual sNPCAppearanceData getAppearanceData() override;
|
||||||
|
|
||||||
virtual bool isExtant() override { return hp > 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 bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
virtual Buff* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId, int buffClass) override;
|
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||||
|
virtual void clearBuffs(bool force) override;
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "EntityRef.hpp"
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
struct Group {
|
struct Group {
|
||||||
std::vector<EntityRef> members;
|
std::vector<EntityRef> members;
|
||||||
@@ -14,6 +15,10 @@ struct Group {
|
|||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
EntityRef getLeader() {
|
||||||
|
assert(members.size() > 0);
|
||||||
|
return members[0];
|
||||||
|
}
|
||||||
|
|
||||||
Group(EntityRef leader);
|
Group(EntityRef leader);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp->iSlotNum = request->iSlotNum;
|
resp->iSlotNum = request->iSlotNum;
|
||||||
resp->RemainItem = gumball;
|
resp->RemainItem = gumball;
|
||||||
resp->iTargetCnt = 1;
|
resp->iTargetCnt = 1;
|
||||||
resp->eST = EST_NANOSTIMPAK;
|
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
||||||
resp->iSkillID = 144;
|
resp->iSkillID = 144;
|
||||||
|
|
||||||
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||||
|
|||||||
179
src/MobAI.cpp
179
src/MobAI.cpp
@@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wake up sleeping monster
|
// wake up sleeping monster
|
||||||
if (cbf & CSB_BIT_MEZ) {
|
if (hasBuff(ECSB_MEZ)) {
|
||||||
cbf &= ~CSB_BIT_MEZ;
|
removeBuff(ECSB_MEZ);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||||
pkt1.eCT = 2;
|
pkt1.eCT = 2;
|
||||||
pkt1.iID = id;
|
pkt1.iID = id;
|
||||||
pkt1.iConditionBitFlag = cbf;
|
pkt1.iConditionBitFlag = getCompositeCondition();
|
||||||
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,13 +96,12 @@ static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
|
|||||||
|
|
||||||
void MobAI::clearDebuff(Mob *mob) {
|
void MobAI::clearDebuff(Mob *mob) {
|
||||||
mob->skillStyle = -1;
|
mob->skillStyle = -1;
|
||||||
mob->cbf = 0;
|
mob->clearBuffs(false);
|
||||||
mob->unbuffTimes.clear();
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||||
pkt1.eCT = 2;
|
pkt1.eCT = 2;
|
||||||
pkt1.iID = mob->id;
|
pkt1.iID = mob->id;
|
||||||
pkt1.iConditionBitFlag = mob->cbf;
|
pkt1.iConditionBitFlag = mob->getCompositeCondition();
|
||||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +227,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int style) {
|
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int mobStyle) {
|
||||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
||||||
@@ -247,7 +246,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
|||||||
|
|
||||||
resp->iNPC_ID = mob->id;
|
resp->iNPC_ID = mob->id;
|
||||||
resp->iSkillID = skillID;
|
resp->iSkillID = skillID;
|
||||||
resp->iStyle = style;
|
resp->iStyle = mobStyle;
|
||||||
resp->iValue1 = plr->x;
|
resp->iValue1 = plr->x;
|
||||||
resp->iValue2 = plr->y;
|
resp->iValue2 = plr->y;
|
||||||
resp->iValue3 = plr->z;
|
resp->iValue3 = plr->z;
|
||||||
@@ -281,26 +280,38 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
|||||||
respdata[i].iActiveNanoSlotNum = n;
|
respdata[i].iActiveNanoSlotNum = n;
|
||||||
respdata[i].iNanoID = plr->activeNano;
|
respdata[i].iNanoID = plr->activeNano;
|
||||||
|
|
||||||
int style2 = Nanos::nanoStyle(plr->activeNano);
|
int nanoStyle = Nanos::nanoStyle(plr->activeNano);
|
||||||
if (style2 == -1) { // no nano
|
if (nanoStyle == -1) { // no nano
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||||
} else if (style == style2) {
|
} else if (mobStyle == nanoStyle) {
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||||
respdata[i].iDamage = 0;
|
respdata[i].iDamage = 0;
|
||||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||||
} else if (style - style2 == 1 || style2 - style == 2) {
|
} else if (mobStyle - nanoStyle == 1 || nanoStyle - mobStyle == 2) {
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_WIN;
|
respdata[i].iHitFlag = HF_BIT_STYLE_WIN;
|
||||||
respdata[i].iDamage = 0;
|
respdata[i].iDamage = 0;
|
||||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
||||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
||||||
// fire damage power disguised as a corruption attack back at the enemy
|
// fire damage power disguised as a corruption attack back at the enemy
|
||||||
// TODO ABILITIES
|
SkillData skill = {
|
||||||
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
|
SkillType::DAMAGE, // skillType
|
||||||
for (auto& pwr : Abilities::Powers)
|
SkillEffectTarget::POINT, // effectTarget
|
||||||
if (pwr.skillType == EST_DAMAGE)
|
1, // effectType
|
||||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
|
SkillTargetType::MOBS, // targetType
|
||||||
|
SkillDrainType::ACTIVE, // drainType
|
||||||
|
0, // effectArea
|
||||||
|
{0, 0, 0, 0}, // batteryUse
|
||||||
|
{0, 0, 0, 0}, // durationTime
|
||||||
|
{0, 0, 0}, // valueTypes (unused)
|
||||||
|
{
|
||||||
|
{200, 200, 200, 200},
|
||||||
|
{200, 200, 200, 200},
|
||||||
|
{200, 200, 200, 200},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
|
||||||
} else {
|
} else {
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
||||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||||
@@ -327,12 +338,6 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void useAbilities(Mob *mob, time_t currTime) {
|
static void useAbilities(Mob *mob, time_t currTime) {
|
||||||
/*
|
|
||||||
* targetData approach
|
|
||||||
* first integer is the count
|
|
||||||
* second to fifth integers are IDs, these can be either player iID or mob's iID
|
|
||||||
* whether the skill targets players or mobs is determined by the skill packet being fired
|
|
||||||
*/
|
|
||||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||||
|
|
||||||
if (mob->skillStyle >= 0) { // corruption hit
|
if (mob->skillStyle >= 0) { // corruption hit
|
||||||
@@ -347,7 +352,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
if (mob->skillStyle == -2) { // eruption hit
|
if (mob->skillStyle == -2) { // eruption hit
|
||||||
int skillID = (int)mob->data["m_iMegaType"];
|
int skillID = (int)mob->data["m_iMegaType"];
|
||||||
std::vector<int> targetData = {0, 0, 0, 0, 0};
|
std::vector<ICombatant*> targets{};
|
||||||
|
|
||||||
// find the players within range of eruption
|
// find the players within range of eruption
|
||||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
||||||
@@ -357,26 +362,22 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
if (ref.kind != EntityKind::PLAYER)
|
if (ref.kind != EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CNSocket *s= ref.sock;
|
CNSocket *s = ref.sock;
|
||||||
Player *plr = PlayerManager::getPlayer(s);
|
Player *plr = PlayerManager::getPlayer(s);
|
||||||
|
|
||||||
if (plr->HP <= 0)
|
if (!plr->isAlive())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
||||||
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
||||||
targetData[0] += 1;
|
targets.push_back(plr);
|
||||||
targetData[targetData[0]] = plr->iID;
|
if (targets.size() > 3) // make sure not to have more than 4
|
||||||
if (targetData[0] > 3) // make sure not to have more than 4
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO ABILITIES
|
Abilities::useNPCSkill(mob->id, skillID, targets);
|
||||||
/*for (auto& pwr : Abilities::Powers)
|
|
||||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
|
||||||
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
|
||||||
mob->skillStyle = -3; // eruption cooldown
|
mob->skillStyle = -3; // eruption cooldown
|
||||||
mob->nextAttack = currTime + 1000;
|
mob->nextAttack = currTime + 1000;
|
||||||
return;
|
return;
|
||||||
@@ -394,14 +395,11 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
if (random < prob1) { // active skill hit
|
if (random < prob1) { // active skill hit
|
||||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||||
// TODO ABILITIES
|
SkillData* skill = &Abilities::SkillTable[skillID];
|
||||||
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
int debuffID = Abilities::getCSTBFromST(skill->skillType);
|
||||||
//for (auto& pwr : Abilities::Powers)
|
if(plr->hasBuff(debuffID))
|
||||||
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
return; // prevent debuffing a player twice
|
||||||
// if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
|
||||||
// return; // prevent debuffing a player twice
|
|
||||||
// pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
|
||||||
// }
|
|
||||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -442,31 +440,6 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drainMobHP(Mob *mob, int amount) {
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
|
||||||
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
|
||||||
|
|
||||||
pkt->iID = mob->id;
|
|
||||||
pkt->eCT = 4; // mob
|
|
||||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
|
||||||
|
|
||||||
drain->eCT = 4;
|
|
||||||
drain->iID = mob->id;
|
|
||||||
drain->iDamage = amount;
|
|
||||||
drain->iHP = mob->hp -= amount;
|
|
||||||
|
|
||||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
|
||||||
|
|
||||||
if (mob->hp <= 0)
|
|
||||||
mob->transition(AIState::DEAD, mob->target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||||
if (currTime == 0)
|
if (currTime == 0)
|
||||||
currTime = getTime();
|
currTime = getTime();
|
||||||
@@ -553,38 +526,27 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// drain
|
// tick buffs
|
||||||
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
auto it = npc->buffs.begin();
|
||||||
&& self->cbf & CSB_BIT_BOUNDINGBALL) {
|
while(it != npc->buffs.end()) {
|
||||||
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
|
Buff* buff = (*it).second;
|
||||||
self->lastDrainTime = currTime;
|
buff->combatTick(currTime);
|
||||||
}
|
|
||||||
|
|
||||||
// if drain killed the mob, return early
|
// if mob state changed, end the step
|
||||||
if (self->hp <= 0)
|
if(self->state != AIState::COMBAT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// unbuffing
|
buff->tick(currTime);
|
||||||
std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
|
if(buff->isStale()) {
|
||||||
while (it != self->unbuffTimes.end()) {
|
// garbage collect
|
||||||
|
it = npc->buffs.erase(it);
|
||||||
if (currTime >= it->second) {
|
delete buff;
|
||||||
self->cbf &= ~it->first;
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
|
||||||
pkt1.eCT = 2;
|
|
||||||
pkt1.iID = self->id;
|
|
||||||
pkt1.iConditionBitFlag = self->cbf;
|
|
||||||
NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
|
||||||
|
|
||||||
it = self->unbuffTimes.erase(it);
|
|
||||||
} else {
|
|
||||||
it++;
|
|
||||||
}
|
}
|
||||||
|
else it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip attack if stunned or asleep
|
// skip attack if stunned or asleep
|
||||||
if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) {
|
||||||
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -600,6 +562,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int distanceToTravel = INT_MAX;
|
int distanceToTravel = INT_MAX;
|
||||||
|
int speed = self->speed;
|
||||||
// movement logic: move when out of range but don't move while casting a skill
|
// movement logic: move when out of range but don't move while casting a skill
|
||||||
if (distance > mobRange && self->skillStyle == -1) {
|
if (distance > mobRange && self->skillStyle == -1) {
|
||||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||||
@@ -609,8 +572,8 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||||
self->speed /= 2;
|
speed /= 2;
|
||||||
|
|
||||||
int targetX = plr->x;
|
int targetX = plr->x;
|
||||||
int targetY = plr->y;
|
int targetY = plr->y;
|
||||||
@@ -619,9 +582,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
targetY += self->offsetY*distance/(self->idleRange + 1);
|
targetY += self->offsetY*distance/(self->idleRange + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5);
|
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
|
||||||
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
|
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
|
||||||
if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack)
|
if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack)
|
||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
|
|
||||||
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
||||||
@@ -629,7 +592,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||||
|
|
||||||
pkt.iNPC_ID = self->id;
|
pkt.iNPC_ID = self->id;
|
||||||
pkt.iSpeed = self->speed;
|
pkt.iSpeed = speed;
|
||||||
pkt.iToX = self->x = targ.first;
|
pkt.iToX = self->x = targ.first;
|
||||||
pkt.iToY = self->y = targ.second;
|
pkt.iToY = self->y = targ.second;
|
||||||
pkt.iToZ = plr->z;
|
pkt.iToZ = plr->z;
|
||||||
@@ -715,7 +678,7 @@ void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
|||||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||||
self->speed /= 2;
|
self->speed /= 2;
|
||||||
|
|
||||||
std::queue<Vec3> queue;
|
std::queue<Vec3> queue;
|
||||||
@@ -788,14 +751,10 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
|||||||
self->hp = self->maxHealth;
|
self->hp = self->maxHealth;
|
||||||
self->killedTime = 0;
|
self->killedTime = 0;
|
||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
self->cbf = 0;
|
|
||||||
|
|
||||||
// cast a return home heal spell, this is the right way(tm)
|
// cast a return home heal spell, this is the right way(tm)
|
||||||
// TODO ABILITIES
|
Abilities::useNPCSkill(npc->getRef(), 110, { npc });
|
||||||
/*std::vector<int> targetData = { 1, 0, 0, 0, 0 };
|
|
||||||
for (auto& pwr : Abilities::Powers)
|
|
||||||
if (pwr.skillType == Abilities::SkillTable[110].skillType)
|
|
||||||
pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/
|
|
||||||
// clear outlying debuffs
|
// clear outlying debuffs
|
||||||
clearDebuff(self);
|
clearDebuff(self);
|
||||||
}
|
}
|
||||||
@@ -812,12 +771,9 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
|||||||
self->roamY = self->y;
|
self->roamY = self->y;
|
||||||
self->roamZ = self->z;
|
self->roamZ = self->z;
|
||||||
|
|
||||||
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
|
int skillID = (int)self->data["m_iPassiveBuff"];
|
||||||
// TODO ABILITIES
|
if(skillID != 0) // cast passive
|
||||||
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
|
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
|
||||||
for (auto& pwr : Abilities::Powers)
|
|
||||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
|
||||||
pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
||||||
@@ -833,9 +789,8 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
|||||||
Mob* self = (Mob*)npc;
|
Mob* self = (Mob*)npc;
|
||||||
|
|
||||||
self->target = nullptr;
|
self->target = nullptr;
|
||||||
self->cbf = 0;
|
|
||||||
self->skillStyle = -1;
|
self->skillStyle = -1;
|
||||||
self->unbuffTimes.clear();
|
self->clearBuffs(true);
|
||||||
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
self->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
|
// check for the edge case where hitting the mob did not aggro it
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ namespace MobAI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Mob : public CombatNPC {
|
struct Mob : public CombatNPC {
|
||||||
// general
|
|
||||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
time_t killedTime = 0;
|
time_t killedTime = 0;
|
||||||
@@ -52,8 +50,8 @@ struct Mob : public CombatNPC {
|
|||||||
// 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 spawnX, int spawnY, int spawnZ, 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(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
||||||
sightRange(d["m_iSightRange"]) {
|
sightRange(d["m_iSightRange"]) {
|
||||||
state = AIState::ROAMING;
|
state = AIState::ROAMING;
|
||||||
|
|
||||||
@@ -64,15 +62,13 @@ struct Mob : public CombatNPC {
|
|||||||
idleRange = (int)data["m_iIdleRange"];
|
idleRange = (int)data["m_iIdleRange"];
|
||||||
level = data["m_iNpcLevel"];
|
level = data["m_iNpcLevel"];
|
||||||
|
|
||||||
roamX = x;
|
roamX = spawnX;
|
||||||
roamY = y;
|
roamY = spawnY;
|
||||||
roamZ = z;
|
roamZ = spawnZ;
|
||||||
|
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
|
|
||||||
cbf = 0;
|
|
||||||
|
|
||||||
// NOTE: there appear to be discrepancies in the dump
|
// NOTE: there appear to be discrepancies in the dump
|
||||||
hp = maxHealth;
|
hp = maxHealth;
|
||||||
|
|
||||||
|
|||||||
@@ -122,16 +122,15 @@ 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 spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||||
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||||
|
|
||||||
//assert(nextId < INT32_MAX);
|
|
||||||
int id = nextId--;
|
int id = nextId--;
|
||||||
int team = NPCData[type]["m_iTeam"];
|
int team = NPCData[type]["m_iTeam"];
|
||||||
BaseNPC *npc = nullptr;
|
BaseNPC *npc = nullptr;
|
||||||
|
|
||||||
if (team == 2) {
|
if (team == 2) {
|
||||||
npc = new Mob(x, y, z, inst, type, NPCData[type], id);
|
npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id);
|
||||||
|
|
||||||
// re-enable respawning, if desired
|
// re-enable respawning, if desired
|
||||||
((Mob*)npc)->summoned = !respawn;
|
((Mob*)npc)->summoned = !respawn;
|
||||||
@@ -294,57 +293,55 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
|||||||
return npc;
|
return npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to MobAI, possibly
|
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
||||||
#pragma region NPCEvents
|
#pragma region NPCEvents
|
||||||
|
|
||||||
// summon right arm and stage 2 body
|
// summon right arm and stage 2 body
|
||||||
static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
|
static void lordFuseStageTwo(CombatNPC *npc) {
|
||||||
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
std::cout << "Lord Fuse stage two" << std::endl;
|
std::cout << "Lord Fuse stage two" << std::endl;
|
||||||
|
|
||||||
// Fuse doesn't move
|
// Fuse doesn't move
|
||||||
// Blastons, Heal
|
// Blastons, Heal
|
||||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
|
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467);
|
||||||
|
|
||||||
newbody->angle = oldbody->angle;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||||
plr->instanceID, oldbody->angle);
|
oldbody->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// right arm, Adaptium, Stun
|
// right arm, Adaptium, Stun
|
||||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
|
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469);
|
||||||
|
|
||||||
arm->angle = oldbody->angle;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||||
plr->instanceID, oldbody->angle);
|
oldbody->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// summon left arm and stage 3 body
|
// summon left arm and stage 3 body
|
||||||
static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
static void lordFuseStageThree(CombatNPC *npc) {
|
||||||
Mob *oldbody = (Mob*)npc;
|
Mob *oldbody = (Mob*)npc;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
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->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
|
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468);
|
||||||
|
|
||||||
newbody->angle = oldbody->angle;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||||
plr->instanceID, oldbody->angle);
|
newbody->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// Blastons, Heal
|
// Blastons, Heal
|
||||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
|
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470);
|
||||||
|
|
||||||
arm->angle = oldbody->angle;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||||
plr->instanceID, oldbody->angle);
|
arm->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||||
NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
|
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
||||||
NPCEvent(2467, ON_KILLED, lordFuseStageThree),
|
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma endregion NPCEvents
|
#pragma endregion NPCEvents
|
||||||
@@ -376,5 +373,5 @@ void NPCManager::init() {
|
|||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||||
|
|
||||||
REGISTER_SHARD_TIMER(step, 200);
|
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,15 @@
|
|||||||
|
|
||||||
#define RESURRECT_HEIGHT 400
|
#define RESURRECT_HEIGHT 400
|
||||||
|
|
||||||
enum Trigger {
|
typedef void (*NPCEventHandler)(CombatNPC*);
|
||||||
ON_KILLED,
|
|
||||||
ON_COMBAT
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
|
|
||||||
|
|
||||||
struct NPCEvent {
|
struct NPCEvent {
|
||||||
int32_t npcType;
|
int32_t npcType;
|
||||||
int trigger;
|
AIState triggerState;
|
||||||
NPCEventHandler handler;
|
NPCEventHandler handler;
|
||||||
|
|
||||||
NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
|
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
||||||
: npcType(t), trigger(tr), handler(hndlr) {}
|
: npcType(t), triggerState(tr), handler(hndlr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace NPCManager {
|
namespace NPCManager {
|
||||||
|
|||||||
@@ -69,52 +69,6 @@ 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);
|
|
||||||
std::vector<ICombatant*> affected;
|
|
||||||
std::vector<EntityRef> targets;
|
|
||||||
if (skill->targetType == SkillTargetType::GROUP) {
|
|
||||||
targets = plr->getGroupMembers(); // group
|
|
||||||
}
|
|
||||||
else if(skill->targetType == SkillTargetType::SELF) {
|
|
||||||
targets.push_back(self); // self
|
|
||||||
} else {
|
|
||||||
std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (EntityRef target : targets) {
|
|
||||||
Entity* entity = target.getEntity();
|
|
||||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
|
||||||
continue; // not a combatant
|
|
||||||
|
|
||||||
passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO;
|
|
||||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
|
||||||
if(combatant->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(combatant);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -139,8 +93,10 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
// passive buff effect
|
// passive buff effect
|
||||||
resp.eCSTB___Add = 1;
|
resp.eCSTB___Add = 1;
|
||||||
std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
int32_t targets[] = { plr->iID };
|
||||||
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
|
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent) // silent nano death but only for the summoning player
|
if (!silent) // silent nano death but only for the summoning player
|
||||||
@@ -317,11 +273,11 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO ABILITIES
|
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
|
||||||
std::vector<ICombatant*> targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||||
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||||
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,5 +26,4 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ struct Player : public Entity, public ICombatant {
|
|||||||
uint64_t iFirstUseFlag[2] = {};
|
uint64_t iFirstUseFlag[2] = {};
|
||||||
time_t lastHeartbeat = 0;
|
time_t lastHeartbeat = 0;
|
||||||
|
|
||||||
|
int suspicionRating = 0;
|
||||||
|
time_t lastShot = 0;
|
||||||
std::vector<sItemBase> buyback = {};
|
std::vector<sItemBase> buyback = {};
|
||||||
|
|
||||||
Player() { kind = EntityKind::PLAYER; }
|
Player() { kind = EntityKind::PLAYER; }
|
||||||
@@ -90,7 +92,8 @@ struct Player : public Entity, public ICombatant {
|
|||||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
virtual Buff* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId, int buffClass) override;
|
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
||||||
|
virtual void clearBuffs(bool force) override;
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
|||||||
@@ -23,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,66 +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);
|
||||||
|
|
||||||
// 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
|
||||||
@@ -284,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
|
||||||
@@ -298,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);
|
||||||
@@ -313,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);
|
||||||
|
|
||||||
@@ -331,12 +332,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||||
|
Player* plr = getPlayer(sock);
|
||||||
|
if (plr->group == nullptr)
|
||||||
|
return;
|
||||||
|
for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER))
|
||||||
|
ref.sock->sendPacket(buf, type, size);
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||||
Player* plr = getPlayer(sock);
|
Player* plr = getPlayer(sock);
|
||||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||||
@@ -400,14 +409,14 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
// TODO ABILITIES
|
|
||||||
//Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // plain respawn
|
default: // plain respawn
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
|
plr->clearBuffs(false);
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case ePCRegenType::Unstick: // warp away
|
case ePCRegenType::Unstick: // warp away
|
||||||
move = true;
|
move = true;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace PlayerManager {
|
|||||||
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
||||||
WarpLocation *getRespawnPoint(Player *plr);
|
WarpLocation *getRespawnPoint(Player *plr);
|
||||||
|
|
||||||
|
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||||
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||||
|
|
||||||
// TODO: unify this under the new Entity system
|
// TODO: unify this under the new Entity system
|
||||||
|
|||||||
@@ -33,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.
|
||||||
*/
|
*/
|
||||||
@@ -674,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"];
|
||||||
@@ -723,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
|
||||||
@@ -776,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);
|
||||||
@@ -795,6 +815,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
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"];
|
||||||
|
|
||||||
@@ -815,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);
|
||||||
|
|
||||||
@@ -871,10 +897,9 @@ 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)
|
||||||
@@ -916,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"];
|
||||||
|
|
||||||
@@ -947,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"];
|
||||||
@@ -977,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);
|
||||||
|
|
||||||
@@ -1082,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];
|
||||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
|
||||||
if (!fstream.fail()) {
|
// scope for fstream
|
||||||
fstream >> *table.first; // load file contents into table
|
{
|
||||||
} else {
|
std::ifstream fstream;
|
||||||
if (table.first != &gruntwork) { // gruntwork isn't critical
|
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||||
|
|
||||||
|
// did we fail to open the file?
|
||||||
|
if (fstream.fail()) {
|
||||||
|
// gruntwork isn't critical
|
||||||
|
if (table.first == &gruntwork)
|
||||||
|
continue;
|
||||||
|
|
||||||
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
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
|
||||||
|
|
||||||
@@ -1109,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
using namespace Trading;
|
using namespace Trading;
|
||||||
|
|
||||||
@@ -272,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) {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "Items.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;
|
||||||
@@ -58,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) {
|
||||||
@@ -229,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;
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,49 +46,8 @@ enum class ePCRegenType {
|
|||||||
End
|
End
|
||||||
};
|
};
|
||||||
|
|
||||||
// nano powers
|
// nano power flags
|
||||||
enum {
|
enum {
|
||||||
EST_NONE = 0,
|
|
||||||
EST_DAMAGE = 1,
|
|
||||||
EST_HEAL_HP = 2,
|
|
||||||
EST_KNOCKDOWN = 3,
|
|
||||||
EST_SLEEP = 4,
|
|
||||||
EST_SNARE = 5,
|
|
||||||
EST_HEAL_STAMINA = 6,
|
|
||||||
EST_STAMINA_SELF = 7,
|
|
||||||
EST_STUN = 8,
|
|
||||||
EST_WEAPONSLOW = 9,
|
|
||||||
EST_JUMP = 10,
|
|
||||||
EST_RUN = 11,
|
|
||||||
EST_STEALTH = 12,
|
|
||||||
EST_SWIM = 13,
|
|
||||||
EST_MINIMAPENEMY = 14,
|
|
||||||
EST_MINIMAPTRESURE = 15,
|
|
||||||
EST_PHOENIX = 16,
|
|
||||||
EST_PROTECTBATTERY = 17,
|
|
||||||
EST_PROTECTINFECTION = 18,
|
|
||||||
EST_REWARDBLOB = 19,
|
|
||||||
EST_REWARDCASH = 20,
|
|
||||||
EST_BATTERYDRAIN = 21,
|
|
||||||
EST_CORRUPTIONATTACK = 22,
|
|
||||||
EST_INFECTIONDAMAGE = 23,
|
|
||||||
EST_KNOCKBACK = 24,
|
|
||||||
EST_FREEDOM = 25,
|
|
||||||
EST_PHOENIX_GROUP = 26,
|
|
||||||
EST_RECALL = 27,
|
|
||||||
EST_RECALL_GROUP = 28,
|
|
||||||
EST_RETROROCKET_SELF = 29,
|
|
||||||
EST_BLOODSUCKING = 30,
|
|
||||||
EST_BOUNDINGBALL = 31,
|
|
||||||
EST_INVULNERABLE = 32,
|
|
||||||
EST_NANOSTIMPAK = 33,
|
|
||||||
EST_RETURNHOMEHEAL = 34,
|
|
||||||
EST_BUFFHEAL = 35,
|
|
||||||
EST_EXTRABANK = 36,
|
|
||||||
EST__END = 37,
|
|
||||||
EST_CORRUPTIONATTACKWIN = 38,
|
|
||||||
EST_CORRUPTIONATTACKLOSE = 39,
|
|
||||||
|
|
||||||
ECSB_NONE = 0,
|
ECSB_NONE = 0,
|
||||||
ECSB_UP_MOVE_SPEED = 1,
|
ECSB_UP_MOVE_SPEED = 1,
|
||||||
ECSB_UP_SWIM_SPEED = 2,
|
ECSB_UP_SWIM_SPEED = 2,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
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();
|
||||||
@@ -210,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;
|
||||||
@@ -473,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();
|
||||||
|
|
||||||
@@ -487,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) {
|
||||||
|
|||||||
@@ -18,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);
|
||||||
@@ -117,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
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#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
|
#define MS_PER_PLAYER_TICK 500
|
||||||
|
#define MS_PER_COMBAT_TICK 200
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -21,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
|
||||||
@@ -109,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);
|
||||||
|
|||||||
@@ -12,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
Submodule tdata updated: 8230fb8649...cc65dbb402
Reference in New Issue
Block a user