56 Commits

Author SHA1 Message Date
gsemaj
bbaaa53df2 [WIP] Active power handling
TODO:
- recall (self and group) is broken
- revive (only group) is broken
- damage + debuff is unimplemented
2022-11-27 17:39:08 -05:00
gsemaj
273b73379e Sync with master 2022-07-30 22:50:03 -07:00
gsemaj
1709d2b0ef Add overload to remove specific class of buff
I initially added this because, despite the higher tickrate for
composite condition calculations thanks to the last commit, there is
still a slight status icon delay when rapidly switching nanos. I
attempted to use this to make that problem go away and for whatever
reason it wasn't effective, but I figure it would be useful to have
anyway so I'm keeping it.
2022-07-30 22:35:20 -07:00
gsemaj
457c320d48 Move some stuff from playerTick to player combat step 2022-07-30 22:35:19 -07:00
gsemaj
29024e9351 Refactor group handling 2022-07-30 22:35:19 -07:00
gsemaj
0994aaff71 Port egg buffs over to new system 2022-07-30 22:35:19 -07:00
gsemaj
cb94018e46 More skill handlers
Note: need to revisit these when active powers are implemented to make
sure they are correct. DamageNDebuff isn't even implemented yet.
2022-07-30 22:35:18 -07:00
gsemaj
e0a0cc186e Passive nano powers 2022-07-30 22:35:18 -07:00
gsemaj
d4253c3b49 YET ANOTHER ITERATION of the new ability system
I am very tired
2022-07-30 22:35:17 -07:00
gsemaj
5b58055924 Passive nano powers pt 1 2022-07-30 22:35:17 -07:00
gsemaj
7a7cdb1330 Passive nano powers boilerplate 2022-07-30 22:35:16 -07:00
gsemaj
0e054e21b6 Fix timed out buffs not calling onExpire 2022-07-30 22:35:16 -07:00
gsemaj
ad9bf2a9e3 Buff framework tweaks + polish 2022-07-30 22:35:16 -07:00
gsemaj
a38bf0e7be Rework buff callbacks
The first implementation was way too complicated and prone to bugs.
This is much more simple flexible; first off, std::function is now used
instead of a raw function pointer, so lambdas and binds are fair game
which is great for scripting. Second, callbacks for all stacks are
executed. It is up to the callback target to ensure correct behavior.
2022-07-30 22:35:15 -07:00
gsemaj
3d22103113 oops 2022-07-30 22:35:15 -07:00
gsemaj
761581afd7 CRLF purge in Buffs.cpp 2022-07-30 22:35:15 -07:00
gsemaj
daec7a33b7 egg prep 2022-07-30 22:35:14 -07:00
gsemaj
462ced6685 Move Buff implementation to Buffs.cpp 2022-07-30 22:35:14 -07:00
gsemaj
cc190efc63 New buff framework (player implementation)
Get rid of `iConditionBitFlag` in favor of a system of individual buff
objects that get composited to a bitflag on-the-fly.
Buff objects can have callbacks for application, expiration, and tick,
making them pretty flexible. Scripting languages can eventually use
these for custom behavior, too.

TODO:
- Get rid of bitflag in BaseNPC
- Apply buffs from passive nano powers
- Apply buffs from active nano powers
- Move eggs to new system
- ???
2022-07-30 22:35:14 -07:00
gsemaj
df1b4ff160 The great re-#include
Was getting frustrated by the inconsistency in our include statements,
which were causing me problems. As a result, I went through and manually
re-organized every include statement in non-core files.

I'm just gonna copy my rant from Discord:
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
2022-07-30 22:35:13 -07:00
gsemaj
9f7c8a18df Get rid of player fire rate suspicion
This was super primitive & jank, and caused false positives.
Will replace with a polished system later on.
2022-07-30 22:32:25 -07:00
gsemaj
ea61ed1aaa Handle case where cmake is invoked outside root 2022-07-30 22:32:25 -07:00
gsemaj
9982d9bdda Start moving passive power processing to playerTick 2022-07-30 22:32:25 -07:00
gsemaj
398e9fddf8 Groundwork for new buff system 2022-07-30 22:32:24 -07:00
gsemaj
fd664c4f19 Active power classification 2022-07-30 22:32:24 -07:00
gsemaj
feaaf4cab9 some struct reorg 2022-07-30 22:32:24 -07:00
gsemaj
d3e6bae5d9 Replace group filter operator with function 2022-07-30 22:32:24 -07:00
gsemaj
0f042c4233 Ignore .bak files
for my local backups lol
2022-07-30 22:32:24 -07:00
gsemaj
3dc7124f58 Refactor player groups
Group structures are used now. Adds more checks in some places but simplifies things overall.
We can expand this system to entities as well now pretty trivially.
2022-07-30 22:32:23 -07:00
gsemaj
71e78afa0b (WIP) EXPERIMENTAL GROUP CHANGES 2022-07-30 22:32:23 -07:00
gsemaj
0a2b3fbdad (WIP) TODO ABILITIES 2022-07-30 22:31:18 -07:00
gsemaj
911dbaab83 Move mob aggro logic into takeDamage override
God that feels good
2022-07-30 22:31:18 -07:00
gsemaj
7183180be5 (WIP) Move away from rigid states/transitions to allow custom behavior 2022-07-30 22:31:18 -07:00
gsemaj
3f1f78942e EntityType -> EntityKind 2022-07-30 22:31:18 -07:00
gsemaj
a9dea36882 (WIP) onRoamStart hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
de23779209 (WIP) Remove BaseNPC::barkerType to save space 2022-07-30 22:31:17 -07:00
gsemaj
748a82e223 ope 2022-07-30 22:31:17 -07:00
gsemaj
231e88fd55 (WIP) onCombatStart hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
abfb562489 (WIP) onDeath hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
589c5a8732 (WIP) Add src param to transition + certain hooks
Should all hooks have src? I think not
2022-07-30 22:31:17 -07:00
gsemaj
0b8b92b7f6 (WIP) Transitions + hook definitions + onRetreat hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
f0cf6326e5 (WIP) Point 2: Generalization 2022-07-30 22:31:16 -07:00
gsemaj
82bc94c01c (WIP) Point 1: step functions 2022-07-30 22:29:14 -07:00
gsemaj
5d9dcb8609 (WIP) Start implementing ICombatant
Start by replacing `hitMob` with `takeDamage` interface function.
Simplify `pcAttackChars` a little by utilizing the new interface, then add more interface functions as needed.

