From 98634d5aa2d94f5132f370dff58a2ccf66ac8061 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Jul 2022 23:33:57 -0700 Subject: [PATCH] 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 - ??? --- Makefile | 2 + src/Abilities.cpp | 4 +- src/Abilities.hpp | 2 +- src/Buffs.cpp | 30 +++++++++ src/Buffs.hpp | 122 ++++++++++++++++++++++++++++++++++ src/Combat.cpp | 107 ++++++++++++++++++++++++----- src/Eggs.cpp | 2 +- src/Entities.hpp | 14 ++++ src/Items.cpp | 31 ++++----- src/Missions.cpp | 4 +- src/MobAI.cpp | 4 +- src/Nanos.cpp | 14 +--- src/Player.hpp | 12 +++- src/PlayerManager.cpp | 7 +- src/core/Defines.hpp | 7 ++ src/servers/CNShardServer.hpp | 1 + 16 files changed, 309 insertions(+), 54 deletions(-) create mode 100644 src/Buffs.cpp create mode 100644 src/Buffs.hpp diff --git a/Makefile b/Makefile index 843352d..c88e00c 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ CXXSRC=\ src/db/email.cpp\ src/sandbox/seccomp.cpp\ src/sandbox/openbsd.cpp\ + src/Buffs.cpp\ src/Chat.cpp\ src/CustomCommands.cpp\ src/Entities.cpp\ @@ -96,6 +97,7 @@ CXXHDR=\ vendor/JSON.hpp\ vendor/INIReader.hpp\ vendor/JSON.hpp\ + src/Buffs.hpp\ src/Chat.hpp\ src/CustomCommands.hpp\ src/Entities.hpp\ diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 691f486..6b309f9 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -35,13 +35,13 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 return targets; } -void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector targets) { +void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector targets) { for (EntityRef target : targets) { Entity* entity = target.getEntity(); if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB) continue; // not a combatant - + // TODO abilities } } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 834c605..8f964ff 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -44,7 +44,7 @@ namespace Abilities { extern std::map SkillTable; std::vector matchTargets(SkillData*, int, int32_t*); - void applyAbility(SkillData*, EntityRef, std::vector); + void useAbility(SkillData*, EntityRef, std::vector); void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp new file mode 100644 index 0000000..133d2da --- /dev/null +++ b/src/Buffs.cpp @@ -0,0 +1,30 @@ +#include "Buffs.hpp" + +#include "PlayerManager.hpp" + +using namespace Buffs; + +static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { + + if(self.kind != EntityKind::PLAYER) + return; // not implemented + + Player* plr = (Player*)self.getEntity(); + if(plr == nullptr) + return; + + INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); + pkt.eCSTB = buff->id; // eCharStatusTimeBuffID + pkt.eTBU = type; // eTimeBuffUpdate + pkt.eTBT = (buff->buffClass <= BuffClass::CONSUMABLE); // eTimeBuffType 1 means nano + pkt.iConditionBitFlag = plr->getCompositeCondition(); + self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); +} + +void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_ADD); +} + +void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_DEL); +} \ No newline at end of file diff --git a/src/Buffs.hpp b/src/Buffs.hpp new file mode 100644 index 0000000..0b55396 --- /dev/null +++ b/src/Buffs.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "core/Core.hpp" + +#include "Entities.hpp" + +#include +#include +#include + +/* forward declaration(s) */ +struct BuffStack; + +#define CSB_FROM_ECSB(x) (1 << (x - 1)) + +enum class BuffClass { + NANO = 0, + CONSUMABLE, + EGG, + OTHER +}; + +typedef void (*BuffCallback)(EntityRef, BuffStack*); + +struct BuffStack { + int id; // ECSB + int durationTicks; + EntityRef source; + BuffClass buffClass; + + BuffCallback onApply; + BuffCallback onTick; + BuffCallback onExpire; +}; + +class Buff { +private: + EntityRef self; + std::vector stacks; + +public: + void onTick() { + assert(!stacks.empty()); + + std::set callbacks; + auto it = stacks.begin(); + while(it != stacks.end()) { + BuffStack& stack = *it; + if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { + // unique callback + stack.onTick(self, &stack); + callbacks.insert(stack.onTick); + } + + if(stack.durationTicks > 0) stack.durationTicks--; + if(stack.durationTicks == 0) { + it = stacks.erase(it); + stack.onExpire(self, &stack); + } else it++; + } + } + + void onExpire() { + assert(!stacks.empty()); + + std::set callbacks; + while(!stacks.empty()) { + BuffStack stack = stacks.back(); + stacks.pop_back(); + if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { + // execute unique callback + callbacks.insert(stack.onExpire); + stack.onExpire(self, &stack); + } + } + } + + void addStack(BuffStack* stack) { + stacks.push_back(*stack); + } + + /* + * Why do this madness? Let me tell you why. + * We need to be able to distinguish whether a player's + * buff is from something like an egg vs. their nano, + * because there are cases where the behavior is different. + * Now, this is impossible to do when they all get composited + * to a single bitfield. So we use a "buff class" and pick + * the buff stack that is most "dominant". + */ + BuffStack* getDominantBuff() { + assert(!stacks.empty()); + + BuffStack* dominant = nullptr; + for(BuffStack& stack : stacks) { + if(stack.buffClass > dominant->buffClass) + dominant = &stack; + } + return dominant; + } + + /* + * 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() { + return stacks.empty(); + } + + Buff(EntityRef pSelf, BuffStack* firstStack) + : self(pSelf) { + addStack(firstStack); + } +}; + +namespace Buffs { + void timeBuffUpdateAdd(EntityRef self, BuffStack* buff); + void timeBuffUpdateDelete(EntityRef self, BuffStack* buff); +} diff --git a/src/Combat.cpp b/src/Combat.cpp index 94e3c86..4631d9e 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -8,14 +8,53 @@ #include "NPCManager.hpp" #include "Nanos.hpp" #include "Abilities.hpp" +#include "Buffs.hpp" #include +#include using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; +void Player::addBuff(BuffStack* buff) { + EntityRef self = PlayerManager::getSockFromID(iID); + if(!hasBuff(buff->id)) { + buffs[buff->id] = new Buff(self, buff); + } + else buffs[buff->id]->addStack(buff); + buff->onApply(self, buff); +} + +BuffStack* Player::getBuff(int buffId) { + if(hasBuff(buffId)) { + return buffs[buffId]->getDominantBuff(); + } + return nullptr; +} + +void Player::removeBuff(int buffId) { + if(hasBuff(buffId)) { + buffs[buffId]->onExpire(); + delete buffs[buffId]; + buffs.erase(buffId); + } +} + +bool Player::hasBuff(int buffId) { + return buffs.find(buffId) != buffs.end(); +} + +int Player::getCompositeCondition() { + int conditionBitFlag = 0; + for(auto buffEntry : buffs) { + if(!buffEntry.second->isStale()) + conditionBitFlag |= CSB_FROM_ECSB(buffEntry.first); + } + return conditionBitFlag; +} + int Player::takeDamage(EntityRef src, int amt) { HP -= amt; return amt; @@ -41,6 +80,22 @@ void Player::step(time_t currTime) { // no-op } +void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ } + +BuffStack* CombatNPC::getBuff(int buffId) { /* stubbed */ + return nullptr; +} + +void CombatNPC::removeBuff(int buffId) { /* stubbed */ } + +bool CombatNPC::hasBuff(int buffId) { /* stubbed */ + return false; +} + +int CombatNPC::getCompositeCondition() { /* stubbed */ + return 0; +} + int CombatNPC::takeDamage(EntityRef src, int amt) { hp -= amt; @@ -303,17 +358,22 @@ 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); - 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)); + // 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 = { + ECSB_INFECTION, + -1, // infinite + sock, // self-inflicted + BuffClass::OTHER, + Buffs::timeBuffUpdateAdd, + nullptr, // client ticks for us! todo anticheat lol + Buffs::timeBuffUpdateDelete + }; + plr->addBuff(&infection); + } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { + plr->removeBuff(ECSB_INFECTION); + } } static void dealGooDamage(CNSocket *sock, int amount) { @@ -327,12 +387,13 @@ static void dealGooDamage(CNSocket *sock, int amount) { 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)); - if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) { + BuffStack* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION); + if (protectionBuff != nullptr) { amount = -2; // -2 is the magic number for "Protected" to appear as the damage number dmg->bProtected = 1; // eggs allow protection without nanos - if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION)) + if (protectionBuff->buffClass == BuffClass::NANO && plr->activeNano != -1) plr->Nanos[plr->activeNano].iStamina -= 3; } else { plr->HP -= amount; @@ -356,7 +417,7 @@ static void dealGooDamage(CNSocket *sock, int amount) { dmg->iID = plr->iID; dmg->iDamage = amount; dmg->iHP = plr->HP; - dmg->iConditionBitFlag = plr->iConditionBitFlag; + dmg->iConditionBitFlag = plr->getCompositeCondition(); 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); @@ -664,7 +725,7 @@ static void playerTick(CNServer *serv, time_t currTime) { continue; // fm patch/lake damage - if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) + if ((plr->hasBuff(ECSB_INFECTION)) && !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20); @@ -692,7 +753,7 @@ static void playerTick(CNServer *serv, time_t currTime) { drainRate = skill->batteryUse[boost * 3]; if (skill->drainType == SkillDrainType::PASSIVE) { - // passive buff + // apply passive buff std::vector targets; if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr) targets = plr->group->members; // group @@ -700,6 +761,7 @@ static void playerTick(CNServer *serv, time_t currTime) { targets.push_back(sock); // self std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl; + // TODO abilities } } @@ -732,6 +794,17 @@ static void playerTick(CNServer *serv, time_t currTime) { 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()) { + int buffId = (*it).first; + Buff* buff = (*it).second; + buff->onTick(); + if(buff->isStale()) it = plr->buffs.erase(it); + else it++; + } + // + if (transmit) { INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt); @@ -752,7 +825,7 @@ static void playerTick(CNServer *serv, time_t currTime) { } 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); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index cddf6b4..ee13444 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -64,7 +64,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iConditionBitFlag = plr->iConditionBitFlag; + skill->iConditionBitFlag = plr->getCompositeCondition(); } skillUse->iNPC_ID = eggId; diff --git a/src/Entities.hpp b/src/Entities.hpp index 1b02f0b..30e7163 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -6,6 +6,10 @@ #include #include +/* forward declaration(s) */ +class Buff; +struct BuffStack; + enum EntityKind { INVALID, PLAYER, @@ -88,6 +92,11 @@ public: ICombatant() {} virtual ~ICombatant() {} + virtual void addBuff(BuffStack* buff) = 0; + virtual BuffStack* getBuff(int buffId) = 0; + virtual void removeBuff(int buffId) = 0; + virtual bool hasBuff(int buffId) = 0; + virtual int getCompositeCondition() = 0; virtual int takeDamage(EntityRef, int) = 0; virtual void heal(EntityRef, int) = 0; virtual bool isAlive() = 0; @@ -149,6 +158,11 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } + virtual void addBuff(BuffStack* buff) override; + virtual BuffStack* getBuff(int buffId) override; + virtual void removeBuff(int buffId) override; + virtual bool hasBuff(int buffId) override; + virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; diff --git a/src/Items.cpp b/src/Items.cpp index fc993e2..4eec2af 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -9,6 +9,7 @@ #include "Eggs.hpp" #include "MobAI.hpp" #include "Missions.hpp" +#include "Buffs.hpp" #include // for memset() #include @@ -484,27 +485,27 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { resp->eST = EST_NANOSTIMPAK; resp->iSkillID = 144; - int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot; - int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot; + int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot; respdata->eCT = 1; respdata->iID = player->iID; - respdata->iConditionBitFlag = value1; + respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB); - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = value2; // eCharStatusTimeBuffID - pkt.eTBU = 1; // eTimeBuffUpdate - pkt.eTBT = 1; // eTimeBuffType 1 means nano - pkt.iConditionBitFlag = player->iConditionBitFlag |= value1; - sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); + int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; + BuffStack gumballBuff = { + eCSB, + durationMilliseconds / MS_PER_PLAYER_TICK, + sock, + BuffClass::CONSUMABLE, + Buffs::timeBuffUpdateAdd, + nullptr, + Buffs::timeBuffUpdateDelete, + }; + player->addBuff(&gumballBuff); sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); // update inventory serverside player->Inven[resp->iSlotNum] = resp->RemainItem; - - std::pair key = std::make_pair(sock, value1); - time_t until = getTime() + (time_t)Abilities::SkillTable[144].durationTime[0] * 100; - Eggs::EggBuffs[key] = until; } static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { @@ -757,7 +758,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) { plr->money += miscDropType.taroAmount; // money nano boost - if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { + if (plr->hasBuff(ECSB_REWARD_CASH)) { int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; @@ -772,7 +773,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo if (levelDifference > 0) fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; // scavenger nano boost - if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { + if (plr->hasBuff(ECSB_REWARD_BLOB)) { int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; diff --git a/src/Missions.cpp b/src/Missions.cpp index db0d17c..b13699a 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -163,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { // update player 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; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; 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; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 7313b85..26552f1 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -187,7 +187,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { int mobRange = mob->sightRange; - if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH + if (plr->hasBuff(ECSB_UP_STEALTH) || Racing::EPRaces.find(s) != Racing::EPRaces.end()) mobRange /= 3; @@ -315,7 +315,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i plr->HP -= respdata[i].iDamage; respdata[i].iHP = plr->HP; - respdata[i].iConditionBitFlag = plr->iConditionBitFlag; + respdata[i].iConditionBitFlag = plr->getCompositeCondition(); if (plr->HP <= 0) { if (!MobAI::aggroCheck(mob, getTime())) diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 088d9cb..551da96 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -184,7 +184,7 @@ int Nanos::nanoStyle(int nanoID) { bool Nanos::getNanoBoost(Player* plr) { for (int i = 0; i < 3; i++) if (plr->equippedNanos[i] == plr->activeNano) - if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i)) + if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i)) return true; return false; } @@ -209,16 +209,8 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { 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); - } + int buffId = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum; + plr->removeBuff(buffId); // unsummon nano if replaced if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) diff --git a/src/Player.hpp b/src/Player.hpp index c58b7d6..b2cd8ef 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -7,6 +7,10 @@ #include +/* forward declaration(s) */ +class Buff; +struct BuffStack; + #define ACTIVE_MISSION_COUNT 6 #define PC_MAXHEALTH(level) (925 + 75 * (level)) @@ -32,9 +36,8 @@ struct Player : public Entity, public ICombatant { int8_t iPCState = 0; int32_t iWarpLocationFlag = 0; int64_t aSkywayLocationFlag[2] = {}; - int32_t iConditionBitFlag = 0; - int32_t iSelfConditionBitFlag = 0; int8_t iSpecialState = 0; + std::unordered_map buffs = {}; int angle = 0; int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0; @@ -86,6 +89,11 @@ struct Player : public Entity, public ICombatant { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + virtual void addBuff(BuffStack* buff) override; + virtual BuffStack* getBuff(int buffId) override; + virtual void removeBuff(int buffId) override; + virtual bool hasBuff(int buffId) override; + virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 91d4c69..2e35846 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -36,6 +36,11 @@ void PlayerManager::removePlayer(CNSocket* key) { Player* plr = getPlayer(key); uint64_t fromInstance = plr->instanceID; + // free buff memory + for(auto buffEntry : plr->buffs) + delete buffEntry.second; + + // leave group if(plr->group != nullptr) Groups::groupKick(plr); @@ -403,7 +408,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { switch ((ePCRegenType)reviveData->iRegenType) { case ePCRegenType::HereByPhoenix: // nano revive - if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX)) + if (!(plr->hasBuff(ECSB_PHOENIX))) return; // sanity check plr->Nanos[plr->activeNano].iStamina = 0; // TODO ABILITIES diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index 185b5c5..e07bb00 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -118,6 +118,13 @@ enum { ECSTB__END = 26, }; +enum { + ETBU_NONE = 0, + ETBU_ADD = 1, + ETBU_DEL = 2, + ETBU_CHANGE = 3 +}; + enum { SUCC = 1, FAIL = 0, diff --git a/src/servers/CNShardServer.hpp b/src/servers/CNShardServer.hpp index 460d2a8..8ab7360 100644 --- a/src/servers/CNShardServer.hpp +++ b/src/servers/CNShardServer.hpp @@ -7,6 +7,7 @@ #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); +#define MS_PER_PLAYER_TICK 2000 class CNShardServer : public CNServer { private: