Compare commits

...

89 Commits

Author SHA1 Message Date
gsemaj
ae9ee6094d
Move corruption block to new system 2023-08-16 16:43:57 -07:00
gsemaj
6d5cea0d9a
Return home heal to new system 2023-08-14 19:49:39 -07:00
gsemaj
fdd7d53aed
Passive mob buffs to new system 2023-08-14 19:44:23 -07:00
gsemaj
57f8f3e0e1
Move mob active skills to new system 2023-08-14 19:28:45 -07:00
gsemaj
a3008cab3c
Move eruption attacks to new system 2023-08-14 18:18:06 -07:00
gsemaj
02fd17c0e7
Implement leech 2023-08-13 18:27:18 -07:00
gsemaj
da33c5c30f
Make clearBuffs memory-safe
improperly erasing during iteration oops
2023-08-13 18:26:30 -07:00
gsemaj
478ae3d996
Break if player dies from buff combat tick 2023-08-13 14:26:05 -07:00
gsemaj
9affe76587
Fix segv: Halt buff iteration if mob is dead 2023-08-13 13:59:28 -07:00
gsemaj
33fde8af69
Tighten parameter for removeBuff 2023-08-13 10:50:50 -07:00
gsemaj
1a7bb826d8 Move drain to new system
TODO
- There is a seg fault that happens when drain kills the mob
- leech unimplemented
- mob skills unimplemented
2023-07-25 20:30:54 +00:00
gsemaj
da9fb499de reorder some stuff 2023-07-25 19:49:38 +00:00
gsemaj
2bc2235179 Fix icon not disappearing for debuffs 2023-07-25 19:11:28 +00:00
gsemaj
618126f963 Tick buffs for mobs 2023-07-25 19:00:35 +00:00
gsemaj
b16ffe4f19 Fix infinite slowdown with snare 2023-07-25 19:00:01 +00:00
gsemaj
6edc01c1f1
Implement buff handling for CombatNPC 2023-07-25 13:49:40 -04:00
gsemaj
c0e566050b
Move buffs from Mob to CombatNPC 2023-07-25 13:42:40 -04:00
gsemaj
7a59248ace
Implement buffs for mobs 2023-07-25 13:19:49 -04:00
gsemaj
6ee55d7406
Damage n debuff handler 2023-07-25 13:09:28 -04:00
gsemaj
41622ad8aa Fix trailing structs
The change to allow flexible trailing struct sizes broke
`attachSkillResults` oops
2023-07-25 17:05:44 +00:00
gsemaj
0f09808bc4
Get rid of cbf 2023-07-25 10:43:48 -04:00
gsemaj
b6171ebdc1
Add clearBuffs 2023-07-25 10:41:45 -04:00
gsemaj
5671adbd74
Make skill result size check an assertion 2023-07-23 10:46:53 -04:00
gsemaj
550b26db39
Oops 2023-07-23 10:42:07 -04:00
gsemaj
68be9cc381
Change SkillResult size validation
Since leech uses trailing structs of two different sizes, just use the max SkillResult size in validation/zeroing and then check for overflow in a couple extra places
2023-07-23 10:31:40 -04:00
gsemaj
ef33a182d1
Reorder abilities to match client handling 2023-07-23 10:15:00 -04:00
gsemaj
9bd8eabed7
Don't add buff if player dead 2023-07-23 10:13:22 -04:00
gsemaj
2249b5381b
Fix seg fault with egg powers 2023-07-22 15:10:00 -04:00
gsemaj
9b7656117d
Reuse useNanoSkill codepath for passive powers 2023-07-20 17:53:13 -04:00
gsemaj
9394825d41
Fix recall 2023-07-12 17:43:05 -04:00
gsemaj
6e0d55c4b7
Make SkillType an enum class 2023-07-12 17:37:05 -04:00
gsemaj
33396f8d28
Fix self recall 2023-07-11 20:21:37 -04:00
gsemaj
cd908666af
[WIP] Fix targeting for groups 2023-07-11 17:42:08 -04:00
gsemaj
74588f2c77
[WIP] Active power handling
TODO:
- recall (self and group) is broken
- revive (only group) is broken
- damage + debuff is unimplemented
2023-07-11 13:52:59 -04:00
gsemaj
829f75112c
Sync with master 2023-07-11 13:52:59 -04:00
gsemaj
343668fbcd
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.
2023-07-11 13:52:59 -04:00
gsemaj
2e572169c0
Move some stuff from playerTick to player combat step 2023-07-11 13:52:59 -04:00
gsemaj
b3e28ddea3
Refactor group handling 2023-07-11 13:52:58 -04:00
gsemaj
1670dfd830
Port egg buffs over to new system 2023-07-11 13:52:58 -04:00
gsemaj
e768ebcabe
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.
2023-07-11 13:52:58 -04:00
gsemaj
9bc7e8de62
Passive nano powers 2023-07-11 13:52:58 -04:00
gsemaj
f249599ab5
YET ANOTHER ITERATION of the new ability system
I am very tired
2023-07-11 13:52:57 -04:00
gsemaj
2901f5f285
Passive nano powers pt 1 2023-07-11 13:52:57 -04:00
gsemaj
36f329c302
Passive nano powers boilerplate 2023-07-11 13:52:57 -04:00
gsemaj
eb7daf8eaa
Fix timed out buffs not calling onExpire 2023-07-11 13:52:57 -04:00
gsemaj
0c5a9400ce
Buff framework tweaks + polish 2023-07-11 13:52:56 -04:00
gsemaj
f150595f70
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.
2023-07-11 13:52:56 -04:00
gsemaj
c6528eb2ac
oops 2023-07-11 13:52:56 -04:00
gsemaj
d631ca1aa1
CRLF purge in Buffs.cpp 2023-07-11 13:52:56 -04:00
gsemaj
a94fb0ed6d
egg prep 2023-07-11 13:52:55 -04:00
gsemaj
d48aa21135
Move Buff implementation to Buffs.cpp 2023-07-11 13:52:55 -04:00
gsemaj
c60c4dac38
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
- ???
2023-07-11 13:52:55 -04:00
gsemaj
215da0130d
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
2023-07-11 13:52:54 -04:00
gsemaj
0e8a4742eb
Get rid of player fire rate suspicion
This was super primitive & jank, and caused false positives.
Will replace with a polished system later on.
2023-07-11 13:52:54 -04:00
gsemaj
85bb4d163e
Handle case where cmake is invoked outside root 2023-07-11 13:52:54 -04:00
gsemaj
09b74a5711
Start moving passive power processing to playerTick 2023-07-11 13:52:53 -04:00
gsemaj
90819bea8e
Groundwork for new buff system 2023-07-11 13:52:53 -04:00
gsemaj
3cc5c09a91
Active power classification 2023-07-11 13:52:53 -04:00
gsemaj
536d5fbcfa
some struct reorg 2023-07-11 13:52:53 -04:00
gsemaj
4ec3a3acb7
Replace group filter operator with function 2023-07-11 13:52:52 -04:00
gsemaj
89ed0b99a3
Ignore .bak files
for my local backups lol
2023-07-11 13:52:52 -04:00
gsemaj
db73b85bc8
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.
2023-07-11 13:52:52 -04:00
gsemaj
6cab203401
(WIP) EXPERIMENTAL GROUP CHANGES 2023-07-11 13:52:52 -04:00
gsemaj
92846e0eac
(WIP) TODO ABILITIES 2023-07-11 13:52:51 -04:00
gsemaj
5963ea06be
Move mob aggro logic into takeDamage override
God that feels good
2023-07-11 13:52:51 -04:00
gsemaj
c28970f2e1
(WIP) Move away from rigid states/transitions to allow custom behavior 2023-07-11 13:52:51 -04:00
gsemaj
8be853c2dc
EntityType -> EntityKind 2023-07-11 13:52:51 -04:00
gsemaj
a811e73fed
(WIP) onRoamStart hook implementation 2023-07-11 13:52:50 -04:00
gsemaj
a58971c270
(WIP) Remove BaseNPC::barkerType to save space 2023-07-11 13:52:50 -04:00
gsemaj
07429a0e51
ope 2023-07-11 13:52:50 -04:00
gsemaj
7c9038cf10
(WIP) onCombatStart hook implementation 2023-07-11 13:52:50 -04:00
gsemaj
c1e391d86a
(WIP) onDeath hook implementation 2023-07-11 13:52:49 -04:00
gsemaj
e23af08838
(WIP) Add src param to transition + certain hooks
Should all hooks have src? I think not
2023-07-11 13:52:49 -04:00
gsemaj
8a26ae2f01
(WIP) Transitions + hook definitions + onRetreat hook implementation 2023-07-11 13:52:49 -04:00
gsemaj
d17694e12e
(WIP) Point 2: Generalization 2023-07-11 13:52:48 -04:00
gsemaj
4b612f35d2
(WIP) Point 1: step functions 2023-07-11 13:52:48 -04:00
gsemaj
d9e0a4a281
(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.
2023-07-11 13:52:48 -04:00
gsemaj
dd9891f668
(WIP) Move ICombatant functions around a bit 2023-07-11 13:52:48 -04:00
gsemaj
962141e54f
(WIP) Initial ICombatant draft 2023-07-11 13:52:47 -04:00
bea41132b4
[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.
2023-07-11 13:52:47 -04:00
f305d7252d
[refactor] Replace a few uses of magic numbers with enums 2023-07-11 13:52:47 -04:00
ff62129eec
[refactor] Cosmetic cleanup in Fuse fight functions 2023-07-11 13:52:47 -04:00
55f3ab8bad
[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.
2023-07-11 13:52:46 -04:00
gsemaj
a732cde117
[WIP] Stub power handler 2023-07-11 13:52:46 -04:00
gsemaj
703eaff2b4
[WIP] Use EntityRef instead of CNSocket in ability handler 2023-07-11 13:52:46 -04:00
gsemaj
f4f5f2e0bd
[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.
2023-07-11 13:52:45 -04:00
gsemaj
1858938280
[WIP] Rename Entity.type -> Entity.kind 2023-07-11 13:52:45 -04:00
gsemaj
0af8f7e91d
[WIP] Fix Nanos -> Abilities namespace calls 2023-07-11 13:52:45 -04:00
gsemaj
168a85e8ff
[WIP] Initial merge of ability namespaces & features 2023-07-11 13:52:45 -04:00
66 changed files with 2755 additions and 2419 deletions

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ build/
version.h version.h
infer-out infer-out
gmon.out gmon.out
*.bak

View File

@ -3,7 +3,8 @@ project(OpenFusion)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# OpenFusion supports multiple packet/struct versions # OpenFusion supports multiple packet/struct versions
# 104 is the default version to build which can be changed # 104 is the default version to build which can be changed

View File

@ -50,6 +50,7 @@ CXXSRC=\
src/db/email.cpp\ src/db/email.cpp\
src/sandbox/seccomp.cpp\ src/sandbox/seccomp.cpp\
src/sandbox/openbsd.cpp\ src/sandbox/openbsd.cpp\
src/Buffs.cpp\
src/Chat.cpp\ src/Chat.cpp\
src/CustomCommands.cpp\ src/CustomCommands.cpp\
src/Entities.cpp\ src/Entities.cpp\
@ -96,6 +97,7 @@ CXXHDR=\
vendor/JSON.hpp\ vendor/JSON.hpp\
vendor/INIReader.hpp\ vendor/INIReader.hpp\
vendor/JSON.hpp\ vendor/JSON.hpp\
src/Buffs.hpp\
src/Chat.hpp\ src/Chat.hpp\
src/CustomCommands.hpp\ src/CustomCommands.hpp\
src/Entities.hpp\ src/Entities.hpp\

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,111 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Combat.hpp"
typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); #include "Entities.hpp"
#include "Player.hpp"
struct NanoPower { #include <map>
int16_t skillType; #include <vector>
int32_t bitFlag; #include <assert.h>
int16_t timeBuffID;
PowerHandler handler;
NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
void handle(CNSocket *sock, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { enum class SkillType {
if (handler == nullptr) DAMAGE = 1,
return; HEAL_HP = 2,
KNOCKDOWN = 3, // dnd
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); SLEEP = 4, // dnd
} SNARE = 5, // dnd
HEAL_STAMINA = 6,
STAMINA_SELF = 7,
STUN = 8, // dnd
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,
}; };
typedef void (*MobPowerHandler)(Mob*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); enum class SkillEffectTarget {
POINT = 1,
SELF = 2,
CONE = 3,
WEAPON = 4,
AREA_SELF = 5,
AREA_TARGET = 6
};
struct MobPower { enum class SkillTargetType {
int16_t skillType; MOBS = 1,
int32_t bitFlag; PLAYERS = 2,
int16_t timeBuffID; GROUP = 3
MobPowerHandler handler; };
MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} enum class SkillDrainType {
ACTIVE = 1,
PASSIVE = 2
};
void handle(Mob *mob, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) { struct SkillResult {
if (handler == nullptr) size_t size;
return; uint8_t payload[MAX_SKILLRESULT_SIZE];
SkillResult(size_t len, void* dat) {
handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID); assert(len <= MAX_SKILLRESULT_SIZE);
size = len;
memcpy(payload, dat, len);
}
SkillResult() {
size = 0;
} }
}; };
struct SkillData { struct SkillData {
int skillType; SkillType skillType; // eST
int targetType; SkillEffectTarget effectTarget;
int drainType; int effectType; // always 1?
SkillTargetType targetType;
SkillDrainType drainType;
int effectArea; int effectArea;
int batteryUse[4]; int batteryUse[4];
int durationTime[4]; int durationTime[4];
int powerIntensity[4];
int valueTypes[3];
int values[3][4];
}; };
namespace Nanos { namespace Abilities {
extern std::vector<NanoPower> NanoPowers;
extern std::map<int32_t, SkillData> SkillTable; extern std::map<int32_t, SkillData> SkillTable;
void nanoUnbuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags); void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr); std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
} int getCSTBFromST(SkillType skillType);
namespace Combat {
extern std::vector<MobPower> MobPowers;
} }

View File

@ -1,15 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "Buddies.hpp" #include "Buddies.hpp"
#include "PlayerManager.hpp"
#include "Buddies.hpp"
#include "db/Database.hpp"
#include "Items.hpp"
#include "db/Database.hpp"
#include <iostream> #include "db/Database.hpp"
#include <chrono> #include "servers/CNShardServer.hpp"
#include <algorithm>
#include <thread> #include "Player.hpp"
#include "PlayerManager.hpp"
using namespace Buddies; using namespace Buddies;

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "Player.hpp"
#include "core/Core.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
namespace Buddies { namespace Buddies {

197
src/Buffs.cpp Normal file
View File

@ -0,0 +1,197 @@
#include "Buffs.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
using namespace Buffs;
void Buff::tick(time_t currTime) {
auto it = stacks.begin();
while(it != stacks.end()) {
BuffStack& stack = *it;
//if(onTick) onTick(self, this, currTime);
if(stack.durationTicks == 0) {
BuffStack deadStack = stack;
it = stacks.erase(it);
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
} else {
if(stack.durationTicks > 0) stack.durationTicks--;
it++;
}
}
}
void Buff::combatTick(time_t currTime) {
if(onCombatTick) onCombatTick(self, this, currTime);
}
void Buff::clear() {
while(!stacks.empty()) {
BuffStack stack = stacks.back();
stacks.pop_back();
if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack);
}
}
void Buff::clear(BuffClass buffClass) {
auto it = stacks.begin();
while(it != stacks.end()) {
BuffStack& stack = *it;
if(stack.buffStackClass == buffClass) {
BuffStack deadStack = stack;
it = stacks.erase(it);
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
} else it++;
}
}
void Buff::addStack(BuffStack* stack) {
stacks.push_back(*stack);
if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back());
}
bool Buff::hasClass(BuffClass buffClass) {
for(BuffStack& stack : stacks) {
if(stack.buffStackClass == buffClass)
return true;
}
return false;
}
BuffClass Buff::maxClass() {
BuffClass buffClass = BuffClass::NONE;
for(BuffStack& stack : stacks) {
if(stack.buffStackClass > buffClass)
buffClass = stack.buffStackClass;
}
return buffClass;
}
int Buff::getValue(BuffValueSelector selector) {
if(isStale()) return 0;
int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value;
for(BuffStack& stack : stacks) {
switch(selector)
{
case BuffValueSelector::NET_TOTAL:
value += stack.value;
break;
case BuffValueSelector::MIN_VALUE:
if(stack.value < value) value = stack.value;
break;
case BuffValueSelector::MAX_VALUE:
if(stack.value > value) value = stack.value;
break;
case BuffValueSelector::MIN_MAGNITUDE:
if(abs(stack.value) < abs(value)) value = stack.value;
break;
case BuffValueSelector::MAX_MAGNITUDE:
default:
if(abs(stack.value) > abs(value)) value = stack.value;
}
}
return value;
}
EntityRef Buff::getLastSource() {
if(stacks.empty())
return self;
return stacks.back().source;
}
bool Buff::isStale() {
return stacks.empty();
}
/* This will practically never do anything important, but it's here just in case */
void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) {
if(!onUpdate) onUpdate = fOnUpdate;
if(!onCombatTick) onCombatTick = fOnCombatTick;
}
#pragma region Handlers
void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) {
if(self.kind != EntityKind::PLAYER)
return; // not implemented
Player* plr = (Player*)self.getEntity();
if(plr == nullptr)
return; // sanity check
if(status == ETBU_DEL && !buff->isStale())
return; // no premature effect deletion
int cbf = plr->getCompositeCondition();
sTimeBuff payload{};
if(status == ETBU_ADD) {
payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE);
// we need to explicitly add the ECSB for this buff,
// in case this is the first stack in and the entry
// in the buff map doesn't yet exist
if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id);
}
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
pkt.eTBU = status; // eTimeBuffUpdate
pkt.eTBT = (int)stack->buffStackClass;
pkt.iConditionBitFlag = cbf;
pkt.TimeBuff = payload;
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
void Buffs::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) {
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
return; // not a combatant
Entity* entity = self.getEntity();
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
int32_t eCharType = combatant->getCharType();
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
pkt.iID = combatant->getID();
pkt.iConditionBitFlag = combatant->getCompositeCondition();
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
void Buffs::tickDrain(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);
int damage = combatant->takeDamage(buff->getLastSource(), combatant->getMaxHP() / 100);
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 = damage;
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

93
src/Buffs.hpp Normal file
View File

@ -0,0 +1,93 @@
#pragma once
#include "core/Core.hpp"
#include "EntityRef.hpp"
#include <vector>
#include <functional>
/* forward declaration(s) */
class Buff;
template<class... Types>
using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>;
#define CSB_FROM_ECSB(x) (1 << (x - 1))
enum class BuffClass {
NONE = ETBT_NONE,
NANO = ETBT_NANO,
GROUP_NANO = ETBT_GROUPNANO,
EGG = ETBT_SHINY,
ENVIRONMENT = ETBT_LANDEFFECT,
ITEM = ETBT_ITEM,
CASH_ITEM = ETBT_CASHITEM
};
enum class BuffValueSelector {
MAX_VALUE,
MIN_VALUE,
MAX_MAGNITUDE,
MIN_MAGNITUDE,
NET_TOTAL
};
struct BuffStack {
int durationTicks;
int value;
EntityRef source;
BuffClass buffStackClass;
};
class Buff {
private:
EntityRef self;
std::vector<BuffStack> stacks;
public:
int id;
/* called just after a stack is added or removed */
BuffCallback<int, BuffStack*> onUpdate;
/* called when the buff is combat-ticked */
BuffCallback<time_t> onCombatTick;
void tick(time_t);
void combatTick(time_t);
void clear();
void clear(BuffClass buffClass);
void addStack(BuffStack* stack);
/*
* Sometimes we need to determine if a buff
* is covered by a certain class, ex: nano
* vs. coco egg in the case of infection protection
*/
bool hasClass(BuffClass buffClass);
BuffClass maxClass();
int getValue(BuffValueSelector selector);
EntityRef getLastSource();
/*
* In general, a Buff object won't exist
* unless it has stacks. However, when
* popping stacks during iteration (onExpire),
* stacks will be empty for a brief moment
* when the last stack is popped.
*/
bool isStale();
void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick);
Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack)
: self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) {
addStack(firstStack);
}
};
namespace Buffs {
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
void timeBuffTick(EntityRef self, Buff* buff);
void timeBuffTimeout(EntityRef self);
void tickDrain(EntityRef self, Buff* buff);
}

View File

@ -1,10 +1,13 @@
#include "BuiltinCommands.hpp" #include "BuiltinCommands.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Chat.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Rand.hpp"
// helper function, not a packet handler // helper function, not a packet handler
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
@ -247,17 +250,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
uint64_t instance = plr->instanceID; uint64_t instance = plr->instanceID;
const int unstickRange = 400; const int unstickRange = 400;
switch (req->eTeleportType) { switch ((eCN_GM_TeleportType)req->eTeleportType) {
case eCN_GM_TeleportMapType__MyLocation: case eCN_GM_TeleportType::MyLocation:
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance); PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
break; break;
case eCN_GM_TeleportMapType__MapXYZ: case eCN_GM_TeleportType::MapXYZ:
instance = req->iToMap; instance = req->iToMap;
// fallthrough // fallthrough
case eCN_GM_TeleportMapType__XYZ: case eCN_GM_TeleportType::XYZ:
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance); PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
break; break;
case eCN_GM_TeleportMapType__SomeoneLocation: case eCN_GM_TeleportType::SomeoneLocation:
// player to teleport to // player to teleport to
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID, goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName)); AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
@ -269,7 +272,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID); PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
break; break;
case eCN_GM_TeleportMapType__Unstick: case eCN_GM_TeleportType::Unstick:
targetPlr = PlayerManager::getPlayer(targetSock); targetPlr = PlayerManager::getPlayer(targetSock);
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),

View File

@ -1,6 +1,9 @@
#include "Chat.hpp" #include "Chat.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Groups.hpp"
#include "CustomCommands.hpp" #include "CustomCommands.hpp"
#include <assert.h> #include <assert.h>
@ -225,10 +228,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
static void groupChatHandler(CNSocket* sock, CNPacketData* data) { static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf; sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
@ -251,16 +250,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
resp.iSendPCID = plr->iID; resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode; resp.iEmoteCode = chat->iEmoteCode;
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); if (plr->group == nullptr)
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
else
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
} }
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf; sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
@ -275,7 +273,9 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
resp.iSendPCID = plr->iID; resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode; resp.iEmoteCode = chat->iEmoteCode;
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); if (plr->group == nullptr)
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
} }
// we only allow plain ascii, at least for now // we only allow plain ascii, at least for now

View File

@ -2,7 +2,10 @@
#define CMD_PREFIX '/' #define CMD_PREFIX '/'
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include <string>
#include <vector>
namespace Chat { namespace Chat {
extern std::vector<std::string> dump; extern std::vector<std::string> dump;

View File

@ -1,9 +1,9 @@
#include "Chunking.hpp" #include "Chunking.hpp"
#include "PlayerManager.hpp"
#include "MobAI.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "settings.hpp"
#include "Combat.hpp" #include <assert.h>
#include "Eggs.hpp"
using namespace Chunking; using namespace Chunking;
@ -26,7 +26,7 @@ static void newChunk(ChunkPos pos) {
// add the chunk to the cache of all players and NPCs in the surrounding chunks // add the chunk to the cache of all players and NPCs in the surrounding chunks
std::set<Chunk*> surroundings = getViewableChunks(pos); std::set<Chunk*> surroundings = getViewableChunks(pos);
for (Chunk* c : surroundings) for (Chunk* c : surroundings)
for (const EntityRef& ref : c->entities) for (const EntityRef ref : c->entities)
ref.getEntity()->viewableChunks.insert(chunk); ref.getEntity()->viewableChunks.insert(chunk);
} }
@ -41,24 +41,24 @@ static void deleteChunk(ChunkPos pos) {
// remove the chunk from the cache of all players and NPCs in the surrounding chunks // remove the chunk from the cache of all players and NPCs in the surrounding chunks
std::set<Chunk*> surroundings = getViewableChunks(pos); std::set<Chunk*> surroundings = getViewableChunks(pos);
for(Chunk* c : surroundings) for(Chunk* c : surroundings)
for (const EntityRef& ref : c->entities) for (const EntityRef ref : c->entities)
ref.getEntity()->viewableChunks.erase(chunk); ref.getEntity()->viewableChunks.erase(chunk);
chunks.erase(pos); // remove from map chunks.erase(pos); // remove from map
delete chunk; // free from memory delete chunk; // free from memory
} }
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
if (!chunkExists(chunkPos)) if (!chunkExists(chunkPos))
return; // shouldn't happen return; // shouldn't happen
chunks[chunkPos]->entities.insert(ref); chunks[chunkPos]->entities.insert(ref);
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
chunks[chunkPos]->nplayers++; chunks[chunkPos]->nplayers++;
} }
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
if (!chunkExists(chunkPos)) if (!chunkExists(chunkPos))
return; // do nothing if chunk doesn't even exist return; // do nothing if chunk doesn't even exist
@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
chunk->entities.erase(ref); // gone chunk->entities.erase(ref); // gone
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
chunks[chunkPos]->nplayers--; chunks[chunkPos]->nplayers--;
assert(chunks[chunkPos]->nplayers >= 0); assert(chunks[chunkPos]->nplayers >= 0);
@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
deleteChunk(chunkPos); deleteChunk(chunkPos);
} }
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) { void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isAlive(); bool alive = ent->isExtant();
// TODO: maybe optimize this, potentially using AROUND packets? // TODO: maybe optimize this, potentially using AROUND packets?
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef& otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
if (ref == otherRef) if (ref == otherRef)
continue; continue;
@ -89,31 +89,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
Entity *other = otherRef.getEntity(); Entity *other = otherRef.getEntity();
// notify all visible players of the existence of this Entity // notify all visible players of the existence of this Entity
if (alive && otherRef.type == EntityType::PLAYER) { if (alive && otherRef.kind == EntityKind::PLAYER) {
ent->enterIntoViewOf(otherRef.sock); ent->enterIntoViewOf(otherRef.sock);
} }
// notify this *player* of the existence of all visible Entities // notify this *player* of the existence of all visible Entities
if (ref.type == EntityType::PLAYER && other->isAlive()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
other->enterIntoViewOf(ref.sock); other->enterIntoViewOf(ref.sock);
} }
// for mobs, increment playersInView // for mobs, increment playersInView
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
((Mob*)ent)->playersInView++; ((Mob*)ent)->playersInView++;
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
((Mob*)other)->playersInView++; ((Mob*)other)->playersInView++;
} }
} }
} }
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) { void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isAlive(); bool alive = ent->isExtant();
// TODO: same as above // TODO: same as above
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef& otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
if (ref == otherRef) if (ref == otherRef)
continue; continue;
@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r
Entity *other = otherRef.getEntity(); Entity *other = otherRef.getEntity();
// notify all visible players of the departure of this Entity // notify all visible players of the departure of this Entity
if (alive && otherRef.type == EntityType::PLAYER) { if (alive && otherRef.kind == EntityKind::PLAYER) {
ent->disappearFromViewOf(otherRef.sock); ent->disappearFromViewOf(otherRef.sock);
} }
// notify this *player* of the departure of all visible Entities // notify this *player* of the departure of all visible Entities
if (ref.type == EntityType::PLAYER && other->isAlive()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
other->disappearFromViewOf(ref.sock); other->disappearFromViewOf(ref.sock);
} }
// for mobs, decrement playersInView // for mobs, decrement playersInView
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
((Mob*)ent)->playersInView--; ((Mob*)ent)->playersInView--;
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
((Mob*)other)->playersInView--; ((Mob*)other)->playersInView--;
} }
} }
@ -154,8 +154,8 @@ static void emptyChunk(ChunkPos chunkPos) {
// unspawn all of the mobs/npcs // unspawn all of the mobs/npcs
std::set refs(chunk->entities); std::set refs(chunk->entities);
for (const EntityRef& ref : refs) { for (const EntityRef ref : refs) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
assert(0); assert(0);
// every call of this will check if the chunk is empty and delete it if so // every call of this will check if the chunk is empty and delete it if so
@ -163,7 +163,7 @@ static void emptyChunk(ChunkPos chunkPos) {
} }
} }
void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) { void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) {
Entity* ent = ref.getEntity(); Entity* ent = ref.getEntity();
// move to other chunk's player set // move to other chunk's player set
@ -267,54 +267,53 @@ void Chunking::createInstance(uint64_t instanceID) {
std::cout << "Creating instance " << instanceID << std::endl; std::cout << "Creating instance " << instanceID << std::endl;
for (ChunkPos &coords : templateChunks) { for (ChunkPos &coords : templateChunks) {
for (const EntityRef& ref : chunks[coords]->entities) { for (const EntityRef ref : chunks[coords]->entities) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
continue; continue;
int npcID = ref.id; int npcID = ref.id;
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity(); BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
// make a copy of each NPC in the template chunks and put them in the new instance // make a copy of each NPC in the template chunks and put them in the new instance
if (baseNPC->type == EntityType::MOB) { if (baseNPC->kind == EntityKind::MOB) {
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
continue; // follower; don't copy individually continue; // follower; don't copy individually
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--); instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob; NPCManager::NPCs[newMob->id] = newMob;
// if in a group, copy over group members as well // if in a group, copy over group members as well
if (((Mob*)baseNPC)->groupLeader != 0) { if (((Mob*)baseNPC)->groupLeader != 0) {
newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader newMob->groupLeader = newMob->id; // set leader ID for new leader
Mob* mobData = (Mob*)baseNPC; Mob* mobData = (Mob*)baseNPC;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mobData->groupMember[i] != 0) { if (mobData->groupMember[i] != 0) {
int followerID = NPCManager::nextId--; // id for follower int followerID = NPCManager::nextId--; // id for follower
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
// new follower instance // new follower instance
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle, Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID); instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
// add follower to NPC maps // add follower to NPC maps
NPCManager::NPCs[followerID] = newMobFollower; NPCManager::NPCs[followerID] = newMobFollower;
// set follower-specific properties // set follower-specific properties
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID; newMobFollower->groupLeader = newMob->id;
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX; newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY; newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
// add follower copy to leader copy // add follower copy to leader copy
newMob->groupMember[i] = followerID; newMob->groupMember[i] = followerID;
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z, NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
instanceID, baseFollower->appearanceData.iAngle); instanceID, baseFollower->angle);
} }
} }
} }
NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
instanceID, baseNPC->appearanceData.iAngle); instanceID, baseNPC->angle);
} else { } else {
BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--); NPCManager::NPCs[newNPC->id] = newNPC;
NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC; NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, instanceID, baseNPC->angle);
instanceID, baseNPC->appearanceData.iAngle);
} }
} }
} }

View File

@ -1,14 +1,10 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "EntityRef.hpp"
#include <utility>
#include <set> #include <set>
#include <map> #include <map>
#include <tuple> #include <vector>
#include <algorithm>
struct EntityRef;
class Chunk { class Chunk {
public: public:
@ -36,13 +32,13 @@ namespace Chunking {
extern const ChunkPos INVALID_CHUNK; extern const ChunkPos INVALID_CHUNK;
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to); void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to);
void trackEntity(ChunkPos chunkPos, const EntityRef& ref); void trackEntity(ChunkPos chunkPos, const EntityRef ref);
void untrackEntity(ChunkPos chunkPos, const EntityRef& ref); void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref); void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref);
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref); void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
bool chunkExists(ChunkPos chunk); bool chunkExists(ChunkPos chunk);
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID); ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);

View File