A lot of the combat logic is tied to the `Mob` class. Need to start moving stuff over to CombatNPC.
2022-07-30 22:29:13 -07:00
gsemaj
166e148878 (WIP) Move ICombatant functions around a bit 2022-07-30 22:29:12 -07:00
gsemaj
14b02ec5d2 (WIP) Initial ICombatant draft 2022-07-30 22:29:12 -07:00
d50c312227 [refactor] Get rid of NPC.hpp
This file was already obsoleted at the start of the refactor, but seems
to have escaped notice until now.
2022-07-30 22:29:10 -07:00
18ed96493f [refactor] Replace a few uses of magic numbers with enums 2022-07-30 22:29:10 -07:00
71a7d3d164 [refactor] Cosmetic cleanup in Fuse fight functions 2022-07-30 22:29:08 -07:00
520efd6dd5 [refactor] Remove redundant coord args from most entity constructors
Mobs and CombatNPCs still need theirs in order to properly set their
roaming and spawn coords. Assignment of the latter has been moved to the
CombatNPC constructor, where it should have already been.
2022-07-30 22:29:08 -07:00
gsemaj
0a8ef2ebbd [WIP] Stub power handler 2022-07-30 22:29:06 -07:00
gsemaj
3f4aca8a5d [WIP] Use EntityRef instead of CNSocket in ability handler 2022-07-30 22:29:06 -07:00
gsemaj
b0de75d80e [WIP] Replace appearance data with individual fields
Storing certain things in appearance data and others in their own fields
was gross. Now everything is stored on the same level and functions have
been added to generate appearance data when it's needed by the client.
2022-07-30 22:29:05 -07:00
gsemaj
8f88edaad1 [WIP] Rename Entity.type -> Entity.kind 2022-07-30 22:29:04 -07:00
gsemaj
6896d35427 [WIP] Fix Nanos -> Abilities namespace calls 2022-07-30 22:29:01 -07:00
gsemaj
d74a9747d9 [WIP] Initial merge of ability namespaces & features 2022-07-30 22:29:00 -07:00
51 changed files with 565 additions and 1127 deletions

View File

@@ -1 +0,0 @@
version.h

26
.vscode/launch.json vendored
View File

@@ -1,26 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug (Linux)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/fusion",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
"cwd": "${workspaceFolder}"
},
{
"name": "Release (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
"cwd": "${workspaceFolder}"
}
]
}

View File

@@ -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 PRIVATE pthread) target_link_libraries(openfusion pthread)
endif() endif()

View File

@@ -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,34 +81,3 @@ That way you can always keep master in sync with upstream with `git pull --ff-on
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`. 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.

View File

@@ -1,21 +0,0 @@
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

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 OpenFusion Contributors Copyright (c) 2020-2022 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

View File

@@ -25,8 +25,6 @@ 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).
@@ -37,7 +35,7 @@ Instructions for getting the client to run on Linux through Wine can be found [h
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 to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/) If you want, [compiled binaries (artifacts) for each 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.
@@ -83,7 +81,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. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg). OpenFusion has one external dependency: SQLite. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file. You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.

View File

@@ -1,12 +0,0 @@
version: '3.4'
services:
openfusion:
image: openfusion
build:
context: .
dockerfile: ./Dockerfile
ports:
- "23000:23000"
- "23001:23001"
- "8003:8003"

View File

