mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-24 22:11:04 +00:00
[refactor] 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
d32827b692
commit
31677e2638
2
Makefile
2
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\
|
||||
|
@ -2,17 +2,260 @@
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Nanos.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Abilities;
|
||||
|
||||
std::map<int32_t, SkillData> Abilities::SkillTable;
|
||||
|
||||
/*
|
||||
// New email notification
|
||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
||||
resp.iNewEmailCnt = Database::getUnreadEmailCount(PlayerManager::getPlayer(sock)->iID);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_NEW_EMAIL);
|
||||
#pragma region Skill handlers
|
||||
static SkillResult handleSkillDamage(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
double scalingFactor = 1;
|
||||
if(sourceRef.kind == EntityKind::PLAYER)
|
||||
scalingFactor = std::max(source->getMaxHP(), target->getMaxHP()) / 1000.0;
|
||||
else
|
||||
scalingFactor = source->getMaxHP() / 1500.0;
|
||||
|
||||
int damage = (int)(skill->values[0][power] * scalingFactor);
|
||||
int dealt = target->takeDamage(sourceRef, damage);
|
||||
|
||||
sSkillResult_Damage result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = dealt <= 0;
|
||||
result.iDamage = dealt;
|
||||
result.iHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Damage), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
int heal = skill->values[0][power];
|
||||
int healed = target->heal(sourceRef, heal);
|
||||
|
||||
sSkillResult_Heal_HP result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iHealHP = healed;
|
||||
result.iHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Heal_HP), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
// TODO abilities
|
||||
sSkillResult_Damage_N_Debuff result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = false;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
sSkillResult_Buff result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = false;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
return SkillResult(sizeof(sSkillResult_Buff), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
if(target->getCharType() != 1)
|
||||
return SkillResult(); // only Players are valid targets for battery drain
|
||||
Player* plr = dynamic_cast<Player*>(target);
|
||||
|
||||
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||
|
||||
int boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
|
||||
int potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
|
||||
sSkillResult_BatteryDrain result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||
result.iDrainW = boostDrain;
|
||||
result.iBatteryW = plr->batteryW;
|
||||
result.iDrainN = potionDrain;
|
||||
result.iBatteryN = plr->batteryN;
|
||||
result.iStamina = plr->getActiveNano()->iStamina;
|
||||
result.bNanoDeactive = plr->getActiveNano()->iStamina <= 0;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
return SkillResult(sizeof(sSkillResult_BatteryDrain), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
if(source->getCharType() != 1)
|
||||
return SkillResult(); // only Players are valid sources for recall
|
||||
Player* plr = dynamic_cast<Player*>(source);
|
||||
|
||||
sSkillResult_Move result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iMapNum = plr->recallInstance;
|
||||
result.iMoveX = plr->recallX;
|
||||
result.iMoveY = plr->recallY;
|
||||
result.iMoveZ = plr->recallZ;
|
||||
return SkillResult(sizeof(sSkillResult_Move), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
sSkillResult_Resurrect result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.iRegenHP = target->getCurrentHP();
|
||||
return SkillResult(sizeof(sSkillResult_Resurrect), &result);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
||||
size_t resultSize = 0;
|
||||
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
||||
std::vector<SkillResult> results;
|
||||
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case EST_DAMAGE:
|
||||
resultSize = sizeof(sSkillResult_Damage);
|
||||
skillHandler = handleSkillDamage;
|
||||
break;
|
||||
case EST_HEAL_HP:
|
||||
case EST_RETURNHOMEHEAL:
|
||||
resultSize = sizeof(sSkillResult_Heal_HP);
|
||||
skillHandler = handleSkillHealHP;
|
||||
break;
|
||||
case EST_JUMP:
|
||||
case EST_RUN:
|
||||
case EST_FREEDOM:
|
||||
case EST_PHOENIX:
|
||||
case EST_INVULNERABLE:
|
||||
case EST_MINIMAPENEMY:
|
||||
case EST_MINIMAPTRESURE:
|
||||
case EST_NANOSTIMPAK:
|
||||
case EST_PROTECTBATTERY:
|
||||
case EST_PROTECTINFECTION:
|
||||
case EST_REWARDBLOB:
|
||||
case EST_REWARDCASH:
|
||||
case EST_STAMINA_SELF:
|
||||
case EST_STEALTH:
|
||||
resultSize = sizeof(sSkillResult_Buff);
|
||||
skillHandler = handleSkillBuff;
|
||||
break;
|
||||
case EST_BATTERYDRAIN:
|
||||
resultSize = sizeof(sSkillResult_BatteryDrain);
|
||||
skillHandler = handleSkillBatteryDrain;
|
||||
break;
|
||||
case EST_RECALL:
|
||||
case EST_RECALL_GROUP:
|
||||
resultSize = sizeof(sSkillResult_Move);
|
||||
skillHandler = handleSkillMove;
|
||||
break;
|
||||
case EST_PHOENIX_GROUP:
|
||||
resultSize = sizeof(sSkillResult_Resurrect);
|
||||
skillHandler = handleSkillResurrect;
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl;
|
||||
return results;
|
||||
}
|
||||
assert(skillHandler != nullptr);
|
||||
|
||||
for(ICombatant* target : targets) {
|
||||
assert(target != nullptr);
|
||||
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
||||
if(result.size == 0) continue; // skill not applicable
|
||||
if(result.size != resultSize) {
|
||||
std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||
continue;
|
||||
}
|
||||
results.push_back(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
|
||||
for(SkillResult& result : results) {
|
||||
memcpy(pivot, result.payload, resultSize);
|
||||
pivot += resultSize;
|
||||
}
|
||||
}
|
||||
|
||||
void Abilities::useNanoSkill(CNSocket* sock, sNano& nano, std::vector<ICombatant*> affected) {
|
||||
if(SkillTable.count(nano.iSkillID) == 0)
|
||||
return;
|
||||
|
||||
SkillData* skill = &SkillTable[nano.iSkillID];
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, Nanos::getNanoBoost(plr), plr, affected);
|
||||
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||
pkt->iPC_ID = plr->iID;
|
||||
pkt->iNanoID = nano.iID;
|
||||
pkt->iSkillID = nano.iSkillID;
|
||||
pkt->iNanoStamina = nano.iStamina;
|
||||
pkt->bNanoDeactive = nano.iStamina <= 0;
|
||||
pkt->eST = skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
|
||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
}
|
||||
|
||||
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
||||
if(SkillTable.count(skillID) == 0)
|
||||
return;
|
||||
|
||||
Entity* entity = npc.getEntity();
|
||||
ICombatant* src = nullptr;
|
||||
if(npc.kind == EntityKind::COMBAT_NPC || npc.kind == EntityKind::MOB)
|
||||
src = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
SkillData* skill = &SkillTable[skillID];
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
pkt->iNPC_ID = npc.id;
|
||||
pkt->iSkillID = skillID;
|
||||
pkt->eST = skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
|
||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
}
|
||||
*/
|
||||
|
||||
std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
|
||||
|
||||
@ -35,16 +278,65 @@ std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int3
|
||||
return targets;
|
||||
}
|
||||
|
||||
void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector<EntityRef> targets) {
|
||||
for (EntityRef target : targets) {
|
||||
Entity* entity = target.getEntity();
|
||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
||||
continue; // not a combatant
|
||||
|
||||
|
||||
/* ripped from client (enums emplaced) */
|
||||
int Abilities::getCSTBFromST(int eSkillType) {
|
||||
int result = 0;
|
||||
switch (eSkillType)
|
||||
{
|
||||
case EST_RUN:
|
||||
result = ECSB_UP_MOVE_SPEED;
|
||||
break;
|
||||
case EST_JUMP:
|
||||
result = ECSB_UP_JUMP_HEIGHT;
|
||||
break;
|
||||
case EST_STEALTH:
|
||||
result = ECSB_UP_STEALTH;
|
||||
break;
|
||||
case EST_PHOENIX:
|
||||
result = ECSB_PHOENIX;
|
||||
break;
|
||||
case EST_PROTECTBATTERY:
|
||||
result = ECSB_PROTECT_BATTERY;
|
||||
break;
|
||||
case EST_PROTECTINFECTION:
|
||||
result = ECSB_PROTECT_INFECTION;
|
||||
break;
|
||||
case EST_SNARE:
|
||||
result = ECSB_DN_MOVE_SPEED;
|
||||
break;
|
||||
case EST_SLEEP:
|
||||
result = ECSB_MEZ;
|
||||
break;
|
||||
case EST_MINIMAPENEMY:
|
||||
result = ECSB_MINIMAP_ENEMY;
|
||||
break;
|
||||
case EST_MINIMAPTRESURE:
|
||||
result = ECSB_MINIMAP_TRESURE;
|
||||
break;
|
||||
case EST_REWARDBLOB:
|
||||
result = ECSB_REWARD_BLOB;
|
||||
break;
|
||||
case EST_REWARDCASH:
|
||||
result = ECSB_REWARD_CASH;
|
||||
break;
|
||||
case EST_INFECTIONDAMAGE:
|
||||
result = ECSB_INFECTION;
|
||||
break;
|
||||
case EST_FREEDOM:
|
||||
result = ECSB_FREEDOM;
|
||||
break;
|
||||
case EST_BOUNDINGBALL:
|
||||
result = ECSB_BOUNDINGBALL;
|
||||
break;
|
||||
case EST_INVULNERABLE:
|
||||
result = ECSB_INVULNERABLE;
|
||||
break;
|
||||
case EST_BUFFHEAL:
|
||||
result = ECSB_HEAL;
|
||||
break;
|
||||
case EST_NANOSTIMPAK:
|
||||
result = ECSB_STIMPAKSLOT1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Abilities::init() {
|
||||
//REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck);
|
||||
return result;
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Entities.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||
|
||||
enum class SkillEffectTarget {
|
||||
POINT = 1,
|
||||
SELF = 2,
|
||||
@ -25,8 +30,20 @@ enum class SkillDrainType {
|
||||
PASSIVE = 2
|
||||
};
|
||||
|
||||
struct SkillResult {
|
||||
size_t size;
|
||||
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||
SkillResult(size_t len, void* dat) {
|
||||
size = len;
|
||||
memcpy(payload, dat, len);
|
||||
}
|
||||
SkillResult() {
|
||||
size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct SkillData {
|
||||
int skillType;
|
||||
int skillType; // eST
|
||||
SkillEffectTarget effectTarget;
|
||||
int effectType; // always 1?
|
||||
SkillTargetType targetType;
|
||||
@ -43,8 +60,9 @@ struct SkillData {
|
||||
namespace Abilities {
|
||||
extern std::map<int32_t, SkillData> SkillTable;
|
||||
|
||||
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
||||
void applyAbility(SkillData*, EntityRef, std::vector<EntityRef>);
|
||||
void useNanoSkill(CNSocket*, sNano&, std::vector<ICombatant*>);
|
||||
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||
|
||||
void init();
|
||||
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
||||
int getCSTBFromST(int eSkillType);
|
||||
}
|
||||
|
151
src/Buffs.cpp
Normal file
151
src/Buffs.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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::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
|
||||
pkt.eCT = combatant->getCharType();
|
||||
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));
|
||||
}
|
||||
#pragma endregion
|
90
src/Buffs.hpp
Normal file
90
src/Buffs.hpp
Normal file
@ -0,0 +1,90 @@
|
||||
#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);
|
||||
|
||||
/*
|
||||
* 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 timeBuffTimeout(EntityRef self);
|
||||
}
|
329
src/Combat.cpp
329
src/Combat.cpp
@ -8,21 +8,84 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "Abilities.hpp"
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
|
||||
using namespace Combat;
|
||||
|
||||
/// Player Id -> Bullet Id -> Bullet
|
||||
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
||||
|
||||
int Player::takeDamage(EntityRef src, int amt) {
|
||||
HP -= amt;
|
||||
return amt;
|
||||
#pragma region Player
|
||||
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||
|
||||
if(!hasBuff(buffId)) {
|
||||
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::heal(EntityRef src, int amt) {
|
||||
// stubbed
|
||||
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, int buffClass) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear((BuffClass)buffClass);
|
||||
if(buffs[buffId]->isStale()) {
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -33,25 +96,107 @@ 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) {
|
||||
// no-op
|
||||
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);
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CombatNPC
|
||||
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||
return false;
|
||||
}
|
||||
|
||||
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
||||
|
||||
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
||||
|
||||
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
||||
return false;
|
||||
}
|
||||
|
||||
int CombatNPC::getCompositeCondition() { /* stubbed */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||
int dmg = amt;
|
||||
if(hp - dmg < 0) dmg = hp;
|
||||
hp -= dmg;
|
||||
|
||||
hp -= amt;
|
||||
if (hp <= 0)
|
||||
transition(AIState::DEAD, src);
|
||||
if(hp <= 0) transition(AIState::DEAD, src);
|
||||
|
||||
return amt;
|
||||
return dmg;
|
||||
}
|
||||
|
||||
void CombatNPC::heal(EntityRef src, int amt) {
|
||||
// stubbed
|
||||
int CombatNPC::heal(EntityRef src, int amt) {
|
||||
int heal = amt;
|
||||
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
|
||||
hp += heal;
|
||||
|
||||
return heal;
|
||||
}
|
||||
|
||||
bool CombatNPC::isAlive() {
|
||||
@ -62,10 +207,37 @@ 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())
|
||||
@ -91,6 +263,7 @@ void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||
event.handler(src, this);
|
||||
*/
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||
bool batteryBoost, int attackerStyle,
|
||||
@ -299,40 +472,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
||||
plr->healCooldown = 4000;
|
||||
}
|
||||
|
||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
||||
static void dealGooDamage(CNSocket *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);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
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) {
|
||||
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
|
||||
dmg->bProtected = 1;
|
||||
|
||||
// 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;
|
||||
} else {
|
||||
plr->HP -= amount;
|
||||
@ -356,12 +516,39 @@ 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
@ -649,6 +836,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
static void playerTick(CNServer *serv, time_t currTime) {
|
||||
static time_t lastHealTime = 0;
|
||||
static time_t lastCombatTIme = 0;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
CNSocket *sock = pair.first;
|
||||
@ -657,17 +845,12 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
|
||||
// group ticks
|
||||
if (plr->group != nullptr)
|
||||
Groups::groupTickInfo(plr);
|
||||
Groups::groupTickInfo(sock);
|
||||
|
||||
// do not tick dead players
|
||||
if (plr->HP <= 0)
|
||||
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
|
||||
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
||||
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
||||
@ -679,44 +862,21 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
plr->healCooldown -= 4000;
|
||||
}
|
||||
|
||||
// combat tick
|
||||
if(currTime - lastCombatTIme >= 2000) {
|
||||
plr->step(currTime);
|
||||
transmit = true;
|
||||
}
|
||||
|
||||
// nanos
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // tick active nano
|
||||
sNano& nano = plr->Nanos[plr->activeNano];
|
||||
int drainRate = 0;
|
||||
|
||||
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
|
||||
if (plr->activeNano != 0) { // tick active nano
|
||||
sNano* nano = plr->getActiveNano();
|
||||
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||
// nano has skill data
|
||||
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
|
||||
int boost = Nanos::getNanoBoost(plr);
|
||||
drainRate = skill->batteryUse[boost * 3];
|
||||
|
||||
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// passive buff
|
||||
std::vector<EntityRef> targets;
|
||||
if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr)
|
||||
targets = plr->group->members; // group
|
||||
else if(skill->targetType == SkillTargetType::SELF)
|
||||
targets.push_back(sock); // self
|
||||
|
||||
std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
nano.iStamina -= 1 + drainRate / 5;
|
||||
|
||||
if (nano.iStamina <= 0)
|
||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
||||
|
||||
transmit = true;
|
||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // tick resting nano
|
||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
||||
nano.iStamina += 1;
|
||||
|
||||
if (nano.iStamina > 150)
|
||||
nano.iStamina = 150;
|
||||
|
||||
transmit = true;
|
||||
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||
Nanos::applyNanoBuff(skill, plr);
|
||||
// ^ composite condition calculation is separate from combat for responsiveness
|
||||
}
|
||||
}
|
||||
|
||||
@ -732,6 +892,19 @@ 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()) {
|
||||
Buff* buff = (*it).second;
|
||||
buff->tick(currTime);
|
||||
if(buff->isStale()) {
|
||||
// garbage collect
|
||||
it = plr->buffs.erase(it);
|
||||
delete buff;
|
||||
}
|
||||
else it++;
|
||||
}
|
||||
|
||||
if (transmit) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||
|
||||
@ -746,13 +919,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)
|
||||
lastHealTime = currTime;
|
||||
if(currTime - lastCombatTIme >= 2000)
|
||||
lastCombatTIme = 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);
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "Missions.hpp"
|
||||
#include "Eggs.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <limits.h>
|
||||
@ -501,9 +502,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
|
||||
if (*tmp)
|
||||
return;
|
||||
|
||||
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0)
|
||||
if (Abilities::SkillTable.count(skillId) == 0) {
|
||||
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) {
|
||||
|
141
src/Eggs.cpp
141
src/Eggs.cpp
@ -13,118 +13,52 @@
|
||||
|
||||
using namespace Eggs;
|
||||
|
||||
/// sock, CBFlag -> until
|
||||
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
|
||||
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);
|
||||
|
||||
// TODO ABILITIES
|
||||
//int bitFlag = plr->group->conditionBitFlag;
|
||||
int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag);
|
||||
// eggId might be 0 if the buff is made by the /buff command
|
||||
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
||||
|
||||
size_t resplen;
|
||||
|
||||
if (skillId == 183) {
|
||||
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) * Abilities::SkillTable[skillId].values[0][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) * Abilities::SkillTable[skillId].values[0][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;
|
||||
if(Abilities::SkillTable.count(skillId) == 0) {
|
||||
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
skillUse->iNPC_ID = eggId;
|
||||
skillUse->iSkillID = skillId;
|
||||
skillUse->eST = Abilities::SkillTable[skillId].skillType;
|
||||
skillUse->iTargetCnt = 1;
|
||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// apply buff
|
||||
if(skill->targetType != SkillTargetType::SELF) {
|
||||
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);
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
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)
|
||||
return -1;
|
||||
|
||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag);
|
||||
|
||||
// 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;
|
||||
// use skill
|
||||
std::vector<ICombatant*> targets;
|
||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||
Abilities::useNPCSkill(src, skillId, targets);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
//int groupFlags = plr->group->conditionBitFlag;
|
||||
// TODO ABILITIES
|
||||
//for (auto& pwr : Abilities::Powers) {
|
||||
// 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
|
||||
for (auto npc : NPCManager::NPCs) {
|
||||
if (npc.second->kind != EntityKind::EGG)
|
||||
@ -134,7 +68,7 @@ static void eggStep(CNServer* serv, time_t currTime) {
|
||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||
continue;
|
||||
|
||||
if (egg->deadUntil <= timeStamp) {
|
||||
if (egg->deadUntil <= currTime) {
|
||||
// respawn it
|
||||
egg->dead = false;
|
||||
egg->deadUntil = 0;
|
||||
@ -191,16 +125,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
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
|
||||
* (buff icon pops up in the bottom of the screen)
|
||||
* so we don't send it for non-effect
|
||||
*/
|
||||
if (type->effectId != 0) {
|
||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||
resp.iSkillID = type->effectId;
|
||||
|
||||
|
@ -10,12 +10,10 @@ struct EggType {
|
||||
};
|
||||
|
||||
namespace Eggs {
|
||||
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
|
||||
extern std::unordered_map<int, EggType> EggTypes;
|
||||
|
||||
void init();
|
||||
|
||||
/// returns -1 on fail
|
||||
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||
}
|
||||
|
@ -83,6 +83,10 @@ void Egg::enterIntoViewOf(CNSocket *sock) {
|
||||
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
||||
}
|
||||
|
||||
sNano* Player::getActiveNano() {
|
||||
return &Nanos[activeNano];
|
||||
}
|
||||
|
||||
sPCAppearanceData Player::getAppearanceData() {
|
||||
sPCAppearanceData data = {};
|
||||
data.iID = iID;
|
||||
|
@ -3,18 +3,16 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "Chunking.hpp"
|
||||
|
||||
#include "EntityRef.hpp"
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
enum EntityKind {
|
||||
INVALID,
|
||||
PLAYER,
|
||||
SIMPLE_NPC,
|
||||
COMBAT_NPC,
|
||||
MOB,
|
||||
EGG,
|
||||
BUS
|
||||
};
|
||||
/* forward declaration(s) */
|
||||
class Chunk;
|
||||
struct Group;
|
||||
|
||||
enum class AIState {
|
||||
INACTIVE,
|
||||
@ -24,9 +22,6 @@ enum class AIState {
|
||||
DEAD
|
||||
};
|
||||
|
||||
class Chunk;
|
||||
struct Group;
|
||||
|
||||
struct Entity {
|
||||
EntityKind kind = EntityKind::INVALID;
|
||||
int x = 0, y = 0, z = 0;
|
||||
@ -44,42 +39,6 @@ struct Entity {
|
||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Interfaces
|
||||
*/
|
||||
@ -88,11 +47,22 @@ public:
|
||||
ICombatant() {}
|
||||
virtual ~ICombatant() {}
|
||||
|
||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||
virtual Buff* getBuff(int) = 0;
|
||||
virtual void removeBuff(int) = 0;
|
||||
virtual void removeBuff(int, int) = 0;
|
||||
virtual bool hasBuff(int) = 0;
|
||||
virtual int getCompositeCondition() = 0;
|
||||
virtual int takeDamage(EntityRef, int) = 0;
|
||||
virtual void heal(EntityRef, int) = 0;
|
||||
virtual int heal(EntityRef, int) = 0;
|
||||
virtual bool isAlive() = 0;
|
||||
virtual int getCurrentHP() = 0;
|
||||
virtual int getMaxHP() = 0;
|
||||
virtual int getLevel() = 0;
|
||||
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||
virtual int32_t getCharType() = 0;
|
||||
virtual int32_t getID() = 0;
|
||||
virtual EntityRef getRef() = 0;
|
||||
virtual void step(time_t currTime) = 0;
|
||||
};
|
||||
|
||||
@ -109,6 +79,7 @@ public:
|
||||
bool loopingPath = false;
|
||||
|
||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||
kind = EntityKind::SIMPLE_NPC;
|
||||
type = t;
|
||||
hp = 400;
|
||||
angle = _A;
|
||||
@ -143,17 +114,30 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
spawnY = y;
|
||||
spawnZ = z;
|
||||
|
||||
kind = EntityKind::COMBAT_NPC;
|
||||
|
||||
stateHandlers[AIState::INACTIVE] = {};
|
||||
transitionHandlers[AIState::INACTIVE] = {};
|
||||
}
|
||||
|
||||
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, int buffClass) 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 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);
|
||||
|
56
src/EntityRef.hpp
Normal file
56
src/EntityRef.hpp
Normal 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;
|
||||
}
|
||||
};
|
355
src/Groups.cpp
355
src/Groups.cpp
@ -15,7 +15,54 @@
|
||||
|
||||
using namespace Groups;
|
||||
|
||||
void Groups::addToGroup(EntityRef member, Group* group) {
|
||||
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;
|
||||
@ -29,37 +76,102 @@ void Groups::addToGroup(EntityRef member, Group* group) {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::removeFromGroup(EntityRef member, Group* group) {
|
||||
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] Adding a weird entity type to a group" << std::endl;
|
||||
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;
|
||||
return;
|
||||
} else {
|
||||
group->members.erase(it);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (group->members.empty()) delete group; // cleanup memory
|
||||
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!!
|
||||
std::vector<EntityRef> members = group->members;
|
||||
for (EntityRef member : members) {
|
||||
removeFromGroup(member, group);
|
||||
bool done = false;
|
||||
while(!done) {
|
||||
EntityRef back = group->members.back();
|
||||
done = removeFromGroup(group, back);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +226,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||
|
||||
if (otherPlr == nullptr)
|
||||
return;
|
||||
return; // disconnect or something
|
||||
|
||||
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||
|
||||
@ -125,220 +237,69 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), size + 1, sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherPlr->group == nullptr) {
|
||||
// create group
|
||||
otherPlr->group = new Group(); // spooky
|
||||
addToGroup(PlayerManager::getSockFromID(recv->iID_From), otherPlr->group);
|
||||
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||
otherPlr->group = new Group(otherPlrRef);
|
||||
}
|
||||
addToGroup(sock, otherPlr->group);
|
||||
auto players = otherPlr->group->filter(EntityKind::PLAYER);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + players.size() * 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 = players.size();
|
||||
|
||||
//int bitFlag = otherPlr->group->conditionBitFlag;
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
|
||||
Player* varPlr = PlayerManager::getPlayer(players[i].sock);
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
|
||||
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
|
||||
// TODO ABILITIES
|
||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
|
||||
if (Abilities::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);*/
|
||||
}
|
||||
}
|
||||
|
||||
Groups::sendToGroup(otherPlr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||
addToGroup(otherPlr->group, sock);
|
||||
}
|
||||
|
||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
groupKick(plr);
|
||||
groupKick(plr->group, sock);
|
||||
}
|
||||
|
||||
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sock = players[i].sock;
|
||||
sock->sendPacket(buf, type, size);
|
||||
for (EntityRef ref : players) {
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupTickInfo(Player* plr) {
|
||||
|
||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), players.size(), sizeof(sPCGroupMemberInfo))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
||||
return;
|
||||
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef ref : players) {
|
||||
if(ref != excluded) ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + players.size() * sizeof(sPCGroupMemberInfo);
|
||||
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();
|
||||
|
||||
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;
|
||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
||||
|
||||
resp->iID = plr->iID;
|
||||
resp->iMemberPCCnt = players.size();
|
||||
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
EntityRef member = players[i];
|
||||
Player* varPlr = PlayerManager::getPlayer(member.sock);
|
||||
|
||||
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];
|
||||
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||
|| !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;
|
||||
} else {
|
||||
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||
attachGroupData(pcs, npcs, pivot);
|
||||
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||
}
|
||||
}
|
||||
|
||||
sendToGroup(plr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
|
||||
}
|
||||
|
||||
static void groupUnbuff(Player* plr) {
|
||||
Group* group = plr->group;
|
||||
for (int i = 0; i < group->members.size(); i++) {
|
||||
for (int n = 0; n < group->members.size(); n++) {
|
||||
if (i == n)
|
||||
continue;
|
||||
|
||||
EntityRef other = group->members[n];
|
||||
|
||||
// TODO ABILITIES
|
||||
//Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Groups::groupKick(Player* plr) {
|
||||
Group* group = plr->group;
|
||||
void Groups::groupKick(Group* group, EntityRef ref) {
|
||||
|
||||
// if you are the group leader, destroy your own group and kick everybody
|
||||
if (plr->group->members[0] == PlayerManager::getSockFromID(plr->iID)) {
|
||||
groupUnbuff(plr);
|
||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
||||
sendToGroup(plr->group, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
||||
disbandGroup(plr->group);
|
||||
if (group->members[0] == ref) {
|
||||
disbandGroup(group);
|
||||
return;
|
||||
}
|
||||
|
||||
auto players = group->filter(EntityKind::PLAYER);
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), players.size() - 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) + (players.size() - 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 = players.size() - 1;
|
||||
|
||||
int bitFlag = 0; // TODO ABILITIES getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
|
||||
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
||||
|
||||
if (sock == nullptr)
|
||||
return;
|
||||
|
||||
removeFromGroup(sock, group);
|
||||
|
||||
players = group->filter(EntityKind::PLAYER);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* varPlr = PlayerManager::getPlayer(sock);
|
||||
|
||||
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
|
||||
|
||||
// remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
|
||||
// TODO ABILITIES
|
||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
if (Abilities::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);*/
|
||||
}
|
||||
|
||||
sendToGroup(group, (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));
|
||||
removeFromGroup(group, ref);
|
||||
}
|
||||
|
||||
void Groups::init() {
|
||||
|
@ -19,16 +19,19 @@ struct Group {
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
Group(EntityRef leader);
|
||||
};
|
||||
|
||||
namespace Groups {
|
||||
void init();
|
||||
|
||||
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(Player* plr);
|
||||
void groupKick(Player* plr);
|
||||
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||
void groupTickInfo(CNSocket* sock);
|
||||
|
||||
void addToGroup(EntityRef member, Group* group);
|
||||
void removeFromGroup(EntityRef member, Group* group);
|
||||
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);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Eggs.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Buffs.hpp"
|
||||
|
||||
#include <string.h> // for memset()
|
||||
#include <assert.h>
|
||||
@ -484,27 +485,31 @@ 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 = {
|
||||
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||
0,
|
||||
sock,
|
||||
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);
|
||||
// update inventory serverside
|
||||
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) {
|
||||
@ -757,7 +762,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 +777,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;
|
||||
|
@ -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;
|
||||
|
@ -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<int> 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()))
|
||||
|
@ -81,7 +81,7 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I,
|
||||
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++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
|
@ -42,7 +42,7 @@ namespace NPCManager {
|
||||
void destroyNPC(int32_t);
|
||||
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);
|
||||
|
||||
|
@ -69,6 +69,52 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
||||
}
|
||||
|
||||
std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
|
||||
assert(skill->drainType == SkillDrainType::PASSIVE);
|
||||
|
||||
EntityRef self = PlayerManager::getSockFromID(plr->iID);
|
||||
std::vector<ICombatant*> affected;
|
||||
std::vector<EntityRef> targets;
|
||||
if (skill->targetType == SkillTargetType::GROUP) {
|
||||
targets = plr->getGroupMembers(); // group
|
||||
}
|
||||
else if(skill->targetType == SkillTargetType::SELF) {
|
||||
targets.push_back(self); // self
|
||||
} else {
|
||||
std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl;
|
||||
}
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
int boost = Nanos::getNanoBoost(plr) ? 3 : 0;
|
||||
int value = skill->values[0][boost];
|
||||
|
||||
BuffStack passiveBuff = {
|
||||
1, // passive nano buffs refreshed every tick
|
||||
value,
|
||||
self,
|
||||
BuffClass::NONE, // overwritten per target
|
||||
};
|
||||
|
||||
for (EntityRef target : targets) {
|
||||
Entity* entity = target.getEntity();
|
||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
||||
continue; // not a combatant
|
||||
|
||||
passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO;
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
if(combatant->addBuff(timeBuffId,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&passiveBuff)) affected.push_back(combatant);
|
||||
}
|
||||
|
||||
return affected;
|
||||
}
|
||||
|
||||
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||
resp.iActiveNanoSlotNum = slot;
|
||||
@ -86,10 +132,16 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
return; // sanity check
|
||||
|
||||
plr->activeNano = nanoID;
|
||||
sNano& nano = plr->Nanos[nanoID];
|
||||
|
||||
int16_t skillID = plr->Nanos[nanoID].iSkillID;
|
||||
if (Abilities::SkillTable.count(skillID) > 0 && Abilities::SkillTable[skillID].drainType == SkillDrainType::PASSIVE)
|
||||
resp.eCSTB___Add = 1; // passive buff effect
|
||||
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// passive buff effect
|
||||
resp.eCSTB___Add = 1;
|
||||
std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
|
||||
if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, nano, affectedCombatants);
|
||||
}
|
||||
|
||||
if (!silent) // silent nano death but only for the summoning player
|
||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC);
|
||||
@ -97,7 +149,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.
|
||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||
pkt1.iPC_ID = plr->iID;
|
||||
pkt1.Nano = plr->Nanos[nanoID];
|
||||
pkt1.Nano = nano;
|
||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||
}
|
||||
|
||||
@ -184,7 +236,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;
|
||||
}
|
||||
@ -208,18 +260,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
||||
// Update player
|
||||
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
|
||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||
summonNano(sock, -1);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
@ -25,4 +26,5 @@ namespace Nanos {
|
||||
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
||||
int nanoStyle(int nanoID);
|
||||
bool getNanoBoost(Player* plr);
|
||||
std::vector<ICombatant*> applyNanoBuff(SkillData* skill, Player* plr);
|
||||
}
|
||||
|
@ -7,6 +7,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
/* 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<int, Buff*> buffs = {};
|
||||
|
||||
int angle = 0;
|
||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||
@ -86,13 +89,25 @@ struct Player : public Entity, public ICombatant {
|
||||
virtual void enterIntoViewOf(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, int buffClass) 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 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();
|
||||
};
|
||||
|
@ -36,8 +36,13 @@ 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);
|
||||
Groups::groupKick(plr->group, key);
|
||||
|
||||
// remove player's bullets
|
||||
Combat::Bullets.erase(plr->iID);
|
||||
@ -61,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
||||
// if the player was in a lair, clean it up
|
||||
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;
|
||||
}
|
||||
|
||||
@ -403,7 +398,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
|
||||
@ -470,10 +465,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
|
||||
if (plr->group != nullptr) {
|
||||
// TODO ABILITIES
|
||||
//int bitFlag = plr->group->conditionBitFlag;
|
||||
//resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
||||
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||
|
@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
|
||||
|
||||
// overflow-safe validation of variable-length 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
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p
|
||||
}
|
||||
|
||||
// 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
|
||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||
return false;
|
||||
|
@ -118,6 +118,27 @@ enum {
|
||||
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 {
|
||||
SUCC = 1,
|
||||
FAIL = 0,
|
||||
|
@ -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 500
|
||||
|
||||
class CNShardServer : public CNServer {
|
||||
private:
|
||||
|
Loading…
Reference in New Issue
Block a user