@ -1,22 +1,328 @@
#include "Combat.hpp" #include "Combat.hpp"
#include "PlayerManager.hpp"
#include "Nanos.hpp" #include "servers/CNShardServer.hpp"
#include "NPCManager.hpp"
#include "Items.hpp"
#include "Missions.hpp"
#include "Groups.hpp"
#include "Transport.hpp"
#include "Racing.hpp"
#include "Abilities.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp"
#include "Buffs.hpp"
#include <assert.h> #include <assert.h>
#include <iostream>
#include <functional>
using namespace Combat; using namespace Combat;
/// Player Id -> Bullet Id -> Bullet /// Player Id -> Bullet Id -> Bullet
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets; std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
#pragma region Player
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
if(!isAlive())
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;
}
Buff* Player::getBuff(int buffId) {
if(hasBuff(buffId)) {
return buffs[buffId];
}
return nullptr;
}
void Player::removeBuff(int buffId) {
if(hasBuff(buffId)) {
buffs[buffId]->clear();
delete buffs[buffId];
buffs.erase(buffId);
}
}
void Player::removeBuff(int buffId, BuffClass buffClass) {
if(hasBuff(buffId)) {
buffs[buffId]->clear(buffClass);
// buff might not be stale since another buff class might remain
if(buffs[buffId]->isStale()) {
delete buffs[buffId];
buffs.erase(buffId);
}
}
}
void 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) {
auto buff = buffs.find(buffId);
return buff != buffs.end() && !buff->second->isStale();
}
int Player::getCompositeCondition() {
int conditionBitFlag = 0;
for(auto buff : buffs) {
if(!buff.second->isStale() && buff.second->id > 0)
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
}
return conditionBitFlag;
}
int Player::takeDamage(EntityRef src, int amt) {
int dmg = amt;
if(HP - dmg < 0) dmg = HP;
HP -= dmg;
return dmg;
}
int Player::heal(EntityRef src, int amt) {
int heal = amt;
if(HP + heal > getMaxHP()) heal = getMaxHP() - HP;
HP += heal;
return heal;
}
bool Player::isAlive() {
return HP > 0;
}
int Player::getCurrentHP() {
return HP;
}
int Player::getMaxHP() {
return PC_MAXHEALTH(level);
}
int Player::getLevel() {
return level;
}
std::vector<EntityRef> Player::getGroupMembers() {
std::vector<EntityRef> members;
if(group != nullptr)
members = group->members;
else
members.push_back(PlayerManager::getSockFromID(iID));
return members;
}
int32_t Player::getCharType() {
return 1; // eCharType (eCT_PC)
}
int32_t Player::getID() {
return iID;
}
EntityRef Player::getRef() {
return EntityRef(PlayerManager::getSockFromID(iID));
}
void Player::step(time_t currTime) {
CNSocket* sock = getRef().sock;
// nanos
for (int i = 0; i < 3; i++) {
if (activeNano != 0 && equippedNanos[i] == activeNano) { // tick active nano
sNano& nano = Nanos[activeNano];
int drainRate = 0;
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
// nano has skill data
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
int boost = Nanos::getNanoBoost(this);
if (skill->drainType == SkillDrainType::PASSIVE)
drainRate = skill->batteryUse[boost * 3];
}
nano.iStamina -= 1 + drainRate / 5;
if (nano.iStamina <= 0)
Nanos::summonNano(sock, -1, true); // unsummon nano silently
} else if (Nanos[equippedNanos[i]].iStamina < 150) { // tick resting nano
sNano& nano = Nanos[equippedNanos[i]];
if (nano.iStamina < 150)
nano.iStamina += 1;
}
}
// buffs
for(auto buffEntry : buffs) {
buffEntry.second->combatTick(currTime);
if(!isAlive())
break; // unsafe to keep ticking if we're dead
}
}
#pragma endregion
#pragma region CombatNPC
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
if(!isAlive())
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;
}
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
if(hasBuff(buffId)) {
return buffs[buffId];
}
return nullptr;
}
void CombatNPC::removeBuff(int buffId) {
if(hasBuff(buffId)) {
buffs[buffId]->clear();
delete buffs[buffId];
buffs.erase(buffId);
}
}
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
if(hasBuff(buffId)) {
buffs[buffId]->clear(buffClass);
// buff might not be stale since another buff class might remain
if(buffs[buffId]->isStale()) {
delete buffs[buffId];
buffs.erase(buffId);
}
}
}
void CombatNPC::clearBuffs(bool force) {
auto it = buffs.begin();
while(it != buffs.end()) {
Buff* buff = (*it).second;
if(!force) buff->clear();
delete buff;
it = buffs.erase(it);
}
}
bool CombatNPC::hasBuff(int buffId) {
auto buff = buffs.find(buffId);
return buff != buffs.end() && !buff->second->isStale();
}
int CombatNPC::getCompositeCondition() {
int conditionBitFlag = 0;
for(auto buff : buffs) {
if(!buff.second->isStale() && buff.second->id > 0)
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
}
return conditionBitFlag;
}
int CombatNPC::takeDamage(EntityRef src, int amt) {
int dmg = amt;
if(hp - dmg < 0) dmg = hp;
hp -= dmg;
if(hp <= 0) transition(AIState::DEAD, src);
return dmg;
}
int CombatNPC::heal(EntityRef src, int amt) {
int heal = amt;
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
hp += heal;
return heal;
}
bool CombatNPC::isAlive() {
return hp > 0;
}
int CombatNPC::getCurrentHP() {
return hp;
}
int CombatNPC::getMaxHP() {
return maxHealth;
}
int CombatNPC::getLevel() {
return level;
}
std::vector<EntityRef> CombatNPC::getGroupMembers() {
std::vector<EntityRef> members;
if(group != nullptr)
members = group->members;
else
members.push_back(id);
return members;
}
int32_t CombatNPC::getCharType() {
if(kind == EntityKind::MOB)
return 4; // eCharType (eCT_MOB)
return 2; // eCharType (eCT_NPC)
}
int32_t CombatNPC::getID() {
return id;
}
EntityRef CombatNPC::getRef() {
return EntityRef(id);
}
void CombatNPC::step(time_t currTime) {
if(stateHandlers.find(state) != stateHandlers.end())
stateHandlers[state](this, currTime);
else {
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
transition(AIState::INACTIVE, id);
}
}
void CombatNPC::transition(AIState newState, EntityRef src) {
state = newState;
if (transitionHandlers.find(newState) != transitionHandlers.end())
transitionHandlers[newState](this, src);
else {
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
transition(AIState::INACTIVE, id);
}
/* TODO: fire any triggered events
for (NPCEvent& event : NPCManager::NPCEvents)
if (event.trigger == ON_KILLED && event.npcType == type)
event.handler(src, this);
*/
}
#pragma endregion
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit, static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
bool batteryBoost, int attackerStyle, bool batteryBoost, int attackerStyle,
int defenderStyle, int difficulty) { int defenderStyle, int difficulty) {
@ -114,7 +420,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
BaseNPC* npc = NPCManager::NPCs[targets[i]]; BaseNPC* npc = NPCManager::NPCs[targets[i]];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl; std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
return; return;
} }
@ -137,11 +443,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
else else
plr->batteryW = 0; plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first); damage.first = mob->takeDamage(sock, damage.first);
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->id;
respdata[i].iDamage = damage.first; respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->hp;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
@ -168,7 +474,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
plr->HP -= damage.first; plr->HP -= damage.first;
pkt->iNPC_ID = mob->appearanceData.iNPC_ID; pkt->iNPC_ID = mob->id;
pkt->iPCCnt = 1; pkt->iPCCnt = 1;
atk->iID = plr->iID; atk->iID = plr->iID;
@ -180,52 +486,11 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; if (!MobAI::aggroCheck(mob, getTime()))
mob->state = MobState::RETREAT; mob->transition(AIState::RETREAT, mob->target);
if (!MobAI::aggroCheck(mob, currTime)) {
MobAI::clearDebuff(mob);
if (mob->groupLeader != 0)
MobAI::groupRetreat(mob);
}
} }
} }
int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
// cannot kill mobs multiple times; cannot harm retreating mobs
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) {
return 0; // no damage
}
if (mob->skillStyle >= 0)
return 0; // don't hurt a mob casting corruption
if (mob->state == MobState::ROAMING) {
assert(mob->target == nullptr);
MobAI::enterCombat(sock, mob);
if (mob->groupLeader != 0)
MobAI::followToCombat(mob);
}
mob->appearanceData.iHP -= damage;
// wake up sleeping monster
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
if (mob->appearanceData.iHP <= 0)
killMob(mob->target, mob);
return damage;
}
/* /*
* When a group of players is doing missions together, we want them to all get * When a group of players is doing missions together, we want them to all get
* quest items at the same time, but we don't want the odds of quest item * quest items at the same time, but we don't want the odds of quest item
@ -233,96 +498,16 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
* single RNG roll per mission task, and every group member shares that same * single RNG roll per mission task, and every group member shares that same
* set of rolls. * set of rolls.
*/ */
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) { void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) {
for (int i = 0; i < leader->groupCnt; i++) { for (int i = 0; i < players.size(); i++) {
if (leader->groupIDs[i] == 0)
continue;
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
if (otherSock == nullptr)
continue;
Player *member = PlayerManager::getPlayer(otherSock);
Player* member = players[i];
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++) for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
if (member->tasks[j] != 0) if (member->tasks[j] != 0)
rolls[member->tasks[j]] = Rand::rand(); rolls[member->tasks[j]] = Rand::rand();
} }
} }
void Combat::killMob(CNSocket *sock, Mob *mob) {
mob->state = MobState::DEAD;
mob->target = nullptr;
mob->appearanceData.iConditionBitFlag = 0;
mob->skillStyle = -1;
mob->unbuffTimes.clear();
mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
// check for the edge case where hitting the mob did not aggro it
if (sock != nullptr) {
Player* plr = PlayerManager::getPlayer(sock);
Items::DropRoll rolled;
Items::DropRoll eventRolled;
std::map<int, int> qitemRolls;
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
assert(leader != nullptr); // should never happen
genQItemRolls(leader, qitemRolls);
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
Items::giveMobDrop(sock, mob, rolled, eventRolled);
Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
} else {
for (int i = 0; i < leader->groupCnt; i++) {
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
if (sockTo == nullptr)
continue;
Player *otherPlr = PlayerManager::getPlayer(sockTo);
// only contribute to group members' kills if they're close enough
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
if (dist > 5000)
continue;
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls);
}
}
}
// delay the despawn animation
mob->despawned = false;
// fire any triggered events
for (NPCEvent& event : NPCManager::NPCEvents)
if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType)
event.handler(sock, mob);
auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID);
if (it == Transport::NPCQueues.end() || it->second.empty())
return;
// rewind or empty the movement queue
if (mob->staticPath) {
/*
* This is inelegant, but we wind forward in the path until we find the point that
* corresponds with the Mob's spawn point.
*
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
*/
auto& queue = it->second;
for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) {
queue.pop();
queue.push(point);
}
} else {
Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID);
}
}
static void combatBegin(CNSocket *sock, CNPacketData *data) { static void combatBegin(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
@ -345,40 +530,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
plr->healCooldown = 4000; plr->healCooldown = 4000;
} }
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { static void dealGooDamage(CNSocket *sock) {
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
return; // ignore completely
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID
pkt1.eTBU = 1; // eTimeBuffUpdate
pkt1.eTBT = 0; // eTimeBuffType 1 means nano
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
static void dealGooDamage(CNSocket *sock, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
Player *plr = PlayerManager::getPlayer(sock);
memset(respbuf, 0, resplen); memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) { int amount = PC_MAXHEALTH(plr->level) * 3 / 20;
Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
if (protectionBuff != nullptr) {
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
dmg->bProtected = 1; dmg->bProtected = 1;
// eggs allow protection without nanos // eggs allow protection without nanos
if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION)) if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1)
plr->Nanos[plr->activeNano].iStamina -= 3; plr->Nanos[plr->activeNano].iStamina -= 3;
} else { } else {
plr->HP -= amount; plr->HP -= amount;
@ -402,12 +574,39 @@ static void dealGooDamage(CNSocket *sock, int amount) {
dmg->iID = plr->iID; dmg->iID = plr->iID;
dmg->iDamage = amount; dmg->iDamage = amount;
dmg->iHP = plr->HP; dmg->iHP = plr->HP;
dmg->iConditionBitFlag = plr->iConditionBitFlag; dmg->iConditionBitFlag = plr->getCompositeCondition();
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
} }
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
// infection debuff toggles as the client asks it to,
// so we add and remove a permanent debuff
if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) {
BuffStack infection = {
-1, // infinite
0, // no value
sock, // self-inflicted
BuffClass::ENVIRONMENT
};
plr->addBuff(ECSB_INFECTION,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack);
},
[](EntityRef self, Buff* buff, time_t currTime) {
if(self.kind == EntityKind::PLAYER)
dealGooDamage(self.sock);
},
&infection);
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
plr->removeBuff(ECSB_INFECTION);
}
}
static void pcAttackChars(CNSocket *sock, CNPacketData *data) { static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf; sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
@ -417,12 +616,12 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
return; return;
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes). // Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) { if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n"; std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
return; return;
} }
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) { if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n"; std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
@ -441,11 +640,19 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
resp->iTargetCnt = pkt->iTargetCnt; resp->iTargetCnt = pkt->iTargetCnt;
for (int i = 0; i < pkt->iTargetCnt; i++) { for (int i = 0; i < pkt->iTargetCnt; i++) {
if (pktdata[i*2+1] == 1) { // eCT == 1; attack player
Player *target = nullptr; ICombatant* target = nullptr;
std::pair<int, int> damage;
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
for (auto& pair : PlayerManager::players) { for (auto& pair : PlayerManager::players) {
if (pair.second->iID == pktdata[i*2]) { if (pair.second->iID == pktdata[i].iID) {
target = pair.second; target = pair.second;
break; break;
} }
@ -457,67 +664,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
return; return;
} }
std::pair<int,int> damage; damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
target->HP -= damage.first;
respdata[i].eCT = pktdata[i*2+1];
respdata[i].iID = target->iID;
respdata[i].iDamage = damage.first;
respdata[i].iHP = target->HP;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} else { // eCT == 4; attack mob } else { // eCT == 4; attack mob
if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) {
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
// not sure how to best handle this // not sure how to best handle this
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl; std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
return; return;
} }
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]]; BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
return; return;
} }
Mob* mob = (Mob*)npc; Mob* mob = (Mob*)npc;
target = mob;
std::pair<int,int> damage;
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
int difficulty = (int)mob->data["m_iNpcLevel"]; int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
if (plr->batteryW >= 6 + difficulty)
plr->batteryW -= 6 + difficulty;
else
plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first);
respdata[i].eCT = pktdata[i*2+1];
respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
damage.first = target->takeDamage(sock, damage.first);
respdata[i].eCT = pktdata[i].eCT;
respdata[i].iID = target->getID();
respdata[i].iDamage = damage.first;
respdata[i].iHP = target->getCurrentHP();
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen); sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
@ -653,21 +834,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
return; return;
} }
// rapid fire anti-cheat
time_t currTime = getTime();
if (currTime - plr->lastShot < plr->fireRate * 80)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
plr->lastShot = currTime;
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
sock->kill();
CNShardServer::_killConnection(sock);
return;
}
/* /*
* initialize response struct * initialize response struct
* rocket style hit doesn't work properly, so we're always sending this one * rocket style hit doesn't work properly, so we're always sending this one
@ -696,7 +862,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
} }
BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl; std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
return; return;
} }
@ -709,11 +875,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
int difficulty = (int)mob->data["m_iNpcLevel"]; int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
damage.first = hitMob(sock, mob, damage.first); damage.first = mob->takeDamage(sock, damage.first);
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->id;
respdata[i].iDamage = damage.first; respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->hp;
respdata[i].iHitFlag = damage.second; respdata[i].iHitFlag = damage.second;
} }
@ -728,6 +894,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
static void playerTick(CNServer *serv, time_t currTime) { static void playerTick(CNServer *serv, time_t currTime) {
static time_t lastHealTime = 0; static time_t lastHealTime = 0;
static time_t lastCombatTIme = 0;
for (auto& pair : PlayerManager::players) { for (auto& pair : PlayerManager::players) {
CNSocket *sock = pair.first; CNSocket *sock = pair.first;
@ -735,18 +902,13 @@ static void playerTick(CNServer *serv, time_t currTime) {
bool transmit = false; bool transmit = false;
// group ticks // group ticks
if (plr->groupCnt > 1) if (plr->group != nullptr)
Groups::groupTickInfo(plr); Groups::groupTickInfo(sock);
// do not tick dead players // do not tick dead players
if (plr->HP <= 0) if (plr->HP <= 0)
continue; continue;
// fm patch/lake damage
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION)
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
// heal // heal
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) { if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
if (currTime - lastHealTime - plr->healCooldown >= 4000) { if (currTime - lastHealTime - plr->healCooldown >= 4000) {
@ -758,22 +920,24 @@ static void playerTick(CNServer *serv, time_t currTime) {
plr->healCooldown -= 4000; plr->healCooldown -= 4000;
} }
for (int i = 0; i < 3; i++) { // combat tick
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina if(currTime - lastCombatTIme >= 2000) {
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5; plr->step(currTime);
transmit = true;
}
if (plr->Nanos[plr->activeNano].iStamina <= 0) // nanos
Nanos::summonNano(sock, -1, true); // unsummon nano silently if (plr->activeNano != 0) { // tick active nano
sNano* nano = plr->getActiveNano();
transmit = true; if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina // nano has skill data
sNano& nano = plr->Nanos[plr->equippedNanos[i]]; SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
nano.iStamina += 1; if (skill->drainType == SkillDrainType::PASSIVE) {
ICombatant* src = dynamic_cast<ICombatant*>(plr);
if (nano.iStamina > 150) int32_t targets[] = { plr->iID };
nano.iStamina = 150; std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
transmit = true; }
} }
} }
@ -789,6 +953,20 @@ static void playerTick(CNServer *serv, time_t currTime) {
PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD)); PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
} }
// process buffsets
auto it = plr->buffs.begin();
while(it != plr->buffs.end()) {
Buff* buff = (*it).second;
//buff->combatTick() gets called in Player::step
buff->tick(currTime);
if(buff->isStale()) {
// garbage collect
it = plr->buffs.erase(it);
delete buff;
}
else it++;
}
if (transmit) { if (transmit) {
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt); INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
@ -803,13 +981,15 @@ static void playerTick(CNServer *serv, time_t currTime) {
} }
} }
// if this was a heal tick, update the counter outside of the loop // if this was a heal/combat tick, update the counters outside of the loop
if (currTime - lastHealTime >= 4000) if (currTime - lastHealTime >= 4000)
lastHealTime = currTime; lastHealTime = currTime;
if(currTime - lastCombatTIme >= 2000)
lastCombatTIme = currTime;
} }
void Combat::init() { void Combat::init() {
REGISTER_SHARD_TIMER(playerTick, 2000); REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);

View File

@ -1,15 +1,10 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "Player.hpp"
#include "servers/CNShardServer.hpp"
#include "NPC.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "JSON.hpp"
#include <map> #include <map>
#include <unordered_map> #include <vector>
#include <queue>
struct Bullet { struct Bullet {
int pointDamage; int pointDamage;
@ -24,6 +19,5 @@ namespace Combat {
void init(); void init();
void npcAttackPc(Mob *mob, time_t currTime); void npcAttackPc(Mob *mob, time_t currTime);
int hitMob(CNSocket *sock, Mob *mob, int damage); void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
void killMob(CNSocket *sock, Mob *mob);
} }

View File

@ -1,18 +1,19 @@
#include "CustomCommands.hpp" #include "CustomCommands.hpp"
#include "Chat.hpp"
#include "db/Database.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Eggs.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "Items.hpp"
#include "db/Database.hpp"
#include "Transport.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Eggs.hpp"
#include "Items.hpp"
#include "Abilities.hpp"
#include <sstream> #include <sstream>
#include <iterator>
#include <math.h>
#include <limits.h> #include <limits.h>
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock); typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
@ -243,20 +244,20 @@ static void summonWCommand(std::string full, std::vector<std::string>& args, CNS
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true); BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
// update angle // update angle
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle);
// if we're in a lair, we need to spawn the NPC in both the private instance and the template // if we're in a lair, we need to spawn the NPC in both the private instance and the template
if (PLAYERID(plr->instanceID) != 0) { if (PLAYERID(plr->instanceID) != 0) {
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true); npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle);
} }
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) + Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template TableData::RunningMobs[npc->id] = npc; // only record the one in the template
} }
static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -269,24 +270,24 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
return; return;
} }
if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) { if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) {
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) + Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID); TableData::RunningEggs.erase(npc->id);
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); NPCManager::destroyNPC(npc->id);
return; return;
} }
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end() if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()
&& TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) { && TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) {
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob."); Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
return; return;
} }
if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) { if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) {
int leadId = ((Mob*)npc)->groupLeader; int leadId = ((Mob*)npc)->groupLeader;
if (leadId != 0) { if (leadId != 0) {
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) {
std::cout << "[WARN] unsummonW: leader not found!" << std::endl; std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
} }
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId]; Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
@ -294,7 +295,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
if (leadNpc->groupMember[i] == 0) if (leadNpc->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl; std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
continue; continue;
} }
@ -308,12 +309,12 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
} }
} }
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) + Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID); TableData::RunningMobs.erase(npc->id);
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); NPCManager::destroyNPC(npc->id);
} }
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -324,11 +325,11 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
// return all mobs to their spawn points // return all mobs to their spawn points
for (auto& pair : NPCManager::NPCs) { for (auto& pair : NPCManager::NPCs) {
if (pair.second->type != EntityType::MOB) if (pair.second->kind != EntityKind::MOB)
continue; continue;
Mob* mob = (Mob*)pair.second; Mob* mob = (Mob*)pair.second;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
mob->target = nullptr; mob->target = nullptr;
mob->nextMovement = getTime(); mob->nextMovement = getTime();
@ -356,24 +357,24 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
} }
int angle = (plr->angle + 180) % 360; int angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
// if it's a gruntwork NPC, rotate in-place // if it's a gruntwork NPC, rotate in-place
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) { if (TableData::RunningMobs.find(npc->id) != TableData::RunningMobs.end()) {
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC " Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
+ std::to_string(npc->appearanceData.iNPC_ID)); + std::to_string(npc->id));
} else { } else {
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle; TableData::RunningNPCRotations[npc->id] = angle;
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC " Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
+ std::to_string(npc->appearanceData.iNPC_ID)); + std::to_string(npc->id));
} }
// update rotation clientside // update rotation clientside
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = npc->appearanceData; pkt.NPCAppearanceData = npc->getAppearanceData();
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
} }
@ -443,9 +444,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args,
return; return;
} }
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance)); Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance));
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance; TableData::RunningNPCMapNumbers[npc->id] = instance;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle);
} }
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -501,9 +502,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
if (*tmp) if (*tmp)
return; return;
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0) if (Abilities::SkillTable.count(skillId) == 0) {
Chat::sendServerMessage(sock, "/buff: unknown skill Id"); Chat::sendServerMessage(sock, "/buff: unknown skill Id");
return;
}
Eggs::eggBuffPlayer(sock, skillId, 0, duration);
} }
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -532,7 +536,7 @@ static void eggCommand(std::string full, std::vector<std::string>& args, CNSocke
int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI); int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI); int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI);
Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
NPCManager::NPCs[id] = egg; NPCManager::NPCs[id] = egg;
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle); NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
@ -609,40 +613,40 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
} }
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
if (team == 2 && i > 0 && npc->type == EntityType::MOB) { if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; leadNpc->groupMember[i-1] = npc->id;
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
mob->groupLeader = leadNpc->appearanceData.iNPC_ID; mob->groupLeader = leadNpc->id;
mob->offsetX = x - plr->x; mob->offsetX = x - plr->x;
mob->offsetY = y - plr->y; mob->offsetY = y - plr->y;
} }
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
// if we're in a lair, we need to spawn the NPC in both the private instance and the template // if we're in a lair, we need to spawn the NPC in both the private instance and the template
if (PLAYERID(plr->instanceID) != 0) { if (PLAYERID(plr->instanceID) != 0) {
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
if (team == 2 && i > 0 && npc->type == EntityType::MOB) { if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; leadNpc->groupMember[i-1] = npc->id;
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
mob->groupLeader = leadNpc->appearanceData.iNPC_ID; mob->groupLeader = leadNpc->id;
mob->offsetX = x - plr->x; mob->offsetX = x - plr->x;
mob->offsetY = y - plr->y; mob->offsetY = y - plr->y;
} }
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
} }
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
if (i == 0 && team == 2 && npc->type == EntityType::MOB) { if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) {
type = type2; type = type2;
leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; leadNpc = (Mob*)NPCManager::NPCs[npc->id];
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID; leadNpc->groupLeader = leadNpc->id;
} }
} }
@ -654,7 +658,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
return; return;
} }
TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader
} }
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -671,15 +675,14 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
return; return;
} }
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID)); Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType)); Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP)); Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag)); Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type));
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z)); Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle)); Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle));
std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos)); std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos));
Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}"); Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}");
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID))); Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
@ -694,7 +697,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
int lastDist = INT_MAX; int lastDist = INT_MAX;
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) { for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
for (const EntityRef& ref : chnk->entities) { for (const EntityRef& ref : chnk->entities) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
continue; continue;
BaseNPC* npc = (BaseNPC*)ref.getEntity(); BaseNPC* npc = (BaseNPC*)ref.getEntity();
@ -705,7 +708,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
continue; continue;
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) { for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
if (it->second.npcID == npc->appearanceData.iNPCType) { if (it->second.npcID == npc->type) {
taskID = it->second.limitTaskID; taskID = it->second.limitTaskID;
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"]; missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
lastDist = dist; lastDist = dist;
@ -977,11 +980,17 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// add first point at NPC's current location // add first point at NPC's current location
std::vector<BaseNPC*> pathPoints; std::vector<BaseNPC*> pathPoints;
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
// assign coords manually, since we aren't actually adding markers to the world
marker->x = npc->x;
marker->y = npc->y;
marker->z = npc->z;
pathPoints.push_back(marker); pathPoints.push_back(marker);
// map from player // map from player
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints); TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you");
updatePathMarkers(sock); updatePathMarkers(sock);
return; return;
} }
@ -998,7 +1007,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path kf // /path kf
if (args[1] == "kf") { if (args[1] == "kf") {
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
marker->x = npc->x;
marker->y = npc->y;
marker->z = npc->z;
entry->second.push_back(marker); entry->second.push_back(marker);
Chat::sendServerMessage(sock, "[PATH] Added keyframe"); Chat::sendServerMessage(sock, "[PATH] Added keyframe");
updatePathMarkers(sock); updatePathMarkers(sock);
@ -1008,8 +1022,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path here // /path here
if (args[1] == "here") { if (args[1] == "here") {
// bring the NPC to where the player is standing // bring the NPC to where the player is standing
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
Chat::sendServerMessage(sock, "[PATH] Come here"); Chat::sendServerMessage(sock, "[PATH] Come here");
@ -1047,9 +1061,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
speed = speedArg; speed = speedArg;
} }
// return NPC to home // return NPC to home
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
@ -1065,7 +1079,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
from = to; // update point A from = to; // update point A
} }
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; Transport::NPCQueues[npc->id] = keyframes;
entry->second.pop_back(); // remove temp end point entry->second.pop_back(); // remove temp end point
Chat::sendServerMessage(sock, "[PATH] Testing NPC path"); Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
@ -1075,9 +1089,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path cancel // /path cancel
if (args[1] == "cancel") { if (args[1] == "cancel") {
// return NPC to home // return NPC to home
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
// deallocate markers // deallocate markers
@ -1087,7 +1101,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
} }
// unmap // unmap
TableData::RunningNPCPaths.erase(plr->iID); TableData::RunningNPCPaths.erase(plr->iID);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
return; return;
} }
@ -1115,9 +1129,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
} }
// return NPC to home and set path to repeat // return NPC to home and set path to repeat
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
npc->loopingPath = true; npc->loopingPath = true;
@ -1134,7 +1148,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
from = to; // update point A from = to; // update point A
} }
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; Transport::NPCQueues[npc->id] = keyframes;
entry->second.pop_back(); // remove temp end point entry->second.pop_back(); // remove temp end point
// save to gruntwork // save to gruntwork
@ -1161,7 +1175,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
finishedPath.isLoop = true; finishedPath.isLoop = true;
finishedPath.speed = speed; finishedPath.speed = speed;
finishedPath.points = finalPoints; finishedPath.points = finalPoints;
finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID); finishedPath.targetIDs.push_back(npc->id);
TableData::FinishedNPCPaths.push_back(finishedPath); TableData::FinishedNPCPaths.push_back(finishedPath);
@ -1173,7 +1187,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// unmap // unmap
TableData::RunningNPCPaths.erase(plr->iID); TableData::RunningNPCPaths.erase(plr->iID);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
TableData::flush(); TableData::flush();
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork"); Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");

View File

@ -2,6 +2,8 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <string>
namespace CustomCommands { namespace CustomCommands {
void init(); void init();

View File

@ -1,141 +1,78 @@
#include "core/Core.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Groups.hpp" #include "NPCManager.hpp"
#include "Entities.hpp"
#include "Items.hpp"
#include <assert.h> #include <assert.h>
using namespace Eggs; using namespace Eggs;
/// sock, CBFlag -> until
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
std::unordered_map<int, EggType> Eggs::EggTypes; std::unordered_map<int, EggType> Eggs::EggTypes;
int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
int bitFlag = Groups::getGroupFlags(otherPlr); // eggId might be 0 if the buff is made by the /buff command
int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag); EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
size_t resplen; if(Abilities::SkillTable.count(skillId) == 0) {
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
if (skillId == 183) { return;
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage);
} else if (skillId == 150) {
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP);
} else {
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff);
}
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
if (skillId == 183) { // damage egg
auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
memset(respbuf, 0, resplen);
skill->eCT = 1;
skill->iID = plr->iID;
skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
plr->HP -= skill->iDamage;
if (plr->HP < 0)
plr->HP = 0;
skill->iHP = plr->HP;
} else if (skillId == 150) { // heal egg
auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
memset(respbuf, 0, resplen);
skill->eCT = 1;
skill->iID = plr->iID;
skill->iHealHP = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
plr->HP += skill->iHealHP;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
skill->iHP = plr->HP;
} else { // regular buff egg
auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
memset(respbuf, 0, resplen);
skill->eCT = 1;
skill->iID = plr->iID;
skill->iConditionBitFlag = plr->iConditionBitFlag;
} }
skillUse->iNPC_ID = eggId; SkillData* skill = &Abilities::SkillTable[skillId];
skillUse->iSkillID = skillId; if(skill->drainType == SkillDrainType::PASSIVE) {
skillUse->eST = Nanos::SkillTable[skillId].skillType; // apply buff
skillUse->iTargetCnt = 1; if(skill->targetType != SkillTargetType::PLAYERS) {
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
}
sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); int value = skill->values[0][0];
BuffStack eggBuff = {
duration * 1000 / MS_PER_PLAYER_TICK,
value,
src,
BuffClass::EGG
};
plr->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack);
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
},
[](EntityRef self, Buff* buff, time_t currTime) {
// no-op
},
&eggBuff);
}
if (CBFlag == 0) // use skill
return -1; std::vector<ICombatant*> targets;
targets.push_back(dynamic_cast<ICombatant*>(plr));
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag); Abilities::useNPCSkill(src, skillId, targets);
// save the buff serverside;
// if you get the same buff again, new duration will override the previous one
time_t until = getTime() + (time_t)duration * 1000;
EggBuffs[key] = until;
return 0;
} }
static void eggStep(CNServer* serv, time_t currTime) { static void eggStep(CNServer* serv, time_t currTime) {
// tick buffs
time_t timeStamp = currTime;
auto it = EggBuffs.begin();
while (it != EggBuffs.end()) {
// check remaining time
if (it->second > timeStamp) {
it++;
} else { // if time reached 0
CNSocket* sock = it->first.first;
int32_t CBFlag = it->first.second;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
int groupFlags = Groups::getGroupFlags(otherPlr);
for (auto& pwr : Nanos::NanoPowers) {
if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
resp.eCSTB = pwr.timeBuffID;
resp.eTBU = 2;
resp.eTBT = 3; // for egg buffs
plr->iConditionBitFlag &= ~CBFlag;
resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
resp2.eCT = 1;
resp2.iID = plr->iID;
resp2.iConditionBitFlag = plr->iConditionBitFlag;
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
}
}
// remove buff from the map
it = EggBuffs.erase(it);
}
}
// check dead eggs and eggs in inactive chunks // check dead eggs and eggs in inactive chunks
for (auto npc : NPCManager::NPCs) { for (auto npc : NPCManager::NPCs) {
if (npc.second->type != EntityType::EGG) if (npc.second->kind != EntityKind::EGG)
continue; continue;
auto egg = (Egg*)npc.second; auto egg = (Egg*)npc.second;
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks)) if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
continue; continue;
if (egg->deadUntil <= timeStamp) { if (egg->deadUntil <= currTime) {
// respawn it // respawn it
egg->dead = false; egg->dead = false;
egg->deadUntil = 0; egg->deadUntil = 0;
egg->appearanceData.iHP = 400; egg->hp = 400;
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
} }
@ -163,7 +100,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
return; return;
} }
auto egg = (Egg*)eggRef.getEntity(); auto egg = (Egg*)eggRef.getEntity();
if (egg->type != EntityType::EGG) { if (egg->kind != EntityKind::EGG) {
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
return; return;
} }
@ -180,7 +117,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
} }
*/ */
int typeId = egg->appearanceData.iNPCType; int typeId = egg->type;
if (EggTypes.find(typeId) == EggTypes.end()) { if (EggTypes.find(typeId) == EggTypes.end()) {
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl; std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
return; return;
@ -188,16 +125,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
EggType* type = &EggTypes[typeId]; EggType* type = &EggTypes[typeId];
// buff the player
if (type->effectId != 0)
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
/* /*
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client * SHINY_PICKUP_SUCC is only causing a GUI effect in the client
* (buff icon pops up in the bottom of the screen) * (buff icon pops up in the bottom of the screen)
* so we don't send it for non-effect * so we don't send it for non-effect
*/ */
if (type->effectId != 0) { if (type->effectId != 0) {
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
resp.iSkillID = type->effectId; resp.iSkillID = type->effectId;
@ -255,7 +189,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
egg->dead = true; egg->dead = true;
egg->deadUntil = getTime() + (time_t)type->regen * 1000; egg->deadUntil = getTime() + (time_t)type->regen * 1000;
egg->appearanceData.iHP = 0; egg->hp = 0;
} }
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Entities.hpp"
struct EggType { struct EggType {
int dropCrateId; int dropCrateId;
@ -11,12 +10,10 @@ struct EggType {
}; };
namespace Eggs { namespace Eggs {
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
extern std::unordered_map<int, EggType> EggTypes; extern std::unordered_map<int, EggType> EggTypes;
void init(); void init();
/// returns -1 on fail void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg); void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
} }

View File

@ -1,9 +1,9 @@
#include "Email.hpp" #include "Email.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "db/Database.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "db/Database.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "Chat.hpp" #include "Chat.hpp"

View File

@ -1,18 +1,15 @@
#include "core/Core.hpp"
#include "Entities.hpp" #include "Entities.hpp"
#include "Chunking.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Eggs.hpp"
#include "MobAI.hpp"
#include <type_traits> #include "NPCManager.hpp"
#include "PlayerManager.hpp"
#include <assert.h>
static_assert(std::is_standard_layout<EntityRef>::value); static_assert(std::is_standard_layout<EntityRef>::value);
static_assert(std::is_trivially_copyable<EntityRef>::value); static_assert(std::is_trivially_copyable<EntityRef>::value);
EntityRef::EntityRef(CNSocket *s) { EntityRef::EntityRef(CNSocket *s) {
type = EntityType::PLAYER; kind = EntityKind::PLAYER;
sock = s; sock = s;
} }
@ -20,11 +17,11 @@ EntityRef::EntityRef(int32_t i) {
id = i; id = i;
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
type = NPCManager::NPCs[id]->type; kind = NPCManager::NPCs[id]->kind;
} }
bool EntityRef::isValid() const { bool EntityRef::isValid() const {
if (type == EntityType::PLAYER) if (kind == EntityKind::PLAYER)
return PlayerManager::players.find(sock) != PlayerManager::players.end(); return PlayerManager::players.find(sock) != PlayerManager::players.end();
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end(); return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
@ -33,21 +30,38 @@ bool EntityRef::isValid() const {
Entity *EntityRef::getEntity() const { Entity *EntityRef::getEntity() const {
assert(isValid()); assert(isValid());
if (type == EntityType::PLAYER) if (kind == EntityKind::PLAYER)
return PlayerManager::getPlayer(sock); return PlayerManager::getPlayer(sock);
return NPCManager::NPCs[id]; return NPCManager::NPCs[id];
} }
sNPCAppearanceData BaseNPC::getAppearanceData() {
sNPCAppearanceData data = {};
data.iAngle = angle;
data.iBarkerType = 0; // unused?
data.iConditionBitFlag = 0;
data.iHP = hp;
data.iNPCType = type;
data.iNPC_ID = id;
data.iX = x;
data.iY = y;
data.iZ = z;
return data;
}
sNPCAppearanceData CombatNPC::getAppearanceData() {
sNPCAppearanceData data = BaseNPC::getAppearanceData();
data.iConditionBitFlag = getCompositeCondition();
return data;
}
/* /*
* Entity coming into view. * Entity coming into view.
*/ */
void BaseNPC::enterIntoViewOf(CNSocket *sock) { void BaseNPC::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = appearanceData; pkt.NPCAppearanceData = getAppearanceData();
pkt.NPCAppearanceData.iX = x;
pkt.NPCAppearanceData.iY = y;
pkt.NPCAppearanceData.iZ = z;
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
} }
@ -56,7 +70,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
// TODO: Potentially decouple this from BaseNPC? // TODO: Potentially decouple this from BaseNPC?
pkt.AppearanceData = { pkt.AppearanceData = {
3, appearanceData.iNPC_ID, appearanceData.iNPCType, 3, id, type,
x, y, z x, y, z
}; };
@ -66,28 +80,40 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
void Egg::enterIntoViewOf(CNSocket *sock) { void Egg::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData); // TODO: Potentially decouple this from BaseNPC?
pkt.ShinyAppearanceData = {
id, type, 0, // client doesn't care about map num
x, y, z
};
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
} }
sNano* Player::getActiveNano() {
return &Nanos[activeNano];
}
sPCAppearanceData Player::getAppearanceData() {
sPCAppearanceData data = {};
data.iID = iID;
data.iHP = HP;
data.iLv = level;
data.iX = x;
data.iY = y;
data.iZ = z;
data.iAngle = angle;
data.PCStyle = PCStyle;
data.Nano = Nanos[activeNano];
data.iPCState = iPCState;
data.iSpecialState = iSpecialState;
memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
return data;
}
// TODO: this is less effiecient than it was, because of memset() // TODO: this is less effiecient than it was, because of memset()
void Player::enterIntoViewOf(CNSocket *sock) { void Player::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_PC_NEW, pkt); INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
pkt.PCAppearanceData = getAppearanceData();
pkt.PCAppearanceData.iID = iID;
pkt.PCAppearanceData.iHP = HP;
pkt.PCAppearanceData.iLv = level;
pkt.PCAppearanceData.iX = x;
pkt.PCAppearanceData.iY = y;
pkt.PCAppearanceData.iZ = z;
pkt.PCAppearanceData.iAngle = angle;
pkt.PCAppearanceData.PCStyle = PCStyle;
pkt.PCAppearanceData.Nano = Nanos[activeNano];
pkt.PCAppearanceData.iPCState = iPCState;
pkt.PCAppearanceData.iSpecialState = iSpecialState;
memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
sock->sendPacket(pkt, P_FE2CL_PC_NEW); sock->sendPacket(pkt, P_FE2CL_PC_NEW);
} }
@ -96,20 +122,20 @@ void Player::enterIntoViewOf(CNSocket *sock) {
*/ */
void BaseNPC::disappearFromViewOf(CNSocket *sock) { void BaseNPC::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
pkt.iNPC_ID = appearanceData.iNPC_ID; pkt.iNPC_ID = id;
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
} }
void Bus::disappearFromViewOf(CNSocket *sock) { void Bus::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt); INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
pkt.eTT = 3; pkt.eTT = 3;
pkt.iT_ID = appearanceData.iNPC_ID; pkt.iT_ID = id;
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT); sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
} }
void Egg::disappearFromViewOf(CNSocket *sock) { void Egg::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt); INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
pkt.iShinyID = appearanceData.iNPC_ID; pkt.iShinyID = id;
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT); sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
} }

View File

@ -1,23 +1,26 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "EntityRef.hpp"
#include "Buffs.hpp"
#include "Chunking.hpp" #include "Chunking.hpp"
#include "Groups.hpp"
#include <stdint.h>
#include <set> #include <set>
#include <map>
#include <functional>
enum class EntityType : uint8_t { enum class AIState {
INVALID, INACTIVE,
PLAYER, ROAMING,
SIMPLE_NPC, COMBAT,
COMBAT_NPC, RETREAT,
MOB, DEAD
EGG,
BUS
}; };
struct Entity { struct Entity {
EntityType type = EntityType::INVALID; EntityKind kind = EntityKind::INVALID;
int x = 0, y = 0, z = 0; int x = 0, y = 0, z = 0;
uint64_t instanceID = 0; uint64_t instanceID = 0;
ChunkPos chunkPos = {}; ChunkPos chunkPos = {};
@ -26,47 +29,39 @@ struct Entity {
// destructor must be virtual, apparently // destructor must be virtual, apparently
virtual ~Entity() {} virtual ~Entity() {}
virtual bool isAlive() { return true; } virtual bool isExtant() { return true; }
// stubs // stubs
virtual void enterIntoViewOf(CNSocket *sock) = 0; virtual void enterIntoViewOf(CNSocket *sock) = 0;
virtual void disappearFromViewOf(CNSocket *sock) = 0; virtual void disappearFromViewOf(CNSocket *sock) = 0;
}; };
struct EntityRef { /*
EntityType type; * Interfaces
union { */
CNSocket *sock; class ICombatant {
int32_t id; public:
}; ICombatant() {}
virtual ~ICombatant() {}
EntityRef(CNSocket *s); virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
EntityRef(int32_t i); virtual Buff* getBuff(int) = 0;
virtual void removeBuff(int) = 0;
bool isValid() const; virtual void removeBuff(int, BuffClass) = 0;
Entity *getEntity() const; virtual void clearBuffs(bool) = 0;
virtual bool hasBuff(int) = 0;
bool operator==(const EntityRef& other) const { virtual int getCompositeCondition() = 0;
if (type != other.type) virtual int takeDamage(EntityRef, int) = 0;
return false; virtual int heal(EntityRef, int) = 0;
virtual bool isAlive() = 0;
if (type == EntityType::PLAYER) virtual int getCurrentHP() = 0;
return sock == other.sock; virtual int getMaxHP() = 0;
virtual int getLevel() = 0;
return id == other.id; virtual std::vector<EntityRef> getGroupMembers() = 0;
} virtual int32_t getCharType() = 0;
virtual int32_t getID() = 0;
// arbitrary ordering virtual EntityRef getRef() = 0;
bool operator<(const EntityRef& other) const { virtual void step(time_t currTime) = 0;
if (type == other.type) {
if (type == EntityType::PLAYER)
return sock < other.sock;
else
return id < other.id;
}
return type < other.type;
}
}; };
/* /*
@ -74,75 +69,104 @@ struct EntityRef {
*/ */
class BaseNPC : public Entity { class BaseNPC : public Entity {
public: public:
sNPCAppearanceData appearanceData = {}; int id;
int type;
int hp;
int angle;
bool loopingPath = false; bool loopingPath = false;
BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX BaseNPC(int _A, uint64_t iID, int t, int _id) {
x = _X; kind = EntityKind::SIMPLE_NPC;
y = _Y; type = t;
z = _Z; hp = 400;
appearanceData.iNPCType = t; angle = _A;
appearanceData.iHP = 400; id = _id;
appearanceData.iAngle = angle;
appearanceData.iConditionBitFlag = 0;
appearanceData.iBarkerType = 0;
appearanceData.iNPC_ID = id;
instanceID = iID; instanceID = iID;
}; };
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
virtual sNPCAppearanceData getAppearanceData();
}; };
struct CombatNPC : public BaseNPC { struct CombatNPC : public BaseNPC, public ICombatant {
int maxHealth = 0; int maxHealth = 0;
int spawnX = 0; int spawnX = 0;
int spawnY = 0; int spawnY = 0;
int spawnZ = 0; int spawnZ = 0;
int level = 0; int level = 0;
int speed = 300; int speed = 300;
AIState state = AIState::INACTIVE;
Group* group = nullptr;
int playersInView = 0; // for optimizing away AI in empty chunks
void (*_stepAI)(CombatNPC*, time_t) = nullptr; std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
// XXX std::unordered_map<int, Buff*> buffs = {};
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) :
BaseNPC(x, y, z, angle, iID, t, id),
maxHealth(maxHP) {}
virtual void stepAI(time_t currTime) { CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
if (_stepAI != nullptr) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
_stepAI(this, currTime); spawnX = x;
spawnY = y;
spawnZ = z;
kind = EntityKind::COMBAT_NPC;
stateHandlers[AIState::INACTIVE] = {};
transitionHandlers[AIState::INACTIVE] = {};
} }
virtual bool isAlive() override { return appearanceData.iHP > 0; } virtual sNPCAppearanceData getAppearanceData() override;
virtual bool isExtant() override { return hp > 0; }
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(int buffId) override;
virtual void removeBuff(int buffId, BuffClass buffClass) override;
virtual void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() override;
virtual int takeDamage(EntityRef src, int amt) override;
virtual int heal(EntityRef src, int amt) override;
virtual bool isAlive() override;
virtual int getCurrentHP() override;
virtual int getMaxHP() override;
virtual int getLevel() override;
virtual std::vector<EntityRef> getGroupMembers() override;
virtual int32_t getCharType() override;
virtual int32_t getID() override;
virtual EntityRef getRef() override;
virtual void step(time_t currTime) override;
virtual void transition(AIState newState, EntityRef src);
}; };
// Mob is in MobAI.hpp, Player is in Player.hpp // Mob is in MobAI.hpp, Player is in Player.hpp
// TODO: decouple from BaseNPC
struct Egg : public BaseNPC { struct Egg : public BaseNPC {
bool summoned = false; bool summoned = false;
bool dead = false; bool dead = false;
time_t deadUntil; time_t deadUntil;
Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon) Egg(uint64_t iID, int t, int32_t id, bool summon)
: BaseNPC(x, y, z, 0, iID, t, id) { : BaseNPC(0, iID, t, id) {
summoned = summon; summoned = summon;
type = EntityType::EGG; kind = EntityKind::EGG;
} }
virtual bool isAlive() override { return !dead; } virtual bool isExtant() override { return !dead; }
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
}; };
// TODO: decouple from BaseNPC
struct Bus : public BaseNPC { struct Bus : public BaseNPC {
Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : Bus(int angle, uint64_t iID, int t, int id) :
BaseNPC(x, y, z, angle, iID, t, id) { BaseNPC(angle, iID, t, id) {
type = EntityType::BUS; kind = EntityKind::BUS;
loopingPath = true; loopingPath = true;
} }

56
src/EntityRef.hpp Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include "core/Core.hpp"
/* forward declaration(s) */
struct Entity;
enum EntityKind {
INVALID,
PLAYER,
SIMPLE_NPC,
COMBAT_NPC,
MOB,
EGG,
BUS
};
struct EntityRef {
EntityKind kind;
union {
CNSocket *sock;
int32_t id;
};
EntityRef(CNSocket *s);
EntityRef(int32_t i);
bool isValid() const;
Entity *getEntity() const;
bool operator==(const EntityRef& other) const {
if (kind != other.kind)
return false;
if (kind == EntityKind::PLAYER)
return sock == other.sock;
return id == other.id;
}
bool operator!=(const EntityRef& other) const {
return !(*this == other);
}
// arbitrary ordering
bool operator<(const EntityRef& other) const {
if (kind == other.kind) {
if (kind == EntityKind::PLAYER)
return sock < other.sock;
else
return id < other.id;
}
return kind < other.kind;
}
};

View File

@ -1,13 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Groups.hpp" #include "Groups.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp"
#include <iostream> #include "servers/CNShardServer.hpp"
#include <chrono>
#include <algorithm> #include "Player.hpp"
#include <thread> #include "PlayerManager.hpp"
#include "Entities.hpp"
/* /*
* NOTE: Variadic response packets that list group members are technically * NOTE: Variadic response packets that list group members are technically
@ -19,22 +16,177 @@
using namespace Groups; using namespace Groups;
Group::Group(EntityRef leader) {
addToGroup(this, leader);
}
static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) {
for(EntityRef pcRef : pcs) {
sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot;
Player* plr = PlayerManager::getPlayer(pcRef.sock);
info->iPC_ID = plr->iID;
info->iPCUID = plr->PCStyle.iPC_UID;
info->iNameCheck = plr->PCStyle.iNameCheck;
memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
info->iSpecialState = plr->iSpecialState;
info->iLv = plr->level;
info->iHP = plr->HP;
info->iMaxHP = PC_MAXHEALTH(plr->level);
// info->iMapType = 0;
// info->iMapNum = 0;
info->iX = plr->x;
info->iY = plr->y;
info->iZ = plr->z;
if(plr->activeNano > 0) {
info->Nano = *plr->getActiveNano();
info->bNano = true;
}
pivot = (uint8_t*)(info + 1);
}
for(EntityRef npcRef : npcs) {
sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot;
// probably should not assume that the combatant is an
// entity, but it works for now
BaseNPC* npc = (BaseNPC*)npcRef.getEntity();
info->iNPC_ID = npcRef.id;
info->iNPC_Type = npc->type;
info->iHP = npc->hp;
info->iX = npc->x;
info->iY = npc->y;
info->iZ = npc->z;
pivot = (uint8_t*)(info + 1);
}
}
void Groups::addToGroup(Group* group, EntityRef member) {
if (member.kind == EntityKind::PLAYER) {
Player* plr = PlayerManager::getPlayer(member.sock);
plr->group = group;
}
else if (member.kind == EntityKind::COMBAT_NPC) {
CombatNPC* npc = (CombatNPC*)member.getEntity();
npc->group = group;
}
else {
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
}
group->members.push_back(member);
if(member.kind == EntityKind::PLAYER) {
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
size_t pcCount = pcs.size();
size_t npcCount = npcs.size();
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
pkt->iMemberPCCnt = (int32_t)pcCount;
pkt->iMemberNPCCnt = (int32_t)npcCount;
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo))
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl;
} else {
uint8_t* pivot = (uint8_t*)(pkt + 1);
attachGroupData(pcs, npcs, pivot);
// PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs
// (and the client does care!) so we need to send one to the new member
// and the other to the rest
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo);
member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen);
sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
}
}
}
bool Groups::removeFromGroup(Group* group, EntityRef member) {
if (member.kind == EntityKind::PLAYER) {
Player* plr = PlayerManager::getPlayer(member.sock);
plr->group = nullptr; // no dangling pointers here muahaahahah
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt);
member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC);
}
else if (member.kind == EntityKind::COMBAT_NPC) {
CombatNPC* npc = (CombatNPC*)member.getEntity();
npc->group = nullptr;
}
else {
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
}
auto it = std::find(group->members.begin(), group->members.end(), member);
if (it == group->members.end()) {
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
} else {
group->members.erase(it);
}
if(member.kind == EntityKind::PLAYER) {
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
size_t pcCount = pcs.size();
size_t npcCount = npcs.size();
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
pkt->iMemberPCCnt = (int32_t)pcCount;
pkt->iMemberNPCCnt = (int32_t)npcCount;
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo))
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl;
} else {
uint8_t* pivot = (uint8_t*)(pkt + 1);
attachGroupData(pcs, npcs, pivot);
sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE,
sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
}
}
if (group->members.size() == 1) {
return removeFromGroup(group, group->members.back());
}
if (group->members.empty()) {
delete group; // cleanup memory
return true;
}
return false;
}
void Groups::disbandGroup(Group* group) {
// remove everyone from the group!!
bool done = false;
while(!done) {
EntityRef back = group->members.back();
done = removeFromGroup(group, back);
}
}
static void requestGroup(CNSocket* sock, CNPacketData* data) { static void requestGroup(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf; sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To); Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
if (otherPlr == nullptr)
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr) if (otherPlr == nullptr)
return; return;
// fail if the group is full or the other player is already in a group // fail if the group is full or the other player is already in a group
if (plr->groupCnt >= 4 || otherPlr->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) { if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) {
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL)); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
return; return;
@ -75,255 +227,80 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From); Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
if (otherPlr == nullptr) if (otherPlr == nullptr)
return; return; // disconnect or something
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
if (otherPlr == nullptr)
return;
// fail if the group is full or the other player is already in a group // fail if the group is full or the other player is already in a group
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) { if (plr->group != nullptr || size + 1 > 4) {
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp); INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL)); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
return; return;
} }
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) { if (otherPlr->group == nullptr) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; // create group
return; EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
otherPlr->group = new Group(otherPlrRef);
} }
addToGroup(otherPlr->group, sock);
plr->iIDGroup = otherPlr->iID;
otherPlr->groupCnt += 1;
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
resp->iID_NewMember = plr->iID;
resp->iMemberPCCnt = otherPlr->groupCnt;
int bitFlag = getGroupFlags(otherPlr);
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (varPlr == nullptr || sockTo == nullptr)
continue;
respdata[i].iPC_ID = varPlr->iID;
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i].iSpecialState = varPlr->iSpecialState;
respdata[i].iLv = varPlr->level;
respdata[i].iHP = varPlr->HP;
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
//respdata[i].iMapType = 0;
//respdata[i].iMapNum = 0;
respdata[i].iX = varPlr->x;
respdata[i].iY = varPlr->y;
respdata[i].iZ = varPlr->z;
// client doesnt read nano data here
if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);
}
}
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
} }
static void leaveGroup(CNSocket* sock, CNPacketData* data) { static void leaveGroup(CNSocket* sock, CNPacketData* data) {
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
groupKickPlayer(plr); groupKick(plr->group, sock);
} }
void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) { void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
for (int i = 0; i < plr->groupCnt; i++) { auto players = group->filter(EntityKind::PLAYER);
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]); for (EntityRef ref : players) {
ref.sock->sendPacket(buf, type, size);
if (sock == nullptr)
continue;
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
Player* leavingPlr = PlayerManager::getPlayer(sock);
leavingPlr->iIDGroup = leavingPlr->iID;
}
sock->sendPacket(buf, type, size);
} }
} }
void Groups::groupTickInfo(Player* plr) { void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) { auto players = group->filter(EntityKind::PLAYER);
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; for (EntityRef ref : players) {
return; if(ref != excluded) ref.sock->sendPacket(buf, type, size);
} }
}
void Groups::groupTickInfo(CNSocket* sock) {
Player* plr = PlayerManager::getPlayer(sock);
Group* group = plr->group;
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
size_t pcCount = pcs.size();
size_t npcCount = npcs.size();
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
memset(respbuf, 0, resplen); pkt->iID = plr->iID;
pkt->iMemberPCCnt = (int32_t)pcCount;
pkt->iMemberNPCCnt = (int32_t)npcCount;
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO)); || !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl;
resp->iID = plr->iID; } else {
resp->iMemberPCCnt = plr->groupCnt; uint8_t* pivot = (uint8_t*)(pkt + 1);
attachGroupData(pcs, npcs, pivot);
for (int i = 0; i < plr->groupCnt; i++) { sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
if (varPlr == nullptr)
continue;
respdata[i].iPC_ID = varPlr->iID;
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i].iSpecialState = varPlr->iSpecialState;
respdata[i].iLv = varPlr->level;
respdata[i].iHP = varPlr->HP;
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
//respdata[i].iMapType = 0;
//respdata[i].iMapNum = 0;
respdata[i].iX = varPlr->x;
respdata[i].iY = varPlr->y;
respdata[i].iZ = varPlr->z;
if (varPlr->activeNano > 0) {
respdata[i].bNano = 1;
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
}
}
sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
}
static void groupUnbuff(Player* plr) {
for (int i = 0; i < plr->groupCnt; i++) {
for (int n = 0; n < plr->groupCnt; n++) {
if (i == n)
continue;
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]);
Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
}
} }
} }
void Groups::groupKickPlayer(Player* plr) { void Groups::groupKick(Group* group, EntityRef ref) {
// if you are the group leader, destroy your own group and kick everybody // if you are the group leader, destroy your own group and kick everybody
if (plr->iID == plr->iIDGroup) { if (group->members[0] == ref) {
groupUnbuff(plr); disbandGroup(group);
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
plr->groupCnt = 1;
return; return;
} }
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); removeFromGroup(group, ref);
if (otherPlr == nullptr)
return;
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
return;
}
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
resp->iID_LeaveMember = plr->iID;
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
int bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
int moveDown = 0;
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
if (sock == nullptr)
return;
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (varPlr == nullptr || sockTo == nullptr)
continue;
if (moveDown == 1)
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
respdata[i-moveDown].iPC_ID = varPlr->iID;
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
respdata[i-moveDown].iLv = varPlr->level;
respdata[i-moveDown].iHP = varPlr->HP;
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
// respdata[i-moveDown]].iMapType = 0;
// respdata[i-moveDown]].iMapNum = 0;
respdata[i-moveDown].iX = varPlr->x;
respdata[i-moveDown].iY = varPlr->y;
respdata[i-moveDown].iZ = varPlr->z;
// client doesnt read nano data here
if (varPlr == plr) {
moveDown = 1;
otherPlr->groupIDs[i] = 0;
} else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);
}
}
plr->iIDGroup = plr->iID;
otherPlr->groupCnt -= 1;
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
}
int Groups::getGroupFlags(Player* plr) {
int bitFlag = 0;
for (int i = 0; i < plr->groupCnt; i++) {
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
if (otherPlr == nullptr)
continue;
bitFlag |= otherPlr->iGroupConditionBitFlag;
}
return bitFlag;
} }
void Groups::init() { void Groups::init() {

View File

@ -1,17 +1,37 @@
#pragma once #pragma once
#include "Player.hpp" #include "EntityRef.hpp"
#include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include <map> #include <vector>
#include <list> #include <assert.h>
struct Group {
std::vector<EntityRef> members;
std::vector<EntityRef> filter(EntityKind kind) {
std::vector<EntityRef> filtered;
std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) {
return e.kind == kind;
});
return filtered;
}
EntityRef getLeader() {
assert(members.size() > 0);
return members[0];
}
Group(EntityRef leader);
};
namespace Groups { namespace Groups {
void init(); void init();
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size); void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
void groupTickInfo(Player* plr); void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
void groupKickPlayer(Player* plr); void groupTickInfo(CNSocket* sock);
int getGroupFlags(Player* plr);
void groupKick(Group* group, EntityRef ref);
void addToGroup(Group* group, EntityRef member);
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
void disbandGroup(Group* group);
} }

View File

@ -1,16 +1,19 @@
#include "servers/CNShardServer.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "NPCManager.hpp"
#include "Player.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Missions.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "Rand.hpp" #include "MobAI.hpp"
#include "Missions.hpp"
#include "Buffs.hpp"
#include <string.h> // for memset() #include <string.h> // for memset()
#include <assert.h> #include <assert.h>
#include <numeric>
using namespace Items; using namespace Items;
@ -479,30 +482,34 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
resp->iSlotNum = request->iSlotNum; resp->iSlotNum = request->iSlotNum;
resp->RemainItem = gumball; resp->RemainItem = gumball;
resp->iTargetCnt = 1; resp->iTargetCnt = 1;
resp->eST = EST_NANOSTIMPAK; resp->eST = (int32_t)SkillType::NANOSTIMPAK;
resp->iSkillID = 144; resp->iSkillID = 144;
int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot; int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
respdata->eCT = 1; respdata->eCT = 1;
respdata->iID = player->iID; respdata->iID = player->iID;
respdata->iConditionBitFlag = value1; respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
pkt.eCSTB = value2; // eCharStatusTimeBuffID BuffStack gumballBuff = {
pkt.eTBU = 1; // eTimeBuffUpdate durationMilliseconds / MS_PER_PLAYER_TICK,
pkt.eTBT = 1; // eTimeBuffType 1 means nano 0,
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1; sock,
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); BuffClass::CASH_ITEM // or BuffClass::ITEM?
};
player->addBuff(eCSB,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack);
},
[](EntityRef self, Buff* buff, time_t currTime) {
// no-op
},
&gumballBuff);
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
// update inventory serverside // update inventory serverside
player->Inven[resp->iSlotNum] = resp->RemainItem; player->Inven[resp->iSlotNum] = resp->RemainItem;
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, value1);
time_t until = getTime() + (time_t)Nanos::SkillTable[144].durationTime[0] * 100;
Eggs::EggBuffs[key] = until;
} }
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
@ -755,7 +762,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) { if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
plr->money += miscDropType.taroAmount; plr->money += miscDropType.taroAmount;
// money nano boost // money nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { if (plr->hasBuff(ECSB_REWARD_CASH)) {
int boost = 0; int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1; boost = 1;
@ -770,7 +777,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
if (levelDifference > 0) if (levelDifference > 0)
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
// scavenger nano boost // scavenger nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { if (plr->hasBuff(ECSB_REWARD_BLOB)) {
int boost = 0; int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1; boost = 1;
@ -822,12 +829,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) { void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
// sanity check // sanity check
if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) { if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl; std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
return; return;
} }
// find mob drop id // find mob drop id
int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType]; int mobDropId = Items::MobToDropMap[mob->type];
giveSingleDrop(sock, mob, mobDropId, rolled); giveSingleDrop(sock, mob, mobDropId, rolled);

View File

@ -1,9 +1,13 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "MobAI.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include "MobAI.hpp"
#include <vector>
#include <map>
struct CrocPotEntry { struct CrocPotEntry {
int multStats, multLooks; int multStats, multLooks;

View File

@ -1,11 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "PlayerManager.hpp"
#include "Nanos.hpp"
#include "Items.hpp"
#include "Transport.hpp"
#include "string.h" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
using namespace Missions; using namespace Missions;
@ -164,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
// update player // update player
plr->money += reward->money; plr->money += reward->money;
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
int boost = 0; int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1; boost = 1;
plr->money += reward->money * (5 + boost) / 25; plr->money += reward->money * (5 + boost) / 25;
} }
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
int boost = 0; int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1; boost = 1;
@ -367,15 +366,15 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
// if escort task, assign matching paths to all nearby NPCs // if escort task, assign matching paths to all nearby NPCs
if (task["m_iHTaskType"] == 6) { if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) {
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
Chunk* chunk = Chunking::chunks[chunkPos]; Chunk* chunk = Chunking::chunks[chunkPos];
for (EntityRef ref : chunk->entities) { for (EntityRef ref : chunk->entities) {
if (ref.type != EntityType::PLAYER) { if (ref.kind != EntityKind::PLAYER) {
BaseNPC* npc = (BaseNPC*)ref.getEntity(); BaseNPC* npc = (BaseNPC*)ref.getEntity();
NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum); NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
if (path != nullptr) { if (path != nullptr) {
Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path); Transport::constructPathNPC(npc->id, path);
return; return;
} }
} }
@ -399,7 +398,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
* once we comb over mission logic more throughly * once we comb over mission logic more throughly
*/ */
bool mobsAreKilled = false; bool mobsAreKilled = false;
if (task->task["m_iHTaskType"] == 5) { if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
mobsAreKilled = true; mobsAreKilled = true;
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == missionData->iTaskNum) { if (plr->tasks[i] == missionData->iTaskNum) {

View File

@ -1,9 +1,11 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include "JSON.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "JSON.hpp" #include <map>
struct Reward { struct Reward {
int32_t id; int32_t id;

View File

@ -1,10 +1,15 @@
#include "MobAI.hpp" #include "MobAI.hpp"
#include "Player.hpp"
#include "Chunking.hpp"
#include "NPCManager.hpp"
#include "Entities.hpp"
#include "PlayerManager.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "Transport.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Combat.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Combat.hpp"
#include "Items.hpp"
#include "Missions.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include <cmath> #include <cmath>
@ -14,7 +19,51 @@ using namespace MobAI;
bool MobAI::simulateMobs = settings::SIMULATEMOBS; bool MobAI::simulateMobs = settings::SIMULATEMOBS;
static void roamingStep(Mob *mob, time_t currTime); void Mob::step(time_t currTime) {
if (playersInView < 0)
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
// skip movement and combat if disabled or not in view
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
&& state != AIState::RETREAT)
return;
// call superclass step
CombatNPC::step(currTime);
}
int Mob::takeDamage(EntityRef src, int amt) {
// cannot kill mobs multiple times; cannot harm retreating mobs
if (state != AIState::ROAMING && state != AIState::COMBAT) {
return 0; // no damage
}
if (skillStyle >= 0)
return 0; // don't hurt a mob casting corruption
if (state == AIState::ROAMING) {
assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now
transition(AIState::COMBAT, src);
if (groupLeader != 0)
MobAI::followToCombat(this);
}
// wake up sleeping monster
if (hasBuff(ECSB_MEZ)) {
removeBuff(ECSB_MEZ);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = id;
pkt1.iConditionBitFlag = getCompositeCondition();
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
// call superclass takeDamage
return CombatNPC::takeDamage(src, amt);
}
/* /*
* Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and * Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and
@ -47,44 +96,43 @@ 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->appearanceData.iConditionBitFlag = 0; mob->clearBuffs(false);
mob->unbuffTimes.clear();
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2; pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID; pkt1.iID = mob->id;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; pkt1.iConditionBitFlag = mob->getCompositeCondition();
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
} }
void MobAI::followToCombat(Mob *mob) { void MobAI::followToCombat(Mob *mob) {
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityKind::MOB) {
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (leadMob->groupMember[i] == 0) if (leadMob->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::ROAMING) // only roaming mobs should transition to combat if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat
continue; continue;
enterCombat(mob->target, followerMob); followerMob->transition(AIState::COMBAT, mob->target);
} }
if (leadMob->state != MobState::ROAMING) if (leadMob->state != AIState::ROAMING)
return; return;
enterCombat(mob->target, leadMob); leadMob->transition(AIState::COMBAT, mob->target);
} }
} }
void MobAI::groupRetreat(Mob *mob) { void MobAI::groupRetreat(Mob *mob) {
if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->type != EntityType::MOB) if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityKind::MOB)
return; return;
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
@ -92,25 +140,25 @@ void MobAI::groupRetreat(Mob *mob) {
if (leadMob->groupMember[i] == 0) if (leadMob->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::COMBAT) if (followerMob->state != AIState::COMBAT)
continue; continue;
followerMob->target = nullptr; followerMob->target = nullptr;
followerMob->state = MobState::RETREAT; followerMob->state = AIState::RETREAT;
clearDebuff(followerMob); clearDebuff(followerMob);
} }
if (leadMob->state != MobState::COMBAT) if (leadMob->state != AIState::COMBAT)
return; return;
leadMob->target = nullptr; leadMob->target = nullptr;
leadMob->state = MobState::RETREAT; leadMob->state = AIState::RETREAT;
clearDebuff(leadMob); clearDebuff(leadMob);
} }
@ -127,7 +175,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
// TODO: support targetting other CombatNPCs // TODO: support targetting other CombatNPCs
if (ref.type != EntityType::PLAYER) if (ref.kind != EntityKind::PLAYER)
continue; continue;
CNSocket *s = ref.sock; CNSocket *s = ref.sock;
@ -138,7 +186,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
int mobRange = mob->sightRange; int mobRange = mob->sightRange;
if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH if (plr->hasBuff(ECSB_UP_STEALTH)
|| Racing::EPRaces.find(s) != Racing::EPRaces.end()) || Racing::EPRaces.find(s) != Racing::EPRaces.end())
mobRange /= 3; mobRange /= 3;
@ -147,7 +195,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
if (levelDifference > -10) if (levelDifference > -10)
mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3; mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3;
if (mob->state != MobState::ROAMING && plr->inCombat) // freshly out of aggro mobs if (mob->state != AIState::ROAMING && plr->inCombat) // freshly out of aggro mobs
mobRange = mob->sightRange * 2; // should not be impacted by the above mobRange = mob->sightRange * 2; // should not be impacted by the above
if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE)) if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE))
@ -168,7 +216,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
if (closest != nullptr) { if (closest != nullptr) {
// found closest player. engage. // found closest player. engage.
enterCombat(closest, mob); mob->transition(AIState::COMBAT, closest);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
followToCombat(mob); followToCombat(mob);
@ -196,7 +244,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf;
sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT));
resp->iNPC_ID = mob->appearanceData.iNPC_ID; resp->iNPC_ID = mob->id;
resp->iSkillID = skillID; resp->iSkillID = skillID;
resp->iStyle = style; resp->iStyle = style;
resp->iValue1 = plr->x; resp->iValue1 = plr->x;
@ -234,26 +282,29 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
int style2 = Nanos::nanoStyle(plr->activeNano); int style2 = Nanos::nanoStyle(plr->activeNano);
if (style2 == -1) { // no nano if (style2 == -1) { // no nano
respdata[i].iHitFlag = 8; respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
} else if (style == style2) { } else if (style == style2) {
respdata[i].iHitFlag = 8; // tie respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
respdata[i].iDamage = 0; respdata[i].iDamage = 0;
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
} else if (style - style2 == 1 || style2 - style == 2) { } else if (style - style2 == 1 || style2 - style == 2) {
respdata[i].iHitFlag = 4; // 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
std::vector<int> targetData2 = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; SkillData skill = Abilities::SkillTable[skillID];
for (auto& pwr : Nanos::NanoPowers) skill.durationTime[0] = 0;
if (pwr.skillType == EST_DAMAGE) skill.values[0][0] = 200; // have to set
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); skill.values[0][1] = 200; // all of these
skill.values[0][2] = 200; // because the player might
skill.values[0][3] = 200; // have a boost
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
} else { } else {
respdata[i].iHitFlag = 16; // lose respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[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;
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90;
if (plr->Nanos[plr->activeNano].iStamina < 0) { if (plr->Nanos[plr->activeNano].iStamina < 0) {
respdata[i].bNanoDeactive = 1; respdata[i].bNanoDeactive = 1;
@ -265,16 +316,11 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
plr->HP -= respdata[i].iDamage; plr->HP -= respdata[i].iDamage;
respdata[i].iHP = plr->HP; respdata[i].iHP = plr->HP;
respdata[i].iConditionBitFlag = plr->iConditionBitFlag; respdata[i].iConditionBitFlag = plr->getCompositeCondition();
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; if (!MobAI::aggroCheck(mob, getTime()))
mob->state = MobState::RETREAT; mob->transition(AIState::RETREAT, mob->target);
if (!aggroCheck(mob, getTime())) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
} }
} }
@ -282,12 +328,6 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
} }
static void useAbilities(Mob *mob, time_t currTime) { static void useAbilities(Mob *mob, time_t currTime) {
/*
* targetData approach
* first integer is the count
* second to fifth integers are IDs, these can be either player iID or mob's iID
* whether the skill targets players or mobs is determined by the skill packet being fired
*/
Player *plr = PlayerManager::getPlayer(mob->target); Player *plr = PlayerManager::getPlayer(mob->target);
if (mob->skillStyle >= 0) { // corruption hit if (mob->skillStyle >= 0) { // corruption hit
@ -302,35 +342,32 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (mob->skillStyle == -2) { // eruption hit if (mob->skillStyle == -2) { // eruption hit
int skillID = (int)mob->data["m_iMegaType"]; int skillID = (int)mob->data["m_iMegaType"];
std::vector<int> targetData = {0, 0, 0, 0, 0}; std::vector<ICombatant*> targets{};
// find the players within range of eruption // find the players within range of eruption
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) { for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
// TODO: see aggroCheck() // TODO: see aggroCheck()
if (ref.type != EntityType::PLAYER) if (ref.kind != EntityKind::PLAYER)
continue; continue;
CNSocket *s= ref.sock; CNSocket *s = ref.sock;
Player *plr = PlayerManager::getPlayer(s); Player *plr = PlayerManager::getPlayer(s);
if (plr->HP <= 0) if (!plr->isAlive())
continue; continue;
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y); int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
if (distance < Nanos::SkillTable[skillID].effectArea) { if (distance < Abilities::SkillTable[skillID].effectArea) {
targetData[0] += 1; targets.push_back(plr);
targetData[targetData[0]] = plr->iID; if (targets.size() > 3) // make sure not to have more than 4
if (targetData[0] > 3) // make sure not to have more than 4
break; break;
} }
} }
} }
for (auto& pwr : Combat::MobPowers) Abilities::useNPCSkill(mob->id, skillID, targets);
if (pwr.skillType == Nanos::SkillTable[skillID].skillType)
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]);
mob->skillStyle = -3; // eruption cooldown mob->skillStyle = -3; // eruption cooldown
mob->nextAttack = currTime + 1000; mob->nextAttack = currTime + 1000;
return; return;
@ -348,13 +385,11 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1) { // active skill hit if (random < prob1) { // active skill hit
int skillID = (int)mob->data["m_iActiveSkill1"]; int skillID = (int)mob->data["m_iActiveSkill1"];
std::vector<int> targetData = {1, plr->iID, 0, 0, 0}; SkillData* skill = &Abilities::SkillTable[skillID];
for (auto& pwr : Combat::MobPowers) int debuffID = Abilities::getCSTBFromST(skill->skillType);
if (pwr.skillType == Nanos::SkillTable[skillID].skillType) { if(plr->hasBuff(debuffID))
if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) return; // prevent debuffing a player twice
return; // prevent debuffing a player twice Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]);
}
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
return; return;
} }
@ -362,7 +397,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1 + prob2) { // corruption windup if (random < prob1 + prob2) { // corruption windup
int skillID = (int)mob->data["m_iCorruptionType"]; int skillID = (int)mob->data["m_iCorruptionType"];
INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt); INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->id;
pkt.iSkillID = skillID; pkt.iSkillID = skillID;
pkt.iValue1 = plr->x; pkt.iValue1 = plr->x;
pkt.iValue2 = plr->y; pkt.iValue2 = plr->y;
@ -381,7 +416,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1 + prob2 + prob3) { // eruption windup if (random < prob1 + prob2 + prob3) { // eruption windup
int skillID = (int)mob->data["m_iMegaType"]; int skillID = (int)mob->data["m_iMegaType"];
INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt); INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->id;
pkt.iSkillID = skillID; pkt.iSkillID = skillID;
pkt.iValue1 = mob->hitX = plr->x; pkt.iValue1 = mob->hitX = plr->x;
pkt.iValue2 = mob->hitY = plr->y; pkt.iValue2 = mob->hitY = plr->y;
@ -395,95 +430,58 @@ static void useAbilities(Mob *mob, time_t currTime) {
return; return;
} }
void MobAI::enterCombat(CNSocket *sock, Mob *mob) { void MobAI::incNextMovement(Mob* mob, time_t currTime) {
mob->target = sock; if (currTime == 0)
mob->state = MobState::COMBAT; currTime = getTime();
mob->nextMovement = getTime();
mob->nextAttack = 0;
mob->roamX = mob->x; int delay = (int)mob->data["m_iDelayTime"] * 1000;
mob->roamY = mob->y; mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2);
mob->roamZ = mob->z;
int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive
std::vector<int> targetData = {1, mob->appearanceData.iNPC_ID, 0, 0, 0};
for (auto& pwr : Combat::MobPowers)
if (pwr.skillType == Nanos::SkillTable[skillID].skillType)
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]);
for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT
if (event.trigger == ON_COMBAT && event.npcType == mob->appearanceData.iNPCType)
event.handler(sock, mob);
} }
static void drainMobHP(Mob *mob, int amount) { void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); Mob* self = (Mob*)npc;
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->appearanceData.iNPC_ID;
pkt->eCT = 4; // mob
pkt->iTB_ID = ECSB_BOUNDINGBALL;
drain->eCT = 4;
drain->iID = mob->appearanceData.iNPC_ID;
drain->iDamage = amount;
drain->iHP = mob->appearanceData.iHP -= amount;
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
if (mob->appearanceData.iHP <= 0)
Combat::killMob(mob->target, mob);
}
static void deadStep(Mob *mob, time_t currTime) {
// despawn the mob after a short delay // despawn the mob after a short delay
if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) { if (self->killedTime != 0 && !self->despawned && currTime - self->killedTime > 2000) {
mob->despawned = true; self->despawned = true;
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
// if it was summoned, mark it for removal // if it was summoned, mark it for removal
if (mob->summoned) { if (self->summoned) {
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
NPCManager::queueNPCRemoval(mob->appearanceData.iNPC_ID); NPCManager::queueNPCRemoval(self->id);
return; return;
} }
// pre-set spawn coordinates if not marked for removal // pre-set spawn coordinates if not marked for removal
mob->x = mob->spawnX; self->x = self->spawnX;
mob->y = mob->spawnY; self->y = self->spawnY;
mob->z = mob->spawnZ; self->z = self->spawnZ;
} }
// to guide their groupmates, group leaders still need to move despite being dead // to guide their groupmates, group leaders still need to move despite being dead
if (mob->groupLeader == mob->appearanceData.iNPC_ID) if (self->groupLeader == self->id)
roamingStep(mob, currTime); roamingStep(self, currTime);
if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100)
return; return;
std::cout << "respawning mob " << mob->appearanceData.iNPC_ID << " with HP = " << mob->maxHealth << std::endl; std::cout << "respawning mob " << self->id << " with HP = " << self->maxHealth << std::endl;
mob->appearanceData.iHP = mob->maxHealth; self->transition(AIState::ROAMING, self->id);
mob->state = MobState::ROAMING;
// if mob is a group leader/follower, spawn where the group is. // if mob is a group leader/follower, spawn where the group is.
if (mob->groupLeader != 0) { if (self->groupLeader != 0) {
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { if (NPCManager::NPCs.find(self->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[self->groupLeader]->kind == EntityKind::MOB) {
Mob* leaderMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leaderMob = (Mob*)NPCManager::NPCs[self->groupLeader];
mob->x = leaderMob->x + mob->offsetX; self->x = leaderMob->x + self->offsetX;
mob->y = leaderMob->y + mob->offsetY; self->y = leaderMob->y + self->offsetY;
mob->z = leaderMob->z; self->z = leaderMob->z;
} else { } else {
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl; std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
} }
@ -491,180 +489,147 @@ static void deadStep(Mob *mob, time_t currTime) {
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
pkt.NPCAppearanceData = mob->appearanceData; pkt.NPCAppearanceData = self->getAppearanceData();
pkt.NPCAppearanceData.iX = mob->x;
pkt.NPCAppearanceData.iY = mob->y;
pkt.NPCAppearanceData.iZ = mob->z;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
} }
static void combatStep(Mob *mob, time_t currTime) { void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
assert(mob->target != nullptr); Mob* self = (Mob*)npc;
assert(self->target != nullptr);
// lose aggro if the player lost connection // lose aggro if the player lost connection
if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) {
mob->target = nullptr; if (!MobAI::aggroCheck(self, getTime()))
mob->state = MobState::RETREAT; self->transition(AIState::RETREAT, self->target);
if (!aggroCheck(mob, currTime)) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
return; return;
} }
Player *plr = PlayerManager::getPlayer(mob->target); Player *plr = PlayerManager::getPlayer(self->target);
// lose aggro if the player became invulnerable or died // lose aggro if the player became invulnerable or died
if (plr->HP <= 0 if (plr->HP <= 0
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
mob->target = nullptr; if (!MobAI::aggroCheck(self, getTime()))
mob->state = MobState::RETREAT; self->transition(AIState::RETREAT, self->target);
if (!aggroCheck(mob, currTime)) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
return; return;
} }
// drain // tick buffs
if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) auto it = npc->buffs.begin();
&& mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) { while(it != npc->buffs.end()) {
drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second Buff* buff = (*it).second;
mob->lastDrainTime = currTime; buff->combatTick(currTime);
}
// if drain killed the mob, return early // if mob state changed, end the step
if (mob->appearanceData.iHP <= 0) if(self->state != AIState::COMBAT)
return; return;
// unbuffing buff->tick(currTime);
std::unordered_map<int32_t, time_t>::iterator it = mob->unbuffTimes.begin(); if(buff->isStale()) {
while (it != mob->unbuffTimes.end()) { // garbage collect
it = npc->buffs.erase(it);
if (currTime >= it->second) { delete buff;
mob->appearanceData.iConditionBitFlag &= ~it->first;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
it = mob->unbuffTimes.erase(it);
} else {
it++;
} }
else it++;
} }
// skip attack if stunned or asleep // skip attack if stunned or asleep
if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) { if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) {
mob->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;
} }
int distance = hypot(plr->x - mob->x, plr->y - mob->y); int distance = hypot(plr->x - self->x, plr->y - self->y);
int mobRange = (int)mob->data["m_iAtkRange"] + (int)mob->data["m_iRadius"]; int mobRange = (int)self->data["m_iAtkRange"] + (int)self->data["m_iRadius"];
if (currTime >= mob->nextAttack) { if (currTime >= self->nextAttack) {
if (mob->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. if (self->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
useAbilities(mob, currTime); useAbilities(self, currTime);
if (mob->target == nullptr) if (self->target == nullptr)
return; return;
} }
int distanceToTravel = INT_MAX; int distanceToTravel = INT_MAX;
int speed = mob->speed; 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 && mob->skillStyle == -1) { if (distance > mobRange && self->skillStyle == -1) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
mob->nextMovement = currTime + 400; self->nextMovement = currTime + 400;
if (currTime >= mob->nextAttack) if (currTime >= self->nextAttack)
mob->nextAttack = 0; self->nextAttack = 0;
// halve movement speed if snared // halve movement speed if snared
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) if (self->hasBuff(ECSB_DN_MOVE_SPEED))
speed /= 2; speed /= 2;
int targetX = plr->x; int targetX = plr->x;
int targetY = plr->y; int targetY = plr->y;
if (mob->groupLeader != 0) { if (self->groupLeader != 0) {
targetX += mob->offsetX*distance/(mob->idleRange + 1); targetX += self->offsetX*distance/(self->idleRange + 1);
targetY += mob->offsetY*distance/(mob->idleRange + 1); targetY += self->offsetY*distance/(self->idleRange + 1);
} }
distanceToTravel = std::min(distance-mobRange+1, speed*2/5); distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
auto targ = lerp(mob->x, mob->y, targetX, targetY, distanceToTravel); auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack) if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack)
mob->nextAttack = 0; self->nextAttack = 0;
NPCManager::updateNPCPosition(mob->appearanceData.iNPC_ID, targ.first, targ.second, mob->z, mob->instanceID, mob->appearanceData.iAngle); NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
pkt.iSpeed = speed; pkt.iSpeed = speed;
pkt.iToX = mob->x = targ.first; pkt.iToX = self->x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = self->y = targ.second;
pkt.iToZ = plr->z; pkt.iToZ = plr->z;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
} }
/* attack logic /* attack logic
* 2/5 represents 400 ms which is the time interval mobs use per movement logic step * 2/5 represents 400 ms which is the time interval mobs use per movement logic step
* if the mob is one move interval away, we should just start attacking anyways. * if the mob is one move interval away, we should just start attacking anyways.
*/ */
if (distance <= mobRange || distanceToTravel < speed*2/5) { if (distance <= mobRange || distanceToTravel < self->speed*2/5) {
if (mob->nextAttack == 0 || currTime >= mob->nextAttack) { if (self->nextAttack == 0 || currTime >= self->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; self->nextAttack = currTime + (int)self->data["m_iDelayTime"] * 100;
Combat::npcAttackPc(mob, currTime); Combat::npcAttackPc(self, currTime);
} }
} }
// retreat if the player leaves combat range // retreat if the player leaves combat range
int xyDistance = hypot(plr->x - mob->roamX, plr->y - mob->roamY); int xyDistance = hypot(plr->x - self->roamX, plr->y - self->roamY);
distance = hypot(xyDistance, plr->z - mob->roamZ); distance = hypot(xyDistance, plr->z - self->roamZ);
if (distance >= mob->data["m_iCombatRange"]) { if (distance >= self->data["m_iCombatRange"]) {
mob->target = nullptr; self->transition(AIState::RETREAT, self->target);
mob->state = MobState::RETREAT;
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
} }
} }
void MobAI::incNextMovement(Mob *mob, time_t currTime) { void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
if (currTime == 0) Mob* self = (Mob*)npc;
currTime = getTime();
int delay = (int)mob->data["m_iDelayTime"] * 1000;
mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2);
}
static void roamingStep(Mob *mob, time_t currTime) {
/* /*
* We reuse nextAttack to avoid scanning for players all the time, but to still * We reuse nextAttack to avoid scanning for players all the time, but to still
* do so more often than if we waited for nextMovement (which is way too slow). * do so more often than if we waited for nextMovement (which is way too slow).
* In the case of group leaders, this step will be called by dead mobs, so disable attack. * In the case of group leaders, this step will be called by dead mobs, so disable attack.
*/ */
if (mob->state != MobState::DEAD && (mob->nextAttack == 0 || currTime >= mob->nextAttack)) { if (self->state != AIState::DEAD && (self->nextAttack == 0 || currTime >= self->nextAttack)) {
mob->nextAttack = currTime + 500; self->nextAttack = currTime + 500;
if (aggroCheck(mob, currTime)) if (aggroCheck(self, currTime))
return; return;
} }
// no random roaming if the mob already has a set path // no random roaming if the mob already has a set path
if (mob->staticPath) if (self->staticPath)
return; return;
if (mob->groupLeader != 0 && mob->groupLeader != mob->appearanceData.iNPC_ID) // don't roam by yourself without group leader if (self->groupLeader != 0 && self->groupLeader != self->id) // don't roam by yourself without group leader
return; return;
/* /*
@ -672,142 +637,208 @@ static void roamingStep(Mob *mob, time_t currTime) {
* Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement), * Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
* so we don't have to check if there's already entries in the queue since we know there won't be. * so we don't have to check if there's already entries in the queue since we know there won't be.
*/ */
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
incNextMovement(mob, currTime); incNextMovement(self, currTime);
int xStart = mob->spawnX - mob->idleRange/2; int xStart = self->spawnX - self->idleRange/2;
int yStart = mob->spawnY - mob->idleRange/2; int yStart = self->spawnY - self->idleRange/2;
int speed = mob->speed;
// some mobs don't move (and we mustn't divide/modulus by zero) // some mobs don't move (and we mustn't divide/modulus by zero)
if (mob->idleRange == 0 || speed == 0) if (self->idleRange == 0 || self->speed == 0)
return; return;
int farX, farY, distance; int farX, farY, distance;
int minDistance = mob->idleRange / 2; int minDistance = self->idleRange / 2;
// pick a random destination // pick a random destination
farX = xStart + Rand::rand(mob->idleRange); farX = xStart + Rand::rand(self->idleRange);
farY = yStart + Rand::rand(mob->idleRange); farY = yStart + Rand::rand(self->idleRange);
distance = std::abs(std::max(farX - mob->x, farY - mob->y)); distance = std::abs(std::max(farX - self->x, farY - self->y));
if (distance == 0) if (distance == 0)
distance += 1; // hack to avoid FPE distance += 1; // hack to avoid FPE
// if it's too short a walk, go further in that direction // if it's too short a walk, go further in that direction
farX = mob->x + (farX - mob->x) * minDistance / distance; farX = self->x + (farX - self->x) * minDistance / distance;
farY = mob->y + (farY - mob->y) * minDistance / distance; farY = self->y + (farY - self->y) * minDistance / distance;
// but don't got out of bounds // but don't got out of bounds
farX = std::clamp(farX, xStart, xStart + mob->idleRange); farX = std::clamp(farX, xStart, xStart + self->idleRange);
farY = std::clamp(farY, yStart, yStart + mob->idleRange); farY = std::clamp(farY, yStart, yStart + self->idleRange);
// halve movement speed if snared // halve movement speed if snared
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) if (self->hasBuff(ECSB_DN_MOVE_SPEED))
speed /= 2; self->speed /= 2;
std::queue<Vec3> queue; std::queue<Vec3> queue;
Vec3 from = { mob->x, mob->y, mob->z }; Vec3 from = { self->x, self->y, self->z };
Vec3 to = { farX, farY, mob->z }; Vec3 to = { farX, farY, self->z };
// add a route to the queue; to be processed in Transport::stepNPCPathing() // add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, speed); Transport::lerp(&queue, from, to, self->speed);
Transport::NPCQueues[mob->appearanceData.iNPC_ID] = queue; Transport::NPCQueues[self->id] = queue;
if (mob->groupLeader != 0 && mob->groupLeader == mob->appearanceData.iNPC_ID) { if (self->groupLeader != 0 && self->groupLeader == self->id) {
// make followers follow this npc. // make followers follow this npc.
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mob->groupMember[i] == 0) if (self->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(self->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[self->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
std::queue<Vec3> queue2; std::queue<Vec3> queue2;
Mob* followerMob = (Mob*)NPCManager::NPCs[mob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[self->groupMember[i]];
from = { followerMob->x, followerMob->y, followerMob->z }; from = { followerMob->x, followerMob->y, followerMob->z };
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z };
Transport::lerp(&queue2, from, to, speed); Transport::lerp(&queue2, from, to, self->speed);
Transport::NPCQueues[followerMob->appearanceData.iNPC_ID] = queue2; Transport::NPCQueues[followerMob->id] = queue2;
} }
} }
} }
static void retreatStep(Mob *mob, time_t currTime) { void MobAI::retreatStep(CombatNPC* npc, time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) Mob* self = (Mob*)npc;
if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
mob->nextMovement = currTime + 400; self->nextMovement = currTime + 400;
// distance between spawn point and current location // distance between spawn point and current location
int distance = hypot(mob->x - mob->roamX, mob->y - mob->roamY); int distance = hypot(self->x - self->roamX, self->y - self->roamY);
//if (distance > mob->data["m_iIdleRange"]) { //if (distance > mob->data["m_iIdleRange"]) {
if (distance > 10) { if (distance > 10) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
auto targ = lerp(mob->x, mob->y, mob->roamX, mob->roamY, (int)mob->speed*4/5); auto targ = lerp(self->x, self->y, self->roamX, self->roamY, (int)self->speed*4/5);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
pkt.iSpeed = (int)mob->speed * 2; pkt.iSpeed = (int)self->speed * 2;
pkt.iToX = mob->x = targ.first; pkt.iToX = self->x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = self->y = targ.second;
pkt.iToZ = mob->z = mob->spawnZ; pkt.iToZ = self->z = self->spawnZ;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
} }
// if we got there // if we got there
//if (distance <= mob->data["m_iIdleRange"]) { //if (distance <= mob->data["m_iIdleRange"]) {
if (distance <= 10) { // retreat back to the spawn point if (distance <= 10) { // retreat back to the spawn point
mob->state = MobState::ROAMING; self->transition(AIState::ROAMING, self->id);
mob->appearanceData.iHP = mob->maxHealth;
mob->killedTime = 0;
mob->nextAttack = 0;
mob->appearanceData.iConditionBitFlag = 0;
// cast a return home heal spell, this is the right way(tm)
std::vector<int> targetData = {1, 0, 0, 0, 0};
for (auto& pwr : Combat::MobPowers)
if (pwr.skillType == Nanos::SkillTable[110].skillType)
pwr.handle(mob, targetData, 110, Nanos::SkillTable[110].durationTime[0], Nanos::SkillTable[110].powerIntensity[0]);
// clear outlying debuffs
clearDebuff(mob);
} }
} }
void MobAI::step(CombatNPC *npc, time_t currTime) { void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
assert(npc->type == EntityType::MOB); Mob* self = (Mob*)npc;
auto mob = (Mob*)npc;
if (mob->playersInView < 0) self->hp = self->maxHealth;
std::cout << "[WARN] Weird playerview value " << mob->playersInView << std::endl; self->killedTime = 0;
self->nextAttack = 0;
// skip mob movement and combat if disabled or not in view // cast a return home heal spell, this is the right way(tm)
if ((!simulateMobs || mob->playersInView == 0) && mob->state != MobState::DEAD Abilities::useNPCSkill(npc->getRef(), 110, { npc });
&& mob->state != MobState::RETREAT)
// clear outlying debuffs
clearDebuff(self);
}
void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
assert(src.kind == EntityKind::PLAYER);
self->target = src.sock;
self->nextMovement = getTime();
self->nextAttack = 0;
self->roamX = self->x;
self->roamY = self->y;
self->roamZ = self->z;
int skillID = (int)self->data["m_iPassiveBuff"];
if(skillID != 0) // cast passive
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
}
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
self->target = nullptr;
MobAI::clearDebuff(self);
if (self->groupLeader != 0)
MobAI::groupRetreat(self);
}
void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
self->target = nullptr;
self->skillStyle = -1;
self->clearBuffs(true);
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
if (src.kind == EntityKind::PLAYER && src.isValid()) {
Player* plr = PlayerManager::getPlayer(src.sock);
Items::DropRoll rolled;
Items::DropRoll eventRolled;
std::map<int, int> qitemRolls;
std::vector<Player*> playerRefs;
if (plr->group == nullptr) {
playerRefs.push_back(plr);
Combat::genQItemRolls(playerRefs, qitemRolls);
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
Missions::mobKilled(src.sock, self->type, qitemRolls);
}
else {
auto players = plr->group->filter(EntityKind::PLAYER);
for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
Combat::genQItemRolls(playerRefs, qitemRolls);
for (int i = 0; i < players.size(); i++) {
CNSocket* sockTo = players[i].sock;
Player* otherPlr = PlayerManager::getPlayer(sockTo);
// only contribute to group members' kills if they're close enough
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
if (dist > 5000)
continue;
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
Missions::mobKilled(sockTo, self->type, qitemRolls);
}
}
}
// delay the despawn animation
self->despawned = false;
auto it = Transport::NPCQueues.find(self->id);
if (it == Transport::NPCQueues.end() || it->second.empty())
return; return;
switch (mob->state) { // rewind or empty the movement queue
case MobState::INACTIVE: if (self->staticPath) {
// no-op /*
break; * This is inelegant, but we wind forward in the path until we find the point that
case MobState::ROAMING: * corresponds with the Mob's spawn point.
roamingStep(mob, currTime); *
break; * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
case MobState::COMBAT: */
combatStep(mob, currTime); auto& queue = it->second;
break; for (auto point = queue.front(); point.x != self->spawnX || point.y != self->spawnY; point = queue.front()) {
case MobState::RETREAT: queue.pop();
retreatStep(mob, currTime); queue.push(point);
break; }
case MobState::DEAD: }
deadStep(mob, currTime); else {
break; Transport::NPCQueues.erase(self->id);
} }
} }

View File

@ -1,26 +1,26 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "NPCManager.hpp" #include "JSON.hpp"
enum class MobState { #include "Entities.hpp"
INACTIVE,
ROAMING, #include <unordered_map>
COMBAT, #include <string>
RETREAT,
DEAD
};
namespace MobAI { namespace MobAI {
// needs to be declared before Mob's constructor void deadStep(CombatNPC* self, time_t currTime);
void step(CombatNPC*, time_t); void combatStep(CombatNPC* self, time_t currTime);
}; void roamingStep(CombatNPC* self, time_t currTime);
void retreatStep(CombatNPC* self, time_t currTime);
void onRoamStart(CombatNPC* self, EntityRef src);
void onCombatStart(CombatNPC* self, EntityRef src);
void onRetreat(CombatNPC* self, EntityRef src);
void onDeath(CombatNPC* self, EntityRef src);
}
struct Mob : public CombatNPC { struct Mob : public CombatNPC {
// general
MobState state = MobState::INACTIVE;
std::unordered_map<int32_t,time_t> unbuffTimes = {};
// dead // dead
time_t killedTime = 0; time_t killedTime = 0;
@ -47,16 +47,13 @@ struct Mob : public CombatNPC {
int offsetX = 0, offsetY = 0; int offsetX = 0, offsetY = 0;
int groupMember[4] = {}; int groupMember[4] = {};
// for optimizing away AI in empty chunks
int playersInView = 0;
// temporary; until we're sure what's what // temporary; until we're sure what's what
nlohmann::json data = {}; nlohmann::json data = {};
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
sightRange(d["m_iSightRange"]) { sightRange(d["m_iSightRange"]) {
state = MobState::ROAMING; state = AIState::ROAMING;
data = d; data = d;
@ -65,20 +62,28 @@ struct Mob : public CombatNPC {
idleRange = (int)data["m_iIdleRange"]; idleRange = (int)data["m_iIdleRange"];
level = data["m_iNpcLevel"]; level = data["m_iNpcLevel"];
roamX = spawnX = x; roamX = x;
roamY = spawnY = y; roamY = y;
roamZ = spawnZ = z; roamZ = z;
offsetX = 0; offsetX = 0;
offsetY = 0; offsetY = 0;
appearanceData.iConditionBitFlag = 0;
// NOTE: there appear to be discrepancies in the dump // NOTE: there appear to be discrepancies in the dump
appearanceData.iHP = maxHealth; hp = maxHealth;
type = EntityType::MOB; kind = EntityKind::MOB;
_stepAI = MobAI::step;
// AI
stateHandlers[AIState::DEAD] = MobAI::deadStep;
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
} }
// constructor for /summon // constructor for /summon
@ -89,6 +94,9 @@ struct Mob : public CombatNPC {
~Mob() {} ~Mob() {}
virtual int takeDamage(EntityRef src, int amt) override;
virtual void step(time_t currTime) override;
auto operator[](std::string s) { auto operator[](std::string s) {
return data[s]; return data[s];
} }
@ -103,5 +111,4 @@ namespace MobAI {
void clearDebuff(Mob *mob); void clearDebuff(Mob *mob);
void followToCombat(Mob *mob); void followToCombat(Mob *mob);
void groupRetreat(Mob *mob); void groupRetreat(Mob *mob);
void enterCombat(CNSocket *sock, Mob *mob);
} }

View File

@ -1,4 +0,0 @@
#pragma once
#include "Chunking.hpp"
#include "Entities.hpp"

View File

@ -1,4 +1,8 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "Combat.hpp" #include "Combat.hpp"
@ -20,8 +24,6 @@
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include "JSON.hpp"
using namespace NPCManager; using namespace NPCManager;
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs; std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
@ -67,7 +69,7 @@ void NPCManager::destroyNPC(int32_t id) {
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) { void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
BaseNPC* npc = NPCs[id]; BaseNPC* npc = NPCs[id];
npc->appearanceData.iAngle = angle; npc->angle = angle;
ChunkPos oldChunk = npc->chunkPos; ChunkPos oldChunk = npc->chunkPos;
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I); ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
npc->x = X; npc->x = X;
@ -79,11 +81,11 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I,
Chunking::updateEntityChunk({id}, oldChunk, newChunk); Chunking::updateEntityChunk({id}, oldChunk, newChunk);
} }
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) { void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) {
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) { for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
ref.sock->sendPacket(buf, type, size); ref.sock->sendPacket(buf, type, size);
} }
} }
@ -122,7 +124,6 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
// type must already be checked and updateNPCPosition() must be called on the result // type must already be checked and updateNPCPosition() must be called on the result
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) { BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
uint64_t inst = baseInstance ? MAPNUM(instance) : instance; uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
#define EXTRA_HEIGHT 0
//assert(nextId < INT32_MAX); //assert(nextId < INT32_MAX);
int id = nextId--; int id = nextId--;
@ -130,12 +131,12 @@ BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type,
BaseNPC *npc = nullptr; BaseNPC *npc = nullptr;
if (team == 2) { if (team == 2) {
npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id); npc = new Mob(x, y, z, inst, type, NPCData[type], id);
// re-enable respawning, if desired // re-enable respawning, if desired
((Mob*)npc)->summoned = !respawn; ((Mob*)npc)->summoned = !respawn;
} else } else
npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id); npc = new BaseNPC(0, inst, type, id);
NPCs[id] = npc; NPCs[id] = npc;
@ -154,7 +155,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
for (int i = 0; i < req->iNPCCnt; i++) { for (int i = 0; i < req->iNPCCnt; i++) {
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType); BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0); updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0);
} }
} }
@ -183,9 +184,12 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
if (Warps[warpId].isInstance) { if (Warps[warpId].isInstance) {
uint64_t instanceID = Warps[warpId].instanceID; uint64_t instanceID = Warps[warpId].instanceID;
Player* leader = plr;
if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock);
// if warp requires you to be on a mission, it's gotta be a unique instance // if warp requires you to be on a mission, it's gotta be a unique instance
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID
Chunking::createInstance(instanceID); Chunking::createInstance(instanceID);
// save Lair entrance coords as a pseudo-Resurrect 'Em // save Lair entrance coords as a pseudo-Resurrect 'Em
@ -195,14 +199,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
plr->recallInstance = instanceID; plr->recallInstance = instanceID;
} }
if (plr->iID == plr->iIDGroup && plr->groupCnt == 1) if (plr->group == nullptr)
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID); PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
else { else {
Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); auto players = plr->group->filter(EntityKind::PLAYER);
for (int i = 0; i < players.size(); i++) {
for (int i = 0; i < leaderPlr->groupCnt; i++) { CNSocket* sockTo = players[i].sock;
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]); Player* otherPlr = PlayerManager::getPlayer(sockTo);
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
if (otherPlr == nullptr || sockTo == nullptr) if (otherPlr == nullptr || sockTo == nullptr)
continue; continue;
@ -276,7 +279,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
Chunk* chunk = *c; Chunk* chunk = *c;
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) { for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
if (ent->type == EntityType::PLAYER) if (ent->kind == EntityKind::PLAYER)
continue; continue;
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity(); BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
@ -301,20 +304,20 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
std::cout << "Lord Fuse stage two" << std::endl; std::cout << "Lord Fuse stage two" << std::endl;
// Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData* // Fuse doesn't move
// Blastons, Heal // Blastons, Heal
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
// right arm, Adaptium, Stun // right arm, Adaptium, Stun
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
} }
// summon left arm and stage 3 body // summon left arm and stage 3 body
@ -325,18 +328,18 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
std::cout << "Lord Fuse stage three" << std::endl; std::cout << "Lord Fuse stage three" << std::endl;
// Cosmix, Damage Point // Cosmix, Damage Point
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
// Blastons, Heal // Blastons, Heal
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
} }
std::vector<NPCEvent> NPCManager::NPCEvents = { std::vector<NPCEvent> NPCManager::NPCEvents = {
@ -352,11 +355,11 @@ void NPCManager::queueNPCRemoval(int32_t id) {
static void step(CNServer *serv, time_t currTime) { static void step(CNServer *serv, time_t currTime) {
for (auto& pair : NPCs) { for (auto& pair : NPCs) {
if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB) if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
continue; continue;
auto npc = (CombatNPC*)pair.second; auto npc = (CombatNPC*)pair.second;
npc->stepAI(currTime); npc->step(currTime);
} }
// deallocate all NPCs queued for removal // deallocate all NPCs queued for removal
@ -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, 200); REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
} }

View File

@ -1,15 +1,16 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "PlayerManager.hpp"
#include "NPC.hpp"
#include "Transport.hpp"
#include "JSON.hpp" #include "JSON.hpp"
#include "Transport.hpp"
#include "Chunking.hpp"
#include "Entities.hpp"
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <set>
#define RESURRECT_HEIGHT 400 #define RESURRECT_HEIGHT 400
@ -29,8 +30,6 @@ struct NPCEvent {
: npcType(t), trigger(tr), handler(hndlr) {} : npcType(t), trigger(tr), handler(hndlr) {}
}; };
struct WarpLocation;
namespace NPCManager { namespace NPCManager {
extern std::unordered_map<int32_t, BaseNPC*> NPCs; extern std::unordered_map<int32_t, BaseNPC*> NPCs;
extern std::map<int32_t, WarpLocation> Warps; extern std::map<int32_t, WarpLocation> Warps;
@ -44,7 +43,7 @@ namespace NPCManager {
void destroyNPC(int32_t); void destroyNPC(int32_t);
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle); void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size); void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size);
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false); BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);