@@ -1,12 +1,11 @@
#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;
@@ -48,107 +47,16 @@ 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) {
// take aggro // TODO abilities
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 = blocked; result.bProtected = false;
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();
@@ -163,24 +71,19 @@ 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 = 0; int boostDrain = (int)(skill->values[0][power] * scalingFactor);
int potionDrain = 0; if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
if(!blocked) { plr->batteryW -= boostDrain;
boostDrain = (int)(skill->values[0][power] * scalingFactor);
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
plr->batteryW -= boostDrain;
potionDrain = (int)(skill->values[1][power] * scalingFactor); int 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 = blocked; result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
result.iDrainW = boostDrain; result.iDrainW = boostDrain;
result.iBatteryW = plr->batteryW; result.iBatteryW = plr->batteryW;
result.iDrainN = potionDrain; result.iDrainN = potionDrain;
@@ -194,13 +97,8 @@ 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);
if(source == target) { PlayerManager::sendPlayerTo(source->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
// 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();
@@ -222,66 +120,56 @@ 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 SkillType::CORRUPTIONATTACK: case EST_DAMAGE:
case SkillType::CORRUPTIONATTACKLOSE: resultSize = sizeof(sSkillResult_Damage);
case SkillType::CORRUPTIONATTACKWIN:
// skillHandler = handleSkillCorruptionReflect;
// break;
case SkillType::DAMAGE:
skillHandler = handleSkillDamage; skillHandler = handleSkillDamage;
break; break;
case SkillType::HEAL_HP: case EST_HEAL_HP:
case SkillType::RETURNHOMEHEAL: case EST_RETURNHOMEHEAL:
resultSize = sizeof(sSkillResult_Heal_HP);
skillHandler = handleSkillHealHP; skillHandler = handleSkillHealHP;
break; break;
case SkillType::KNOCKDOWN: case EST_JUMP:
case SkillType::SLEEP: case EST_RUN:
case SkillType::SNARE: case EST_FREEDOM:
case SkillType::STUN: case EST_PHOENIX:
skillHandler = handleSkillDamageNDebuff; case EST_INVULNERABLE:
break; case EST_MINIMAPENEMY:
case SkillType::JUMP: case EST_MINIMAPTRESURE:
case SkillType::RUN: case EST_NANOSTIMPAK:
case SkillType::STEALTH: case EST_PROTECTBATTERY:
case SkillType::MINIMAPENEMY: case EST_PROTECTINFECTION:
case SkillType::MINIMAPTRESURE: case EST_REWARDBLOB:
case SkillType::PHOENIX: case EST_REWARDCASH:
case SkillType::PROTECTBATTERY: case EST_STAMINA_SELF:
case SkillType::PROTECTINFECTION: case EST_STEALTH:
case SkillType::REWARDBLOB: resultSize = sizeof(sSkillResult_Buff);
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 SkillType::BLOODSUCKING: case EST_BATTERYDRAIN:
skillHandler = handleSkillLeech; resultSize = sizeof(sSkillResult_BatteryDrain);
break;
case SkillType::RETROROCKET_SELF:
// no-op
return results;
case SkillType::PHOENIX_GROUP:
skillHandler = handleSkillResurrect;
break;
case SkillType::RECALL:
case SkillType::RECALL_GROUP:
skillHandler = handleSkillMove;
break;
case SkillType::BATTERYDRAIN:
skillHandler = handleSkillBatteryDrain; skillHandler = handleSkillBatteryDrain;
break; break;
case EST_RECALL: // still soft lock
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
return results;
default: default:
std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl; std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl;
return results; return results;
} }
@@ -289,8 +177,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 > MAX_SKILLRESULT_SIZE) { if(result.size != resultSize) {
std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl; std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
continue; continue;
} }
results.push_back(result); results.push_back(result);
@@ -298,42 +186,36 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
return results; return results;
} }
static void attachSkillResults(std::vector<SkillResult> results, uint8_t* pivot) { static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
for(SkillResult& result : results) { for(SkillResult& result : results) {
size_t sz = result.size; memcpy(pivot, result.payload, resultSize);
memcpy(pivot, result.payload, sz); pivot += resultSize;
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;
if(skill->drainType == SkillDrainType::ACTIVE) { nano.iStamina -= skill->batteryUse[boost];
nano.iStamina -= skill->batteryUse[boost]; if (nano.iStamina < 0)
if (nano.iStamina <= 0) nano.iStamina = 0;
nano.iStamina = 0;
}
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected); std::vector<SkillResult> results = handleSkill(skill, boost, plr, affected);
if(results.empty()) return; // no effect; no need for confirmation packets size_t resultSize = 0; // guaranteed to be the same for every item
if (!results.empty()) resultSize = results.back().size;
// lazy validation since skill results might be different sizes if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
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); size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
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);
@@ -343,17 +225,15 @@ 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 = (int32_t)skill->skillType; pkt->eST = skill->skillType;
pkt->iTargetCnt = (int32_t)results.size(); pkt->iTargetCnt = (int32_t)results.size();
attachSkillResults(results, (uint8_t*)(pkt + 1)); attachSkillResults(results, resultSize, (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(skill->skillType == SkillType::RECALL_GROUP) if (nano.iStamina <= 0)
// group recall packet is sent only to group members Nanos::summonNano(sock, -1);
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) {
@@ -368,59 +248,32 @@ 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);
if(results.empty()) return; // no effect; no need for confirmation packets size_t resultSize = results.back().size; // guaranteed to be the same for every item
// lazy validation since skill results might be different sizes if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
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); size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
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 = (int32_t)skill->skillType; pkt->eST = 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, (uint8_t*)(pkt + 1)); attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
} }
static std::vector<ICombatant*> entityRefsToCombatants(std::vector<EntityRef> refs) { std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
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) {
@@ -433,8 +286,8 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
} }
} }
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::PLAYERS) { } else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) {
// player // players (?)
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));
@@ -448,69 +301,63 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
} }
/* ripped from client (enums emplaced) */ /* ripped from client (enums emplaced) */
int Abilities::getCSTBFromST(SkillType skillType) { int Abilities::getCSTBFromST(int eSkillType) {
int result = 0; int result = 0;
switch (skillType) switch (eSkillType)
{ {
case SkillType::RUN: case EST_RUN:
result = ECSB_UP_MOVE_SPEED; result = ECSB_UP_MOVE_SPEED;
break; break;
case SkillType::JUMP: case EST_JUMP:
result = ECSB_UP_JUMP_HEIGHT; result = ECSB_UP_JUMP_HEIGHT;
break; break;
case SkillType::STEALTH: case EST_STEALTH:
result = ECSB_UP_STEALTH; result = ECSB_UP_STEALTH;
break; break;
case SkillType::PHOENIX: case EST_PHOENIX:
result = ECSB_PHOENIX; result = ECSB_PHOENIX;
break; break;
case SkillType::PROTECTBATTERY: case EST_PROTECTBATTERY:
result = ECSB_PROTECT_BATTERY; result = ECSB_PROTECT_BATTERY;
break; break;
case SkillType::PROTECTINFECTION: case EST_PROTECTINFECTION:
result = ECSB_PROTECT_INFECTION; result = ECSB_PROTECT_INFECTION;
break; break;
case SkillType::MINIMAPENEMY: case EST_SNARE:
result = ECSB_MINIMAP_ENEMY;
break;
case SkillType::MINIMAPTRESURE:
result = ECSB_MINIMAP_TRESURE;
break;
case SkillType::REWARDBLOB:
result = ECSB_REWARD_BLOB;
break;
case SkillType::REWARDCASH:
result = ECSB_REWARD_CASH;
break;
case SkillType::FREEDOM:
result = ECSB_FREEDOM;
break;
case SkillType::INVULNERABLE:
result = ECSB_INVULNERABLE;
break;
case SkillType::BUFFHEAL:
result = ECSB_HEAL;
break;
case SkillType::NANOSTIMPAK:
result = ECSB_STIMPAKSLOT1;
// shift as necessary
break;
case SkillType::SNARE:
result = ECSB_DN_MOVE_SPEED; result = ECSB_DN_MOVE_SPEED;
break; break;
case SkillType::STUN: case EST_SLEEP:
result = ECSB_STUN;
break;
case SkillType::SLEEP:
result = ECSB_MEZ; result = ECSB_MEZ;
break; break;
case SkillType::INFECTIONDAMAGE: case EST_MINIMAPENEMY:
result = ECSB_MINIMAP_ENEMY;
break;
case EST_MINIMAPTRESURE:
result = ECSB_MINIMAP_TRESURE;
break;
case EST_REWARDBLOB:
result = ECSB_REWARD_BLOB;
break;
case EST_REWARDCASH:
result = ECSB_REWARD_CASH;
break;
case EST_INFECTIONDAMAGE:
result = ECSB_INFECTION; result = ECSB_INFECTION;
break; break;
case SkillType::BOUNDINGBALL: case EST_FREEDOM:
result = ECSB_FREEDOM;
break;
case EST_BOUNDINGBALL:
result = ECSB_BOUNDINGBALL; result = ECSB_BOUNDINGBALL;
break; break;
default: case EST_INVULNERABLE:
result = ECSB_INVULNERABLE;
break;
case EST_BUFFHEAL:
result = ECSB_HEAL;
break;
case EST_NANOSTIMPAK:
result = ECSB_STIMPAKSLOT1;
break; break;
} }
return result; return result;

View File

@@ -7,52 +7,9 @@
#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,
@@ -64,7 +21,7 @@ enum class SkillEffectTarget {
enum class SkillTargetType { enum class SkillTargetType {
MOBS = 1, MOBS = 1,
PLAYERS = 2, SELF = 2,
GROUP = 3 GROUP = 3
}; };
@@ -77,7 +34,6 @@ 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);
} }
@@ -87,7 +43,7 @@ struct SkillResult {
}; };
struct SkillData { struct SkillData {
SkillType skillType; // eST int skillType; // eST
SkillEffectTarget effectTarget; SkillEffectTarget effectTarget;
int effectType; // always 1? int effectType; // always 1?
SkillTargetType targetType; SkillTargetType targetType;
@@ -107,6 +63,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(ICombatant*, SkillData*, int, int32_t*); std::vector<ICombatant*> matchTargets(SkillData*, int, int32_t*);
int getCSTBFromST(SkillType skillType); int getCSTBFromST(int eSkillType);
} }

View File

@@ -95,12 +95,6 @@ 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();
} }
@@ -143,56 +137,15 @@ 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
int32_t eCharType = combatant->getCharType(); pkt.eCT = 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

View File

@@ -66,7 +66,6 @@ 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
@@ -87,7 +86,5 @@ 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);
} }

View File

@@ -21,14 +21,13 @@ 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) {
if(!isAlive()) EntityRef self = PlayerManager::getSockFromID(iID);
return false;
if(!hasBuff(buffId)) { if(!hasBuff(buffId)) {
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); buffs[buffId] = new Buff(buffId, self, 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;
@@ -49,10 +48,9 @@ void Player::removeBuff(int buffId) {
} }
} }
void Player::removeBuff(int buffId, BuffClass buffClass) { void Player::removeBuff(int buffId, int buffClass) {
if(hasBuff(buffId)) { if(hasBuff(buffId)) {
buffs[buffId]->clear(buffClass); buffs[buffId]->clear((BuffClass)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);
@@ -60,16 +58,6 @@ void Player::removeBuff(int buffId, BuffClass 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();
@@ -168,78 +156,29 @@ 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) { bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
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) { Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
if(hasBuff(buffId)) {
return buffs[buffId];
}
return nullptr; return nullptr;
} }
void CombatNPC::removeBuff(int buffId) { void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
if(hasBuff(buffId)) {
buffs[buffId]->clear(); void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
delete buffs[buffId];
buffs.erase(buffId); bool CombatNPC::hasBuff(int buffId) { /* stubbed */
} return false;
} }
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) { int CombatNPC::getCompositeCondition() { /* stubbed */
if(hasBuff(buffId)) { return 0;
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) {
@@ -310,19 +249,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.triggerState == newState && event.npcType == type) if (event.trigger == ON_KILLED && event.npcType == type)
event.handler(this); event.handler(src, this);
*/
} }
#pragma endregion #pragma endregion
@@ -365,40 +304,11 @@ 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
@@ -935,12 +845,9 @@ 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)
ICombatant* src = dynamic_cast<ICombatant*>(plr); Nanos::applyNanoBuff(skill, plr);
int32_t targets[] = { plr->iID }; // ^ composite condition calculation is separate from combat for responsiveness
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
}
} }
} }
@@ -960,7 +867,6 @@ 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

View File

@@ -678,6 +678,7 @@ 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));

View File

