mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 21:40:05 +00:00
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.
This commit is contained in:
parent
3d22103113
commit
a38bf0e7be
@ -2,67 +2,58 @@
|
|||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
using namespace Buffs;
|
using namespace Buffs;
|
||||||
|
|
||||||
void Buff::onTick() {
|
void Buff::tick() {
|
||||||
assert(!stacks.empty());
|
|
||||||
|
|
||||||
std::set<BuffCallback> callbacks;
|
|
||||||
auto it = stacks.begin();
|
auto it = stacks.begin();
|
||||||
while(it != stacks.end()) {
|
while(it != stacks.end()) {
|
||||||
BuffStack& stack = *it;
|
BuffStack& stack = *it;
|
||||||
if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) {
|
if(stack.onTick) stack.onTick(self, &stack);
|
||||||
// unique callback
|
|
||||||
stack.onTick(self, &stack);
|
|
||||||
callbacks.insert(stack.onTick);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(stack.durationTicks > 0) stack.durationTicks--;
|
if(stack.durationTicks > 0) stack.durationTicks--;
|
||||||
if(stack.durationTicks == 0) {
|
if(stack.durationTicks == 0) {
|
||||||
it = stacks.erase(it);
|
it = stacks.erase(it);
|
||||||
if(stack.onExpire != nullptr) stack.onExpire(self, &stack);
|
if(stack.onExpire) stack.onExpire(self, &stack);
|
||||||
} else it++;
|
} else it++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buff::onExpire() {
|
void Buff::clear() {
|
||||||
assert(!stacks.empty());
|
|
||||||
|
|
||||||
std::set<BuffCallback> callbacks;
|
|
||||||
while(!stacks.empty()) {
|
while(!stacks.empty()) {
|
||||||
BuffStack stack = stacks.back();
|
BuffStack stack = stacks.back();
|
||||||
stacks.pop_back();
|
stacks.pop_back();
|
||||||
if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) {
|
if(stack.onExpire) stack.onExpire(self, &stack);
|
||||||
// execute unique callback
|
|
||||||
callbacks.insert(stack.onExpire);
|
|
||||||
stack.onExpire(self, &stack);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buff::addStack(BuffStack* stack) {
|
void Buff::addStack(BuffStack* stack) {
|
||||||
|
if(stack->onApply) stack->onApply(self, stack);
|
||||||
stacks.push_back(*stack);
|
stacks.push_back(*stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuffStack* Buff::getDominantBuff() {
|
bool Buff::hasClass(BuffClass buffClass) {
|
||||||
assert(!stacks.empty());
|
|
||||||
|
|
||||||
BuffStack* dominant = nullptr;
|
|
||||||
for(BuffStack& stack : stacks) {
|
for(BuffStack& stack : stacks) {
|
||||||
if(stack.buffClass > dominant->buffClass)
|
if(stack.buffClass == buffClass)
|
||||||
dominant = &stack;
|
return true;
|
||||||
}
|
}
|
||||||
return dominant;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuffClass Buff::maxClass() {
|
||||||
|
BuffClass buffClass = BuffClass::NONE;
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
if(stack.buffClass > buffClass)
|
||||||
|
buffClass = stack.buffClass;
|
||||||
|
}
|
||||||
|
return buffClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Buff::isStale() {
|
bool Buff::isStale() {
|
||||||
return stacks.empty();
|
return stacks.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) {
|
#pragma region Handlers
|
||||||
|
void Buffs::timeBuffUpdate(EntityRef self, BuffStack* buff, int status) {
|
||||||
|
|
||||||
if(self.kind != EntityKind::PLAYER)
|
if(self.kind != EntityKind::PLAYER)
|
||||||
return; // not implemented
|
return; // not implemented
|
||||||
@ -71,15 +62,21 @@ static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) {
|
|||||||
if(plr == nullptr)
|
if(plr == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(status == ETBU_DEL && plr->hasBuff(buff->id))
|
||||||
|
return; // no premature status removal!
|
||||||
|
|
||||||
|
int cbf = plr->getCompositeCondition();
|
||||||
|
if(status == ETBU_ADD) cbf |= CSB_FROM_ECSB(buff->id);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||||
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
||||||
pkt.eTBU = type; // eTimeBuffUpdate
|
pkt.eTBU = status; // eTimeBuffUpdate
|
||||||
pkt.eTBT = (int)buff->buffClass;
|
pkt.eTBT = (int)buff->buffClass;
|
||||||
pkt.iConditionBitFlag = plr->getCompositeCondition();
|
pkt.iConditionBitFlag = cbf;
|
||||||
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void timeBuffTimeoutViewable(EntityRef self) {
|
void Buffs::timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct) {
|
||||||
if(self.kind != EntityKind::PLAYER)
|
if(self.kind != EntityKind::PLAYER)
|
||||||
return; // not implemented
|
return; // not implemented
|
||||||
|
|
||||||
@ -88,21 +85,15 @@ static void timeBuffTimeoutViewable(EntityRef self) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||||
pkt.eCT = 1;
|
pkt.eCT = ct; // 1 for eggs, at least
|
||||||
pkt.iID = plr->iID;
|
pkt.iID = plr->iID;
|
||||||
pkt.iConditionBitFlag = plr->getCompositeCondition();
|
pkt.iConditionBitFlag = plr->getCompositeCondition();
|
||||||
PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) {
|
/* MOVE TO EGG LAMBDA
|
||||||
timeBuffUpdate(self, buff, ETBU_ADD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) {
|
|
||||||
timeBuffUpdate(self, buff, ETBU_DEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) {
|
void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) {
|
||||||
timeBuffUpdate(self, buff, ETBU_DEL);
|
timeBuffUpdate(self, buff, ETBU_DEL);
|
||||||
timeBuffTimeoutViewable(self);
|
timeBuffTimeoutViewable(self);
|
||||||
}
|
} */
|
||||||
|
#pragma endregion
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
/* forward declaration(s) */
|
/* forward declaration(s) */
|
||||||
struct BuffStack;
|
struct BuffStack;
|
||||||
@ -21,7 +22,7 @@ enum class BuffClass {
|
|||||||
CASH_ITEM = ETBT_CASHITEM
|
CASH_ITEM = ETBT_CASHITEM
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*BuffCallback)(EntityRef, BuffStack*);
|
typedef std::function<void(EntityRef, BuffStack*)> BuffCallback;
|
||||||
|
|
||||||
struct BuffStack {
|
struct BuffStack {
|
||||||
int id; // ECSB
|
int id; // ECSB
|
||||||
@ -29,8 +30,13 @@ struct BuffStack {
|
|||||||
EntityRef source;
|
EntityRef source;
|
||||||
BuffClass buffClass;
|
BuffClass buffClass;
|
||||||
|
|
||||||
|
/* called just before the stack is added */
|
||||||
BuffCallback onApply;
|
BuffCallback onApply;
|
||||||
|
|
||||||
|
/* called when the stack is ticked */
|
||||||
BuffCallback onTick;
|
BuffCallback onTick;
|
||||||
|
|
||||||
|
/* called just after the stack is removed */
|
||||||
BuffCallback onExpire;
|
BuffCallback onExpire;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,20 +46,17 @@ private:
|
|||||||
std::vector<BuffStack> stacks;
|
std::vector<BuffStack> stacks;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void onTick();
|
void tick();
|
||||||
void onExpire();
|
void clear();
|
||||||
void addStack(BuffStack* stack);
|
void addStack(BuffStack* stack);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Why do this madness? Let me tell you why.
|
* Sometimes we need to determine if a buff
|
||||||
* We need to be able to distinguish whether a player's
|
* is covered by a certain class, ex: nano
|
||||||
* buff is from something like an egg vs. their nano,
|
* vs. coco egg in the case of infection protection
|
||||||
* 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();
|
bool hasClass(BuffClass buffClass);
|
||||||
|
BuffClass maxClass();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In general, a Buff object won't exist
|
* In general, a Buff object won't exist
|
||||||
@ -71,7 +74,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace Buffs {
|
namespace Buffs {
|
||||||
void timeBuffUpdateAdd(EntityRef self, BuffStack* buff);
|
void timeBuffUpdate(EntityRef self, BuffStack* buff, int status);
|
||||||
void timeBuffUpdateDelete(EntityRef self, BuffStack* buff);
|
void timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct);
|
||||||
void timeBuffTimeout(EntityRef self, BuffStack* buff);
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
using namespace Combat;
|
using namespace Combat;
|
||||||
|
|
||||||
@ -20,37 +21,35 @@ std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
|||||||
|
|
||||||
void Player::addBuff(BuffStack* buff) {
|
void Player::addBuff(BuffStack* buff) {
|
||||||
EntityRef self = PlayerManager::getSockFromID(iID);
|
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||||
if(!hasBuff(buff->id)) {
|
if(!hasBuff(buff->id)) buffs[buff->id] = new Buff(self, buff);
|
||||||
buffs[buff->id] = new Buff(self, buff);
|
|
||||||
}
|
|
||||||
else buffs[buff->id]->addStack(buff);
|
else buffs[buff->id]->addStack(buff);
|
||||||
buff->onApply(self, buff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BuffStack* Player::getBuff(int buffId) {
|
Buff* Player::getBuff(int buffId) {
|
||||||
if(hasBuff(buffId)) {
|
if(hasBuff(buffId)) {
|
||||||
return buffs[buffId]->getDominantBuff();
|
return buffs[buffId];
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::removeBuff(int buffId) {
|
void Player::removeBuff(int buffId) {
|
||||||
if(hasBuff(buffId)) {
|
if(hasBuff(buffId)) {
|
||||||
buffs[buffId]->onExpire();
|
buffs[buffId]->clear();
|
||||||
delete buffs[buffId];
|
delete buffs[buffId];
|
||||||
buffs.erase(buffId);
|
buffs.erase(buffId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Player::hasBuff(int buffId) {
|
bool Player::hasBuff(int buffId) {
|
||||||
return buffs.find(buffId) != buffs.end();
|
auto buff = buffs.find(buffId);
|
||||||
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Player::getCompositeCondition() {
|
int Player::getCompositeCondition() {
|
||||||
int conditionBitFlag = 0;
|
int conditionBitFlag = 0;
|
||||||
for(auto buffEntry : buffs) {
|
for(auto buff : buffs) {
|
||||||
if(!buffEntry.second->isStale())
|
if(!buff.second->isStale())
|
||||||
conditionBitFlag |= CSB_FROM_ECSB(buffEntry.first);
|
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
||||||
}
|
}
|
||||||
return conditionBitFlag;
|
return conditionBitFlag;
|
||||||
}
|
}
|
||||||
@ -82,7 +81,7 @@ void Player::step(time_t currTime) {
|
|||||||
|
|
||||||
void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ }
|
void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ }
|
||||||
|
|
||||||
BuffStack* CombatNPC::getBuff(int buffId) { /* stubbed */
|
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,9 +336,14 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
|||||||
-1, // infinite
|
-1, // infinite
|
||||||
sock, // self-inflicted
|
sock, // self-inflicted
|
||||||
BuffClass::ENVIRONMENT,
|
BuffClass::ENVIRONMENT,
|
||||||
Buffs::timeBuffUpdateAdd,
|
|
||||||
nullptr, // client ticks for us! todo anticheat lol
|
[](EntityRef host, BuffStack* stack) {
|
||||||
Buffs::timeBuffUpdateDelete
|
Buffs::timeBuffUpdate(host, stack, ETBU_ADD);
|
||||||
|
},
|
||||||
|
nullptr, // client toggles for us! todo anticheat lol
|
||||||
|
[](EntityRef host, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(host, stack, ETBU_DEL);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
plr->addBuff(&infection);
|
plr->addBuff(&infection);
|
||||||
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
||||||
@ -358,13 +362,13 @@ static void dealGooDamage(CNSocket *sock, int amount) {
|
|||||||
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));
|
||||||
|
|
||||||
BuffStack* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
|
Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
|
||||||
if (protectionBuff != nullptr) {
|
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 (protectionBuff->buffClass == BuffClass::NANO && plr->activeNano != -1)
|
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;
|
||||||
@ -770,8 +774,12 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
while(it != plr->buffs.end()) {
|
while(it != plr->buffs.end()) {
|
||||||
int buffId = (*it).first;
|
int buffId = (*it).first;
|
||||||
Buff* buff = (*it).second;
|
Buff* buff = (*it).second;
|
||||||
buff->onTick();
|
buff->tick();
|
||||||
if(buff->isStale()) it = plr->buffs.erase(it);
|
if(buff->isStale()) {
|
||||||
|
// garbage collect
|
||||||
|
it = plr->buffs.erase(it);
|
||||||
|
delete buff;
|
||||||
|
}
|
||||||
else it++;
|
else it++;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
@ -93,7 +93,7 @@ public:
|
|||||||
virtual ~ICombatant() {}
|
virtual ~ICombatant() {}
|
||||||
|
|
||||||
virtual void addBuff(BuffStack* buff) = 0;
|
virtual void addBuff(BuffStack* buff) = 0;
|
||||||
virtual BuffStack* getBuff(int buffId) = 0;
|
virtual Buff* getBuff(int buffId) = 0;
|
||||||
virtual void removeBuff(int buffId) = 0;
|
virtual void removeBuff(int buffId) = 0;
|
||||||
virtual bool hasBuff(int buffId) = 0;
|
virtual bool hasBuff(int buffId) = 0;
|
||||||
virtual int getCompositeCondition() = 0;
|
virtual int getCompositeCondition() = 0;
|
||||||
@ -159,7 +159,7 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
virtual bool isExtant() override { return hp > 0; }
|
virtual bool isExtant() override { return hp > 0; }
|
||||||
|
|
||||||
virtual void addBuff(BuffStack* buff) override;
|
virtual void addBuff(BuffStack* buff) override;
|
||||||
virtual BuffStack* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
|
@ -497,9 +497,14 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
durationMilliseconds / MS_PER_PLAYER_TICK,
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||||
sock,
|
sock,
|
||||||
BuffClass::CASH_ITEM, // or BuffClass::ITEM?
|
BuffClass::CASH_ITEM, // or BuffClass::ITEM?
|
||||||
Buffs::timeBuffUpdateAdd,
|
|
||||||
nullptr,
|
[](EntityRef host, BuffStack* stack) {
|
||||||
Buffs::timeBuffUpdateDelete,
|
Buffs::timeBuffUpdate(host, stack, ETBU_ADD);
|
||||||
|
},
|
||||||
|
nullptr, // client toggles for us! todo anticheat lol
|
||||||
|
[](EntityRef host, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(host, stack, ETBU_DEL);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
player->addBuff(&gumballBuff);
|
player->addBuff(&gumballBuff);
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ struct Player : public Entity, public ICombatant {
|
|||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
|
|
||||||
virtual void addBuff(BuffStack* buff) override;
|
virtual void addBuff(BuffStack* buff) override;
|
||||||
virtual BuffStack* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
|
Loading…
Reference in New Issue
Block a user