View File

@ -1,11 +1,11 @@
#include "servers/CNShardServer.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Combat.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Groups.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "NPCManager.hpp"
#include <cmath> #include <cmath>
@ -82,40 +82,21 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0) if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
return; // prevent powerless nanos from summoning return; // prevent powerless nanos from summoning
plr->nanoDrainRate = 0;
int16_t skillID = plr->Nanos[plr->activeNano].iSkillID;
// passive nano unbuffing
if (SkillTable[skillID].drainType == 2) {
std::vector<int> targetData = findTargets(plr, skillID);
for (auto& pwr : NanoPowers)
if (pwr.skillType == SkillTable[skillID].skillType)
nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3));
}
if (nanoID >= NANO_COUNT || nanoID < 0) if (nanoID >= NANO_COUNT || nanoID < 0)
return; // sanity check return; // sanity check
plr->activeNano = nanoID; plr->activeNano = nanoID;
skillID = plr->Nanos[nanoID].iSkillID; sNano& nano = plr->Nanos[nanoID];
// passive nano buffing SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
if (SkillTable[skillID].drainType == 2) { ? &Abilities::SkillTable[nano.iSkillID] : nullptr;
std::vector<int> targetData = findTargets(plr, skillID); if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
// passive buff effect
int boost = 0; resp.eCSTB___Add = 1;
if (getNanoBoost(plr)) ICombatant* src = dynamic_cast<ICombatant*>(plr);
boost = 1; int32_t targets[] = { plr->iID };
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
for (auto& pwr : NanoPowers) { Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
if (pwr.skillType == SkillTable[skillID].skillType) {
resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3];
pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]);
}
}
} }
if (!silent) // silent nano death but only for the summoning player if (!silent) // silent nano death but only for the summoning player
@ -124,7 +105,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent. // Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1); INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
pkt1.iPC_ID = plr->iID; pkt1.iPC_ID = plr->iID;
pkt1.Nano = plr->Nanos[nanoID]; pkt1.Nano = nano;
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE); PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
} }
@ -211,7 +192,7 @@ int Nanos::nanoStyle(int nanoID) {
bool Nanos::getNanoBoost(Player* plr) { bool Nanos::getNanoBoost(Player* plr) {
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
if (plr->equippedNanos[i] == plr->activeNano) if (plr->equippedNanos[i] == plr->activeNano)
if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i)) if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
return true; return true;
return false; return false;
} }
@ -235,18 +216,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
// Update player // Update player
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID; plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
// Unbuff gumballs
int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum;
if (plr->iConditionBitFlag & value1) {
int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum;
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
pkt.eCSTB = value2; // eCharStatusTimeBuffID
pkt.eTBU = 2; // eTimeBuffUpdate
pkt.eTBT = 1; // eTimeBuffType 1 means nano
pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1;
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
}
// unsummon nano if replaced // unsummon nano if replaced
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
summonNano(sock, -1); summonNano(sock, -1);
@ -289,28 +258,26 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
int16_t nanoID = plr->activeNano; // validate request check
int16_t skillID = plr->Nanos[nanoID].iSkillID; sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
return;
}
sNano& nano = plr->Nanos[plr->activeNano];
int16_t skillID = nano.iSkillID;
SkillData* skillData = &Abilities::SkillTable[skillID];
DEBUGLOG( DEBUGLOG(
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
) )
std::vector<int> targetData = findTargets(plr, skillID, data); ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
Abilities::useNanoSkill(sock, skillData, nano, targetData);
int boost = 0; if (plr->Nanos[plr->activeNano].iStamina <= 0)
if (getNanoBoost(plr))
boost = 1;
plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3];
if (plr->Nanos[plr->activeNano].iStamina < 0)
plr->Nanos[plr->activeNano].iStamina = 0;
for (auto& pwr : NanoPowers)
if (pwr.skillType == SkillTable[skillID].skillType)
pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]);
if (plr->Nanos[plr->activeNano].iStamina < 0)
summonNano(sock, -1); summonNano(sock, -1);
} }