@@ -26,11 +26,10 @@ 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::PLAYERS) { if(skill->targetType != SkillTargetType::SELF) {
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;
} }
@@ -51,57 +50,12 @@ 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;
}
} }
// initialize response struct // use skill
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; std::vector<ICombatant*> targets;
uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; targets.push_back(dynamic_cast<ICombatant*>(plr));
memset(respbuf, 0, resplen); Abilities::useNPCSkill(src, skillId, targets);
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) {

View File

@@ -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->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4) if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
return; // sanity check return; // sanity check
// get email item from db and delete it // get email item from db and delete it
@@ -252,26 +252,11 @@ 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);
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack) // delete item
*real = { 0, 0, 0, 0 }; plr->Inven[attachment.iSlotNum] = { 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
@@ -291,7 +276,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
0 // DeleteTime (unimplemented) 0 // DeleteTime (unimplemented)
}; };
if (!Database::sendEmail(&email, attachments, plr)) { if (!Database::sendEmail(&email, attachments)) {
plr->money += cost; // give money back plr->money += cost; // give money back
// give items back // give items back
while (!attachments.empty()) { while (!attachments.empty()) {

View File

@@ -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 = 0; data.iConditionBitFlag = cbf;
data.iHP = hp; data.iHP = hp;
data.iNPCType = type; data.iNPCType = type;
data.iNPC_ID = id; data.iNPC_ID = id;
@@ -50,12 +50,6 @@ 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.
*/ */

View File

@@ -47,8 +47,7 @@ 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, BuffClass) = 0; virtual void removeBuff(int, int) = 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;
@@ -73,6 +72,7 @@ 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,6 +80,7 @@ public:
type = t; type = t;
hp = 400; hp = 400;
angle = _A; angle = _A;
cbf = 0;
id = _id; id = _id;
instanceID = iID; instanceID = iID;
}; };
@@ -87,7 +88,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;
virtual sNPCAppearanceData getAppearanceData(); sNPCAppearanceData getAppearanceData();
}; };
struct CombatNPC : public BaseNPC, public ICombatant { struct CombatNPC : public BaseNPC, public ICombatant {
@@ -104,13 +105,11 @@ 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;
std::unordered_map<int, Buff*> buffs = {}; CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) { : BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
this->spawnX = spawnX; spawnX = x;
this->spawnY = spawnY; spawnY = y;
this->spawnZ = spawnZ; spawnZ = z;
kind = EntityKind::COMBAT_NPC; kind = EntityKind::COMBAT_NPC;
@@ -118,15 +117,12 @@ 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, BuffClass buffClass) override; virtual void removeBuff(int buffId, int 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;

View File

@@ -3,7 +3,6 @@
#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;
@@ -15,10 +14,6 @@ struct Group {
}); });
return filtered; return filtered;
} }
EntityRef getLeader() {
assert(members.size() > 0);
return members[0];
}
Group(EntityRef leader); Group(EntityRef leader);
}; };

View File

@@ -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 = (int32_t)SkillType::NANOSTIMPAK; resp->eST = EST_NANOSTIMPAK;
resp->iSkillID = 144; resp->iSkillID = 144;
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot; int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;

View File

