mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 21:40:05 +00:00
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 - ???
This commit is contained in:
parent
df1b4ff160
commit
cc190efc63
2
Makefile
2
Makefile
@ -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\
|
||||||
|
@ -35,13 +35,13 @@ std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int3
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector<EntityRef> targets) {
|
void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector<EntityRef> targets) {
|
||||||
for (EntityRef target : targets) {
|
for (EntityRef target : targets) {
|
||||||
Entity* entity = target.getEntity();
|
Entity* entity = target.getEntity();
|
||||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
||||||
continue; // not a combatant
|
continue; // not a combatant
|
||||||
|
|
||||||
|
// TODO abilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ namespace Abilities {
|
|||||||
extern std::map<int32_t, SkillData> SkillTable;
|
extern std::map<int32_t, SkillData> SkillTable;
|
||||||
|
|
||||||
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
||||||
void applyAbility(SkillData*, EntityRef, std::vector<EntityRef>);
|
void useAbility(SkillData*, EntityRef, std::vector<EntityRef>);
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
30
src/Buffs.cpp
Normal file
30
src/Buffs.cpp
Normal file
@ -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);
|
||||||
|
}
|
122
src/Buffs.hpp
Normal file
122
src/Buffs.hpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
/* 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<BuffStack> stacks;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void onTick() {
|
||||||
|
assert(!stacks.empty());
|
||||||
|
|
||||||
|
std::set<BuffCallback> 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<BuffCallback> 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);
|
||||||
|
}
|
107
src/Combat.cpp
107
src/Combat.cpp
@ -8,14 +8,53 @@
|
|||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
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) {
|
int Player::takeDamage(EntityRef src, int amt) {
|
||||||
HP -= amt;
|
HP -= amt;
|
||||||
return amt;
|
return amt;
|
||||||
@ -41,6 +80,22 @@ void Player::step(time_t currTime) {
|
|||||||
// no-op
|
// 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) {
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||||
|
|
||||||
hp -= amt;
|
hp -= amt;
|
||||||
@ -274,17 +329,22 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
|||||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
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->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
|
// infection debuff toggles as the client asks it to,
|
||||||
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
|
// so we add and remove a permanent debuff
|
||||||
|
if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) {
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
BuffStack infection = {
|
||||||
|
ECSB_INFECTION,
|
||||||
pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID
|
-1, // infinite
|
||||||
pkt1.eTBU = 1; // eTimeBuffUpdate
|
sock, // self-inflicted
|
||||||
pkt1.eTBT = 0; // eTimeBuffType 1 means nano
|
BuffClass::OTHER,
|
||||||
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
|
Buffs::timeBuffUpdateAdd,
|
||||||
|
nullptr, // client ticks for us! todo anticheat lol
|
||||||
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
Buffs::timeBuffUpdateDelete
|
||||||
|
};
|
||||||
|
plr->addBuff(&infection);
|
||||||
|
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
||||||
|
plr->removeBuff(ECSB_INFECTION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dealGooDamage(CNSocket *sock, int amount) {
|
static void dealGooDamage(CNSocket *sock, int amount) {
|
||||||
@ -298,12 +358,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));
|
||||||
|
|
||||||
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
|
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->buffClass == 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;
|
||||||
@ -327,7 +388,7 @@ 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);
|
||||||
@ -635,7 +696,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// fm patch/lake damage
|
// fm patch/lake damage
|
||||||
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION)
|
if ((plr->hasBuff(ECSB_INFECTION))
|
||||||
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||||
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
|
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
|
||||||
|
|
||||||
@ -663,7 +724,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
drainRate = skill->batteryUse[boost * 3];
|
drainRate = skill->batteryUse[boost * 3];
|
||||||
|
|
||||||
if (skill->drainType == SkillDrainType::PASSIVE) {
|
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
// passive buff
|
// apply passive buff
|
||||||
std::vector<EntityRef> targets;
|
std::vector<EntityRef> targets;
|
||||||
if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr)
|
if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr)
|
||||||
targets = plr->group->members; // group
|
targets = plr->group->members; // group
|
||||||
@ -671,6 +732,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
targets.push_back(sock); // self
|
targets.push_back(sock); // self
|
||||||
|
|
||||||
std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl;
|
std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl;
|
||||||
|
// TODO abilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,6 +765,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));
|
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) {
|
if (transmit) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||||
|
|
||||||
@ -723,7 +796,7 @@ static void playerTick(CNServer *serv, time_t 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);
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
skill->eCT = 1;
|
skill->eCT = 1;
|
||||||
skill->iID = plr->iID;
|
skill->iID = plr->iID;
|
||||||
skill->iConditionBitFlag = plr->iConditionBitFlag;
|
skill->iConditionBitFlag = plr->getCompositeCondition();
|
||||||
}
|
}
|
||||||
|
|
||||||
skillUse->iNPC_ID = eggId;
|
skillUse->iNPC_ID = eggId;
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
struct BuffStack;
|
||||||
|
|
||||||
enum EntityKind {
|
enum EntityKind {
|
||||||
INVALID,
|
INVALID,
|
||||||
PLAYER,
|
PLAYER,
|
||||||
@ -88,6 +92,11 @@ public:
|
|||||||
ICombatant() {}
|
ICombatant() {}
|
||||||
virtual ~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 int takeDamage(EntityRef, int) = 0;
|
||||||
virtual void heal(EntityRef, int) = 0;
|
virtual void heal(EntityRef, int) = 0;
|
||||||
virtual bool isAlive() = 0;
|
virtual bool isAlive() = 0;
|
||||||
@ -149,6 +158,11 @@ 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 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 int takeDamage(EntityRef src, int amt) override;
|
||||||
virtual void heal(EntityRef src, int amt) override;
|
virtual void heal(EntityRef src, int amt) override;
|
||||||
virtual bool isAlive() override;
|
virtual bool isAlive() override;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <string.h> // for memset()
|
#include <string.h> // for memset()
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -484,27 +485,27 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp->eST = EST_NANOSTIMPAK;
|
resp->eST = EST_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
|
eCSB,
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||||
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1;
|
sock,
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
BuffClass::CONSUMABLE,
|
||||||
|
Buffs::timeBuffUpdateAdd,
|
||||||
|
nullptr,
|
||||||
|
Buffs::timeBuffUpdateDelete,
|
||||||
|
};
|
||||||
|
player->addBuff(&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)Abilities::SkillTable[144].durationTime[0] * 100;
|
|
||||||
Eggs::EggBuffs[key] = until;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
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) {
|
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;
|
||||||
@ -772,7 +773,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;
|
||||||
|
@ -163,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;
|
||||||
|
@ -187,7 +187,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;
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ 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) {
|
||||||
if (!MobAI::aggroCheck(mob, getTime()))
|
if (!MobAI::aggroCheck(mob, getTime()))
|
||||||
|
@ -184,7 +184,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;
|
||||||
}
|
}
|
||||||
@ -209,16 +209,8 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||||
|
|
||||||
// Unbuff gumballs
|
// Unbuff gumballs
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum;
|
int buffId = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum;
|
||||||
if (plr->iConditionBitFlag & value1) {
|
plr->removeBuff(buffId);
|
||||||
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])
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
#include <vector>
|
#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))
|
||||||
@ -32,9 +36,8 @@ struct Player : public Entity, public ICombatant {
|
|||||||
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;
|
||||||
@ -84,6 +87,11 @@ struct Player : public Entity, public ICombatant {
|
|||||||
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 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 int takeDamage(EntityRef src, int amt) override;
|
||||||
virtual void heal(EntityRef src, int amt) override;
|
virtual void heal(EntityRef src, int amt) override;
|
||||||
virtual bool isAlive() override;
|
virtual bool isAlive() override;
|
||||||
|
@ -41,6 +41,11 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
Player* plr = getPlayer(key);
|
Player* plr = getPlayer(key);
|
||||||
uint64_t fromInstance = plr->instanceID;
|
uint64_t fromInstance = plr->instanceID;
|
||||||
|
|
||||||
|
// free buff memory
|
||||||
|
for(auto buffEntry : plr->buffs)
|
||||||
|
delete buffEntry.second;
|
||||||
|
|
||||||
|
// leave group
|
||||||
if(plr->group != nullptr)
|
if(plr->group != nullptr)
|
||||||
Groups::groupKick(plr);
|
Groups::groupKick(plr);
|
||||||
|
|
||||||
@ -402,7 +407,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
switch ((ePCRegenType)reviveData->iRegenType) {
|
switch ((ePCRegenType)reviveData->iRegenType) {
|
||||||
case ePCRegenType::HereByPhoenix: // nano revive
|
case ePCRegenType::HereByPhoenix: // nano revive
|
||||||
if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX))
|
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
// TODO ABILITIES
|
// TODO ABILITIES
|
||||||
|
@ -118,6 +118,13 @@ enum {
|
|||||||
ECSTB__END = 26,
|
ECSTB__END = 26,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ETBU_NONE = 0,
|
||||||
|
ETBU_ADD = 1,
|
||||||
|
ETBU_DEL = 2,
|
||||||
|
ETBU_CHANGE = 3
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SUCC = 1,
|
SUCC = 1,
|
||||||
FAIL = 0,
|
FAIL = 0,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
||||||
|
#define MS_PER_PLAYER_TICK 2000
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user