View File

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <set> #include "core/Core.hpp"
#include <vector>
#include "Player.hpp" #include "Player.hpp"
#include "servers/CNShardServer.hpp" #include "Abilities.hpp"
#include <map>
struct NanoData { struct NanoData {
int style; int style;

View File

@ -1,17 +1,21 @@
#pragma once #pragma once
#include <string>
#include <cstring>
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Chunking.hpp"
#include "Entities.hpp" #include "Entities.hpp"
#include "Groups.hpp"
#include <vector>
/* forward declaration(s) */
class Buff;
struct BuffStack;
#define ACTIVE_MISSION_COUNT 6 #define ACTIVE_MISSION_COUNT 6
#define PC_MAXHEALTH(level) (925 + 75 * (level)) #define PC_MAXHEALTH(level) (925 + 75 * (level))
struct Player : public Entity { struct Player : public Entity, public ICombatant {
int accountId = 0; int accountId = 0;
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
int32_t iID = 0; int32_t iID = 0;
@ -32,9 +36,8 @@ struct Player : public Entity {
int8_t iPCState = 0; int8_t iPCState = 0;
int32_t iWarpLocationFlag = 0; int32_t iWarpLocationFlag = 0;
int64_t aSkywayLocationFlag[2] = {}; int64_t aSkywayLocationFlag[2] = {};
int32_t iConditionBitFlag = 0;
int32_t iSelfConditionBitFlag = 0;
int8_t iSpecialState = 0; int8_t iSpecialState = 0;
std::unordered_map<int, Buff*> buffs = {};
int angle = 0; int angle = 0;
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0; int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
@ -49,7 +52,6 @@ struct Player : public Entity {
bool inCombat = false; bool inCombat = false;
bool onMonkey = false; bool onMonkey = false;
int nanoDrainRate = 0;
int healCooldown = 0; int healCooldown = 0;
int pointDamage = 0; int pointDamage = 0;
@ -65,10 +67,7 @@ struct Player : public Entity {
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {}; sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
int32_t iIDGroup = 0; Group* group = nullptr;
int groupCnt = 0;
int32_t groupIDs[4] = {};
int32_t iGroupConditionBitFlag = 0;
bool notify = false; bool notify = false;
bool hidden = false; bool hidden = false;
@ -85,8 +84,31 @@ struct Player : public Entity {
time_t lastShot = 0; time_t lastShot = 0;
std::vector<sItemBase> buyback = {}; std::vector<sItemBase> buyback = {};
Player() { type = EntityType::PLAYER; } Player() { kind = EntityKind::PLAYER; }
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(int buffId) override;
virtual void removeBuff(int buffId, BuffClass buffClass) override;
virtual void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() override;
virtual int takeDamage(EntityRef src, int amt) override;
virtual int heal(EntityRef src, int amt) override;
virtual bool isAlive() override;
virtual int getCurrentHP() override;
virtual int getMaxHP() override;
virtual int getLevel() override;
virtual std::vector<EntityRef> getGroupMembers() override;
virtual int32_t getCharType() override;
virtual int32_t getID() override;
virtual EntityRef getRef() override;
virtual void step(time_t currTime) override;
sNano* getActiveNano();
sPCAppearanceData getAppearanceData();
}; };

View File

@ -1,25 +1,20 @@
#include "core/Core.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "db/Database.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Missions.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
#include "Groups.hpp"
#include "Chat.hpp"
#include "Buddies.hpp"
#include "Combat.hpp" #include "Combat.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "BuiltinCommands.hpp"
#include "Abilities.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "Missions.hpp"
#include "settings.hpp" #include "Chat.hpp"
#include "Items.hpp"
#include "Buddies.hpp"
#include "BuiltinCommands.hpp"
#include <assert.h> #include <assert.h>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include <cmath> #include <cmath>
@ -41,7 +36,13 @@ void PlayerManager::removePlayer(CNSocket* key) {
Player* plr = getPlayer(key); Player* plr = getPlayer(key);
uint64_t fromInstance = plr->instanceID; uint64_t fromInstance = plr->instanceID;
Groups::groupKickPlayer(plr); // free buff memory
for(auto buffEntry : plr->buffs)
delete buffEntry.second;
// leave group
if(plr->group != nullptr)
Groups::groupKick(plr->group, key);
// remove player's bullets // remove player's bullets
Combat::Bullets.erase(plr->iID); Combat::Bullets.erase(plr->iID);
@ -65,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) {
// if the player was in a lair, clean it up // if the player was in a lair, clean it up
Chunking::destroyInstanceIfEmpty(fromInstance); Chunking::destroyInstanceIfEmpty(fromInstance);
// remove player's buffs from the server
auto it = Eggs::EggBuffs.begin();
while (it != Eggs::EggBuffs.end()) {
if (it->first.first == key) {
it = Eggs::EggBuffs.erase(it);
}
else
it++;
}
std::cout << players.size() << " players" << std::endl; std::cout << players.size() << " players" << std::endl;
} }
@ -232,8 +223,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
Database::getPlayer(plr, lm->playerId); Database::getPlayer(plr, lm->playerId);
} }
plr->groupCnt = 1; plr->group = nullptr;
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
response.iID = plr->iID; response.iID = plr->iID;
response.uiSvrTime = getTime(); response.uiSvrTime = getTime();
@ -348,12 +338,20 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
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++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
if (ref.type != EntityType::PLAYER || ref.sock == sock) if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
continue; continue;
ref.sock->sendPacket(buf, type, size); ref.sock->sendPacket(buf, type, size);
@ -406,21 +404,22 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
int activeSlot = -1; int activeSlot = -1;
bool move = false; bool move = false;
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) { switch ((ePCRegenType)reviveData->iRegenType) {
// nano revive case ePCRegenType::HereByPhoenix: // nano revive
if (!(plr->hasBuff(ECSB_PHOENIX)))
return; // sanity check
plr->Nanos[plr->activeNano].iStamina = 0; plr->Nanos[plr->activeNano].iStamina = 0;
// fallthrough
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); break;
} else if (reviveData->iRegenType == 4) {
// revived by group member's nano default: // plain respawn
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
} else if (reviveData->iRegenType == 5) { // fallthrough
// warp away case ePCRegenType::Unstick: // warp away
move = true; move = true;
} else { break;
// plain respawn
move = true;
plr->HP = PC_MAXHEALTH(plr->level) / 2;
} }
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
@ -472,11 +471,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
resp2.PCRegenDataForOtherPC.iHP = plr->HP; resp2.PCRegenDataForOtherPC.iHP = plr->HP;
resp2.PCRegenDataForOtherPC.iAngle = plr->angle; resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
Player *otherPlr = getPlayerFromID(plr->iIDGroup); if (plr->group != nullptr) {
if (otherPlr != nullptr) {
int bitFlag = Groups::getGroupFlags(otherPlr);
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState; resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano]; resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
@ -667,16 +664,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last
} }
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) { CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
switch (by) { switch ((eCN_GM_TargetSearchBy)by) {
case eCN_GM_TargetSearchBy__PC_ID: case eCN_GM_TargetSearchBy::PC_ID:
assert(id != 0); assert(id != 0);
return getSockFromID(id); return getSockFromID(id);
case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id
assert(uid != 0); assert(uid != 0);
for (auto& pair : players) for (auto& pair : players)
if (pair.second->accountId == uid) if (pair.second->accountId == uid)
return pair.first; return pair.first;
case eCN_GM_TargetSearchBy__PC_Name: case eCN_GM_TargetSearchBy::PC_Name:
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names? assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
return getSockFromName(firstname, lastname); return getSockFromName(firstname, lastname);
} }

View File

@ -1,16 +1,15 @@
#pragma once #pragma once
#include "Player.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include "Chunking.hpp"
#include <utility> #include "Player.hpp"
#include "Chunking.hpp"
#include "Transport.hpp"
#include "Entities.hpp"
#include <map> #include <map>
#include <list> #include <list>
struct WarpLocation;
namespace PlayerManager { namespace PlayerManager {
extern std::map<CNSocket*, Player*> players; extern std::map<CNSocket*, Player*> players;
void init(); void init();
@ -34,6 +33,7 @@ namespace PlayerManager {
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname); CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
WarpLocation *getRespawnPoint(Player *plr); WarpLocation *getRespawnPoint(Player *plr);
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size); void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
// TODO: unify this under the new Entity system // TODO: unify this under the new Entity system
@ -43,7 +43,7 @@ namespace PlayerManager {
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
if (ref.type != EntityType::PLAYER || ref.sock == sock) if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
continue; continue;
ref.sock->sendPacket(pkt, type); ref.sock->sendPacket(pkt, type);

View File

@ -1,4 +1,7 @@
#include "PlayerMovement.hpp" #include "PlayerMovement.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
@ -33,7 +36,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
// [gruntwork] check if player has a follower and move it // [gruntwork] check if player has a follower and move it
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) { if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first; BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points Transport::NPCQueues.erase(follower->id); // erase existing points
std::queue<Vec3> queue; std::queue<Vec3> queue;
Vec3 from = { follower->x, follower->y, follower->z }; Vec3 from = { follower->x, follower->y, follower->z };
float drag = 0.95f; // this ensures that they don't bump into the player float drag = 0.95f; // this ensures that they don't bump into the player
@ -45,7 +48,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
// add a route to the queue; to be processed in Transport::stepNPCPathing() // add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue; Transport::NPCQueues[follower->id] = queue;
} }
} }