@@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) {
} }
// wake up sleeping monster // wake up sleeping monster
if (hasBuff(ECSB_MEZ)) { if (cbf & CSB_BIT_MEZ) {
removeBuff(ECSB_MEZ); cbf &= ~CSB_BIT_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 = getCompositeCondition(); pkt1.iConditionBitFlag = cbf;
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,12 +96,13 @@ 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->clearBuffs(false); mob->cbf = 0;
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->getCompositeCondition(); pkt1.iConditionBitFlag = mob->cbf;
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));
} }
@@ -227,7 +228,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 mobStyle) { static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int style) {
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);
@@ -246,7 +247,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 = mobStyle; resp->iStyle = style;
resp->iValue1 = plr->x; resp->iValue1 = plr->x;
resp->iValue2 = plr->y; resp->iValue2 = plr->y;
resp->iValue3 = plr->z; resp->iValue3 = plr->z;
@@ -280,38 +281,26 @@ 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 nanoStyle = Nanos::nanoStyle(plr->activeNano); int style2 = Nanos::nanoStyle(plr->activeNano);
if (nanoStyle == -1) { // no nano if (style2 == -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 (mobStyle == nanoStyle) { } else if (style == style2) {
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 (mobStyle - nanoStyle == 1 || nanoStyle - mobStyle == 2) { } else if (style - style2 == 1 || style2 - style == 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
SkillData skill = { // TODO ABILITIES
SkillType::DAMAGE, // skillType /*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
SkillEffectTarget::POINT, // effectTarget for (auto& pwr : Abilities::Powers)
1, // effectType if (pwr.skillType == EST_DAMAGE)
SkillTargetType::MOBS, // targetType pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
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;
@@ -338,6 +327,12 @@ 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
@@ -352,7 +347,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<ICombatant*> targets{}; std::vector<int> targetData = {0, 0, 0, 0, 0};
// 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++) {
@@ -362,22 +357,26 @@ 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->isAlive()) if (plr->HP <= 0)
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) {
targets.push_back(plr); targetData[0] += 1;
if (targets.size() > 3) // make sure not to have more than 4 targetData[targetData[0]] = plr->iID;
if (targetData[0] > 3) // make sure not to have more than 4
break; break;
} }
} }
} }
Abilities::useNPCSkill(mob->id, skillID, targets); // TODO ABILITIES
/*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;
@@ -395,11 +394,14 @@ 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"];
SkillData* skill = &Abilities::SkillTable[skillID]; // TODO ABILITIES
int debuffID = Abilities::getCSTBFromST(skill->skillType); //std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
if(plr->hasBuff(debuffID)) //for (auto& pwr : Abilities::Powers)
return; // prevent debuffing a player twice // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
Abilities::useNPCSkill(mob->getRef(), skillID, { plr }); // if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
// 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;
} }
@@ -440,6 +442,31 @@ 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();
@@ -526,27 +553,38 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
return; return;
} }
// tick buffs // drain
auto it = npc->buffs.begin(); if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
while(it != npc->buffs.end()) { && self->cbf & CSB_BIT_BOUNDINGBALL) {
Buff* buff = (*it).second; drainMobHP(self, self->maxHealth / 20); // lose 5% every second
buff->combatTick(currTime); self->lastDrainTime = currTime;
}
// if mob state changed, end the step // if drain killed the mob, return early
if(self->state != AIState::COMBAT) if (self->hp <= 0)
return; return;
buff->tick(currTime); // unbuffing
if(buff->isStale()) { std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
// garbage collect while (it != self->unbuffTimes.end()) {
it = npc->buffs.erase(it);
delete buff; if (currTime >= it->second) {
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->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) { if (self->cbf & (CSB_BIT_STUN|CSB_BIT_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;
} }
@@ -562,7 +600,6 @@ 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)
@@ -572,8 +609,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->hasBuff(ECSB_DN_MOVE_SPEED)) if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; self->speed /= 2;
int targetX = plr->x; int targetX = plr->x;
int targetY = plr->y; int targetY = plr->y;
@@ -582,9 +619,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, speed*2/5); distanceToTravel = std::min(distance-mobRange+1, self->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 < speed*2/5 && currTime >= self->nextAttack) if (distanceToTravel < self->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);
@@ -592,7 +629,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 = speed; pkt.iSpeed = self->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;
@@ -678,7 +715,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->hasBuff(ECSB_DN_MOVE_SPEED)) if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
self->speed /= 2; self->speed /= 2;
std::queue<Vec3> queue; std::queue<Vec3> queue;
@@ -751,10 +788,14 @@ 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)
Abilities::useNPCSkill(npc->getRef(), 110, { npc }); // TODO ABILITIES
/*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);
} }
@@ -771,9 +812,12 @@ 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"]; int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
if(skillID != 0) // cast passive // TODO ABILITIES
Abilities::useNPCSkill(npc->getRef(), skillID, { npc }); /*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
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) {
@@ -789,8 +833,9 @@ 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->clearBuffs(true); self->unbuffTimes.clear();
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

View File

@@ -21,6 +21,8 @@ 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;
@@ -50,8 +52,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 spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]), : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
sightRange(d["m_iSightRange"]) { sightRange(d["m_iSightRange"]) {
state = AIState::ROAMING; state = AIState::ROAMING;
@@ -62,13 +64,15 @@ struct Mob : public CombatNPC {
idleRange = (int)data["m_iIdleRange"]; idleRange = (int)data["m_iIdleRange"];
level = data["m_iNpcLevel"]; level = data["m_iNpcLevel"];
roamX = spawnX; roamX = x;
roamY = spawnY; roamY = y;
roamZ = spawnZ; roamZ = z;
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;

View File

@@ -122,15 +122,16 @@ 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 spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) { BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
uint64_t inst = baseInstance ? MAPNUM(instance) : instance; 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(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id); npc = new Mob(x, y, z, inst, type, NPCData[type], id);
// re-enable respawning, if desired // re-enable respawning, if desired
((Mob*)npc)->summoned = !respawn; ((Mob*)npc)->summoned = !respawn;
@@ -293,55 +294,57 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
return npc; return npc;
} }
// TODO: Move this to separate file in ai/ subdir when implementing more events // TODO: Move this to MobAI, possibly
#pragma region NPCEvents #pragma region NPCEvents
// summon right arm and stage 2 body // summon right arm and stage 2 body
static void lordFuseStageTwo(CombatNPC *npc) { static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
Mob *oldbody = (Mob*)npc; // adaptium, stun 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, oldbody->instanceID, 2467); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
newbody->angle = oldbody->angle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
oldbody->instanceID, oldbody->angle); plr->instanceID, oldbody->angle);
// right arm, Adaptium, Stun // right arm, Adaptium, Stun
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
arm->angle = oldbody->angle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
oldbody->instanceID, oldbody->angle); plr->instanceID, oldbody->angle);
} }
// summon left arm and stage 3 body // summon left arm and stage 3 body
static void lordFuseStageThree(CombatNPC *npc) { static void lordFuseStageThree(CNSocket *sock, BaseNPC *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, oldbody->instanceID, 2468); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
newbody->angle = oldbody->angle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
newbody->instanceID, oldbody->angle); plr->instanceID, oldbody->angle);
// Blastons, Heal // Blastons, Heal
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
arm->angle = oldbody->angle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
arm->instanceID, oldbody->angle); plr->instanceID, oldbody->angle);
} }
std::vector<NPCEvent> NPCManager::NPCEvents = { std::vector<NPCEvent> NPCManager::NPCEvents = {
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo), NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
NPCEvent(2467, AIState::DEAD, lordFuseStageThree), NPCEvent(2467, ON_KILLED, lordFuseStageThree),
}; };
#pragma endregion NPCEvents #pragma endregion NPCEvents
@@ -373,5 +376,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, MS_PER_COMBAT_TICK); REGISTER_SHARD_TIMER(step, 200);
} }

View File

@@ -14,15 +14,20 @@
#define RESURRECT_HEIGHT 400 #define RESURRECT_HEIGHT 400
typedef void (*NPCEventHandler)(CombatNPC*); enum Trigger {
ON_KILLED,
ON_COMBAT
};
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
struct NPCEvent { struct NPCEvent {
int32_t npcType; int32_t npcType;
AIState triggerState; int trigger;
NPCEventHandler handler; NPCEventHandler handler;
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr) NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
: npcType(t), triggerState(tr), handler(hndlr) {} : npcType(t), trigger(tr), handler(hndlr) {}
}; };
namespace NPCManager { namespace NPCManager {

View File

@@ -69,6 +69,52 @@ 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;
@@ -93,10 +139,8 @@ 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;
ICombatant* src = dynamic_cast<ICombatant*>(plr); std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
int32_t targets[] = { plr->iID }; if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
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
@@ -273,11 +317,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;
) )
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr); // TODO ABILITIES
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); std::vector<ICombatant*> targetData = Abilities::matchTargets(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);
} }

View File

@@ -26,4 +26,5 @@ namespace Nanos {
void summonNano(CNSocket* sock, int slot, bool silent = false); void summonNano(CNSocket* sock, int slot, bool silent = false);
int nanoStyle(int nanoID); int nanoStyle(int nanoID);
bool getNanoBoost(Player* plr); bool getNanoBoost(Player* plr);
std::vector<ICombatant*> applyNanoBuff(SkillData* skill, Player* plr);
} }

View File

@@ -80,8 +80,6 @@ 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; }
@@ -92,8 +90,7 @@ 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, BuffClass buffClass) override; virtual void removeBuff(int buffId, int 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;

View File

@@ -23,12 +23,17 @@ 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) {
players[key] = plr; Player *p = new Player();
plr->chunkPos = Chunking::INVALID_CHUNK;
plr->lastHeartbeat = 0;
std::cout << getPlayerName(plr) << " has joined!" << std::endl; // copy object into heap memory
*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;
} }
@@ -210,72 +215,66 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
return; return;
} }
Player *plr = new Player(); // for convenience
Database::getPlayer(plr, lm->playerId); Player& plr = lm->plr;
// 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);
} }
plr->group = nullptr; response.iID = plr.iID;
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
@@ -285,12 +284,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
@@ -299,11 +298,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);
@@ -314,14 +313,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);
// transfer ownership of Player object into the shard (still valid in this function though) // copy Player object into the shard
addPlayer(sock, plr); addPlayer(sock, plr);
// check if there is an expiring vehicle // check if there is an expiring vehicle
Items::checkItemExpire(sock, plr); Items::checkItemExpire(sock, getPlayer(sock));
// set player equip stats // set player equip stats
Items::setItemStats(plr); Items::setItemStats(getPlayer(sock));
Missions::failInstancedMissions(sock); Missions::failInstancedMissions(sock);
@@ -332,20 +331,12 @@ 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 // deallocate lm (and therefore the plr object)
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++) {
@@ -409,14 +400,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;

View File

@@ -33,7 +33,6 @@ 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

View File

@@ -33,22 +33,6 @@ 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.
*/ */
@@ -690,8 +674,6 @@ 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"];
@@ -741,8 +723,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())
return; if (gruntwork.is_null()) return;
try { try {
// skyway paths // skyway paths
@@ -794,8 +776,6 @@ 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);
@@ -815,9 +795,6 @@ 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"];
@@ -838,9 +815,6 @@ 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);
@@ -897,9 +871,10 @@ 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()) {
ensureValidNPCType(type, settings::NPCJSON); std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
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)
@@ -941,9 +916,10 @@ 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()) {
ensureValidNPCType(type, settings::MOBJSON); std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
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"];
@@ -971,10 +947,7 @@ 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"];
@@ -1004,9 +977,6 @@ 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);
@@ -1112,39 +1082,19 @@ 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
// scope for fstream if (!fstream.fail()) {
{ fstream >> *table.first; // load file contents into table
std::ifstream fstream; } else {
fstream.open(settings::TDATADIR + "/" + table.second); // open file if (table.first != &gruntwork) { // gruntwork isn't critical
// 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
@@ -1159,11 +1109,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
} }

View File

@@ -3,7 +3,6 @@
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp"
using namespace Trading; using namespace Trading;
@@ -273,8 +272,6 @@ 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) {

View File

@@ -6,9 +6,6 @@
#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;
@@ -61,8 +58,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 + expiry duration // set time limit: current time + 7days
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION; req->Item.iTimeLimit = getTimestamp() + 604800;
} }
if (slot != req->iInvenSlotNum) { if (slot != req->iInvenSlotNum) {
@@ -232,20 +229,12 @@ 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;

View File

@@ -41,8 +41,7 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) { uint64_t 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 dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
memcpy(&dEKey, defaultKey, sizeof(dEKey));
return dEKey * (uTime * num * num2); return dEKey * (uTime * num * num2);
} }
@@ -66,7 +65,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
// ========================================================[[ CNSocket ]]======================================================== // ========================================================[[ CNSocket ]]========================================================
CNSocket::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) {
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey)); EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
} }
bool CNSocket::sendData(uint8_t* data, int size) { bool CNSocket::sendData(uint8_t* data, int size) {
@@ -110,11 +109,7 @@ 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);
@@ -246,10 +241,9 @@ 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 && recved < sizeof(int32_t)) { if (recved == 0) {
// too little data for readSize or the socket was closed normally (when 0 bytes were read) // the socket was closed normally
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);
@@ -270,12 +264,11 @@ void CNSocket::step() {
} }
if (readSize > 0 && readBufferIndex < readSize) { if (readSize > 0 && readBufferIndex < readSize) {
// read until the end of the packet (or at least try to) // read until the end of the packet! (or at least try too)
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0); 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) {
@@ -418,9 +411,9 @@ void CNServer::addPollFD(SOCKET s) {
fds.push_back({s, POLLIN}); fds.push_back({s, POLLIN});
} }
void CNServer::removePollFD(int fd) { void CNServer::removePollFD(int i) {
auto it = fds.begin(); auto it = fds.begin();
while (it != fds.end() && it->fd != fd) while (it != fds.end() && it->fd != fds[i].fd)
it++; it++;
assert(it != fds.end()); assert(it != fds.end());
@@ -465,7 +458,7 @@ void CNServer::start() {
if (!setSockNonblocking(sock, newConnectionSocket)) if (!setSockNonblocking(sock, newConnectionSocket))
continue; continue;
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl; std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
addPollFD(newConnectionSocket); addPollFD(newConnectionSocket);
@@ -480,15 +473,10 @@ 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 << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl; std::cout << "[WARN] Event on non-existant socket?" << std::endl;
assert(0); continue; // just to be safe
/* not reached */
} }
CNSocket* cSock = connections[fds[i].fd]; CNSocket* cSock = connections[fds[i].fd];
@@ -497,29 +485,22 @@ 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++;
}
}
} }
} }