View File

@ -1,10 +1,12 @@
#include "servers/CNShardServer.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "db/Database.hpp"
#include "servers/CNShardServer.hpp"
#include "NPCManager.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "db/Database.hpp"
#include "NPCManager.hpp"
using namespace Racing; using namespace Racing;

View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include <set> #include "core/Core.hpp"
#include "servers/CNShardServer.hpp" #include <map>
#include <vector>
#include <set>
struct EPInfo { struct EPInfo {
int zoneX, zoneY, EPID, maxScore, maxTime; int zoneX, zoneY, EPID, maxScore, maxTime;

View File

@ -2,6 +2,7 @@
#include <random> #include <random>
#include <memory> #include <memory>
#include <vector>
namespace Rand { namespace Rand {
extern std::unique_ptr<std::mt19937> generator; extern std::unique_ptr<std::mt19937> generator;

View File

@ -1,18 +1,14 @@
#include "TableData.hpp" #include "TableData.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Transport.hpp"
#include "Items.hpp"
#include "settings.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Chunking.hpp" #include "Items.hpp"
#include "Nanos.hpp"
#include "Racing.hpp"
#include "Vendors.hpp" #include "Vendors.hpp"
#include "Racing.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "JSON.hpp"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <cmath> #include <cmath>
@ -231,18 +227,34 @@ static void loadXDT(json& xdtData) {
// load nano powers // load nano powers
json skills = xdtData["m_pSkillTable"]["m_pSkillData"]; json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) { for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) {
auto skills = _skills.value(); auto skill = _skill.value();
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] }; SkillData skillData = {
skill["m_iSkillType"],
skill["m_iEffectTarget"],
skill["m_iEffectType"],
skill["m_iTargetType"],
skill["m_iBatteryDrainType"],
skill["m_iEffectArea"]
};
skillData.valueTypes[0] = skill["m_iValueA_Type"];
skillData.valueTypes[1] = skill["m_iValueB_Type"];
skillData.valueTypes[2] = skill["m_iValueC_Type"];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i]; skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i];
skillData.durationTime[i] = skills["m_iDurationTime"][i]; skillData.durationTime[i] = skill["m_iDurationTime"][i];
skillData.powerIntensity[i] = skills["m_iValueA"][i];
skillData.values[0][i] = skill["m_iValueA"][i];
skillData.values[1][i] = skill["m_iValueB"][i];
skillData.values[2][i] = skill["m_iValueC"][i];
} }
Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData;
Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData;
} }
std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl; std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl;
// load EP data // load EP data
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"]; json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
@ -314,10 +326,10 @@ static void loadPaths(json& pathData, int32_t* nextId) {
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
passedDistance -= SLIDER_GAP_SIZE; // step down passedDistance -= SLIDER_GAP_SIZE; // step down
// spawn a slider // spawn a slider
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--); Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--);
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider; NPCManager::NPCs[slider->id] = slider;
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0);
Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route; Transport::NPCQueues[slider->id] = route;
} }
// rotate // rotate
route.pop(); route.pop();
@ -659,7 +671,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
int id = (*nextId)--; int id = (*nextId)--;
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
NPCManager::NPCs[id] = addEgg; NPCManager::NPCs[id] = addEgg;
eggCount++; eggCount++;
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
@ -755,7 +767,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID]; BaseNPC* npc = NPCManager::NPCs[npcID];
npc->appearanceData.iAngle = angle; npc->angle = angle;
RunningNPCRotations[npcID] = angle; RunningNPCRotations[npcID] = angle;
} }
@ -768,8 +780,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID]; BaseNPC* npc = NPCManager::NPCs[npcID];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
npc->z, instanceID, npc->appearanceData.iAngle); npc->z, instanceID, npc->angle);
RunningNPCMapNumbers[npcID] = instanceID; RunningNPCMapNumbers[npcID] = instanceID;
} }
@ -791,12 +803,12 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
// re-enable respawning // re-enable respawning
((Mob*)npc)->summoned = false; ((Mob*)npc)->summoned = false;
} else { } else {
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id); npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id);
} }
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; NPCManager::NPCs[npc->id] = npc;
RunningMobs[npc->appearanceData.iNPC_ID] = npc; RunningMobs[npc->id] = npc;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
} }
// mob groups // mob groups
@ -840,7 +852,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; tmpFol->groupLeader = tmp->id;
tmp->groupMember[followerCount++] = *nextId; tmp->groupMember[followerCount++] = *nextId;
(*nextId)--; (*nextId)--;
@ -850,7 +862,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n"; std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
} }
RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running RunningGroups[tmp->id] = tmp; // store as running
} }
auto eggs = gruntwork["eggs"]; auto eggs = gruntwork["eggs"];
@ -859,7 +871,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
int id = (*nextId)--; int id = (*nextId)--;
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
NPCManager::NPCs[id] = addEgg; NPCManager::NPCs[id] = addEgg;
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
RunningEggs[id] = addEgg; RunningEggs[id] = addEgg;
@ -893,7 +905,7 @@ static void loadNPCs(json& npcData) {
if (npc["iX"] > 512000 && npc["iY"] < 256000) if (npc["iX"] > 512000 && npc["iY"] < 256000)
continue; continue;
#endif #endif
BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID); BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID);
NPCManager::NPCs[npcID] = tmp; NPCManager::NPCs[npcID] = tmp;
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]); NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
@ -1003,7 +1015,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; tmpFol->groupLeader = tmp->id;
tmp->groupMember[followerCount++] = *nextId; tmp->groupMember[followerCount++] = *nextId;
(*nextId)--; (*nextId)--;
@ -1226,7 +1238,7 @@ void TableData::flush() {
continue; continue;
int x, y, z; int x, y, z;
if (npc->type == EntityType::MOB) { if (npc->kind == EntityKind::MOB) {
Mob *m = (Mob*)npc; Mob *m = (Mob*)npc;
x = m->spawnX; x = m->spawnX;
y = m->spawnY; y = m->spawnY;
@ -1238,13 +1250,13 @@ void TableData::flush() {
} }
// NOTE: this format deviates slightly from the one in mobs.json // NOTE: this format deviates slightly from the one in mobs.json
mob["iNPCType"] = (int)npc->appearanceData.iNPCType; mob["iNPCType"] = (int)npc->type;
mob["iX"] = x; mob["iX"] = x;
mob["iY"] = y; mob["iY"] = y;
mob["iZ"] = z; mob["iZ"] = z;
mob["iMapNum"] = MAPNUM(npc->instanceID); mob["iMapNum"] = MAPNUM(npc->instanceID);
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
mob["iAngle"] = npc->appearanceData.iAngle; mob["iAngle"] = npc->angle;
// it's called mobs, but really it's everything // it's called mobs, but really it's everything
gruntwork["mobs"].push_back(mob); gruntwork["mobs"].push_back(mob);
@ -1259,19 +1271,19 @@ void TableData::flush() {
int x, y, z; int x, y, z;
std::vector<Mob*> followers; std::vector<Mob*> followers;
if (npc->type == EntityType::MOB) { if (npc->kind == EntityKind::MOB) {
Mob* m = (Mob*)npc; Mob* m = (Mob*)npc;
x = m->spawnX; x = m->spawnX;
y = m->spawnY; y = m->spawnY;
z = m->spawnZ; z = m->spawnZ;
if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader if (m->groupLeader != m->id) { // make sure this is a leader
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n"; std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
continue; continue;
} }
// add follower data to vector; go until OOB or until follower ID is 0 // add follower data to vector; go until OOB or until follower ID is 0
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) { for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n"; std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
continue; continue;
} }
@ -1285,13 +1297,13 @@ void TableData::flush() {
} }
// NOTE: this format deviates slightly from the one in mobs.json // NOTE: this format deviates slightly from the one in mobs.json
mob["iNPCType"] = (int)npc->appearanceData.iNPCType; mob["iNPCType"] = (int)npc->type;
mob["iX"] = x; mob["iX"] = x;
mob["iY"] = y; mob["iY"] = y;
mob["iZ"] = z; mob["iZ"] = z;
mob["iMapNum"] = MAPNUM(npc->instanceID); mob["iMapNum"] = MAPNUM(npc->instanceID);
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
mob["iAngle"] = npc->appearanceData.iAngle; mob["iAngle"] = npc->angle;
// followers // followers
while (followers.size() > 0) { while (followers.size() > 0) {
@ -1300,7 +1312,7 @@ void TableData::flush() {
// populate JSON entry // populate JSON entry
json fol; json fol;
fol["iNPCType"] = follower->appearanceData.iNPCType; fol["iNPCType"] = follower->type;
fol["iOffsetX"] = follower->offsetX; fol["iOffsetX"] = follower->offsetX;
fol["iOffsetY"] = follower->offsetY; fol["iOffsetY"] = follower->offsetY;
@ -1325,7 +1337,7 @@ void TableData::flush() {
int mapnum = MAPNUM(npc->instanceID); int mapnum = MAPNUM(npc->instanceID);
if (mapnum != 0) if (mapnum != 0)
egg["iMapNum"] = mapnum; egg["iMapNum"] = mapnum;
egg["iType"] = npc->appearanceData.iNPCType; egg["iType"] = npc->type;
gruntwork["eggs"].push_back(egg); gruntwork["eggs"].push_back(egg);
} }

View File

@ -1,8 +1,13 @@
#pragma once #pragma once
#include <map> #include "JSON.hpp"
#include "NPCManager.hpp" #include "Entities.hpp"
#include "Transport.hpp"
#include <map>
#include <unordered_map>
#include <vector>
// these are added to the NPC's static key to avoid collisions // these are added to the NPC's static key to avoid collisions
const int NPC_ID_OFFSET = 1; const int NPC_ID_OFFSET = 1;

View File

@ -1,4 +1,7 @@
#include "Trading.hpp" #include "Trading.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "Items.hpp"
namespace Trading { namespace Trading {
void init(); void init();
} }

View File

@ -1,9 +1,12 @@
#include "Transport.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Transport.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "Combat.hpp" #include "Entities.hpp"
#include "NPCManager.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include <unordered_map> #include <unordered_map>
@ -257,13 +260,13 @@ static void stepNPCPathing() {
} }
// skip if not simulating mobs // skip if not simulating mobs
if (npc->type == EntityType::MOB && !MobAI::simulateMobs) { if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
it++; it++;
continue; continue;
} }
// do not roam if not roaming // do not roam if not roaming
if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
it++; it++;
continue; continue;
} }
@ -276,15 +279,15 @@ static void stepNPCPathing() {
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
// update NPC location to update viewables // update NPC location to update viewables
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
// TODO: move walking logic into Entity stack // TODO: move walking logic into Entity stack
switch (npc->type) { switch (npc->kind) {
case EntityType::BUS: case EntityKind::BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
busMove.eTT = 3; busMove.eTT = 3;
busMove.iT_ID = npc->appearanceData.iNPC_ID; busMove.iT_ID = npc->id;
busMove.iMoveStyle = 0; // ??? busMove.iMoveStyle = 0; // ???
busMove.iToX = point.x; busMove.iToX = point.x;
busMove.iToY = point.y; busMove.iToY = point.y;
@ -293,12 +296,12 @@ static void stepNPCPathing() {
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE)); NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
break; break;
case EntityType::MOB: case EntityKind::MOB:
MobAI::incNextMovement((Mob*)npc); MobAI::incNextMovement((Mob*)npc);
/* fallthrough */ /* fallthrough */
default: default:
INITSTRUCT(sP_FE2CL_NPC_MOVE, move); INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
move.iNPC_ID = npc->appearanceData.iNPC_ID; move.iNPC_ID = npc->id;
move.iMoveStyle = 0; // ??? move.iMoveStyle = 0; // ???
move.iToX = point.x; move.iToX = point.x;
move.iToY = point.y; move.iToY = point.y;
@ -385,7 +388,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
void Transport::constructPathNPC(int32_t id, NPCPath* path) { void Transport::constructPathNPC(int32_t id, NPCPath* path) {
BaseNPC* npc = NPCManager::NPCs[id]; BaseNPC* npc = NPCManager::NPCs[id];
if (npc->type == EntityType::MOB) if (npc->kind == EntityKind::MOB)
((Mob*)(npc))->staticPath = true; ((Mob*)(npc))->staticPath = true;
npc->loopingPath = path->isLoop; npc->loopingPath = path->isLoop;

View File

@ -1,8 +1,11 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include <unordered_map> #include <unordered_map>
#include <map>
#include <vector>
#include <queue>
const int SLIDER_SPEED = 1200; const int SLIDER_SPEED = 1200;
const int SLIDER_STOP_TICKS = 16; const int SLIDER_STOP_TICKS = 16;

View File

@ -1,4 +1,9 @@
#include "Vendors.hpp" #include "Vendors.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "Rand.hpp" #include "Rand.hpp"
// 7 days // 7 days

View File

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include "Items.hpp" #include <vector>
#include "PlayerManager.hpp" #include <map>
struct VendorListing { struct VendorListing {
int sort, type, id; int sort, type, id;

View File

@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
// overflow-safe validation of variable-length packets // overflow-safe validation of variable-length packets
// for outbound packets // for outbound packets
inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) { inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
// check for multiplication overflow // check for multiplication overflow
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false; return false;
@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p
} }
// for inbound packets // for inbound packets
inline constexpr bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) { inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
// check for multiplication overflow // check for multiplication overflow
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false; return false;

View File

@ -23,6 +23,7 @@
#include <string> #include <string>
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
#include <tuple>
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs. // yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
#define INITSTRUCT(T, x) T x; \ #define INITSTRUCT(T, x) T x; \

View File

@ -10,63 +10,44 @@ const float CN_EP_RANK_4 = 0.3f;
const float CN_EP_RANK_5 = 0.29f; const float CN_EP_RANK_5 = 0.29f;
// methods of finding players for GM commands // methods of finding players for GM commands
enum eCN_GM_TargetSearchBy { enum class eCN_GM_TargetSearchBy {
eCN_GM_TargetSearchBy__PC_ID, // player id PC_ID, // player id
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname PC_Name, // firstname, lastname
eCN_GM_TargetSearchBy__PC_UID // account id PC_UID // account id
}; };
enum eCN_GM_TeleportType { enum class eCN_GM_TeleportType {
eCN_GM_TeleportMapType__XYZ, XYZ,
eCN_GM_TeleportMapType__MapXYZ, MapXYZ,
eCN_GM_TeleportMapType__MyLocation, MyLocation,
eCN_GM_TeleportMapType__SomeoneLocation, SomeoneLocation,
eCN_GM_TeleportMapType__Unstick Unstick
}; };
// nano powers enum class eTaskTypeProperty {
None = -1,
Talk = 1,
GotoLocation = 2,
UseItems = 3,
Delivery = 4,
Defeat = 5,
EscortDefence = 6,
Max = 7
};
enum class ePCRegenType {
None,
Xcom,
Here,
HereByPhoenix,
HereByPhoenixGroup,
Unstick,
HereByPhoenixItem,
End
};
// nano power flags
enum { enum {
EST_NONE = 0,
EST_DAMAGE = 1,
EST_HEAL_HP = 2,
EST_KNOCKDOWN = 3,
EST_SLEEP = 4,
EST_SNARE = 5,
EST_HEAL_STAMINA = 6,
EST_STAMINA_SELF = 7,
EST_STUN = 8,
EST_WEAPONSLOW = 9,
EST_JUMP = 10,
EST_RUN = 11,
EST_STEALTH = 12,
EST_SWIM = 13,
EST_MINIMAPENEMY = 14,
EST_MINIMAPTRESURE = 15,
EST_PHOENIX = 16,
EST_PROTECTBATTERY = 17,
EST_PROTECTINFECTION = 18,
EST_REWARDBLOB = 19,
EST_REWARDCASH = 20,
EST_BATTERYDRAIN = 21,
EST_CORRUPTIONATTACK = 22,
EST_INFECTIONDAMAGE = 23,
EST_KNOCKBACK = 24,
EST_FREEDOM = 25,
EST_PHOENIX_GROUP = 26,
EST_RECALL = 27,
EST_RECALL_GROUP = 28,
EST_RETROROCKET_SELF = 29,
EST_BLOODSUCKING = 30,
EST_BOUNDINGBALL = 31,
EST_INVULNERABLE = 32,
EST_NANOSTIMPAK = 33,
EST_RETURNHOMEHEAL = 34,
EST_BUFFHEAL = 35,
EST_EXTRABANK = 36,
EST__END = 37,
EST_CORRUPTIONATTACKWIN = 38,
EST_CORRUPTIONATTACKLOSE = 39,
ECSB_NONE = 0, ECSB_NONE = 0,
ECSB_UP_MOVE_SPEED = 1, ECSB_UP_MOVE_SPEED = 1,
ECSB_UP_SWIM_SPEED = 2, ECSB_UP_SWIM_SPEED = 2,
@ -96,6 +77,27 @@ enum {
ECSTB__END = 26, ECSTB__END = 26,
}; };
enum {
ETBU_NONE = 0,
ETBU_ADD = 1,
ETBU_DEL = 2,
ETBU_CHANGE = 3,
ETBU__END = 4,
};
enum {
ETBT_NONE = 0,
ETBT_NANO = 1,
ETBT_GROUPNANO = 2,
ETBT_SHINY = 3,
ETBT_LANDEFFECT = 4,
ETBT_ITEM = 5,
ETBT_CASHITEM = 6,
ETBT__END = 7,
ETBT_SKILL = 1,
ETBT_GROUPSKILL = 2
};
enum { enum {
SUCC = 1, SUCC = 1,
FAIL = 0, FAIL = 0,

View File

@ -47,8 +47,8 @@ struct PacketDesc {
* really should. * really should.
*/ */
struct sGM_PVPTarget { struct sGM_PVPTarget {
uint32_t eCT;
uint32_t iID; uint32_t iID;
uint32_t eCT;
}; };
struct sSkillResult_Leech { struct sSkillResult_Leech {

View File

@ -1,13 +1,15 @@
#include "servers/CNLoginServer.hpp" #include "servers/CNLoginServer.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include <regex>
#include "bcrypt/BCrypt.hpp" #include "bcrypt/BCrypt.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <regex>
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Player.hpp" #include "Player.hpp"
#include <map> #include <map>

View File

@ -1,10 +1,12 @@
#include "servers/CNShardServer.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "servers/Monitor.hpp" #include "servers/Monitor.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "core/CNShared.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "TableData.hpp" // for flush() #include "TableData.hpp" // for flush()

View File

@ -3,9 +3,12 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <map> #include <map>
#include <list>
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
#define MS_PER_PLAYER_TICK 500
#define MS_PER_COMBAT_TICK 200
class CNShardServer : public CNServer { class CNShardServer : public CNServer {
private: private:

View File

@ -1,8 +1,10 @@
#include "servers/Monitor.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp" #include "Chat.hpp"
#include "Email.hpp" #include "Email.hpp"
#include "servers/Monitor.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <cstdio> #include <cstdio>

View File

@ -2,9 +2,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <list>
#include <mutex>
namespace Monitor { namespace Monitor {
SOCKET init(); SOCKET init();
bool acceptConnection(SOCKET, uint16_t); bool acceptConnection(SOCKET, uint16_t);

View File

@ -1,9 +1,10 @@
#include <iostream>
#include "settings.hpp" #include "settings.hpp"
#include "core/CNStructs.hpp" // so we get the ACADEMY definition
#include "INIReader.hpp" #include "INIReader.hpp"
// so we get the ACADEMY definition #include <iostream>
#include "core/CNStructs.hpp"
// defaults :) // defaults :)
int settings::VERBOSITY = 1; int settings::VERBOSITY = 1;

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string>
namespace settings { namespace settings {
extern int VERBOSITY; extern int VERBOSITY;
extern bool SANDBOX; extern bool SANDBOX;

2
vendor/JSON.hpp vendored
View File

@ -27,6 +27,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#pragma once
#ifndef INCLUDE_NLOHMANN_JSON_HPP_ #ifndef INCLUDE_NLOHMANN_JSON_HPP_
#define INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_