View File

@@ -230,7 +230,6 @@ 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;

View File

@@ -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 (currTime > lm->timestamp + CNSHARED_TIMEOUT) { if (lm->timestamp + CNSHARED_TIMEOUT > currTime) {
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

View File

@@ -11,14 +11,14 @@
#include "Player.hpp" #include "Player.hpp"
/* /*
* Connecions time out after 5 minutes, checked every 30 seconds. * Connecions time out after 15 minutes, checked every 30 seconds.
*/ */
#define CNSHARED_TIMEOUT 300000 #define CNSHARED_TIMEOUT 900000
#define CNSHARED_PERIOD 30000 #define CNSHARED_PERIOD 30000
struct LoginMetadata { struct LoginMetadata {
uint64_t FEKey; uint64_t FEKey;
int32_t playerId; Player plr;
time_t timestamp; time_t timestamp;
}; };

View File

@@ -46,8 +46,49 @@ enum class ePCRegenType {
End End
}; };
// nano power flags // nano powers
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,

View File

@@ -41,7 +41,6 @@ namespace Database {
uint64_t Timestamp; uint64_t Timestamp;
}; };
void init();
void open(); void open();
void close(); void close();
@@ -53,8 +52,7 @@ 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 slot); void updateSelected(int accountId, int playerId);
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);
@@ -80,9 +78,7 @@ 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);
@@ -102,7 +98,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, Player *sender); bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
// racing // racing
RaceRanking getTopRaceRanking(int epID, int playerID); RaceRanking getTopRaceRanking(int epID, int playerID);

View File

@@ -152,8 +152,7 @@ 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);
// set attachment flag dynamically data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@@ -266,7 +265,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, Player *sender) { bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
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);
@@ -331,13 +330,6 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
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;
} }

View File

@@ -236,20 +236,7 @@ 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) {

View File

@@ -3,15 +3,6 @@
#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;

View File

@@ -79,29 +79,6 @@ 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);

View File

@@ -285,13 +285,11 @@ void Database::getPlayer(Player* plr, int id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
/* void Database::updatePlayer(Player *player) {
* Low-level function to save a player to DB. std::lock_guard<std::mutex> lock(dbCrit);
* Must be run in a SQL transaction and with dbCrit locked.
* The caller manages the transacstion, so if this function returns false, sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
* 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
@@ -338,8 +336,10 @@ bool 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);
return false; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@@ -375,8 +375,10 @@ bool 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 false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@@ -393,8 +395,10 @@ bool 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 false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@@ -411,8 +415,10 @@ bool 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 false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@@ -445,8 +451,10 @@ bool 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 false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@@ -479,8 +487,10 @@ bool 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 false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@@ -514,47 +524,14 @@ bool 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 false; return;
} }
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);
} }

View File

@@ -98,9 +98,6 @@ 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) {
@@ -108,15 +105,15 @@ int main() {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#endif #endif
initsignals(); initsignals();
settings::init(); settings::init();
Database::init();
Rand::init(getTime());
TableData::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; std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
Rand::init(getTime());
TableData::init();
PlayerManager::init(); PlayerManager::init();
PlayerMovement::init(); PlayerMovement::init();
BuiltinCommands::init(); BuiltinCommands::init();
@@ -135,9 +132,8 @@ int main() {
Email::init(); Email::init();
Groups::init(); Groups::init();
Racing::init(); Racing::init();
Trading::init();
Database::open(); Database::open();
Trading::init();
switch (settings::EVENTMODE) { switch (settings::EVENTMODE) {
case 0: break; // no event case 0: break; // no event

View File

@@ -153,18 +153,15 @@ static sock_filter filter[] = {
ALLOW_SYSCALL(read), ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write), ALLOW_SYSCALL(write),
ALLOW_SYSCALL(close), ALLOW_SYSCALL(close),
#ifdef __NR_stat #if __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
#ifdef __NR_creat #if __NR_creat
ALLOW_SYSCALL(creat), // maybe; for DB journal ALLOW_SYSCALL(creat), // maybe; for DB journal
#endif #endif
#ifdef __NR_unlink #if __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
@@ -195,9 +192,6 @@ 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),
@@ -280,7 +274,7 @@ static sock_filter filter[] = {
#endif #endif
// AArch64 (ARM64) // AArch64 (ARM64)
#ifdef __NR_unlinkat #if __NR_unlinkat
ALLOW_SYSCALL(unlinkat), ALLOW_SYSCALL(unlinkat),
#endif #endif
#ifdef __NR_fstatat64 #ifdef __NR_fstatat64

View File

@@ -13,7 +13,6 @@
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();
@@ -211,12 +210,9 @@ 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(defaultKey, login->iClientVerC, 1)); sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), 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;
@@ -477,7 +473,11 @@ 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::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID); Database::updateSelected(loginSessions[sock].userID, lm->plr.slot);
} }
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) { void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {

View File

@@ -18,7 +18,6 @@ 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);
@@ -118,10 +117,6 @@ 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

View File

@@ -8,7 +8,6 @@
#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:

View File

@@ -21,7 +21,6 @@ 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
@@ -110,7 +109,6 @@ 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);

View File

@@ -12,7 +12,6 @@ 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

Submodule tdata updated: cc65dbb402...8230fb8649