From c965024d1cd859b11aed4f5030cc1ee200b4d48c Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 11:02:16 -0400 Subject: [PATCH 001/104] [WIP] Initial merge of ability namespaces & features --- src/Abilities.cpp | 205 +++++++++++++++------------------------------- src/Abilities.hpp | 32 ++------ 2 files changed, 69 insertions(+), 168 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index baec351..e034b65 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -10,14 +10,14 @@ * TODO: This file is in desperate need of deduplication and rewriting. */ -std::map Nanos::SkillTable; +std::map Abilities::SkillTable; /* * targetData approach * first integer is the count * second to fifth integers are IDs, these can be either player iID or mob's iID */ -std::vector Nanos::findTargets(Player* plr, int skillID, CNPacketData* data) { +std::vector Abilities::findTargets(Player* plr, int skillID, CNPacketData* data) { std::vector tD(5); if (SkillTable[skillID].targetType <= 2 && data != nullptr) { // client gives us the targets @@ -66,7 +66,7 @@ std::vector Nanos::findTargets(Player* plr, int skillID, CNPacketData* data return tD; } -void Nanos::nanoUnbuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower) { +void Abilities::removeBuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower) { Player *plr = PlayerManager::getPlayer(sock); plr->iSelfConditionBitFlag &= ~bitFlag; @@ -101,7 +101,7 @@ void Nanos::nanoUnbuff(CNSocket* sock, std::vector targetData, int32_t bitF } } -int Nanos::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags) { +int Abilities::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags) { if (SkillTable[skillID].drainType == 1) return 0; @@ -133,8 +133,7 @@ int Nanos::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t gr return 0; } -#pragma region Nano Powers -namespace Nanos { +namespace Abilities { bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { @@ -406,111 +405,9 @@ bool doMove(CNSocket *sock, sSkillResult_Move *respdata, int i, int32_t targetID return true; } -template -void nanoPower(CNSocket *sock, std::vector targetData, - int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount, - int16_t skillType, int32_t bitFlag, int16_t timeBuffID) { - Player *plr = PlayerManager::getPlayer(sock); - - if (skillType == EST_RETROROCKET_SELF || skillType == EST_RECALL) // rocket and self recall does not need any trailing structs - targetData[0] = 0; - - size_t resplen; - // special case since leech is atypically encoded - if (skillType == EST_BLOODSUCKING) - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); - else - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + targetData[0] * sizeof(sPAYLOAD); - - // validate response packet - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), targetData[0], sizeof(sPAYLOAD))) { - std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl; - return; - } - - uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - memset(respbuf, 0, resplen); - - sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; - sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); - - resp->iPC_ID = plr->iID; - resp->iSkillID = skillID; - resp->iNanoID = nanoID; - resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina; - resp->eST = skillType; - resp->iTargetCnt = targetData[0]; - - if (SkillTable[skillID].drainType == 2) { - if (SkillTable[skillID].targetType >= 2) - plr->iSelfConditionBitFlag |= bitFlag; - if (SkillTable[skillID].targetType == 3) - plr->iGroupConditionBitFlag |= bitFlag; - } - - for (int i = 0; i < targetData[0]; i++) - if (!work(sock, respdata, i, targetData[i+1], bitFlag, timeBuffID, duration, amount)) - return; - - sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); - assert(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) == sizeof(sP_FE2CL_NANO_SKILL_USE)); - if (skillType == EST_RECALL_GROUP) { // in the case of group recall, nobody but group members need the packet - for (int i = 0; i < targetData[0]; i++) { - CNSocket *sock2 = PlayerManager::getSockFromID(targetData[i+1]); - sock2->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); - } - } else - PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); - - // Warping on recall - if (skillType == EST_RECALL || skillType == EST_RECALL_GROUP) { - if ((int32_t)plr->instanceID == plr->recallInstance) - PlayerManager::sendPlayerTo(sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); - else { - INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response) - sock->sendPacket((void*)&response, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL)); - } - } -} - -// nano power dispatch table -std::vector NanoPowers = { - NanoPower(EST_STUN, CSB_BIT_STUN, ECSB_STUN, nanoPower), - NanoPower(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, ECSB_BOUNDINGBALL, nanoPower), - NanoPower(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, nanoPower), - NanoPower(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, nanoPower), - NanoPower(EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, nanoPower), - NanoPower(EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, nanoPower), - NanoPower(EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, nanoPower), - NanoPower(EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, nanoPower), - NanoPower(EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, nanoPower), - NanoPower(EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, nanoPower), - NanoPower(EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, nanoPower), - NanoPower(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, nanoPower), - NanoPower(EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, nanoPower), - NanoPower(EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, nanoPower), - NanoPower(EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, nanoPower), - NanoPower(EST_RECALL, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_RECALL_GROUP, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_RETROROCKET_SELF, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_PHOENIX_GROUP, CSB_BIT_NONE, ECSB_NONE, nanoPower), - NanoPower(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT1, ECSB_STIMPAKSLOT1, nanoPower), - NanoPower(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT2, ECSB_STIMPAKSLOT2, nanoPower), - NanoPower(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT3, ECSB_STIMPAKSLOT3, nanoPower) -}; - -}; // namespace -#pragma endregion - -#pragma region Mob Powers -namespace Combat { -bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - CNSocket *sock = nullptr; - Player *plr = nullptr; +bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { + CNSocket* sock = nullptr; + Player* plr = nullptr; for (auto& pair : PlayerManager::players) { if (pair.second->iID == targetID) { @@ -534,7 +431,7 @@ bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, in if (plr->iConditionBitFlag & CSB_BIT_FREEDOM) respdata[i].bProtected = 1; else { - if (!(plr->iConditionBitFlag & bitFlag)) { + if (!(plr->iConditionBitFlag & bitFlag)) { INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); pkt.eCSTB = timeBuffID; // eCharStatusTimeBuffID pkt.eTBU = 1; // eTimeBuffUpdate @@ -564,7 +461,7 @@ bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, in return true; } -bool doHeal(Mob *mob, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { +bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { std::cout << "[WARN] doHeal: NPC ID not found" << std::endl; return false; @@ -591,7 +488,7 @@ bool doHeal(Mob *mob, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, i return true; } -bool doReturnHeal(Mob *mob, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { +bool doReturnHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { int healedAmount = amount * mob->maxHealth / 1000; mob->appearanceData.iHP += healedAmount; if (mob->appearanceData.iHP > mob->maxHealth) @@ -605,8 +502,8 @@ bool doReturnHeal(Mob *mob, sSkillResult_Heal_HP *respdata, int i, int32_t targe return true; } -bool doDamage(Mob *mob, sSkillResult_Damage *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; +bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { + Player* plr = nullptr; for (auto& pair : PlayerManager::players) { if (pair.second->iID == targetID) { @@ -644,14 +541,14 @@ bool doDamage(Mob *mob, sSkillResult_Damage *respdata, int i, int32_t targetID, return true; } -bool doLeech(Mob *mob, sSkillResult_Heal_HP *healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { +bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { // this sanity check is VERY important if (i != 0) { std::cout << "[WARN] Mob attempted to leech more than one player!" << std::endl; return false; } - Player *plr = nullptr; + Player* plr = nullptr; for (auto& pair : PlayerManager::players) { if (pair.second->iID == targetID) { @@ -666,7 +563,7 @@ bool doLeech(Mob *mob, sSkillResult_Heal_HP *healdata, int i, int32_t targetID, return false; } - sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP)); + sSkillResult_Damage* damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP)); int healedAmount = amount * PC_MAXHEALTH(plr->level) / 1000; @@ -702,8 +599,8 @@ bool doLeech(Mob *mob, sSkillResult_Heal_HP *healdata, int i, int32_t targetID, return true; } -bool doBatteryDrain(Mob *mob, sSkillResult_BatteryDrain *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; +bool doBatteryDrain(Mob* mob, sSkillResult_BatteryDrain* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { + Player* plr = nullptr; for (auto& pair : PlayerManager::players) { if (pair.second->iID == targetID) { @@ -725,7 +622,8 @@ bool doBatteryDrain(Mob *mob, sSkillResult_BatteryDrain *respdata, int i, int32_ respdata[i].bProtected = 1; respdata[i].iDrainW = 0; respdata[i].iDrainN = 0; - } else { + } + else { respdata[i].bProtected = 0; respdata[i].iDrainW = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36; respdata[i].iDrainN = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36; @@ -739,7 +637,7 @@ bool doBatteryDrain(Mob *mob, sSkillResult_BatteryDrain *respdata, int i, int32_ return true; } -bool doBuff(Mob *mob, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { +bool doBuff(Mob* mob, sSkillResult_Buff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { respdata[i].eCT = 4; respdata[i].iID = mob->appearanceData.iNPC_ID; mob->appearanceData.iConditionBitFlag |= bitFlag; @@ -749,10 +647,10 @@ bool doBuff(Mob *mob, sSkillResult_Buff *respdata, int i, int32_t targetID, int3 } template -void mobPower(Mob *mob, std::vector targetData, - int16_t skillID, int16_t duration, int16_t amount, - int16_t skillType, int32_t bitFlag, int16_t timeBuffID) { + bool (*work)(EntityRef, sPAYLOAD*, int, int32_t, int32_t, int16_t, int16_t, int16_t)> + void power(EntityRef ref, std::vector targetData, + int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount, + int16_t skillType, int32_t bitFlag, int16_t timeBuffID) { size_t resplen; // special case since leech is atypically encoded if (skillType == EST_BLOODSUCKING) @@ -769,8 +667,8 @@ void mobPower(Mob *mob, std::vector targetData, uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); - sP_FE2CL_NPC_SKILL_HIT *resp = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; - sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_HIT)); + sP_FE2CL_NPC_SKILL_HIT* resp = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; + sPAYLOAD* respdata = (sPAYLOAD*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); resp->iNPC_ID = mob->appearanceData.iNPC_ID; resp->iSkillID = skillID; @@ -781,24 +679,49 @@ void mobPower(Mob *mob, std::vector targetData, resp->iTargetCnt = targetData[0]; for (int i = 0; i < targetData[0]; i++) - if (!work(mob, respdata, i, targetData[i+1], bitFlag, timeBuffID, duration, amount)) + if (!work(mob, respdata, i, targetData[i + 1], bitFlag, timeBuffID, duration, amount)) return; NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); } // nano power dispatch table -std::vector MobPowers = { - MobPower(EST_STUN, CSB_BIT_STUN, ECSB_STUN, mobPower), - MobPower(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, mobPower), - MobPower(EST_RETURNHOMEHEAL, CSB_BIT_NONE, ECSB_NONE, mobPower), - MobPower(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, mobPower), - MobPower(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, mobPower), - MobPower(EST_BATTERYDRAIN, CSB_BIT_NONE, ECSB_NONE, mobPower), - MobPower(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, mobPower), - MobPower(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, mobPower), - MobPower(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, mobPower) +std::vector Powers = { + Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power), + Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, ECSB_BOUNDINGBALL, power), + Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power), + Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power), + Power(EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, power), + Power(EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, power), + Power(EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, power), + Power(EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, power), + Power(EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, power), + Power(EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, power), + Power(EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, power), + Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power), + Power(EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, power), + Power(EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, power), + Power(EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, power), + Power(EST_RECALL, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_RECALL_GROUP, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_RETROROCKET_SELF, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_PHOENIX_GROUP, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT1, ECSB_STIMPAKSLOT1, power), + Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT2, ECSB_STIMPAKSLOT2, power), + Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT3, ECSB_STIMPAKSLOT3, power), + // + Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power), + Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_RETURNHOMEHEAL, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power), + Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_BATTERYDRAIN, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power), + Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power), + Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power) }; }; // namespace -#pragma endregion diff --git a/src/Abilities.hpp b/src/Abilities.hpp index fc3ea76..180234e 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -5,13 +5,13 @@ typedef void (*PowerHandler)(CNSocket*, std::vector, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); -struct NanoPower { +struct Power { int16_t skillType; int32_t bitFlag; int16_t timeBuffID; PowerHandler handler; - NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} + Power(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} void handle(CNSocket *sock, std::vector targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { if (handler == nullptr) @@ -21,24 +21,6 @@ struct NanoPower { } }; -typedef void (*MobPowerHandler)(Mob*, std::vector, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); - -struct MobPower { - int16_t skillType; - int32_t bitFlag; - int16_t timeBuffID; - MobPowerHandler handler; - - MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} - - void handle(Mob *mob, std::vector targetData, int16_t skillID, int16_t duration, int16_t amount) { - if (handler == nullptr) - return; - - handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID); - } -}; - struct SkillData { int skillType; int targetType; @@ -49,16 +31,12 @@ struct SkillData { int powerIntensity[4]; }; -namespace Nanos { - extern std::vector NanoPowers; +namespace Abilities { + extern std::vector Powers; extern std::map SkillTable; - void nanoUnbuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); + void removeBuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags); std::vector findTargets(Player* plr, int skillID, CNPacketData* data = nullptr); } - -namespace Combat { - extern std::vector MobPowers; -} From 32db574700105ba5de4c9e328932b3eab5ec2985 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 13:05:10 -0400 Subject: [PATCH 002/104] [WIP] Fix Nanos -> Abilities namespace calls --- src/Abilities.cpp | 6 +++--- src/Eggs.cpp | 10 +++++----- src/Groups.cpp | 18 +++++++++--------- src/Items.cpp | 2 +- src/MobAI.cpp | 32 ++++++++++++++++---------------- src/Nanos.cpp | 32 ++++++++++++++++---------------- src/PlayerManager.cpp | 2 +- src/TableData.cpp | 4 ++-- 8 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index e034b65..721114b 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -107,7 +107,7 @@ int Abilities::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_ int32_t bitFlag = 0; - for (auto& pwr : NanoPowers) { + for (auto& pwr : Powers) { if (pwr.skillType == SkillTable[skillID].skillType) { bitFlag = pwr.bitFlag; Player *plr = PlayerManager::getPlayer(sock); @@ -686,7 +686,7 @@ template Powers = { +std::vector Powers = {};/* Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power), Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power), Power(EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, ECSB_BOUNDINGBALL, power), @@ -722,6 +722,6 @@ std::vector Powers = { Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power), Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power), Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power) -}; +};*/ }; // namespace diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 683582d..5b5a79c 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -19,7 +19,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); int bitFlag = Groups::getGroupFlags(otherPlr); - int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag); + int CBFlag = Abilities::applyBuff(sock, skillId, 1, 3, bitFlag); size_t resplen; @@ -41,7 +41,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000; + skill->iDamage = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].powerIntensity[0] / 1000; plr->HP -= skill->iDamage; if (plr->HP < 0) plr->HP = 0; @@ -51,7 +51,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iHealHP = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000; + skill->iHealHP = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].powerIntensity[0] / 1000; plr->HP += skill->iHealHP; if (plr->HP > PC_MAXHEALTH(plr->level)) plr->HP = PC_MAXHEALTH(plr->level); @@ -66,7 +66,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { skillUse->iNPC_ID = eggId; skillUse->iSkillID = skillId; - skillUse->eST = Nanos::SkillTable[skillId].skillType; + skillUse->eST = Abilities::SkillTable[skillId].skillType; skillUse->iTargetCnt = 1; sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); @@ -100,7 +100,7 @@ static void eggStep(CNServer* serv, time_t currTime) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); int groupFlags = Groups::getGroupFlags(otherPlr); - for (auto& pwr : Nanos::NanoPowers) { + 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; diff --git a/src/Groups.cpp b/src/Groups.cpp index 2fe7dfb..59dfdf7 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -135,10 +135,10 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { // client doesnt read nano data here if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member - if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) - Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag); - if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3) - Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag); + 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); } } @@ -221,7 +221,7 @@ static void groupUnbuff(Player* plr) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]); - Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); + Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); } } } @@ -295,10 +295,10 @@ void Groups::groupKickPlayer(Player* plr) { moveDown = 1; otherPlr->groupIDs[i] = 0; } else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member. - if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) - Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0); - if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) - Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag); + 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); } } diff --git a/src/Items.cpp b/src/Items.cpp index a3618dc..9328c66 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -501,7 +501,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { player->Inven[resp->iSlotNum] = resp->RemainItem; std::pair key = std::make_pair(sock, value1); - time_t until = getTime() + (time_t)Nanos::SkillTable[144].durationTime[0] * 100; + time_t until = getTime() + (time_t)Abilities::SkillTable[144].durationTime[0] * 100; Eggs::EggBuffs[key] = until; } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index c31e19c..21a05da 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -235,7 +235,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i int style2 = Nanos::nanoStyle(plr->activeNano); if (style2 == -1) { // no nano respdata[i].iHitFlag = 8; - respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; + respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; } else if (style == style2) { respdata[i].iHitFlag = 8; // tie respdata[i].iDamage = 0; @@ -248,12 +248,12 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; // fire damage power disguised as a corruption attack back at the enemy std::vector targetData2 = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; - for (auto& pwr : Nanos::NanoPowers) + for (auto& pwr : Abilities::Powers) if (pwr.skillType == EST_DAMAGE) pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); } else { respdata[i].iHitFlag = 16; // lose - respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; + respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90; if (plr->Nanos[plr->activeNano].iStamina < 0) { respdata[i].bNanoDeactive = 1; @@ -319,7 +319,7 @@ static void useAbilities(Mob *mob, time_t currTime) { continue; int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y); - if (distance < Nanos::SkillTable[skillID].effectArea) { + if (distance < Abilities::SkillTable[skillID].effectArea) { targetData[0] += 1; targetData[targetData[0]] = plr->iID; if (targetData[0] > 3) // make sure not to have more than 4 @@ -328,9 +328,9 @@ static void useAbilities(Mob *mob, time_t currTime) { } } - for (auto& pwr : Combat::MobPowers) - if (pwr.skillType == Nanos::SkillTable[skillID].skillType) - pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]); + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) + pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); mob->skillStyle = -3; // eruption cooldown mob->nextAttack = currTime + 1000; return; @@ -349,11 +349,11 @@ static void useAbilities(Mob *mob, time_t currTime) { if (random < prob1) { // active skill hit int skillID = (int)mob->data["m_iActiveSkill1"]; std::vector targetData = {1, plr->iID, 0, 0, 0}; - for (auto& pwr : Combat::MobPowers) - if (pwr.skillType == Nanos::SkillTable[skillID].skillType) { + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) return; // prevent debuffing a player twice - pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]); + pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); } mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; return; @@ -407,9 +407,9 @@ void MobAI::enterCombat(CNSocket *sock, Mob *mob) { int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive std::vector targetData = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; - for (auto& pwr : Combat::MobPowers) - if (pwr.skillType == Nanos::SkillTable[skillID].skillType) - pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]); + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) + pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT if (event.trigger == ON_COMBAT && event.npcType == mob->appearanceData.iNPCType) @@ -773,9 +773,9 @@ static void retreatStep(Mob *mob, time_t currTime) { // cast a return home heal spell, this is the right way(tm) std::vector targetData = {1, 0, 0, 0, 0}; - for (auto& pwr : Combat::MobPowers) - if (pwr.skillType == Nanos::SkillTable[110].skillType) - pwr.handle(mob, targetData, 110, Nanos::SkillTable[110].durationTime[0], Nanos::SkillTable[110].powerIntensity[0]); + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[110].skillType) + pwr.handle(mob, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); // clear outlying debuffs clearDebuff(mob); } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index c6d4adf..0a8318d 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -86,12 +86,12 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; // passive nano unbuffing - if (SkillTable[skillID].drainType == 2) { - std::vector targetData = findTargets(plr, skillID); + if (Abilities::SkillTable[skillID].drainType == 2) { + std::vector targetData = Abilities::findTargets(plr, skillID); - for (auto& pwr : NanoPowers) - if (pwr.skillType == SkillTable[skillID].skillType) - nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3)); + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) + Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3)); } if (nanoID >= NANO_COUNT || nanoID < 0) @@ -101,19 +101,19 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { skillID = plr->Nanos[nanoID].iSkillID; // passive nano buffing - if (SkillTable[skillID].drainType == 2) { - std::vector targetData = findTargets(plr, skillID); + if (Abilities::SkillTable[skillID].drainType == 2) { + std::vector targetData = Abilities::findTargets(plr, skillID); int boost = 0; if (getNanoBoost(plr)) boost = 1; - for (auto& pwr : NanoPowers) { - if (pwr.skillType == SkillTable[skillID].skillType) { + for (auto& pwr : Abilities::Powers) { + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM - plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3]; + plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3]; - pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]); + pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]); } } } @@ -296,19 +296,19 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; ) - std::vector targetData = findTargets(plr, skillID, data); + std::vector targetData = Abilities::findTargets(plr, skillID, data); int boost = 0; if (getNanoBoost(plr)) boost = 1; - plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3]; + plr->Nanos[plr->activeNano].iStamina -= Abilities::SkillTable[skillID].batteryUse[boost*3]; if (plr->Nanos[plr->activeNano].iStamina < 0) plr->Nanos[plr->activeNano].iStamina = 0; - for (auto& pwr : NanoPowers) - if (pwr.skillType == SkillTable[skillID].skillType) - pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]); + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) + pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]); if (plr->Nanos[plr->activeNano].iStamina < 0) summonNano(sock, -1); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index f920f76..5785db2 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -410,7 +410,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { // nano revive plr->Nanos[plr->activeNano].iStamina = 0; plr->HP = PC_MAXHEALTH(plr->level) / 2; - Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); + Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); } else if (reviveData->iRegenType == 4) { // revived by group member's nano plr->HP = PC_MAXHEALTH(plr->level) / 2; diff --git a/src/TableData.cpp b/src/TableData.cpp index a685cee..d8c9f9b 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -239,10 +239,10 @@ static void loadXDT(json& xdtData) { skillData.durationTime[i] = skills["m_iDurationTime"][i]; skillData.powerIntensity[i] = skills["m_iValueA"][i]; } - Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData; + Abilities::SkillTable[skills["m_iSkillNumber"]] = skillData; } - std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl; + std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl; // load EP data json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"]; From 7ab01b098d996391adf67e28eb25bcd648bf2f7f Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 14:12:40 -0400 Subject: [PATCH 003/104] [WIP] Rename Entity.type -> Entity.kind --- src/Abilities.cpp | 10 +++++----- src/Chunking.cpp | 2 +- src/Combat.cpp | 6 +++--- src/CustomCommands.cpp | 16 ++++++++-------- src/Eggs.cpp | 4 ++-- src/Entities.cpp | 2 +- src/Entities.hpp | 6 +++--- src/MobAI.cpp | 14 +++++++------- src/MobAI.hpp | 2 +- src/NPCManager.cpp | 2 +- src/Player.hpp | 2 +- src/TableData.cpp | 6 +++--- src/Transport.cpp | 8 ++++---- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 721114b..27f36b9 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -142,7 +142,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] doDebuff: NPC is not a mob" << std::endl; return false; } @@ -213,7 +213,7 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] doDamageNDebuff: NPC is not a mob" << std::endl; return false; } @@ -281,7 +281,7 @@ bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targ } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] doDamage: NPC is not a mob" << std::endl; return false; } @@ -337,7 +337,7 @@ bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targ } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] doLeech: NPC is not a mob" << std::endl; return false; } @@ -468,7 +468,7 @@ bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, i } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] doHeal: NPC is not a mob" << std::endl; return false; } diff --git a/src/Chunking.cpp b/src/Chunking.cpp index 3d3330a..fcc9150 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -275,7 +275,7 @@ void Chunking::createInstance(uint64_t instanceID) { BaseNPC* baseNPC = (BaseNPC*)ref.getEntity(); // make a copy of each NPC in the template chunks and put them in the new instance - if (baseNPC->type == EntityType::MOB) { + if (baseNPC->kind == EntityType::MOB) { if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) continue; // follower; don't copy individually diff --git a/src/Combat.cpp b/src/Combat.cpp index d14a794..ce5f668 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -114,7 +114,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { BaseNPC* npc = NPCManager::NPCs[targets[i]]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl; return; } @@ -486,7 +486,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { } BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; return; } @@ -696,7 +696,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { } BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; - if (npc->type != EntityType::MOB) { + if (npc->kind != EntityType::MOB) { std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl; return; } diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 06d9d69..b277328 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -283,10 +283,10 @@ static void unsummonWCommand(std::string full, std::vector& args, C return; } - if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) { + if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->kind == EntityType::MOB) { int leadId = ((Mob*)npc)->groupLeader; if (leadId != 0) { - if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityType::MOB) { std::cout << "[WARN] unsummonW: leader not found!" << std::endl; } Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId]; @@ -294,7 +294,7 @@ static void unsummonWCommand(std::string full, std::vector& args, C if (leadNpc->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl; continue; } @@ -324,7 +324,7 @@ static void toggleAiCommand(std::string full, std::vector& args, CN // return all mobs to their spawn points for (auto& pair : NPCManager::NPCs) { - if (pair.second->type != EntityType::MOB) + if (pair.second->kind != EntityType::MOB) continue; Mob* mob = (Mob*)pair.second; @@ -609,7 +609,7 @@ static void summonGroupCommand(std::string full, std::vector& args, } BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); - if (team == 2 && i > 0 && npc->type == EntityType::MOB) { + if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; mob->groupLeader = leadNpc->appearanceData.iNPC_ID; @@ -624,7 +624,7 @@ static void summonGroupCommand(std::string full, std::vector& args, if (PLAYERID(plr->instanceID) != 0) { npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); - if (team == 2 && i > 0 && npc->type == EntityType::MOB) { + if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; mob->groupLeader = leadNpc->appearanceData.iNPC_ID; @@ -639,7 +639,7 @@ static void summonGroupCommand(std::string full, std::vector& args, Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); - if (i == 0 && team == 2 && npc->type == EntityType::MOB) { + if (i == 0 && team == 2 && npc->kind == EntityType::MOB) { type = type2; leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID; @@ -675,7 +675,7 @@ static void whoisCommand(std::string full, std::vector& args, CNSoc Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType)); Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP)); Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag)); - Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type)); + Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind)); Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z)); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 5b5a79c..52fa35e 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -124,7 +124,7 @@ static void eggStep(CNServer* serv, time_t currTime) { // check dead eggs and eggs in inactive chunks for (auto npc : NPCManager::NPCs) { - if (npc.second->type != EntityType::EGG) + if (npc.second->kind != EntityType::EGG) continue; auto egg = (Egg*)npc.second; @@ -163,7 +163,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { return; } auto egg = (Egg*)eggRef.getEntity(); - if (egg->type != EntityType::EGG) { + if (egg->kind != EntityType::EGG) { std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; return; } diff --git a/src/Entities.cpp b/src/Entities.cpp index 973a78c..ea52eba 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -20,7 +20,7 @@ EntityRef::EntityRef(int32_t i) { id = i; assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); - type = NPCManager::NPCs[id]->type; + type = NPCManager::NPCs[id]->kind; } bool EntityRef::isValid() const { diff --git a/src/Entities.hpp b/src/Entities.hpp index 4975279..a231541 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -17,7 +17,7 @@ enum class EntityType : uint8_t { }; struct Entity { - EntityType type = EntityType::INVALID; + EntityType kind = EntityType::INVALID; int x = 0, y = 0, z = 0; uint64_t instanceID = 0; ChunkPos chunkPos = {}; @@ -129,7 +129,7 @@ struct Egg : public BaseNPC { Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon) : BaseNPC(x, y, z, 0, iID, t, id) { summoned = summon; - type = EntityType::EGG; + kind = EntityType::EGG; } virtual bool isAlive() override { return !dead; } @@ -142,7 +142,7 @@ struct Egg : public BaseNPC { struct Bus : public BaseNPC { Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : BaseNPC(x, y, z, angle, iID, t, id) { - type = EntityType::BUS; + kind = EntityType::BUS; loopingPath = true; } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 21a05da..562ccce 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -58,13 +58,13 @@ void MobAI::clearDebuff(Mob *mob) { } void MobAI::followToCombat(Mob *mob) { - if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { + if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) { Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; for (int i = 0; i < 4; i++) { if (leadMob->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -84,7 +84,7 @@ void MobAI::followToCombat(Mob *mob) { } void MobAI::groupRetreat(Mob *mob) { - if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->type != EntityType::MOB) + if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityType::MOB) return; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; @@ -92,7 +92,7 @@ void MobAI::groupRetreat(Mob *mob) { if (leadMob->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -479,7 +479,7 @@ static void deadStep(Mob *mob, time_t currTime) { // if mob is a group leader/follower, spawn where the group is. if (mob->groupLeader != 0) { - if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { + if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) { Mob* leaderMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; mob->x = leaderMob->x + mob->offsetX; mob->y = leaderMob->y + mob->offsetY; @@ -721,7 +721,7 @@ static void roamingStep(Mob *mob, time_t currTime) { if (mob->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -782,7 +782,7 @@ static void retreatStep(Mob *mob, time_t currTime) { } void MobAI::step(CombatNPC *npc, time_t currTime) { - assert(npc->type == EntityType::MOB); + assert(npc->kind == EntityType::MOB); auto mob = (Mob*)npc; if (mob->playersInView < 0) diff --git a/src/MobAI.hpp b/src/MobAI.hpp index c526561..452f6c6 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -77,7 +77,7 @@ struct Mob : public CombatNPC { // NOTE: there appear to be discrepancies in the dump appearanceData.iHP = maxHealth; - type = EntityType::MOB; + kind = EntityType::MOB; _stepAI = MobAI::step; } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 901bfb3..eb262d9 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -352,7 +352,7 @@ void NPCManager::queueNPCRemoval(int32_t id) { static void step(CNServer *serv, time_t currTime) { for (auto& pair : NPCs) { - if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB) + if (pair.second->kind != EntityType::COMBAT_NPC && pair.second->kind != EntityType::MOB) continue; auto npc = (CombatNPC*)pair.second; diff --git a/src/Player.hpp b/src/Player.hpp index baa77b1..7821593 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -85,7 +85,7 @@ struct Player : public Entity { time_t lastShot = 0; std::vector buyback = {}; - Player() { type = EntityType::PLAYER; } + Player() { kind = EntityType::PLAYER; } virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; diff --git a/src/TableData.cpp b/src/TableData.cpp index d8c9f9b..892509f 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -1226,7 +1226,7 @@ void TableData::flush() { continue; int x, y, z; - if (npc->type == EntityType::MOB) { + if (npc->kind == EntityType::MOB) { Mob *m = (Mob*)npc; x = m->spawnX; y = m->spawnY; @@ -1259,7 +1259,7 @@ void TableData::flush() { int x, y, z; std::vector followers; - if (npc->type == EntityType::MOB) { + if (npc->kind == EntityType::MOB) { Mob* m = (Mob*)npc; x = m->spawnX; y = m->spawnY; @@ -1271,7 +1271,7 @@ void TableData::flush() { // add follower data to vector; go until OOB or until follower ID is 0 for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) { - if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) { + if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n"; continue; } diff --git a/src/Transport.cpp b/src/Transport.cpp index 0fe4f10..7ed5836 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -257,13 +257,13 @@ static void stepNPCPathing() { } // skip if not simulating mobs - if (npc->type == EntityType::MOB && !MobAI::simulateMobs) { + if (npc->kind == EntityType::MOB && !MobAI::simulateMobs) { it++; continue; } // do not roam if not roaming - if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { + if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { it++; continue; } @@ -279,7 +279,7 @@ static void stepNPCPathing() { NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle); // TODO: move walking logic into Entity stack - switch (npc->type) { + switch (npc->kind) { case EntityType::BUS: INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); @@ -385,7 +385,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) { void Transport::constructPathNPC(int32_t id, NPCPath* path) { BaseNPC* npc = NPCManager::NPCs[id]; - if (npc->type == EntityType::MOB) + if (npc->kind == EntityType::MOB) ((Mob*)(npc))->staticPath = true; npc->loopingPath = path->isLoop; From efc00e63b3d87d425a842e652591ee89e62d9892 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 14:37:37 -0400 Subject: [PATCH 004/104] [WIP] Replace appearance data with individual fields Storing certain things in appearance data and others in their own fields was gross. Now everything is stored on the same level and functions have been added to generate appearance data when it's needed by the client. --- src/Abilities.cpp | 64 ++++++++++----------- src/Chunking.cpp | 30 +++++----- src/Combat.cpp | 38 ++++++------- src/CustomCommands.cpp | 124 ++++++++++++++++++++--------------------- src/Eggs.cpp | 6 +- src/Entities.cpp | 67 ++++++++++++++-------- src/Entities.hpp | 26 +++++---- src/Items.cpp | 6 +- src/Missions.cpp | 4 +- src/MobAI.cpp | 75 ++++++++++++------------- src/MobAI.hpp | 4 +- src/NPCManager.cpp | 28 +++++----- src/Player.hpp | 2 + src/PlayerMovement.cpp | 4 +- src/TableData.cpp | 38 ++++++------- src/Transport.cpp | 6 +- 16 files changed, 273 insertions(+), 249 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 27f36b9..bc438f6 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -151,15 +151,15 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target Combat::hitMob(sock, mob, 0); respdata[i].eCT = 4; - respdata[i].iID = mob->appearanceData.iNPC_ID; + respdata[i].iID = mob->id; respdata[i].bProtected = 1; if (mob->skillStyle < 0 && mob->state != MobState::RETREAT - && !(mob->appearanceData.iConditionBitFlag & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom - mob->appearanceData.iConditionBitFlag |= bitFlag; + && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom + mob->cbf |= bitFlag; mob->unbuffTimes[bitFlag] = getTime() + duration * 100; respdata[i].bProtected = 0; } - respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + respdata[i].iConditionBitFlag = mob->cbf; return true; } @@ -224,16 +224,16 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int respdata[i].eCT = 4; respdata[i].iDamage = duration / 10; - respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iID = mob->id; + respdata[i].iHP = mob->hp; respdata[i].bProtected = 1; if (mob->skillStyle < 0 && mob->state != MobState::RETREAT - && !(mob->appearanceData.iConditionBitFlag & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom - mob->appearanceData.iConditionBitFlag |= bitFlag; + && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom + mob->cbf |= bitFlag; mob->unbuffTimes[bitFlag] = getTime() + duration * 100; respdata[i].bProtected = 0; } - respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + respdata[i].iConditionBitFlag = mob->cbf; return true; } @@ -293,8 +293,8 @@ bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targ respdata[i].eCT = 4; respdata[i].iDamage = damage; - respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iID = mob->id; + respdata[i].iHP = mob->hp; return true; } @@ -348,8 +348,8 @@ bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targ damagedata->eCT = 4; damagedata->iDamage = damage; - damagedata->iID = mob->appearanceData.iNPC_ID; - damagedata->iHP = mob->appearanceData.iHP; + damagedata->iID = mob->id; + damagedata->iHP = mob->hp; return true; } @@ -476,13 +476,13 @@ bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, i Mob* targetMob = (Mob*)npc; int healedAmount = amount * targetMob->maxHealth / 1000; - targetMob->appearanceData.iHP += healedAmount; - if (targetMob->appearanceData.iHP > targetMob->maxHealth) - targetMob->appearanceData.iHP = targetMob->maxHealth; + targetMob->hp += healedAmount; + if (targetMob->hp > targetMob->maxHealth) + targetMob->hp = targetMob->maxHealth; respdata[i].eCT = 4; - respdata[i].iID = targetMob->appearanceData.iNPC_ID; - respdata[i].iHP = targetMob->appearanceData.iHP; + respdata[i].iID = targetMob->id; + respdata[i].iHP = targetMob->hp; respdata[i].iHealHP = healedAmount; return true; @@ -490,13 +490,13 @@ bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, i bool doReturnHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { int healedAmount = amount * mob->maxHealth / 1000; - mob->appearanceData.iHP += healedAmount; - if (mob->appearanceData.iHP > mob->maxHealth) - mob->appearanceData.iHP = mob->maxHealth; + mob->hp += healedAmount; + if (mob->hp > mob->maxHealth) + mob->hp = mob->maxHealth; respdata[i].eCT = 4; - respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iID = mob->id; + respdata[i].iHP = mob->hp; respdata[i].iHealHP = healedAmount; return true; @@ -567,13 +567,13 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, int healedAmount = amount * PC_MAXHEALTH(plr->level) / 1000; - mob->appearanceData.iHP += healedAmount; - if (mob->appearanceData.iHP > mob->maxHealth) - mob->appearanceData.iHP = mob->maxHealth; + mob->hp += healedAmount; + if (mob->hp > mob->maxHealth) + mob->hp = mob->maxHealth; healdata->eCT = 4; - healdata->iID = mob->appearanceData.iNPC_ID; - healdata->iHP = mob->appearanceData.iHP; + healdata->iID = mob->id; + healdata->iHP = mob->hp; healdata->iHealHP = healedAmount; int damage = healedAmount; @@ -639,9 +639,9 @@ bool doBatteryDrain(Mob* mob, sSkillResult_BatteryDrain* respdata, int i, int32_ bool doBuff(Mob* mob, sSkillResult_Buff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { respdata[i].eCT = 4; - respdata[i].iID = mob->appearanceData.iNPC_ID; - mob->appearanceData.iConditionBitFlag |= bitFlag; - respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + respdata[i].iID = mob->id; + mob->cbf |= bitFlag; + respdata[i].iConditionBitFlag = mob->cbf; return true; } @@ -670,7 +670,7 @@ templateiNPC_ID = mob->appearanceData.iNPC_ID; + resp->iNPC_ID = mob->id; resp->iSkillID = skillID; resp->iValue1 = mob->hitX; resp->iValue2 = mob->hitY; diff --git a/src/Chunking.cpp b/src/Chunking.cpp index fcc9150..04b391f 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -279,42 +279,42 @@ void Chunking::createInstance(uint64_t instanceID) { if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) continue; // follower; don't copy individually - Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, - instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--); - NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob; + Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle, + instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--); + NPCManager::NPCs[newMob->id] = newMob; // if in a group, copy over group members as well if (((Mob*)baseNPC)->groupLeader != 0) { - newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader + newMob->groupLeader = newMob->id; // set leader ID for new leader Mob* mobData = (Mob*)baseNPC; for (int i = 0; i < 4; i++) { if (mobData->groupMember[i] != 0) { int followerID = NPCManager::nextId--; // id for follower BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template // new follower instance - Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle, - instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID); + Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle, + instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID); // add follower to NPC maps NPCManager::NPCs[followerID] = newMobFollower; // set follower-specific properties - newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID; + newMobFollower->groupLeader = newMob->id; newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX; newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY; // add follower copy to leader copy newMob->groupMember[i] = followerID; NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z, - instanceID, baseFollower->appearanceData.iAngle); + instanceID, baseFollower->angle); } } } - NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, - instanceID, baseNPC->appearanceData.iAngle); + NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z, + instanceID, baseNPC->angle); } else { - BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, - instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--); - NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC; - NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, - instanceID, baseNPC->appearanceData.iAngle); + BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle, + instanceID, baseNPC->type, NPCManager::nextId--); + NPCManager::NPCs[newNPC->id] = newNPC; + NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z, + instanceID, baseNPC->angle); } } } diff --git a/src/Combat.cpp b/src/Combat.cpp index ce5f668..2e7f76c 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -139,9 +139,9 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { damage.first = hitMob(sock, mob, damage.first); - respdata[i].iID = mob->appearanceData.iNPC_ID; + respdata[i].iID = mob->id; respdata[i].iDamage = damage.first; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iHP = mob->hp; respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } @@ -168,7 +168,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) plr->HP -= damage.first; - pkt->iNPC_ID = mob->appearanceData.iNPC_ID; + pkt->iNPC_ID = mob->id; pkt->iPCCnt = 1; atk->iID = plr->iID; @@ -207,20 +207,20 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { MobAI::followToCombat(mob); } - mob->appearanceData.iHP -= damage; + mob->hp -= damage; // wake up sleeping monster - if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) { - mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; + if (mob->cbf & CSB_BIT_MEZ) { + mob->cbf &= ~CSB_BIT_MEZ; INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; - pkt1.iID = mob->appearanceData.iNPC_ID; - pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + pkt1.iID = mob->id; + pkt1.iConditionBitFlag = mob->cbf; NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } - if (mob->appearanceData.iHP <= 0) + if (mob->hp <= 0) killMob(mob->target, mob); return damage; @@ -253,7 +253,7 @@ static void genQItemRolls(Player *leader, std::map& rolls) { void Combat::killMob(CNSocket *sock, Mob *mob) { mob->state = MobState::DEAD; mob->target = nullptr; - mob->appearanceData.iConditionBitFlag = 0; + mob->cbf = 0; mob->skillStyle = -1; mob->unbuffTimes.clear(); mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? @@ -273,7 +273,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { Items::giveMobDrop(sock, mob, rolled, eventRolled); - Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls); + Missions::mobKilled(sock, mob->type, qitemRolls); } else { for (int i = 0; i < leader->groupCnt; i++) { CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); @@ -288,7 +288,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { continue; Items::giveMobDrop(sockTo, mob, rolled, eventRolled); - Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls); + Missions::mobKilled(sockTo, mob->type, qitemRolls); } } } @@ -298,10 +298,10 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { // fire any triggered events for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType) + if (event.trigger == ON_KILLED && event.npcType == mob->type) event.handler(sock, mob); - auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID); + auto it = Transport::NPCQueues.find(mob->id); if (it == Transport::NPCQueues.end() || it->second.empty()) return; @@ -319,7 +319,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { queue.push(point); } } else { - Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID); + Transport::NPCQueues.erase(mob->id); } } @@ -513,9 +513,9 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { damage.first = hitMob(sock, mob, damage.first); respdata[i].eCT = pktdata[i*2+1]; - respdata[i].iID = mob->appearanceData.iNPC_ID; + respdata[i].iID = mob->id; respdata[i].iDamage = damage.first; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iHP = mob->hp; respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } } @@ -711,9 +711,9 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { damage.first = hitMob(sock, mob, damage.first); - respdata[i].iID = mob->appearanceData.iNPC_ID; + respdata[i].iID = mob->id; respdata[i].iDamage = damage.first; - respdata[i].iHP = mob->appearanceData.iHP; + respdata[i].iHP = mob->hp; respdata[i].iHitFlag = damage.second; } diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index b277328..d171bbb 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -243,20 +243,20 @@ static void summonWCommand(std::string full, std::vector& args, CNS BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true); // update angle - npc->appearanceData.iAngle = (plr->angle + 180) % 360; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle); + npc->angle = (plr->angle + 180) % 360; + NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle); // if we're in a lair, we need to spawn the NPC in both the private instance and the template if (PLAYERID(plr->instanceID) != 0) { npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true); - npc->appearanceData.iAngle = (plr->angle + 180) % 360; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle); + npc->angle = (plr->angle + 180) % 360; + NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle); } Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) + - ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); - TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template + ", id: " + std::to_string(npc->id)); + TableData::RunningMobs[npc->id] = npc; // only record the one in the template } static void unsummonWCommand(std::string full, std::vector& args, CNSocket* sock) { @@ -269,21 +269,21 @@ static void unsummonWCommand(std::string full, std::vector& args, C return; } - if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) { - Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) + - ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); - TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID); - NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); + if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) { + Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) + + ", id: " + std::to_string(npc->id)); + TableData::RunningEggs.erase(npc->id); + NPCManager::destroyNPC(npc->id); return; } - if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end() - && TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) { + if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end() + && TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) { Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob."); return; } - if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->kind == EntityType::MOB) { + if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityType::MOB) { int leadId = ((Mob*)npc)->groupLeader; if (leadId != 0) { if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityType::MOB) { @@ -308,12 +308,12 @@ static void unsummonWCommand(std::string full, std::vector& args, C } } - Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) + - ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); + Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) + + ", id: " + std::to_string(npc->id)); - TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID); + TableData::RunningMobs.erase(npc->id); - NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); + NPCManager::destroyNPC(npc->id); } static void toggleAiCommand(std::string full, std::vector& args, CNSocket* sock) { @@ -356,24 +356,24 @@ static void npcRotateCommand(std::string full, std::vector& args, C } int angle = (plr->angle + 180) % 360; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); + NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle); // if it's a gruntwork NPC, rotate in-place - if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) { - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); + if (TableData::RunningMobs.find(npc->id) != TableData::RunningMobs.end()) { + NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle); Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC " - + std::to_string(npc->appearanceData.iNPC_ID)); + + std::to_string(npc->id)); } else { - TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle; + TableData::RunningNPCRotations[npc->id] = angle; Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC " - + std::to_string(npc->appearanceData.iNPC_ID)); + + std::to_string(npc->id)); } // update rotation clientside INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); - pkt.NPCAppearanceData = npc->appearanceData; + pkt.NPCAppearanceData = npc->getAppearanceData(); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); } @@ -443,9 +443,9 @@ static void npcInstanceCommand(std::string full, std::vector& args, return; } - Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance)); - TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle); + Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance)); + TableData::RunningNPCMapNumbers[npc->id] = instance; + NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle); } static void minfoCommand(std::string full, std::vector& args, CNSocket* sock) { @@ -610,39 +610,39 @@ static void summonGroupCommand(std::string full, std::vector& args, BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { - leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; - Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; - mob->groupLeader = leadNpc->appearanceData.iNPC_ID; + leadNpc->groupMember[i-1] = npc->id; + Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; + mob->groupLeader = leadNpc->id; mob->offsetX = x - plr->x; mob->offsetY = y - plr->y; } - npc->appearanceData.iAngle = (plr->angle + 180) % 360; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); + npc->angle = (plr->angle + 180) % 360; + NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle); // if we're in a lair, we need to spawn the NPC in both the private instance and the template if (PLAYERID(plr->instanceID) != 0) { npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { - leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; - Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; - mob->groupLeader = leadNpc->appearanceData.iNPC_ID; + leadNpc->groupMember[i-1] = npc->id; + Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; + mob->groupLeader = leadNpc->id; mob->offsetX = x - plr->x; mob->offsetY = y - plr->y; } - npc->appearanceData.iAngle = (plr->angle + 180) % 360; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); + npc->angle = (plr->angle + 180) % 360; + NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle); } Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + - ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); + ", id: " + std::to_string(npc->id)); if (i == 0 && team == 2 && npc->kind == EntityType::MOB) { type = type2; - leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; - leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID; + leadNpc = (Mob*)NPCManager::NPCs[npc->id]; + leadNpc->groupLeader = leadNpc->id; } } @@ -654,7 +654,7 @@ static void summonGroupCommand(std::string full, std::vector& args, return; } - TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader + TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader } static void flushCommand(std::string full, std::vector& args, CNSocket* sock) { @@ -671,15 +671,15 @@ static void whoisCommand(std::string full, std::vector& args, CNSoc return; } - Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID)); - Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType)); - Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP)); - Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag)); + Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id)); + Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type)); + Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp)); + Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf)); Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind)); Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z)); - Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle)); + Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle)); std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos)); Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}"); Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID))); @@ -705,7 +705,7 @@ static void lairUnlockCommand(std::string full, std::vector& args, continue; for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) { - if (it->second.npcID == npc->appearanceData.iNPCType) { + if (it->second.npcID == npc->type) { taskID = it->second.limitTaskID; missionID = Missions::Tasks[taskID]->task["m_iHMissionID"]; lastDist = dist; @@ -981,7 +981,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock pathPoints.push_back(marker); // map from player TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints); - Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you"); + Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you"); updatePathMarkers(sock); return; } @@ -1008,8 +1008,8 @@ static void pathCommand(std::string full, std::vector& args, CNSock // /path here if (args[1] == "here") { // bring the NPC to where the player is standing - Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0); + Transport::NPCQueues.erase(npc->id); // delete transport queue + NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0); npc->disappearFromViewOf(sock); npc->enterIntoViewOf(sock); Chat::sendServerMessage(sock, "[PATH] Come here"); @@ -1047,9 +1047,9 @@ static void pathCommand(std::string full, std::vector& args, CNSock speed = speedArg; } // return NPC to home - Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue + Transport::NPCQueues.erase(npc->id); // delete transport queue BaseNPC* home = entry->second[0]; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); + NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); npc->disappearFromViewOf(sock); npc->enterIntoViewOf(sock); @@ -1065,7 +1065,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock Transport::lerp(&keyframes, from, to, speed); // lerp from A to B from = to; // update point A } - Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; + Transport::NPCQueues[npc->id] = keyframes; entry->second.pop_back(); // remove temp end point Chat::sendServerMessage(sock, "[PATH] Testing NPC path"); @@ -1075,9 +1075,9 @@ static void pathCommand(std::string full, std::vector& args, CNSock // /path cancel if (args[1] == "cancel") { // return NPC to home - Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue + Transport::NPCQueues.erase(npc->id); // delete transport queue BaseNPC* home = entry->second[0]; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); + NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); npc->disappearFromViewOf(sock); npc->enterIntoViewOf(sock); // deallocate markers @@ -1087,7 +1087,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock } // unmap TableData::RunningNPCPaths.erase(plr->iID); - Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); + Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you"); return; } @@ -1115,9 +1115,9 @@ static void pathCommand(std::string full, std::vector& args, CNSock } // return NPC to home and set path to repeat - Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue + Transport::NPCQueues.erase(npc->id); // delete transport queue BaseNPC* home = entry->second[0]; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); + NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); npc->disappearFromViewOf(sock); npc->enterIntoViewOf(sock); npc->loopingPath = true; @@ -1134,7 +1134,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock Transport::lerp(&keyframes, from, to, speed); // lerp from A to B from = to; // update point A } - Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; + Transport::NPCQueues[npc->id] = keyframes; entry->second.pop_back(); // remove temp end point // save to gruntwork @@ -1161,7 +1161,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock finishedPath.isLoop = true; finishedPath.speed = speed; finishedPath.points = finalPoints; - finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID); + finishedPath.targetIDs.push_back(npc->id); TableData::FinishedNPCPaths.push_back(finishedPath); @@ -1173,7 +1173,7 @@ static void pathCommand(std::string full, std::vector& args, CNSock // unmap TableData::RunningNPCPaths.erase(plr->iID); - Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); + Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you"); TableData::flush(); Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork"); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 52fa35e..f29d5cc 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -135,7 +135,7 @@ static void eggStep(CNServer* serv, time_t currTime) { // respawn it egg->dead = false; egg->deadUntil = 0; - egg->appearanceData.iHP = 400; + egg->hp = 400; Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); } @@ -180,7 +180,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { } */ - int typeId = egg->appearanceData.iNPCType; + int typeId = egg->type; if (EggTypes.find(typeId) == EggTypes.end()) { std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl; return; @@ -255,7 +255,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); egg->dead = true; egg->deadUntil = getTime() + (time_t)type->regen * 1000; - egg->appearanceData.iHP = 0; + egg->hp = 0; } } diff --git a/src/Entities.cpp b/src/Entities.cpp index ea52eba..a859af2 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -39,15 +39,26 @@ Entity *EntityRef::getEntity() const { return NPCManager::NPCs[id]; } +sNPCAppearanceData BaseNPC::getAppearanceData() { + sNPCAppearanceData data = {}; + data.iAngle = angle; + data.iBarkerType = barkerType; + data.iConditionBitFlag = cbf; + data.iHP = hp; + data.iNPCType = type; + data.iNPC_ID = id; + data.iX = x; + data.iY = y; + data.iZ = z; + return data; +} + /* * Entity coming into view. */ void BaseNPC::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); - pkt.NPCAppearanceData = appearanceData; - pkt.NPCAppearanceData.iX = x; - pkt.NPCAppearanceData.iY = y; - pkt.NPCAppearanceData.iZ = z; + pkt.NPCAppearanceData = getAppearanceData(); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); } @@ -56,7 +67,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) { // TODO: Potentially decouple this from BaseNPC? pkt.AppearanceData = { - 3, appearanceData.iNPC_ID, appearanceData.iNPCType, + 3, id, type, x, y, z }; @@ -66,28 +77,36 @@ void Bus::enterIntoViewOf(CNSocket *sock) { void Egg::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); - Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData); + // TODO: Potentially decouple this from BaseNPC? + pkt.ShinyAppearanceData = { + id, type, 0, // client doesn't care about map num + x, y, z + }; sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); } - + +sPCAppearanceData Player::getAppearanceData() { + sPCAppearanceData data = {}; + data.iID = iID; + data.iHP = HP; + data.iLv = level; + data.iX = x; + data.iY = y; + data.iZ = z; + data.iAngle = angle; + data.PCStyle = PCStyle; + data.Nano = Nanos[activeNano]; + data.iPCState = iPCState; + data.iSpecialState = iSpecialState; + memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT); + return data; +} + // TODO: this is less effiecient than it was, because of memset() void Player::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_PC_NEW, pkt); - - pkt.PCAppearanceData.iID = iID; - pkt.PCAppearanceData.iHP = HP; - pkt.PCAppearanceData.iLv = level; - pkt.PCAppearanceData.iX = x; - pkt.PCAppearanceData.iY = y; - pkt.PCAppearanceData.iZ = z; - pkt.PCAppearanceData.iAngle = angle; - pkt.PCAppearanceData.PCStyle = PCStyle; - pkt.PCAppearanceData.Nano = Nanos[activeNano]; - pkt.PCAppearanceData.iPCState = iPCState; - pkt.PCAppearanceData.iSpecialState = iSpecialState; - memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT); - + pkt.PCAppearanceData = getAppearanceData(); sock->sendPacket(pkt, P_FE2CL_PC_NEW); } @@ -96,20 +115,20 @@ void Player::enterIntoViewOf(CNSocket *sock) { */ void BaseNPC::disappearFromViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); - pkt.iNPC_ID = appearanceData.iNPC_ID; + pkt.iNPC_ID = id; sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); } void Bus::disappearFromViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt); pkt.eTT = 3; - pkt.iT_ID = appearanceData.iNPC_ID; + pkt.iT_ID = id; sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT); } void Egg::disappearFromViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt); - pkt.iShinyID = appearanceData.iNPC_ID; + pkt.iShinyID = id; sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT); } diff --git a/src/Entities.hpp b/src/Entities.hpp index a231541..9e56ea0 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -74,25 +74,31 @@ struct EntityRef { */ class BaseNPC : public Entity { public: - sNPCAppearanceData appearanceData = {}; + int id; + int type; + int hp; + int angle; + int cbf; + int barkerType; bool loopingPath = false; - BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX + BaseNPC(int _X, int _Y, int _Z, int _A, uint64_t iID, int t, int _id) { // XXX x = _X; y = _Y; z = _Z; - appearanceData.iNPCType = t; - appearanceData.iHP = 400; - appearanceData.iAngle = angle; - appearanceData.iConditionBitFlag = 0; - appearanceData.iBarkerType = 0; - appearanceData.iNPC_ID = id; - + type = t; + hp = 400; + angle = _A; + cbf = 0; + barkerType = 0; + id = _id; instanceID = iID; }; virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + + sNPCAppearanceData getAppearanceData(); }; struct CombatNPC : public BaseNPC { @@ -115,7 +121,7 @@ struct CombatNPC : public BaseNPC { _stepAI(this, currTime); } - virtual bool isAlive() override { return appearanceData.iHP > 0; } + virtual bool isAlive() override { return hp > 0; } }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/Items.cpp b/src/Items.cpp index 9328c66..166526b 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -822,12 +822,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) { // sanity check - if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) { - std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl; + if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) { + std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl; return; } // find mob drop id - int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType]; + int mobDropId = Items::MobToDropMap[mob->type]; giveSingleDrop(sock, mob, mobDropId, rolled); diff --git a/src/Missions.cpp b/src/Missions.cpp index 9d2dd69..bd6891e 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -373,9 +373,9 @@ static void taskStart(CNSocket* sock, CNPacketData* data) { for (EntityRef ref : chunk->entities) { if (ref.type != EntityType::PLAYER) { BaseNPC* npc = (BaseNPC*)ref.getEntity(); - NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum); + NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum); if (path != nullptr) { - Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path); + Transport::constructPathNPC(npc->id, path); return; } } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 562ccce..e396499 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -47,13 +47,13 @@ static std::pair lerp(int x1, int y1, int x2, int y2, int speed) { void MobAI::clearDebuff(Mob *mob) { mob->skillStyle = -1; - mob->appearanceData.iConditionBitFlag = 0; + mob->cbf = 0; mob->unbuffTimes.clear(); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; - pkt1.iID = mob->appearanceData.iNPC_ID; - pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + pkt1.iID = mob->id; + pkt1.iConditionBitFlag = mob->cbf; NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } @@ -196,7 +196,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); - resp->iNPC_ID = mob->appearanceData.iNPC_ID; + resp->iNPC_ID = mob->id; resp->iSkillID = skillID; resp->iStyle = style; resp->iValue1 = plr->x; @@ -247,7 +247,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->Nanos[plr->activeNano].iStamina > 150) respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; // fire damage power disguised as a corruption attack back at the enemy - std::vector targetData2 = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; + std::vector targetData2 = {1, mob->id, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == EST_DAMAGE) pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); @@ -362,7 +362,7 @@ static void useAbilities(Mob *mob, time_t currTime) { if (random < prob1 + prob2) { // corruption windup int skillID = (int)mob->data["m_iCorruptionType"]; INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt); - pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + pkt.iNPC_ID = mob->id; pkt.iSkillID = skillID; pkt.iValue1 = plr->x; pkt.iValue2 = plr->y; @@ -381,7 +381,7 @@ static void useAbilities(Mob *mob, time_t currTime) { if (random < prob1 + prob2 + prob3) { // eruption windup int skillID = (int)mob->data["m_iMegaType"]; INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt); - pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + pkt.iNPC_ID = mob->id; pkt.iSkillID = skillID; pkt.iValue1 = mob->hitX = plr->x; pkt.iValue2 = mob->hitY = plr->y; @@ -406,13 +406,13 @@ void MobAI::enterCombat(CNSocket *sock, Mob *mob) { mob->roamZ = mob->z; int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive - std::vector targetData = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; + std::vector targetData = {1, mob->id, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT - if (event.trigger == ON_COMBAT && event.npcType == mob->appearanceData.iNPCType) + if (event.trigger == ON_COMBAT && event.npcType == mob->type) event.handler(sock, mob); } @@ -426,18 +426,18 @@ static void drainMobHP(Mob *mob, int amount) { sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - pkt->iID = mob->appearanceData.iNPC_ID; + pkt->iID = mob->id; pkt->eCT = 4; // mob pkt->iTB_ID = ECSB_BOUNDINGBALL; drain->eCT = 4; - drain->iID = mob->appearanceData.iNPC_ID; + drain->iID = mob->id; drain->iDamage = amount; - drain->iHP = mob->appearanceData.iHP -= amount; + drain->iHP = mob->hp -= amount; NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); - if (mob->appearanceData.iHP <= 0) + if (mob->hp <= 0) Combat::killMob(mob->target, mob); } @@ -448,14 +448,14 @@ static void deadStep(Mob *mob, time_t currTime) { INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); - pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + pkt.iNPC_ID = mob->id; NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); // if it was summoned, mark it for removal if (mob->summoned) { std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; - NPCManager::queueNPCRemoval(mob->appearanceData.iNPC_ID); + NPCManager::queueNPCRemoval(mob->id); return; } @@ -466,15 +466,15 @@ static void deadStep(Mob *mob, time_t currTime) { } // to guide their groupmates, group leaders still need to move despite being dead - if (mob->groupLeader == mob->appearanceData.iNPC_ID) + if (mob->groupLeader == mob->id) roamingStep(mob, currTime); if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) return; - std::cout << "respawning mob " << mob->appearanceData.iNPC_ID << " with HP = " << mob->maxHealth << std::endl; + std::cout << "respawning mob " << mob->id << " with HP = " << mob->maxHealth << std::endl; - mob->appearanceData.iHP = mob->maxHealth; + mob->hp = mob->maxHealth; mob->state = MobState::ROAMING; // if mob is a group leader/follower, spawn where the group is. @@ -491,10 +491,7 @@ static void deadStep(Mob *mob, time_t currTime) { INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); - pkt.NPCAppearanceData = mob->appearanceData; - pkt.NPCAppearanceData.iX = mob->x; - pkt.NPCAppearanceData.iY = mob->y; - pkt.NPCAppearanceData.iZ = mob->z; + pkt.NPCAppearanceData = mob->getAppearanceData(); // notify all nearby players NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); @@ -532,13 +529,13 @@ static void combatStep(Mob *mob, time_t currTime) { // drain if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) - && mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) { + && mob->cbf & CSB_BIT_BOUNDINGBALL) { drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second mob->lastDrainTime = currTime; } // if drain killed the mob, return early - if (mob->appearanceData.iHP <= 0) + if (mob->hp <= 0) return; // unbuffing @@ -546,12 +543,12 @@ static void combatStep(Mob *mob, time_t currTime) { while (it != mob->unbuffTimes.end()) { if (currTime >= it->second) { - mob->appearanceData.iConditionBitFlag &= ~it->first; + mob->cbf &= ~it->first; INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; - pkt1.iID = mob->appearanceData.iNPC_ID; - pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + pkt1.iID = mob->id; + pkt1.iConditionBitFlag = mob->cbf; NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); it = mob->unbuffTimes.erase(it); @@ -561,7 +558,7 @@ static void combatStep(Mob *mob, time_t currTime) { } // skip attack if stunned or asleep - if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) { + if (mob->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { mob->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. return; } @@ -587,7 +584,7 @@ static void combatStep(Mob *mob, time_t currTime) { mob->nextAttack = 0; // halve movement speed if snared - if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) + if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) speed /= 2; int targetX = plr->x; @@ -602,11 +599,11 @@ static void combatStep(Mob *mob, time_t currTime) { if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack) mob->nextAttack = 0; - NPCManager::updateNPCPosition(mob->appearanceData.iNPC_ID, targ.first, targ.second, mob->z, mob->instanceID, mob->appearanceData.iAngle); + NPCManager::updateNPCPosition(mob->id, targ.first, targ.second, mob->z, mob->instanceID, mob->angle); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + pkt.iNPC_ID = mob->id; pkt.iSpeed = speed; pkt.iToX = mob->x = targ.first; pkt.iToY = mob->y = targ.second; @@ -664,7 +661,7 @@ static void roamingStep(Mob *mob, time_t currTime) { if (mob->staticPath) return; - if (mob->groupLeader != 0 && mob->groupLeader != mob->appearanceData.iNPC_ID) // don't roam by yourself without group leader + if (mob->groupLeader != 0 && mob->groupLeader != mob->id) // don't roam by yourself without group leader return; /* @@ -704,7 +701,7 @@ static void roamingStep(Mob *mob, time_t currTime) { farY = std::clamp(farY, yStart, yStart + mob->idleRange); // halve movement speed if snared - if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) + if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) speed /= 2; std::queue queue; @@ -713,9 +710,9 @@ static void roamingStep(Mob *mob, time_t currTime) { // add a route to the queue; to be processed in Transport::stepNPCPathing() Transport::lerp(&queue, from, to, speed); - Transport::NPCQueues[mob->appearanceData.iNPC_ID] = queue; + Transport::NPCQueues[mob->id] = queue; - if (mob->groupLeader != 0 && mob->groupLeader == mob->appearanceData.iNPC_ID) { + if (mob->groupLeader != 0 && mob->groupLeader == mob->id) { // make followers follow this npc. for (int i = 0; i < 4; i++) { if (mob->groupMember[i] == 0) @@ -731,7 +728,7 @@ static void roamingStep(Mob *mob, time_t currTime) { from = { followerMob->x, followerMob->y, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; Transport::lerp(&queue2, from, to, speed); - Transport::NPCQueues[followerMob->appearanceData.iNPC_ID] = queue2; + Transport::NPCQueues[followerMob->id] = queue2; } } } @@ -751,7 +748,7 @@ static void retreatStep(Mob *mob, time_t currTime) { auto targ = lerp(mob->x, mob->y, mob->roamX, mob->roamY, (int)mob->speed*4/5); - pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + pkt.iNPC_ID = mob->id; pkt.iSpeed = (int)mob->speed * 2; pkt.iToX = mob->x = targ.first; pkt.iToY = mob->y = targ.second; @@ -766,10 +763,10 @@ static void retreatStep(Mob *mob, time_t currTime) { //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point mob->state = MobState::ROAMING; - mob->appearanceData.iHP = mob->maxHealth; + mob->hp = mob->maxHealth; mob->killedTime = 0; mob->nextAttack = 0; - mob->appearanceData.iConditionBitFlag = 0; + mob->cbf = 0; // cast a return home heal spell, this is the right way(tm) std::vector targetData = {1, 0, 0, 0, 0}; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 452f6c6..dbf9c84 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -72,10 +72,10 @@ struct Mob : public CombatNPC { offsetX = 0; offsetY = 0; - appearanceData.iConditionBitFlag = 0; + cbf = 0; // NOTE: there appear to be discrepancies in the dump - appearanceData.iHP = maxHealth; + hp = maxHealth; kind = EntityType::MOB; _stepAI = MobAI::step; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index eb262d9..50b3a0a 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -67,7 +67,7 @@ void NPCManager::destroyNPC(int32_t id) { void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) { BaseNPC* npc = NPCs[id]; - npc->appearanceData.iAngle = angle; + npc->angle = angle; ChunkPos oldChunk = npc->chunkPos; ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I); npc->x = X; @@ -154,7 +154,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) { for (int i = 0; i < req->iNPCCnt; i++) { BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType); - updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0); + updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0); } } @@ -305,16 +305,16 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { // Blastons, Heal Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467); - newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; - NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, - plr->instanceID, oldbody->appearanceData.iAngle); + newbody->angle = oldbody->angle; + NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + plr->instanceID, oldbody->angle); // right arm, Adaptium, Stun Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469); - arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; - NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, - plr->instanceID, oldbody->appearanceData.iAngle); + arm->angle = oldbody->angle; + NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + plr->instanceID, oldbody->angle); } // summon left arm and stage 3 body @@ -327,16 +327,16 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { // Cosmix, Damage Point Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468); - newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; - NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, - plr->instanceID, oldbody->appearanceData.iAngle); + newbody->angle = oldbody->angle; + NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + plr->instanceID, oldbody->angle); // Blastons, Heal Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470); - arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; - NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, - plr->instanceID, oldbody->appearanceData.iAngle); + arm->angle = oldbody->angle; + NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + plr->instanceID, oldbody->angle); } std::vector NPCManager::NPCEvents = { diff --git a/src/Player.hpp b/src/Player.hpp index 7821593..0c425d1 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -89,4 +89,6 @@ struct Player : public Entity { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + + sPCAppearanceData getAppearanceData(); }; diff --git a/src/PlayerMovement.cpp b/src/PlayerMovement.cpp index c19af28..b00f15b 100644 --- a/src/PlayerMovement.cpp +++ b/src/PlayerMovement.cpp @@ -33,7 +33,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { // [gruntwork] check if player has a follower and move it if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) { BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first; - Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points + Transport::NPCQueues.erase(follower->id); // erase existing points std::queue queue; Vec3 from = { follower->x, follower->y, follower->z }; float drag = 0.95f; // this ensures that they don't bump into the player @@ -45,7 +45,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { // add a route to the queue; to be processed in Transport::stepNPCPathing() Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical - Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue; + Transport::NPCQueues[follower->id] = queue; } } diff --git a/src/TableData.cpp b/src/TableData.cpp index 892509f..92a4aca 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -315,9 +315,9 @@ static void loadPaths(json& pathData, int32_t* nextId) { passedDistance -= SLIDER_GAP_SIZE; // step down // spawn a slider Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--); - NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider; - NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); - Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route; + NPCManager::NPCs[slider->id] = slider; + NPCManager::updateNPCPosition(slider->id, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); + Transport::NPCQueues[slider->id] = route; } // rotate route.pop(); @@ -755,7 +755,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) continue; // NPC not found BaseNPC* npc = NPCManager::NPCs[npcID]; - npc->appearanceData.iAngle = angle; + npc->angle = angle; RunningNPCRotations[npcID] = angle; } @@ -768,8 +768,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) continue; // NPC not found BaseNPC* npc = NPCManager::NPCs[npcID]; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, - npc->z, instanceID, npc->appearanceData.iAngle); + NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, + npc->z, instanceID, npc->angle); RunningNPCMapNumbers[npcID] = instanceID; } @@ -794,9 +794,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id); } - NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; - RunningMobs[npc->appearanceData.iNPC_ID] = npc; - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); + NPCManager::NPCs[npc->id] = npc; + RunningMobs[npc->id] = npc; + NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); } // mob groups @@ -840,7 +840,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; - tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; + tmpFol->groupLeader = tmp->id; tmp->groupMember[followerCount++] = *nextId; (*nextId)--; @@ -850,7 +850,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n"; } - RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running + RunningGroups[tmp->id] = tmp; // store as running } auto eggs = gruntwork["eggs"]; @@ -1003,7 +1003,7 @@ static void loadMobs(json& npcData, int32_t* nextId) { tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; - tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; + tmpFol->groupLeader = tmp->id; tmp->groupMember[followerCount++] = *nextId; (*nextId)--; @@ -1238,13 +1238,13 @@ void TableData::flush() { } // NOTE: this format deviates slightly from the one in mobs.json - mob["iNPCType"] = (int)npc->appearanceData.iNPCType; + mob["iNPCType"] = (int)npc->type; mob["iX"] = x; mob["iY"] = y; mob["iZ"] = z; mob["iMapNum"] = MAPNUM(npc->instanceID); // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh - mob["iAngle"] = npc->appearanceData.iAngle; + mob["iAngle"] = npc->angle; // it's called mobs, but really it's everything gruntwork["mobs"].push_back(mob); @@ -1264,7 +1264,7 @@ void TableData::flush() { x = m->spawnX; y = m->spawnY; z = m->spawnZ; - if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader + if (m->groupLeader != m->id) { // make sure this is a leader std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n"; continue; } @@ -1285,13 +1285,13 @@ void TableData::flush() { } // NOTE: this format deviates slightly from the one in mobs.json - mob["iNPCType"] = (int)npc->appearanceData.iNPCType; + mob["iNPCType"] = (int)npc->type; mob["iX"] = x; mob["iY"] = y; mob["iZ"] = z; mob["iMapNum"] = MAPNUM(npc->instanceID); // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh - mob["iAngle"] = npc->appearanceData.iAngle; + mob["iAngle"] = npc->angle; // followers while (followers.size() > 0) { @@ -1300,7 +1300,7 @@ void TableData::flush() { // populate JSON entry json fol; - fol["iNPCType"] = follower->appearanceData.iNPCType; + fol["iNPCType"] = follower->type; fol["iOffsetX"] = follower->offsetX; fol["iOffsetY"] = follower->offsetY; @@ -1325,7 +1325,7 @@ void TableData::flush() { int mapnum = MAPNUM(npc->instanceID); if (mapnum != 0) egg["iMapNum"] = mapnum; - egg["iType"] = npc->appearanceData.iNPCType; + egg["iType"] = npc->type; gruntwork["eggs"].push_back(egg); } diff --git a/src/Transport.cpp b/src/Transport.cpp index 7ed5836..36b0de6 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -276,7 +276,7 @@ static void stepNPCPathing() { int distanceBetween = hypot(dXY, point.z - npc->z); // total distance // update NPC location to update viewables - NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle); + NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle); // TODO: move walking logic into Entity stack switch (npc->kind) { @@ -284,7 +284,7 @@ static void stepNPCPathing() { INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); busMove.eTT = 3; - busMove.iT_ID = npc->appearanceData.iNPC_ID; + busMove.iT_ID = npc->id; busMove.iMoveStyle = 0; // ??? busMove.iToX = point.x; busMove.iToY = point.y; @@ -298,7 +298,7 @@ static void stepNPCPathing() { /* fallthrough */ default: INITSTRUCT(sP_FE2CL_NPC_MOVE, move); - move.iNPC_ID = npc->appearanceData.iNPC_ID; + move.iNPC_ID = npc->id; move.iMoveStyle = 0; // ??? move.iToX = point.x; move.iToY = point.y; From af7b99195f9eb36154a6e5c31c46d1ab1c03e03e Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 14:39:59 -0400 Subject: [PATCH 005/104] [WIP] Use EntityRef instead of CNSocket in ability handler --- src/Abilities.hpp | 11 ++++++++--- src/MobAI.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 180234e..760ec56 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -3,7 +3,7 @@ #include "core/Core.hpp" #include "Combat.hpp" -typedef void (*PowerHandler)(CNSocket*, std::vector, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); +typedef void (*PowerHandler)(EntityRef, std::vector, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); struct Power { int16_t skillType; @@ -13,11 +13,16 @@ struct Power { Power(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} - void handle(CNSocket *sock, std::vector targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { + void handle(EntityRef ref, std::vector targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { if (handler == nullptr) return; - handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); + handler(ref, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); + } + + /* overload for non-nano abilities */ + void handle(EntityRef ref, std::vector targetData, int16_t skillID, int16_t duration, int16_t amount) { + handle(ref, targetData, -1, skillID, duration, amount); } }; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index e396499..4808156 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -330,7 +330,7 @@ static void useAbilities(Mob *mob, time_t currTime) { for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); mob->skillStyle = -3; // eruption cooldown mob->nextAttack = currTime + 1000; return; @@ -353,7 +353,7 @@ static void useAbilities(Mob *mob, time_t currTime) { if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) return; // prevent debuffing a player twice - pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); } mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; return; @@ -409,7 +409,7 @@ void MobAI::enterCombat(CNSocket *sock, Mob *mob) { std::vector targetData = {1, mob->id, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(mob, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT if (event.trigger == ON_COMBAT && event.npcType == mob->type) @@ -772,7 +772,7 @@ static void retreatStep(Mob *mob, time_t currTime) { std::vector targetData = {1, 0, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(mob, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); + pwr.handle(mob->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); // clear outlying debuffs clearDebuff(mob); } From 32fad56d3849194193fe37bec977faec0814d074 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Jun 2021 15:30:32 -0400 Subject: [PATCH 006/104] [WIP] Stub power handler --- src/Abilities.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index bc438f6..cc613a4 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -651,6 +651,72 @@ template targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount, int16_t skillType, int32_t bitFlag, int16_t timeBuffID) { + + /*** OLD NANO HANDLER *** + Player *plr = PlayerManager::getPlayer(sock); + + if (skillType == EST_RETROROCKET_SELF || skillType == EST_RECALL) // rocket and self recall does not need any trailing structs + targetData[0] = 0; + + size_t resplen; + // special case since leech is atypically encoded + if (skillType == EST_BLOODSUCKING) + resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); + else + resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + targetData[0] * sizeof(sPAYLOAD); + + // validate response packet + if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), targetData[0], sizeof(sPAYLOAD))) { + std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl; + return; + } + + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + memset(respbuf, 0, resplen); + + sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; + sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); + + resp->iPC_ID = plr->iID; + resp->iSkillID = skillID; + resp->iNanoID = nanoID; + resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina; + resp->eST = skillType; + resp->iTargetCnt = targetData[0]; + + if (SkillTable[skillID].drainType == 2) { + if (SkillTable[skillID].targetType >= 2) + plr->iSelfConditionBitFlag |= bitFlag; + if (SkillTable[skillID].targetType == 3) + plr->iGroupConditionBitFlag |= bitFlag; + } + + for (int i = 0; i < targetData[0]; i++) + if (!work(sock, respdata, i, targetData[i+1], bitFlag, timeBuffID, duration, amount)) + return; + + sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); + assert(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) == sizeof(sP_FE2CL_NANO_SKILL_USE)); + if (skillType == EST_RECALL_GROUP) { // in the case of group recall, nobody but group members need the packet + for (int i = 0; i < targetData[0]; i++) { + CNSocket *sock2 = PlayerManager::getSockFromID(targetData[i+1]); + sock2->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); + } + } else + PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); + + // Warping on recall + if (skillType == EST_RECALL || skillType == EST_RECALL_GROUP) { + if ((int32_t)plr->instanceID == plr->recallInstance) + PlayerManager::sendPlayerTo(sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); + else { + INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response) + sock->sendPacket((void*)&response, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL)); + } + } + */ + + /*** OLD MOB ABILITY HANDLER *** size_t resplen; // special case since leech is atypically encoded if (skillType == EST_BLOODSUCKING) @@ -683,6 +749,7 @@ template Date: Wed, 20 Oct 2021 00:30:53 +0200 Subject: [PATCH 007/104] [refactor] Remove redundant coord args from most entity constructors Mobs and CombatNPCs still need theirs in order to properly set their roaming and spawn coords. Assignment of the latter has been moved to the CombatNPC constructor, where it should have already been. --- src/Chunking.cpp | 3 +-- src/CustomCommands.cpp | 17 ++++++++++++++--- src/Entities.hpp | 23 +++++++++++------------ src/MobAI.hpp | 6 +++--- src/NPCManager.cpp | 5 ++--- src/TableData.cpp | 12 ++++++------ 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/Chunking.cpp b/src/Chunking.cpp index 04b391f..cbb439b 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -310,8 +310,7 @@ void Chunking::createInstance(uint64_t instanceID) { NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z, instanceID, baseNPC->angle); } else { - BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle, - instanceID, baseNPC->type, NPCManager::nextId--); + BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--); NPCManager::NPCs[newNPC->id] = newNPC; NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z, instanceID, baseNPC->angle); diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index d171bbb..2d92c21 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -532,7 +532,7 @@ static void eggCommand(std::string full, std::vector& args, CNSocke int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI); int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI); - Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork + Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork NPCManager::NPCs[id] = egg; NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle); @@ -977,7 +977,13 @@ static void pathCommand(std::string full, std::vector& args, CNSock // add first point at NPC's current location std::vector pathPoints; - BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); + BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--); + + // assign coords manually, since we aren't actually adding markers to the world + marker->x = npc->x; + marker->y = npc->y; + marker->z = npc->z; + pathPoints.push_back(marker); // map from player TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints); @@ -998,7 +1004,12 @@ static void pathCommand(std::string full, std::vector& args, CNSock // /path kf if (args[1] == "kf") { - BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); + BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--); + + marker->x = npc->x; + marker->y = npc->y; + marker->z = npc->z; + entry->second.push_back(marker); Chat::sendServerMessage(sock, "[PATH] Added keyframe"); updatePathMarkers(sock); diff --git a/src/Entities.hpp b/src/Entities.hpp index 9e56ea0..3e613ce 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -82,10 +82,7 @@ public: int barkerType; bool loopingPath = false; - BaseNPC(int _X, int _Y, int _Z, int _A, uint64_t iID, int t, int _id) { // XXX - x = _X; - y = _Y; - z = _Z; + BaseNPC(int _A, uint64_t iID, int t, int _id) { type = t; hp = 400; angle = _A; @@ -111,10 +108,12 @@ struct CombatNPC : public BaseNPC { void (*_stepAI)(CombatNPC*, time_t) = nullptr; - // XXX - CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : - BaseNPC(x, y, z, angle, iID, t, id), - maxHealth(maxHP) {} + CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) + : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { + spawnX = x; + spawnY = y; + spawnZ = z; + } virtual void stepAI(time_t currTime) { if (_stepAI != nullptr) @@ -132,8 +131,8 @@ struct Egg : public BaseNPC { bool dead = false; time_t deadUntil; - Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon) - : BaseNPC(x, y, z, 0, iID, t, id) { + Egg(uint64_t iID, int t, int32_t id, bool summon) + : BaseNPC(0, iID, t, id) { summoned = summon; kind = EntityType::EGG; } @@ -146,8 +145,8 @@ struct Egg : public BaseNPC { // TODO: decouple from BaseNPC struct Bus : public BaseNPC { - Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : - BaseNPC(x, y, z, angle, iID, t, id) { + Bus(int angle, uint64_t iID, int t, int id) : + BaseNPC(angle, iID, t, id) { kind = EntityType::BUS; loopingPath = true; } diff --git a/src/MobAI.hpp b/src/MobAI.hpp index dbf9c84..5eb3db8 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -65,9 +65,9 @@ struct Mob : public CombatNPC { idleRange = (int)data["m_iIdleRange"]; level = data["m_iNpcLevel"]; - roamX = spawnX = x; - roamY = spawnY = y; - roamZ = spawnZ = z; + roamX = x; + roamY = y; + roamZ = z; offsetX = 0; offsetY = 0; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 50b3a0a..a0cb0c6 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -122,7 +122,6 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) { // type must already be checked and updateNPCPosition() must be called on the result BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) { uint64_t inst = baseInstance ? MAPNUM(instance) : instance; -#define EXTRA_HEIGHT 0 //assert(nextId < INT32_MAX); int id = nextId--; @@ -130,12 +129,12 @@ BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, BaseNPC *npc = nullptr; if (team == 2) { - npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id); + npc = new Mob(x, y, z, inst, type, NPCData[type], id); // re-enable respawning, if desired ((Mob*)npc)->summoned = !respawn; } else - npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id); + npc = new BaseNPC(0, inst, type, id); NPCs[id] = npc; diff --git a/src/TableData.cpp b/src/TableData.cpp index 92a4aca..6b11c98 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -314,9 +314,9 @@ static void loadPaths(json& pathData, int32_t* nextId) { if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly passedDistance -= SLIDER_GAP_SIZE; // step down // spawn a slider - Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--); + Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--); NPCManager::NPCs[slider->id] = slider; - NPCManager::updateNPCPosition(slider->id, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); + NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0); Transport::NPCQueues[slider->id] = route; } // rotate @@ -659,7 +659,7 @@ static void loadEggs(json& eggData, int32_t* nextId) { int id = (*nextId)--; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; - Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); + Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false); NPCManager::NPCs[id] = addEgg; eggCount++; NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); @@ -791,7 +791,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { // re-enable respawning ((Mob*)npc)->summoned = false; } else { - npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id); + npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id); } NPCManager::NPCs[npc->id] = npc; @@ -859,7 +859,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { int id = (*nextId)--; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; - Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); + Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false); NPCManager::NPCs[id] = addEgg; NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); RunningEggs[id] = addEgg; @@ -893,7 +893,7 @@ static void loadNPCs(json& npcData) { if (npc["iX"] > 512000 && npc["iY"] < 256000) continue; #endif - BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID); + BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID); NPCManager::NPCs[npcID] = tmp; NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]); From 4153d5cd302060d98f03d151443646e51a90c8d9 Mon Sep 17 00:00:00 2001 From: dongresource Date: Wed, 20 Oct 2021 00:33:21 +0200 Subject: [PATCH 008/104] [refactor] Cosmetic cleanup in Fuse fight functions --- src/NPCManager.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index a0cb0c6..92c0f2a 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -300,19 +300,19 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { std::cout << "Lord Fuse stage two" << std::endl; - // Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData* + // Fuse doesn't move // Blastons, Heal - Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467); + Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467); newbody->angle = oldbody->angle; - NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z, plr->instanceID, oldbody->angle); // right arm, Adaptium, Stun - Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469); + Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469); arm->angle = oldbody->angle; - NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z, plr->instanceID, oldbody->angle); } @@ -324,17 +324,17 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { std::cout << "Lord Fuse stage three" << std::endl; // Cosmix, Damage Point - Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468); + Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468); newbody->angle = oldbody->angle; - NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z, plr->instanceID, oldbody->angle); // Blastons, Heal - Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470); + Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470); arm->angle = oldbody->angle; - NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z, plr->instanceID, oldbody->angle); } From 803073213e103c3c32e203ed2301288adc23d1e8 Mon Sep 17 00:00:00 2001 From: dongresource Date: Wed, 20 Oct 2021 00:40:40 +0200 Subject: [PATCH 009/104] [refactor] Replace a few uses of magic numbers with enums --- src/BuiltinCommands.cpp | 12 ++++++------ src/Email.hpp | 2 +- src/Missions.cpp | 4 ++-- src/MobAI.cpp | 8 ++++---- src/PlayerManager.cpp | 31 +++++++++++++++--------------- src/core/Defines.hpp | 42 +++++++++++++++++++++++++++++++---------- 6 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/BuiltinCommands.cpp b/src/BuiltinCommands.cpp index 6f60430..356992a 100644 --- a/src/BuiltinCommands.cpp +++ b/src/BuiltinCommands.cpp @@ -247,17 +247,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { uint64_t instance = plr->instanceID; const int unstickRange = 400; - switch (req->eTeleportType) { - case eCN_GM_TeleportMapType__MyLocation: + switch ((eCN_GM_TeleportType)req->eTeleportType) { + case eCN_GM_TeleportType::MyLocation: PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance); break; - case eCN_GM_TeleportMapType__MapXYZ: + case eCN_GM_TeleportType::MapXYZ: instance = req->iToMap; // fallthrough - case eCN_GM_TeleportMapType__XYZ: + case eCN_GM_TeleportType::XYZ: PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance); break; - case eCN_GM_TeleportMapType__SomeoneLocation: + case eCN_GM_TeleportType::SomeoneLocation: // player to teleport to goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID, AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName)); @@ -269,7 +269,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID); break; - case eCN_GM_TeleportMapType__Unstick: + case eCN_GM_TeleportType::Unstick: targetPlr = PlayerManager::getPlayer(targetSock); PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), diff --git a/src/Email.hpp b/src/Email.hpp index 3e5c557..55e4302 100644 --- a/src/Email.hpp +++ b/src/Email.hpp @@ -5,6 +5,6 @@ namespace Email { extern std::vector dump; - + void init(); } diff --git a/src/Missions.cpp b/src/Missions.cpp index bd6891e..5a33354 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -367,7 +367,7 @@ static void taskStart(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); // if escort task, assign matching paths to all nearby NPCs - if (task["m_iHTaskType"] == 6) { + if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) { for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance Chunk* chunk = Chunking::chunks[chunkPos]; for (EntityRef ref : chunk->entities) { @@ -399,7 +399,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) { * once we comb over mission logic more throughly */ bool mobsAreKilled = false; - if (task->task["m_iHTaskType"] == 5) { + if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) { mobsAreKilled = true; for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (plr->tasks[i] == missionData->iTaskNum) { diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 4808156..3f4c020 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -234,14 +234,14 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i int style2 = Nanos::nanoStyle(plr->activeNano); if (style2 == -1) { // no nano - respdata[i].iHitFlag = 8; + respdata[i].iHitFlag = HF_BIT_STYLE_TIE; respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; } else if (style == style2) { - respdata[i].iHitFlag = 8; // tie + respdata[i].iHitFlag = HF_BIT_STYLE_TIE; respdata[i].iDamage = 0; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina; } else if (style - style2 == 1 || style2 - style == 2) { - respdata[i].iHitFlag = 4; // win + respdata[i].iHitFlag = HF_BIT_STYLE_WIN; respdata[i].iDamage = 0; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45; if (plr->Nanos[plr->activeNano].iStamina > 150) @@ -252,7 +252,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (pwr.skillType == EST_DAMAGE) pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); } else { - respdata[i].iHitFlag = 16; // lose + respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90; if (plr->Nanos[plr->activeNano].iStamina < 0) { diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 5785db2..98f351b 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -406,21 +406,22 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { int activeSlot = -1; bool move = false; - if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) { - // nano revive + switch ((ePCRegenType)reviveData->iRegenType) { + case ePCRegenType::HereByPhoenix: // nano revive + if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX)) + return; // sanity check plr->Nanos[plr->activeNano].iStamina = 0; - plr->HP = PC_MAXHEALTH(plr->level) / 2; Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); - } else if (reviveData->iRegenType == 4) { - // revived by group member's nano + // fallthrough + case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano plr->HP = PC_MAXHEALTH(plr->level) / 2; - } else if (reviveData->iRegenType == 5) { - // warp away - move = true; - } else { - // plain respawn - move = true; + break; + default: // plain respawn plr->HP = PC_MAXHEALTH(plr->level) / 2; + // fallthrough + case ePCRegenType::Unstick: // warp away + move = true; + break; } for (int i = 0; i < 3; i++) { @@ -667,16 +668,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last } CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) { - switch (by) { - case eCN_GM_TargetSearchBy__PC_ID: + switch ((eCN_GM_TargetSearchBy)by) { + case eCN_GM_TargetSearchBy::PC_ID: assert(id != 0); return getSockFromID(id); - case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id + case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id assert(uid != 0); for (auto& pair : players) if (pair.second->accountId == uid) return pair.first; - case eCN_GM_TargetSearchBy__PC_Name: + case eCN_GM_TargetSearchBy::PC_Name: assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names? return getSockFromName(firstname, lastname); } diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index 3cb0e05..185b5c5 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -10,18 +10,40 @@ const float CN_EP_RANK_4 = 0.3f; const float CN_EP_RANK_5 = 0.29f; // methods of finding players for GM commands -enum eCN_GM_TargetSearchBy { - eCN_GM_TargetSearchBy__PC_ID, // player id - eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname - eCN_GM_TargetSearchBy__PC_UID // account id +enum class eCN_GM_TargetSearchBy { + PC_ID, // player id + PC_Name, // firstname, lastname + PC_UID // account id }; -enum eCN_GM_TeleportType { - eCN_GM_TeleportMapType__XYZ, - eCN_GM_TeleportMapType__MapXYZ, - eCN_GM_TeleportMapType__MyLocation, - eCN_GM_TeleportMapType__SomeoneLocation, - eCN_GM_TeleportMapType__Unstick +enum class eCN_GM_TeleportType { + XYZ, + MapXYZ, + MyLocation, + SomeoneLocation, + Unstick +}; + +enum class eTaskTypeProperty { + None = -1, + Talk = 1, + GotoLocation = 2, + UseItems = 3, + Delivery = 4, + Defeat = 5, + EscortDefence = 6, + Max = 7 +}; + +enum class ePCRegenType { + None, + Xcom, + Here, + HereByPhoenix, + HereByPhoenixGroup, + Unstick, + HereByPhoenixItem, + End }; // nano powers From 4494ba5932ecedeb2fa0a3d3b26cb315ec6bdba7 Mon Sep 17 00:00:00 2001 From: dongresource Date: Sat, 23 Oct 2021 14:11:31 +0200 Subject: [PATCH 010/104] [refactor] Get rid of NPC.hpp This file was already obsoleted at the start of the refactor, but seems to have escaped notice until now. --- src/Combat.hpp | 1 - src/NPC.hpp | 4 ---- src/NPCManager.hpp | 1 - 3 files changed, 6 deletions(-) delete mode 100644 src/NPC.hpp diff --git a/src/Combat.hpp b/src/Combat.hpp index f7ee0f9..89863d1 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -2,7 +2,6 @@ #include "core/Core.hpp" #include "servers/CNShardServer.hpp" -#include "NPC.hpp" #include "MobAI.hpp" #include "JSON.hpp" diff --git a/src/NPC.hpp b/src/NPC.hpp deleted file mode 100644 index b5d57dd..0000000 --- a/src/NPC.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "Chunking.hpp" -#include "Entities.hpp" diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 8a4ff62..d390e3b 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -2,7 +2,6 @@ #include "core/Core.hpp" #include "PlayerManager.hpp" -#include "NPC.hpp" #include "Transport.hpp" #include "JSON.hpp" From 5ab011229863c12fbd44e5b4c4a67eaa499ce876 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 11 Apr 2022 10:26:57 -0400 Subject: [PATCH 011/104] (WIP) Initial ICombatant draft --- src/Chunking.cpp | 8 ++++---- src/Entities.cpp | 13 +++++++++++++ src/Entities.hpp | 28 +++++++++++++++++++++++++--- src/Player.hpp | 6 +++++- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/Chunking.cpp b/src/Chunking.cpp index cbb439b..d26fe8d 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -77,7 +77,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { Entity *ent = ref.getEntity(); - bool alive = ent->isAlive(); + bool alive = ent->isExtant(); // TODO: maybe optimize this, potentially using AROUND packets? for (Chunk *chunk : chnks) { @@ -94,7 +94,7 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { } // notify this *player* of the existence of all visible Entities - if (ref.type == EntityType::PLAYER && other->isAlive()) { + if (ref.type == EntityType::PLAYER && other->isExtant()) { other->enterIntoViewOf(ref.sock); } @@ -109,7 +109,7 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef& ref) { Entity *ent = ref.getEntity(); - bool alive = ent->isAlive(); + bool alive = ent->isExtant(); // TODO: same as above for (Chunk *chunk : chnks) { @@ -126,7 +126,7 @@ void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef& r } // notify this *player* of the departure of all visible Entities - if (ref.type == EntityType::PLAYER && other->isAlive()) { + if (ref.type == EntityType::PLAYER && other->isExtant()) { other->disappearFromViewOf(ref.sock); } diff --git a/src/Entities.cpp b/src/Entities.cpp index a859af2..51aaa27 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -103,6 +103,19 @@ sPCAppearanceData Player::getAppearanceData() { return data; } +// player combat methods; not sure if this is the right place to put them +void Player::takeDamage(EntityRef src, int amt) { + // stubbed +} + +void Player::heal(EntityRef src, int amt) { + // stubbed +} + +bool Player::isAlive() { + return HP > 0; +} + // TODO: this is less effiecient than it was, because of memset() void Player::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_PC_NEW, pkt); diff --git a/src/Entities.hpp b/src/Entities.hpp index 3e613ce..a39512a 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -26,7 +26,7 @@ struct Entity { // destructor must be virtual, apparently virtual ~Entity() {} - virtual bool isAlive() { return true; } + virtual bool isExtant() { return true; } // stubs virtual void enterIntoViewOf(CNSocket *sock) = 0; @@ -69,6 +69,20 @@ struct EntityRef { } }; +/* + * Interfaces + */ + +class ICombatant { +public: + ICombatant() {} + virtual ~ICombatant() {} + + virtual void takeDamage(EntityRef, int) = 0; + virtual void heal(EntityRef, int) = 0; + virtual bool isAlive() = 0; +}; + /* * Subclasses */ @@ -98,7 +112,7 @@ public: sNPCAppearanceData getAppearanceData(); }; -struct CombatNPC : public BaseNPC { +struct CombatNPC : public BaseNPC, public ICombatant { int maxHealth = 0; int spawnX = 0; int spawnY = 0; @@ -120,6 +134,14 @@ struct CombatNPC : public BaseNPC { _stepAI(this, currTime); } + virtual void takeDamage(EntityRef src, int amt) override { + // stubbed + } + + virtual void heal(EntityRef src, int amt) override { + // stubbed + } + virtual bool isAlive() override { return hp > 0; } }; @@ -137,7 +159,7 @@ struct Egg : public BaseNPC { kind = EntityType::EGG; } - virtual bool isAlive() override { return !dead; } + virtual bool isExtant() override { return !dead; } virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; diff --git a/src/Player.hpp b/src/Player.hpp index 0c425d1..6fb451e 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -11,7 +11,7 @@ #define PC_MAXHEALTH(level) (925 + 75 * (level)) -struct Player : public Entity { +struct Player : public Entity, public ICombatant { int accountId = 0; int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) int32_t iID = 0; @@ -90,5 +90,9 @@ struct Player : public Entity { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + virtual void takeDamage(EntityRef src, int amt) override; + virtual void heal(EntityRef src, int amt) override; + virtual bool isAlive() override; + sPCAppearanceData getAppearanceData(); }; From ed866fbee491453ce52d91b7705422c7182249dd Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 11 Apr 2022 16:52:35 -0400 Subject: [PATCH 012/104] (WIP) Move ICombatant functions around a bit --- src/Combat.cpp | 24 ++++++++++++++++++++++++ src/Entities.cpp | 13 ------------- src/Entities.hpp | 12 ++++-------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 2e7f76c..25179c7 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -17,6 +17,30 @@ using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; +void Player::takeDamage(EntityRef src, int amt) { + // stubbed +} + +void Player::heal(EntityRef src, int amt) { + // stubbed +} + +bool Player::isAlive() { + return HP > 0; +} + +void CombatNPC::takeDamage(EntityRef src, int amt) { + // stubbed +} + +void CombatNPC::heal(EntityRef src, int amt) { + // stubbed +} + +bool CombatNPC::isAlive() { + return hp > 0; +} + static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, int defenderStyle, int difficulty) { diff --git a/src/Entities.cpp b/src/Entities.cpp index 51aaa27..a859af2 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -103,19 +103,6 @@ sPCAppearanceData Player::getAppearanceData() { return data; } -// player combat methods; not sure if this is the right place to put them -void Player::takeDamage(EntityRef src, int amt) { - // stubbed -} - -void Player::heal(EntityRef src, int amt) { - // stubbed -} - -bool Player::isAlive() { - return HP > 0; -} - // TODO: this is less effiecient than it was, because of memset() void Player::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_PC_NEW, pkt); diff --git a/src/Entities.hpp b/src/Entities.hpp index a39512a..1671727 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -134,15 +134,11 @@ struct CombatNPC : public BaseNPC, public ICombatant { _stepAI(this, currTime); } - virtual void takeDamage(EntityRef src, int amt) override { - // stubbed - } + virtual bool isExtant() override { return hp > 0; } - virtual void heal(EntityRef src, int amt) override { - // stubbed - } - - virtual bool isAlive() override { return hp > 0; } + virtual void takeDamage(EntityRef src, int amt) override; + virtual void heal(EntityRef src, int amt) override; + virtual bool isAlive() override; }; // Mob is in MobAI.hpp, Player is in Player.hpp From 0c4cdaeabf78846ab563a8e6459f3e45cb1cdc5a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 11 Apr 2022 23:14:03 -0400 Subject: [PATCH 013/104] (WIP) Start implementing ICombatant Start by replacing `hitMob` with `takeDamage` interface function. Simplify `pcAttackChars` a little by utilizing the new interface, then add more interface functions as needed. A lot of the combat logic is tied to the `Mob` class. Need to start moving stuff over to CombatNPC. --- src/Abilities.cpp | 8 +- src/Combat.cpp | 182 ++++++++++++++++++++++--------------------- src/Combat.hpp | 1 - src/Entities.hpp | 8 +- src/Player.hpp | 4 +- src/core/Packets.hpp | 2 +- 6 files changed, 106 insertions(+), 99 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index cc613a4..a2ed2cf 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -148,7 +148,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target } Mob* mob = (Mob*)npc; - Combat::hitMob(sock, mob, 0); + mob->takeDamage(sock, 0); respdata[i].eCT = 4; respdata[i].iID = mob->id; @@ -220,7 +220,7 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int Mob* mob = (Mob*)npc; - Combat::hitMob(sock, mob, 0); // just to gain aggro + mob->takeDamage(sock, 0); respdata[i].eCT = 4; respdata[i].iDamage = duration / 10; @@ -289,7 +289,7 @@ bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targ Mob* mob = (Mob*)npc; Player *plr = PlayerManager::getPlayer(sock); - int damage = Combat::hitMob(sock, mob, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000)); + int damage = mob->takeDamage(sock, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000)); respdata[i].eCT = 4; respdata[i].iDamage = damage; @@ -344,7 +344,7 @@ bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targ Mob* mob = (Mob*)npc; - int damage = Combat::hitMob(sock, mob, amount * 2); + int damage = mob->takeDamage(sock, amount * 2); damagedata->eCT = 4; damagedata->iDamage = damage; diff --git a/src/Combat.cpp b/src/Combat.cpp index 25179c7..e58cf42 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -17,8 +17,9 @@ using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; -void Player::takeDamage(EntityRef src, int amt) { - // stubbed +int Player::takeDamage(EntityRef src, int amt) { + HP -= amt; + return amt; } void Player::heal(EntityRef src, int amt) { @@ -29,8 +30,54 @@ bool Player::isAlive() { return HP > 0; } -void CombatNPC::takeDamage(EntityRef src, int amt) { - // stubbed +int Player::getCurrentHP() { + return HP; +} + +int32_t Player::getID() { + return iID; +} + +int CombatNPC::takeDamage(EntityRef src, int amt) { + + /* REFACTOR: all of this logic is strongly coupled to mobs. + * come back to this when more of it is moved to CombatNPC. + * remove this cast when done */ + Mob* mob = (Mob*)this; + + // cannot kill mobs multiple times; cannot harm retreating mobs + if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { + return 0; // no damage + } + + if (mob->skillStyle >= 0) + return 0; // don't hurt a mob casting corruption + + if (mob->state == MobState::ROAMING) { + assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now + MobAI::enterCombat(src.sock, mob); + + if (mob->groupLeader != 0) + MobAI::followToCombat(mob); + } + + hp -= amt; + + // wake up sleeping monster + if (mob->cbf & CSB_BIT_MEZ) { + mob->cbf &= ~CSB_BIT_MEZ; + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); + pkt1.eCT = 2; + pkt1.iID = mob->id; + pkt1.iConditionBitFlag = mob->cbf; + NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + } + + if (mob->hp <= 0) + killMob(mob->target, mob); + + return amt; } void CombatNPC::heal(EntityRef src, int amt) { @@ -41,6 +88,14 @@ bool CombatNPC::isAlive() { return hp > 0; } +int CombatNPC::getCurrentHP() { + return hp; +} + +int32_t CombatNPC::getID() { + return id; +} + static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, int defenderStyle, int difficulty) { @@ -161,7 +216,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { else plr->batteryW = 0; - damage.first = hitMob(sock, mob, damage.first); + damage.first = mob->takeDamage(sock, damage.first); respdata[i].iID = mob->id; respdata[i].iDamage = damage.first; @@ -214,42 +269,6 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { } } -int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { - // cannot kill mobs multiple times; cannot harm retreating mobs - if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { - return 0; // no damage - } - - if (mob->skillStyle >= 0) - return 0; // don't hurt a mob casting corruption - - if (mob->state == MobState::ROAMING) { - assert(mob->target == nullptr); - MobAI::enterCombat(sock, mob); - - if (mob->groupLeader != 0) - MobAI::followToCombat(mob); - } - - mob->hp -= damage; - - // wake up sleeping monster - if (mob->cbf & CSB_BIT_MEZ) { - mob->cbf &= ~CSB_BIT_MEZ; - - INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); - pkt1.eCT = 2; - pkt1.iID = mob->id; - pkt1.iConditionBitFlag = mob->cbf; - NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - } - - if (mob->hp <= 0) - killMob(mob->target, mob); - - return damage; -} - /* * When a group of players is doing missions together, we want them to all get * quest items at the same time, but we don't want the odds of quest item @@ -441,7 +460,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { return; // Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes). - if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) { + if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) { std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n"; return; } @@ -465,11 +484,20 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { resp->iTargetCnt = pkt->iTargetCnt; for (int i = 0; i < pkt->iTargetCnt; i++) { - if (pktdata[i*2+1] == 1) { // eCT == 1; attack player - Player *target = nullptr; + + ICombatant* target = nullptr; + sGM_PVPTarget* targdata = (sGM_PVPTarget*)(pktdata + i * 2); + std::pair damage; + + if (pkt->iTargetCnt > 1) + damage.first = plr->groupDamage; + else + damage.first = plr->pointDamage; + + if (targdata->eCT == 1) { // eCT == 1; attack player for (auto& pair : PlayerManager::players) { - if (pair.second->iID == pktdata[i*2]) { + if (pair.second->iID == targdata->iID) { target = pair.second; break; } @@ -481,67 +509,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { return; } - std::pair damage; + damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0); - if (pkt->iTargetCnt > 1) - damage.first = plr->groupDamage; - else - damage.first = plr->pointDamage; - - damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0); - - if (plr->batteryW >= 6 + plr->level) - plr->batteryW -= 6 + plr->level; - else - plr->batteryW = 0; - - target->HP -= damage.first; - - respdata[i].eCT = pktdata[i*2+1]; - respdata[i].iID = target->iID; - respdata[i].iDamage = damage.first; - respdata[i].iHP = target->HP; - respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } else { // eCT == 4; attack mob - if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) { + + if (NPCManager::NPCs.find(targdata->iID) == NPCManager::NPCs.end()) { // not sure how to best handle this std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl; return; } - BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]]; + BaseNPC* npc = NPCManager::NPCs[targdata->iID]; if (npc->kind != EntityType::MOB) { std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; return; } Mob* mob = (Mob*)npc; - - std::pair damage; - - if (pkt->iTargetCnt > 1) - damage.first = plr->groupDamage; - else - damage.first = plr->pointDamage; - + target = mob; int difficulty = (int)mob->data["m_iNpcLevel"]; - damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); - - if (plr->batteryW >= 6 + difficulty) - plr->batteryW -= 6 + difficulty; - else - plr->batteryW = 0; - - damage.first = hitMob(sock, mob, damage.first); - - respdata[i].eCT = pktdata[i*2+1]; - respdata[i].iID = mob->id; - respdata[i].iDamage = damage.first; - respdata[i].iHP = mob->hp; - respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } + + if (plr->batteryW >= 6 + plr->level) + plr->batteryW -= 6 + plr->level; + else + plr->batteryW = 0; + + damage.first = target->takeDamage(sock, damage.first); + + respdata[i].eCT = targdata->eCT; + respdata[i].iID = target->getID(); + respdata[i].iDamage = damage.first; + respdata[i].iHP = target->getCurrentHP(); + respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen); @@ -733,7 +735,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { int difficulty = (int)mob->data["m_iNpcLevel"]; damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); - damage.first = hitMob(sock, mob, damage.first); + damage.first = mob->takeDamage(sock, damage.first); respdata[i].iID = mob->id; respdata[i].iDamage = damage.first; diff --git a/src/Combat.hpp b/src/Combat.hpp index 89863d1..bb06e54 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -23,6 +23,5 @@ namespace Combat { void init(); void npcAttackPc(Mob *mob, time_t currTime); - int hitMob(CNSocket *sock, Mob *mob, int damage); void killMob(CNSocket *sock, Mob *mob); } diff --git a/src/Entities.hpp b/src/Entities.hpp index 1671727..0db6fb1 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -78,9 +78,11 @@ public: ICombatant() {} virtual ~ICombatant() {} - virtual void takeDamage(EntityRef, int) = 0; + virtual int takeDamage(EntityRef, int) = 0; virtual void heal(EntityRef, int) = 0; virtual bool isAlive() = 0; + virtual int getCurrentHP() = 0; + virtual int32_t getID() = 0; }; /* @@ -136,9 +138,11 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } - virtual void takeDamage(EntityRef src, int amt) override; + virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; + virtual int getCurrentHP() override; + virtual int32_t getID() override; }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/Player.hpp b/src/Player.hpp index 6fb451e..845b2c0 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -90,9 +90,11 @@ struct Player : public Entity, public ICombatant { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; - virtual void takeDamage(EntityRef src, int amt) override; + virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; + virtual int getCurrentHP() override; + virtual int32_t getID() override; sPCAppearanceData getAppearanceData(); }; diff --git a/src/core/Packets.hpp b/src/core/Packets.hpp index 55a0358..f79bb2a 100644 --- a/src/core/Packets.hpp +++ b/src/core/Packets.hpp @@ -47,8 +47,8 @@ struct PacketDesc { * really should. */ struct sGM_PVPTarget { - uint32_t eCT; uint32_t iID; + uint32_t eCT; }; struct sSkillResult_Leech { From 3d572432b37b16b6f22c59c323231afa7b181281 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 12 Apr 2022 17:12:08 -0400 Subject: [PATCH 014/104] (WIP) Point 1: step functions --- src/Combat.cpp | 8 ++ src/Entities.hpp | 10 +- src/MobAI.cpp | 295 ++++++++++++++++++++++----------------------- src/MobAI.hpp | 14 ++- src/NPCManager.cpp | 2 +- src/Player.hpp | 2 + 6 files changed, 168 insertions(+), 163 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index e58cf42..94a1620 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -38,6 +38,10 @@ int32_t Player::getID() { return iID; } +void Player::step(time_t currTime) { + // no-op +} + int CombatNPC::takeDamage(EntityRef src, int amt) { /* REFACTOR: all of this logic is strongly coupled to mobs. @@ -96,6 +100,10 @@ int32_t CombatNPC::getID() { return id; } +void CombatNPC::step(time_t currTime) { + // stubbed +} + static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, int defenderStyle, int difficulty) { diff --git a/src/Entities.hpp b/src/Entities.hpp index 0db6fb1..013fc7a 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -83,6 +83,8 @@ public: virtual bool isAlive() = 0; virtual int getCurrentHP() = 0; virtual int32_t getID() = 0; + + virtual void step(time_t currTime) = 0; }; /* @@ -122,8 +124,6 @@ struct CombatNPC : public BaseNPC, public ICombatant { int level = 0; int speed = 300; - void (*_stepAI)(CombatNPC*, time_t) = nullptr; - CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { spawnX = x; @@ -131,11 +131,6 @@ struct CombatNPC : public BaseNPC, public ICombatant { spawnZ = z; } - virtual void stepAI(time_t currTime) { - if (_stepAI != nullptr) - _stepAI(this, currTime); - } - virtual bool isExtant() override { return hp > 0; } virtual int takeDamage(EntityRef src, int amt) override; @@ -143,6 +138,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual int32_t getID() override; + virtual void step(time_t currTime) override; }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 3f4c020..0ddeca6 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -14,8 +14,6 @@ using namespace MobAI; bool MobAI::simulateMobs = settings::SIMULATEMOBS; -static void roamingStep(Mob *mob, time_t currTime); - /* * Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and * only returns the first step, since the rest will need to be recalculated anyway if chasing player. @@ -441,49 +439,49 @@ static void drainMobHP(Mob *mob, int amount) { Combat::killMob(mob->target, mob); } -static void deadStep(Mob *mob, time_t currTime) { +void Mob::deadStep(time_t currTime) { // despawn the mob after a short delay - if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) { - mob->despawned = true; + if (killedTime != 0 && !despawned && currTime - killedTime > 2000) { + despawned = true; INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); - pkt.iNPC_ID = mob->id; + pkt.iNPC_ID = id; - NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); // if it was summoned, mark it for removal - if (mob->summoned) { + if (summoned) { std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; - NPCManager::queueNPCRemoval(mob->id); + NPCManager::queueNPCRemoval(id); return; } // pre-set spawn coordinates if not marked for removal - mob->x = mob->spawnX; - mob->y = mob->spawnY; - mob->z = mob->spawnZ; + x = spawnX; + y = spawnY; + z = spawnZ; } // to guide their groupmates, group leaders still need to move despite being dead - if (mob->groupLeader == mob->id) - roamingStep(mob, currTime); + if (groupLeader == id) + roamingStep(currTime); - if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) + if (killedTime != 0 && currTime - killedTime < regenTime * 100) return; - std::cout << "respawning mob " << mob->id << " with HP = " << mob->maxHealth << std::endl; + std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; - mob->hp = mob->maxHealth; - mob->state = MobState::ROAMING; + hp = maxHealth; + state = MobState::ROAMING; // if mob is a group leader/follower, spawn where the group is. - if (mob->groupLeader != 0) { - if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) { - Mob* leaderMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; - mob->x = leaderMob->x + mob->offsetX; - mob->y = leaderMob->y + mob->offsetY; - mob->z = leaderMob->z; + if (groupLeader != 0) { + if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityType::MOB) { + Mob* leaderMob = (Mob*)NPCManager::NPCs[groupLeader]; + x = leaderMob->x + offsetX; + y = leaderMob->y + offsetY; + z = leaderMob->z; } else { std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl; } @@ -491,127 +489,127 @@ static void deadStep(Mob *mob, time_t currTime) { INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); - pkt.NPCAppearanceData = mob->getAppearanceData(); + pkt.NPCAppearanceData = getAppearanceData(); // notify all nearby players - NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); + NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); } -static void combatStep(Mob *mob, time_t currTime) { - assert(mob->target != nullptr); +void Mob::combatStep(time_t currTime) { + assert(target != nullptr); // lose aggro if the player lost connection - if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { - mob->target = nullptr; - mob->state = MobState::RETREAT; - if (!aggroCheck(mob, currTime)) { - clearDebuff(mob); - if (mob->groupLeader != 0) - groupRetreat(mob); + if (PlayerManager::players.find(target) == PlayerManager::players.end()) { + target = nullptr; + state = MobState::RETREAT; + if (!aggroCheck(this, currTime)) { + clearDebuff(this); + if (groupLeader != 0) + groupRetreat(this); } return; } - Player *plr = PlayerManager::getPlayer(mob->target); + Player *plr = PlayerManager::getPlayer(target); // lose aggro if the player became invulnerable or died if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { - mob->target = nullptr; - mob->state = MobState::RETREAT; - if (!aggroCheck(mob, currTime)) { - clearDebuff(mob); - if (mob->groupLeader != 0) - groupRetreat(mob); + target = nullptr; + state = MobState::RETREAT; + if (!aggroCheck(this, currTime)) { + clearDebuff(this); + if (groupLeader != 0) + groupRetreat(this); } return; } // drain - if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) - && mob->cbf & CSB_BIT_BOUNDINGBALL) { - drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second - mob->lastDrainTime = currTime; + if (skillStyle < 0 && (lastDrainTime == 0 || currTime - lastDrainTime >= 1000) + && cbf & CSB_BIT_BOUNDINGBALL) { + drainMobHP(this, maxHealth / 20); // lose 5% every second + lastDrainTime = currTime; } // if drain killed the mob, return early - if (mob->hp <= 0) + if (hp <= 0) return; // unbuffing - std::unordered_map::iterator it = mob->unbuffTimes.begin(); - while (it != mob->unbuffTimes.end()) { + std::unordered_map::iterator it = unbuffTimes.begin(); + while (it != unbuffTimes.end()) { if (currTime >= it->second) { - mob->cbf &= ~it->first; + cbf &= ~it->first; INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; - pkt1.iID = mob->id; - pkt1.iConditionBitFlag = mob->cbf; - NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + pkt1.iID = id; + pkt1.iConditionBitFlag = cbf; + NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - it = mob->unbuffTimes.erase(it); + it = unbuffTimes.erase(it); } else { it++; } } // skip attack if stunned or asleep - if (mob->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { - mob->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. + if (cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { + skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. return; } - int distance = hypot(plr->x - mob->x, plr->y - mob->y); - int mobRange = (int)mob->data["m_iAtkRange"] + (int)mob->data["m_iRadius"]; + int distance = hypot(plr->x - x, plr->y - y); + int mobRange = (int)data["m_iAtkRange"] + (int)data["m_iRadius"]; - if (currTime >= mob->nextAttack) { - if (mob->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. - useAbilities(mob, currTime); - if (mob->target == nullptr) + if (currTime >= nextAttack) { + if (skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. + useAbilities(this, currTime); + if (target == nullptr) return; } int distanceToTravel = INT_MAX; - int speed = mob->speed; + int speed = speed; // movement logic: move when out of range but don't move while casting a skill - if (distance > mobRange && mob->skillStyle == -1) { - if (mob->nextMovement != 0 && currTime < mob->nextMovement) + if (distance > mobRange && skillStyle == -1) { + if (nextMovement != 0 && currTime < nextMovement) return; - mob->nextMovement = currTime + 400; - if (currTime >= mob->nextAttack) - mob->nextAttack = 0; + nextMovement = currTime + 400; + if (currTime >= nextAttack) + nextAttack = 0; // halve movement speed if snared - if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) + if (cbf & CSB_BIT_DN_MOVE_SPEED) speed /= 2; int targetX = plr->x; int targetY = plr->y; - if (mob->groupLeader != 0) { - targetX += mob->offsetX*distance/(mob->idleRange + 1); - targetY += mob->offsetY*distance/(mob->idleRange + 1); + if (groupLeader != 0) { + targetX += offsetX*distance/(idleRange + 1); + targetY += offsetY*distance/(idleRange + 1); } distanceToTravel = std::min(distance-mobRange+1, speed*2/5); - auto targ = lerp(mob->x, mob->y, targetX, targetY, distanceToTravel); - if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack) - mob->nextAttack = 0; + auto targ = lerp(x, y, targetX, targetY, distanceToTravel); + if (distanceToTravel < speed*2/5 && currTime >= nextAttack) + nextAttack = 0; - NPCManager::updateNPCPosition(mob->id, targ.first, targ.second, mob->z, mob->instanceID, mob->angle); + NPCManager::updateNPCPosition(id, targ.first, targ.second, z, instanceID, angle); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - pkt.iNPC_ID = mob->id; + pkt.iNPC_ID = id; pkt.iSpeed = speed; - pkt.iToX = mob->x = targ.first; - pkt.iToY = mob->y = targ.second; + pkt.iToX = x = targ.first; + pkt.iToY = y = targ.second; pkt.iToZ = plr->z; pkt.iMoveStyle = 1; // notify all nearby players - NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); + NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } /* attack logic @@ -619,21 +617,21 @@ static void combatStep(Mob *mob, time_t currTime) { * if the mob is one move interval away, we should just start attacking anyways. */ if (distance <= mobRange || distanceToTravel < speed*2/5) { - if (mob->nextAttack == 0 || currTime >= mob->nextAttack) { - mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; - Combat::npcAttackPc(mob, currTime); + if (nextAttack == 0 || currTime >= nextAttack) { + nextAttack = currTime + (int)data["m_iDelayTime"] * 100; + Combat::npcAttackPc(this, currTime); } } // retreat if the player leaves combat range - int xyDistance = hypot(plr->x - mob->roamX, plr->y - mob->roamY); - distance = hypot(xyDistance, plr->z - mob->roamZ); - if (distance >= mob->data["m_iCombatRange"]) { - mob->target = nullptr; - mob->state = MobState::RETREAT; - clearDebuff(mob); - if (mob->groupLeader != 0) - groupRetreat(mob); + int xyDistance = hypot(plr->x - roamX, plr->y - roamY); + distance = hypot(xyDistance, plr->z - roamZ); + if (distance >= data["m_iCombatRange"]) { + target = nullptr; + state = MobState::RETREAT; + clearDebuff(this); + if (groupLeader != 0) + groupRetreat(this); } } @@ -645,23 +643,23 @@ void MobAI::incNextMovement(Mob *mob, time_t currTime) { mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2); } -static void roamingStep(Mob *mob, time_t currTime) { +void Mob::roamingStep(time_t currTime) { /* * We reuse nextAttack to avoid scanning for players all the time, but to still * do so more often than if we waited for nextMovement (which is way too slow). * In the case of group leaders, this step will be called by dead mobs, so disable attack. */ - if (mob->state != MobState::DEAD && (mob->nextAttack == 0 || currTime >= mob->nextAttack)) { - mob->nextAttack = currTime + 500; - if (aggroCheck(mob, currTime)) + if (state != MobState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) { + nextAttack = currTime + 500; + if (aggroCheck(this, currTime)) return; } // no random roaming if the mob already has a set path - if (mob->staticPath) + if (staticPath) return; - if (mob->groupLeader != 0 && mob->groupLeader != mob->id) // don't roam by yourself without group leader + if (groupLeader != 0 && groupLeader != id) // don't roam by yourself without group leader return; /* @@ -669,62 +667,62 @@ static void roamingStep(Mob *mob, time_t currTime) { * Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement), * so we don't have to check if there's already entries in the queue since we know there won't be. */ - if (mob->nextMovement != 0 && currTime < mob->nextMovement) + if (nextMovement != 0 && currTime < nextMovement) return; - incNextMovement(mob, currTime); + incNextMovement(this, currTime); - int xStart = mob->spawnX - mob->idleRange/2; - int yStart = mob->spawnY - mob->idleRange/2; - int speed = mob->speed; + int xStart = spawnX - idleRange/2; + int yStart = spawnY - idleRange/2; + int speed = speed; // some mobs don't move (and we mustn't divide/modulus by zero) - if (mob->idleRange == 0 || speed == 0) + if (idleRange == 0 || speed == 0) return; int farX, farY, distance; - int minDistance = mob->idleRange / 2; + int minDistance = idleRange / 2; // pick a random destination - farX = xStart + Rand::rand(mob->idleRange); - farY = yStart + Rand::rand(mob->idleRange); + farX = xStart + Rand::rand(idleRange); + farY = yStart + Rand::rand(idleRange); - distance = std::abs(std::max(farX - mob->x, farY - mob->y)); + distance = std::abs(std::max(farX - x, farY - y)); if (distance == 0) distance += 1; // hack to avoid FPE // if it's too short a walk, go further in that direction - farX = mob->x + (farX - mob->x) * minDistance / distance; - farY = mob->y + (farY - mob->y) * minDistance / distance; + farX = x + (farX - x) * minDistance / distance; + farY = y + (farY - y) * minDistance / distance; // but don't got out of bounds - farX = std::clamp(farX, xStart, xStart + mob->idleRange); - farY = std::clamp(farY, yStart, yStart + mob->idleRange); + farX = std::clamp(farX, xStart, xStart + idleRange); + farY = std::clamp(farY, yStart, yStart + idleRange); // halve movement speed if snared - if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) + if (cbf & CSB_BIT_DN_MOVE_SPEED) speed /= 2; std::queue queue; - Vec3 from = { mob->x, mob->y, mob->z }; - Vec3 to = { farX, farY, mob->z }; + Vec3 from = { x, y, z }; + Vec3 to = { farX, farY, z }; // add a route to the queue; to be processed in Transport::stepNPCPathing() Transport::lerp(&queue, from, to, speed); - Transport::NPCQueues[mob->id] = queue; + Transport::NPCQueues[id] = queue; - if (mob->groupLeader != 0 && mob->groupLeader == mob->id) { + if (groupLeader != 0 && groupLeader == id) { // make followers follow this npc. for (int i = 0; i < 4; i++) { - if (mob->groupMember[i] == 0) + if (groupMember[i] == 0) break; - if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityType::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } std::queue queue2; - Mob* followerMob = (Mob*)NPCManager::NPCs[mob->groupMember[i]]; + Mob* followerMob = (Mob*)NPCManager::NPCs[groupMember[i]]; from = { followerMob->x, followerMob->y, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; Transport::lerp(&queue2, from, to, speed); @@ -733,78 +731,77 @@ static void roamingStep(Mob *mob, time_t currTime) { } } -static void retreatStep(Mob *mob, time_t currTime) { - if (mob->nextMovement != 0 && currTime < mob->nextMovement) +void Mob::retreatStep(time_t currTime) { + if (nextMovement != 0 && currTime < nextMovement) return; - mob->nextMovement = currTime + 400; + nextMovement = currTime + 400; // distance between spawn point and current location - int distance = hypot(mob->x - mob->roamX, mob->y - mob->roamY); + int distance = hypot(x - roamX, y - roamY); //if (distance > mob->data["m_iIdleRange"]) { if (distance > 10) { INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - auto targ = lerp(mob->x, mob->y, mob->roamX, mob->roamY, (int)mob->speed*4/5); + auto targ = lerp(x, y, roamX, roamY, (int)speed*4/5); - pkt.iNPC_ID = mob->id; - pkt.iSpeed = (int)mob->speed * 2; - pkt.iToX = mob->x = targ.first; - pkt.iToY = mob->y = targ.second; - pkt.iToZ = mob->z = mob->spawnZ; + pkt.iNPC_ID = id; + pkt.iSpeed = (int)speed * 2; + pkt.iToX = x = targ.first; + pkt.iToY = y = targ.second; + pkt.iToZ = z = spawnZ; pkt.iMoveStyle = 1; // notify all nearby players - NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); + NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } // if we got there //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point - mob->state = MobState::ROAMING; - mob->hp = mob->maxHealth; - mob->killedTime = 0; - mob->nextAttack = 0; - mob->cbf = 0; + state = MobState::ROAMING; + hp = maxHealth; + killedTime = 0; + nextAttack = 0; + cbf = 0; // cast a return home heal spell, this is the right way(tm) std::vector targetData = {1, 0, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(mob->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); + pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); // clear outlying debuffs - clearDebuff(mob); + clearDebuff(this); } } -void MobAI::step(CombatNPC *npc, time_t currTime) { - assert(npc->kind == EntityType::MOB); - auto mob = (Mob*)npc; +void Mob::step(time_t currTime) { + assert(kind == EntityType::MOB); - if (mob->playersInView < 0) - std::cout << "[WARN] Weird playerview value " << mob->playersInView << std::endl; + if (playersInView < 0) + std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; // skip mob movement and combat if disabled or not in view - if ((!simulateMobs || mob->playersInView == 0) && mob->state != MobState::DEAD - && mob->state != MobState::RETREAT) + if ((!simulateMobs || playersInView == 0) && state != MobState::DEAD + && state != MobState::RETREAT) return; - switch (mob->state) { + switch (state) { case MobState::INACTIVE: // no-op break; case MobState::ROAMING: - roamingStep(mob, currTime); + roamingStep(currTime); break; case MobState::COMBAT: - combatStep(mob, currTime); + combatStep(currTime); break; case MobState::RETREAT: - retreatStep(mob, currTime); + retreatStep(currTime); break; case MobState::DEAD: - deadStep(mob, currTime); + deadStep(currTime); break; } } diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 5eb3db8..1c20c2d 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -11,11 +11,6 @@ enum class MobState { DEAD }; -namespace MobAI { - // needs to be declared before Mob's constructor - void step(CombatNPC*, time_t); -}; - struct Mob : public CombatNPC { // general MobState state = MobState::INACTIVE; @@ -78,7 +73,6 @@ struct Mob : public CombatNPC { hp = maxHealth; kind = EntityType::MOB; - _stepAI = MobAI::step; } // constructor for /summon @@ -89,6 +83,14 @@ struct Mob : public CombatNPC { ~Mob() {} + virtual void step(time_t currTime) override; + + // we may or may not want these to be generalized to all CombatNPCs later + void roamingStep(time_t currTime); + void combatStep(time_t currTime); + void retreatStep(time_t currTime); + void deadStep(time_t currTime); + auto operator[](std::string s) { return data[s]; } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 92c0f2a..990d2d1 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -355,7 +355,7 @@ static void step(CNServer *serv, time_t currTime) { continue; auto npc = (CombatNPC*)pair.second; - npc->stepAI(currTime); + npc->step(currTime); } // deallocate all NPCs queued for removal diff --git a/src/Player.hpp b/src/Player.hpp index 845b2c0..b0c970a 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -96,5 +96,7 @@ struct Player : public Entity, public ICombatant { virtual int getCurrentHP() override; virtual int32_t getID() override; + virtual void step(time_t currTime) override; + sPCAppearanceData getAppearanceData(); }; From c32e5b2d5e55c8a5ed6b5d1a4230fec3c84aa926 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 12 Apr 2022 22:22:12 -0400 Subject: [PATCH 015/104] (WIP) Point 2: Generalization --- src/Abilities.cpp | 10 +++---- src/Combat.cpp | 34 +++++++++++++++++++---- src/CustomCommands.cpp | 2 +- src/Entities.hpp | 19 +++++++++++-- src/MobAI.cpp | 62 ++++++++++-------------------------------- src/MobAI.hpp | 27 ++++-------------- src/Transport.cpp | 2 +- 7 files changed, 74 insertions(+), 82 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a2ed2cf..a84fddb 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -153,7 +153,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target respdata[i].eCT = 4; respdata[i].iID = mob->id; respdata[i].bProtected = 1; - if (mob->skillStyle < 0 && mob->state != MobState::RETREAT + if (mob->skillStyle < 0 && mob->state != AIState::RETREAT && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom mob->cbf |= bitFlag; mob->unbuffTimes[bitFlag] = getTime() + duration * 100; @@ -227,7 +227,7 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int respdata[i].iID = mob->id; respdata[i].iHP = mob->hp; respdata[i].bProtected = 1; - if (mob->skillStyle < 0 && mob->state != MobState::RETREAT + if (mob->skillStyle < 0 && mob->state != AIState::RETREAT && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom mob->cbf |= bitFlag; mob->unbuffTimes[bitFlag] = getTime() + duration * 100; @@ -450,7 +450,7 @@ bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, in if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) @@ -530,7 +530,7 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) @@ -588,7 +588,7 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) diff --git a/src/Combat.cpp b/src/Combat.cpp index 94a1620..c34d9fd 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -50,14 +50,14 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { Mob* mob = (Mob*)this; // cannot kill mobs multiple times; cannot harm retreating mobs - if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { + if (mob->state != AIState::ROAMING && mob->state != AIState::COMBAT) { return 0; // no damage } if (mob->skillStyle >= 0) return 0; // don't hurt a mob casting corruption - if (mob->state == MobState::ROAMING) { + if (mob->state == AIState::ROAMING) { assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now MobAI::enterCombat(src.sock, mob); @@ -101,7 +101,31 @@ int32_t CombatNPC::getID() { } void CombatNPC::step(time_t currTime) { - // stubbed + if (playersInView < 0) + std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; + + // skip movement and combat if disabled or not in view + if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD + && state != AIState::RETREAT) + return; + + switch (state) { + case AIState::INACTIVE: + // no-op + break; + case AIState::ROAMING: + roamingStep(currTime); + break; + case AIState::COMBAT: + combatStep(currTime); + break; + case AIState::RETREAT: + retreatStep(currTime); + break; + case AIState::DEAD: + deadStep(currTime); + break; + } } static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, @@ -268,7 +292,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, currTime)) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) @@ -302,7 +326,7 @@ static void genQItemRolls(Player *leader, std::map& rolls) { } void Combat::killMob(CNSocket *sock, Mob *mob) { - mob->state = MobState::DEAD; + mob->state = AIState::DEAD; mob->target = nullptr; mob->cbf = 0; mob->skillStyle = -1; diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 2d92c21..4c2bb9e 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -328,7 +328,7 @@ static void toggleAiCommand(std::string full, std::vector& args, CN continue; Mob* mob = (Mob*)pair.second; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; mob->target = nullptr; mob->nextMovement = getTime(); diff --git a/src/Entities.hpp b/src/Entities.hpp index 013fc7a..8c3bfea 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -16,6 +16,16 @@ enum class EntityType : uint8_t { BUS }; +enum class AIState { + INACTIVE, + ROAMING, + COMBAT, + RETREAT, + DEAD +}; + +class Chunk; + struct Entity { EntityType kind = EntityType::INVALID; int x = 0, y = 0, z = 0; @@ -123,6 +133,8 @@ struct CombatNPC : public BaseNPC, public ICombatant { int spawnZ = 0; int level = 0; int speed = 300; + AIState state = AIState::INACTIVE; + int playersInView = 0; // for optimizing away AI in empty chunks CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { @@ -138,12 +150,16 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual int32_t getID() override; + virtual void step(time_t currTime) override; + virtual void roamingStep(time_t currTime) {} // no-op by default + virtual void combatStep(time_t currTime) {} + virtual void retreatStep(time_t currTime) {} + virtual void deadStep(time_t currTime) {} }; // Mob is in MobAI.hpp, Player is in Player.hpp -// TODO: decouple from BaseNPC struct Egg : public BaseNPC { bool summoned = false; bool dead = false; @@ -161,7 +177,6 @@ struct Egg : public BaseNPC { virtual void disappearFromViewOf(CNSocket *sock) override; }; -// TODO: decouple from BaseNPC struct Bus : public BaseNPC { Bus(int angle, uint64_t iID, int t, int id) : BaseNPC(angle, iID, t, id) { diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 0ddeca6..a16478c 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -68,13 +68,13 @@ void MobAI::followToCombat(Mob *mob) { } Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; - if (followerMob->state != MobState::ROAMING) // only roaming mobs should transition to combat + if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat continue; enterCombat(mob->target, followerMob); } - if (leadMob->state != MobState::ROAMING) + if (leadMob->state != AIState::ROAMING) return; enterCombat(mob->target, leadMob); @@ -96,19 +96,19 @@ void MobAI::groupRetreat(Mob *mob) { } Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; - if (followerMob->state != MobState::COMBAT) + if (followerMob->state != AIState::COMBAT) continue; followerMob->target = nullptr; - followerMob->state = MobState::RETREAT; + followerMob->state = AIState::RETREAT; clearDebuff(followerMob); } - if (leadMob->state != MobState::COMBAT) + if (leadMob->state != AIState::COMBAT) return; leadMob->target = nullptr; - leadMob->state = MobState::RETREAT; + leadMob->state = AIState::RETREAT; clearDebuff(leadMob); } @@ -145,7 +145,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { if (levelDifference > -10) mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3; - if (mob->state != MobState::ROAMING && plr->inCombat) // freshly out of aggro mobs + if (mob->state != AIState::ROAMING && plr->inCombat) // freshly out of aggro mobs mobRange = mob->sightRange * 2; // should not be impacted by the above if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE)) @@ -267,7 +267,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!aggroCheck(mob, getTime())) { clearDebuff(mob); if (mob->groupLeader != 0) @@ -395,7 +395,7 @@ static void useAbilities(Mob *mob, time_t currTime) { void MobAI::enterCombat(CNSocket *sock, Mob *mob) { mob->target = sock; - mob->state = MobState::COMBAT; + mob->state = AIState::COMBAT; mob->nextMovement = getTime(); mob->nextAttack = 0; @@ -473,7 +473,7 @@ void Mob::deadStep(time_t currTime) { std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; hp = maxHealth; - state = MobState::ROAMING; + state = AIState::ROAMING; // if mob is a group leader/follower, spawn where the group is. if (groupLeader != 0) { @@ -501,7 +501,7 @@ void Mob::combatStep(time_t currTime) { // lose aggro if the player lost connection if (PlayerManager::players.find(target) == PlayerManager::players.end()) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; if (!aggroCheck(this, currTime)) { clearDebuff(this); if (groupLeader != 0) @@ -516,7 +516,7 @@ void Mob::combatStep(time_t currTime) { if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; if (!aggroCheck(this, currTime)) { clearDebuff(this); if (groupLeader != 0) @@ -572,7 +572,6 @@ void Mob::combatStep(time_t currTime) { } int distanceToTravel = INT_MAX; - int speed = speed; // movement logic: move when out of range but don't move while casting a skill if (distance > mobRange && skillStyle == -1) { if (nextMovement != 0 && currTime < nextMovement) @@ -628,7 +627,7 @@ void Mob::combatStep(time_t currTime) { distance = hypot(xyDistance, plr->z - roamZ); if (distance >= data["m_iCombatRange"]) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; clearDebuff(this); if (groupLeader != 0) groupRetreat(this); @@ -649,7 +648,7 @@ void Mob::roamingStep(time_t currTime) { * do so more often than if we waited for nextMovement (which is way too slow). * In the case of group leaders, this step will be called by dead mobs, so disable attack. */ - if (state != MobState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) { + if (state != AIState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) { nextAttack = currTime + 500; if (aggroCheck(this, currTime)) return; @@ -673,7 +672,6 @@ void Mob::roamingStep(time_t currTime) { int xStart = spawnX - idleRange/2; int yStart = spawnY - idleRange/2; - int speed = speed; // some mobs don't move (and we mustn't divide/modulus by zero) if (idleRange == 0 || speed == 0) @@ -760,7 +758,7 @@ void Mob::retreatStep(time_t currTime) { // if we got there //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point - state = MobState::ROAMING; + state = AIState::ROAMING; hp = maxHealth; killedTime = 0; nextAttack = 0; @@ -775,33 +773,3 @@ void Mob::retreatStep(time_t currTime) { clearDebuff(this); } } - -void Mob::step(time_t currTime) { - assert(kind == EntityType::MOB); - - if (playersInView < 0) - std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; - - // skip mob movement and combat if disabled or not in view - if ((!simulateMobs || playersInView == 0) && state != MobState::DEAD - && state != MobState::RETREAT) - return; - - switch (state) { - case MobState::INACTIVE: - // no-op - break; - case MobState::ROAMING: - roamingStep(currTime); - break; - case MobState::COMBAT: - combatStep(currTime); - break; - case MobState::RETREAT: - retreatStep(currTime); - break; - case MobState::DEAD: - deadStep(currTime); - break; - } -} diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 1c20c2d..1c6c178 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -2,19 +2,10 @@ #include "core/Core.hpp" #include "NPCManager.hpp" - -enum class MobState { - INACTIVE, - ROAMING, - COMBAT, - RETREAT, - DEAD -}; +#include "Entities.hpp" struct Mob : public CombatNPC { // general - MobState state = MobState::INACTIVE; - std::unordered_map unbuffTimes = {}; // dead @@ -42,16 +33,13 @@ struct Mob : public CombatNPC { int offsetX = 0, offsetY = 0; int groupMember[4] = {}; - // for optimizing away AI in empty chunks - int playersInView = 0; - // temporary; until we're sure what's what nlohmann::json data = {}; Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), sightRange(d["m_iSightRange"]) { - state = MobState::ROAMING; + state = AIState::ROAMING; data = d; @@ -83,13 +71,10 @@ struct Mob : public CombatNPC { ~Mob() {} - virtual void step(time_t currTime) override; - - // we may or may not want these to be generalized to all CombatNPCs later - void roamingStep(time_t currTime); - void combatStep(time_t currTime); - void retreatStep(time_t currTime); - void deadStep(time_t currTime); + virtual void roamingStep(time_t currTime) override; + virtual void combatStep(time_t currTime) override; + virtual void retreatStep(time_t currTime) override; + virtual void deadStep(time_t currTime) override; auto operator[](std::string s) { return data[s]; diff --git a/src/Transport.cpp b/src/Transport.cpp index 36b0de6..9fe2ccc 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -263,7 +263,7 @@ static void stepNPCPathing() { } // do not roam if not roaming - if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { + if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != AIState::ROAMING) { it++; continue; } From 69a478b7772f48db711db33a8d9877f55f7f2cf3 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 12 Apr 2022 23:29:11 -0400 Subject: [PATCH 016/104] (WIP) Transitions + hook definitions + onRetreat hook implementation --- src/Abilities.cpp | 24 +++----------------- src/Combat.cpp | 29 ++++++++++++++++++------ src/Entities.hpp | 9 +++++++- src/MobAI.cpp | 56 +++++++++++++++++++++++++---------------------- src/MobAI.hpp | 6 +++++ 5 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a84fddb..988947b 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -449,13 +449,7 @@ bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, in respdata[i].iConditionBitFlag = plr->iConditionBitFlag; if (plr->HP <= 0) { - mob->target = nullptr; - mob->state = AIState::RETREAT; - if (!MobAI::aggroCheck(mob, getTime())) { - MobAI::clearDebuff(mob); - if (mob->groupLeader != 0) - MobAI::groupRetreat(mob); - } + mob->transition(AIState::RETREAT); } return true; @@ -529,13 +523,7 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, respdata[i].iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->target = nullptr; - mob->state = AIState::RETREAT; - if (!MobAI::aggroCheck(mob, getTime())) { - MobAI::clearDebuff(mob); - if (mob->groupLeader != 0) - MobAI::groupRetreat(mob); - } + mob->transition(AIState::RETREAT); } return true; @@ -587,13 +575,7 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, damagedata->iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->target = nullptr; - mob->state = AIState::RETREAT; - if (!MobAI::aggroCheck(mob, getTime())) { - MobAI::clearDebuff(mob); - if (mob->groupLeader != 0) - MobAI::groupRetreat(mob); - } + mob->transition(AIState::RETREAT); } return true; diff --git a/src/Combat.cpp b/src/Combat.cpp index c34d9fd..9588dde 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -128,6 +128,27 @@ void CombatNPC::step(time_t currTime) { } } +void CombatNPC::transition(AIState newState) { + state = newState; + switch (newState) { + case AIState::INACTIVE: + onInactive(); + break; + case AIState::ROAMING: + onRoamStart(); + break; + case AIState::COMBAT: + onCombatStart(); + break; + case AIState::RETREAT: + onRetreat(); + break; + case AIState::DEAD: + onDeath(); + break; + } +} + static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, int defenderStyle, int difficulty) { @@ -291,13 +312,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); if (plr->HP <= 0) { - mob->target = nullptr; - mob->state = AIState::RETREAT; - if (!MobAI::aggroCheck(mob, currTime)) { - MobAI::clearDebuff(mob); - if (mob->groupLeader != 0) - MobAI::groupRetreat(mob); - } + mob->transition(AIState::RETREAT); } } diff --git a/src/Entities.hpp b/src/Entities.hpp index 8c3bfea..cbfeb32 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -152,10 +152,17 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual int32_t getID() override; virtual void step(time_t currTime) override; - virtual void roamingStep(time_t currTime) {} // no-op by default + virtual void roamingStep(time_t currTime) {} // no-ops by default virtual void combatStep(time_t currTime) {} virtual void retreatStep(time_t currTime) {} virtual void deadStep(time_t currTime) {} + + virtual void transition(AIState newState); + virtual void onInactive() {} // no-ops by default + virtual void onRoamStart() {} + virtual void onCombatStart() {} + virtual void onRetreat() {} + virtual void onDeath() {} }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/MobAI.cpp b/src/MobAI.cpp index a16478c..0e9675b 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -266,13 +266,8 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i respdata[i].iConditionBitFlag = plr->iConditionBitFlag; if (plr->HP <= 0) { - mob->target = nullptr; - mob->state = AIState::RETREAT; - if (!aggroCheck(mob, getTime())) { - clearDebuff(mob); - if (mob->groupLeader != 0) - groupRetreat(mob); - } + if (!MobAI::aggroCheck(mob, getTime())) + mob->transition(AIState::RETREAT); } } @@ -500,13 +495,8 @@ void Mob::combatStep(time_t currTime) { // lose aggro if the player lost connection if (PlayerManager::players.find(target) == PlayerManager::players.end()) { - target = nullptr; - state = AIState::RETREAT; - if (!aggroCheck(this, currTime)) { - clearDebuff(this); - if (groupLeader != 0) - groupRetreat(this); - } + if (!MobAI::aggroCheck(this, getTime())) + transition(AIState::RETREAT); return; } @@ -515,13 +505,8 @@ void Mob::combatStep(time_t currTime) { // lose aggro if the player became invulnerable or died if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { - target = nullptr; - state = AIState::RETREAT; - if (!aggroCheck(this, currTime)) { - clearDebuff(this); - if (groupLeader != 0) - groupRetreat(this); - } + if (!MobAI::aggroCheck(this, getTime())) + transition(AIState::RETREAT); return; } @@ -626,11 +611,7 @@ void Mob::combatStep(time_t currTime) { int xyDistance = hypot(plr->x - roamX, plr->y - roamY); distance = hypot(xyDistance, plr->z - roamZ); if (distance >= data["m_iCombatRange"]) { - target = nullptr; - state = AIState::RETREAT; - clearDebuff(this); - if (groupLeader != 0) - groupRetreat(this); + transition(AIState::RETREAT); } } @@ -773,3 +754,26 @@ void Mob::retreatStep(time_t currTime) { clearDebuff(this); } } + +void Mob::onInactive() { + // stub +} + +void Mob::onRoamStart() { + // stub +} + +void Mob::onCombatStart() { + // stub +} + +void Mob::onRetreat() { + target = nullptr; + MobAI::clearDebuff(this); + if (groupLeader != 0) + MobAI::groupRetreat(this); +} + +void Mob::onDeath() { + // stub +} diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 1c6c178..86721e6 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -76,6 +76,12 @@ struct Mob : public CombatNPC { virtual void retreatStep(time_t currTime) override; virtual void deadStep(time_t currTime) override; + virtual void onInactive() override; + virtual void onRoamStart() override; + virtual void onCombatStart() override; + virtual void onRetreat() override; + virtual void onDeath() override; + auto operator[](std::string s) { return data[s]; } From 45742e90a20c316fbcbb4b18e70b478462fc316d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 12 Apr 2022 23:44:12 -0400 Subject: [PATCH 017/104] (WIP) Add src param to transition + certain hooks Should all hooks have src? I think not --- src/Abilities.cpp | 6 +++--- src/Combat.cpp | 8 ++++---- src/Entities.hpp | 6 +++--- src/MobAI.cpp | 12 ++++++------ src/MobAI.hpp | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 988947b..f7730e3 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -449,7 +449,7 @@ bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, in respdata[i].iConditionBitFlag = plr->iConditionBitFlag; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT); + mob->transition(AIState::RETREAT, mob->target); } return true; @@ -523,7 +523,7 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, respdata[i].iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT); + mob->transition(AIState::RETREAT, mob->target); } return true; @@ -575,7 +575,7 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, damagedata->iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT); + mob->transition(AIState::RETREAT, mob->target); } return true; diff --git a/src/Combat.cpp b/src/Combat.cpp index 9588dde..9109a87 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -128,7 +128,7 @@ void CombatNPC::step(time_t currTime) { } } -void CombatNPC::transition(AIState newState) { +void CombatNPC::transition(AIState newState, EntityRef src) { state = newState; switch (newState) { case AIState::INACTIVE: @@ -138,13 +138,13 @@ void CombatNPC::transition(AIState newState) { onRoamStart(); break; case AIState::COMBAT: - onCombatStart(); + onCombatStart(src); break; case AIState::RETREAT: onRetreat(); break; case AIState::DEAD: - onDeath(); + onDeath(src); break; } } @@ -312,7 +312,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); if (plr->HP <= 0) { - mob->transition(AIState::RETREAT); + mob->transition(AIState::RETREAT, mob->target); } } diff --git a/src/Entities.hpp b/src/Entities.hpp index cbfeb32..638b7d8 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -157,12 +157,12 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual void retreatStep(time_t currTime) {} virtual void deadStep(time_t currTime) {} - virtual void transition(AIState newState); + virtual void transition(AIState newState, EntityRef src); virtual void onInactive() {} // no-ops by default virtual void onRoamStart() {} - virtual void onCombatStart() {} + virtual void onCombatStart(EntityRef src) {} virtual void onRetreat() {} - virtual void onDeath() {} + virtual void onDeath(EntityRef src) {} }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 0e9675b..ffc8bb2 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -267,7 +267,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->HP <= 0) { if (!MobAI::aggroCheck(mob, getTime())) - mob->transition(AIState::RETREAT); + mob->transition(AIState::RETREAT, mob->target); } } @@ -496,7 +496,7 @@ void Mob::combatStep(time_t currTime) { // lose aggro if the player lost connection if (PlayerManager::players.find(target) == PlayerManager::players.end()) { if (!MobAI::aggroCheck(this, getTime())) - transition(AIState::RETREAT); + transition(AIState::RETREAT, target); return; } @@ -506,7 +506,7 @@ void Mob::combatStep(time_t currTime) { if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { if (!MobAI::aggroCheck(this, getTime())) - transition(AIState::RETREAT); + transition(AIState::RETREAT, target); return; } @@ -611,7 +611,7 @@ void Mob::combatStep(time_t currTime) { int xyDistance = hypot(plr->x - roamX, plr->y - roamY); distance = hypot(xyDistance, plr->z - roamZ); if (distance >= data["m_iCombatRange"]) { - transition(AIState::RETREAT); + transition(AIState::RETREAT, target); } } @@ -763,7 +763,7 @@ void Mob::onRoamStart() { // stub } -void Mob::onCombatStart() { +void Mob::onCombatStart(EntityRef src) { // stub } @@ -774,6 +774,6 @@ void Mob::onRetreat() { MobAI::groupRetreat(this); } -void Mob::onDeath() { +void Mob::onDeath(EntityRef src) { // stub } diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 86721e6..7ad9353 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -78,9 +78,9 @@ struct Mob : public CombatNPC { virtual void onInactive() override; virtual void onRoamStart() override; - virtual void onCombatStart() override; + virtual void onCombatStart(EntityRef src) override; virtual void onRetreat() override; - virtual void onDeath() override; + virtual void onDeath(EntityRef src) override; auto operator[](std::string s) { return data[s]; From 68d53feea324d34cef4b052477350883ec4f0bd0 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 00:23:53 -0400 Subject: [PATCH 018/104] (WIP) onDeath hook implementation --- src/Combat.cpp | 82 +++++--------------------------------------------- src/Combat.hpp | 4 ++- src/MobAI.cpp | 71 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 9109a87..047c80c 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -79,7 +79,7 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { } if (mob->hp <= 0) - killMob(mob->target, mob); + transition(AIState::DEAD, src); return amt; } @@ -144,6 +144,11 @@ void CombatNPC::transition(AIState newState, EntityRef src) { onRetreat(); break; case AIState::DEAD: + /* TODO: fire any triggered events + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.trigger == ON_KILLED && event.npcType == type) + event.handler(src, this); + */ onDeath(src); break; } @@ -323,7 +328,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { * single RNG roll per mission task, and every group member shares that same * set of rolls. */ -static void genQItemRolls(Player *leader, std::map& rolls) { +void Combat::genQItemRolls(Player *leader, std::map& rolls) { for (int i = 0; i < leader->groupCnt; i++) { if (leader->groupIDs[i] == 0) continue; @@ -340,79 +345,6 @@ static void genQItemRolls(Player *leader, std::map& rolls) { } } -void Combat::killMob(CNSocket *sock, Mob *mob) { - mob->state = AIState::DEAD; - mob->target = nullptr; - mob->cbf = 0; - mob->skillStyle = -1; - mob->unbuffTimes.clear(); - mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? - - // check for the edge case where hitting the mob did not aggro it - if (sock != nullptr) { - Player* plr = PlayerManager::getPlayer(sock); - - Items::DropRoll rolled; - Items::DropRoll eventRolled; - std::map qitemRolls; - - Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); - assert(leader != nullptr); // should never happen - - genQItemRolls(leader, qitemRolls); - - if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { - Items::giveMobDrop(sock, mob, rolled, eventRolled); - Missions::mobKilled(sock, mob->type, qitemRolls); - } else { - for (int i = 0; i < leader->groupCnt; i++) { - CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); - if (sockTo == nullptr) - continue; - - Player *otherPlr = PlayerManager::getPlayer(sockTo); - - // only contribute to group members' kills if they're close enough - int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); - if (dist > 5000) - continue; - - Items::giveMobDrop(sockTo, mob, rolled, eventRolled); - Missions::mobKilled(sockTo, mob->type, qitemRolls); - } - } - } - - // delay the despawn animation - mob->despawned = false; - - // fire any triggered events - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == mob->type) - event.handler(sock, mob); - - auto it = Transport::NPCQueues.find(mob->id); - if (it == Transport::NPCQueues.end() || it->second.empty()) - return; - - // rewind or empty the movement queue - if (mob->staticPath) { - /* - * This is inelegant, but we wind forward in the path until we find the point that - * corresponds with the Mob's spawn point. - * - * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever. - */ - auto& queue = it->second; - for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) { - queue.pop(); - queue.push(point); - } - } else { - Transport::NPCQueues.erase(mob->id); - } -} - static void combatBegin(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); diff --git a/src/Combat.hpp b/src/Combat.hpp index bb06e54..14d0076 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -17,11 +17,13 @@ struct Bullet { int bulletType; }; + + namespace Combat { extern std::map> Bullets; void init(); void npcAttackPc(Mob *mob, time_t currTime); - void killMob(CNSocket *sock, Mob *mob); + void genQItemRolls(Player* leader, std::map& rolls); } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index ffc8bb2..d9d430e 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -6,6 +6,8 @@ #include "Combat.hpp" #include "Abilities.hpp" #include "Rand.hpp" +#include "Items.hpp" +#include "Missions.hpp" #include #include @@ -431,7 +433,7 @@ static void drainMobHP(Mob *mob, int amount) { NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); if (mob->hp <= 0) - Combat::killMob(mob->target, mob); + mob->transition(AIState::DEAD, mob->target); } void Mob::deadStep(time_t currTime) { @@ -775,5 +777,70 @@ void Mob::onRetreat() { } void Mob::onDeath(EntityRef src) { - // stub + target = nullptr; + cbf = 0; + skillStyle = -1; + unbuffTimes.clear(); + killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? + + // check for the edge case where hitting the mob did not aggro it + if (src.type == EntityType::PLAYER && src.isValid()) { + Player* plr = PlayerManager::getPlayer(src.sock); + + Items::DropRoll rolled; + Items::DropRoll eventRolled; + std::map qitemRolls; + + Player* leader = PlayerManager::getPlayerFromID(plr->iIDGroup); + assert(leader != nullptr); // should never happen + + Combat::genQItemRolls(leader, qitemRolls); + + if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { + Items::giveMobDrop(src.sock, this, rolled, eventRolled); + Missions::mobKilled(src.sock, type, qitemRolls); + } + else { + for (int i = 0; i < leader->groupCnt; i++) { + CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); + if (sockTo == nullptr) + continue; + + Player* otherPlr = PlayerManager::getPlayer(sockTo); + + // only contribute to group members' kills if they're close enough + int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); + if (dist > 5000) + continue; + + Items::giveMobDrop(sockTo, this, rolled, eventRolled); + Missions::mobKilled(sockTo, type, qitemRolls); + } + } + } + + // delay the despawn animation + despawned = false; + + auto it = Transport::NPCQueues.find(id); + if (it == Transport::NPCQueues.end() || it->second.empty()) + return; + + // rewind or empty the movement queue + if (staticPath) { + /* + * This is inelegant, but we wind forward in the path until we find the point that + * corresponds with the Mob's spawn point. + * + * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever. + */ + auto& queue = it->second; + for (auto point = queue.front(); point.x != spawnX || point.y != spawnY; point = queue.front()) { + queue.pop(); + queue.push(point); + } + } + else { + Transport::NPCQueues.erase(id); + } } From 345c9cd3b266aad69cae0b1b79e2ee8716bfc323 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 00:54:13 -0400 Subject: [PATCH 019/104] (WIP) onCombatStart hook implementation --- src/Combat.cpp | 7 ++++++- src/MobAI.cpp | 44 ++++++++++++++++++-------------------------- src/MobAI.hpp | 1 - 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 047c80c..2b9a251 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -59,7 +59,7 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { if (mob->state == AIState::ROAMING) { assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now - MobAI::enterCombat(src.sock, mob); + mob->transition(AIState::COMBAT, src); if (mob->groupLeader != 0) MobAI::followToCombat(mob); @@ -138,6 +138,11 @@ void CombatNPC::transition(AIState newState, EntityRef src) { onRoamStart(); break; case AIState::COMBAT: + /* TODO: fire any triggered events + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.trigger == ON_COMBAT && event.npcType == type) + event.handler(src, this); + */ onCombatStart(src); break; case AIState::RETREAT: diff --git a/src/MobAI.cpp b/src/MobAI.cpp index d9d430e..cc73de5 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -73,13 +73,13 @@ void MobAI::followToCombat(Mob *mob) { if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat continue; - enterCombat(mob->target, followerMob); + followerMob->transition(AIState::COMBAT, mob->target); } if (leadMob->state != AIState::ROAMING) return; - enterCombat(mob->target, leadMob); + leadMob->transition(AIState::COMBAT, mob->target); } } @@ -168,7 +168,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { if (closest != nullptr) { // found closest player. engage. - enterCombat(closest, mob); + mob->transition(AIState::COMBAT, closest); if (mob->groupLeader != 0) followToCombat(mob); @@ -390,27 +390,6 @@ static void useAbilities(Mob *mob, time_t currTime) { return; } -void MobAI::enterCombat(CNSocket *sock, Mob *mob) { - mob->target = sock; - mob->state = AIState::COMBAT; - mob->nextMovement = getTime(); - mob->nextAttack = 0; - - mob->roamX = mob->x; - mob->roamY = mob->y; - mob->roamZ = mob->z; - - int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive - std::vector targetData = {1, mob->id, 0, 0, 0}; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); - - for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT - if (event.trigger == ON_COMBAT && event.npcType == mob->type) - event.handler(sock, mob); -} - static void drainMobHP(Mob *mob, int amount) { size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); @@ -758,7 +737,7 @@ void Mob::retreatStep(time_t currTime) { } void Mob::onInactive() { - // stub + // no-op } void Mob::onRoamStart() { @@ -766,7 +745,20 @@ void Mob::onRoamStart() { } void Mob::onCombatStart(EntityRef src) { - // stub + assert(src.type == EntityType::PLAYER); + target = src.sock; + nextMovement = getTime(); + nextAttack = 0; + + roamX = x; + roamY = y; + roamZ = z; + + int skillID = (int)data["m_iPassiveBuff"]; // cast passive + std::vector targetData = { 1, id, 0, 0, 0 }; + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[skillID].skillType) + pwr.handle(id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); } void Mob::onRetreat() { diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 7ad9353..dbb267b 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -96,5 +96,4 @@ namespace MobAI { void clearDebuff(Mob *mob); void followToCombat(Mob *mob); void groupRetreat(Mob *mob); - void enterCombat(CNSocket *sock, Mob *mob); } From 35e938b8c6023df06f290ca37f0dcbd0f5951de9 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 08:51:50 -0400 Subject: [PATCH 020/104] ope --- src/Abilities.cpp | 9 ++++++--- src/Combat.cpp | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index f7730e3..d0c2a44 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -449,7 +449,8 @@ bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, in respdata[i].iConditionBitFlag = plr->iConditionBitFlag; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT, mob->target); + if (!MobAI::aggroCheck(mob, getTime())) + mob->transition(AIState::RETREAT, mob->target); } return true; @@ -523,7 +524,8 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, respdata[i].iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT, mob->target); + if (!MobAI::aggroCheck(mob, getTime())) + mob->transition(AIState::RETREAT, mob->target); } return true; @@ -575,7 +577,8 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, damagedata->iHP = plr->HP -= damage; if (plr->HP <= 0) { - mob->transition(AIState::RETREAT, mob->target); + if (!MobAI::aggroCheck(mob, getTime())) + mob->transition(AIState::RETREAT, mob->target); } return true; diff --git a/src/Combat.cpp b/src/Combat.cpp index 2b9a251..1b83cdf 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -322,7 +322,8 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); if (plr->HP <= 0) { - mob->transition(AIState::RETREAT, mob->target); + if (!MobAI::aggroCheck(mob, getTime())) + mob->transition(AIState::RETREAT, mob->target); } } From b6f15824f1b9fb5db30035ef852508bdca4ce429 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 14:18:54 -0400 Subject: [PATCH 021/104] (WIP) Remove `BaseNPC::barkerType` to save space --- src/Entities.cpp | 2 +- src/Entities.hpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Entities.cpp b/src/Entities.cpp index a859af2..ba6d136 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -42,7 +42,7 @@ Entity *EntityRef::getEntity() const { sNPCAppearanceData BaseNPC::getAppearanceData() { sNPCAppearanceData data = {}; data.iAngle = angle; - data.iBarkerType = barkerType; + data.iBarkerType = 0; // unused? data.iConditionBitFlag = cbf; data.iHP = hp; data.iNPCType = type; diff --git a/src/Entities.hpp b/src/Entities.hpp index 638b7d8..d1e46e5 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -107,7 +107,6 @@ public: int hp; int angle; int cbf; - int barkerType; bool loopingPath = false; BaseNPC(int _A, uint64_t iID, int t, int _id) { @@ -115,7 +114,6 @@ public: hp = 400; angle = _A; cbf = 0; - barkerType = 0; id = _id; instanceID = iID; }; From 595dcda1b762c97682b34f0bc01adcad36437e35 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 15:09:43 -0400 Subject: [PATCH 022/104] (WIP) onRoamStart hook implementation --- src/MobAI.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index cc73de5..2a7661b 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -448,8 +448,7 @@ void Mob::deadStep(time_t currTime) { std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; - hp = maxHealth; - state = AIState::ROAMING; + transition(AIState::ROAMING, id); // if mob is a group leader/follower, spawn where the group is. if (groupLeader != 0) { @@ -720,19 +719,7 @@ void Mob::retreatStep(time_t currTime) { // if we got there //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point - state = AIState::ROAMING; - hp = maxHealth; - killedTime = 0; - nextAttack = 0; - cbf = 0; - - // cast a return home heal spell, this is the right way(tm) - std::vector targetData = {1, 0, 0, 0, 0}; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); - // clear outlying debuffs - clearDebuff(this); + transition(AIState::ROAMING, id); } } @@ -741,7 +728,18 @@ void Mob::onInactive() { } void Mob::onRoamStart() { - // stub + hp = maxHealth; + killedTime = 0; + nextAttack = 0; + cbf = 0; + + // cast a return home heal spell, this is the right way(tm) + std::vector targetData = { 1, 0, 0, 0, 0 }; + for (auto& pwr : Abilities::Powers) + if (pwr.skillType == Abilities::SkillTable[110].skillType) + pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); + // clear outlying debuffs + clearDebuff(this); } void Mob::onCombatStart(EntityRef src) { From f6094fde589befc0c3660d82ad65ca672d31c722 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 15:56:12 -0400 Subject: [PATCH 023/104] EntityType -> EntityKind --- src/Abilities.cpp | 10 +++++----- src/Chunking.cpp | 26 +++++++++++++------------- src/Combat.cpp | 21 ++++++++++----------- src/CustomCommands.cpp | 16 ++++++++-------- src/Eggs.cpp | 4 ++-- src/Entities.cpp | 8 ++++---- src/Entities.hpp | 21 ++++++++++----------- src/Missions.cpp | 2 +- src/MobAI.cpp | 20 ++++++++++---------- src/MobAI.hpp | 2 +- src/NPCManager.cpp | 6 +++--- src/Player.hpp | 2 +- src/PlayerManager.cpp | 2 +- src/PlayerManager.hpp | 2 +- src/TableData.cpp | 6 +++--- src/Transport.cpp | 10 +++++----- 16 files changed, 78 insertions(+), 80 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index d0c2a44..e07f913 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -142,7 +142,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] doDebuff: NPC is not a mob" << std::endl; return false; } @@ -213,7 +213,7 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] doDamageNDebuff: NPC is not a mob" << std::endl; return false; } @@ -281,7 +281,7 @@ bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targ } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] doDamage: NPC is not a mob" << std::endl; return false; } @@ -337,7 +337,7 @@ bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targ } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] doLeech: NPC is not a mob" << std::endl; return false; } @@ -463,7 +463,7 @@ bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, i } BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] doHeal: NPC is not a mob" << std::endl; return false; } diff --git a/src/Chunking.cpp b/src/Chunking.cpp index d26fe8d..6e96c0a 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -54,7 +54,7 @@ void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { chunks[chunkPos]->entities.insert(ref); - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) chunks[chunkPos]->nplayers++; } @@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { chunk->entities.erase(ref); // gone - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) chunks[chunkPos]->nplayers--; assert(chunks[chunkPos]->nplayers >= 0); @@ -89,19 +89,19 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { Entity *other = otherRef.getEntity(); // notify all visible players of the existence of this Entity - if (alive && otherRef.type == EntityType::PLAYER) { + if (alive && otherRef.kind == EntityKind::PLAYER) { ent->enterIntoViewOf(otherRef.sock); } // notify this *player* of the existence of all visible Entities - if (ref.type == EntityType::PLAYER && other->isExtant()) { + if (ref.kind == EntityKind::PLAYER && other->isExtant()) { other->enterIntoViewOf(ref.sock); } // for mobs, increment playersInView - if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) + if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER) ((Mob*)ent)->playersInView++; - if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) + if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER) ((Mob*)other)->playersInView++; } } @@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef& r Entity *other = otherRef.getEntity(); // notify all visible players of the departure of this Entity - if (alive && otherRef.type == EntityType::PLAYER) { + if (alive && otherRef.kind == EntityKind::PLAYER) { ent->disappearFromViewOf(otherRef.sock); } // notify this *player* of the departure of all visible Entities - if (ref.type == EntityType::PLAYER && other->isExtant()) { + if (ref.kind == EntityKind::PLAYER && other->isExtant()) { other->disappearFromViewOf(ref.sock); } // for mobs, decrement playersInView - if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) + if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER) ((Mob*)ent)->playersInView--; - if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) + if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER) ((Mob*)other)->playersInView--; } } @@ -155,7 +155,7 @@ static void emptyChunk(ChunkPos chunkPos) { // unspawn all of the mobs/npcs std::set refs(chunk->entities); for (const EntityRef& ref : refs) { - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) assert(0); // every call of this will check if the chunk is empty and delete it if so @@ -268,14 +268,14 @@ void Chunking::createInstance(uint64_t instanceID) { std::cout << "Creating instance " << instanceID << std::endl; for (ChunkPos &coords : templateChunks) { for (const EntityRef& ref : chunks[coords]->entities) { - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) continue; int npcID = ref.id; BaseNPC* baseNPC = (BaseNPC*)ref.getEntity(); // make a copy of each NPC in the template chunks and put them in the new instance - if (baseNPC->kind == EntityType::MOB) { + if (baseNPC->kind == EntityKind::MOB) { if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) continue; // follower; don't copy individually diff --git a/src/Combat.cpp b/src/Combat.cpp index 1b83cdf..538b425 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -58,7 +58,7 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { return 0; // don't hurt a mob casting corruption if (mob->state == AIState::ROAMING) { - assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now + assert(mob->target == nullptr && src.kind == EntityKind::PLAYER); // players only for now mob->transition(AIState::COMBAT, src); if (mob->groupLeader != 0) @@ -256,7 +256,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { BaseNPC* npc = NPCManager::NPCs[targets[i]]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl; return; } @@ -450,7 +450,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { return; } - int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); + sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) { std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n"; @@ -471,7 +471,6 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { for (int i = 0; i < pkt->iTargetCnt; i++) { ICombatant* target = nullptr; - sGM_PVPTarget* targdata = (sGM_PVPTarget*)(pktdata + i * 2); std::pair damage; if (pkt->iTargetCnt > 1) @@ -479,10 +478,10 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { else damage.first = plr->pointDamage; - if (targdata->eCT == 1) { // eCT == 1; attack player + if (pktdata[i].eCT == 1) { // eCT == 1; attack player for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targdata->iID) { + if (pair.second->iID == pktdata[i].iID) { target = pair.second; break; } @@ -498,14 +497,14 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { } else { // eCT == 4; attack mob - if (NPCManager::NPCs.find(targdata->iID) == NPCManager::NPCs.end()) { + if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) { // not sure how to best handle this std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl; return; } - BaseNPC* npc = NPCManager::NPCs[targdata->iID]; - if (npc->kind != EntityType::MOB) { + BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID]; + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; return; } @@ -524,7 +523,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { damage.first = target->takeDamage(sock, damage.first); - respdata[i].eCT = targdata->eCT; + respdata[i].eCT = pktdata[i].eCT; respdata[i].iID = target->getID(); respdata[i].iDamage = damage.first; respdata[i].iHP = target->getCurrentHP(); @@ -707,7 +706,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { } BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; - if (npc->kind != EntityType::MOB) { + if (npc->kind != EntityKind::MOB) { std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl; return; } diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 4c2bb9e..36e0108 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -283,10 +283,10 @@ static void unsummonWCommand(std::string full, std::vector& args, C return; } - if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityType::MOB) { + if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) { int leadId = ((Mob*)npc)->groupLeader; if (leadId != 0) { - if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) { std::cout << "[WARN] unsummonW: leader not found!" << std::endl; } Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId]; @@ -294,7 +294,7 @@ static void unsummonWCommand(std::string full, std::vector& args, C if (leadNpc->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl; continue; } @@ -324,7 +324,7 @@ static void toggleAiCommand(std::string full, std::vector& args, CN // return all mobs to their spawn points for (auto& pair : NPCManager::NPCs) { - if (pair.second->kind != EntityType::MOB) + if (pair.second->kind != EntityKind::MOB) continue; Mob* mob = (Mob*)pair.second; @@ -609,7 +609,7 @@ static void summonGroupCommand(std::string full, std::vector& args, } BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); - if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { + if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) { leadNpc->groupMember[i-1] = npc->id; Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; mob->groupLeader = leadNpc->id; @@ -624,7 +624,7 @@ static void summonGroupCommand(std::string full, std::vector& args, if (PLAYERID(plr->instanceID) != 0) { npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); - if (team == 2 && i > 0 && npc->kind == EntityType::MOB) { + if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) { leadNpc->groupMember[i-1] = npc->id; Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; mob->groupLeader = leadNpc->id; @@ -639,7 +639,7 @@ static void summonGroupCommand(std::string full, std::vector& args, Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + ", id: " + std::to_string(npc->id)); - if (i == 0 && team == 2 && npc->kind == EntityType::MOB) { + if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) { type = type2; leadNpc = (Mob*)NPCManager::NPCs[npc->id]; leadNpc->groupLeader = leadNpc->id; @@ -694,7 +694,7 @@ static void lairUnlockCommand(std::string full, std::vector& args, int lastDist = INT_MAX; for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) { for (const EntityRef& ref : chnk->entities) { - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) continue; BaseNPC* npc = (BaseNPC*)ref.getEntity(); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index f29d5cc..64e2f12 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -124,7 +124,7 @@ static void eggStep(CNServer* serv, time_t currTime) { // check dead eggs and eggs in inactive chunks for (auto npc : NPCManager::NPCs) { - if (npc.second->kind != EntityType::EGG) + if (npc.second->kind != EntityKind::EGG) continue; auto egg = (Egg*)npc.second; @@ -163,7 +163,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { return; } auto egg = (Egg*)eggRef.getEntity(); - if (egg->kind != EntityType::EGG) { + if (egg->kind != EntityKind::EGG) { std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; return; } diff --git a/src/Entities.cpp b/src/Entities.cpp index ba6d136..ff63053 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -12,7 +12,7 @@ static_assert(std::is_standard_layout::value); static_assert(std::is_trivially_copyable::value); EntityRef::EntityRef(CNSocket *s) { - type = EntityType::PLAYER; + kind = EntityKind::PLAYER; sock = s; } @@ -20,11 +20,11 @@ EntityRef::EntityRef(int32_t i) { id = i; assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); - type = NPCManager::NPCs[id]->kind; + kind = NPCManager::NPCs[id]->kind; } bool EntityRef::isValid() const { - if (type == EntityType::PLAYER) + if (kind == EntityKind::PLAYER) return PlayerManager::players.find(sock) != PlayerManager::players.end(); return NPCManager::NPCs.find(id) != NPCManager::NPCs.end(); @@ -33,7 +33,7 @@ bool EntityRef::isValid() const { Entity *EntityRef::getEntity() const { assert(isValid()); - if (type == EntityType::PLAYER) + if (kind == EntityKind::PLAYER) return PlayerManager::getPlayer(sock); return NPCManager::NPCs[id]; diff --git a/src/Entities.hpp b/src/Entities.hpp index d1e46e5..d6423ee 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -6,7 +6,7 @@ #include #include -enum class EntityType : uint8_t { +enum class EntityKind : uint8_t { INVALID, PLAYER, SIMPLE_NPC, @@ -27,7 +27,7 @@ enum class AIState { class Chunk; struct Entity { - EntityType kind = EntityType::INVALID; + EntityKind kind = EntityKind::INVALID; int x = 0, y = 0, z = 0; uint64_t instanceID = 0; ChunkPos chunkPos = {}; @@ -44,7 +44,7 @@ struct Entity { }; struct EntityRef { - EntityType type; + EntityKind kind; union { CNSocket *sock; int32_t id; @@ -57,10 +57,10 @@ struct EntityRef { Entity *getEntity() const; bool operator==(const EntityRef& other) const { - if (type != other.type) + if (kind != other.kind) return false; - if (type == EntityType::PLAYER) + if (kind == EntityKind::PLAYER) return sock == other.sock; return id == other.id; @@ -68,21 +68,20 @@ struct EntityRef { // arbitrary ordering bool operator<(const EntityRef& other) const { - if (type == other.type) { - if (type == EntityType::PLAYER) + if (kind == other.kind) { + if (kind == EntityKind::PLAYER) return sock < other.sock; else return id < other.id; } - return type < other.type; + return kind < other.kind; } }; /* * Interfaces */ - class ICombatant { public: ICombatant() {} @@ -173,7 +172,7 @@ struct Egg : public BaseNPC { Egg(uint64_t iID, int t, int32_t id, bool summon) : BaseNPC(0, iID, t, id) { summoned = summon; - kind = EntityType::EGG; + kind = EntityKind::EGG; } virtual bool isExtant() override { return !dead; } @@ -185,7 +184,7 @@ struct Egg : public BaseNPC { struct Bus : public BaseNPC { Bus(int angle, uint64_t iID, int t, int id) : BaseNPC(angle, iID, t, id) { - kind = EntityType::BUS; + kind = EntityKind::BUS; loopingPath = true; } diff --git a/src/Missions.cpp b/src/Missions.cpp index 5a33354..890bec5 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -371,7 +371,7 @@ static void taskStart(CNSocket* sock, CNPacketData* data) { for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance Chunk* chunk = Chunking::chunks[chunkPos]; for (EntityRef ref : chunk->entities) { - if (ref.type != EntityType::PLAYER) { + if (ref.kind != EntityKind::PLAYER) { BaseNPC* npc = (BaseNPC*)ref.getEntity(); NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum); if (path != nullptr) { diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 2a7661b..e4be6e5 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -58,13 +58,13 @@ void MobAI::clearDebuff(Mob *mob) { } void MobAI::followToCombat(Mob *mob) { - if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) { + if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityKind::MOB) { Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; for (int i = 0; i < 4; i++) { if (leadMob->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -84,7 +84,7 @@ void MobAI::followToCombat(Mob *mob) { } void MobAI::groupRetreat(Mob *mob) { - if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityType::MOB) + if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityKind::MOB) return; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; @@ -92,7 +92,7 @@ void MobAI::groupRetreat(Mob *mob) { if (leadMob->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -127,7 +127,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { // TODO: support targetting other CombatNPCs - if (ref.type != EntityType::PLAYER) + if (ref.kind != EntityKind::PLAYER) continue; CNSocket *s = ref.sock; @@ -304,7 +304,7 @@ static void useAbilities(Mob *mob, time_t currTime) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { // TODO: see aggroCheck() - if (ref.type != EntityType::PLAYER) + if (ref.kind != EntityKind::PLAYER) continue; CNSocket *s= ref.sock; @@ -452,7 +452,7 @@ void Mob::deadStep(time_t currTime) { // if mob is a group leader/follower, spawn where the group is. if (groupLeader != 0) { - if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityType::MOB) { + if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityKind::MOB) { Mob* leaderMob = (Mob*)NPCManager::NPCs[groupLeader]; x = leaderMob->x + offsetX; y = leaderMob->y + offsetY; @@ -675,7 +675,7 @@ void Mob::roamingStep(time_t currTime) { if (groupMember[i] == 0) break; - if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } @@ -743,7 +743,7 @@ void Mob::onRoamStart() { } void Mob::onCombatStart(EntityRef src) { - assert(src.type == EntityType::PLAYER); + assert(src.kind == EntityKind::PLAYER); target = src.sock; nextMovement = getTime(); nextAttack = 0; @@ -774,7 +774,7 @@ void Mob::onDeath(EntityRef src) { killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? // check for the edge case where hitting the mob did not aggro it - if (src.type == EntityType::PLAYER && src.isValid()) { + if (src.kind == EntityKind::PLAYER && src.isValid()) { Player* plr = PlayerManager::getPlayer(src.sock); Items::DropRoll rolled; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index dbb267b..8b59b2f 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -60,7 +60,7 @@ struct Mob : public CombatNPC { // NOTE: there appear to be discrepancies in the dump hp = maxHealth; - kind = EntityType::MOB; + kind = EntityKind::MOB; } // constructor for /summon diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 990d2d1..6b3b253 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -83,7 +83,7 @@ void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t s for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { - if (ref.type == EntityType::PLAYER) + if (ref.kind == EntityKind::PLAYER) ref.sock->sendPacket(buf, type, size); } } @@ -275,7 +275,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set* chunks, int X, int Y, int Z for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it Chunk* chunk = *c; for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) { - if (ent->type == EntityType::PLAYER) + if (ent->kind == EntityKind::PLAYER) continue; BaseNPC* npcTemp = (BaseNPC*)ent->getEntity(); @@ -351,7 +351,7 @@ void NPCManager::queueNPCRemoval(int32_t id) { static void step(CNServer *serv, time_t currTime) { for (auto& pair : NPCs) { - if (pair.second->kind != EntityType::COMBAT_NPC && pair.second->kind != EntityType::MOB) + if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB) continue; auto npc = (CombatNPC*)pair.second; diff --git a/src/Player.hpp b/src/Player.hpp index b0c970a..c10446a 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -85,7 +85,7 @@ struct Player : public Entity, public ICombatant { time_t lastShot = 0; std::vector buyback = {}; - Player() { kind = EntityType::PLAYER; } + Player() { kind = EntityKind::PLAYER; } virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 98f351b..ec305c0 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -353,7 +353,7 @@ void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, siz for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { - if (ref.type != EntityType::PLAYER || ref.sock == sock) + if (ref.kind != EntityKind::PLAYER || ref.sock == sock) continue; ref.sock->sendPacket(buf, type, size); diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 0ab2049..883cf74 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -43,7 +43,7 @@ namespace PlayerManager { for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { - if (ref.type != EntityType::PLAYER || ref.sock == sock) + if (ref.kind != EntityKind::PLAYER || ref.sock == sock) continue; ref.sock->sendPacket(pkt, type); diff --git a/src/TableData.cpp b/src/TableData.cpp index 6b11c98..3461564 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -1226,7 +1226,7 @@ void TableData::flush() { continue; int x, y, z; - if (npc->kind == EntityType::MOB) { + if (npc->kind == EntityKind::MOB) { Mob *m = (Mob*)npc; x = m->spawnX; y = m->spawnY; @@ -1259,7 +1259,7 @@ void TableData::flush() { int x, y, z; std::vector followers; - if (npc->kind == EntityType::MOB) { + if (npc->kind == EntityKind::MOB) { Mob* m = (Mob*)npc; x = m->spawnX; y = m->spawnY; @@ -1271,7 +1271,7 @@ void TableData::flush() { // add follower data to vector; go until OOB or until follower ID is 0 for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) { - if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityType::MOB) { + if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n"; continue; } diff --git a/src/Transport.cpp b/src/Transport.cpp index 9fe2ccc..0374e00 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -257,13 +257,13 @@ static void stepNPCPathing() { } // skip if not simulating mobs - if (npc->kind == EntityType::MOB && !MobAI::simulateMobs) { + if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) { it++; continue; } // do not roam if not roaming - if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != AIState::ROAMING) { + if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) { it++; continue; } @@ -280,7 +280,7 @@ static void stepNPCPathing() { // TODO: move walking logic into Entity stack switch (npc->kind) { - case EntityType::BUS: + case EntityKind::BUS: INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); busMove.eTT = 3; @@ -293,7 +293,7 @@ static void stepNPCPathing() { NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE)); break; - case EntityType::MOB: + case EntityKind::MOB: MobAI::incNextMovement((Mob*)npc); /* fallthrough */ default: @@ -385,7 +385,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) { void Transport::constructPathNPC(int32_t id, NPCPath* path) { BaseNPC* npc = NPCManager::NPCs[id]; - if (npc->kind == EntityType::MOB) + if (npc->kind == EntityKind::MOB) ((Mob*)(npc))->staticPath = true; npc->loopingPath = path->isLoop; From 4f49bcea8770506b5fc6375c3058d3f75744680d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 13 Apr 2022 18:01:52 -0400 Subject: [PATCH 024/104] (WIP) Move away from rigid states/transitions to allow custom behavior --- src/Combat.cpp | 67 +++------ src/Entities.hpp | 19 +-- src/MobAI.cpp | 360 +++++++++++++++++++++++++---------------------- src/MobAI.hpp | 36 +++-- 4 files changed, 242 insertions(+), 240 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 538b425..a7c4e70 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -101,62 +101,29 @@ int32_t CombatNPC::getID() { } void CombatNPC::step(time_t currTime) { - if (playersInView < 0) - std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; - - // skip movement and combat if disabled or not in view - if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD - && state != AIState::RETREAT) - return; - - switch (state) { - case AIState::INACTIVE: - // no-op - break; - case AIState::ROAMING: - roamingStep(currTime); - break; - case AIState::COMBAT: - combatStep(currTime); - break; - case AIState::RETREAT: - retreatStep(currTime); - break; - case AIState::DEAD: - deadStep(currTime); - break; + + if(stateHandlers.find(state) != stateHandlers.end()) + stateHandlers[state](this, currTime); + else { + std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl; + transition(AIState::INACTIVE, id); } } void CombatNPC::transition(AIState newState, EntityRef src) { + state = newState; - switch (newState) { - case AIState::INACTIVE: - onInactive(); - break; - case AIState::ROAMING: - onRoamStart(); - break; - case AIState::COMBAT: - /* TODO: fire any triggered events - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_COMBAT && event.npcType == type) - event.handler(src, this); - */ - onCombatStart(src); - break; - case AIState::RETREAT: - onRetreat(); - break; - case AIState::DEAD: - /* TODO: fire any triggered events - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == type) - event.handler(src, this); - */ - onDeath(src); - break; + if (transitionHandlers.find(newState) != transitionHandlers.end()) + transitionHandlers[newState](this, src); + else { + std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl; + transition(AIState::INACTIVE, id); } + /* TODO: fire any triggered events + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.trigger == ON_KILLED && event.npcType == type) + event.handler(src, this); + */ } static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, diff --git a/src/Entities.hpp b/src/Entities.hpp index d6423ee..41d9dfd 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -6,7 +6,7 @@ #include #include -enum class EntityKind : uint8_t { +enum EntityKind { INVALID, PLAYER, SIMPLE_NPC, @@ -92,7 +92,6 @@ public: virtual bool isAlive() = 0; virtual int getCurrentHP() = 0; virtual int32_t getID() = 0; - virtual void step(time_t currTime) = 0; }; @@ -133,11 +132,17 @@ struct CombatNPC : public BaseNPC, public ICombatant { AIState state = AIState::INACTIVE; int playersInView = 0; // for optimizing away AI in empty chunks + std::map stateHandlers; + std::map transitionHandlers; + CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { spawnX = x; spawnY = y; spawnZ = z; + + stateHandlers[AIState::INACTIVE] = {}; + transitionHandlers[AIState::INACTIVE] = {}; } virtual bool isExtant() override { return hp > 0; } @@ -147,19 +152,9 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual int32_t getID() override; - virtual void step(time_t currTime) override; - virtual void roamingStep(time_t currTime) {} // no-ops by default - virtual void combatStep(time_t currTime) {} - virtual void retreatStep(time_t currTime) {} - virtual void deadStep(time_t currTime) {} virtual void transition(AIState newState, EntityRef src); - virtual void onInactive() {} // no-ops by default - virtual void onRoamStart() {} - virtual void onCombatStart(EntityRef src) {} - virtual void onRetreat() {} - virtual void onDeath(EntityRef src) {} }; // Mob is in MobAI.hpp, Player is in Player.hpp diff --git a/src/MobAI.cpp b/src/MobAI.cpp index e4be6e5..9453bc7 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -415,48 +415,71 @@ static void drainMobHP(Mob *mob, int amount) { mob->transition(AIState::DEAD, mob->target); } -void Mob::deadStep(time_t currTime) { +void MobAI::incNextMovement(Mob* mob, time_t currTime) { + if (currTime == 0) + currTime = getTime(); + + int delay = (int)mob->data["m_iDelayTime"] * 1000; + mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2); +} + +void Mob::step(time_t currTime) { + if (playersInView < 0) + std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; + + // skip movement and combat if disabled or not in view + if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD + && state != AIState::RETREAT) + return; + + // call superclass step + CombatNPC::step(currTime); +} + +void MobAI::deadStep(CombatNPC* npc, time_t currTime) { + Mob* self = (Mob*)npc; + // despawn the mob after a short delay - if (killedTime != 0 && !despawned && currTime - killedTime > 2000) { - despawned = true; + if (self->killedTime != 0 && !self->despawned && currTime - self->killedTime > 2000) { + self->despawned = true; INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); - pkt.iNPC_ID = id; + pkt.iNPC_ID = self->id; - NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); // if it was summoned, mark it for removal - if (summoned) { + if (self->summoned) { std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; - NPCManager::queueNPCRemoval(id); + NPCManager::queueNPCRemoval(self->id); return; } // pre-set spawn coordinates if not marked for removal - x = spawnX; - y = spawnY; - z = spawnZ; + self->x = self->spawnX; + self->y = self->spawnY; + self->z = self->spawnZ; } // to guide their groupmates, group leaders still need to move despite being dead - if (groupLeader == id) - roamingStep(currTime); + if (self->groupLeader == self->id) + roamingStep(self, currTime); - if (killedTime != 0 && currTime - killedTime < regenTime * 100) + if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100) return; - std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; + std::cout << "respawning mob " << self->id << " with HP = " << self->maxHealth << std::endl; - transition(AIState::ROAMING, id); + self->transition(AIState::ROAMING, self->id); // if mob is a group leader/follower, spawn where the group is. - if (groupLeader != 0) { - if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityKind::MOB) { - Mob* leaderMob = (Mob*)NPCManager::NPCs[groupLeader]; - x = leaderMob->x + offsetX; - y = leaderMob->y + offsetY; - z = leaderMob->z; + if (self->groupLeader != 0) { + if (NPCManager::NPCs.find(self->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[self->groupLeader]->kind == EntityKind::MOB) { + Mob* leaderMob = (Mob*)NPCManager::NPCs[self->groupLeader]; + self->x = leaderMob->x + self->offsetX; + self->y = leaderMob->y + self->offsetY; + self->z = leaderMob->z; } else { std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl; } @@ -464,162 +487,157 @@ void Mob::deadStep(time_t currTime) { INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); - pkt.NPCAppearanceData = getAppearanceData(); + pkt.NPCAppearanceData = self->getAppearanceData(); // notify all nearby players - NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); + NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); } -void Mob::combatStep(time_t currTime) { - assert(target != nullptr); +void MobAI::combatStep(CombatNPC* npc, time_t currTime) { + Mob* self = (Mob*)npc; + assert(self->target != nullptr); // lose aggro if the player lost connection - if (PlayerManager::players.find(target) == PlayerManager::players.end()) { - if (!MobAI::aggroCheck(this, getTime())) - transition(AIState::RETREAT, target); + if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) { + if (!MobAI::aggroCheck(self, getTime())) + self->transition(AIState::RETREAT, self->target); return; } - Player *plr = PlayerManager::getPlayer(target); + Player *plr = PlayerManager::getPlayer(self->target); // lose aggro if the player became invulnerable or died if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { - if (!MobAI::aggroCheck(this, getTime())) - transition(AIState::RETREAT, target); + if (!MobAI::aggroCheck(self, getTime())) + self->transition(AIState::RETREAT, self->target); return; } // drain - if (skillStyle < 0 && (lastDrainTime == 0 || currTime - lastDrainTime >= 1000) - && cbf & CSB_BIT_BOUNDINGBALL) { - drainMobHP(this, maxHealth / 20); // lose 5% every second - lastDrainTime = currTime; + if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000) + && self->cbf & CSB_BIT_BOUNDINGBALL) { + drainMobHP(self, self->maxHealth / 20); // lose 5% every second + self->lastDrainTime = currTime; } // if drain killed the mob, return early - if (hp <= 0) + if (self->hp <= 0) return; // unbuffing - std::unordered_map::iterator it = unbuffTimes.begin(); - while (it != unbuffTimes.end()) { + std::unordered_map::iterator it = self->unbuffTimes.begin(); + while (it != self->unbuffTimes.end()) { if (currTime >= it->second) { - cbf &= ~it->first; + self->cbf &= ~it->first; INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; - pkt1.iID = id; - pkt1.iConditionBitFlag = cbf; - NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + pkt1.iID = self->id; + pkt1.iConditionBitFlag = self->cbf; + NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - it = unbuffTimes.erase(it); + it = self->unbuffTimes.erase(it); } else { it++; } } // skip attack if stunned or asleep - if (cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { - skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. + if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { + self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. return; } - int distance = hypot(plr->x - x, plr->y - y); - int mobRange = (int)data["m_iAtkRange"] + (int)data["m_iRadius"]; + int distance = hypot(plr->x - self->x, plr->y - self->y); + int mobRange = (int)self->data["m_iAtkRange"] + (int)self->data["m_iRadius"]; - if (currTime >= nextAttack) { - if (skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. - useAbilities(this, currTime); - if (target == nullptr) + if (currTime >= self->nextAttack) { + if (self->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. + useAbilities(self, currTime); + if (self->target == nullptr) return; } int distanceToTravel = INT_MAX; // movement logic: move when out of range but don't move while casting a skill - if (distance > mobRange && skillStyle == -1) { - if (nextMovement != 0 && currTime < nextMovement) + if (distance > mobRange && self->skillStyle == -1) { + if (self->nextMovement != 0 && currTime < self->nextMovement) return; - nextMovement = currTime + 400; - if (currTime >= nextAttack) - nextAttack = 0; + self->nextMovement = currTime + 400; + if (currTime >= self->nextAttack) + self->nextAttack = 0; // halve movement speed if snared - if (cbf & CSB_BIT_DN_MOVE_SPEED) - speed /= 2; + if (self->cbf & CSB_BIT_DN_MOVE_SPEED) + self->speed /= 2; int targetX = plr->x; int targetY = plr->y; - if (groupLeader != 0) { - targetX += offsetX*distance/(idleRange + 1); - targetY += offsetY*distance/(idleRange + 1); + if (self->groupLeader != 0) { + targetX += self->offsetX*distance/(self->idleRange + 1); + targetY += self->offsetY*distance/(self->idleRange + 1); } - distanceToTravel = std::min(distance-mobRange+1, speed*2/5); - auto targ = lerp(x, y, targetX, targetY, distanceToTravel); - if (distanceToTravel < speed*2/5 && currTime >= nextAttack) - nextAttack = 0; + distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5); + auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel); + if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack) + self->nextAttack = 0; - NPCManager::updateNPCPosition(id, targ.first, targ.second, z, instanceID, angle); + NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - pkt.iNPC_ID = id; - pkt.iSpeed = speed; - pkt.iToX = x = targ.first; - pkt.iToY = y = targ.second; + pkt.iNPC_ID = self->id; + pkt.iSpeed = self->speed; + pkt.iToX = self->x = targ.first; + pkt.iToY = self->y = targ.second; pkt.iToZ = plr->z; pkt.iMoveStyle = 1; // notify all nearby players - NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); + NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } /* attack logic * 2/5 represents 400 ms which is the time interval mobs use per movement logic step * if the mob is one move interval away, we should just start attacking anyways. */ - if (distance <= mobRange || distanceToTravel < speed*2/5) { - if (nextAttack == 0 || currTime >= nextAttack) { - nextAttack = currTime + (int)data["m_iDelayTime"] * 100; - Combat::npcAttackPc(this, currTime); + if (distance <= mobRange || distanceToTravel < self->speed*2/5) { + if (self->nextAttack == 0 || currTime >= self->nextAttack) { + self->nextAttack = currTime + (int)self->data["m_iDelayTime"] * 100; + Combat::npcAttackPc(self, currTime); } } // retreat if the player leaves combat range - int xyDistance = hypot(plr->x - roamX, plr->y - roamY); - distance = hypot(xyDistance, plr->z - roamZ); - if (distance >= data["m_iCombatRange"]) { - transition(AIState::RETREAT, target); + int xyDistance = hypot(plr->x - self->roamX, plr->y - self->roamY); + distance = hypot(xyDistance, plr->z - self->roamZ); + if (distance >= self->data["m_iCombatRange"]) { + self->transition(AIState::RETREAT, self->target); } } -void MobAI::incNextMovement(Mob *mob, time_t currTime) { - if (currTime == 0) - currTime = getTime(); +void MobAI::roamingStep(CombatNPC* npc, time_t currTime) { + Mob* self = (Mob*)npc; - int delay = (int)mob->data["m_iDelayTime"] * 1000; - mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2); -} - -void Mob::roamingStep(time_t currTime) { /* * We reuse nextAttack to avoid scanning for players all the time, but to still * do so more often than if we waited for nextMovement (which is way too slow). * In the case of group leaders, this step will be called by dead mobs, so disable attack. */ - if (state != AIState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) { - nextAttack = currTime + 500; - if (aggroCheck(this, currTime)) + if (self->state != AIState::DEAD && (self->nextAttack == 0 || currTime >= self->nextAttack)) { + self->nextAttack = currTime + 500; + if (aggroCheck(self, currTime)) return; } // no random roaming if the mob already has a set path - if (staticPath) + if (self->staticPath) return; - if (groupLeader != 0 && groupLeader != id) // don't roam by yourself without group leader + if (self->groupLeader != 0 && self->groupLeader != self->id) // don't roam by yourself without group leader return; /* @@ -627,151 +645,157 @@ void Mob::roamingStep(time_t currTime) { * Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement), * so we don't have to check if there's already entries in the queue since we know there won't be. */ - if (nextMovement != 0 && currTime < nextMovement) + if (self->nextMovement != 0 && currTime < self->nextMovement) return; - incNextMovement(this, currTime); + incNextMovement(self, currTime); - int xStart = spawnX - idleRange/2; - int yStart = spawnY - idleRange/2; + int xStart = self->spawnX - self->idleRange/2; + int yStart = self->spawnY - self->idleRange/2; // some mobs don't move (and we mustn't divide/modulus by zero) - if (idleRange == 0 || speed == 0) + if (self->idleRange == 0 || self->speed == 0) return; int farX, farY, distance; - int minDistance = idleRange / 2; + int minDistance = self->idleRange / 2; // pick a random destination - farX = xStart + Rand::rand(idleRange); - farY = yStart + Rand::rand(idleRange); + farX = xStart + Rand::rand(self->idleRange); + farY = yStart + Rand::rand(self->idleRange); - distance = std::abs(std::max(farX - x, farY - y)); + distance = std::abs(std::max(farX - self->x, farY - self->y)); if (distance == 0) distance += 1; // hack to avoid FPE // if it's too short a walk, go further in that direction - farX = x + (farX - x) * minDistance / distance; - farY = y + (farY - y) * minDistance / distance; + farX = self->x + (farX - self->x) * minDistance / distance; + farY = self->y + (farY - self->y) * minDistance / distance; // but don't got out of bounds - farX = std::clamp(farX, xStart, xStart + idleRange); - farY = std::clamp(farY, yStart, yStart + idleRange); + farX = std::clamp(farX, xStart, xStart + self->idleRange); + farY = std::clamp(farY, yStart, yStart + self->idleRange); // halve movement speed if snared - if (cbf & CSB_BIT_DN_MOVE_SPEED) - speed /= 2; + if (self->cbf & CSB_BIT_DN_MOVE_SPEED) + self->speed /= 2; std::queue queue; - Vec3 from = { x, y, z }; - Vec3 to = { farX, farY, z }; + Vec3 from = { self->x, self->y, self->z }; + Vec3 to = { farX, farY, self->z }; // add a route to the queue; to be processed in Transport::stepNPCPathing() - Transport::lerp(&queue, from, to, speed); - Transport::NPCQueues[id] = queue; + Transport::lerp(&queue, from, to, self->speed); + Transport::NPCQueues[self->id] = queue; - if (groupLeader != 0 && groupLeader == id) { + if (self->groupLeader != 0 && self->groupLeader == self->id) { // make followers follow this npc. for (int i = 0; i < 4; i++) { - if (groupMember[i] == 0) + if (self->groupMember[i] == 0) break; - if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityKind::MOB) { + if (NPCManager::NPCs.find(self->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[self->groupMember[i]]->kind != EntityKind::MOB) { std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; continue; } std::queue queue2; - Mob* followerMob = (Mob*)NPCManager::NPCs[groupMember[i]]; + Mob* followerMob = (Mob*)NPCManager::NPCs[self->groupMember[i]]; from = { followerMob->x, followerMob->y, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; - Transport::lerp(&queue2, from, to, speed); + Transport::lerp(&queue2, from, to, self->speed); Transport::NPCQueues[followerMob->id] = queue2; } } } -void Mob::retreatStep(time_t currTime) { - if (nextMovement != 0 && currTime < nextMovement) +void MobAI::retreatStep(CombatNPC* npc, time_t currTime) { + Mob* self = (Mob*)npc; + + if (self->nextMovement != 0 && currTime < self->nextMovement) return; - nextMovement = currTime + 400; + self->nextMovement = currTime + 400; // distance between spawn point and current location - int distance = hypot(x - roamX, y - roamY); + int distance = hypot(self->x - self->roamX, self->y - self->roamY); //if (distance > mob->data["m_iIdleRange"]) { if (distance > 10) { INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - auto targ = lerp(x, y, roamX, roamY, (int)speed*4/5); + auto targ = lerp(self->x, self->y, self->roamX, self->roamY, (int)self->speed*4/5); - pkt.iNPC_ID = id; - pkt.iSpeed = (int)speed * 2; - pkt.iToX = x = targ.first; - pkt.iToY = y = targ.second; - pkt.iToZ = z = spawnZ; + pkt.iNPC_ID = self->id; + pkt.iSpeed = (int)self->speed * 2; + pkt.iToX = self->x = targ.first; + pkt.iToY = self->y = targ.second; + pkt.iToZ = self->z = self->spawnZ; pkt.iMoveStyle = 1; // notify all nearby players - NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); + NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } // if we got there //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point - transition(AIState::ROAMING, id); + self->transition(AIState::ROAMING, self->id); } } -void Mob::onInactive() { - // no-op -} +void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) { + Mob* self = (Mob*)npc; -void Mob::onRoamStart() { - hp = maxHealth; - killedTime = 0; - nextAttack = 0; - cbf = 0; + self->hp = self->maxHealth; + self->killedTime = 0; + self->nextAttack = 0; + self->cbf = 0; // cast a return home heal spell, this is the right way(tm) std::vector targetData = { 1, 0, 0, 0, 0 }; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); + pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); // clear outlying debuffs - clearDebuff(this); + clearDebuff(self); } -void Mob::onCombatStart(EntityRef src) { +void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) { + Mob* self = (Mob*)npc; + assert(src.kind == EntityKind::PLAYER); - target = src.sock; - nextMovement = getTime(); - nextAttack = 0; + self->target = src.sock; + self->nextMovement = getTime(); + self->nextAttack = 0; - roamX = x; - roamY = y; - roamZ = z; + self->roamX = self->x; + self->roamY = self->y; + self->roamZ = self->z; - int skillID = (int)data["m_iPassiveBuff"]; // cast passive - std::vector targetData = { 1, id, 0, 0, 0 }; + int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive + std::vector targetData = { 1, self->id, 0, 0, 0 }; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); } -void Mob::onRetreat() { - target = nullptr; - MobAI::clearDebuff(this); - if (groupLeader != 0) - MobAI::groupRetreat(this); +void MobAI::onRetreat(CombatNPC* npc, EntityRef src) { + Mob* self = (Mob*)npc; + + self->target = nullptr; + MobAI::clearDebuff(self); + if (self->groupLeader != 0) + MobAI::groupRetreat(self); } -void Mob::onDeath(EntityRef src) { - target = nullptr; - cbf = 0; - skillStyle = -1; - unbuffTimes.clear(); - killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? +void MobAI::onDeath(CombatNPC* npc, EntityRef src) { + Mob* self = (Mob*)npc; + + self->target = nullptr; + self->cbf = 0; + self->skillStyle = -1; + self->unbuffTimes.clear(); + self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? // check for the edge case where hitting the mob did not aggro it if (src.kind == EntityKind::PLAYER && src.isValid()) { @@ -787,8 +811,8 @@ void Mob::onDeath(EntityRef src) { Combat::genQItemRolls(leader, qitemRolls); if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { - Items::giveMobDrop(src.sock, this, rolled, eventRolled); - Missions::mobKilled(src.sock, type, qitemRolls); + Items::giveMobDrop(src.sock, self, rolled, eventRolled); + Missions::mobKilled(src.sock, self->type, qitemRolls); } else { for (int i = 0; i < leader->groupCnt; i++) { @@ -803,21 +827,21 @@ void Mob::onDeath(EntityRef src) { if (dist > 5000) continue; - Items::giveMobDrop(sockTo, this, rolled, eventRolled); - Missions::mobKilled(sockTo, type, qitemRolls); + Items::giveMobDrop(sockTo, self, rolled, eventRolled); + Missions::mobKilled(sockTo, self->type, qitemRolls); } } } // delay the despawn animation - despawned = false; + self->despawned = false; - auto it = Transport::NPCQueues.find(id); + auto it = Transport::NPCQueues.find(self->id); if (it == Transport::NPCQueues.end() || it->second.empty()) return; // rewind or empty the movement queue - if (staticPath) { + if (self->staticPath) { /* * This is inelegant, but we wind forward in the path until we find the point that * corresponds with the Mob's spawn point. @@ -825,12 +849,12 @@ void Mob::onDeath(EntityRef src) { * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever. */ auto& queue = it->second; - for (auto point = queue.front(); point.x != spawnX || point.y != spawnY; point = queue.front()) { + for (auto point = queue.front(); point.x != self->spawnX || point.y != self->spawnY; point = queue.front()) { queue.pop(); queue.push(point); } } else { - Transport::NPCQueues.erase(id); + Transport::NPCQueues.erase(self->id); } } diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 8b59b2f..45a768a 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -4,6 +4,20 @@ #include "NPCManager.hpp" #include "Entities.hpp" +/* kill me */ +struct Mob; +namespace MobAI { + void deadStep(CombatNPC* self, time_t currTime); + void combatStep(CombatNPC* self, time_t currTime); + void roamingStep(CombatNPC* self, time_t currTime); + void retreatStep(CombatNPC* self, time_t currTime); + + void onRoamStart(CombatNPC* self, EntityRef src); + void onCombatStart(CombatNPC* self, EntityRef src); + void onRetreat(CombatNPC* self, EntityRef src); + void onDeath(CombatNPC* self, EntityRef src); +} + struct Mob : public CombatNPC { // general std::unordered_map unbuffTimes = {}; @@ -61,6 +75,17 @@ struct Mob : public CombatNPC { hp = maxHealth; kind = EntityKind::MOB; + + // AI + stateHandlers[AIState::DEAD] = MobAI::deadStep; + stateHandlers[AIState::COMBAT] = MobAI::combatStep; + stateHandlers[AIState::ROAMING] = MobAI::roamingStep; + stateHandlers[AIState::RETREAT] = MobAI::retreatStep; + + transitionHandlers[AIState::DEAD] = MobAI::onDeath; + transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart; + transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart; + transitionHandlers[AIState::RETREAT] = MobAI::onRetreat; } // constructor for /summon @@ -71,16 +96,7 @@ struct Mob : public CombatNPC { ~Mob() {} - virtual void roamingStep(time_t currTime) override; - virtual void combatStep(time_t currTime) override; - virtual void retreatStep(time_t currTime) override; - virtual void deadStep(time_t currTime) override; - - virtual void onInactive() override; - virtual void onRoamStart() override; - virtual void onCombatStart(EntityRef src) override; - virtual void onRetreat() override; - virtual void onDeath(EntityRef src) override; + virtual void step(time_t currTime) override; auto operator[](std::string s) { return data[s]; From 828f49cd628c43b3c4e8f31ba3b1ed2659d30a1a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Apr 2022 20:01:58 -0400 Subject: [PATCH 025/104] Move mob aggro logic into `takeDamage` override God that feels good --- src/Combat.cpp | 35 +----------------------------- src/MobAI.cpp | 59 +++++++++++++++++++++++++++++++++++++++----------- src/MobAI.hpp | 1 + 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index a7c4e70..0082363 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -44,41 +44,8 @@ void Player::step(time_t currTime) { int CombatNPC::takeDamage(EntityRef src, int amt) { - /* REFACTOR: all of this logic is strongly coupled to mobs. - * come back to this when more of it is moved to CombatNPC. - * remove this cast when done */ - Mob* mob = (Mob*)this; - - // cannot kill mobs multiple times; cannot harm retreating mobs - if (mob->state != AIState::ROAMING && mob->state != AIState::COMBAT) { - return 0; // no damage - } - - if (mob->skillStyle >= 0) - return 0; // don't hurt a mob casting corruption - - if (mob->state == AIState::ROAMING) { - assert(mob->target == nullptr && src.kind == EntityKind::PLAYER); // players only for now - mob->transition(AIState::COMBAT, src); - - if (mob->groupLeader != 0) - MobAI::followToCombat(mob); - } - hp -= amt; - - // wake up sleeping monster - if (mob->cbf & CSB_BIT_MEZ) { - mob->cbf &= ~CSB_BIT_MEZ; - - INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); - pkt1.eCT = 2; - pkt1.iID = mob->id; - pkt1.iConditionBitFlag = mob->cbf; - NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - } - - if (mob->hp <= 0) + if (hp <= 0) transition(AIState::DEAD, src); return amt; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 9453bc7..e1d4308 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -16,6 +16,52 @@ using namespace MobAI; bool MobAI::simulateMobs = settings::SIMULATEMOBS; +void Mob::step(time_t currTime) { + if (playersInView < 0) + std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; + + // skip movement and combat if disabled or not in view + if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD + && state != AIState::RETREAT) + return; + + // call superclass step + CombatNPC::step(currTime); +} + +int Mob::takeDamage(EntityRef src, int amt) { + + // cannot kill mobs multiple times; cannot harm retreating mobs + if (state != AIState::ROAMING && state != AIState::COMBAT) { + return 0; // no damage + } + + if (skillStyle >= 0) + return 0; // don't hurt a mob casting corruption + + if (state == AIState::ROAMING) { + assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now + transition(AIState::COMBAT, src); + + if (groupLeader != 0) + MobAI::followToCombat(this); + } + + // wake up sleeping monster + if (cbf & CSB_BIT_MEZ) { + cbf &= ~CSB_BIT_MEZ; + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); + pkt1.eCT = 2; + pkt1.iID = id; + pkt1.iConditionBitFlag = cbf; + NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + } + + // call superclass takeDamage + return CombatNPC::takeDamage(src, amt); +} + /* * Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and * only returns the first step, since the rest will need to be recalculated anyway if chasing player. @@ -423,19 +469,6 @@ void MobAI::incNextMovement(Mob* mob, time_t currTime) { mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2); } -void Mob::step(time_t currTime) { - if (playersInView < 0) - std::cout << "[WARN] Weird playerview value " << playersInView << std::endl; - - // skip movement and combat if disabled or not in view - if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD - && state != AIState::RETREAT) - return; - - // call superclass step - CombatNPC::step(currTime); -} - void MobAI::deadStep(CombatNPC* npc, time_t currTime) { Mob* self = (Mob*)npc; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 45a768a..cb6bfc8 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -96,6 +96,7 @@ struct Mob : public CombatNPC { ~Mob() {} + virtual int takeDamage(EntityRef src, int amt) override; virtual void step(time_t currTime) override; auto operator[](std::string s) { From 04221f1c5fcadce9b0ea504048507b85890641ee Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Apr 2022 21:51:55 -0400 Subject: [PATCH 026/104] (WIP) TODO ABILITIES --- src/Abilities.cpp | 766 ------------------------------------------ src/Abilities.hpp | 29 -- src/Eggs.cpp | 36 +- src/Groups.cpp | 13 +- src/MobAI.cpp | 35 +- src/Nanos.cpp | 29 +- src/PlayerManager.cpp | 3 +- 7 files changed, 64 insertions(+), 847 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index e07f913..8c7f84d 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -6,774 +6,8 @@ #include "Groups.hpp" #include "Eggs.hpp" -/* - * TODO: This file is in desperate need of deduplication and rewriting. - */ - std::map Abilities::SkillTable; -/* - * targetData approach - * first integer is the count - * second to fifth integers are IDs, these can be either player iID or mob's iID - */ -std::vector Abilities::findTargets(Player* plr, int skillID, CNPacketData* data) { - std::vector tD(5); - - if (SkillTable[skillID].targetType <= 2 && data != nullptr) { // client gives us the targets - sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf; - - // validate request check - if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) { - std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl; - return tD; - } - - int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE)); - tD[0] = pkt->iTargetCnt; - - for (int i = 0; i < pkt->iTargetCnt; i++) - tD[i+1] = pktdata[i]; - - } else if (SkillTable[skillID].targetType == 2) { // self target only - tD[0] = 1; - tD[1] = plr->iID; - - } else if (SkillTable[skillID].targetType == 3) { // entire group as target - Player *otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - - if (otherPlr == nullptr) - return tD; - - if (SkillTable[skillID].effectArea == 0) { // for buffs - tD[0] = otherPlr->groupCnt; - for (int i = 0; i < otherPlr->groupCnt; i++) - tD[i+1] = otherPlr->groupIDs[i]; - return tD; - } - - for (int i = 0; i < otherPlr->groupCnt; i++) { // group heals have an area limit - Player *otherPlr2 = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]); - if (otherPlr2 == nullptr) - continue; - if (true) {//hypot(otherPlr2->x - plr->x, otherPlr2->y - plr->y) < SkillTable[skillID].effectArea) { - tD[i+1] = otherPlr->groupIDs[i]; - tD[0] += 1; - } - } - } - - return tD; -} - -void Abilities::removeBuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower) { - Player *plr = PlayerManager::getPlayer(sock); - - plr->iSelfConditionBitFlag &= ~bitFlag; - int groupFlags = 0; - - if (groupPower) { - plr->iGroupConditionBitFlag &= ~bitFlag; - Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); - if (leader != nullptr) - groupFlags = Groups::getGroupFlags(leader); - } - - for (int i = 0; i < targetData[0]; i++) { - Player* varPlr = PlayerManager::getPlayerFromID(targetData[i+1]); - if (!((groupFlags | varPlr->iSelfConditionBitFlag) & bitFlag)) { - CNSocket* sockTo = PlayerManager::getSockFromID(targetData[i+1]); - if (sockTo == nullptr) - continue; // sanity check - - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp); - resp.eCSTB = timeBuffID; // eCharStatusTimeBuffID - resp.eTBU = 2; // eTimeBuffUpdate - resp.eTBT = 1; // eTimeBuffType 1 means nano - varPlr->iConditionBitFlag &= ~bitFlag; - resp.iConditionBitFlag = varPlr->iConditionBitFlag |= groupFlags | varPlr->iSelfConditionBitFlag; - - if (amount > 0) - resp.TimeBuff.iValue = amount; - - sockTo->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); - } - } -} - -int Abilities::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags) { - if (SkillTable[skillID].drainType == 1) - return 0; - - int32_t bitFlag = 0; - - for (auto& pwr : Powers) { - if (pwr.skillType == SkillTable[skillID].skillType) { - bitFlag = pwr.bitFlag; - Player *plr = PlayerManager::getPlayer(sock); - if (eTBU == 1 || !((groupFlags | plr->iSelfConditionBitFlag) & bitFlag)) { - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp); - resp.eCSTB = pwr.timeBuffID; - resp.eTBU = eTBU; - resp.eTBT = eTBT; - - if (eTBU == 1) - plr->iConditionBitFlag |= bitFlag; - else - plr->iConditionBitFlag &= ~bitFlag; - - resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag; - resp.TimeBuff.iValue = SkillTable[skillID].powerIntensity[0]; - sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); - } - return bitFlag; - } - } - - return 0; -} - namespace Abilities { -bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { - std::cout << "[WARN] doDebuff: NPC ID not found" << std::endl; - return false; - } - - BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityKind::MOB) { - std::cout << "[WARN] doDebuff: NPC is not a mob" << std::endl; - return false; - } - - Mob* mob = (Mob*)npc; - mob->takeDamage(sock, 0); - - respdata[i].eCT = 4; - respdata[i].iID = mob->id; - respdata[i].bProtected = 1; - if (mob->skillStyle < 0 && mob->state != AIState::RETREAT - && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom - mob->cbf |= bitFlag; - mob->unbuffTimes[bitFlag] = getTime() + duration * 100; - respdata[i].bProtected = 0; - } - respdata[i].iConditionBitFlag = mob->cbf; - - return true; -} - -bool doBuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; - CNSocket *sockTo = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - sockTo = pair.first; - plr = pair.second; - break; - } - } - - // player not found - if (sockTo == nullptr || plr == nullptr) { - std::cout << "[WARN] doBuff: player ID not found" << std::endl; - return false; - } - - respdata[i].eCT = 1; - respdata[i].iID = plr->iID; - respdata[i].iConditionBitFlag = 0; - - // only apply buffs if the player is actually alive - if (plr->HP > 0) { - respdata[i].iConditionBitFlag = bitFlag; - - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = timeBuffID; // eCharStatusTimeBuffID - pkt.eTBU = 1; // eTimeBuffUpdate - pkt.eTBT = 1; // eTimeBuffType 1 means nano - pkt.iConditionBitFlag = plr->iConditionBitFlag |= bitFlag; - - if (amount > 0) - pkt.TimeBuff.iValue = amount; - - sockTo->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); - } - - return true; -} - -bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { - // not sure how to best handle this - std::cout << "[WARN] doDamageNDebuff: NPC ID not found" << std::endl; - return false; - } - - BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityKind::MOB) { - std::cout << "[WARN] doDamageNDebuff: NPC is not a mob" << std::endl; - return false; - } - - Mob* mob = (Mob*)npc; - - mob->takeDamage(sock, 0); - - respdata[i].eCT = 4; - respdata[i].iDamage = duration / 10; - respdata[i].iID = mob->id; - respdata[i].iHP = mob->hp; - respdata[i].bProtected = 1; - if (mob->skillStyle < 0 && mob->state != AIState::RETREAT - && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom - mob->cbf |= bitFlag; - mob->unbuffTimes[bitFlag] = getTime() + duration * 100; - respdata[i].bProtected = 0; - } - respdata[i].iConditionBitFlag = mob->cbf; - - return true; -} - -bool doHeal(CNSocket *sock, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doHeal: player ID not found" << std::endl; - return false; - } - - int healedAmount = PC_MAXHEALTH(plr->level) * amount / 1000; - - // do not heal dead players - if (plr->HP <= 0) - healedAmount = 0; - - plr->HP += healedAmount; - - if (plr->HP > PC_MAXHEALTH(plr->level)) - plr->HP = PC_MAXHEALTH(plr->level); - - respdata[i].eCT = 1; - respdata[i].iID = plr->iID; - respdata[i].iHP = plr->HP; - respdata[i].iHealHP = healedAmount; - - return true; -} - -bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { - // not sure how to best handle this - std::cout << "[WARN] doDamage: NPC ID not found" << std::endl; - return false; - } - - BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityKind::MOB) { - std::cout << "[WARN] doDamage: NPC is not a mob" << std::endl; - return false; - } - - Mob* mob = (Mob*)npc; - Player *plr = PlayerManager::getPlayer(sock); - - int damage = mob->takeDamage(sock, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000)); - - respdata[i].eCT = 4; - respdata[i].iDamage = damage; - respdata[i].iID = mob->id; - respdata[i].iHP = mob->hp; - - return true; -} - -/* - * NOTE: Leech is specially encoded. - * - * It manages to fit inside the nanoPower<>() mold with only a slight hack, - * but it really is it's own thing. There is a hard assumption that players - * will only every leech a single mob, and the sanity check that enforces that - * assumption is critical. - */ - -bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - // this sanity check is VERY important - if (i != 0) { - std::cout << "[WARN] Player attempted to leech more than one mob!" << std::endl; - return false; - } - - sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP)); - Player *plr = PlayerManager::getPlayer(sock); - - int healedAmount = amount; - - plr->HP += healedAmount; - - if (plr->HP > PC_MAXHEALTH(plr->level)) - plr->HP = PC_MAXHEALTH(plr->level); - - healdata->eCT = 1; - healdata->iID = plr->iID; - healdata->iHP = plr->HP; - healdata->iHealHP = healedAmount; - - if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { - // not sure how to best handle this - std::cout << "[WARN] doLeech: NPC ID not found" << std::endl; - return false; - } - - BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityKind::MOB) { - std::cout << "[WARN] doLeech: NPC is not a mob" << std::endl; - return false; - } - - Mob* mob = (Mob*)npc; - - int damage = mob->takeDamage(sock, amount * 2); - - damagedata->eCT = 4; - damagedata->iDamage = damage; - damagedata->iID = mob->id; - damagedata->iHP = mob->hp; - - return true; -} - -bool doResurrect(CNSocket *sock, sSkillResult_Resurrect *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doResurrect: player ID not found" << std::endl; - return false; - } - - respdata[i].eCT = 1; - respdata[i].iID = plr->iID; - respdata[i].iRegenHP = plr->HP; - - return true; -} - -bool doMove(CNSocket *sock, sSkillResult_Move *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player *plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doMove: player ID not found" << std::endl; - return false; - } - - Player *plr2 = PlayerManager::getPlayer(sock); - - respdata[i].eCT = 1; - respdata[i].iID = plr->iID; - respdata[i].iMapNum = plr2->recallInstance; - respdata[i].iMoveX = plr2->recallX; - respdata[i].iMoveY = plr2->recallY; - respdata[i].iMoveZ = plr2->recallZ; - - return true; -} - -bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - CNSocket* sock = nullptr; - Player* plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - sock = pair.first; - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doDamageNDebuff: player ID not found" << std::endl; - return false; - } - - respdata[i].eCT = 1; - respdata[i].iDamage = duration / 10; - respdata[i].iID = plr->iID; - respdata[i].iHP = plr->HP; - respdata[i].iStamina = plr->Nanos[plr->activeNano].iStamina; - if (plr->iConditionBitFlag & CSB_BIT_FREEDOM) - respdata[i].bProtected = 1; - else { - if (!(plr->iConditionBitFlag & bitFlag)) { - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = timeBuffID; // eCharStatusTimeBuffID - pkt.eTBU = 1; // eTimeBuffUpdate - pkt.eTBT = 2; - pkt.iConditionBitFlag = plr->iConditionBitFlag |= bitFlag; - pkt.TimeBuff.iValue = amount * 5; - sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); - } - - respdata[i].bProtected = 0; - std::pair key = std::make_pair(sock, bitFlag); - time_t until = getTime() + (time_t)duration * 100; - Eggs::EggBuffs[key] = until; - } - respdata[i].iConditionBitFlag = plr->iConditionBitFlag; - - if (plr->HP <= 0) { - if (!MobAI::aggroCheck(mob, getTime())) - mob->transition(AIState::RETREAT, mob->target); - } - - return true; -} - -bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) { - std::cout << "[WARN] doHeal: NPC ID not found" << std::endl; - return false; - } - - BaseNPC* npc = NPCManager::NPCs[targetID]; - if (npc->kind != EntityKind::MOB) { - std::cout << "[WARN] doHeal: NPC is not a mob" << std::endl; - return false; - } - - Mob* targetMob = (Mob*)npc; - - int healedAmount = amount * targetMob->maxHealth / 1000; - targetMob->hp += healedAmount; - if (targetMob->hp > targetMob->maxHealth) - targetMob->hp = targetMob->maxHealth; - - respdata[i].eCT = 4; - respdata[i].iID = targetMob->id; - respdata[i].iHP = targetMob->hp; - respdata[i].iHealHP = healedAmount; - - return true; -} - -bool doReturnHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - int healedAmount = amount * mob->maxHealth / 1000; - mob->hp += healedAmount; - if (mob->hp > mob->maxHealth) - mob->hp = mob->maxHealth; - - respdata[i].eCT = 4; - respdata[i].iID = mob->id; - respdata[i].iHP = mob->hp; - respdata[i].iHealHP = healedAmount; - - return true; -} - -bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player* plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doDamage: player ID not found" << std::endl; - return false; - } - - int damage = amount * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; - - if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE) - damage = 0; - - respdata[i].eCT = 1; - respdata[i].iDamage = damage; - respdata[i].iID = plr->iID; - respdata[i].iHP = plr->HP -= damage; - - if (plr->HP <= 0) { - if (!MobAI::aggroCheck(mob, getTime())) - mob->transition(AIState::RETREAT, mob->target); - } - - return true; -} - -bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - // this sanity check is VERY important - if (i != 0) { - std::cout << "[WARN] Mob attempted to leech more than one player!" << std::endl; - return false; - } - - Player* plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doLeech: player ID not found" << std::endl; - return false; - } - - sSkillResult_Damage* damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP)); - - int healedAmount = amount * PC_MAXHEALTH(plr->level) / 1000; - - mob->hp += healedAmount; - if (mob->hp > mob->maxHealth) - mob->hp = mob->maxHealth; - - healdata->eCT = 4; - healdata->iID = mob->id; - healdata->iHP = mob->hp; - healdata->iHealHP = healedAmount; - - int damage = healedAmount; - - if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE) - damage = 0; - - damagedata->eCT = 1; - damagedata->iDamage = damage; - damagedata->iID = plr->iID; - damagedata->iHP = plr->HP -= damage; - - if (plr->HP <= 0) { - if (!MobAI::aggroCheck(mob, getTime())) - mob->transition(AIState::RETREAT, mob->target); - } - - return true; -} - -bool doBatteryDrain(Mob* mob, sSkillResult_BatteryDrain* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - Player* plr = nullptr; - - for (auto& pair : PlayerManager::players) { - if (pair.second->iID == targetID) { - plr = pair.second; - break; - } - } - - // player not found - if (plr == nullptr) { - std::cout << "[WARN] doBatteryDrain: player ID not found" << std::endl; - return false; - } - - respdata[i].eCT = 1; - respdata[i].iID = plr->iID; - - if (plr->iConditionBitFlag & CSB_BIT_PROTECT_BATTERY) { - respdata[i].bProtected = 1; - respdata[i].iDrainW = 0; - respdata[i].iDrainN = 0; - } - else { - respdata[i].bProtected = 0; - respdata[i].iDrainW = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36; - respdata[i].iDrainN = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36; - } - - respdata[i].iBatteryW = plr->batteryW -= (respdata[i].iDrainW < plr->batteryW) ? respdata[i].iDrainW : plr->batteryW; - respdata[i].iBatteryN = plr->batteryN -= (respdata[i].iDrainN < plr->batteryN) ? respdata[i].iDrainN : plr->batteryN; - respdata[i].iStamina = plr->Nanos[plr->activeNano].iStamina; - respdata[i].iConditionBitFlag = plr->iConditionBitFlag; - - return true; -} - -bool doBuff(Mob* mob, sSkillResult_Buff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) { - respdata[i].eCT = 4; - respdata[i].iID = mob->id; - mob->cbf |= bitFlag; - respdata[i].iConditionBitFlag = mob->cbf; - - return true; -} - -template - void power(EntityRef ref, std::vector targetData, - int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount, - int16_t skillType, int32_t bitFlag, int16_t timeBuffID) { - - /*** OLD NANO HANDLER *** - Player *plr = PlayerManager::getPlayer(sock); - - if (skillType == EST_RETROROCKET_SELF || skillType == EST_RECALL) // rocket and self recall does not need any trailing structs - targetData[0] = 0; - - size_t resplen; - // special case since leech is atypically encoded - if (skillType == EST_BLOODSUCKING) - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); - else - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + targetData[0] * sizeof(sPAYLOAD); - - // validate response packet - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), targetData[0], sizeof(sPAYLOAD))) { - std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl; - return; - } - - uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - memset(respbuf, 0, resplen); - - sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; - sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); - - resp->iPC_ID = plr->iID; - resp->iSkillID = skillID; - resp->iNanoID = nanoID; - resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina; - resp->eST = skillType; - resp->iTargetCnt = targetData[0]; - - if (SkillTable[skillID].drainType == 2) { - if (SkillTable[skillID].targetType >= 2) - plr->iSelfConditionBitFlag |= bitFlag; - if (SkillTable[skillID].targetType == 3) - plr->iGroupConditionBitFlag |= bitFlag; - } - - for (int i = 0; i < targetData[0]; i++) - if (!work(sock, respdata, i, targetData[i+1], bitFlag, timeBuffID, duration, amount)) - return; - - sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); - assert(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) == sizeof(sP_FE2CL_NANO_SKILL_USE)); - if (skillType == EST_RECALL_GROUP) { // in the case of group recall, nobody but group members need the packet - for (int i = 0; i < targetData[0]; i++) { - CNSocket *sock2 = PlayerManager::getSockFromID(targetData[i+1]); - sock2->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); - } - } else - PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); - - // Warping on recall - if (skillType == EST_RECALL || skillType == EST_RECALL_GROUP) { - if ((int32_t)plr->instanceID == plr->recallInstance) - PlayerManager::sendPlayerTo(sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); - else { - INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response) - sock->sendPacket((void*)&response, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL)); - } - } - */ - - /*** OLD MOB ABILITY HANDLER *** - size_t resplen; - // special case since leech is atypically encoded - if (skillType == EST_BLOODSUCKING) - resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); - else - resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + targetData[0] * sizeof(sPAYLOAD); - - // validate response packet - if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), targetData[0], sizeof(sPAYLOAD))) { - std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size" << std::endl; - return; - } - - uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - memset(respbuf, 0, resplen); - - sP_FE2CL_NPC_SKILL_HIT* resp = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; - sPAYLOAD* respdata = (sPAYLOAD*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); - - resp->iNPC_ID = mob->id; - resp->iSkillID = skillID; - resp->iValue1 = mob->hitX; - resp->iValue2 = mob->hitY; - resp->iValue3 = mob->hitZ; - resp->eST = skillType; - resp->iTargetCnt = targetData[0]; - - for (int i = 0; i < targetData[0]; i++) - if (!work(mob, respdata, i, targetData[i + 1], bitFlag, timeBuffID, duration, amount)) - return; - - NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); - */ -} - -// nano power dispatch table -std::vector Powers = {};/* - Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power), - Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, ECSB_BOUNDINGBALL, power), - Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power), - Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power), - Power(EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, power), - Power(EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, power), - Power(EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, power), - Power(EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, power), - Power(EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, power), - Power(EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, power), - Power(EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, power), - Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power), - Power(EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, power), - Power(EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, power), - Power(EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, power), - Power(EST_RECALL, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_RECALL_GROUP, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_RETROROCKET_SELF, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_PHOENIX_GROUP, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT1, ECSB_STIMPAKSLOT1, power), - Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT2, ECSB_STIMPAKSLOT2, power), - Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT3, ECSB_STIMPAKSLOT3, power), - // - Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power), - Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_RETURNHOMEHEAL, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power), - Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_BATTERYDRAIN, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power), - Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power), - Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power) -};*/ - }; // namespace diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 760ec56..022ee64 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -3,29 +3,6 @@ #include "core/Core.hpp" #include "Combat.hpp" -typedef void (*PowerHandler)(EntityRef, std::vector, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); - -struct Power { - int16_t skillType; - int32_t bitFlag; - int16_t timeBuffID; - PowerHandler handler; - - Power(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} - - void handle(EntityRef ref, std::vector targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { - if (handler == nullptr) - return; - - handler(ref, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); - } - - /* overload for non-nano abilities */ - void handle(EntityRef ref, std::vector targetData, int16_t skillID, int16_t duration, int16_t amount) { - handle(ref, targetData, -1, skillID, duration, amount); - } -}; - struct SkillData { int skillType; int targetType; @@ -37,11 +14,5 @@ struct SkillData { }; namespace Abilities { - extern std::vector Powers; extern std::map SkillTable; - - void removeBuff(CNSocket* sock, std::vector targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); - int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags); - - std::vector findTargets(Player* plr, int skillID, CNPacketData* data = nullptr); } diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 64e2f12..a3b0fa2 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -19,7 +19,8 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); int bitFlag = Groups::getGroupFlags(otherPlr); - int CBFlag = Abilities::applyBuff(sock, skillId, 1, 3, bitFlag); + // TODO ABILITIES + int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag); size_t resplen; @@ -100,23 +101,24 @@ static void eggStep(CNServer* serv, time_t currTime) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); int groupFlags = Groups::getGroupFlags(otherPlr); - 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); + // 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); - } - } + // 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); } diff --git a/src/Groups.cpp b/src/Groups.cpp index 59dfdf7..28d76e4 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -135,10 +135,11 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { // client doesnt read nano data here if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member - if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) + // 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); + Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);*/ } } @@ -221,7 +222,8 @@ static void groupUnbuff(Player* plr) { Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]); - Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); + // TODO ABILITIES + //Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); } } } @@ -295,10 +297,11 @@ void Groups::groupKickPlayer(Player* plr) { moveDown = 1; otherPlr->groupIDs[i] = 0; } else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member. - if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) + // 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); + Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);*/ } } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index e1d4308..9a930ec 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -293,10 +293,11 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->Nanos[plr->activeNano].iStamina > 150) respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; // fire damage power disguised as a corruption attack back at the enemy - std::vector targetData2 = {1, mob->id, 0, 0, 0}; + // TODO ABILITIES + /*std::vector targetData2 = {1, mob->id, 0, 0, 0}; for (auto& pwr : Abilities::Powers) if (pwr.skillType == EST_DAMAGE) - pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); + pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/ } else { respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; @@ -369,9 +370,10 @@ static void useAbilities(Mob *mob, time_t currTime) { } } - for (auto& pwr : Abilities::Powers) + // TODO ABILITIES + /*for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/ mob->skillStyle = -3; // eruption cooldown mob->nextAttack = currTime + 1000; return; @@ -389,13 +391,14 @@ static void useAbilities(Mob *mob, time_t currTime) { if (random < prob1) { // active skill hit int skillID = (int)mob->data["m_iActiveSkill1"]; - std::vector targetData = {1, plr->iID, 0, 0, 0}; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { - if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) - return; // prevent debuffing a player twice - pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); - } + // TODO ABILITIES + //std::vector targetData = {1, plr->iID, 0, 0, 0}; + //for (auto& pwr : Abilities::Powers) + // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { + // if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) + // return; // prevent debuffing a player twice + // pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + // } mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; return; } @@ -785,10 +788,11 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) { self->cbf = 0; // cast a return home heal spell, this is the right way(tm) - std::vector targetData = { 1, 0, 0, 0, 0 }; + // TODO ABILITIES + /*std::vector targetData = { 1, 0, 0, 0, 0 }; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); + pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/ // clear outlying debuffs clearDebuff(self); } @@ -806,10 +810,11 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) { self->roamZ = self->z; int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive - std::vector targetData = { 1, self->id, 0, 0, 0 }; + // TODO ABILITIES + /*std::vector targetData = { 1, self->id, 0, 0, 0 }; for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); + pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/ } void MobAI::onRetreat(CombatNPC* npc, EntityRef src) { diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 0a8318d..dbab11a 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -87,11 +87,12 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { // passive nano unbuffing if (Abilities::SkillTable[skillID].drainType == 2) { - std::vector targetData = Abilities::findTargets(plr, skillID); + // TODO ABILITIES + /*std::vector targetData = Abilities::findTargets(plr, skillID); for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3)); + Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3));*/ } if (nanoID >= NANO_COUNT || nanoID < 0) @@ -102,20 +103,20 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { // passive nano buffing if (Abilities::SkillTable[skillID].drainType == 2) { - std::vector targetData = Abilities::findTargets(plr, skillID); - int boost = 0; if (getNanoBoost(plr)) boost = 1; - for (auto& pwr : Abilities::Powers) { - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { - resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM - plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3]; + // TODO ABILITIES + //std::vector targetData = Abilities::findTargets(plr, skillID); + //for (auto& pwr : Abilities::Powers) { + // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { + // resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM + // plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3]; - pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]); - } - } + // pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]); + // } + //} } if (!silent) // silent nano death but only for the summoning player @@ -296,8 +297,6 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; ) - std::vector targetData = Abilities::findTargets(plr, skillID, data); - int boost = 0; if (getNanoBoost(plr)) boost = 1; @@ -306,9 +305,11 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { if (plr->Nanos[plr->activeNano].iStamina < 0) plr->Nanos[plr->activeNano].iStamina = 0; + // TODO ABILITIES + /*std::vector targetData = Abilities::findTargets(plr, skillID, data); for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]); + pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]);*/ if (plr->Nanos[plr->activeNano].iStamina < 0) summonNano(sock, -1); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index ec305c0..e79bde9 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -411,7 +411,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX)) return; // sanity check plr->Nanos[plr->activeNano].iStamina = 0; - Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); + // TODO ABILITIES + //Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); // fallthrough case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano plr->HP = PC_MAXHEALTH(plr->level) / 2; From 710300c04cfec883554f584dadf1a6219ae73c26 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Fri, 22 Apr 2022 21:13:00 -0400 Subject: [PATCH 027/104] (WIP) EXPERIMENTAL GROUP CHANGES --- src/Chat.cpp | 17 ++-- src/Combat.cpp | 9 +- src/Eggs.cpp | 6 +- src/Entities.hpp | 2 + src/Groups.cpp | 220 ++++++++++++++++++++++-------------------- src/Groups.hpp | 27 +++++- src/MobAI.cpp | 18 ++-- src/NPCManager.cpp | 16 +-- src/Player.hpp | 6 +- src/PlayerManager.cpp | 10 +- 10 files changed, 177 insertions(+), 154 deletions(-) diff --git a/src/Chat.cpp b/src/Chat.cpp index 61b9803..f692a3e 100644 --- a/src/Chat.cpp +++ b/src/Chat.cpp @@ -225,10 +225,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) { static void groupChatHandler(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); - Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - - if (otherPlr == nullptr) - return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); @@ -251,16 +247,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) { resp.iSendPCID = plr->iID; resp.iEmoteCode = chat->iEmoteCode; - Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); + if (plr->group == nullptr) + sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); + else + Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); } static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); - Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - - if (otherPlr == nullptr) - return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; @@ -275,7 +270,9 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { resp.iSendPCID = plr->iID; resp.iEmoteCode = chat->iEmoteCode; - Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); + if (plr->group == nullptr) + sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); + Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); } // we only allow plain ascii, at least for now diff --git a/src/Combat.cpp b/src/Combat.cpp index 0082363..5304c16 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -269,11 +269,10 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { * set of rolls. */ void Combat::genQItemRolls(Player *leader, std::map& rolls) { - for (int i = 0; i < leader->groupCnt; i++) { - if (leader->groupIDs[i] == 0) - continue; + auto players = (*leader->group)[EntityKind::PLAYER]; + for (int i = 0; i < players.size(); i++) { - CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]); + CNSocket *otherSock = players[i].sock; if (otherSock == nullptr) continue; @@ -679,7 +678,7 @@ static void playerTick(CNServer *serv, time_t currTime) { bool transmit = false; // group ticks - if (plr->groupCnt > 1) + if (plr->group != nullptr) Groups::groupTickInfo(plr); // do not tick dead players diff --git a/src/Eggs.cpp b/src/Eggs.cpp index a3b0fa2..64b1e93 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -16,10 +16,9 @@ std::unordered_map Eggs::EggTypes; int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* plr = PlayerManager::getPlayer(sock); - Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - int bitFlag = Groups::getGroupFlags(otherPlr); // TODO ABILITIES + int bitFlag = plr->group->conditionBitFlag; int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag); size_t resplen; @@ -98,9 +97,8 @@ static void eggStep(CNServer* serv, time_t currTime) { CNSocket* sock = it->first.first; int32_t CBFlag = it->first.second; Player* plr = PlayerManager::getPlayer(sock); - Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - int groupFlags = Groups::getGroupFlags(otherPlr); + 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 diff --git a/src/Entities.hpp b/src/Entities.hpp index 41d9dfd..f3f061a 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -25,6 +25,7 @@ enum class AIState { }; class Chunk; +struct Group; struct Entity { EntityKind kind = EntityKind::INVALID; @@ -130,6 +131,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { int level = 0; int speed = 300; AIState state = AIState::INACTIVE; + Group* group = nullptr; int playersInView = 0; // for optimizing away AI in empty chunks std::map stateHandlers; diff --git a/src/Groups.cpp b/src/Groups.cpp index 28d76e4..605beef 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -19,22 +19,65 @@ using namespace Groups; +void Groups::addToGroup(EntityRef member, Group* group) { + if (member.kind == EntityKind::PLAYER) { + Player* plr = PlayerManager::getPlayer(member.sock); + plr->group = group; + } + else if (member.kind == EntityKind::COMBAT_NPC) { + CombatNPC* npc = (CombatNPC*)member.getEntity(); + npc->group = group; + } + else { + std::cout << "[WARN] Adding a weird entity type to a group" << std::endl; + } + + group->members.push_back(member); +} + +void Groups::removeFromGroup(EntityRef member, Group* group) { + if (member.kind == EntityKind::PLAYER) { + Player* plr = PlayerManager::getPlayer(member.sock); + plr->group = nullptr; // no dangling pointers here muahaahahah + } + 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; + } + + 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; + } + + group->members.erase(it); + + if (group->members.empty()) delete group; // cleanup memory +} + +void Groups::disbandGroup(Group* group) { + // remove everyone from the group!! + std::vector members = group->members; + for (EntityRef member : members) { + removeFromGroup(member, group); + } +} + static void requestGroup(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To); - if (otherPlr == nullptr) - return; - - otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); - if (otherPlr == nullptr) return; // fail if the group is full or the other player is already in a group - if (plr->groupCnt >= 4 || otherPlr->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) { + if ((plr->group != nullptr && (*plr->group)[EntityKind::PLAYER].size() >= 4) || otherPlr->group != nullptr) { INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL)); return; @@ -74,31 +117,30 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From); - if (otherPlr == nullptr) - return; - - otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); - if (otherPlr == nullptr) return; // fail if the group is full or the other player is already in a group - if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) { + if (plr->group != nullptr || (otherPlr->group != nullptr && (*otherPlr->group)[EntityKind::PLAYER].size() >= 4)) { INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL)); return; } - if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) { + if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), (*otherPlr->group)[EntityKind::PLAYER].size() + 1, sizeof(sPCGroupMemberInfo))) { std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; return; } - plr->iIDGroup = otherPlr->iID; - otherPlr->groupCnt += 1; - otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID; + if (otherPlr->group == nullptr) { + // create group + otherPlr->group = new Group(); // spooky + addToGroup(PlayerManager::getSockFromID(recv->iID_From), otherPlr->group); + } + addToGroup(sock, otherPlr->group); + auto players = (*otherPlr->group)[EntityKind::PLAYER]; - size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo); + size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + players.size() * sizeof(sPCGroupMemberInfo); uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -107,13 +149,13 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN)); resp->iID_NewMember = plr->iID; - resp->iMemberPCCnt = otherPlr->groupCnt; + resp->iMemberPCCnt = players.size(); - int bitFlag = getGroupFlags(otherPlr); + int bitFlag = otherPlr->group->conditionBitFlag; + for (int i = 0; i < players.size(); i++) { - for (int i = 0; i < otherPlr->groupCnt; i++) { - Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]); - CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]); + Player* varPlr = PlayerManager::getPlayer(players[i].sock); + CNSocket* sockTo = players[i].sock; if (varPlr == nullptr || sockTo == nullptr) continue; @@ -143,37 +185,32 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { } } - sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen); + Groups::sendToGroup(otherPlr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen); } static void leaveGroup(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); - groupKickPlayer(plr); + groupKick(plr); } -void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) { - for (int i = 0; i < plr->groupCnt; i++) { - CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]); - - if (sock == nullptr) - continue; - - if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) { - Player* leavingPlr = PlayerManager::getPlayer(sock); - leavingPlr->iIDGroup = leavingPlr->iID; - } - +void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) { + auto players = (*group)[EntityKind::PLAYER]; + for (int i = 0; i < players.size(); i++) { + CNSocket* sock = players[i].sock; sock->sendPacket(buf, type, size); } } void Groups::groupTickInfo(Player* plr) { - if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) { + + auto players = (*plr->group)[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; } - size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo); + size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + players.size() * sizeof(sPCGroupMemberInfo); uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -182,10 +219,11 @@ void Groups::groupTickInfo(Player* plr) { sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO)); resp->iID = plr->iID; - resp->iMemberPCCnt = plr->groupCnt; + resp->iMemberPCCnt = players.size(); - for (int i = 0; i < plr->groupCnt; i++) { - Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); + for (int i = 0; i < players.size(); i++) { + EntityRef member = players[i]; + Player* varPlr = PlayerManager::getPlayer(member.sock); if (varPlr == nullptr) continue; @@ -210,17 +248,17 @@ void Groups::groupTickInfo(Player* plr) { } } - sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen); + sendToGroup(plr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen); } static void groupUnbuff(Player* plr) { - for (int i = 0; i < plr->groupCnt; i++) { - for (int n = 0; n < plr->groupCnt; n++) { + 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; - Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); - CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]); + EntityRef other = group->members[n]; // TODO ABILITIES //Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); @@ -228,27 +266,26 @@ static void groupUnbuff(Player* plr) { } } -void Groups::groupKickPlayer(Player* plr) { +void Groups::groupKick(Player* plr) { + Group* group = plr->group; + // if you are the group leader, destroy your own group and kick everybody - if (plr->iID == plr->iIDGroup) { + if (plr->group->members[0] == PlayerManager::getSockFromID(plr->iID)) { groupUnbuff(plr); INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1); - sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC)); - plr->groupCnt = 1; + sendToGroup(plr->group, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC)); + disbandGroup(plr->group); return; } - Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); + auto players = (*group)[EntityKind::PLAYER]; - if (otherPlr == nullptr) - return; - - if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) { + 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) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo); + 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); @@ -257,78 +294,55 @@ void Groups::groupKickPlayer(Player* plr) { sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE)); resp->iID_LeaveMember = plr->iID; - resp->iMemberPCCnt = otherPlr->groupCnt - 1; + resp->iMemberPCCnt = players.size() - 1; - int bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag; - int moveDown = 0; + int bitFlag = 0; // TODO ABILITIES getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag; CNSocket* sock = PlayerManager::getSockFromID(plr->iID); if (sock == nullptr) return; - for (int i = 0; i < otherPlr->groupCnt; i++) { - Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]); - CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]); + removeFromGroup(sock, group); + + players = (*group)[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; - if (moveDown == 1) - otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i]; - - respdata[i-moveDown].iPC_ID = varPlr->iID; - respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID; - respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck; - memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName)); - memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName)); - respdata[i-moveDown].iSpecialState = varPlr->iSpecialState; - respdata[i-moveDown].iLv = varPlr->level; - respdata[i-moveDown].iHP = varPlr->HP; - respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level); - // respdata[i-moveDown]].iMapType = 0; - // respdata[i-moveDown]].iMapNum = 0; - respdata[i-moveDown].iX = varPlr->x; - respdata[i-moveDown].iY = varPlr->y; - respdata[i-moveDown].iZ = varPlr->z; + 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) { - moveDown = 1; - otherPlr->groupIDs[i] = 0; - } else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member. + // 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);*/ - } } - plr->iIDGroup = plr->iID; - otherPlr->groupCnt -= 1; - - sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen); + 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)); } -int Groups::getGroupFlags(Player* plr) { - int bitFlag = 0; - - for (int i = 0; i < plr->groupCnt; i++) { - Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); - - if (otherPlr == nullptr) - continue; - - bitFlag |= otherPlr->iGroupConditionBitFlag; - } - - return bitFlag; -} - void Groups::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup); diff --git a/src/Groups.hpp b/src/Groups.hpp index b12f9f9..b437827 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -1,17 +1,36 @@ #pragma once -#include "Player.hpp" #include "core/Core.hpp" #include "servers/CNShardServer.hpp" +#include "Entities.hpp" #include #include +struct Player; +enum EntityKind; + +struct Group { + std::vector members; + int32_t conditionBitFlag; + + auto operator[](EntityKind kind) { + std::vector filtered; + std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) { + return e.kind == kind; + }); + return filtered; + } +}; + namespace Groups { void init(); - void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size); + void sendToGroup(Group* group, void* buf, uint32_t type, size_t size); void groupTickInfo(Player* plr); - void groupKickPlayer(Player* plr); - int getGroupFlags(Player* plr); + void groupKick(Player* plr); + + void addToGroup(EntityRef member, Group* group); + void removeFromGroup(EntityRef member, Group* group); + void disbandGroup(Group* group); } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 9a930ec..6d71e81 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -843,21 +843,17 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { Items::DropRoll eventRolled; std::map qitemRolls; - Player* leader = PlayerManager::getPlayerFromID(plr->iIDGroup); - assert(leader != nullptr); // should never happen - - Combat::genQItemRolls(leader, qitemRolls); - - if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { + if (plr->group == nullptr) { + Combat::genQItemRolls(plr, qitemRolls); Items::giveMobDrop(src.sock, self, rolled, eventRolled); Missions::mobKilled(src.sock, self->type, qitemRolls); } else { - for (int i = 0; i < leader->groupCnt; i++) { - CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); - if (sockTo == nullptr) - continue; - + auto players = (*plr->group)[EntityKind::PLAYER]; + Player* leader = PlayerManager::getPlayer(players[0].sock); + Combat::genQItemRolls(leader, qitemRolls); + for (int i = 0; i < players.size(); i++) { + CNSocket* sockTo = players[i].sock; Player* otherPlr = PlayerManager::getPlayer(sockTo); // only contribute to group members' kills if they're close enough diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 6b3b253..682f549 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -182,9 +182,12 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { if (Warps[warpId].isInstance) { uint64_t instanceID = Warps[warpId].instanceID; + Player* leader = plr; + if (plr->group != nullptr) leader = PlayerManager::getPlayer((*plr->group)[EntityKind::PLAYER][0].sock); + // if warp requires you to be on a mission, it's gotta be a unique instance if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab - instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID + instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID Chunking::createInstance(instanceID); // save Lair entrance coords as a pseudo-Resurrect 'Em @@ -194,14 +197,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { plr->recallInstance = instanceID; } - if (plr->iID == plr->iIDGroup && plr->groupCnt == 1) + if (plr->group == nullptr) PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID); else { - Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); - - for (int i = 0; i < leaderPlr->groupCnt; i++) { - Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]); - CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]); + auto players = (*plr->group)[EntityKind::PLAYER]; + for (int i = 0; i < players.size(); i++) { + CNSocket* sockTo = players[i].sock; + Player* otherPlr = PlayerManager::getPlayer(sockTo); if (otherPlr == nullptr || sockTo == nullptr) continue; diff --git a/src/Player.hpp b/src/Player.hpp index c10446a..84513b8 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -6,6 +6,7 @@ #include "core/Core.hpp" #include "Chunking.hpp" #include "Entities.hpp" +#include "Groups.hpp" #define ACTIVE_MISSION_COUNT 6 @@ -65,10 +66,7 @@ struct Player : public Entity, public ICombatant { sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {}; - int32_t iIDGroup = 0; - int groupCnt = 0; - int32_t groupIDs[4] = {}; - int32_t iGroupConditionBitFlag = 0; + Group* group = nullptr; bool notify = false; bool hidden = false; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index e79bde9..2cdd0fc 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -41,7 +41,7 @@ void PlayerManager::removePlayer(CNSocket* key) { Player* plr = getPlayer(key); uint64_t fromInstance = plr->instanceID; - Groups::groupKickPlayer(plr); + Groups::groupKick(plr); // remove player's bullets Combat::Bullets.erase(plr->iID); @@ -232,8 +232,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { Database::getPlayer(plr, lm->playerId); } - plr->groupCnt = 1; - plr->iIDGroup = plr->groupIDs[0] = plr->iID; + plr->group = nullptr; response.iID = plr->iID; response.uiSvrTime = getTime(); @@ -474,9 +473,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { resp2.PCRegenDataForOtherPC.iHP = plr->HP; resp2.PCRegenDataForOtherPC.iAngle = plr->angle; - Player *otherPlr = getPlayerFromID(plr->iIDGroup); - if (otherPlr != nullptr) { - int bitFlag = Groups::getGroupFlags(otherPlr); + if (plr->group != nullptr) { + int bitFlag = plr->group->conditionBitFlag; resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag; resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; From 22c93ac8540d3d96863121598e05c02ec5665d27 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 23 Apr 2022 15:58:08 -0400 Subject: [PATCH 028/104] Refactor player groups Group structures are used now. Adds more checks in some places but simplifies things overall. We can expand this system to entities as well now pretty trivially. --- src/Combat.cpp | 10 ++-------- src/Combat.hpp | 2 +- src/Groups.cpp | 6 ++++-- src/MobAI.cpp | 8 +++++--- src/PlayerManager.cpp | 3 ++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 5304c16..5f08f9e 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -268,16 +268,10 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { * single RNG roll per mission task, and every group member shares that same * set of rolls. */ -void Combat::genQItemRolls(Player *leader, std::map& rolls) { - auto players = (*leader->group)[EntityKind::PLAYER]; +void Combat::genQItemRolls(std::vector players, std::map& rolls) { for (int i = 0; i < players.size(); i++) { - CNSocket *otherSock = players[i].sock; - if (otherSock == nullptr) - continue; - - Player *member = PlayerManager::getPlayer(otherSock); - + Player* member = players[i]; for (int j = 0; j < ACTIVE_MISSION_COUNT; j++) if (member->tasks[j] != 0) rolls[member->tasks[j]] = Rand::rand(); diff --git a/src/Combat.hpp b/src/Combat.hpp index 14d0076..5b53c86 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -25,5 +25,5 @@ namespace Combat { void init(); void npcAttackPc(Mob *mob, time_t currTime); - void genQItemRolls(Player* leader, std::map& rolls); + void genQItemRolls(std::vector players, std::map& rolls); } diff --git a/src/Groups.cpp b/src/Groups.cpp index 605beef..ba85d4c 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -120,14 +120,16 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { if (otherPlr == nullptr) return; + int size = otherPlr->group == nullptr ? 1 : (*otherPlr->group)[EntityKind::PLAYER].size(); + // fail if the group is full or the other player is already in a group - if (plr->group != nullptr || (otherPlr->group != nullptr && (*otherPlr->group)[EntityKind::PLAYER].size() >= 4)) { + if (plr->group != nullptr || size + 1 > 4) { INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL)); return; } - if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), (*otherPlr->group)[EntityKind::PLAYER].size() + 1, sizeof(sPCGroupMemberInfo))) { + 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; } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 6d71e81..1c9b860 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -842,16 +842,18 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { Items::DropRoll rolled; Items::DropRoll eventRolled; std::map qitemRolls; + std::vector playerRefs; if (plr->group == nullptr) { - Combat::genQItemRolls(plr, qitemRolls); + playerRefs.push_back(plr); + Combat::genQItemRolls(playerRefs, qitemRolls); Items::giveMobDrop(src.sock, self, rolled, eventRolled); Missions::mobKilled(src.sock, self->type, qitemRolls); } else { auto players = (*plr->group)[EntityKind::PLAYER]; - Player* leader = PlayerManager::getPlayer(players[0].sock); - Combat::genQItemRolls(leader, qitemRolls); + for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock)); + Combat::genQItemRolls(playerRefs, qitemRolls); for (int i = 0; i < players.size(); i++) { CNSocket* sockTo = players[i].sock; Player* otherPlr = PlayerManager::getPlayer(sockTo); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 2cdd0fc..1dcc579 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -41,7 +41,8 @@ void PlayerManager::removePlayer(CNSocket* key) { Player* plr = getPlayer(key); uint64_t fromInstance = plr->instanceID; - Groups::groupKick(plr); + if(plr->group != nullptr) + Groups::groupKick(plr); // remove player's bullets Combat::Bullets.erase(plr->iID); From ad53ec82af91f123e9121fe1d2afed1cef89154a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 23 Apr 2022 16:06:31 -0400 Subject: [PATCH 029/104] Ignore .bak files for my local backups lol --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7ce449c..ab596ff 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ build/ version.h infer-out gmon.out +*.bak + From eea4107665e44abb33837f6e8608bb6f890d53c1 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 24 Apr 2022 16:50:03 -0400 Subject: [PATCH 030/104] Replace group filter operator with function --- src/Groups.cpp | 14 +++++++------- src/Groups.hpp | 2 +- src/MobAI.cpp | 2 +- src/NPCManager.cpp | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Groups.cpp b/src/Groups.cpp index ba85d4c..57418b8 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -77,7 +77,7 @@ static void requestGroup(CNSocket* sock, CNPacketData* data) { return; // fail if the group is full or the other player is already in a group - if ((plr->group != nullptr && (*plr->group)[EntityKind::PLAYER].size() >= 4) || otherPlr->group != nullptr) { + if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) { INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL)); return; @@ -120,7 +120,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { if (otherPlr == nullptr) return; - int size = otherPlr->group == nullptr ? 1 : (*otherPlr->group)[EntityKind::PLAYER].size(); + int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size(); // fail if the group is full or the other player is already in a group if (plr->group != nullptr || size + 1 > 4) { @@ -140,7 +140,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { addToGroup(PlayerManager::getSockFromID(recv->iID_From), otherPlr->group); } addToGroup(sock, otherPlr->group); - auto players = (*otherPlr->group)[EntityKind::PLAYER]; + 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]; @@ -196,7 +196,7 @@ static void leaveGroup(CNSocket* sock, CNPacketData* data) { } void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) { - auto players = (*group)[EntityKind::PLAYER]; + auto players = group->filter(EntityKind::PLAYER); for (int i = 0; i < players.size(); i++) { CNSocket* sock = players[i].sock; sock->sendPacket(buf, type, size); @@ -205,7 +205,7 @@ void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) { void Groups::groupTickInfo(Player* plr) { - auto players = (*plr->group)[EntityKind::PLAYER]; + 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"; @@ -280,7 +280,7 @@ void Groups::groupKick(Player* plr) { return; } - auto players = (*group)[EntityKind::PLAYER]; + 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"; @@ -307,7 +307,7 @@ void Groups::groupKick(Player* plr) { removeFromGroup(sock, group); - players = (*group)[EntityKind::PLAYER]; + players = group->filter(EntityKind::PLAYER); for (int i = 0; i < players.size(); i++) { CNSocket* sockTo = players[i].sock; Player* varPlr = PlayerManager::getPlayer(sock); diff --git a/src/Groups.hpp b/src/Groups.hpp index b437827..05bf569 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -14,7 +14,7 @@ struct Group { std::vector members; int32_t conditionBitFlag; - auto operator[](EntityKind kind) { + std::vector filter(EntityKind kind) { std::vector filtered; std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) { return e.kind == kind; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 1c9b860..4299727 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -851,7 +851,7 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { Missions::mobKilled(src.sock, self->type, qitemRolls); } else { - auto players = (*plr->group)[EntityKind::PLAYER]; + auto players = plr->group->filter(EntityKind::PLAYER); for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock)); Combat::genQItemRolls(playerRefs, qitemRolls); for (int i = 0; i < players.size(); i++) { diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 682f549..ee38ece 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -183,7 +183,7 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { uint64_t instanceID = Warps[warpId].instanceID; Player* leader = plr; - if (plr->group != nullptr) leader = PlayerManager::getPlayer((*plr->group)[EntityKind::PLAYER][0].sock); + if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock); // if warp requires you to be on a mission, it's gotta be a unique instance if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab @@ -200,7 +200,7 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { if (plr->group == nullptr) PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID); else { - auto players = (*plr->group)[EntityKind::PLAYER]; + auto players = plr->group->filter(EntityKind::PLAYER); for (int i = 0; i < players.size(); i++) { CNSocket* sockTo = players[i].sock; Player* otherPlr = PlayerManager::getPlayer(sockTo); From 9f74b7decbacb56abf177978bda1787daaac5fa2 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 16 May 2022 16:47:30 -0400 Subject: [PATCH 031/104] some struct reorg --- src/Abilities.cpp | 13 +++++++++++-- src/Abilities.hpp | 11 +++++++++-- src/Eggs.cpp | 4 ++-- src/MobAI.cpp | 4 ++-- src/Nanos.cpp | 4 ++-- src/TableData.cpp | 30 +++++++++++++++++++++++------- 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 8c7f84d..f9dcc75 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -8,6 +8,15 @@ std::map Abilities::SkillTable; -namespace Abilities { +/* +// 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); +} +*/ -}; // namespace +void Abilities::init() { + //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); +} diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 022ee64..83cab06 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -5,14 +5,21 @@ struct SkillData { int skillType; + int effectTarget; + int effectType; int targetType; - int drainType; + int batteryDrainType; int effectArea; + int batteryUse[4]; int durationTime[4]; - int powerIntensity[4]; + + int valueTypes[3]; + int values[3][4]; }; namespace Abilities { extern std::map SkillTable; + + void init(); } diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 64b1e93..5a5c903 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -41,7 +41,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iDamage = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].powerIntensity[0] / 1000; + skill->iDamage = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].values[0][0] / 1000; plr->HP -= skill->iDamage; if (plr->HP < 0) plr->HP = 0; @@ -51,7 +51,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iHealHP = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].powerIntensity[0] / 1000; + 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); diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 4299727..55d6747 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -281,7 +281,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i int style2 = Nanos::nanoStyle(plr->activeNano); if (style2 == -1) { // no nano respdata[i].iHitFlag = HF_BIT_STYLE_TIE; - respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; + respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; } else if (style == style2) { respdata[i].iHitFlag = HF_BIT_STYLE_TIE; respdata[i].iDamage = 0; @@ -300,7 +300,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/ } else { respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; - respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; + respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90; if (plr->Nanos[plr->activeNano].iStamina < 0) { respdata[i].bNanoDeactive = 1; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index dbab11a..df0ae81 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -86,7 +86,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; // passive nano unbuffing - if (Abilities::SkillTable[skillID].drainType == 2) { + if (Abilities::SkillTable[skillID].batteryDrainType == 2) { // TODO ABILITIES /*std::vector targetData = Abilities::findTargets(plr, skillID); @@ -102,7 +102,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { skillID = plr->Nanos[nanoID].iSkillID; // passive nano buffing - if (Abilities::SkillTable[skillID].drainType == 2) { + if (Abilities::SkillTable[skillID].batteryDrainType == 2) { int boost = 0; if (getNanoBoost(plr)) boost = 1; diff --git a/src/TableData.cpp b/src/TableData.cpp index 3461564..11e23fd 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -231,15 +231,31 @@ static void loadXDT(json& xdtData) { // load nano powers json skills = xdtData["m_pSkillTable"]["m_pSkillData"]; - for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) { - auto skills = _skills.value(); - SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] }; + for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) { + auto skill = _skill.value(); + SkillData skillData = { + skill["m_iSkillType"], + skill["m_iEffectTarget"], + skill["m_iEffectType"], + skill["m_iTargetType"], + skill["m_iBatteryDrainType"], + skill["m_iEffectArea"] + }; + + skillData.valueTypes[0] = skill["m_iValueA_Type"]; + skillData.valueTypes[1] = skill["m_iValueB_Type"]; + skillData.valueTypes[2] = skill["m_iValueC_Type"]; + for (int i = 0; i < 4; i++) { - skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i]; - skillData.durationTime[i] = skills["m_iDurationTime"][i]; - skillData.powerIntensity[i] = skills["m_iValueA"][i]; + skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i]; + skillData.durationTime[i] = skill["m_iDurationTime"][i]; + + skillData.values[0][i] = skill["m_iValueA"][i]; + skillData.values[1][i] = skill["m_iValueB"][i]; + skillData.values[2][i] = skill["m_iValueC"][i]; } - Abilities::SkillTable[skills["m_iSkillNumber"]] = skillData; + + Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData; } std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl; From c8f5aab929f83717dd83f93fb3fe00c1f1fa7bb3 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 16 May 2022 20:28:27 -0400 Subject: [PATCH 032/104] Active power classification --- src/Abilities.cpp | 33 +++++++++++++++++++++++++++++++-- src/Abilities.hpp | 22 +++++++++++++++++++--- src/Nanos.cpp | 22 +++++++++++++++------- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index f9dcc75..328b3e7 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -17,6 +17,35 @@ static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { } */ -void Abilities::init() { - //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); +std::vector Abilities::matchTargets(SkillData *skill, int count, int32_t *ids) { + + std::vector tempTargs; + switch (skill->effectTarget) + { + case SkillEffectTarget::POINT: + std::cout << "[SKILL] POINT; "; + break; + case SkillEffectTarget::SELF: + std::cout << "[SKILL] SELF; "; + break; + case SkillEffectTarget::CONE: + std::cout << "[SKILL] CONE; "; + break; + case SkillEffectTarget::AREA_SELF: + std::cout << "[SKILL] AREA_SELF; "; + break; + case SkillEffectTarget::AREA_TARGET: + std::cout << "[SKILL] AREA_TARGET; "; + break; + } + + for (int i = 0; i < count; i++) std::cout << ids[i] << " "; + std::cout << std::endl; + + std::vector targets; + return targets; +} + +void Abilities::init() { + //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 83cab06..4f0cfd4 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -3,11 +3,26 @@ #include "core/Core.hpp" #include "Combat.hpp" +enum class SkillEffectTarget { + POINT = 1, + SELF = 2, + CONE = 3, + WEAPON = 4, + AREA_SELF = 5, + AREA_TARGET = 6 +}; + +enum class SkillTargetType { + MOBS = 1, + PLAYERS = 2, + SELF = 3 // only used once by client /shrug +}; + struct SkillData { int skillType; - int effectTarget; - int effectType; - int targetType; + SkillEffectTarget effectTarget; + int effectType; // always 1? + SkillTargetType targetType; int batteryDrainType; int effectArea; @@ -21,5 +36,6 @@ struct SkillData { namespace Abilities { extern std::map SkillTable; + std::vector matchTargets(SkillData*, int, int32_t*); void init(); } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index df0ae81..36a682a 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -84,13 +84,14 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { plr->nanoDrainRate = 0; int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; + SkillData* skillData = &Abilities::SkillTable[skillID]; // passive nano unbuffing - if (Abilities::SkillTable[skillID].batteryDrainType == 2) { + if (skillData->batteryDrainType == 2) { // TODO ABILITIES - /*std::vector targetData = Abilities::findTargets(plr, skillID); + std::vector targetData = Abilities::matchTargets(skillData, 0, nullptr); - for (auto& pwr : Abilities::Powers) + /*for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3));*/ } @@ -290,8 +291,15 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) { static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); - int16_t nanoID = plr->activeNano; - int16_t skillID = plr->Nanos[nanoID].iSkillID; + // validate request check + sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf; + if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) { + std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl; + return; + } + + int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; + SkillData* skillData = &Abilities::SkillTable[skillID]; DEBUGLOG( std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; @@ -306,8 +314,8 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { plr->Nanos[plr->activeNano].iStamina = 0; // TODO ABILITIES - /*std::vector targetData = Abilities::findTargets(plr, skillID, data); - for (auto& pwr : Abilities::Powers) + std::vector targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); + /*for (auto& pwr : Abilities::Powers) if (pwr.skillType == Abilities::SkillTable[skillID].skillType) pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]);*/ From 2f4c8cdd60d1f6c73d1b769ca1caa996a7b6bb81 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 18 May 2022 10:04:28 -0400 Subject: [PATCH 033/104] Groundwork for new buff system --- src/Abilities.cpp | 52 ++++++++++++++++++++++++----------------------- src/Abilities.hpp | 6 ++++-- src/Groups.hpp | 2 +- src/Nanos.cpp | 18 ++++++++++------ 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 328b3e7..efcbab7 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -17,33 +17,35 @@ static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { } */ -std::vector Abilities::matchTargets(SkillData *skill, int count, int32_t *ids) { - - std::vector tempTargs; - switch (skill->effectTarget) - { - case SkillEffectTarget::POINT: - std::cout << "[SKILL] POINT; "; - break; - case SkillEffectTarget::SELF: - std::cout << "[SKILL] SELF; "; - break; - case SkillEffectTarget::CONE: - std::cout << "[SKILL] CONE; "; - break; - case SkillEffectTarget::AREA_SELF: - std::cout << "[SKILL] AREA_SELF; "; - break; - case SkillEffectTarget::AREA_TARGET: - std::cout << "[SKILL] AREA_TARGET; "; - break; - } - - for (int i = 0; i < count; i++) std::cout << ids[i] << " "; - std::cout << std::endl; +std::vector Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { std::vector targets; - return targets; + + for (int i = 0; i < count; i++) { + int32_t id = ids[i]; + if (skill->targetType == SkillTargetType::MOBS) { + // mob? + if (NPCManager::NPCs.find(id) != NPCManager::NPCs.end()) targets.push_back(id); + else std::cout << "[WARN] skill: id not found\n"; + } else { + // player? + CNSocket* sock = PlayerManager::getSockFromID(id); + if (sock != nullptr) targets.push_back(sock); + else std::cout << "[WARN] skill: sock not found\n"; + } + } + + return targets; +} + +void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector targets) { + for (EntityRef target : targets) { + Entity* entity = target.getEntity(); + if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB) + continue; // not a combatant + + + } } void Abilities::init() { diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 4f0cfd4..3179cc1 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -14,8 +14,8 @@ enum class SkillEffectTarget { enum class SkillTargetType { MOBS = 1, - PLAYERS = 2, - SELF = 3 // only used once by client /shrug + SELF = 2, + GROUP = 3 }; struct SkillData { @@ -37,5 +37,7 @@ namespace Abilities { extern std::map SkillTable; std::vector matchTargets(SkillData*, int, int32_t*); + void applyAbility(SkillData*, EntityRef, std::vector); + void init(); } diff --git a/src/Groups.hpp b/src/Groups.hpp index 05bf569..ac6ad57 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -12,7 +12,7 @@ enum EntityKind; struct Group { std::vector members; - int32_t conditionBitFlag; + int8_t conditionCounters[32]; std::vector filter(EntityKind kind) { std::vector filtered; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 36a682a..c37b7ea 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -85,15 +85,16 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { plr->nanoDrainRate = 0; int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; SkillData* skillData = &Abilities::SkillTable[skillID]; + std::vector targetData; // passive nano unbuffing if (skillData->batteryDrainType == 2) { - // TODO ABILITIES - std::vector targetData = Abilities::matchTargets(skillData, 0, nullptr); + targetData.push_back(sock); // self + if (skillData->targetType == SkillTargetType::GROUP && plr->group != nullptr) + targetData = plr->group->members; // whole group - /*for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3));*/ + /* TODO ABILITIES */ + targetData.clear(); } if (nanoID >= NANO_COUNT || nanoID < 0) @@ -101,13 +102,18 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { plr->activeNano = nanoID; skillID = plr->Nanos[nanoID].iSkillID; + skillData = &Abilities::SkillTable[skillID]; // passive nano buffing - if (Abilities::SkillTable[skillID].batteryDrainType == 2) { + if (skillData->batteryDrainType == 2) { int boost = 0; if (getNanoBoost(plr)) boost = 1; + targetData.push_back(sock); // self + if (skillData->targetType == SkillTargetType::GROUP && plr->group != nullptr) + targetData = plr->group->members; // whole group + // TODO ABILITIES //std::vector targetData = Abilities::findTargets(plr, skillID); //for (auto& pwr : Abilities::Powers) { From 760170af94f85dff68e15600fd0a5c9b1c2c4a0c Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 18 May 2022 21:30:53 -0400 Subject: [PATCH 034/104] Start moving passive power processing to `playerTick` --- src/Abilities.cpp | 2 +- src/Abilities.hpp | 7 ++++++- src/Combat.cpp | 30 ++++++++++++++++++++++++++---- src/Eggs.cpp | 4 ++-- src/Groups.cpp | 2 +- src/Groups.hpp | 1 - src/Nanos.cpp | 41 +++-------------------------------------- src/Player.hpp | 1 - src/PlayerManager.cpp | 5 +++-- 9 files changed, 42 insertions(+), 51 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index efcbab7..54f8c91 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -38,7 +38,7 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 return targets; } -void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector targets) { +void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector targets) { for (EntityRef target : targets) { Entity* entity = target.getEntity(); if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB) diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 3179cc1..27c376b 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -18,12 +18,17 @@ enum class SkillTargetType { GROUP = 3 }; +enum class SkillDrainType { + ACTIVE = 1, + PASSIVE = 2 +}; + struct SkillData { int skillType; SkillEffectTarget effectTarget; int effectType; // always 1? SkillTargetType targetType; - int batteryDrainType; + SkillDrainType drainType; int effectArea; int batteryUse[4]; diff --git a/src/Combat.cpp b/src/Combat.cpp index 5f08f9e..16ce751 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -695,15 +695,37 @@ static void playerTick(CNServer *serv, time_t currTime) { plr->healCooldown -= 4000; } + // nanos for (int i = 0; i < 3; i++) { - if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina - plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5; + if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // tick active nano + sNano& nano = plr->Nanos[plr->activeNano]; + int drainRate = 0; - if (plr->Nanos[plr->activeNano].iStamina <= 0) + 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 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) { // regain stamina + } else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // tick resting nano sNano& nano = plr->Nanos[plr->equippedNanos[i]]; nano.iStamina += 1; diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 5a5c903..ca82b54 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -18,7 +18,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* plr = PlayerManager::getPlayer(sock); // TODO ABILITIES - int bitFlag = plr->group->conditionBitFlag; + //int bitFlag = plr->group->conditionBitFlag; int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag); size_t resplen; @@ -98,7 +98,7 @@ static void eggStep(CNServer* serv, time_t currTime) { int32_t CBFlag = it->first.second; Player* plr = PlayerManager::getPlayer(sock); - int groupFlags = plr->group->conditionBitFlag; + //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 diff --git a/src/Groups.cpp b/src/Groups.cpp index 57418b8..f3bf7d8 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -153,7 +153,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { resp->iID_NewMember = plr->iID; resp->iMemberPCCnt = players.size(); - int bitFlag = otherPlr->group->conditionBitFlag; + //int bitFlag = otherPlr->group->conditionBitFlag; for (int i = 0; i < players.size(); i++) { Player* varPlr = PlayerManager::getPlayer(players[i].sock); diff --git a/src/Groups.hpp b/src/Groups.hpp index ac6ad57..c694597 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -12,7 +12,6 @@ enum EntityKind; struct Group { std::vector members; - int8_t conditionCounters[32]; std::vector filter(EntityKind kind) { std::vector filtered; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index c37b7ea..34e200d 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -82,49 +82,14 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0) return; // prevent powerless nanos from summoning - plr->nanoDrainRate = 0; - int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; - SkillData* skillData = &Abilities::SkillTable[skillID]; - std::vector targetData; - - // passive nano unbuffing - if (skillData->batteryDrainType == 2) { - targetData.push_back(sock); // self - if (skillData->targetType == SkillTargetType::GROUP && plr->group != nullptr) - targetData = plr->group->members; // whole group - - /* TODO ABILITIES */ - targetData.clear(); - } - if (nanoID >= NANO_COUNT || nanoID < 0) return; // sanity check plr->activeNano = nanoID; - skillID = plr->Nanos[nanoID].iSkillID; - skillData = &Abilities::SkillTable[skillID]; - // passive nano buffing - if (skillData->batteryDrainType == 2) { - int boost = 0; - if (getNanoBoost(plr)) - boost = 1; - - targetData.push_back(sock); // self - if (skillData->targetType == SkillTargetType::GROUP && plr->group != nullptr) - targetData = plr->group->members; // whole group - - // TODO ABILITIES - //std::vector targetData = Abilities::findTargets(plr, skillID); - //for (auto& pwr : Abilities::Powers) { - // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { - // resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM - // plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3]; - - // pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]); - // } - //} - } + 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 if (!silent) // silent nano death but only for the summoning player sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC); diff --git a/src/Player.hpp b/src/Player.hpp index 84513b8..3b9a40c 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -50,7 +50,6 @@ struct Player : public Entity, public ICombatant { bool inCombat = false; bool onMonkey = false; - int nanoDrainRate = 0; int healCooldown = 0; int pointDamage = 0; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 1dcc579..fe71245 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -475,8 +475,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { resp2.PCRegenDataForOtherPC.iAngle = plr->angle; if (plr->group != nullptr) { - int bitFlag = plr->group->conditionBitFlag; - resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag; + // TODO ABILITIES + //int bitFlag = plr->group->conditionBitFlag; + //resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag; resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState; From 2f612ce0e197a934b2c0fa10e51f5ff63c81f7bb Mon Sep 17 00:00:00 2001 From: gsemaj Date: Fri, 15 Jul 2022 22:17:59 -0700 Subject: [PATCH 035/104] Handle case where cmake is invoked outside root --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2093a94..7aee17d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,8 @@ project(OpenFusion) set(CMAKE_CXX_STANDARD 17) -execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) # OpenFusion supports multiple packet/struct versions # 104 is the default version to build which can be changed From e4e4a421f4ef5f1f784c6a3d0d1543fd38b31373 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Jul 2022 12:18:32 -0700 Subject: [PATCH 036/104] Get rid of player fire rate suspicion This was super primitive & jank, and caused false positives. Will replace with a polished system later on. --- src/Combat.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 16ce751..f7aba6d 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -590,21 +590,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { return; } - // rapid fire anti-cheat - time_t currTime = getTime(); - if (currTime - plr->lastShot < plr->fireRate * 80) - plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing - else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0) - plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing - - plr->lastShot = currTime; - - if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious - sock->kill(); - CNShardServer::_killConnection(sock); - return; - } - /* * initialize response struct * rocket style hit doesn't work properly, so we're always sending this one From 306a75f469ad8d116004ccee4891d86f0a689c21 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Jul 2022 16:19:40 -0700 Subject: [PATCH 037/104] The great re-`#include` Was getting frustrated by the inconsistency in our include statements, which were causing me problems. As a result, I went through and manually re-organized every include statement in non-core files. I'm just gonna copy my rant from Discord: FOR HEADER FILES (.hpp): - everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp - you may NOT include ANYTHING ELSE FOR SOURCE FILES (.cpp): - you can #include whatever you want as long as the partner header is included first - anything that gets included by another include is fair game - redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean. the point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues --- src/Abilities.cpp | 7 ++----- src/Abilities.hpp | 6 ++++-- src/Buddies.cpp | 15 +++++---------- src/Buddies.hpp | 2 -- src/BuiltinCommands.cpp | 7 +++++-- src/Chat.cpp | 5 ++++- src/Chat.hpp | 5 ++++- src/Chunking.cpp | 8 ++++---- src/Chunking.hpp | 6 ++---- src/Combat.cpp | 17 ++++++++--------- src/Combat.hpp | 10 ++-------- src/CustomCommands.cpp | 14 +++++++------- src/CustomCommands.hpp | 2 ++ src/Eggs.cpp | 11 +++++++---- src/Eggs.hpp | 1 - src/Email.cpp | 2 +- src/Entities.cpp | 11 ++++------- src/Entities.hpp | 2 +- src/Groups.cpp | 12 ++++-------- src/Groups.hpp | 5 ++--- src/Items.cpp | 12 +++++++----- src/Items.hpp | 8 ++++++-- src/Missions.cpp | 11 +++++------ src/Missions.hpp | 6 ++++-- src/MobAI.cpp | 11 +++++++---- src/MobAI.hpp | 8 +++++--- src/NPCManager.cpp | 6 ++++-- src/NPCManager.hpp | 9 ++++----- src/Nanos.cpp | 8 ++++---- src/Nanos.hpp | 6 +++--- src/Player.hpp | 7 +++---- src/PlayerManager.cpp | 23 +++++++++-------------- src/PlayerManager.hpp | 11 +++++------ src/PlayerMovement.cpp | 3 +++ src/Racing.cpp | 8 +++++--- src/Racing.hpp | 6 ++++-- src/Rand.hpp | 1 + src/TableData.cpp | 12 ++++-------- src/TableData.hpp | 9 +++++++-- src/Trading.cpp | 3 +++ src/Trading.hpp | 2 -- src/Transport.cpp | 7 +++++-- src/Transport.hpp | 5 ++++- src/Vendors.cpp | 5 +++++ src/Vendors.hpp | 5 ++--- src/core/CNStructs.hpp | 1 + src/servers/CNLoginServer.cpp | 8 +++++--- src/servers/CNLoginServer.hpp | 1 + src/servers/CNShardServer.cpp | 6 ++++-- src/servers/CNShardServer.hpp | 1 + src/servers/Monitor.cpp | 4 +++- src/servers/Monitor.hpp | 3 --- src/settings.cpp | 7 ++++--- src/settings.hpp | 2 ++ vendor/JSON.hpp | 2 ++ 55 files changed, 200 insertions(+), 175 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 54f8c91..691f486 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -1,10 +1,7 @@ #include "Abilities.hpp" -#include "PlayerManager.hpp" -#include "Player.hpp" + #include "NPCManager.hpp" -#include "Nanos.hpp" -#include "Groups.hpp" -#include "Eggs.hpp" +#include "PlayerManager.hpp" std::map Abilities::SkillTable; diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 27c376b..834c605 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -1,7 +1,9 @@ #pragma once -#include "core/Core.hpp" -#include "Combat.hpp" +#include "Entities.hpp" + +#include +#include enum class SkillEffectTarget { POINT = 1, diff --git a/src/Buddies.cpp b/src/Buddies.cpp index 25f19cc..e1e6a02 100644 --- a/src/Buddies.cpp +++ b/src/Buddies.cpp @@ -1,15 +1,10 @@ -#include "servers/CNShardServer.hpp" #include "Buddies.hpp" -#include "PlayerManager.hpp" -#include "Buddies.hpp" -#include "db/Database.hpp" -#include "Items.hpp" -#include "db/Database.hpp" -#include -#include -#include -#include +#include "db/Database.hpp" +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" +#include "PlayerManager.hpp" using namespace Buddies; diff --git a/src/Buddies.hpp b/src/Buddies.hpp index e7f9d2e..f0bc455 100644 --- a/src/Buddies.hpp +++ b/src/Buddies.hpp @@ -1,7 +1,5 @@ #pragma once -#include "Player.hpp" -#include "core/Core.hpp" #include "core/Core.hpp" namespace Buddies { diff --git a/src/BuiltinCommands.cpp b/src/BuiltinCommands.cpp index 356992a..e36b2a4 100644 --- a/src/BuiltinCommands.cpp +++ b/src/BuiltinCommands.cpp @@ -1,10 +1,13 @@ #include "BuiltinCommands.hpp" + +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" #include "PlayerManager.hpp" -#include "Chat.hpp" #include "Items.hpp" #include "Missions.hpp" +#include "Chat.hpp" #include "Nanos.hpp" -#include "Rand.hpp" // helper function, not a packet handler void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { diff --git a/src/Chat.cpp b/src/Chat.cpp index f692a3e..51d4222 100644 --- a/src/Chat.cpp +++ b/src/Chat.cpp @@ -1,6 +1,9 @@ #include "Chat.hpp" + +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" #include "PlayerManager.hpp" -#include "Groups.hpp" #include "CustomCommands.hpp" #include diff --git a/src/Chat.hpp b/src/Chat.hpp index acaca06..26b4236 100644 --- a/src/Chat.hpp +++ b/src/Chat.hpp @@ -2,7 +2,10 @@ #define CMD_PREFIX '/' -#include "servers/CNShardServer.hpp" +#include "core/Core.hpp" + +#include +#include namespace Chat { extern std::vector dump; diff --git a/src/Chunking.cpp b/src/Chunking.cpp index 6e96c0a..71ce013 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -1,9 +1,9 @@ #include "Chunking.hpp" -#include "PlayerManager.hpp" + +#include "MobAI.hpp" #include "NPCManager.hpp" -#include "settings.hpp" -#include "Combat.hpp" -#include "Eggs.hpp" + +#include using namespace Chunking; diff --git a/src/Chunking.hpp b/src/Chunking.hpp index 61ead49..3673638 100644 --- a/src/Chunking.hpp +++ b/src/Chunking.hpp @@ -1,12 +1,10 @@ #pragma once -#include "core/Core.hpp" +#include "Entities.hpp" -#include #include #include -#include -#include +#include struct EntityRef; diff --git a/src/Combat.cpp b/src/Combat.cpp index f7aba6d..94e3c86 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -1,14 +1,13 @@ #include "Combat.hpp" -#include "PlayerManager.hpp" -#include "Nanos.hpp" -#include "NPCManager.hpp" -#include "Items.hpp" -#include "Missions.hpp" -#include "Groups.hpp" -#include "Transport.hpp" -#include "Racing.hpp" -#include "Abilities.hpp" + +#include "servers/CNShardServer.hpp" + #include "Rand.hpp" +#include "Player.hpp" +#include "PlayerManager.hpp" +#include "NPCManager.hpp" +#include "Nanos.hpp" +#include "Abilities.hpp" #include diff --git a/src/Combat.hpp b/src/Combat.hpp index 5b53c86..a4868d3 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -1,14 +1,10 @@ #pragma once -#include "core/Core.hpp" -#include "servers/CNShardServer.hpp" +#include "Player.hpp" #include "MobAI.hpp" -#include "JSON.hpp" - #include -#include -#include +#include struct Bullet { int pointDamage; @@ -17,8 +13,6 @@ struct Bullet { int bulletType; }; - - namespace Combat { extern std::map> Bullets; diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 36e0108..3b48907 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -1,18 +1,18 @@ #include "CustomCommands.hpp" -#include "Chat.hpp" + +#include "db/Database.hpp" + +#include "Player.hpp" #include "PlayerManager.hpp" +#include "Chat.hpp" #include "TableData.hpp" #include "NPCManager.hpp" -#include "Eggs.hpp" #include "MobAI.hpp" -#include "Items.hpp" -#include "db/Database.hpp" -#include "Transport.hpp" #include "Missions.hpp" +#include "Eggs.hpp" +#include "Items.hpp" #include -#include -#include #include typedef void (*CommandHandler)(std::string fullString, std::vector& args, CNSocket* sock); diff --git a/src/CustomCommands.hpp b/src/CustomCommands.hpp index 62103da..ae9ea7b 100644 --- a/src/CustomCommands.hpp +++ b/src/CustomCommands.hpp @@ -2,6 +2,8 @@ #include "core/Core.hpp" +#include + namespace CustomCommands { void init(); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index ca82b54..cddf6b4 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -1,10 +1,13 @@ -#include "core/Core.hpp" #include "Eggs.hpp" + +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" #include "PlayerManager.hpp" -#include "Items.hpp" -#include "Nanos.hpp" #include "Abilities.hpp" -#include "Groups.hpp" +#include "NPCManager.hpp" +#include "Entities.hpp" +#include "Items.hpp" #include diff --git a/src/Eggs.hpp b/src/Eggs.hpp index 2155272..eeea678 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -1,7 +1,6 @@ #pragma once #include "core/Core.hpp" -#include "Entities.hpp" struct EggType { int dropCrateId; diff --git a/src/Email.cpp b/src/Email.cpp index 3064138..9928af6 100644 --- a/src/Email.cpp +++ b/src/Email.cpp @@ -1,9 +1,9 @@ #include "Email.hpp" #include "core/Core.hpp" +#include "db/Database.hpp" #include "servers/CNShardServer.hpp" -#include "db/Database.hpp" #include "PlayerManager.hpp" #include "Items.hpp" #include "Chat.hpp" diff --git a/src/Entities.cpp b/src/Entities.cpp index ff63053..a8c53b1 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -1,12 +1,9 @@ -#include "core/Core.hpp" #include "Entities.hpp" -#include "Chunking.hpp" -#include "PlayerManager.hpp" -#include "NPCManager.hpp" -#include "Eggs.hpp" -#include "MobAI.hpp" -#include +#include "NPCManager.hpp" +#include "PlayerManager.hpp" + +#include static_assert(std::is_standard_layout::value); static_assert(std::is_trivially_copyable::value); diff --git a/src/Entities.hpp b/src/Entities.hpp index f3f061a..1b02f0b 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -3,8 +3,8 @@ #include "core/Core.hpp" #include "Chunking.hpp" -#include #include +#include enum EntityKind { INVALID, diff --git a/src/Groups.cpp b/src/Groups.cpp index f3bf7d8..5655b54 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -1,13 +1,9 @@ -#include "servers/CNShardServer.hpp" -#include "PlayerManager.hpp" #include "Groups.hpp" -#include "Nanos.hpp" -#include "Abilities.hpp" -#include -#include -#include -#include +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" +#include "PlayerManager.hpp" /* * NOTE: Variadic response packets that list group members are technically diff --git a/src/Groups.hpp b/src/Groups.hpp index c694597..18cf6e5 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -1,14 +1,13 @@ #pragma once -#include "core/Core.hpp" -#include "servers/CNShardServer.hpp" #include "Entities.hpp" +#include #include #include +/* forward declaration(s) */ struct Player; -enum EntityKind; struct Group { std::vector members; diff --git a/src/Items.cpp b/src/Items.cpp index 166526b..fc993e2 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -1,16 +1,18 @@ -#include "servers/CNShardServer.hpp" #include "Items.hpp" + +#include "servers/CNShardServer.hpp" + +#include "Player.hpp" #include "PlayerManager.hpp" #include "Nanos.hpp" -#include "NPCManager.hpp" -#include "Player.hpp" #include "Abilities.hpp" -#include "Missions.hpp" #include "Eggs.hpp" -#include "Rand.hpp" +#include "MobAI.hpp" +#include "Missions.hpp" #include // for memset() #include +#include using namespace Items; diff --git a/src/Items.hpp b/src/Items.hpp index 97c7733..2156b92 100644 --- a/src/Items.hpp +++ b/src/Items.hpp @@ -1,9 +1,13 @@ #pragma once -#include "servers/CNShardServer.hpp" +#include "core/Core.hpp" + #include "Player.hpp" -#include "MobAI.hpp" #include "Rand.hpp" +#include "MobAI.hpp" + +#include +#include struct CrocPotEntry { int multStats, multLooks; diff --git a/src/Missions.cpp b/src/Missions.cpp index 890bec5..db0d17c 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -1,11 +1,10 @@ -#include "servers/CNShardServer.hpp" #include "Missions.hpp" -#include "PlayerManager.hpp" -#include "Nanos.hpp" -#include "Items.hpp" -#include "Transport.hpp" -#include "string.h" +#include "servers/CNShardServer.hpp" + +#include "PlayerManager.hpp" +#include "Items.hpp" +#include "Nanos.hpp" using namespace Missions; diff --git a/src/Missions.hpp b/src/Missions.hpp index 4e572a6..46eaa4c 100644 --- a/src/Missions.hpp +++ b/src/Missions.hpp @@ -1,9 +1,11 @@ #pragma once -#include "servers/CNShardServer.hpp" +#include "core/Core.hpp" +#include "JSON.hpp" + #include "Player.hpp" -#include "JSON.hpp" +#include struct Reward { int32_t id; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 55d6747..7313b85 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -1,13 +1,16 @@ #include "MobAI.hpp" -#include "Player.hpp" + +#include "Chunking.hpp" +#include "NPCManager.hpp" +#include "Entities.hpp" +#include "PlayerManager.hpp" #include "Racing.hpp" -#include "Transport.hpp" #include "Nanos.hpp" -#include "Combat.hpp" #include "Abilities.hpp" -#include "Rand.hpp" +#include "Combat.hpp" #include "Items.hpp" #include "Missions.hpp" +#include "Rand.hpp" #include #include diff --git a/src/MobAI.hpp b/src/MobAI.hpp index cb6bfc8..0941919 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -1,11 +1,13 @@ #pragma once #include "core/Core.hpp" -#include "NPCManager.hpp" +#include "JSON.hpp" + #include "Entities.hpp" -/* kill me */ -struct Mob; +#include +#include + namespace MobAI { void deadStep(CombatNPC* self, time_t currTime); void combatStep(CombatNPC* self, time_t currTime); diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index ee38ece..5baddb6 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -1,4 +1,8 @@ #include "NPCManager.hpp" + +#include "servers/CNShardServer.hpp" + +#include "PlayerManager.hpp" #include "Items.hpp" #include "settings.hpp" #include "Combat.hpp" @@ -20,8 +24,6 @@ #include #include -#include "JSON.hpp" - using namespace NPCManager; std::unordered_map NPCManager::NPCs; diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index d390e3b..3429d42 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -1,14 +1,15 @@ #pragma once #include "core/Core.hpp" -#include "PlayerManager.hpp" -#include "Transport.hpp" - #include "JSON.hpp" +#include "Transport.hpp" +#include "Chunking.hpp" + #include #include #include +#include #define RESURRECT_HEIGHT 400 @@ -28,8 +29,6 @@ struct NPCEvent { : npcType(t), trigger(tr), handler(hndlr) {} }; -struct WarpLocation; - namespace NPCManager { extern std::unordered_map NPCs; extern std::map Warps; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 34e200d..088d9cb 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -1,11 +1,11 @@ -#include "servers/CNShardServer.hpp" #include "Nanos.hpp" + +#include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" -#include "NPCManager.hpp" -#include "Combat.hpp" #include "Missions.hpp" -#include "Groups.hpp" #include "Abilities.hpp" +#include "NPCManager.hpp" #include diff --git a/src/Nanos.hpp b/src/Nanos.hpp index c41318e..2002330 100644 --- a/src/Nanos.hpp +++ b/src/Nanos.hpp @@ -1,10 +1,10 @@ #pragma once -#include -#include +#include "core/Core.hpp" #include "Player.hpp" -#include "servers/CNShardServer.hpp" + +#include struct NanoData { int style; diff --git a/src/Player.hpp b/src/Player.hpp index 3b9a40c..c58b7d6 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -1,13 +1,12 @@ #pragma once -#include -#include - #include "core/Core.hpp" -#include "Chunking.hpp" + #include "Entities.hpp" #include "Groups.hpp" +#include + #define ACTIVE_MISSION_COUNT 6 #define PC_MAXHEALTH(level) (925 + 75 * (level)) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index fe71245..91d4c69 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -1,25 +1,20 @@ -#include "core/Core.hpp" +#include "PlayerManager.hpp" + +#include "db/Database.hpp" #include "core/CNShared.hpp" #include "servers/CNShardServer.hpp" -#include "db/Database.hpp" -#include "PlayerManager.hpp" + #include "NPCManager.hpp" -#include "Missions.hpp" -#include "Items.hpp" -#include "Nanos.hpp" -#include "Groups.hpp" -#include "Chat.hpp" -#include "Buddies.hpp" #include "Combat.hpp" #include "Racing.hpp" -#include "BuiltinCommands.hpp" -#include "Abilities.hpp" #include "Eggs.hpp" - -#include "settings.hpp" +#include "Missions.hpp" +#include "Chat.hpp" +#include "Items.hpp" +#include "Buddies.hpp" +#include "BuiltinCommands.hpp" #include - #include #include #include diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 883cf74..1e15ddc 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -1,16 +1,15 @@ #pragma once -#include "Player.hpp" #include "core/Core.hpp" -#include "servers/CNShardServer.hpp" -#include "Chunking.hpp" -#include +#include "Player.hpp" +#include "Chunking.hpp" +#include "Transport.hpp" +#include "Entities.hpp" + #include #include -struct WarpLocation; - namespace PlayerManager { extern std::map players; void init(); diff --git a/src/PlayerMovement.cpp b/src/PlayerMovement.cpp index b00f15b..c4d5150 100644 --- a/src/PlayerMovement.cpp +++ b/src/PlayerMovement.cpp @@ -1,4 +1,7 @@ #include "PlayerMovement.hpp" + +#include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" #include "TableData.hpp" #include "core/Core.hpp" diff --git a/src/Racing.cpp b/src/Racing.cpp index 565850c..c3b7f6f 100644 --- a/src/Racing.cpp +++ b/src/Racing.cpp @@ -1,10 +1,12 @@ -#include "servers/CNShardServer.hpp" #include "Racing.hpp" + +#include "db/Database.hpp" +#include "servers/CNShardServer.hpp" + +#include "NPCManager.hpp" #include "PlayerManager.hpp" #include "Missions.hpp" #include "Items.hpp" -#include "db/Database.hpp" -#include "NPCManager.hpp" using namespace Racing; diff --git a/src/Racing.hpp b/src/Racing.hpp index f6e6acb..a4f3b6d 100644 --- a/src/Racing.hpp +++ b/src/Racing.hpp @@ -1,8 +1,10 @@ #pragma once -#include +#include "core/Core.hpp" -#include "servers/CNShardServer.hpp" +#include +#include +#include struct EPInfo { int zoneX, zoneY, EPID, maxScore, maxTime; diff --git a/src/Rand.hpp b/src/Rand.hpp index 8f93053..2a73937 100644 --- a/src/Rand.hpp +++ b/src/Rand.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace Rand { extern std::unique_ptr generator; diff --git a/src/TableData.cpp b/src/TableData.cpp index 11e23fd..68b7cce 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -1,18 +1,14 @@ #include "TableData.hpp" + #include "NPCManager.hpp" -#include "Transport.hpp" -#include "Items.hpp" -#include "settings.hpp" #include "Missions.hpp" -#include "Chunking.hpp" -#include "Nanos.hpp" -#include "Racing.hpp" +#include "Items.hpp" #include "Vendors.hpp" +#include "Racing.hpp" +#include "Nanos.hpp" #include "Abilities.hpp" #include "Eggs.hpp" -#include "JSON.hpp" - #include #include #include diff --git a/src/TableData.hpp b/src/TableData.hpp index d8be3fc..5f54071 100644 --- a/src/TableData.hpp +++ b/src/TableData.hpp @@ -1,8 +1,13 @@ #pragma once -#include +#include "JSON.hpp" -#include "NPCManager.hpp" +#include "Entities.hpp" +#include "Transport.hpp" + +#include +#include +#include // these are added to the NPC's static key to avoid collisions const int NPC_ID_OFFSET = 1; diff --git a/src/Trading.cpp b/src/Trading.cpp index 2a258f1..8d70fd9 100644 --- a/src/Trading.cpp +++ b/src/Trading.cpp @@ -1,4 +1,7 @@ #include "Trading.hpp" + +#include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" #include "db/Database.hpp" diff --git a/src/Trading.hpp b/src/Trading.hpp index 108eefb..d7a0398 100644 --- a/src/Trading.hpp +++ b/src/Trading.hpp @@ -1,7 +1,5 @@ #pragma once -#include "Items.hpp" - namespace Trading { void init(); } \ No newline at end of file diff --git a/src/Transport.cpp b/src/Transport.cpp index 0374e00..0c43d9c 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -1,9 +1,12 @@ +#include "Transport.hpp" + #include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" #include "Nanos.hpp" -#include "Transport.hpp" #include "TableData.hpp" -#include "Combat.hpp" +#include "Entities.hpp" +#include "NPCManager.hpp" #include "MobAI.hpp" #include diff --git a/src/Transport.hpp b/src/Transport.hpp index 1efe304..fbacb60 100644 --- a/src/Transport.hpp +++ b/src/Transport.hpp @@ -1,8 +1,11 @@ #pragma once -#include "servers/CNShardServer.hpp" +#include "core/Core.hpp" #include +#include +#include +#include const int SLIDER_SPEED = 1200; const int SLIDER_STOP_TICKS = 16; diff --git a/src/Vendors.cpp b/src/Vendors.cpp index 732cc28..5e2a7a3 100644 --- a/src/Vendors.cpp +++ b/src/Vendors.cpp @@ -1,4 +1,9 @@ #include "Vendors.hpp" + +#include "servers/CNShardServer.hpp" + +#include "PlayerManager.hpp" +#include "Items.hpp" #include "Rand.hpp" // 7 days diff --git a/src/Vendors.hpp b/src/Vendors.hpp index 851791a..8df4c0f 100644 --- a/src/Vendors.hpp +++ b/src/Vendors.hpp @@ -1,10 +1,9 @@ #pragma once #include "core/Core.hpp" -#include "servers/CNShardServer.hpp" -#include "Items.hpp" -#include "PlayerManager.hpp" +#include +#include struct VendorListing { int sort, type, id; diff --git a/src/core/CNStructs.hpp b/src/core/CNStructs.hpp index fbadff2..290f8ca 100644 --- a/src/core/CNStructs.hpp +++ b/src/core/CNStructs.hpp @@ -23,6 +23,7 @@ #include #include #include +#include // yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs. #define INITSTRUCT(T, x) T x; \ diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index 41e4b04..427e7bb 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -1,13 +1,15 @@ #include "servers/CNLoginServer.hpp" + #include "core/CNShared.hpp" #include "db/Database.hpp" -#include "PlayerManager.hpp" -#include "Items.hpp" -#include #include "bcrypt/BCrypt.hpp" +#include "PlayerManager.hpp" +#include "Items.hpp" #include "settings.hpp" +#include + std::map CNLoginServer::loginSessions; CNLoginServer::CNLoginServer(uint16_t p) { diff --git a/src/servers/CNLoginServer.hpp b/src/servers/CNLoginServer.hpp index 6547b44..9b13829 100644 --- a/src/servers/CNLoginServer.hpp +++ b/src/servers/CNLoginServer.hpp @@ -1,6 +1,7 @@ #pragma once #include "core/Core.hpp" + #include "Player.hpp" #include diff --git a/src/servers/CNShardServer.cpp b/src/servers/CNShardServer.cpp index c56657e..c6d93b3 100644 --- a/src/servers/CNShardServer.cpp +++ b/src/servers/CNShardServer.cpp @@ -1,10 +1,12 @@ +#include "servers/CNShardServer.hpp" + #include "core/Core.hpp" +#include "core/CNShared.hpp" #include "db/Database.hpp" #include "servers/Monitor.hpp" -#include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" #include "MobAI.hpp" -#include "core/CNShared.hpp" #include "settings.hpp" #include "TableData.hpp" // for flush() diff --git a/src/servers/CNShardServer.hpp b/src/servers/CNShardServer.hpp index a16f38a..460d2a8 100644 --- a/src/servers/CNShardServer.hpp +++ b/src/servers/CNShardServer.hpp @@ -3,6 +3,7 @@ #include "core/Core.hpp" #include +#include #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); diff --git a/src/servers/Monitor.cpp b/src/servers/Monitor.cpp index 4b7819f..5a28719 100644 --- a/src/servers/Monitor.cpp +++ b/src/servers/Monitor.cpp @@ -1,8 +1,10 @@ +#include "servers/Monitor.hpp" + #include "servers/CNShardServer.hpp" + #include "PlayerManager.hpp" #include "Chat.hpp" #include "Email.hpp" -#include "servers/Monitor.hpp" #include "settings.hpp" #include diff --git a/src/servers/Monitor.hpp b/src/servers/Monitor.hpp index dd40877..3edcf73 100644 --- a/src/servers/Monitor.hpp +++ b/src/servers/Monitor.hpp @@ -2,9 +2,6 @@ #include "core/Core.hpp" -#include -#include - namespace Monitor { SOCKET init(); bool acceptConnection(SOCKET, uint16_t); diff --git a/src/settings.cpp b/src/settings.cpp index 14d8de4..45d2cfc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,9 +1,10 @@ -#include + #include "settings.hpp" + +#include "core/CNStructs.hpp" // so we get the ACADEMY definition #include "INIReader.hpp" -// so we get the ACADEMY definition -#include "core/CNStructs.hpp" +#include // defaults :) int settings::VERBOSITY = 1; diff --git a/src/settings.hpp b/src/settings.hpp index 1ea3a43..097a1cd 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace settings { extern int VERBOSITY; extern bool SANDBOX; diff --git a/vendor/JSON.hpp b/vendor/JSON.hpp index 242f034..1866ad4 100644 --- a/vendor/JSON.hpp +++ b/vendor/JSON.hpp @@ -27,6 +27,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#pragma once + #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ From 98634d5aa2d94f5132f370dff58a2ccf66ac8061 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 16 Jul 2022 23:33:57 -0700 Subject: [PATCH 038/104] New buff framework (player implementation) Get rid of `iConditionBitFlag` in favor of a system of individual buff objects that get composited to a bitflag on-the-fly. Buff objects can have callbacks for application, expiration, and tick, making them pretty flexible. Scripting languages can eventually use these for custom behavior, too. TODO: - Get rid of bitflag in BaseNPC - Apply buffs from passive nano powers - Apply buffs from active nano powers - Move eggs to new system - ??? --- Makefile | 2 + src/Abilities.cpp | 4 +- src/Abilities.hpp | 2 +- src/Buffs.cpp | 30 +++++++++ src/Buffs.hpp | 122 ++++++++++++++++++++++++++++++++++ src/Combat.cpp | 107 ++++++++++++++++++++++++----- src/Eggs.cpp | 2 +- src/Entities.hpp | 14 ++++ src/Items.cpp | 31 ++++----- src/Missions.cpp | 4 +- src/MobAI.cpp | 4 +- src/Nanos.cpp | 14 +--- src/Player.hpp | 12 +++- src/PlayerManager.cpp | 7 +- src/core/Defines.hpp | 7 ++ src/servers/CNShardServer.hpp | 1 + 16 files changed, 309 insertions(+), 54 deletions(-) create mode 100644 src/Buffs.cpp create mode 100644 src/Buffs.hpp diff --git a/Makefile b/Makefile index 843352d..c88e00c 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ CXXSRC=\ src/db/email.cpp\ src/sandbox/seccomp.cpp\ src/sandbox/openbsd.cpp\ + src/Buffs.cpp\ src/Chat.cpp\ src/CustomCommands.cpp\ src/Entities.cpp\ @@ -96,6 +97,7 @@ CXXHDR=\ vendor/JSON.hpp\ vendor/INIReader.hpp\ vendor/JSON.hpp\ + src/Buffs.hpp\ src/Chat.hpp\ src/CustomCommands.hpp\ src/Entities.hpp\ diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 691f486..6b309f9 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -35,13 +35,13 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 return targets; } -void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector targets) { +void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector targets) { for (EntityRef target : targets) { Entity* entity = target.getEntity(); if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB) continue; // not a combatant - + // TODO abilities } } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 834c605..8f964ff 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -44,7 +44,7 @@ namespace Abilities { extern std::map SkillTable; std::vector matchTargets(SkillData*, int, int32_t*); - void applyAbility(SkillData*, EntityRef, std::vector); + void useAbility(SkillData*, EntityRef, std::vector); void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp new file mode 100644 index 0000000..133d2da --- /dev/null +++ b/src/Buffs.cpp @@ -0,0 +1,30 @@ +#include "Buffs.hpp" + +#include "PlayerManager.hpp" + +using namespace Buffs; + +static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { + + if(self.kind != EntityKind::PLAYER) + return; // not implemented + + Player* plr = (Player*)self.getEntity(); + if(plr == nullptr) + return; + + INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); + pkt.eCSTB = buff->id; // eCharStatusTimeBuffID + pkt.eTBU = type; // eTimeBuffUpdate + pkt.eTBT = (buff->buffClass <= BuffClass::CONSUMABLE); // eTimeBuffType 1 means nano + pkt.iConditionBitFlag = plr->getCompositeCondition(); + self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); +} + +void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_ADD); +} + +void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_DEL); +} \ No newline at end of file diff --git a/src/Buffs.hpp b/src/Buffs.hpp new file mode 100644 index 0000000..0b55396 --- /dev/null +++ b/src/Buffs.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "core/Core.hpp" + +#include "Entities.hpp" + +#include +#include +#include + +/* forward declaration(s) */ +struct BuffStack; + +#define CSB_FROM_ECSB(x) (1 << (x - 1)) + +enum class BuffClass { + NANO = 0, + CONSUMABLE, + EGG, + OTHER +}; + +typedef void (*BuffCallback)(EntityRef, BuffStack*); + +struct BuffStack { + int id; // ECSB + int durationTicks; + EntityRef source; + BuffClass buffClass; + + BuffCallback onApply; + BuffCallback onTick; + BuffCallback onExpire; +}; + +class Buff { +private: + EntityRef self; + std::vector stacks; + +public: + void onTick() { + assert(!stacks.empty()); + + std::set callbacks; + auto it = stacks.begin(); + while(it != stacks.end()) { + BuffStack& stack = *it; + if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { + // unique callback + stack.onTick(self, &stack); + callbacks.insert(stack.onTick); + } + + if(stack.durationTicks > 0) stack.durationTicks--; + if(stack.durationTicks == 0) { + it = stacks.erase(it); + stack.onExpire(self, &stack); + } else it++; + } + } + + void onExpire() { + assert(!stacks.empty()); + + std::set callbacks; + while(!stacks.empty()) { + BuffStack stack = stacks.back(); + stacks.pop_back(); + if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { + // execute unique callback + callbacks.insert(stack.onExpire); + stack.onExpire(self, &stack); + } + } + } + + void addStack(BuffStack* stack) { + stacks.push_back(*stack); + } + + /* + * Why do this madness? Let me tell you why. + * We need to be able to distinguish whether a player's + * buff is from something like an egg vs. their nano, + * because there are cases where the behavior is different. + * Now, this is impossible to do when they all get composited + * to a single bitfield. So we use a "buff class" and pick + * the buff stack that is most "dominant". + */ + BuffStack* getDominantBuff() { + assert(!stacks.empty()); + + BuffStack* dominant = nullptr; + for(BuffStack& stack : stacks) { + if(stack.buffClass > dominant->buffClass) + dominant = &stack; + } + return dominant; + } + + /* + * In general, a Buff object won't exist + * unless it has stacks. However, when + * popping stacks during iteration (onExpire), + * stacks will be empty for a brief moment + * when the last stack is popped. + */ + bool isStale() { + return stacks.empty(); + } + + Buff(EntityRef pSelf, BuffStack* firstStack) + : self(pSelf) { + addStack(firstStack); + } +}; + +namespace Buffs { + void timeBuffUpdateAdd(EntityRef self, BuffStack* buff); + void timeBuffUpdateDelete(EntityRef self, BuffStack* buff); +} diff --git a/src/Combat.cpp b/src/Combat.cpp index 94e3c86..4631d9e 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -8,14 +8,53 @@ #include "NPCManager.hpp" #include "Nanos.hpp" #include "Abilities.hpp" +#include "Buffs.hpp" #include +#include using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; +void Player::addBuff(BuffStack* buff) { + EntityRef self = PlayerManager::getSockFromID(iID); + if(!hasBuff(buff->id)) { + buffs[buff->id] = new Buff(self, buff); + } + else buffs[buff->id]->addStack(buff); + buff->onApply(self, buff); +} + +BuffStack* Player::getBuff(int buffId) { + if(hasBuff(buffId)) { + return buffs[buffId]->getDominantBuff(); + } + return nullptr; +} + +void Player::removeBuff(int buffId) { + if(hasBuff(buffId)) { + buffs[buffId]->onExpire(); + delete buffs[buffId]; + buffs.erase(buffId); + } +} + +bool Player::hasBuff(int buffId) { + return buffs.find(buffId) != buffs.end(); +} + +int Player::getCompositeCondition() { + int conditionBitFlag = 0; + for(auto buffEntry : buffs) { + if(!buffEntry.second->isStale()) + conditionBitFlag |= CSB_FROM_ECSB(buffEntry.first); + } + return conditionBitFlag; +} + int Player::takeDamage(EntityRef src, int amt) { HP -= amt; return amt; @@ -41,6 +80,22 @@ void Player::step(time_t currTime) { // no-op } +void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ } + +BuffStack* CombatNPC::getBuff(int buffId) { /* stubbed */ + return nullptr; +} + +void CombatNPC::removeBuff(int buffId) { /* stubbed */ } + +bool CombatNPC::hasBuff(int buffId) { /* stubbed */ + return false; +} + +int CombatNPC::getCompositeCondition() { /* stubbed */ + return 0; +} + int CombatNPC::takeDamage(EntityRef src, int amt) { hp -= amt; @@ -303,17 +358,22 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf; Player *plr = PlayerManager::getPlayer(sock); - if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag) - plr->iConditionBitFlag ^= CSB_BIT_INFECTION; - - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1); - - pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID - pkt1.eTBU = 1; // eTimeBuffUpdate - pkt1.eTBT = 0; // eTimeBuffType 1 means nano - pkt1.iConditionBitFlag = plr->iConditionBitFlag; - - sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); + // infection debuff toggles as the client asks it to, + // so we add and remove a permanent debuff + if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) { + BuffStack infection = { + ECSB_INFECTION, + -1, // infinite + sock, // self-inflicted + BuffClass::OTHER, + Buffs::timeBuffUpdateAdd, + nullptr, // client ticks for us! todo anticheat lol + Buffs::timeBuffUpdateDelete + }; + plr->addBuff(&infection); + } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { + plr->removeBuff(ECSB_INFECTION); + } } static void dealGooDamage(CNSocket *sock, int amount) { @@ -327,12 +387,13 @@ static void dealGooDamage(CNSocket *sock, int amount) { sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) { + BuffStack* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION); + if (protectionBuff != nullptr) { amount = -2; // -2 is the magic number for "Protected" to appear as the damage number dmg->bProtected = 1; // eggs allow protection without nanos - if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION)) + if (protectionBuff->buffClass == BuffClass::NANO && plr->activeNano != -1) plr->Nanos[plr->activeNano].iStamina -= 3; } else { plr->HP -= amount; @@ -356,7 +417,7 @@ static void dealGooDamage(CNSocket *sock, int amount) { dmg->iID = plr->iID; dmg->iDamage = amount; dmg->iHP = plr->HP; - dmg->iConditionBitFlag = plr->iConditionBitFlag; + dmg->iConditionBitFlag = plr->getCompositeCondition(); sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); @@ -664,7 +725,7 @@ static void playerTick(CNServer *serv, time_t currTime) { continue; // fm patch/lake damage - if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) + if ((plr->hasBuff(ECSB_INFECTION)) && !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20); @@ -692,7 +753,7 @@ static void playerTick(CNServer *serv, time_t currTime) { drainRate = skill->batteryUse[boost * 3]; if (skill->drainType == SkillDrainType::PASSIVE) { - // passive buff + // apply passive buff std::vector targets; if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr) targets = plr->group->members; // group @@ -700,6 +761,7 @@ static void playerTick(CNServer *serv, time_t currTime) { targets.push_back(sock); // self std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl; + // TODO abilities } } @@ -732,6 +794,17 @@ static void playerTick(CNServer *serv, time_t currTime) { PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD)); } + // process buffsets + auto it = plr->buffs.begin(); + while(it != plr->buffs.end()) { + int buffId = (*it).first; + Buff* buff = (*it).second; + buff->onTick(); + if(buff->isStale()) it = plr->buffs.erase(it); + else it++; + } + // + if (transmit) { INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt); @@ -752,7 +825,7 @@ static void playerTick(CNServer *serv, time_t currTime) { } void Combat::init() { - REGISTER_SHARD_TIMER(playerTick, 2000); + REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index cddf6b4..ee13444 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -64,7 +64,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; - skill->iConditionBitFlag = plr->iConditionBitFlag; + skill->iConditionBitFlag = plr->getCompositeCondition(); } skillUse->iNPC_ID = eggId; diff --git a/src/Entities.hpp b/src/Entities.hpp index 1b02f0b..30e7163 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -6,6 +6,10 @@ #include #include +/* forward declaration(s) */ +class Buff; +struct BuffStack; + enum EntityKind { INVALID, PLAYER, @@ -88,6 +92,11 @@ public: ICombatant() {} virtual ~ICombatant() {} + virtual void addBuff(BuffStack* buff) = 0; + virtual BuffStack* getBuff(int buffId) = 0; + virtual void removeBuff(int buffId) = 0; + virtual bool hasBuff(int buffId) = 0; + virtual int getCompositeCondition() = 0; virtual int takeDamage(EntityRef, int) = 0; virtual void heal(EntityRef, int) = 0; virtual bool isAlive() = 0; @@ -149,6 +158,11 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } + virtual void addBuff(BuffStack* buff) override; + virtual BuffStack* getBuff(int buffId) override; + virtual void removeBuff(int buffId) override; + virtual bool hasBuff(int buffId) override; + virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; diff --git a/src/Items.cpp b/src/Items.cpp index fc993e2..4eec2af 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -9,6 +9,7 @@ #include "Eggs.hpp" #include "MobAI.hpp" #include "Missions.hpp" +#include "Buffs.hpp" #include // for memset() #include @@ -484,27 +485,27 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { resp->eST = EST_NANOSTIMPAK; resp->iSkillID = 144; - int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot; - int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot; + int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot; respdata->eCT = 1; respdata->iID = player->iID; - respdata->iConditionBitFlag = value1; + respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB); - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = value2; // eCharStatusTimeBuffID - pkt.eTBU = 1; // eTimeBuffUpdate - pkt.eTBT = 1; // eTimeBuffType 1 means nano - pkt.iConditionBitFlag = player->iConditionBitFlag |= value1; - sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); + int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; + BuffStack gumballBuff = { + eCSB, + durationMilliseconds / MS_PER_PLAYER_TICK, + sock, + BuffClass::CONSUMABLE, + Buffs::timeBuffUpdateAdd, + nullptr, + Buffs::timeBuffUpdateDelete, + }; + player->addBuff(&gumballBuff); sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); // update inventory serverside player->Inven[resp->iSlotNum] = resp->RemainItem; - - std::pair key = std::make_pair(sock, value1); - time_t until = getTime() + (time_t)Abilities::SkillTable[144].durationTime[0] * 100; - Eggs::EggBuffs[key] = until; } static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { @@ -757,7 +758,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) { plr->money += miscDropType.taroAmount; // money nano boost - if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { + if (plr->hasBuff(ECSB_REWARD_CASH)) { int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; @@ -772,7 +773,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo if (levelDifference > 0) fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; // scavenger nano boost - if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { + if (plr->hasBuff(ECSB_REWARD_BLOB)) { int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; diff --git a/src/Missions.cpp b/src/Missions.cpp index db0d17c..b13699a 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -163,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { // update player plr->money += reward->money; - if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros + if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; plr->money += reward->money * (5 + boost) / 25; } - if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm + if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 7313b85..26552f1 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -187,7 +187,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { int mobRange = mob->sightRange; - if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH + if (plr->hasBuff(ECSB_UP_STEALTH) || Racing::EPRaces.find(s) != Racing::EPRaces.end()) mobRange /= 3; @@ -315,7 +315,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i plr->HP -= respdata[i].iDamage; respdata[i].iHP = plr->HP; - respdata[i].iConditionBitFlag = plr->iConditionBitFlag; + respdata[i].iConditionBitFlag = plr->getCompositeCondition(); if (plr->HP <= 0) { if (!MobAI::aggroCheck(mob, getTime())) diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 088d9cb..551da96 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -184,7 +184,7 @@ int Nanos::nanoStyle(int nanoID) { bool Nanos::getNanoBoost(Player* plr) { for (int i = 0; i < 3; i++) if (plr->equippedNanos[i] == plr->activeNano) - if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i)) + if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i)) return true; return false; } @@ -209,16 +209,8 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID; // Unbuff gumballs - int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum; - if (plr->iConditionBitFlag & value1) { - int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum; - INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = value2; // eCharStatusTimeBuffID - pkt.eTBU = 2; // eTimeBuffUpdate - pkt.eTBT = 1; // eTimeBuffType 1 means nano - pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1; - sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); - } + int buffId = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum; + plr->removeBuff(buffId); // unsummon nano if replaced if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) diff --git a/src/Player.hpp b/src/Player.hpp index c58b7d6..b2cd8ef 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -7,6 +7,10 @@ #include +/* forward declaration(s) */ +class Buff; +struct BuffStack; + #define ACTIVE_MISSION_COUNT 6 #define PC_MAXHEALTH(level) (925 + 75 * (level)) @@ -32,9 +36,8 @@ struct Player : public Entity, public ICombatant { int8_t iPCState = 0; int32_t iWarpLocationFlag = 0; int64_t aSkywayLocationFlag[2] = {}; - int32_t iConditionBitFlag = 0; - int32_t iSelfConditionBitFlag = 0; int8_t iSpecialState = 0; + std::unordered_map buffs = {}; int angle = 0; int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0; @@ -86,6 +89,11 @@ struct Player : public Entity, public ICombatant { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + virtual void addBuff(BuffStack* buff) override; + virtual BuffStack* getBuff(int buffId) override; + virtual void removeBuff(int buffId) override; + virtual bool hasBuff(int buffId) override; + virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 91d4c69..2e35846 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -36,6 +36,11 @@ void PlayerManager::removePlayer(CNSocket* key) { Player* plr = getPlayer(key); uint64_t fromInstance = plr->instanceID; + // free buff memory + for(auto buffEntry : plr->buffs) + delete buffEntry.second; + + // leave group if(plr->group != nullptr) Groups::groupKick(plr); @@ -403,7 +408,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { switch ((ePCRegenType)reviveData->iRegenType) { case ePCRegenType::HereByPhoenix: // nano revive - if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX)) + if (!(plr->hasBuff(ECSB_PHOENIX))) return; // sanity check plr->Nanos[plr->activeNano].iStamina = 0; // TODO ABILITIES diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index 185b5c5..e07bb00 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -118,6 +118,13 @@ enum { ECSTB__END = 26, }; +enum { + ETBU_NONE = 0, + ETBU_ADD = 1, + ETBU_DEL = 2, + ETBU_CHANGE = 3 +}; + enum { SUCC = 1, FAIL = 0, diff --git a/src/servers/CNShardServer.hpp b/src/servers/CNShardServer.hpp index 460d2a8..8ab7360 100644 --- a/src/servers/CNShardServer.hpp +++ b/src/servers/CNShardServer.hpp @@ -7,6 +7,7 @@ #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); +#define MS_PER_PLAYER_TICK 2000 class CNShardServer : public CNServer { private: From 704d6a2452ca939dabc9110e9b0e5b41059c4733 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 17 Jul 2022 18:39:25 -0700 Subject: [PATCH 039/104] Move `Buff` implementation to Buffs.cpp --- src/Buffs.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/Buffs.hpp | 59 +++++---------------------------------------------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 133d2da..69ae3d8 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -2,8 +2,66 @@ #include "PlayerManager.hpp" +#include +#include + using namespace Buffs; +void Buff::onTick() { + assert(!stacks.empty()); + + std::set callbacks; + auto it = stacks.begin(); + while(it != stacks.end()) { + BuffStack& stack = *it; + if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { + // unique callback + stack.onTick(self, &stack); + callbacks.insert(stack.onTick); + } + + if(stack.durationTicks > 0) stack.durationTicks--; + if(stack.durationTicks == 0) { + it = stacks.erase(it); + stack.onExpire(self, &stack); + } else it++; + } +} + +void Buff::onExpire() { + assert(!stacks.empty()); + + std::set callbacks; + while(!stacks.empty()) { + BuffStack stack = stacks.back(); + stacks.pop_back(); + if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { + // execute unique callback + callbacks.insert(stack.onExpire); + stack.onExpire(self, &stack); + } + } +} + +void Buff::addStack(BuffStack* stack) { + stacks.push_back(*stack); +} + +BuffStack* Buff::getDominantBuff() { + assert(!stacks.empty()); + + BuffStack* dominant = nullptr; + for(BuffStack& stack : stacks) { + if(stack.buffClass > dominant->buffClass) + dominant = &stack; + } + return dominant; +} + +bool Buff::isStale() { + return stacks.empty(); +} + static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { if(self.kind != EntityKind::PLAYER) diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 0b55396..86953f0 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -4,9 +4,7 @@ #include "Entities.hpp" -#include #include -#include /* forward declaration(s) */ struct BuffStack; @@ -39,45 +37,9 @@ private: std::vector stacks; public: - void onTick() { - assert(!stacks.empty()); - - std::set callbacks; - auto it = stacks.begin(); - while(it != stacks.end()) { - BuffStack& stack = *it; - if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { - // unique callback - stack.onTick(self, &stack); - callbacks.insert(stack.onTick); - } - - if(stack.durationTicks > 0) stack.durationTicks--; - if(stack.durationTicks == 0) { - it = stacks.erase(it); - stack.onExpire(self, &stack); - } else it++; - } - } - - void onExpire() { - assert(!stacks.empty()); - - std::set callbacks; - while(!stacks.empty()) { - BuffStack stack = stacks.back(); - stacks.pop_back(); - if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { - // execute unique callback - callbacks.insert(stack.onExpire); - stack.onExpire(self, &stack); - } - } - } - - void addStack(BuffStack* stack) { - stacks.push_back(*stack); - } + void onTick(); + void onExpire(); + void addStack(BuffStack* stack); /* * Why do this madness? Let me tell you why. @@ -88,16 +50,7 @@ public: * 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; - } + BuffStack* getDominantBuff(); /* * In general, a Buff object won't exist @@ -106,9 +59,7 @@ public: * stacks will be empty for a brief moment * when the last stack is popped. */ - bool isStale() { - return stacks.empty(); - } + bool isStale(); Buff(EntityRef pSelf, BuffStack* firstStack) : self(pSelf) { From ded746267741c2935b5baed28c6a51a0b47697fd Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 17 Jul 2022 20:47:54 -0700 Subject: [PATCH 040/104] egg prep --- src/Buffs.cpp | 24 ++++++++++++++++++-- src/Buffs.hpp | 12 ++++++---- src/Combat.cpp | 2 +- src/Eggs.cpp | 52 +------------------------------------------ src/Eggs.hpp | 1 - src/Groups.cpp | 18 --------------- src/PlayerManager.cpp | 6 ++--- src/core/Defines.hpp | 16 ++++++++++++- 8 files changed, 49 insertions(+), 82 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 69ae3d8..115e3a5 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -74,15 +74,35 @@ static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { 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.eTBT = (int)buff->buffClass; pkt.iConditionBitFlag = plr->getCompositeCondition(); self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } +static void timeBuffTimeoutViewable(EntityRef self) { + if(self.kind != EntityKind::PLAYER) + return; // not implemented + + Player* plr = (Player*)self.getEntity(); + if(plr == nullptr) + return; + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players + pkt.eCT = 1; + pkt.iID = plr->iID; + pkt.iConditionBitFlag = plr->getCompositeCondition(); + PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); +} + void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { timeBuffUpdate(self, buff, ETBU_ADD); } void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { timeBuffUpdate(self, buff, ETBU_DEL); -} \ No newline at end of file +} + +void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_DEL); + timeBuffTimeoutViewable(self); +} diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 86953f0..03e48df 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -12,10 +12,13 @@ struct BuffStack; #define CSB_FROM_ECSB(x) (1 << (x - 1)) enum class BuffClass { - NANO = 0, - CONSUMABLE, - EGG, - OTHER + NONE = ETBT_NONE, + NANO = ETBT_NANO, + GROUP_NANO = ETBT_GROUPNANO, + EGG = ETBT_SHINY, + ENVIRONMENT = ETBT_LANDEFFECT, + ITEM = ETBT_ITEM, + CASH_ITEM = ETBT_CASHITEM }; typedef void (*BuffCallback)(EntityRef, BuffStack*); @@ -70,4 +73,5 @@ public: namespace Buffs { void timeBuffUpdateAdd(EntityRef self, BuffStack* buff); void timeBuffUpdateDelete(EntityRef self, BuffStack* buff); + void timeBuffTimeout(EntityRef self, BuffStack* buff); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 4631d9e..c2889ed 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -365,7 +365,7 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { ECSB_INFECTION, -1, // infinite sock, // self-inflicted - BuffClass::OTHER, + BuffClass::ENVIRONMENT, Buffs::timeBuffUpdateAdd, nullptr, // client ticks for us! todo anticheat lol Buffs::timeBuffUpdateDelete diff --git a/src/Eggs.cpp b/src/Eggs.cpp index ee13444..5e81c25 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -13,16 +13,12 @@ using namespace Eggs; -/// sock, CBFlag -> until -std::map, time_t> Eggs::EggBuffs; std::unordered_map Eggs::EggTypes; int 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); size_t resplen; @@ -75,56 +71,10 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); - if (CBFlag == 0) - return -1; - - std::pair 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; } 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 +84,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; diff --git a/src/Eggs.hpp b/src/Eggs.hpp index eeea678..e1460de 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -10,7 +10,6 @@ struct EggType { }; namespace Eggs { - extern std::map, time_t> EggBuffs; extern std::unordered_map EggTypes; void init(); diff --git a/src/Groups.cpp b/src/Groups.cpp index 5655b54..5dda07c 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -149,7 +149,6 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { 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); @@ -173,14 +172,6 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { 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); @@ -294,8 +285,6 @@ void Groups::groupKick(Player* plr) { 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) @@ -326,13 +315,6 @@ void Groups::groupKick(Player* plr) { 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); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 2e35846..a3b350c 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -475,10 +475,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]; diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index e07bb00..4e20658 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -122,7 +122,21 @@ enum { ETBU_NONE = 0, ETBU_ADD = 1, ETBU_DEL = 2, - ETBU_CHANGE = 3 + 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 { From 34669eb7b9633329785e386053de6ca98c695dfa Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 17 Jul 2022 20:49:10 -0700 Subject: [PATCH 041/104] CRLF purge in Buffs.cpp --- src/Buffs.cpp | 216 +++++++++++++++++++++++++------------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 115e3a5..430fab9 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -1,108 +1,108 @@ -#include "Buffs.hpp" - -#include "PlayerManager.hpp" - -#include -#include - -using namespace Buffs; - -void Buff::onTick() { - assert(!stacks.empty()); - - std::set callbacks; - auto it = stacks.begin(); - while(it != stacks.end()) { - BuffStack& stack = *it; - if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { - // unique callback - stack.onTick(self, &stack); - callbacks.insert(stack.onTick); - } - - if(stack.durationTicks > 0) stack.durationTicks--; - if(stack.durationTicks == 0) { - it = stacks.erase(it); - stack.onExpire(self, &stack); - } else it++; - } -} - -void Buff::onExpire() { - assert(!stacks.empty()); - - std::set callbacks; - while(!stacks.empty()) { - BuffStack stack = stacks.back(); - stacks.pop_back(); - if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { - // execute unique callback - callbacks.insert(stack.onExpire); - stack.onExpire(self, &stack); - } - } -} - -void Buff::addStack(BuffStack* stack) { - stacks.push_back(*stack); -} - -BuffStack* Buff::getDominantBuff() { - assert(!stacks.empty()); - - BuffStack* dominant = nullptr; - for(BuffStack& stack : stacks) { - if(stack.buffClass > dominant->buffClass) - dominant = &stack; - } - return dominant; -} - -bool Buff::isStale() { - return stacks.empty(); -} - -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 = (int)buff->buffClass; - pkt.iConditionBitFlag = plr->getCompositeCondition(); - self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); -} - -static void timeBuffTimeoutViewable(EntityRef self) { - if(self.kind != EntityKind::PLAYER) - return; // not implemented - - Player* plr = (Player*)self.getEntity(); - if(plr == nullptr) - return; - - INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players - pkt.eCT = 1; - pkt.iID = plr->iID; - pkt.iConditionBitFlag = plr->getCompositeCondition(); - PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); -} - -void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_ADD); -} - -void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_DEL); -} - -void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_DEL); - timeBuffTimeoutViewable(self); -} +#include "Buffs.hpp" + +#include "PlayerManager.hpp" + +#include +#include + +using namespace Buffs; + +void Buff::onTick() { + assert(!stacks.empty()); + + std::set callbacks; + auto it = stacks.begin(); + while(it != stacks.end()) { + BuffStack& stack = *it; + if(stack.onTick != nullptr && callbacks.count(stack.onTick) == 0) { + // unique callback + stack.onTick(self, &stack); + callbacks.insert(stack.onTick); + } + + if(stack.durationTicks > 0) stack.durationTicks--; + if(stack.durationTicks == 0) { + it = stacks.erase(it); + stack.onExpire(self, &stack); + } else it++; + } +} + +void Buff::onExpire() { + assert(!stacks.empty()); + + std::set callbacks; + while(!stacks.empty()) { + BuffStack stack = stacks.back(); + stacks.pop_back(); + if(stack.onExpire != nullptr && callbacks.count(stack.onExpire) == 0) { + // execute unique callback + callbacks.insert(stack.onExpire); + stack.onExpire(self, &stack); + } + } +} + +void Buff::addStack(BuffStack* stack) { + stacks.push_back(*stack); +} + +BuffStack* Buff::getDominantBuff() { + assert(!stacks.empty()); + + BuffStack* dominant = nullptr; + for(BuffStack& stack : stacks) { + if(stack.buffClass > dominant->buffClass) + dominant = &stack; + } + return dominant; +} + +bool Buff::isStale() { + return stacks.empty(); +} + +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 = (int)buff->buffClass; + pkt.iConditionBitFlag = plr->getCompositeCondition(); + self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); +} + +static void timeBuffTimeoutViewable(EntityRef self) { + if(self.kind != EntityKind::PLAYER) + return; // not implemented + + Player* plr = (Player*)self.getEntity(); + if(plr == nullptr) + return; + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players + pkt.eCT = 1; + pkt.iID = plr->iID; + pkt.iConditionBitFlag = plr->getCompositeCondition(); + PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); +} + +void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_ADD); +} + +void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_DEL); +} + +void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { + timeBuffUpdate(self, buff, ETBU_DEL); + timeBuffTimeoutViewable(self); +} From 15b6cd2fb4815e4dce662cfbdb18a66af189699e Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 17 Jul 2022 21:10:56 -0700 Subject: [PATCH 042/104] oops --- src/Buffs.cpp | 2 +- src/Items.cpp | 2 +- src/PlayerManager.cpp | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 430fab9..2369ddd 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -23,7 +23,7 @@ void Buff::onTick() { if(stack.durationTicks > 0) stack.durationTicks--; if(stack.durationTicks == 0) { it = stacks.erase(it); - stack.onExpire(self, &stack); + if(stack.onExpire != nullptr) stack.onExpire(self, &stack); } else it++; } } diff --git a/src/Items.cpp b/src/Items.cpp index 4eec2af..d2778f1 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -496,7 +496,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { eCSB, durationMilliseconds / MS_PER_PLAYER_TICK, sock, - BuffClass::CONSUMABLE, + BuffClass::CASH_ITEM, // or BuffClass::ITEM? Buffs::timeBuffUpdateAdd, nullptr, Buffs::timeBuffUpdateDelete, diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index a3b350c..8419a23 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -66,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; } From afc48b7676c65fa655b82458a0528882a32a3440 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 18 Jul 2022 08:53:53 -0700 Subject: [PATCH 043/104] Rework buff callbacks The first implementation was way too complicated and prone to bugs. This is much more simple flexible; first off, std::function is now used instead of a raw function pointer, so lambdas and binds are fair game which is great for scripting. Second, callbacks for all stacks are executed. It is up to the callback target to ensure correct behavior. --- src/Buffs.cpp | 77 +++++++++++++++++++++--------------------------- src/Buffs.hpp | 30 ++++++++++--------- src/Combat.cpp | 46 +++++++++++++++++------------ src/Entities.hpp | 4 +-- src/Items.cpp | 11 +++++-- src/Player.hpp | 2 +- 6 files changed, 88 insertions(+), 82 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 2369ddd..30beb48 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -2,67 +2,58 @@ #include "PlayerManager.hpp" -#include -#include - using namespace Buffs; -void Buff::onTick() { - assert(!stacks.empty()); - - std::set callbacks; +void Buff::tick() { 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.onTick) stack.onTick(self, &stack); if(stack.durationTicks > 0) stack.durationTicks--; if(stack.durationTicks == 0) { it = stacks.erase(it); - if(stack.onExpire != nullptr) stack.onExpire(self, &stack); + if(stack.onExpire) stack.onExpire(self, &stack); } else it++; } } -void Buff::onExpire() { - assert(!stacks.empty()); - - std::set callbacks; +void Buff::clear() { 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); - } + if(stack.onExpire) stack.onExpire(self, &stack); } } void Buff::addStack(BuffStack* stack) { + if(stack->onApply) stack->onApply(self, stack); stacks.push_back(*stack); } -BuffStack* Buff::getDominantBuff() { - assert(!stacks.empty()); - - BuffStack* dominant = nullptr; +bool Buff::hasClass(BuffClass buffClass) { for(BuffStack& stack : stacks) { - if(stack.buffClass > dominant->buffClass) - dominant = &stack; + if(stack.buffClass == buffClass) + return true; } - return dominant; + return false; +} + +BuffClass Buff::maxClass() { + BuffClass buffClass = BuffClass::NONE; + for(BuffStack& stack : stacks) { + if(stack.buffClass > buffClass) + buffClass = stack.buffClass; + } + return buffClass; } bool Buff::isStale() { return stacks.empty(); } -static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { +#pragma region Handlers +void Buffs::timeBuffUpdate(EntityRef self, BuffStack* buff, int status) { if(self.kind != EntityKind::PLAYER) return; // not implemented @@ -70,16 +61,22 @@ static void timeBuffUpdate(EntityRef self, BuffStack* buff, int type) { Player* plr = (Player*)self.getEntity(); if(plr == nullptr) return; + + if(status == ETBU_DEL && plr->hasBuff(buff->id)) + return; // no premature status removal! + + int cbf = plr->getCompositeCondition(); + if(status == ETBU_ADD) cbf |= CSB_FROM_ECSB(buff->id); INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); pkt.eCSTB = buff->id; // eCharStatusTimeBuffID - pkt.eTBU = type; // eTimeBuffUpdate + pkt.eTBU = status; // eTimeBuffUpdate pkt.eTBT = (int)buff->buffClass; - pkt.iConditionBitFlag = plr->getCompositeCondition(); + pkt.iConditionBitFlag = cbf; self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } -static void timeBuffTimeoutViewable(EntityRef self) { +void Buffs::timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct) { if(self.kind != EntityKind::PLAYER) return; // not implemented @@ -88,21 +85,15 @@ static void timeBuffTimeoutViewable(EntityRef self) { return; INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players - pkt.eCT = 1; + pkt.eCT = ct; // 1 for eggs, at least pkt.iID = plr->iID; pkt.iConditionBitFlag = plr->getCompositeCondition(); PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); } -void Buffs::timeBuffUpdateAdd(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_ADD); -} - -void Buffs::timeBuffUpdateDelete(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_DEL); -} - +/* MOVE TO EGG LAMBDA void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { timeBuffUpdate(self, buff, ETBU_DEL); timeBuffTimeoutViewable(self); -} +} */ +#pragma endregion diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 03e48df..55f33dd 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -5,6 +5,7 @@ #include "Entities.hpp" #include +#include /* forward declaration(s) */ struct BuffStack; @@ -21,7 +22,7 @@ enum class BuffClass { CASH_ITEM = ETBT_CASHITEM }; -typedef void (*BuffCallback)(EntityRef, BuffStack*); +typedef std::function BuffCallback; struct BuffStack { int id; // ECSB @@ -29,8 +30,13 @@ struct BuffStack { EntityRef source; BuffClass buffClass; + /* called just before the stack is added */ BuffCallback onApply; + + /* called when the stack is ticked */ BuffCallback onTick; + + /* called just after the stack is removed */ BuffCallback onExpire; }; @@ -40,20 +46,17 @@ private: std::vector stacks; public: - void onTick(); - void onExpire(); + void tick(); + void clear(); void addStack(BuffStack* 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". + * 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 */ - BuffStack* getDominantBuff(); + bool hasClass(BuffClass buffClass); + BuffClass maxClass(); /* * In general, a Buff object won't exist @@ -71,7 +74,6 @@ public: }; namespace Buffs { - void timeBuffUpdateAdd(EntityRef self, BuffStack* buff); - void timeBuffUpdateDelete(EntityRef self, BuffStack* buff); - void timeBuffTimeout(EntityRef self, BuffStack* buff); + void timeBuffUpdate(EntityRef self, BuffStack* buff, int status); + void timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct); } diff --git a/src/Combat.cpp b/src/Combat.cpp index c2889ed..7dda8e3 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace Combat; @@ -20,37 +21,35 @@ std::map> Combat::Bullets; void Player::addBuff(BuffStack* buff) { EntityRef self = PlayerManager::getSockFromID(iID); - if(!hasBuff(buff->id)) { - buffs[buff->id] = new Buff(self, buff); - } + 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) { +Buff* Player::getBuff(int buffId) { if(hasBuff(buffId)) { - return buffs[buffId]->getDominantBuff(); + return buffs[buffId]; } return nullptr; } void Player::removeBuff(int buffId) { if(hasBuff(buffId)) { - buffs[buffId]->onExpire(); + buffs[buffId]->clear(); delete buffs[buffId]; buffs.erase(buffId); } } bool Player::hasBuff(int buffId) { - return buffs.find(buffId) != buffs.end(); + auto buff = buffs.find(buffId); + return buff != buffs.end() && !buff->second->isStale(); } int Player::getCompositeCondition() { int conditionBitFlag = 0; - for(auto buffEntry : buffs) { - if(!buffEntry.second->isStale()) - conditionBitFlag |= CSB_FROM_ECSB(buffEntry.first); + for(auto buff : buffs) { + if(!buff.second->isStale()) + conditionBitFlag |= CSB_FROM_ECSB(buff.first); } return conditionBitFlag; } @@ -82,7 +81,7 @@ void Player::step(time_t currTime) { void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ } -BuffStack* CombatNPC::getBuff(int buffId) { /* stubbed */ +Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ return nullptr; } @@ -366,9 +365,14 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { -1, // infinite sock, // self-inflicted BuffClass::ENVIRONMENT, - Buffs::timeBuffUpdateAdd, - nullptr, // client ticks for us! todo anticheat lol - Buffs::timeBuffUpdateDelete + + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_ADD); + }, + nullptr, // client toggles for us! todo anticheat lol + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_DEL); + }, }; plr->addBuff(&infection); } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { @@ -387,13 +391,13 @@ static void dealGooDamage(CNSocket *sock, int amount) { sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - BuffStack* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION); + 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 (protectionBuff->buffClass == BuffClass::NANO && plr->activeNano != -1) + if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1) plr->Nanos[plr->activeNano].iStamina -= 3; } else { plr->HP -= amount; @@ -799,8 +803,12 @@ static void playerTick(CNServer *serv, time_t currTime) { while(it != plr->buffs.end()) { int buffId = (*it).first; Buff* buff = (*it).second; - buff->onTick(); - if(buff->isStale()) it = plr->buffs.erase(it); + buff->tick(); + if(buff->isStale()) { + // garbage collect + it = plr->buffs.erase(it); + delete buff; + } else it++; } // diff --git a/src/Entities.hpp b/src/Entities.hpp index 30e7163..47249d3 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -93,7 +93,7 @@ public: virtual ~ICombatant() {} virtual void addBuff(BuffStack* buff) = 0; - virtual BuffStack* getBuff(int buffId) = 0; + virtual Buff* getBuff(int buffId) = 0; virtual void removeBuff(int buffId) = 0; virtual bool hasBuff(int buffId) = 0; virtual int getCompositeCondition() = 0; @@ -159,7 +159,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } virtual void addBuff(BuffStack* buff) override; - virtual BuffStack* getBuff(int buffId) override; + virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; diff --git a/src/Items.cpp b/src/Items.cpp index d2778f1..4408651 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -497,9 +497,14 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { durationMilliseconds / MS_PER_PLAYER_TICK, sock, BuffClass::CASH_ITEM, // or BuffClass::ITEM? - Buffs::timeBuffUpdateAdd, - nullptr, - Buffs::timeBuffUpdateDelete, + + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_ADD); + }, + nullptr, // client toggles for us! todo anticheat lol + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_DEL); + }, }; player->addBuff(&gumballBuff); diff --git a/src/Player.hpp b/src/Player.hpp index b2cd8ef..b6c05bd 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -90,7 +90,7 @@ struct Player : public Entity, public ICombatant { virtual void disappearFromViewOf(CNSocket *sock) override; virtual void addBuff(BuffStack* buff) override; - virtual BuffStack* getBuff(int buffId) override; + virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; From a07f36e379e7f0f083b61b1c792bf138673a4d2b Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 19 Jul 2022 01:09:25 -0700 Subject: [PATCH 044/104] Buff framework tweaks + polish --- src/Abilities.hpp | 3 ++- src/Buffs.cpp | 19 ++++++++++--------- src/Buffs.hpp | 15 +++++++++------ src/Combat.cpp | 15 +++++++-------- src/Entities.hpp | 4 ++-- src/Items.cpp | 7 +++---- src/Player.hpp | 2 +- src/TableData.cpp | 3 ++- 8 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 8f964ff..f376a7e 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -26,12 +26,13 @@ enum class SkillDrainType { }; struct SkillData { - int skillType; + int skillType; // eST SkillEffectTarget effectTarget; int effectType; // always 1? SkillTargetType targetType; SkillDrainType drainType; int effectArea; + int charStatusTimeBuffId; // eCS(T)B int batteryUse[4]; int durationTime[4]; diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 30beb48..d790566 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -27,13 +27,14 @@ void Buff::clear() { } void Buff::addStack(BuffStack* stack) { + stack->buff = this; if(stack->onApply) stack->onApply(self, stack); stacks.push_back(*stack); } bool Buff::hasClass(BuffClass buffClass) { for(BuffStack& stack : stacks) { - if(stack.buffClass == buffClass) + if(stack.buffStackClass == buffClass) return true; } return false; @@ -42,8 +43,8 @@ bool Buff::hasClass(BuffClass buffClass) { BuffClass Buff::maxClass() { BuffClass buffClass = BuffClass::NONE; for(BuffStack& stack : stacks) { - if(stack.buffClass > buffClass) - buffClass = stack.buffClass; + if(stack.buffStackClass > buffClass) + buffClass = stack.buffStackClass; } return buffClass; } @@ -53,7 +54,7 @@ bool Buff::isStale() { } #pragma region Handlers -void Buffs::timeBuffUpdate(EntityRef self, BuffStack* buff, int status) { +void Buffs::timeBuffUpdate(EntityRef self, BuffStack* stack, int status) { if(self.kind != EntityKind::PLAYER) return; // not implemented @@ -62,21 +63,21 @@ void Buffs::timeBuffUpdate(EntityRef self, BuffStack* buff, int status) { if(plr == nullptr) return; - if(status == ETBU_DEL && plr->hasBuff(buff->id)) + if(status == ETBU_DEL && plr->hasBuff(stack->buff->id)) return; // no premature status removal! int cbf = plr->getCompositeCondition(); - if(status == ETBU_ADD) cbf |= CSB_FROM_ECSB(buff->id); + if(status == ETBU_ADD && stack->buff->id > 0) cbf |= CSB_FROM_ECSB(stack->buff->id); INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); - pkt.eCSTB = buff->id; // eCharStatusTimeBuffID + pkt.eCSTB = stack->buff->id; // eCharStatusTimeBuffID pkt.eTBU = status; // eTimeBuffUpdate - pkt.eTBT = (int)buff->buffClass; + pkt.eTBT = (int)stack->buffStackClass; pkt.iConditionBitFlag = cbf; self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } -void Buffs::timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct) { +void Buffs::timeBuffTimeoutViewable(EntityRef self, BuffStack* stack, int ct) { if(self.kind != EntityKind::PLAYER) return; // not implemented diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 55f33dd..dc73f6a 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -25,10 +25,9 @@ enum class BuffClass { typedef std::function BuffCallback; struct BuffStack { - int id; // ECSB int durationTicks; EntityRef source; - BuffClass buffClass; + BuffClass buffStackClass; /* called just before the stack is added */ BuffCallback onApply; @@ -38,6 +37,8 @@ struct BuffStack { /* called just after the stack is removed */ BuffCallback onExpire; + + Buff* buff; }; class Buff { @@ -46,6 +47,8 @@ private: std::vector stacks; public: + int id; + void tick(); void clear(); void addStack(BuffStack* stack); @@ -67,13 +70,13 @@ public: */ bool isStale(); - Buff(EntityRef pSelf, BuffStack* firstStack) - : self(pSelf) { + Buff(int iid, EntityRef pSelf, BuffStack* firstStack) + : id(iid), self(pSelf) { addStack(firstStack); } }; namespace Buffs { - void timeBuffUpdate(EntityRef self, BuffStack* buff, int status); - void timeBuffTimeoutViewable(EntityRef self, BuffStack* buff, int ct); + void timeBuffUpdate(EntityRef self, BuffStack* stack, int status); + void timeBuffTimeoutViewable(EntityRef self, BuffStack* stack, int ct); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 7dda8e3..d04a2b7 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -19,10 +19,10 @@ using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; -void Player::addBuff(BuffStack* buff) { +void Player::addBuff(int buffId, BuffStack* stack) { EntityRef self = PlayerManager::getSockFromID(iID); - if(!hasBuff(buff->id)) buffs[buff->id] = new Buff(self, buff); - else buffs[buff->id]->addStack(buff); + if(!hasBuff(buffId)) buffs[buffId] = new Buff(buffId, self, stack); + else buffs[buffId]->addStack(stack); } Buff* Player::getBuff(int buffId) { @@ -48,7 +48,7 @@ bool Player::hasBuff(int buffId) { int Player::getCompositeCondition() { int conditionBitFlag = 0; for(auto buff : buffs) { - if(!buff.second->isStale()) + if(!buff.second->isStale() && buff.second->id > 0) conditionBitFlag |= CSB_FROM_ECSB(buff.first); } return conditionBitFlag; @@ -79,7 +79,7 @@ void Player::step(time_t currTime) { // no-op } -void CombatNPC::addBuff(BuffStack* buff) { /* stubbed */ } +void CombatNPC::addBuff(int buffId, BuffStack* stack) { /* stubbed */ } Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ return nullptr; @@ -361,7 +361,6 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { // so we add and remove a permanent debuff if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) { BuffStack infection = { - ECSB_INFECTION, -1, // infinite sock, // self-inflicted BuffClass::ENVIRONMENT, @@ -372,9 +371,9 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { nullptr, // client toggles for us! todo anticheat lol [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_DEL); - }, + } }; - plr->addBuff(&infection); + plr->addBuff(ECSB_INFECTION, &infection); } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { plr->removeBuff(ECSB_INFECTION); } diff --git a/src/Entities.hpp b/src/Entities.hpp index 47249d3..891db22 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -92,7 +92,7 @@ public: ICombatant() {} virtual ~ICombatant() {} - virtual void addBuff(BuffStack* buff) = 0; + virtual void addBuff(int buffId, BuffStack* stack) = 0; virtual Buff* getBuff(int buffId) = 0; virtual void removeBuff(int buffId) = 0; virtual bool hasBuff(int buffId) = 0; @@ -158,7 +158,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } - virtual void addBuff(BuffStack* buff) override; + virtual void addBuff(int buffId, BuffStack* stack) override; virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; diff --git a/src/Items.cpp b/src/Items.cpp index 4408651..b756547 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -493,7 +493,6 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; BuffStack gumballBuff = { - eCSB, durationMilliseconds / MS_PER_PLAYER_TICK, sock, BuffClass::CASH_ITEM, // or BuffClass::ITEM? @@ -501,12 +500,12 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_ADD); }, - nullptr, // client toggles for us! todo anticheat lol + nullptr, [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_DEL); - }, + } }; - player->addBuff(&gumballBuff); + player->addBuff(eCSB, &gumballBuff); sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); // update inventory serverside diff --git a/src/Player.hpp b/src/Player.hpp index b6c05bd..ca55ccc 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -89,7 +89,7 @@ struct Player : public Entity, public ICombatant { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; - virtual void addBuff(BuffStack* buff) override; + virtual void addBuff(int buffId, BuffStack* stack) override; virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; diff --git a/src/TableData.cpp b/src/TableData.cpp index 68b7cce..3c8c0ff 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -235,7 +235,8 @@ static void loadXDT(json& xdtData) { skill["m_iEffectType"], skill["m_iTargetType"], skill["m_iBatteryDrainType"], - skill["m_iEffectArea"] + skill["m_iEffectArea"], + (int)skill["m_iIcon"] - 1 }; skillData.valueTypes[0] = skill["m_iValueA_Type"]; From da38bbec299aba8ae7d3e559e82b6c763a1e3325 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 19 Jul 2022 09:14:38 -0700 Subject: [PATCH 045/104] Fix timed out buffs not calling onExpire --- src/Buffs.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index d790566..2c12434 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -12,8 +12,12 @@ void Buff::tick() { if(stack.durationTicks > 0) stack.durationTicks--; if(stack.durationTicks == 0) { + // erase() destroys the callbacks + // with the stack struct, so we need + // to copy it first. + BuffStack deadStack = stack; it = stacks.erase(it); - if(stack.onExpire) stack.onExpire(self, &stack); + if(deadStack.onExpire) deadStack.onExpire(self, &deadStack); } else it++; } } From 1637b8e7899a3b2f1b836f83634398eeea3cabbc Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 20 Jul 2022 09:15:01 -0700 Subject: [PATCH 046/104] Passive nano powers boilerplate --- src/Abilities.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++- src/Abilities.hpp | 6 ++- src/Buffs.cpp | 13 +++--- src/Combat.cpp | 31 +++++++++----- src/Entities.hpp | 2 + src/Items.cpp | 1 - src/Player.hpp | 1 + src/TableData.cpp | 3 +- 8 files changed, 137 insertions(+), 22 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 6b309f9..f24b3a2 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -2,6 +2,11 @@ #include "NPCManager.hpp" #include "PlayerManager.hpp" +#include "Buffs.hpp" + +#include + +using namespace Abilities; std::map Abilities::SkillTable; @@ -35,15 +40,108 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 return targets; } -void Abilities::useAbility(SkillData* skill, EntityRef src, std::vector targets) { +/* ripped from client */ +int Abilities::getCSTBFromST(int eSkillType) { + int result = 0; + switch (eSkillType) + { + case 11: + result = 1; + break; + case 10: + result = 3; + break; + case 12: + result = 4; + break; + case 16: + result = 5; + break; + case 17: + result = 6; + break; + case 18: + result = 7; + break; + case 5: + result = 8; + break; + case 4: + result = 11; + break; + case 14: + result = 13; + break; + case 15: + result = 14; + break; + case 19: + result = 15; + break; + case 20: + result = 16; + break; + case 23: + result = 17; + break; + case 25: + result = 18; + break; + case 31: + result = 19; + break; + case 32: + result = 20; + break; + case 35: + result = 24; + break; + case 33: + result = 21; + break; + } + return result; +} + +#pragma region Skill Handlers +void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { + assert(skill->drainType == SkillDrainType::PASSIVE); + + EntityRef self = PlayerManager::getSockFromID(plr->iID); + std::vector 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; + } + + BuffStack passiveBuff = { + 1, // passive nano buffs refreshed every tick + self, + BuffClass::NONE, // overwritten per target + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_ADD); + }, + nullptr, + [](EntityRef host, BuffStack* stack) { + Buffs::timeBuffUpdate(host, stack, ETBU_DEL); + } + }; + for (EntityRef target : targets) { Entity* entity = target.getEntity(); if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB) continue; // not a combatant - // TODO abilities + passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO; + ICombatant* combatant = dynamic_cast(entity); + combatant->addBuff(getCSTBFromST(skill->skillType), &passiveBuff); } } +#pragma endregion void Abilities::init() { //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); diff --git a/src/Abilities.hpp b/src/Abilities.hpp index f376a7e..03b8fa7 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -1,6 +1,7 @@ #pragma once #include "Entities.hpp" +#include "Player.hpp" #include #include @@ -32,7 +33,6 @@ struct SkillData { SkillTargetType targetType; SkillDrainType drainType; int effectArea; - int charStatusTimeBuffId; // eCS(T)B int batteryUse[4]; int durationTime[4]; @@ -45,7 +45,9 @@ namespace Abilities { extern std::map SkillTable; std::vector matchTargets(SkillData*, int, int32_t*); - void useAbility(SkillData*, EntityRef, std::vector); + int getCSTBFromST(int eSkillType); + + void usePassiveNanoSkill(SkillData*, Player*); void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 2c12434..a925b97 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -10,7 +10,6 @@ void Buff::tick() { BuffStack& stack = *it; if(stack.onTick) stack.onTick(self, &stack); - if(stack.durationTicks > 0) stack.durationTicks--; if(stack.durationTicks == 0) { // erase() destroys the callbacks // with the stack struct, so we need @@ -18,7 +17,10 @@ void Buff::tick() { BuffStack deadStack = stack; it = stacks.erase(it); if(deadStack.onExpire) deadStack.onExpire(self, &deadStack); - } else it++; + } else { + if(stack.durationTicks > 0) stack.durationTicks--; + it++; + } } } @@ -31,9 +33,10 @@ void Buff::clear() { } void Buff::addStack(BuffStack* stack) { - stack->buff = this; - if(stack->onApply) stack->onApply(self, stack); - stacks.push_back(*stack); + BuffStack newStack = *stack; + newStack.buff = this; + if(newStack.onApply) newStack.onApply(self, &newStack); + stacks.push_back(newStack); } bool Buff::hasClass(BuffClass buffClass) { diff --git a/src/Combat.cpp b/src/Combat.cpp index d04a2b7..452b065 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -71,6 +71,15 @@ int Player::getCurrentHP() { return HP; } +std::vector Player::getGroupMembers() { + std::vector members; + if(group != nullptr) + members = group->members; + else + members.push_back(PlayerManager::getSockFromID(iID)); + return members; +} + int32_t Player::getID() { return iID; } @@ -116,6 +125,15 @@ int CombatNPC::getCurrentHP() { return hp; } +std::vector CombatNPC::getGroupMembers() { + std::vector members; + if(group != nullptr) + members = group->members; + else + members.push_back(id); + return members; +} + int32_t CombatNPC::getID() { return id; } @@ -364,7 +382,6 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { -1, // infinite sock, // self-inflicted BuffClass::ENVIRONMENT, - [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_ADD); }, @@ -753,18 +770,12 @@ static void playerTick(CNServer *serv, time_t currTime) { // nano has skill data SkillData* skill = &Abilities::SkillTable[nano.iSkillID]; int boost = Nanos::getNanoBoost(plr); - drainRate = skill->batteryUse[boost * 3]; + std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl; if (skill->drainType == SkillDrainType::PASSIVE) { // apply passive buff - std::vector 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; - // TODO abilities + drainRate = skill->batteryUse[boost * 3]; + Abilities::usePassiveNanoSkill(skill, plr); } } diff --git a/src/Entities.hpp b/src/Entities.hpp index 891db22..5e98064 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -101,6 +101,7 @@ public: virtual void heal(EntityRef, int) = 0; virtual bool isAlive() = 0; virtual int getCurrentHP() = 0; + virtual std::vector getGroupMembers() = 0; virtual int32_t getID() = 0; virtual void step(time_t currTime) = 0; }; @@ -167,6 +168,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; virtual int getCurrentHP() override; + virtual std::vector getGroupMembers() override; virtual int32_t getID() override; virtual void step(time_t currTime) override; diff --git a/src/Items.cpp b/src/Items.cpp index b756547..832b2f5 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -496,7 +496,6 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { durationMilliseconds / MS_PER_PLAYER_TICK, sock, BuffClass::CASH_ITEM, // or BuffClass::ITEM? - [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_ADD); }, diff --git a/src/Player.hpp b/src/Player.hpp index ca55ccc..02b6b14 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -98,6 +98,7 @@ struct Player : public Entity, public ICombatant { virtual void heal(EntityRef src, int amt) override; virtual bool isAlive() override; virtual int getCurrentHP() override; + virtual std::vector getGroupMembers() override; virtual int32_t getID() override; virtual void step(time_t currTime) override; diff --git a/src/TableData.cpp b/src/TableData.cpp index 3c8c0ff..68b7cce 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -235,8 +235,7 @@ static void loadXDT(json& xdtData) { skill["m_iEffectType"], skill["m_iTargetType"], skill["m_iBatteryDrainType"], - skill["m_iEffectArea"], - (int)skill["m_iIcon"] - 1 + skill["m_iEffectArea"] }; skillData.valueTypes[0] = skill["m_iValueA_Type"]; From 8b94bcd5cab826c452286f7ecacfa47588b846a1 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Thu, 21 Jul 2022 09:22:19 -0700 Subject: [PATCH 047/104] Passive nano powers pt 1 --- src/Abilities.cpp | 83 +++++++++++++++++++++++++---------------------- src/Abilities.hpp | 2 +- src/Buffs.cpp | 9 ++++- src/Buffs.hpp | 1 + src/Combat.cpp | 3 +- src/Items.cpp | 1 + 6 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index f24b3a2..009c951 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -40,71 +40,71 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 return targets; } -/* ripped from client */ +/* ripped from client (enums emplaced) */ int Abilities::getCSTBFromST(int eSkillType) { int result = 0; switch (eSkillType) { - case 11: - result = 1; + case EST_RUN: + result = ECSB_UP_MOVE_SPEED; break; - case 10: - result = 3; + case EST_JUMP: + result = ECSB_UP_JUMP_HEIGHT; break; - case 12: - result = 4; + case EST_STEALTH: + result = ECSB_UP_STEALTH; break; - case 16: - result = 5; + case EST_PHOENIX: + result = ECSB_PHOENIX; break; - case 17: - result = 6; + case EST_PROTECTBATTERY: + result = ECSB_PROTECT_BATTERY; break; - case 18: - result = 7; + case EST_PROTECTINFECTION: + result = ECSB_PROTECT_INFECTION; break; - case 5: - result = 8; + case EST_SNARE: + result = ECSB_DN_MOVE_SPEED; break; - case 4: - result = 11; + case EST_SLEEP: + result = ECSB_MEZ; break; - case 14: - result = 13; + case EST_MINIMAPENEMY: + result = ECSB_MINIMAP_ENEMY; break; - case 15: - result = 14; + case EST_MINIMAPTRESURE: + result = ECSB_MINIMAP_TRESURE; break; - case 19: - result = 15; + case EST_REWARDBLOB: + result = ECSB_REWARD_BLOB; break; - case 20: - result = 16; + case EST_REWARDCASH: + result = ECSB_REWARD_CASH; break; - case 23: - result = 17; + case EST_INFECTIONDAMAGE: + result = ECSB_INFECTION; break; - case 25: - result = 18; + case EST_FREEDOM: + result = ECSB_FREEDOM; break; - case 31: - result = 19; + case EST_BOUNDINGBALL: + result = ECSB_BOUNDINGBALL; break; - case 32: - result = 20; + case EST_INVULNERABLE: + result = ECSB_INVULNERABLE; break; - case 35: - result = 24; + case EST_BUFFHEAL: + result = ECSB_HEAL; break; - case 33: - result = 21; + case EST_NANOSTIMPAK: + result = ECSB_STIMPAKSLOT1; break; } return result; } #pragma region Skill Handlers -void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { +void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { assert(skill->drainType == SkillDrainType::PASSIVE); EntityRef self = PlayerManager::getSockFromID(plr->iID); @@ -118,12 +118,17 @@ void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl; } + int timeBuffId = getCSTBFromST(skill->skillType); + int value = skill->values[0][boost]; + BuffStack passiveBuff = { 1, // passive nano buffs refreshed every tick + value, self, BuffClass::NONE, // overwritten per target [](EntityRef host, BuffStack* stack) { Buffs::timeBuffUpdate(host, stack, ETBU_ADD); + // TODO SKILLUSE/SKILLUSESUCC, sSkillResult_Buff }, nullptr, [](EntityRef host, BuffStack* stack) { @@ -138,7 +143,7 @@ void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO; ICombatant* combatant = dynamic_cast(entity); - combatant->addBuff(getCSTBFromST(skill->skillType), &passiveBuff); + combatant->addBuff(timeBuffId, &passiveBuff); } } #pragma endregion diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 03b8fa7..4600339 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -47,7 +47,7 @@ namespace Abilities { std::vector matchTargets(SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); - void usePassiveNanoSkill(SkillData*, Player*); + void usePassiveNanoSkill(SkillData*, Player*, int); void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index a925b97..8ffdace 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -73,14 +73,21 @@ void Buffs::timeBuffUpdate(EntityRef self, BuffStack* stack, int status) { if(status == ETBU_DEL && plr->hasBuff(stack->buff->id)) return; // no premature status removal! + sTimeBuff payload{}; + int cbf = plr->getCompositeCondition(); - if(status == ETBU_ADD && stack->buff->id > 0) cbf |= CSB_FROM_ECSB(stack->buff->id); + if(status == ETBU_ADD) { + if(stack->buff->id > 0) cbf |= CSB_FROM_ECSB(stack->buff->id); + payload.iValue = stack->value; + //payload.iTimeLimit = stack->durationTicks * MS_PER_PLAYER_TICK; + } INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); pkt.eCSTB = stack->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)); } diff --git a/src/Buffs.hpp b/src/Buffs.hpp index dc73f6a..2fc8163 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -26,6 +26,7 @@ typedef std::function BuffCallback; struct BuffStack { int durationTicks; + int value; EntityRef source; BuffClass buffStackClass; diff --git a/src/Combat.cpp b/src/Combat.cpp index 452b065..dbab50f 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -380,6 +380,7 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) { BuffStack infection = { -1, // infinite + NULL, sock, // self-inflicted BuffClass::ENVIRONMENT, [](EntityRef host, BuffStack* stack) { @@ -775,7 +776,7 @@ static void playerTick(CNServer *serv, time_t currTime) { if (skill->drainType == SkillDrainType::PASSIVE) { // apply passive buff drainRate = skill->batteryUse[boost * 3]; - Abilities::usePassiveNanoSkill(skill, plr); + Abilities::usePassiveNanoSkill(skill, plr, boost * 3); } } diff --git a/src/Items.cpp b/src/Items.cpp index 832b2f5..94b2c20 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -494,6 +494,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; BuffStack gumballBuff = { durationMilliseconds / MS_PER_PLAYER_TICK, + NULL, sock, BuffClass::CASH_ITEM, // or BuffClass::ITEM? [](EntityRef host, BuffStack* stack) { From ab4b763f008fb186584a2413a7a87a3d4da02b1d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Fri, 22 Jul 2022 06:47:52 -0700 Subject: [PATCH 048/104] YET ANOTHER ITERATION of the new ability system I am very tired --- src/Abilities.cpp | 23 +++++++------ src/Abilities.hpp | 2 +- src/Buffs.cpp | 82 +++++++++++++++++++++++++++---------------- src/Buffs.hpp | 43 +++++++++++++---------- src/Combat.cpp | 88 ++++++++++++++++++++++++++--------------------- src/Entities.hpp | 67 ++++++------------------------------ src/EntityRef.hpp | 52 ++++++++++++++++++++++++++++ src/Items.cpp | 20 +++++------ src/Nanos.cpp | 4 --- src/Player.hpp | 2 +- 10 files changed, 214 insertions(+), 169 deletions(-) create mode 100644 src/EntityRef.hpp diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 009c951..4b23152 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -104,9 +104,11 @@ int Abilities::getCSTBFromST(int eSkillType) { } #pragma region Skill Handlers -void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { +bool Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { assert(skill->drainType == SkillDrainType::PASSIVE); + bool newBuffApplied = false; + EntityRef self = PlayerManager::getSockFromID(plr->iID); std::vector targets; if (skill->targetType == SkillTargetType::GROUP) { @@ -126,14 +128,6 @@ void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { value, self, BuffClass::NONE, // overwritten per target - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_ADD); - // TODO SKILLUSE/SKILLUSESUCC, sSkillResult_Buff - }, - nullptr, - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_DEL); - } }; for (EntityRef target : targets) { @@ -143,8 +137,17 @@ void Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO; ICombatant* combatant = dynamic_cast(entity); - combatant->addBuff(timeBuffId, &passiveBuff); + newBuffApplied |= combatant->addBuff(timeBuffId, + [](EntityRef self, Buff* buff, int status, BuffStack* stack) { + Buffs::timeBuffUpdate(self, buff, status, stack); + }, + [](EntityRef self, Buff* buff, time_t currTime) { + // TODO time buff tick + }, + &passiveBuff); } + + return newBuffApplied; } #pragma endregion diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 4600339..22be0c7 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -47,7 +47,7 @@ namespace Abilities { std::vector matchTargets(SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); - void usePassiveNanoSkill(SkillData*, Player*, int); + bool usePassiveNanoSkill(SkillData*, Player*, int); void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 8ffdace..c40f7e1 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -4,19 +4,16 @@ using namespace Buffs; -void Buff::tick() { +void Buff::tick(time_t currTime) { auto it = stacks.begin(); while(it != stacks.end()) { BuffStack& stack = *it; - if(stack.onTick) stack.onTick(self, &stack); + if(onTick) onTick(self, this, currTime); if(stack.durationTicks == 0) { - // erase() destroys the callbacks - // with the stack struct, so we need - // to copy it first. BuffStack deadStack = stack; it = stacks.erase(it); - if(deadStack.onExpire) deadStack.onExpire(self, &deadStack); + if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack); } else { if(stack.durationTicks > 0) stack.durationTicks--; it++; @@ -28,15 +25,13 @@ void Buff::clear() { while(!stacks.empty()) { BuffStack stack = stacks.back(); stacks.pop_back(); - if(stack.onExpire) stack.onExpire(self, &stack); + if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack); } } void Buff::addStack(BuffStack* stack) { - BuffStack newStack = *stack; - newStack.buff = this; - if(newStack.onApply) newStack.onApply(self, &newStack); - stacks.push_back(newStack); + stacks.push_back(*stack); + if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back()); } bool Buff::hasClass(BuffClass buffClass) { @@ -56,34 +51,68 @@ BuffClass Buff::maxClass() { 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 fOnUpdate, BuffCallback fonTick) { + if(!onUpdate) onUpdate = fOnUpdate; + if(!onTick) onTick = fonTick; +} + #pragma region Handlers -void Buffs::timeBuffUpdate(EntityRef self, BuffStack* stack, int status) { +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; + return; // sanity check - if(status == ETBU_DEL && plr->hasBuff(stack->buff->id)) - return; // no premature status removal! - - sTimeBuff payload{}; + if(status == ETBU_DEL && !buff->isStale()) + return; // no premature effect deletion int cbf = plr->getCompositeCondition(); + sTimeBuff payload{}; if(status == ETBU_ADD) { - if(stack->buff->id > 0) cbf |= CSB_FROM_ECSB(stack->buff->id); - payload.iValue = stack->value; - //payload.iTimeLimit = stack->durationTicks * MS_PER_PLAYER_TICK; + 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 = stack->buff->id; // eCharStatusTimeBuffID + pkt.eCSTB = buff->id; // eCharStatusTimeBuffID pkt.eTBU = status; // eTimeBuffUpdate pkt.eTBT = (int)stack->buffStackClass; pkt.iConditionBitFlag = cbf; @@ -91,20 +120,15 @@ void Buffs::timeBuffUpdate(EntityRef self, BuffStack* stack, int status) { self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } -void Buffs::timeBuffTimeoutViewable(EntityRef self, BuffStack* stack, int ct) { - if(self.kind != EntityKind::PLAYER) - return; // not implemented - - Player* plr = (Player*)self.getEntity(); - if(plr == nullptr) - return; - +/* +void Buffs::timeBuffTimeoutViewable(EntityRef self, Buff* buff, int ct) { INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players pkt.eCT = ct; // 1 for eggs, at least pkt.iID = plr->iID; pkt.iConditionBitFlag = plr->getCompositeCondition(); PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); } +*/ /* MOVE TO EGG LAMBDA void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 2fc8163..1d0c8df 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -2,13 +2,15 @@ #include "core/Core.hpp" -#include "Entities.hpp" +#include "EntityRef.hpp" #include #include /* forward declaration(s) */ -struct BuffStack; +class Buff; +template +using BuffCallback = std::function; #define CSB_FROM_ECSB(x) (1 << (x - 1)) @@ -22,24 +24,19 @@ enum class BuffClass { CASH_ITEM = ETBT_CASHITEM }; -typedef std::function BuffCallback; +enum class BuffValueSelector { + MAX_VALUE, + MIN_VALUE, + MAX_MAGNITUDE, + MIN_MAGNITUDE, + NET_TOTAL +}; struct BuffStack { int durationTicks; int value; EntityRef source; BuffClass buffStackClass; - - /* called just before the stack is added */ - BuffCallback onApply; - - /* called when the stack is ticked */ - BuffCallback onTick; - - /* called just after the stack is removed */ - BuffCallback onExpire; - - Buff* buff; }; class Buff { @@ -49,8 +46,12 @@ private: public: int id; + /* called just after a stack is added or removed */ + BuffCallback onUpdate; + /* called when the buff is ticked */ + BuffCallback onTick; - void tick(); + void tick(time_t); void clear(); void addStack(BuffStack* stack); @@ -62,6 +63,8 @@ public: bool hasClass(BuffClass buffClass); BuffClass maxClass(); + int getValue(BuffValueSelector selector); + /* * In general, a Buff object won't exist * unless it has stacks. However, when @@ -71,13 +74,15 @@ public: */ bool isStale(); - Buff(int iid, EntityRef pSelf, BuffStack* firstStack) - : id(iid), self(pSelf) { + void updateCallbacks(BuffCallback fOnUpdate, BuffCallback fonTick); + + Buff(int iid, EntityRef pSelf, BuffCallback fOnUpdate, BuffCallback fOnTick, BuffStack* firstStack) + : self(pSelf), id(iid), onUpdate(fOnUpdate), onTick(fOnTick) { addStack(firstStack); } }; namespace Buffs { - void timeBuffUpdate(EntityRef self, BuffStack* stack, int status); - void timeBuffTimeoutViewable(EntityRef self, BuffStack* stack, int ct); + void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); + //void timeBuffTimeoutViewable(EntityRef self, Buff* buff, int ct); } diff --git a/src/Combat.cpp b/src/Combat.cpp index dbab50f..9b66305 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -19,10 +19,17 @@ using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; -void Player::addBuff(int buffId, BuffStack* stack) { +bool Player::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { EntityRef self = PlayerManager::getSockFromID(iID); - if(!hasBuff(buffId)) buffs[buffId] = new Buff(buffId, self, stack); - else buffs[buffId]->addStack(stack); + + if(!hasBuff(buffId)) { + buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack); + return true; + } + + buffs[buffId]->updateCallbacks(onUpdate, onTick); + buffs[buffId]->addStack(stack); + return false; } Buff* Player::getBuff(int buffId) { @@ -88,7 +95,9 @@ void Player::step(time_t currTime) { // no-op } -void CombatNPC::addBuff(int buffId, BuffStack* stack) { /* stubbed */ } +bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { /* stubbed */ + return false; +} Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ return nullptr; @@ -371,43 +380,20 @@ 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 - // 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 - NULL, - sock, // self-inflicted - BuffClass::ENVIRONMENT, - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_ADD); - }, - nullptr, // client toggles for us! todo anticheat lol - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_DEL); - } - }; - plr->addBuff(ECSB_INFECTION, &infection); - } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { - plr->removeBuff(ECSB_INFECTION); - } -} - -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)); + 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 @@ -444,6 +430,33 @@ static void dealGooDamage(CNSocket *sock, int amount) { 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); @@ -745,11 +758,6 @@ static void playerTick(CNServer *serv, time_t currTime) { if (plr->HP <= 0) continue; - // fm patch/lake damage - if ((plr->hasBuff(ECSB_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) { @@ -776,7 +784,10 @@ static void playerTick(CNServer *serv, time_t currTime) { if (skill->drainType == SkillDrainType::PASSIVE) { // apply passive buff drainRate = skill->batteryUse[boost * 3]; - Abilities::usePassiveNanoSkill(skill, plr, boost * 3); + if(Abilities::usePassiveNanoSkill(skill, plr, boost * 3)) { + // first buff, send back skill use packet(s) + // TODO + } } } @@ -812,9 +823,8 @@ static void playerTick(CNServer *serv, time_t currTime) { // process buffsets auto it = plr->buffs.begin(); while(it != plr->buffs.end()) { - int buffId = (*it).first; Buff* buff = (*it).second; - buff->tick(); + buff->tick(currTime); if(buff->isStale()) { // garbage collect it = plr->buffs.erase(it); diff --git a/src/Entities.hpp b/src/Entities.hpp index 5e98064..d6cd361 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -3,22 +3,16 @@ #include "core/Core.hpp" #include "Chunking.hpp" +#include "EntityRef.hpp" +#include "Buffs.hpp" + #include #include +#include /* forward declaration(s) */ -class Buff; -struct BuffStack; - -enum EntityKind { - INVALID, - PLAYER, - SIMPLE_NPC, - COMBAT_NPC, - MOB, - EGG, - BUS -}; +class Chunk; +struct Group; enum class AIState { INACTIVE, @@ -28,9 +22,6 @@ enum class AIState { DEAD }; -class Chunk; -struct Group; - struct Entity { EntityKind kind = EntityKind::INVALID; int x = 0, y = 0, z = 0; @@ -48,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 */ @@ -92,10 +47,10 @@ public: ICombatant() {} virtual ~ICombatant() {} - virtual void addBuff(int buffId, BuffStack* stack) = 0; - virtual Buff* getBuff(int buffId) = 0; - virtual void removeBuff(int buffId) = 0; - virtual bool hasBuff(int buffId) = 0; + virtual bool addBuff(int, BuffCallback, BuffCallback, BuffStack*) = 0; + virtual Buff* getBuff(int) = 0; + virtual void removeBuff(int) = 0; + virtual bool hasBuff(int) = 0; virtual int getCompositeCondition() = 0; virtual int takeDamage(EntityRef, int) = 0; virtual void heal(EntityRef, int) = 0; @@ -159,7 +114,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isExtant() override { return hp > 0; } - virtual void addBuff(int buffId, BuffStack* stack) override; + virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) override; virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; diff --git a/src/EntityRef.hpp b/src/EntityRef.hpp new file mode 100644 index 0000000..2aab56e --- /dev/null +++ b/src/EntityRef.hpp @@ -0,0 +1,52 @@ +#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; + } + + // 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; + } +}; \ No newline at end of file diff --git a/src/Items.cpp b/src/Items.cpp index 94b2c20..0186d51 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -494,18 +494,18 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; BuffStack gumballBuff = { durationMilliseconds / MS_PER_PLAYER_TICK, - NULL, + 0, sock, - BuffClass::CASH_ITEM, // or BuffClass::ITEM? - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_ADD); - }, - nullptr, - [](EntityRef host, BuffStack* stack) { - Buffs::timeBuffUpdate(host, stack, ETBU_DEL); - } + BuffClass::CASH_ITEM // or BuffClass::ITEM? }; - player->addBuff(eCSB, &gumballBuff); + player->addBuff(eCSB, + [](EntityRef self, Buff* buff, int status, BuffStack* stack) { + Buffs::timeBuffUpdate(self, buff, status, stack); + }, + [](EntityRef self, Buff* buff, time_t currTime) { + // TODO passive tick + }, + &gumballBuff); sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); // update inventory serverside diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 551da96..d5f83f9 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -208,10 +208,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { // Update player plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID; - // Unbuff gumballs - int buffId = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum; - plr->removeBuff(buffId); - // unsummon nano if replaced if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) summonNano(sock, -1); diff --git a/src/Player.hpp b/src/Player.hpp index 02b6b14..7f65840 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -89,7 +89,7 @@ struct Player : public Entity, public ICombatant { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; - virtual void addBuff(int buffId, BuffStack* stack) override; + virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) override; virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual bool hasBuff(int buffId) override; From 723e455b1d5a2654921ac7c7dc15bb8c8446ce55 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 23 Jul 2022 18:37:42 -0700 Subject: [PATCH 049/104] Passive nano powers --- src/Abilities.cpp | 107 +++++++++++++++++++++++++++++++++++++++------- src/Abilities.hpp | 16 ++++++- src/Combat.cpp | 19 ++++++-- src/Entities.hpp | 5 +++ src/Nanos.cpp | 15 +++++-- src/Player.hpp | 1 + 6 files changed, 139 insertions(+), 24 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 4b23152..c9d0d61 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -3,6 +3,7 @@ #include "NPCManager.hpp" #include "PlayerManager.hpp" #include "Buffs.hpp" +#include "Nanos.hpp" #include @@ -10,14 +11,92 @@ using namespace Abilities; std::map 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 handleSkillBuff(SkillData* skill, ICombatant* target) { + // the buff skill is special in that its application is not aligned + // with the SKILL_USE_SUCC packet, since buffs are calculated every tick. + // thus, no game state changes should happen here. + sSkillResult_Buff result{}; + result.iConditionBitFlag = target->getCompositeCondition(); + result.iID = target->getID(); + result.bProtected = 0; + result.eCT = target->getCharType(); + return SkillResult(sizeof(sSkillResult_Buff), &result); +} +#pragma endregion + +void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vector affected) { + if(SkillTable.count(nano.iSkillID) == 0) + return; + + SkillData* skill = &SkillTable[nano.iSkillID]; + Player* plr = PlayerManager::getPlayer(sock); + int count = affected.size(); + + size_t resultSize = 0; + SkillResult (*skillHandler)(SkillData*, ICombatant*) = nullptr; + switch(skill->skillType) + { + 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; + default: + std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl; + return; + } + + std::vector results; + for(ICombatant* target : affected) { + SkillResult result = handleSkillBuff(skill, target); + if(result.size != resultSize) { + std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << handleSkillBuff << std::endl; + return; + } + results.push_back(result); + } + + if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), count, 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) + count * 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 = count; + + char* appended = (char*)(pkt + 1); + for(SkillResult& result : results) { + memcpy(appended, result.payload, resultSize); + appended += resultSize; + } + + sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); + PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); } -*/ std::vector Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { @@ -103,13 +182,11 @@ int Abilities::getCSTBFromST(int eSkillType) { return result; } -#pragma region Skill Handlers -bool Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { +std::vector Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { assert(skill->drainType == SkillDrainType::PASSIVE); - bool newBuffApplied = false; - EntityRef self = PlayerManager::getSockFromID(plr->iID); + std::vector affected; std::vector targets; if (skill->targetType == SkillTargetType::GROUP) { targets = plr->getGroupMembers(); // group @@ -121,6 +198,7 @@ bool Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { } int timeBuffId = getCSTBFromST(skill->skillType); + int boost = Nanos::getNanoBoost(plr) ? 3 : 0; int value = skill->values[0][boost]; BuffStack passiveBuff = { @@ -137,19 +215,18 @@ bool Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr, int boost) { passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO; ICombatant* combatant = dynamic_cast(entity); - newBuffApplied |= combatant->addBuff(timeBuffId, + 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) { // TODO time buff tick }, - &passiveBuff); + &passiveBuff)) affected.push_back(combatant); } - return newBuffApplied; + return affected; } -#pragma endregion void Abilities::init() { //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 22be0c7..cc02b5e 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -1,11 +1,15 @@ #pragma once +#include "core/Core.hpp" + #include "Entities.hpp" #include "Player.hpp" #include #include +constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); + enum class SkillEffectTarget { POINT = 1, SELF = 2, @@ -26,6 +30,15 @@ enum class SkillDrainType { PASSIVE = 2 }; +struct SkillResult { + size_t size; + char payload[MAX_SKILLRESULT_SIZE]; + SkillResult(size_t len, void* dat) { + size = len; + memcpy(payload, dat, len); + } +}; + struct SkillData { int skillType; // eST SkillEffectTarget effectTarget; @@ -44,10 +57,11 @@ struct SkillData { namespace Abilities { extern std::map SkillTable; + void broadcastNanoSkill(CNSocket*, sNano&, std::vector); std::vector matchTargets(SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); - bool usePassiveNanoSkill(SkillData*, Player*, int); + std::vector usePassiveNanoSkill(SkillData*, Player*); void init(); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 9b66305..9d9b887 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -19,6 +19,7 @@ using namespace Combat; /// Player Id -> Bullet Id -> Bullet std::map> Combat::Bullets; +#pragma region Player bool Player::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { EntityRef self = PlayerManager::getSockFromID(iID); @@ -87,6 +88,10 @@ std::vector Player::getGroupMembers() { return members; } +int32_t Player::getCharType() { + return 1; // eCharType (eCT_PC) +} + int32_t Player::getID() { return iID; } @@ -94,7 +99,9 @@ int32_t Player::getID() { void Player::step(time_t currTime) { // no-op } +#pragma endregion +#pragma region CombatNPC bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { /* stubbed */ return false; } @@ -143,6 +150,12 @@ std::vector CombatNPC::getGroupMembers() { 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; } @@ -172,6 +185,7 @@ void CombatNPC::transition(AIState newState, EntityRef src) { event.handler(src, this); */ } +#pragma endregion static std::pair getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, @@ -784,10 +798,7 @@ static void playerTick(CNServer *serv, time_t currTime) { if (skill->drainType == SkillDrainType::PASSIVE) { // apply passive buff drainRate = skill->batteryUse[boost * 3]; - if(Abilities::usePassiveNanoSkill(skill, plr, boost * 3)) { - // first buff, send back skill use packet(s) - // TODO - } + Abilities::usePassiveNanoSkill(skill, plr); } } diff --git a/src/Entities.hpp b/src/Entities.hpp index d6cd361..bba7bd8 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -57,6 +57,7 @@ public: virtual bool isAlive() = 0; virtual int getCurrentHP() = 0; virtual std::vector getGroupMembers() = 0; + virtual int32_t getCharType() = 0; virtual int32_t getID() = 0; virtual void step(time_t currTime) = 0; }; @@ -74,6 +75,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; @@ -108,6 +110,8 @@ struct CombatNPC : public BaseNPC, public ICombatant { spawnY = y; spawnZ = z; + kind = EntityKind::COMBAT_NPC; + stateHandlers[AIState::INACTIVE] = {}; transitionHandlers[AIState::INACTIVE] = {}; } @@ -124,6 +128,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual std::vector getGroupMembers() override; + virtual int32_t getCharType() override; virtual int32_t getID() override; virtual void step(time_t currTime) override; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index d5f83f9..de91c8b 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -86,10 +86,17 @@ 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; + int boost = Nanos::getNanoBoost(plr); + std::vector affectedCombatants = Abilities::usePassiveNanoSkill(skill, plr); + if(!affectedCombatants.empty()) Abilities::broadcastNanoSkill(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 +104,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); } diff --git a/src/Player.hpp b/src/Player.hpp index 7f65840..311b923 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -99,6 +99,7 @@ struct Player : public Entity, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual std::vector getGroupMembers() override; + virtual int32_t getCharType() override; virtual int32_t getID() override; virtual void step(time_t currTime) override; From 9977907842be535726c08c2ddf8205eaa3442057 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 25 Jul 2022 23:01:20 -0700 Subject: [PATCH 050/104] More skill handlers Note: need to revisit these when active powers are implemented to make sure they are correct. DamageNDebuff isn't even implemented yet. --- src/Abilities.cpp | 176 ++++++++++++++++++++++++++++++++++------ src/Abilities.hpp | 5 +- src/Combat.cpp | 56 ++++++++++--- src/Entities.cpp | 4 + src/Entities.hpp | 10 ++- src/Player.hpp | 6 +- src/core/CNProtocol.hpp | 4 +- 7 files changed, 218 insertions(+), 43 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index c9d0d61..295b82f 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -12,31 +12,127 @@ using namespace Abilities; std::map Abilities::SkillTable; #pragma region Skill handlers -static SkillResult handleSkillBuff(SkillData* skill, ICombatant* target) { - // the buff skill is special in that its application is not aligned - // with the SKILL_USE_SUCC packet, since buffs are calculated every tick. - // thus, no game state changes should happen here. - sSkillResult_Buff result{}; - result.iConditionBitFlag = target->getCompositeCondition(); - result.iID = target->getID(); - result.bProtected = 0; +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 = heal; + 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(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(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 -void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vector affected) { - if(SkillTable.count(nano.iSkillID) == 0) - return; - - SkillData* skill = &SkillTable[nano.iSkillID]; - Player* plr = PlayerManager::getPlayer(sock); - int count = affected.size(); - +static std::vector handleSkill(SkillData* skill, int power, ICombatant* src, std::vector targets) { size_t resultSize = 0; - SkillResult (*skillHandler)(SkillData*, ICombatant*) = nullptr; + SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr; + std::vector 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: @@ -54,28 +150,54 @@ void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vectorskillType << std::endl; - return; + return results; } + assert(skillHandler != nullptr); - std::vector results; - for(ICombatant* target : affected) { - SkillResult result = handleSkillBuff(skill, target); + for(ICombatant* target : targets) { + SkillResult result = skillHandler(skill, power, src, target); + if(result.size == 0) continue; // skill not applicable if(result.size != resultSize) { - std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << handleSkillBuff << std::endl; - return; + std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl; + continue; } results.push_back(result); } + return results; +} - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), count, resultSize)) { +void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vector affected) { + if(SkillTable.count(nano.iSkillID) == 0) + return; + + SkillData* skill = &SkillTable[nano.iSkillID]; + Player* plr = PlayerManager::getPlayer(sock); + + std::vector 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) + count * resultSize; + size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize; uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -86,9 +208,9 @@ void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vectoriNanoStamina = nano.iStamina; pkt->bNanoDeactive = nano.iStamina <= 0; pkt->eST = skill->skillType; - pkt->iTargetCnt = count; + pkt->iTargetCnt = (int32_t)results.size(); - char* appended = (char*)(pkt + 1); + uint8_t* appended = (uint8_t*)(pkt + 1); for(SkillResult& result : results) { memcpy(appended, result.payload, resultSize); appended += resultSize; diff --git a/src/Abilities.hpp b/src/Abilities.hpp index cc02b5e..fe531a5 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -32,11 +32,14 @@ enum class SkillDrainType { struct SkillResult { size_t size; - char payload[MAX_SKILLRESULT_SIZE]; + uint8_t payload[MAX_SKILLRESULT_SIZE]; SkillResult(size_t len, void* dat) { size = len; memcpy(payload, dat, len); } + SkillResult() { + size = 0; + } }; struct SkillData { diff --git a/src/Combat.cpp b/src/Combat.cpp index 9d9b887..74f46f1 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -63,12 +63,19 @@ int Player::getCompositeCondition() { } int Player::takeDamage(EntityRef src, int amt) { - HP -= amt; - return amt; + int dmg = amt; + if(HP - dmg < 0) dmg = HP; + HP -= dmg; + + return dmg; } -void Player::heal(EntityRef src, int amt) { - // stubbed +int Player::heal(EntityRef src, int amt) { + int heal = amt; + if(HP + heal > getMaxHP()) heal = getMaxHP() - HP; + HP += heal; + + return heal; } bool Player::isAlive() { @@ -79,6 +86,14 @@ int Player::getCurrentHP() { return HP; } +int Player::getMaxHP() { + return PC_MAXHEALTH(level); +} + +int Player::getLevel() { + return level; +} + std::vector Player::getGroupMembers() { std::vector members; if(group != nullptr) @@ -96,6 +111,10 @@ int32_t Player::getID() { return iID; } +EntityRef Player::getRef() { + return EntityRef(PlayerManager::getSockFromID(iID)); +} + void Player::step(time_t currTime) { // no-op } @@ -121,16 +140,21 @@ int CombatNPC::getCompositeCondition() { /* stubbed */ } 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() { @@ -141,6 +165,14 @@ int CombatNPC::getCurrentHP() { return hp; } +int CombatNPC::getMaxHP() { + return maxHealth; +} + +int CombatNPC::getLevel() { + return level; +} + std::vector CombatNPC::getGroupMembers() { std::vector members; if(group != nullptr) @@ -160,6 +192,10 @@ int32_t CombatNPC::getID() { return id; } +EntityRef CombatNPC::getRef() { + return EntityRef(id); +} + void CombatNPC::step(time_t currTime) { if(stateHandlers.find(state) != stateHandlers.end()) diff --git a/src/Entities.cpp b/src/Entities.cpp index a8c53b1..77b5e3f 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -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; diff --git a/src/Entities.hpp b/src/Entities.hpp index bba7bd8..f7234af 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -53,12 +53,15 @@ public: 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 getGroupMembers() = 0; virtual int32_t getCharType() = 0; virtual int32_t getID() = 0; + virtual EntityRef getRef() = 0; virtual void step(time_t currTime) = 0; }; @@ -124,12 +127,15 @@ struct CombatNPC : public BaseNPC, public ICombatant { 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 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); diff --git a/src/Player.hpp b/src/Player.hpp index 311b923..a697dae 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -95,14 +95,18 @@ struct Player : public Entity, public ICombatant { 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 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(); }; diff --git a/src/core/CNProtocol.hpp b/src/core/CNProtocol.hpp index 782190f..9d1a6b2 100644 --- a/src/core/CNProtocol.hpp +++ b/src/core/CNProtocol.hpp @@ -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; From dc6386131af2036324a32860f9e2bdfcb948df1d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 30 Jul 2022 13:47:27 -0700 Subject: [PATCH 051/104] Port egg buffs over to new system --- src/Abilities.cpp | 105 ++++++++++++++++++----------------------- src/Abilities.hpp | 8 ++-- src/Buffs.cpp | 23 ++++----- src/Buffs.hpp | 2 +- src/Combat.cpp | 2 +- src/CustomCommands.cpp | 8 +++- src/Eggs.cpp | 91 ++++++++++++++--------------------- src/Eggs.hpp | 3 +- src/Groups.cpp | 16 ------- src/Items.cpp | 2 +- src/NPCManager.cpp | 2 +- src/NPCManager.hpp | 2 +- src/Nanos.cpp | 50 +++++++++++++++++++- src/Nanos.hpp | 2 + 14 files changed, 157 insertions(+), 159 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 295b82f..9bb14b0 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -40,7 +40,7 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so sSkillResult_Heal_HP result{}; result.eCT = target->getCharType(); result.iID = target->getID(); - result.iHealHP = heal; + result.iHealHP = healed; result.iHP = target->getCurrentHP(); return SkillResult(sizeof(sSkillResult_Heal_HP), &result); } @@ -170,7 +170,8 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat assert(skillHandler != nullptr); for(ICombatant* target : targets) { - SkillResult result = skillHandler(skill, power, src, target); + 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; @@ -181,7 +182,14 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat return results; } -void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vector affected) { +static void attachSkillResults(std::vector 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 affected) { if(SkillTable.count(nano.iSkillID) == 0) return; @@ -210,16 +218,45 @@ void Abilities::broadcastNanoSkill(CNSocket* sock, sNano& nano, std::vectoreST = skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); - uint8_t* appended = (uint8_t*)(pkt + 1); - for(SkillResult& result : results) { - memcpy(appended, result.payload, resultSize); - appended += resultSize; - } - + 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 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(entity); + + SkillData* skill = &SkillTable[skillID]; + + std::vector 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 Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { std::vector targets; @@ -303,53 +340,3 @@ int Abilities::getCSTBFromST(int eSkillType) { } return result; } - -std::vector Abilities::usePassiveNanoSkill(SkillData* skill, Player* plr) { - assert(skill->drainType == SkillDrainType::PASSIVE); - - EntityRef self = PlayerManager::getSockFromID(plr->iID); - std::vector affected; - std::vector 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 = 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(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) { - // TODO time buff tick - }, - &passiveBuff)) affected.push_back(combatant); - } - - return affected; -} - -void Abilities::init() { - //REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck); -} diff --git a/src/Abilities.hpp b/src/Abilities.hpp index fe531a5..275c2c5 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -60,11 +60,9 @@ struct SkillData { namespace Abilities { extern std::map SkillTable; - void broadcastNanoSkill(CNSocket*, sNano&, std::vector); + void useNanoSkill(CNSocket*, sNano&, std::vector); + void useNPCSkill(EntityRef, int skillID, std::vector); + std::vector matchTargets(SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); - - std::vector usePassiveNanoSkill(SkillData*, Player*); - - void init(); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index c40f7e1..01f6a07 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -1,6 +1,7 @@ #include "Buffs.hpp" #include "PlayerManager.hpp" +#include "NPCManager.hpp" using namespace Buffs; @@ -120,19 +121,15 @@ void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* st self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } -/* -void Buffs::timeBuffTimeoutViewable(EntityRef self, Buff* buff, int ct) { +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(entity); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players - pkt.eCT = ct; // 1 for eggs, at least - pkt.iID = plr->iID; - pkt.iConditionBitFlag = plr->getCompositeCondition(); - PlayerManager::sendToViewable(self.sock, pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); + 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)); } -*/ - -/* MOVE TO EGG LAMBDA -void Buffs::timeBuffTimeout(EntityRef self, BuffStack* buff) { - timeBuffUpdate(self, buff, ETBU_DEL); - timeBuffTimeoutViewable(self); -} */ #pragma endregion diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 1d0c8df..bc7f1f5 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -84,5 +84,5 @@ public: namespace Buffs { void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); - //void timeBuffTimeoutViewable(EntityRef self, Buff* buff, int ct); + void timeBuffTimeout(EntityRef self); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 74f46f1..951e7c2 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -834,7 +834,7 @@ static void playerTick(CNServer *serv, time_t currTime) { if (skill->drainType == SkillDrainType::PASSIVE) { // apply passive buff drainRate = skill->batteryUse[boost * 3]; - Abilities::usePassiveNanoSkill(skill, plr); + Nanos::applyNanoBuff(skill, plr); } } diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 3b48907..217a33b 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -11,6 +11,7 @@ #include "Missions.hpp" #include "Eggs.hpp" #include "Items.hpp" +#include "Abilities.hpp" #include #include @@ -501,9 +502,12 @@ static void buffCommand(std::string full, std::vector& 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& args, CNSocket* sock) { diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 5e81c25..1aa9ef4 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -15,63 +15,47 @@ using namespace Eggs; std::unordered_map 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 - - 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->getCompositeCondition(); + // eggId might be 0 if the buff is made by the /buff command + EntityRef src = eggId == 0 ? sock : EntityRef(eggId); + + 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); + } - return 0; + // use skill + std::vector targets; + targets.push_back(dynamic_cast(plr)); + Abilities::useNPCSkill(src, skillId, targets); } static void eggStep(CNServer* serv, time_t currTime) { @@ -141,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; diff --git a/src/Eggs.hpp b/src/Eggs.hpp index e1460de..9c34ec9 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -14,7 +14,6 @@ namespace Eggs { 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); } diff --git a/src/Groups.cpp b/src/Groups.cpp index 5dda07c..f3894bc 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -240,27 +240,11 @@ void Groups::groupTickInfo(Player* plr) { 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; // 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); diff --git a/src/Items.cpp b/src/Items.cpp index 0186d51..2698c14 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -503,7 +503,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { Buffs::timeBuffUpdate(self, buff, status, stack); }, [](EntityRef self, Buff* buff, time_t currTime) { - // TODO passive tick + // no-op }, &gumballBuff); diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 5baddb6..f1a5e13 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -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) { diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 3429d42..5009e3c 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -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); diff --git a/src/Nanos.cpp b/src/Nanos.cpp index de91c8b..1ff9859 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -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 Nanos::applyNanoBuff(SkillData* skill, Player* plr) { + assert(skill->drainType == SkillDrainType::PASSIVE); + + EntityRef self = PlayerManager::getSockFromID(plr->iID); + std::vector affected; + std::vector 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(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; @@ -94,8 +140,8 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { // passive buff effect resp.eCSTB___Add = 1; int boost = Nanos::getNanoBoost(plr); - std::vector affectedCombatants = Abilities::usePassiveNanoSkill(skill, plr); - if(!affectedCombatants.empty()) Abilities::broadcastNanoSkill(sock, nano, affectedCombatants); + std::vector affectedCombatants = applyNanoBuff(skill, plr); + if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, nano, affectedCombatants); } if (!silent) // silent nano death but only for the summoning player diff --git a/src/Nanos.hpp b/src/Nanos.hpp index 2002330..b4a3955 100644 --- a/src/Nanos.hpp +++ b/src/Nanos.hpp @@ -3,6 +3,7 @@ #include "core/Core.hpp" #include "Player.hpp" +#include "Abilities.hpp" #include @@ -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 applyNanoBuff(SkillData* skill, Player* plr); } From 0dfcc928a91b549eaa03a1e764ce442c0fcf787d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 30 Jul 2022 17:43:17 -0700 Subject: [PATCH 052/104] Refactor group handling --- src/Combat.cpp | 2 +- src/EntityRef.hpp | 4 + src/Groups.cpp | 329 +++++++++++++++++++++--------------------- src/Groups.hpp | 11 +- src/Nanos.cpp | 1 - src/PlayerManager.cpp | 2 +- 6 files changed, 175 insertions(+), 174 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 951e7c2..4f7ee96 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -802,7 +802,7 @@ 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) diff --git a/src/EntityRef.hpp b/src/EntityRef.hpp index 2aab56e..2d974fa 100644 --- a/src/EntityRef.hpp +++ b/src/EntityRef.hpp @@ -38,6 +38,10 @@ struct EntityRef { 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) { diff --git a/src/Groups.cpp b/src/Groups.cpp index f3894bc..2ba539b 100644 --- a/src/Groups.cpp +++ b/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& pcs, std::vector& 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 pcs = group->filter(EntityKind::PLAYER); + std::vector 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 pcs = group->filter(EntityKind::PLAYER); + std::vector 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 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,186 +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(); - - 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 - } - - 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); - uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - - memset(respbuf, 0, resplen); - - 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]; - } - } - - sendToGroup(plr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen); } -void Groups::groupKick(Player* plr) { +void Groups::groupTickInfo(CNSocket* sock) { + Player* plr = PlayerManager::getPlayer(sock); Group* group = plr->group; + std::vector pcs = group->filter(EntityKind::PLAYER); + std::vector 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; + + pkt->iID = plr->iID; + pkt->iMemberPCCnt = (int32_t)pcCount; + pkt->iMemberNPCCnt = (int32_t)npcCount; + + 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)); + } +} + +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)) { - 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; - - 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 - } - - 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() { diff --git a/src/Groups.hpp b/src/Groups.hpp index 18cf6e5..d8ddd67 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -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); } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 1ff9859..f587b68 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -139,7 +139,6 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) { // passive buff effect resp.eCSTB___Add = 1; - int boost = Nanos::getNanoBoost(plr); std::vector affectedCombatants = applyNanoBuff(skill, plr); if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, nano, affectedCombatants); } diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 8419a23..a4c4865 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -42,7 +42,7 @@ void PlayerManager::removePlayer(CNSocket* key) { // leave group if(plr->group != nullptr) - Groups::groupKick(plr); + Groups::groupKick(plr->group, key); // remove player's bullets Combat::Bullets.erase(plr->iID); From f0bb90b547d6a5e21e8b2df17923979b0d019358 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 30 Jul 2022 21:43:28 -0700 Subject: [PATCH 053/104] Move some stuff from playerTick to player combat step --- src/Buffs.cpp | 10 +++-- src/Buffs.hpp | 9 ++-- src/Combat.cpp | 84 ++++++++++++++++++++--------------- src/servers/CNShardServer.hpp | 2 +- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 01f6a07..33760f6 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -9,7 +9,7 @@ void Buff::tick(time_t currTime) { auto it = stacks.begin(); while(it != stacks.end()) { BuffStack& stack = *it; - if(onTick) onTick(self, this, currTime); + //if(onTick) onTick(self, this, currTime); if(stack.durationTicks == 0) { BuffStack deadStack = stack; @@ -22,6 +22,10 @@ void Buff::tick(time_t currTime) { } } +void Buff::combatTick(time_t currTime) { + if(onCombatTick) onCombatTick(self, this, currTime); +} + void Buff::clear() { while(!stacks.empty()) { BuffStack stack = stacks.back(); @@ -84,9 +88,9 @@ bool Buff::isStale() { } /* This will practically never do anything important, but it's here just in case */ -void Buff::updateCallbacks(BuffCallback fOnUpdate, BuffCallback fonTick) { +void Buff::updateCallbacks(BuffCallback fOnUpdate, BuffCallback fOnCombatTick) { if(!onUpdate) onUpdate = fOnUpdate; - if(!onTick) onTick = fonTick; + if(!onCombatTick) onCombatTick = fOnCombatTick; } #pragma region Handlers diff --git a/src/Buffs.hpp b/src/Buffs.hpp index bc7f1f5..5f9bcbc 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -48,10 +48,11 @@ public: int id; /* called just after a stack is added or removed */ BuffCallback onUpdate; - /* called when the buff is ticked */ - BuffCallback onTick; + /* called when the buff is combat-ticked */ + BuffCallback onCombatTick; void tick(time_t); + void combatTick(time_t); void clear(); void addStack(BuffStack* stack); @@ -76,8 +77,8 @@ public: void updateCallbacks(BuffCallback fOnUpdate, BuffCallback fonTick); - Buff(int iid, EntityRef pSelf, BuffCallback fOnUpdate, BuffCallback fOnTick, BuffStack* firstStack) - : self(pSelf), id(iid), onUpdate(fOnUpdate), onTick(fOnTick) { + Buff(int iid, EntityRef pSelf, BuffCallback fOnUpdate, BuffCallback fOnCombatTick, BuffStack* firstStack) + : self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) { addStack(firstStack); } }; diff --git a/src/Combat.cpp b/src/Combat.cpp index 4f7ee96..ef5075e 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -116,7 +116,37 @@ EntityRef Player::getRef() { } 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 @@ -794,6 +824,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; @@ -819,39 +850,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()) { - // nano has skill data - SkillData* skill = &Abilities::SkillTable[nano.iSkillID]; - int boost = Nanos::getNanoBoost(plr); - std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl; - - if (skill->drainType == SkillDrainType::PASSIVE) { - // apply passive buff - drainRate = skill->batteryUse[boost * 3]; - Nanos::applyNanoBuff(skill, plr); - } - } - - 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; + 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]; + if (skill->drainType == SkillDrainType::PASSIVE) + Nanos::applyNanoBuff(skill, plr); + // ^ composite condition calculation is separate from combat for responsiveness } } @@ -879,7 +892,6 @@ static void playerTick(CNServer *serv, time_t currTime) { } else it++; } - // if (transmit) { INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt); @@ -895,9 +907,11 @@ 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() { diff --git a/src/servers/CNShardServer.hpp b/src/servers/CNShardServer.hpp index 8ab7360..c585428 100644 --- a/src/servers/CNShardServer.hpp +++ b/src/servers/CNShardServer.hpp @@ -7,7 +7,7 @@ #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); -#define MS_PER_PLAYER_TICK 2000 +#define MS_PER_PLAYER_TICK 500 class CNShardServer : public CNServer { private: From ec71fd8f46750666c6031cba722a8073775b604c Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 30 Jul 2022 21:46:17 -0700 Subject: [PATCH 054/104] Add overload to remove specific class of buff I initially added this because, despite the higher tickrate for composite condition calculations thanks to the last commit, there is still a slight status icon delay when rapidly switching nanos. I attempted to use this to make that problem go away and for whatever reason it wasn't effective, but I figure it would be useful to have anyway so I'm keeping it. --- src/Buffs.cpp | 12 ++++++++++++ src/Buffs.hpp | 1 + src/Combat.cpp | 12 ++++++++++++ src/Entities.hpp | 2 ++ src/Player.hpp | 1 + 5 files changed, 28 insertions(+) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 33760f6..072001f 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -34,6 +34,18 @@ void Buff::clear() { } } +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()); diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 5f9bcbc..3247143 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -54,6 +54,7 @@ public: void tick(time_t); void combatTick(time_t); void clear(); + void clear(BuffClass buffClass); void addStack(BuffStack* stack); /* diff --git a/src/Combat.cpp b/src/Combat.cpp index ef5075e..4220b1e 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -48,6 +48,16 @@ void Player::removeBuff(int 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(); @@ -161,6 +171,8 @@ Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ void CombatNPC::removeBuff(int buffId) { /* stubbed */ } +void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ } + bool CombatNPC::hasBuff(int buffId) { /* stubbed */ return false; } diff --git a/src/Entities.hpp b/src/Entities.hpp index f7234af..9078d4f 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -50,6 +50,7 @@ public: virtual bool addBuff(int, BuffCallback, BuffCallback, 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; @@ -124,6 +125,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback 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; diff --git a/src/Player.hpp b/src/Player.hpp index a697dae..224ea90 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -92,6 +92,7 @@ struct Player : public Entity, public ICombatant { virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback 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; From 751fd4fd9d15706308714b2672127a3a0545b864 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 30 Jul 2022 22:50:03 -0700 Subject: [PATCH 055/104] Sync with master --- src/Chunking.cpp | 22 +++++++++++----------- src/Chunking.hpp | 14 ++++++-------- src/Entities.hpp | 7 ++----- src/Groups.cpp | 1 + src/Groups.hpp | 7 +------ src/NPCManager.hpp | 1 + 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/Chunking.cpp b/src/Chunking.cpp index 71ce013..65231fe 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -26,7 +26,7 @@ static void newChunk(ChunkPos pos) { // add the chunk to the cache of all players and NPCs in the surrounding chunks std::set surroundings = getViewableChunks(pos); for (Chunk* c : surroundings) - for (const EntityRef& ref : c->entities) + for (const EntityRef ref : c->entities) ref.getEntity()->viewableChunks.insert(chunk); } @@ -41,14 +41,14 @@ static void deleteChunk(ChunkPos pos) { // remove the chunk from the cache of all players and NPCs in the surrounding chunks std::set surroundings = getViewableChunks(pos); for(Chunk* c : surroundings) - for (const EntityRef& ref : c->entities) + for (const EntityRef ref : c->entities) ref.getEntity()->viewableChunks.erase(chunk); chunks.erase(pos); // remove from map delete chunk; // free from memory } -void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { +void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) { if (!chunkExists(chunkPos)) return; // shouldn't happen @@ -58,7 +58,7 @@ void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { chunks[chunkPos]->nplayers++; } -void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { +void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { if (!chunkExists(chunkPos)) return; // do nothing if chunk doesn't even exist @@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { deleteChunk(chunkPos); } -void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { +void Chunking::addEntityToChunks(std::set chnks, const EntityRef ref) { Entity *ent = ref.getEntity(); bool alive = ent->isExtant(); // TODO: maybe optimize this, potentially using AROUND packets? for (Chunk *chunk : chnks) { - for (const EntityRef& otherRef : chunk->entities) { + for (const EntityRef otherRef : chunk->entities) { // skip oneself if (ref == otherRef) continue; @@ -107,13 +107,13 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { } } -void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef& ref) { +void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef ref) { Entity *ent = ref.getEntity(); bool alive = ent->isExtant(); // TODO: same as above for (Chunk *chunk : chnks) { - for (const EntityRef& otherRef : chunk->entities) { + for (const EntityRef otherRef : chunk->entities) { // skip oneself if (ref == otherRef) continue; @@ -154,7 +154,7 @@ static void emptyChunk(ChunkPos chunkPos) { // unspawn all of the mobs/npcs std::set refs(chunk->entities); - for (const EntityRef& ref : refs) { + for (const EntityRef ref : refs) { if (ref.kind == EntityKind::PLAYER) assert(0); @@ -163,7 +163,7 @@ static void emptyChunk(ChunkPos chunkPos) { } } -void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) { +void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) { Entity* ent = ref.getEntity(); // move to other chunk's player set @@ -267,7 +267,7 @@ void Chunking::createInstance(uint64_t instanceID) { std::cout << "Creating instance " << instanceID << std::endl; for (ChunkPos &coords : templateChunks) { - for (const EntityRef& ref : chunks[coords]->entities) { + for (const EntityRef ref : chunks[coords]->entities) { if (ref.kind == EntityKind::PLAYER) continue; diff --git a/src/Chunking.hpp b/src/Chunking.hpp index 3673638..4b4cee0 100644 --- a/src/Chunking.hpp +++ b/src/Chunking.hpp @@ -1,13 +1,11 @@ #pragma once -#include "Entities.hpp" +#include "EntityRef.hpp" #include #include #include -struct EntityRef; - class Chunk { public: std::set entities; @@ -34,13 +32,13 @@ namespace Chunking { extern const ChunkPos INVALID_CHUNK; - void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to); + void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to); - void trackEntity(ChunkPos chunkPos, const EntityRef& ref); - void untrackEntity(ChunkPos chunkPos, const EntityRef& ref); + void trackEntity(ChunkPos chunkPos, const EntityRef ref); + void untrackEntity(ChunkPos chunkPos, const EntityRef ref); - void addEntityToChunks(std::set chnks, const EntityRef& ref); - void removeEntityFromChunks(std::set chnks, const EntityRef& ref); + void addEntityToChunks(std::set chnks, const EntityRef ref); + void removeEntityFromChunks(std::set chnks, const EntityRef ref); bool chunkExists(ChunkPos chunk); ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID); diff --git a/src/Entities.hpp b/src/Entities.hpp index 9078d4f..28a47c0 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -1,19 +1,16 @@ #pragma once #include "core/Core.hpp" -#include "Chunking.hpp" #include "EntityRef.hpp" #include "Buffs.hpp" +#include "Chunking.hpp" +#include "Groups.hpp" #include #include #include -/* forward declaration(s) */ -class Chunk; -struct Group; - enum class AIState { INACTIVE, ROAMING, diff --git a/src/Groups.cpp b/src/Groups.cpp index 2ba539b..ac58d6e 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -4,6 +4,7 @@ #include "Player.hpp" #include "PlayerManager.hpp" +#include "Entities.hpp" /* * NOTE: Variadic response packets that list group members are technically diff --git a/src/Groups.hpp b/src/Groups.hpp index d8ddd67..b8a1782 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -1,13 +1,8 @@ #pragma once -#include "Entities.hpp" +#include "EntityRef.hpp" #include -#include -#include - -/* forward declaration(s) */ -struct Player; struct Group { std::vector members; diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 5009e3c..c8738d0 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -5,6 +5,7 @@ #include "Transport.hpp" #include "Chunking.hpp" +#include "Entities.hpp" #include #include From 80dd6b54794f110dace14e990baee8f6c3aeb609 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 27 Nov 2022 17:36:47 -0500 Subject: [PATCH 056/104] [WIP] Active power handling TODO: - recall (self and group) is broken - revive (only group) is broken - damage + debuff is unimplemented --- src/Abilities.cpp | 64 +++++++++++++++++++++++++++++++---------------- src/Abilities.hpp | 4 +-- src/Nanos.cpp | 19 ++++---------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 9bb14b0..a94f69e 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -35,7 +35,8 @@ static SkillResult handleSkillDamage(SkillData* skill, int power, ICombatant* so 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); + double scalingFactor = target->getMaxHP() / 1000.0; + int healed = target->heal(sourceRef, heal * scalingFactor); sSkillResult_Heal_HP result{}; result.eCT = target->getCharType(); @@ -97,6 +98,7 @@ static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* sour if(source->getCharType() != 1) return SkillResult(); // only Players are valid sources for recall Player* plr = dynamic_cast(source); + PlayerManager::sendPlayerTo(source->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); sSkillResult_Move result{}; result.eCT = target->getCharType(); @@ -154,20 +156,22 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat resultSize = sizeof(sSkillResult_BatteryDrain); skillHandler = handleSkillBatteryDrain; break; - case EST_RECALL: - case EST_RECALL_GROUP: + case EST_RECALL: // still soft lock + case EST_RECALL_GROUP: // works for player who uses it resultSize = sizeof(sSkillResult_Move); skillHandler = handleSkillMove; break; - case EST_PHOENIX_GROUP: + case EST_PHOENIX_GROUP: // broken resultSize = sizeof(sSkillResult_Resurrect); skillHandler = handleSkillResurrect; break; + case EST_RETROROCKET_SELF: + // no-op + return results; default: std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl; return results; } - assert(skillHandler != nullptr); for(ICombatant* target : targets) { assert(target != nullptr); @@ -189,15 +193,21 @@ static void attachSkillResults(std::vector results, size_t resultSi } } -void Abilities::useNanoSkill(CNSocket* sock, sNano& nano, std::vector affected) { - if(SkillTable.count(nano.iSkillID) == 0) - return; +void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector affected) { - SkillData* skill = &SkillTable[nano.iSkillID]; Player* plr = PlayerManager::getPlayer(sock); - std::vector results = handleSkill(skill, Nanos::getNanoBoost(plr), plr, affected); - size_t resultSize = results.back().size; // guaranteed to be the same for every item + int boost = 0; + if (Nanos::getNanoBoost(plr)) + boost = 3; + + nano.iStamina -= skill->batteryUse[boost]; + if (nano.iStamina < 0) + nano.iStamina = 0; + + std::vector results = handleSkill(skill, boost, plr, affected); + size_t resultSize = 0; // guaranteed to be the same for every item + if (!results.empty()) resultSize = results.back().size; 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"; @@ -221,6 +231,9 @@ void Abilities::useNanoSkill(CNSocket* sock, sNano& nano, std::vectorsendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); + + if (nano.iStamina <= 0) + Nanos::summonNano(sock, -1); } void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector affected) { @@ -257,21 +270,30 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); } -std::vector Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { +std::vector Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { - std::vector targets; + std::vector targets; for (int i = 0; i < count; i++) { int32_t id = ids[i]; if (skill->targetType == SkillTargetType::MOBS) { - // mob? - if (NPCManager::NPCs.find(id) != NPCManager::NPCs.end()) targets.push_back(id); - else std::cout << "[WARN] skill: id not found\n"; - } else { - // player? - CNSocket* sock = PlayerManager::getSockFromID(id); - if (sock != nullptr) targets.push_back(sock); - else std::cout << "[WARN] skill: sock not found\n"; + // mob + if (NPCManager::NPCs.find(id) != NPCManager::NPCs.end()) { + BaseNPC* npc = NPCManager::NPCs[id]; + if (npc->kind == EntityKind::COMBAT_NPC || npc->kind == EntityKind::MOB) { + targets.push_back(dynamic_cast(npc)); + continue; + } + } + std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n"; + } else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) { + // players (?) + Player* plr = PlayerManager::getPlayerFromID(id); + if (plr != nullptr) { + targets.push_back(dynamic_cast(plr)); + continue; + } + std::cout << "[WARN] skill: invalid player target (id " << id << ")\n"; } } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 275c2c5..04680b9 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -60,9 +60,9 @@ struct SkillData { namespace Abilities { extern std::map SkillTable; - void useNanoSkill(CNSocket*, sNano&, std::vector); + void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector); void useNPCSkill(EntityRef, int skillID, std::vector); - std::vector matchTargets(SkillData*, int, int32_t*); + std::vector matchTargets(SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index f587b68..03fae57 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -140,7 +140,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { // passive buff effect resp.eCSTB___Add = 1; std::vector affectedCombatants = applyNanoBuff(skill, plr); - if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, nano, affectedCombatants); + if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants); } if (!silent) // silent nano death but only for the summoning player @@ -309,26 +309,17 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { return; } - int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; + sNano& nano = plr->Nanos[plr->activeNano]; + int16_t skillID = nano.iSkillID; SkillData* skillData = &Abilities::SkillTable[skillID]; DEBUGLOG( std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; ) - int boost = 0; - if (getNanoBoost(plr)) - boost = 1; - - plr->Nanos[plr->activeNano].iStamina -= Abilities::SkillTable[skillID].batteryUse[boost*3]; - if (plr->Nanos[plr->activeNano].iStamina < 0) - plr->Nanos[plr->activeNano].iStamina = 0; - // TODO ABILITIES - std::vector targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); - /*for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]);*/ + std::vector targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); + Abilities::useNanoSkill(sock, skillData, nano, targetData); if (plr->Nanos[plr->activeNano].iStamina < 0) summonNano(sock, -1); From 9312706524097377225b462000e908f165dcb2ce Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 11 Jul 2023 17:42:08 -0400 Subject: [PATCH 057/104] [WIP] Fix targeting for groups --- src/Abilities.cpp | 28 ++++++++++++++++++++++++---- src/Abilities.hpp | 4 ++-- src/Eggs.cpp | 2 +- src/Nanos.cpp | 31 +++++++++++-------------------- src/PlayerManager.cpp | 3 +-- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a94f69e..ed29699 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -270,10 +270,30 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); } -std::vector Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) { +static std::vector entityRefsToCombatants(std::vector refs) { + std::vector combatants; + for(EntityRef ref : refs) { + if(ref.kind == EntityKind::PLAYER) + combatants.push_back(dynamic_cast(PlayerManager::getPlayer(ref.sock))); + else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB) + combatants.push_back(dynamic_cast(ref.getEntity())); + } + return combatants; +} +std::vector Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) { + + if(skill->targetType == SkillTargetType::GROUP) { + // group + if(count != 1 || ids[0] != src->getID()) { + std::cout << "[WARN] skill: bad group targeting (id " << ids[0] << ")\n"; + return {}; + } + return entityRefsToCombatants(src->getGroupMembers()); + } + + // individuals std::vector targets; - for (int i = 0; i < count; i++) { int32_t id = ids[i]; if (skill->targetType == SkillTargetType::MOBS) { @@ -286,8 +306,8 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, in } } std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n"; - } else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) { - // players (?) + } else if(skill->targetType == SkillTargetType::PLAYERS) { + // player Player* plr = PlayerManager::getPlayerFromID(id); if (plr != nullptr) { targets.push_back(dynamic_cast(plr)); diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 04680b9..c8d8ee1 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -21,7 +21,7 @@ enum class SkillEffectTarget { enum class SkillTargetType { MOBS = 1, - SELF = 2, + PLAYERS = 2, GROUP = 3 }; @@ -63,6 +63,6 @@ namespace Abilities { void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector); void useNPCSkill(EntityRef, int skillID, std::vector); - std::vector matchTargets(SkillData*, int, int32_t*); + std::vector matchTargets(ICombatant*, SkillData*, int, int32_t*); int getCSTBFromST(int eSkillType); } diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 1aa9ef4..04e276d 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -29,7 +29,7 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { SkillData* skill = &Abilities::SkillTable[skillId]; if(skill->drainType == SkillDrainType::PASSIVE) { // apply buff - if(skill->targetType != SkillTargetType::SELF) { + if(skill->targetType != SkillTargetType::PLAYERS) { std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl; } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 03fae57..0c47219 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -73,16 +73,6 @@ std::vector Nanos::applyNanoBuff(SkillData* skill, Player* plr) { assert(skill->drainType == SkillDrainType::PASSIVE); EntityRef self = PlayerManager::getSockFromID(plr->iID); - std::vector affected; - std::vector 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; @@ -95,21 +85,22 @@ std::vector Nanos::applyNanoBuff(SkillData* skill, Player* plr) { 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 + // for passive skills, using just the player as a target is fine + // this is because the group skill type will ignore the count, + // and the other option is single-target + std::vector targets = Abilities::matchTargets(dynamic_cast(plr), skill, 1, &plr->iID); + std::vector affected; + for (ICombatant* target : targets) { - passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO; - ICombatant* combatant = dynamic_cast(entity); - if(combatant->addBuff(timeBuffId, + passiveBuff.buffStackClass = target == plr ? BuffClass::NANO : BuffClass::GROUP_NANO; + if(target->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); + &passiveBuff)) affected.push_back(target); } return affected; @@ -317,8 +308,8 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; ) - // TODO ABILITIES - std::vector targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); + ICombatant* plrCombatant = dynamic_cast(plr); + std::vector targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); Abilities::useNanoSkill(sock, skillData, nano, targetData); if (plr->Nanos[plr->activeNano].iStamina < 0) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index a4c4865..dfd0e5a 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -401,12 +401,11 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { if (!(plr->hasBuff(ECSB_PHOENIX))) return; // sanity check plr->Nanos[plr->activeNano].iStamina = 0; - // TODO ABILITIES - //Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); // fallthrough case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano plr->HP = PC_MAXHEALTH(plr->level) / 2; break; + default: // plain respawn plr->HP = PC_MAXHEALTH(plr->level) / 2; // fallthrough From eaebe3ba4c75517a17cdc246fb9ff0aaeda74a01 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 11 Jul 2023 20:21:37 -0400 Subject: [PATCH 058/104] Fix self recall --- src/Abilities.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index ed29699..b84e629 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -283,6 +283,9 @@ static std::vector entityRefsToCombatants(std::vector re std::vector Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) { + if(skill->effectTarget == SkillEffectTarget::SELF) + return {src}; // client sends 0 targets for certain self-targeting skills (recall) + if(skill->targetType == SkillTargetType::GROUP) { // group if(count != 1 || ids[0] != src->getID()) { From 78c15e2899efa7547ea123c39baba77259114afe Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 12 Jul 2023 17:04:09 -0400 Subject: [PATCH 059/104] Make SkillType an enum class --- src/Abilities.cpp | 92 ++++++++++++++++++++++---------------------- src/Abilities.hpp | 45 +++++++++++++++++++++- src/Items.cpp | 2 +- src/core/Defines.hpp | 43 +-------------------- 4 files changed, 91 insertions(+), 91 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index b84e629..14d4f0a 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -126,50 +126,50 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat switch(skill->skillType) { - case EST_DAMAGE: + case SkillType::DAMAGE: resultSize = sizeof(sSkillResult_Damage); skillHandler = handleSkillDamage; break; - case EST_HEAL_HP: - case EST_RETURNHOMEHEAL: + case SkillType::HEAL_HP: + case SkillType::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: + case SkillType::JUMP: + case SkillType::RUN: + case SkillType::FREEDOM: + case SkillType::PHOENIX: + case SkillType::INVULNERABLE: + case SkillType::MINIMAPENEMY: + case SkillType::MINIMAPTRESURE: + case SkillType::NANOSTIMPAK: + case SkillType::PROTECTBATTERY: + case SkillType::PROTECTINFECTION: + case SkillType::REWARDBLOB: + case SkillType::REWARDCASH: + case SkillType::STAMINA_SELF: + case SkillType::STEALTH: resultSize = sizeof(sSkillResult_Buff); skillHandler = handleSkillBuff; break; - case EST_BATTERYDRAIN: + case SkillType::BATTERYDRAIN: resultSize = sizeof(sSkillResult_BatteryDrain); skillHandler = handleSkillBatteryDrain; break; - case EST_RECALL: // still soft lock - case EST_RECALL_GROUP: // works for player who uses it + case SkillType::RECALL: // still soft lock + case SkillType::RECALL_GROUP: // works for player who uses it resultSize = sizeof(sSkillResult_Move); skillHandler = handleSkillMove; break; - case EST_PHOENIX_GROUP: // broken + case SkillType::PHOENIX_GROUP: // broken resultSize = sizeof(sSkillResult_Resurrect); skillHandler = handleSkillResurrect; break; - case EST_RETROROCKET_SELF: + case SkillType::RETROROCKET_SELF: // no-op return results; default: - std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl; + std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl; return results; } @@ -178,7 +178,7 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat 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; + std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl; continue; } results.push_back(result); @@ -225,7 +225,7 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: pkt->iSkillID = nano.iSkillID; pkt->iNanoStamina = nano.iStamina; pkt->bNanoDeactive = nano.iStamina <= 0; - pkt->eST = skill->skillType; + pkt->eST = (int32_t)skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1)); @@ -263,7 +263,7 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector 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->eST = (int32_t)skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1)); @@ -324,62 +324,62 @@ std::vector Abilities::matchTargets(ICombatant* src, SkillData* ski } /* ripped from client (enums emplaced) */ -int Abilities::getCSTBFromST(int eSkillType) { +int Abilities::getCSTBFromST(SkillType skillType) { int result = 0; - switch (eSkillType) + switch (skillType) { - case EST_RUN: + case SkillType::RUN: result = ECSB_UP_MOVE_SPEED; break; - case EST_JUMP: + case SkillType::JUMP: result = ECSB_UP_JUMP_HEIGHT; break; - case EST_STEALTH: + case SkillType::STEALTH: result = ECSB_UP_STEALTH; break; - case EST_PHOENIX: + case SkillType::PHOENIX: result = ECSB_PHOENIX; break; - case EST_PROTECTBATTERY: + case SkillType::PROTECTBATTERY: result = ECSB_PROTECT_BATTERY; break; - case EST_PROTECTINFECTION: + case SkillType::PROTECTINFECTION: result = ECSB_PROTECT_INFECTION; break; - case EST_SNARE: + case SkillType::SNARE: result = ECSB_DN_MOVE_SPEED; break; - case EST_SLEEP: + case SkillType::SLEEP: result = ECSB_MEZ; break; - case EST_MINIMAPENEMY: + case SkillType::MINIMAPENEMY: result = ECSB_MINIMAP_ENEMY; break; - case EST_MINIMAPTRESURE: + case SkillType::MINIMAPTRESURE: result = ECSB_MINIMAP_TRESURE; break; - case EST_REWARDBLOB: + case SkillType::REWARDBLOB: result = ECSB_REWARD_BLOB; break; - case EST_REWARDCASH: + case SkillType::REWARDCASH: result = ECSB_REWARD_CASH; break; - case EST_INFECTIONDAMAGE: + case SkillType::INFECTIONDAMAGE: result = ECSB_INFECTION; break; - case EST_FREEDOM: + case SkillType::FREEDOM: result = ECSB_FREEDOM; break; - case EST_BOUNDINGBALL: + case SkillType::BOUNDINGBALL: result = ECSB_BOUNDINGBALL; break; - case EST_INVULNERABLE: + case SkillType::INVULNERABLE: result = ECSB_INVULNERABLE; break; - case EST_BUFFHEAL: + case SkillType::BUFFHEAL: result = ECSB_HEAL; break; - case EST_NANOSTIMPAK: + case SkillType::NANOSTIMPAK: result = ECSB_STIMPAKSLOT1; break; } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index c8d8ee1..672ac5c 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -10,6 +10,47 @@ constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); +enum class SkillType { + DAMAGE = 1, + HEAL_HP = 2, + KNOCKDOWN = 3, + SLEEP = 4, + SNARE = 5, + HEAL_STAMINA = 6, + STAMINA_SELF = 7, + STUN = 8, + WEAPONSLOW = 9, + JUMP = 10, + RUN = 11, + STEALTH = 12, + SWIM = 13, + MINIMAPENEMY = 14, + MINIMAPTRESURE = 15, + PHOENIX = 16, + PROTECTBATTERY = 17, + PROTECTINFECTION = 18, + REWARDBLOB = 19, + REWARDCASH = 20, + BATTERYDRAIN = 21, + CORRUPTIONATTACK = 22, + INFECTIONDAMAGE = 23, + KNOCKBACK = 24, + FREEDOM = 25, + PHOENIX_GROUP = 26, + RECALL = 27, + RECALL_GROUP = 28, + RETROROCKET_SELF = 29, + BLOODSUCKING = 30, + BOUNDINGBALL = 31, + INVULNERABLE = 32, + NANOSTIMPAK = 33, + RETURNHOMEHEAL = 34, + BUFFHEAL = 35, + EXTRABANK = 36, + CORRUPTIONATTACKWIN = 38, + CORRUPTIONATTACKLOSE = 39, +}; + enum class SkillEffectTarget { POINT = 1, SELF = 2, @@ -43,7 +84,7 @@ struct SkillResult { }; struct SkillData { - int skillType; // eST + SkillType skillType; // eST SkillEffectTarget effectTarget; int effectType; // always 1? SkillTargetType targetType; @@ -64,5 +105,5 @@ namespace Abilities { void useNPCSkill(EntityRef, int skillID, std::vector); std::vector matchTargets(ICombatant*, SkillData*, int, int32_t*); - int getCSTBFromST(int eSkillType); + int getCSTBFromST(SkillType skillType); } diff --git a/src/Items.cpp b/src/Items.cpp index 2698c14..70a6b9e 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -482,7 +482,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { resp->iSlotNum = request->iSlotNum; resp->RemainItem = gumball; resp->iTargetCnt = 1; - resp->eST = EST_NANOSTIMPAK; + resp->eST = (int32_t)SkillType::NANOSTIMPAK; resp->iSkillID = 144; int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot; diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index 4e20658..7939d14 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -46,49 +46,8 @@ enum class ePCRegenType { End }; -// nano powers +// nano power flags enum { - EST_NONE = 0, - EST_DAMAGE = 1, - EST_HEAL_HP = 2, - EST_KNOCKDOWN = 3, - EST_SLEEP = 4, - EST_SNARE = 5, - EST_HEAL_STAMINA = 6, - EST_STAMINA_SELF = 7, - EST_STUN = 8, - EST_WEAPONSLOW = 9, - EST_JUMP = 10, - EST_RUN = 11, - EST_STEALTH = 12, - EST_SWIM = 13, - EST_MINIMAPENEMY = 14, - EST_MINIMAPTRESURE = 15, - EST_PHOENIX = 16, - EST_PROTECTBATTERY = 17, - EST_PROTECTINFECTION = 18, - EST_REWARDBLOB = 19, - EST_REWARDCASH = 20, - EST_BATTERYDRAIN = 21, - EST_CORRUPTIONATTACK = 22, - EST_INFECTIONDAMAGE = 23, - EST_KNOCKBACK = 24, - EST_FREEDOM = 25, - EST_PHOENIX_GROUP = 26, - EST_RECALL = 27, - EST_RECALL_GROUP = 28, - EST_RETROROCKET_SELF = 29, - EST_BLOODSUCKING = 30, - EST_BOUNDINGBALL = 31, - EST_INVULNERABLE = 32, - EST_NANOSTIMPAK = 33, - EST_RETURNHOMEHEAL = 34, - EST_BUFFHEAL = 35, - EST_EXTRABANK = 36, - EST__END = 37, - EST_CORRUPTIONATTACKWIN = 38, - EST_CORRUPTIONATTACKLOSE = 39, - ECSB_NONE = 0, ECSB_UP_MOVE_SPEED = 1, ECSB_UP_SWIM_SPEED = 2, From 945599f93c8e6d5f03d96eab11e08dfe59e3f567 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 12 Jul 2023 17:43:05 -0400 Subject: [PATCH 060/104] Fix recall --- src/Abilities.cpp | 21 ++++++++++++++++----- src/PlayerManager.cpp | 8 ++++++++ src/PlayerManager.hpp | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 14d4f0a..8b4d19c 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -97,8 +97,13 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata 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(source); - PlayerManager::sendPlayerTo(source->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); + if(source == target) { + // no trailing struct for self + PlayerManager::sendPlayerTo(target->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance); + return SkillResult(); + } sSkillResult_Move result{}; result.eCT = target->getCharType(); @@ -230,7 +235,12 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: 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); + + if(skill->skillType == SkillType::RECALL_GROUP) + // group recall packet is sent only to group members + PlayerManager::sendToGroup(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen); + else + PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen); if (nano.iStamina <= 0) Nanos::summonNano(sock, -1); @@ -283,9 +293,6 @@ static std::vector entityRefsToCombatants(std::vector re std::vector Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) { - if(skill->effectTarget == SkillEffectTarget::SELF) - return {src}; // client sends 0 targets for certain self-targeting skills (recall) - if(skill->targetType == SkillTargetType::GROUP) { // group if(count != 1 || ids[0] != src->getID()) { @@ -295,6 +302,10 @@ std::vector Abilities::matchTargets(ICombatant* src, SkillData* ski return entityRefsToCombatants(src->getGroupMembers()); } + // this check *has* to happen after the group check above due to cases like group recall that use both + if(skill->effectTarget == SkillEffectTarget::SELF) + return {src}; // client sends 0 targets for certain self-targeting skills (recall) + // individuals std::vector targets; for (int i = 0; i < count; i++) { diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index dfd0e5a..c07df89 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -338,6 +338,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { delete lm; } +void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) { + Player* plr = getPlayer(sock); + if (plr->group == nullptr) + return; + for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER)) + ref.sock->sendPacket(buf, type, size); +} + void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { Player* plr = getPlayer(sock); for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 1e15ddc..ad9c767 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -33,6 +33,7 @@ namespace PlayerManager { CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname); WarpLocation *getRespawnPoint(Player *plr); + void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size); void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size); // TODO: unify this under the new Entity system From 773e4b36e1273249e908a6cc9676ff48417d508f Mon Sep 17 00:00:00 2001 From: gsemaj Date: Thu, 20 Jul 2023 17:53:13 -0400 Subject: [PATCH 061/104] Reuse `useNanoSkill` codepath for passive powers --- src/Abilities.cpp | 42 +++++++++++++++++++++++++++--------------- src/Combat.cpp | 9 ++++++--- src/Nanos.cpp | 45 +++++---------------------------------------- src/Nanos.hpp | 1 - 4 files changed, 38 insertions(+), 59 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 8b4d19c..34e5d12 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -57,6 +57,23 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat } static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) { + BuffStack passiveBuff = { + skill->drainType == SkillDrainType::PASSIVE ? 1 : skill->durationTime[power], // ticks + skill->values[0][power], // value + source->getRef(), // source + source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class + }; + + int timeBuffId = Abilities::getCSTBFromST(skill->skillType); + if(!target->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)) return SkillResult(); // no result if already buffed + sSkillResult_Buff result{}; result.eCT = target->getCharType(); result.iID = target->getID(); @@ -206,14 +223,16 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: if (Nanos::getNanoBoost(plr)) boost = 3; - nano.iStamina -= skill->batteryUse[boost]; - if (nano.iStamina < 0) - nano.iStamina = 0; + if(skill->drainType == SkillDrainType::ACTIVE) { + nano.iStamina -= skill->batteryUse[boost]; + if (nano.iStamina <= 0) + nano.iStamina = 0; + } std::vector results = handleSkill(skill, boost, plr, affected); - size_t resultSize = 0; // guaranteed to be the same for every item - if (!results.empty()) resultSize = results.back().size; + if(results.empty()) return; // no effect; no need for confirmation packets + 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; @@ -241,9 +260,6 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: PlayerManager::sendToGroup(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen); else PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen); - - if (nano.iStamina <= 0) - Nanos::summonNano(sock, -1); } void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector affected) { @@ -293,14 +309,8 @@ static std::vector entityRefsToCombatants(std::vector re std::vector Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) { - if(skill->targetType == SkillTargetType::GROUP) { - // group - if(count != 1 || ids[0] != src->getID()) { - std::cout << "[WARN] skill: bad group targeting (id " << ids[0] << ")\n"; - return {}; - } + if(skill->targetType == SkillTargetType::GROUP) return entityRefsToCombatants(src->getGroupMembers()); - } // this check *has* to happen after the group check above due to cases like group recall that use both if(skill->effectTarget == SkillEffectTarget::SELF) @@ -393,6 +403,8 @@ int Abilities::getCSTBFromST(SkillType skillType) { case SkillType::NANOSTIMPAK: result = ECSB_STIMPAKSLOT1; break; + default: + break; } return result; } diff --git a/src/Combat.cpp b/src/Combat.cpp index 4220b1e..4c932a9 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -874,9 +874,12 @@ static void playerTick(CNServer *serv, time_t currTime) { if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) { // nano has skill data SkillData* skill = &Abilities::SkillTable[nano->iSkillID]; - if (skill->drainType == SkillDrainType::PASSIVE) - Nanos::applyNanoBuff(skill, plr); - // ^ composite condition calculation is separate from combat for responsiveness + if (skill->drainType == SkillDrainType::PASSIVE) { + ICombatant* src = dynamic_cast(plr); + int32_t targets[] = { plr->iID }; + std::vector affectedCombatants = Abilities::matchTargets(src, skill, 1, targets); + Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants); + } } } diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 0c47219..8fa9629 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -69,43 +69,6 @@ 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 Nanos::applyNanoBuff(SkillData* skill, Player* plr) { - assert(skill->drainType == SkillDrainType::PASSIVE); - - EntityRef self = PlayerManager::getSockFromID(plr->iID); - - 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 passive skills, using just the player as a target is fine - // this is because the group skill type will ignore the count, - // and the other option is single-target - std::vector targets = Abilities::matchTargets(dynamic_cast(plr), skill, 1, &plr->iID); - std::vector affected; - for (ICombatant* target : targets) { - - passiveBuff.buffStackClass = target == plr ? BuffClass::NANO : BuffClass::GROUP_NANO; - if(target->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(target); - } - - return affected; -} - void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp); resp.iActiveNanoSlotNum = slot; @@ -130,8 +93,10 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) { // passive buff effect resp.eCSTB___Add = 1; - std::vector affectedCombatants = applyNanoBuff(skill, plr); - if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants); + ICombatant* src = dynamic_cast(plr); + int32_t targets[] = { plr->iID }; + std::vector affectedCombatants = Abilities::matchTargets(src, skill, 1, targets); + Abilities::useNanoSkill(sock, skill, nano, affectedCombatants); } if (!silent) // silent nano death but only for the summoning player @@ -312,7 +277,7 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { std::vector targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); Abilities::useNanoSkill(sock, skillData, nano, targetData); - if (plr->Nanos[plr->activeNano].iStamina < 0) + if (plr->Nanos[plr->activeNano].iStamina <= 0) summonNano(sock, -1); } diff --git a/src/Nanos.hpp b/src/Nanos.hpp index b4a3955..e21a297 100644 --- a/src/Nanos.hpp +++ b/src/Nanos.hpp @@ -26,5 +26,4 @@ namespace Nanos { void summonNano(CNSocket* sock, int slot, bool silent = false); int nanoStyle(int nanoID); bool getNanoBoost(Player* plr); - std::vector applyNanoBuff(SkillData* skill, Player* plr); } From 49fa6d65c43a3601748c5e57b853ad107962f0eb Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 22 Jul 2023 15:10:00 -0400 Subject: [PATCH 062/104] Fix seg fault with egg powers --- src/Abilities.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 34e5d12..fe987ed 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -274,6 +274,8 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector SkillData* skill = &SkillTable[skillID]; std::vector results = handleSkill(skill, 0, src, affected); + if(results.empty()) return; // no effect; no need for confirmation packets + 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)) { From bd1abcef725ec0354791749615fd5d1dcf62b2a1 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jul 2023 10:13:22 -0400 Subject: [PATCH 063/104] Don't add buff if player dead --- src/Combat.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Combat.cpp b/src/Combat.cpp index 4c932a9..b83583a 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -21,6 +21,9 @@ std::map> Combat::Bullets; #pragma region Player bool Player::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { + if(!isAlive()) + return false; + EntityRef self = PlayerManager::getSockFromID(iID); if(!hasBuff(buffId)) { From 03abb6f830176b67cb294d45deb0c11dbdd59dc1 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jul 2023 10:15:00 -0400 Subject: [PATCH 064/104] Reorder abilities to match client handling --- src/Abilities.cpp | 52 +++++++++++++++++++++++++++++++---------------- src/Abilities.hpp | 8 ++++---- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index fe987ed..f27b3fe 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -56,6 +56,11 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result); } +static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) { + // TODO abilities + return SkillResult(); +} + static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) { BuffStack passiveBuff = { skill->drainType == SkillDrainType::PASSIVE ? 1 : skill->durationTime[power], // ticks @@ -157,39 +162,52 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat resultSize = sizeof(sSkillResult_Heal_HP); skillHandler = handleSkillHealHP; break; + case SkillType::KNOCKDOWN: + case SkillType::SLEEP: + case SkillType::SNARE: + case SkillType::STUN: + resultSize = sizeof(sSkillResult_Damage_N_Debuff); + skillHandler = handleSkillDamageNDebuff; + break; case SkillType::JUMP: case SkillType::RUN: - case SkillType::FREEDOM: - case SkillType::PHOENIX: - case SkillType::INVULNERABLE: + case SkillType::STEALTH: case SkillType::MINIMAPENEMY: case SkillType::MINIMAPTRESURE: - case SkillType::NANOSTIMPAK: + case SkillType::PHOENIX: case SkillType::PROTECTBATTERY: case SkillType::PROTECTINFECTION: case SkillType::REWARDBLOB: case SkillType::REWARDCASH: + // case SkillType::INFECTIONDAMAGE: + case SkillType::FREEDOM: + case SkillType::BOUNDINGBALL: + case SkillType::INVULNERABLE: case SkillType::STAMINA_SELF: - case SkillType::STEALTH: + case SkillType::NANOSTIMPAK: + case SkillType::BUFFHEAL: resultSize = sizeof(sSkillResult_Buff); skillHandler = handleSkillBuff; break; + case SkillType::BLOODSUCKING: + resultSize = sizeof(sSkillResult_Heal_HP); + skillHandler = handleSkillLeech; + case SkillType::RETROROCKET_SELF: + // no-op + return results; + case SkillType::PHOENIX_GROUP: + resultSize = sizeof(sSkillResult_Resurrect); + skillHandler = handleSkillResurrect; + break; + case SkillType::RECALL: + case SkillType::RECALL_GROUP: + resultSize = sizeof(sSkillResult_Move); + skillHandler = handleSkillMove; + break; case SkillType::BATTERYDRAIN: resultSize = sizeof(sSkillResult_BatteryDrain); skillHandler = handleSkillBatteryDrain; break; - case SkillType::RECALL: // still soft lock - case SkillType::RECALL_GROUP: // works for player who uses it - resultSize = sizeof(sSkillResult_Move); - skillHandler = handleSkillMove; - break; - case SkillType::PHOENIX_GROUP: // broken - resultSize = sizeof(sSkillResult_Resurrect); - skillHandler = handleSkillResurrect; - break; - case SkillType::RETROROCKET_SELF: - // no-op - return results; default: std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl; return results; diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 672ac5c..f7c9daa 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -13,12 +13,12 @@ constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); enum class SkillType { DAMAGE = 1, HEAL_HP = 2, - KNOCKDOWN = 3, - SLEEP = 4, - SNARE = 5, + KNOCKDOWN = 3, // dnd + SLEEP = 4, // dnd + SNARE = 5, // dnd HEAL_STAMINA = 6, STAMINA_SELF = 7, - STUN = 8, + STUN = 8, // dnd WEAPONSLOW = 9, JUMP = 10, RUN = 11, From 4bc53b2e7e0d7c831fb00ae295d8f965cfd40d4c Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jul 2023 10:31:40 -0400 Subject: [PATCH 065/104] Change SkillResult size validation Since leech uses trailing structs of two different sizes, just use the max SkillResult size in validation/zeroing and then check for overflow in a couple extra places --- src/Abilities.cpp | 13 ++----------- src/Abilities.hpp | 8 ++++++-- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index f27b3fe..810c526 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -147,26 +147,22 @@ static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant* #pragma endregion static std::vector handleSkill(SkillData* skill, int power, ICombatant* src, std::vector targets) { - size_t resultSize = 0; SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr; std::vector results; switch(skill->skillType) { case SkillType::DAMAGE: - resultSize = sizeof(sSkillResult_Damage); skillHandler = handleSkillDamage; break; case SkillType::HEAL_HP: case SkillType::RETURNHOMEHEAL: - resultSize = sizeof(sSkillResult_Heal_HP); skillHandler = handleSkillHealHP; break; case SkillType::KNOCKDOWN: case SkillType::SLEEP: case SkillType::SNARE: case SkillType::STUN: - resultSize = sizeof(sSkillResult_Damage_N_Debuff); skillHandler = handleSkillDamageNDebuff; break; case SkillType::JUMP: @@ -186,26 +182,21 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat case SkillType::STAMINA_SELF: case SkillType::NANOSTIMPAK: case SkillType::BUFFHEAL: - resultSize = sizeof(sSkillResult_Buff); skillHandler = handleSkillBuff; break; case SkillType::BLOODSUCKING: - resultSize = sizeof(sSkillResult_Heal_HP); skillHandler = handleSkillLeech; case SkillType::RETROROCKET_SELF: // no-op return results; case SkillType::PHOENIX_GROUP: - resultSize = sizeof(sSkillResult_Resurrect); skillHandler = handleSkillResurrect; break; case SkillType::RECALL: case SkillType::RECALL_GROUP: - resultSize = sizeof(sSkillResult_Move); skillHandler = handleSkillMove; break; case SkillType::BATTERYDRAIN: - resultSize = sizeof(sSkillResult_BatteryDrain); skillHandler = handleSkillBatteryDrain; break; default: @@ -217,7 +208,7 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat 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) { + if(result.size > MAX_SKILLRESULT_SIZE) { std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl; continue; } @@ -250,7 +241,7 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: std::vector results = handleSkill(skill, boost, plr, affected); if(results.empty()) return; // no effect; no need for confirmation packets - size_t resultSize = results.back().size; // guaranteed to be the same for every item + size_t resultSize = MAX_SKILLRESULT_SIZE; // lazy 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; diff --git a/src/Abilities.hpp b/src/Abilities.hpp index f7c9daa..316e216 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -75,8 +75,12 @@ struct SkillResult { size_t size; uint8_t payload[MAX_SKILLRESULT_SIZE]; SkillResult(size_t len, void* dat) { - size = len; - memcpy(payload, dat, len); + if(len <= MAX_SKILLRESULT_SIZE) { + size = len; + memcpy(payload, dat, len); + } else { + size = 0; + } } SkillResult() { size = 0; From e3c3da87b257428efc921487d6e3119bd437a259 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jul 2023 10:42:07 -0400 Subject: [PATCH 066/104] Oops --- src/Abilities.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 810c526..a3b9b35 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -186,6 +186,7 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat break; case SkillType::BLOODSUCKING: skillHandler = handleSkillLeech; + break; case SkillType::RETROROCKET_SELF: // no-op return results; From 13e71de785bb161fe2629968b31f6616985f9574 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jul 2023 10:46:53 -0400 Subject: [PATCH 067/104] Make skill result size check an assertion --- src/Abilities.cpp | 2 -- src/Abilities.hpp | 10 ++++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a3b9b35..73bf929 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -5,8 +5,6 @@ #include "Buffs.hpp" #include "Nanos.hpp" -#include - using namespace Abilities; std::map Abilities::SkillTable; diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 316e216..ef703b1 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -7,6 +7,7 @@ #include #include +#include constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); @@ -75,12 +76,9 @@ struct SkillResult { size_t size; uint8_t payload[MAX_SKILLRESULT_SIZE]; SkillResult(size_t len, void* dat) { - if(len <= MAX_SKILLRESULT_SIZE) { - size = len; - memcpy(payload, dat, len); - } else { - size = 0; - } + assert(len <= MAX_SKILLRESULT_SIZE); + size = len; + memcpy(payload, dat, len); } SkillResult() { size = 0; From e7450b974c4b70062ebca235a37541472ad4462a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 10:41:45 -0400 Subject: [PATCH 068/104] Add `clearBuffs` --- src/Combat.cpp | 14 ++++++++++++++ src/Entities.hpp | 2 ++ src/Player.hpp | 1 + 3 files changed, 17 insertions(+) diff --git a/src/Combat.cpp b/src/Combat.cpp index b83583a..93e3542 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -54,6 +54,7 @@ void Player::removeBuff(int buffId) { void Player::removeBuff(int buffId, int buffClass) { if(hasBuff(buffId)) { buffs[buffId]->clear((BuffClass)buffClass); + // buff might not be stale since another buff class might remain if(buffs[buffId]->isStale()) { delete buffs[buffId]; buffs.erase(buffId); @@ -61,6 +62,17 @@ void Player::removeBuff(int buffId, int buffClass) { } } +void Player::clearBuffs(bool force) { + for(auto buff : buffs) { + if(!force) { + removeBuff(buff.first); + } else { + delete buff.second; + } + } + buffs.clear(); +} + bool Player::hasBuff(int buffId) { auto buff = buffs.find(buffId); return buff != buffs.end() && !buff->second->isStale(); @@ -176,6 +188,8 @@ void CombatNPC::removeBuff(int buffId) { /* stubbed */ } void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ } +void CombatNPC::clearBuffs(bool force) { /* stubbed */ } + bool CombatNPC::hasBuff(int buffId) { /* stubbed */ return false; } diff --git a/src/Entities.hpp b/src/Entities.hpp index 28a47c0..816fbbe 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -48,6 +48,7 @@ public: virtual Buff* getBuff(int) = 0; virtual void removeBuff(int) = 0; virtual void removeBuff(int, int) = 0; + virtual void clearBuffs(bool) = 0; virtual bool hasBuff(int) = 0; virtual int getCompositeCondition() = 0; virtual int takeDamage(EntityRef, int) = 0; @@ -123,6 +124,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual void removeBuff(int buffId, int buffClass) override; + virtual void clearBuffs(bool force) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; diff --git a/src/Player.hpp b/src/Player.hpp index 224ea90..5ca112c 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -93,6 +93,7 @@ struct Player : public Entity, public ICombatant { virtual Buff* getBuff(int buffId) override; virtual void removeBuff(int buffId) override; virtual void removeBuff(int buffId, int buffClass) override; + virtual void clearBuffs(bool force) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; virtual int takeDamage(EntityRef src, int amt) override; From e9cd5db8a2fbda0a5271e21233445f4b83ab7c5b Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 10:43:48 -0400 Subject: [PATCH 069/104] Get rid of cbf --- src/CustomCommands.cpp | 1 - src/Entities.cpp | 8 +++++++- src/Entities.hpp | 6 +++--- src/MobAI.cpp | 45 +++++++++++++----------------------------- src/MobAI.hpp | 4 ---- 5 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 217a33b..2021d32 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -678,7 +678,6 @@ static void whoisCommand(std::string full, std::vector& args, CNSoc Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id)); Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type)); Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp)); - Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf)); Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind)); Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); diff --git a/src/Entities.cpp b/src/Entities.cpp index 77b5e3f..bd97981 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -40,7 +40,7 @@ sNPCAppearanceData BaseNPC::getAppearanceData() { sNPCAppearanceData data = {}; data.iAngle = angle; data.iBarkerType = 0; // unused? - data.iConditionBitFlag = cbf; + data.iConditionBitFlag = 0; data.iHP = hp; data.iNPCType = type; data.iNPC_ID = id; @@ -50,6 +50,12 @@ sNPCAppearanceData BaseNPC::getAppearanceData() { return data; } +sNPCAppearanceData CombatNPC::getAppearanceData() { + sNPCAppearanceData data = BaseNPC::getAppearanceData(); + data.iConditionBitFlag = getCompositeCondition(); + return data; +} + /* * Entity coming into view. */ diff --git a/src/Entities.hpp b/src/Entities.hpp index 816fbbe..d84f756 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -73,7 +73,6 @@ public: int type; int hp; int angle; - int cbf; bool loopingPath = false; BaseNPC(int _A, uint64_t iID, int t, int _id) { @@ -81,7 +80,6 @@ public: type = t; hp = 400; angle = _A; - cbf = 0; id = _id; instanceID = iID; }; @@ -89,7 +87,7 @@ public: virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; - sNPCAppearanceData getAppearanceData(); + virtual sNPCAppearanceData getAppearanceData(); }; struct CombatNPC : public BaseNPC, public ICombatant { @@ -118,6 +116,8 @@ struct CombatNPC : public BaseNPC, public ICombatant { transitionHandlers[AIState::INACTIVE] = {}; } + virtual sNPCAppearanceData getAppearanceData() override; + virtual bool isExtant() override { return hp > 0; } virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) override; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 26552f1..42a12df 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) { } // wake up sleeping monster - if (cbf & CSB_BIT_MEZ) { - cbf &= ~CSB_BIT_MEZ; + if (hasBuff(ECSB_MEZ)) { + removeBuff(ECSB_MEZ); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; pkt1.iID = id; - pkt1.iConditionBitFlag = cbf; + pkt1.iConditionBitFlag = getCompositeCondition(); NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } @@ -96,13 +96,12 @@ static std::pair lerp(int x1, int y1, int x2, int y2, int speed) { void MobAI::clearDebuff(Mob *mob) { mob->skillStyle = -1; - mob->cbf = 0; - mob->unbuffTimes.clear(); + mob->clearBuffs(false); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); pkt1.eCT = 2; pkt1.iID = mob->id; - pkt1.iConditionBitFlag = mob->cbf; + pkt1.iConditionBitFlag = mob->getCompositeCondition(); NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } @@ -555,7 +554,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { // drain if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000) - && self->cbf & CSB_BIT_BOUNDINGBALL) { + && self->hasBuff(ECSB_BOUNDINGBALL)) { drainMobHP(self, self->maxHealth / 20); // lose 5% every second self->lastDrainTime = currTime; } @@ -564,27 +563,13 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { if (self->hp <= 0) return; - // unbuffing - std::unordered_map::iterator it = self->unbuffTimes.begin(); - while (it != self->unbuffTimes.end()) { - - if (currTime >= it->second) { - self->cbf &= ~it->first; - - INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); - pkt1.eCT = 2; - pkt1.iID = self->id; - pkt1.iConditionBitFlag = self->cbf; - NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - - it = self->unbuffTimes.erase(it); - } else { - it++; - } - } + // tick buffs TODO abilities + //for(auto buffEntry : self->buffs) { + // buffEntry.second->combatTick(currTime); + //} // skip attack if stunned or asleep - if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { + if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) { self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. return; } @@ -609,7 +594,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { self->nextAttack = 0; // halve movement speed if snared - if (self->cbf & CSB_BIT_DN_MOVE_SPEED) + if (self->hasBuff(ECSB_DN_MOVE_SPEED)) self->speed /= 2; int targetX = plr->x; @@ -715,7 +700,7 @@ void MobAI::roamingStep(CombatNPC* npc, time_t currTime) { farY = std::clamp(farY, yStart, yStart + self->idleRange); // halve movement speed if snared - if (self->cbf & CSB_BIT_DN_MOVE_SPEED) + if (self->hasBuff(ECSB_DN_MOVE_SPEED)) self->speed /= 2; std::queue queue; @@ -788,7 +773,6 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) { self->hp = self->maxHealth; self->killedTime = 0; self->nextAttack = 0; - self->cbf = 0; // cast a return home heal spell, this is the right way(tm) // TODO ABILITIES @@ -833,9 +817,8 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { Mob* self = (Mob*)npc; self->target = nullptr; - self->cbf = 0; self->skillStyle = -1; - self->unbuffTimes.clear(); + self->clearBuffs(true); self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? // check for the edge case where hitting the mob did not aggro it diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 0941919..3e74d3f 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -21,8 +21,6 @@ namespace MobAI { } struct Mob : public CombatNPC { - // general - std::unordered_map unbuffTimes = {}; // dead time_t killedTime = 0; @@ -71,8 +69,6 @@ struct Mob : public CombatNPC { offsetX = 0; offsetY = 0; - cbf = 0; - // NOTE: there appear to be discrepancies in the dump hp = maxHealth; From 3b5f6c0fe750ec3a98d79cb5a5b569370b987491 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 17:05:44 +0000 Subject: [PATCH 070/104] Fix trailing structs The change to allow flexible trailing struct sizes broke `attachSkillResults` oops --- src/Abilities.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 73bf929..e218707 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -216,16 +216,18 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat return results; } -static void attachSkillResults(std::vector results, size_t resultSize, uint8_t* pivot) { +static void attachSkillResults(std::vector results, uint8_t* pivot) { for(SkillResult& result : results) { - memcpy(pivot, result.payload, resultSize); - pivot += resultSize; + size_t sz = result.size; + memcpy(pivot, result.payload, sz); + pivot += sz; } } void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector affected) { Player* plr = PlayerManager::getPlayer(sock); + ICombatant* combatant = dynamic_cast(plr); int boost = 0; if (Nanos::getNanoBoost(plr)) @@ -237,17 +239,19 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: nano.iStamina = 0; } - std::vector results = handleSkill(skill, boost, plr, affected); + std::vector results = handleSkill(skill, boost, combatant, affected); if(results.empty()) return; // no effect; no need for confirmation packets - size_t resultSize = MAX_SKILLRESULT_SIZE; // lazy - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) { + // lazy validation since skill results might be different sizes + if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), MAX_SKILLRESULT_SIZE)) { 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; + size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC); + for(SkillResult& sr : results) + resplen += sr.size; uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -260,7 +264,7 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: pkt->eST = (int32_t)skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); - attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1)); + attachSkillResults(results, (uint8_t*)(pkt + 1)); sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); if(skill->skillType == SkillType::RECALL_GROUP) @@ -284,15 +288,16 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector std::vector results = handleSkill(skill, 0, src, affected); if(results.empty()) return; // no effect; no need for confirmation packets - 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)) { + // lazy validation since skill results might be different sizes + if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) { 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; + size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT); + for(SkillResult& sr : results) + resplen += sr.size; uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -302,7 +307,7 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector pkt->eST = (int32_t)skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); - attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1)); + attachSkillResults(results, (uint8_t*)(pkt + 1)); NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); } From 8062e52c551f805fead47625c4508b677afdae2a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 13:09:28 -0400 Subject: [PATCH 071/104] Damage n debuff handler --- src/Abilities.cpp | 51 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index e218707..63586a9 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -45,11 +45,38 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so } static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) { - // TODO abilities + // take aggro + target->takeDamage(source->getRef(), 0); + + int duration = 0; + int strength = 0; + bool blocked = target->hasBuff(ECSB_FREEDOM); + if(!blocked) { + duration = skill->durationTime[power]; + strength = skill->values[0][power]; + BuffStack debuff = { + duration, // ticks + strength, // value + source->getRef(), // source + BuffClass::NANO, // buff class + }; + int timeBuffId = Abilities::getCSTBFromST(skill->skillType); + target->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 + }, + &debuff); + } + sSkillResult_Damage_N_Debuff result{}; + result.iDamage = duration / 10; // we use the duration as the damage number (why?) + result.iHP = target->getCurrentHP(); result.eCT = target->getCharType(); result.iID = target->getID(); - result.bProtected = false; + result.bProtected = blocked; result.iConditionBitFlag = target->getCompositeCondition(); return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result); } @@ -60,22 +87,24 @@ static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* sou } static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) { + int duration = skill->durationTime[power]; + int strength = skill->values[0][power]; BuffStack passiveBuff = { - skill->drainType == SkillDrainType::PASSIVE ? 1 : skill->durationTime[power], // ticks - skill->values[0][power], // value + skill->drainType == SkillDrainType::PASSIVE ? 1 : duration, // ticks + strength, // value source->getRef(), // source source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class }; int timeBuffId = Abilities::getCSTBFromST(skill->skillType); if(!target->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)) return SkillResult(); // no result if already buffed + [](EntityRef self, Buff* buff, int status, BuffStack* stack) { + Buffs::timeBuffUpdate(self, buff, status, stack); + }, + [](EntityRef self, Buff* buff, time_t currTime) { + // no-op + }, + &passiveBuff)) return SkillResult(); // no result if already buffed sSkillResult_Buff result{}; result.eCT = target->getCharType(); From c70205a15b0daff1071e03c5888a08a2fbd8ebb7 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 13:19:49 -0400 Subject: [PATCH 072/104] Implement buffs for mobs --- src/MobAI.cpp | 11 ++++++----- src/MobAI.hpp | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 42a12df..53b8e2b 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -441,6 +441,7 @@ static void useAbilities(Mob *mob, time_t currTime) { return; } +// TODO abiilities static void drainMobHP(Mob *mob, int amount) { size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); @@ -552,7 +553,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { return; } - // drain + // drain TODO abilities if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000) && self->hasBuff(ECSB_BOUNDINGBALL)) { drainMobHP(self, self->maxHealth / 20); // lose 5% every second @@ -563,10 +564,10 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { if (self->hp <= 0) return; - // tick buffs TODO abilities - //for(auto buffEntry : self->buffs) { - // buffEntry.second->combatTick(currTime); - //} + // tick buffs + for(auto buffEntry : self->buffs) { + buffEntry.second->combatTick(currTime); + } // skip attack if stunned or asleep if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) { diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 3e74d3f..09eeefb 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -41,6 +41,7 @@ struct Mob : public CombatNPC { time_t lastDrainTime = 0; int skillStyle = -1; // -1 for nothing, 0-2 for corruption, -2 for eruption int hitX = 0, hitY = 0, hitZ = 0; // for use in ability targeting + std::unordered_map buffs = {}; // group int groupLeader = 0; From 0dd628717d7619551e302995c65b81c2d98daa42 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 13:42:40 -0400 Subject: [PATCH 073/104] Move buffs from Mob to CombatNPC --- src/Entities.hpp | 2 ++ src/Groups.hpp | 5 +++++ src/MobAI.hpp | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Entities.hpp b/src/Entities.hpp index d84f756..9dffe5d 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -104,6 +104,8 @@ struct CombatNPC : public BaseNPC, public ICombatant { std::map stateHandlers; std::map transitionHandlers; + std::unordered_map buffs = {}; + CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { spawnX = x; diff --git a/src/Groups.hpp b/src/Groups.hpp index b8a1782..4fe40f5 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -3,6 +3,7 @@ #include "EntityRef.hpp" #include +#include struct Group { std::vector members; @@ -14,6 +15,10 @@ struct Group { }); return filtered; } + EntityRef getLeader() { + assert(members.size() > 0); + return members[0]; + } Group(EntityRef leader); }; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 09eeefb..3e74d3f 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -41,7 +41,6 @@ struct Mob : public CombatNPC { time_t lastDrainTime = 0; int skillStyle = -1; // -1 for nothing, 0-2 for corruption, -2 for eruption int hitX = 0, hitY = 0, hitZ = 0; // for use in ability targeting - std::unordered_map buffs = {}; // group int groupLeader = 0; From 807e182407ddd92c921017204aa41425c4365c60 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 13:49:40 -0400 Subject: [PATCH 074/104] Implement buff handling for CombatNPC --- src/Combat.cpp | 69 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 93e3542..8b96845 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -24,13 +24,11 @@ bool Player::addBuff(int buffId, BuffCallback onUpdate, BuffCal if(!isAlive()) return false; - EntityRef self = PlayerManager::getSockFromID(iID); - if(!hasBuff(buffId)) { - buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack); + buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); return true; } - + buffs[buffId]->updateCallbacks(onUpdate, onTick); buffs[buffId]->addStack(stack); return false; @@ -177,25 +175,68 @@ void Player::step(time_t currTime) { #pragma region CombatNPC bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { /* stubbed */ + if(!isAlive()) + return false; + + if(!hasBuff(buffId)) { + buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); + return true; + } + + buffs[buffId]->updateCallbacks(onUpdate, onTick); + buffs[buffId]->addStack(stack); return false; } Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ + if(hasBuff(buffId)) { + return buffs[buffId]; + } return nullptr; } -void CombatNPC::removeBuff(int buffId) { /* stubbed */ } - -void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ } - -void CombatNPC::clearBuffs(bool force) { /* stubbed */ } - -bool CombatNPC::hasBuff(int buffId) { /* stubbed */ - return false; +void CombatNPC::removeBuff(int buffId) { + if(hasBuff(buffId)) { + buffs[buffId]->clear(); + delete buffs[buffId]; + buffs.erase(buffId); + } } -int CombatNPC::getCompositeCondition() { /* stubbed */ - return 0; +void CombatNPC::removeBuff(int buffId, int buffClass) { + if(hasBuff(buffId)) { + buffs[buffId]->clear((BuffClass)buffClass); + // buff might not be stale since another buff class might remain + if(buffs[buffId]->isStale()) { + delete buffs[buffId]; + buffs.erase(buffId); + } + } +} + +void CombatNPC::clearBuffs(bool force) { + for(auto buff : buffs) { + if(!force) { + removeBuff(buff.first); + } else { + delete buff.second; + } + } + buffs.clear(); +} + +bool CombatNPC::hasBuff(int buffId) { + auto buff = buffs.find(buffId); + return buff != buffs.end() && !buff->second->isStale(); +} + +int CombatNPC::getCompositeCondition() { + int conditionBitFlag = 0; + for(auto buff : buffs) { + if(!buff.second->isStale() && buff.second->id > 0) + conditionBitFlag |= CSB_FROM_ECSB(buff.first); + } + return conditionBitFlag; } int CombatNPC::takeDamage(EntityRef src, int amt) { From 288a4a3da5b93e8a03cf26e8ef23a0f794f071eb Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 19:00:01 +0000 Subject: [PATCH 075/104] Fix infinite slowdown with snare --- src/MobAI.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 53b8e2b..bcadb87 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -586,6 +586,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { } int distanceToTravel = INT_MAX; + int speed = self->speed; // movement logic: move when out of range but don't move while casting a skill if (distance > mobRange && self->skillStyle == -1) { if (self->nextMovement != 0 && currTime < self->nextMovement) @@ -596,7 +597,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { // halve movement speed if snared if (self->hasBuff(ECSB_DN_MOVE_SPEED)) - self->speed /= 2; + speed /= 2; int targetX = plr->x; int targetY = plr->y; @@ -605,9 +606,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { targetY += self->offsetY*distance/(self->idleRange + 1); } - distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5); + distanceToTravel = std::min(distance-mobRange+1, speed*2/5); auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel); - if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack) + if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack) self->nextAttack = 0; NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle); @@ -615,7 +616,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); pkt.iNPC_ID = self->id; - pkt.iSpeed = self->speed; + pkt.iSpeed = speed; pkt.iToX = self->x = targ.first; pkt.iToY = self->y = targ.second; pkt.iToZ = plr->z; From 464d18820bce0bac5f88c5705a5177a220022cac Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 19:00:35 +0000 Subject: [PATCH 076/104] Tick buffs for mobs --- src/Abilities.cpp | 7 +++++-- src/Buffs.cpp | 12 ++++++++++++ src/Buffs.hpp | 1 + src/MobAI.cpp | 13 +++++++++++-- src/NPCManager.cpp | 2 +- src/servers/CNShardServer.hpp | 1 + 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 63586a9..d1fa9a4 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -1,5 +1,7 @@ #include "Abilities.hpp" +#include "servers/CNShardServer.hpp" + #include "NPCManager.hpp" #include "PlayerManager.hpp" #include "Buffs.hpp" @@ -55,7 +57,7 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat duration = skill->durationTime[power]; strength = skill->values[0][power]; BuffStack debuff = { - duration, // ticks + (duration * 100) / MS_PER_COMBAT_TICK, // ticks strength, // value source->getRef(), // source BuffClass::NANO, // buff class @@ -64,9 +66,10 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat target->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 + //Buffs::timeBuffTick(self, buff); }, &debuff); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 072001f..2b42e15 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -137,6 +137,18 @@ void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* st self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } +void Buffs::timeBuffTick(EntityRef self, Buff* buff) { + if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) + return; // not implemented + Entity* entity = self.getEntity(); + ICombatant* combatant = dynamic_cast(entity); + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt); + pkt.eCT = combatant->getCharType(); + pkt.iID = combatant->getID(); + pkt.iTB_ID = buff->id; + NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); +} + void Buffs::timeBuffTimeout(EntityRef self) { if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) return; // not a combatant diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 3247143..95718af 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -86,5 +86,6 @@ public: namespace Buffs { void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); + void timeBuffTick(EntityRef self, Buff* buff); void timeBuffTimeout(EntityRef self); } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index bcadb87..70a8d8e 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -565,8 +565,17 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { return; // tick buffs - for(auto buffEntry : self->buffs) { - buffEntry.second->combatTick(currTime); + auto it = npc->buffs.begin(); + while(it != npc->buffs.end()) { + Buff* buff = (*it).second; + buff->combatTick(currTime); + buff->tick(currTime); + if(buff->isStale()) { + // garbage collect + it = npc->buffs.erase(it); + delete buff; + } + else it++; } // skip attack if stunned or asleep diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index f1a5e13..cba6656 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -376,5 +376,5 @@ void NPCManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler); - REGISTER_SHARD_TIMER(step, 200); + REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK); } diff --git a/src/servers/CNShardServer.hpp b/src/servers/CNShardServer.hpp index c585428..7ed4e3f 100644 --- a/src/servers/CNShardServer.hpp +++ b/src/servers/CNShardServer.hpp @@ -8,6 +8,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 +#define MS_PER_COMBAT_TICK 200 class CNShardServer : public CNServer { private: From dd4063e4163fd0c3a14d88eef583b8a0d331921d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 19:11:28 +0000 Subject: [PATCH 077/104] Fix icon not disappearing for debuffs --- src/Abilities.cpp | 2 +- src/Buffs.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index d1fa9a4..929a4a3 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -69,7 +69,7 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat if(status == ETBU_DEL) Buffs::timeBuffTimeout(self); }, [](EntityRef self, Buff* buff, time_t currTime) { - //Buffs::timeBuffTick(self, buff); + Buffs::timeBuffTick(self, buff); }, &debuff); } diff --git a/src/Buffs.cpp b/src/Buffs.cpp index 2b42e15..c5fd82a 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -155,7 +155,8 @@ void Buffs::timeBuffTimeout(EntityRef self) { Entity* entity = self.getEntity(); ICombatant* combatant = dynamic_cast(entity); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players - pkt.eCT = combatant->getCharType(); + int32_t eCharType = combatant->getCharType(); + pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here pkt.iID = combatant->getID(); pkt.iConditionBitFlag = combatant->getCompositeCondition(); NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); From 1cf2be9968b19023631bbe8dbffe654e49d911dd Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 19:49:38 +0000 Subject: [PATCH 078/104] reorder some stuff --- src/Abilities.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 929a4a3..cc2900e 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -414,12 +414,6 @@ int Abilities::getCSTBFromST(SkillType skillType) { case SkillType::PROTECTINFECTION: result = ECSB_PROTECT_INFECTION; break; - case SkillType::SNARE: - result = ECSB_DN_MOVE_SPEED; - break; - case SkillType::SLEEP: - result = ECSB_MEZ; - break; case SkillType::MINIMAPENEMY: result = ECSB_MINIMAP_ENEMY; break; @@ -432,15 +426,9 @@ int Abilities::getCSTBFromST(SkillType skillType) { case SkillType::REWARDCASH: result = ECSB_REWARD_CASH; break; - case SkillType::INFECTIONDAMAGE: - result = ECSB_INFECTION; - break; case SkillType::FREEDOM: result = ECSB_FREEDOM; break; - case SkillType::BOUNDINGBALL: - result = ECSB_BOUNDINGBALL; - break; case SkillType::INVULNERABLE: result = ECSB_INVULNERABLE; break; @@ -449,6 +437,22 @@ int Abilities::getCSTBFromST(SkillType skillType) { break; case SkillType::NANOSTIMPAK: result = ECSB_STIMPAKSLOT1; + // shift as necessary + break; + case SkillType::SNARE: + result = ECSB_DN_MOVE_SPEED; + break; + case SkillType::STUN: + result = ECSB_STUN; + break; + case SkillType::SLEEP: + result = ECSB_MEZ; + break; + case SkillType::INFECTIONDAMAGE: + result = ECSB_INFECTION; + break; + case SkillType::BOUNDINGBALL: + result = ECSB_BOUNDINGBALL; break; default: break; From c1aff8d3c33d154a5c3b51207be216a97a7c6cc6 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jul 2023 20:22:00 +0000 Subject: [PATCH 079/104] Move drain to new system TODO - There is a seg fault that happens when drain kills the mob - leech unimplemented - mob skills unimplemented --- src/Abilities.cpp | 15 ++++++++++++--- src/Buffs.cpp | 33 +++++++++++++++++++++++++++++++++ src/Buffs.hpp | 2 ++ src/MobAI.cpp | 15 ++++----------- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index cc2900e..cc99c10 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -93,19 +93,28 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour int duration = skill->durationTime[power]; int strength = skill->values[0][power]; BuffStack passiveBuff = { - skill->drainType == SkillDrainType::PASSIVE ? 1 : duration, // ticks + skill->drainType == SkillDrainType::PASSIVE ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks strength, // value source->getRef(), // source source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class }; int timeBuffId = Abilities::getCSTBFromST(skill->skillType); + SkillDrainType drainType = skill->drainType; if(!target->addBuff(timeBuffId, - [](EntityRef self, Buff* buff, int status, BuffStack* stack) { + [drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) { + if(buff->id == ECSB_BOUNDINGBALL) { + // drain + ICombatant* combatant = dynamic_cast(self.getEntity()); + combatant->takeDamage(buff->getLastSource(), 0); // aggro + } Buffs::timeBuffUpdate(self, buff, status, stack); + if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL) + Buffs::timeBuffTimeout(self); }, [](EntityRef self, Buff* buff, time_t currTime) { - // no-op + if(buff->id == ECSB_BOUNDINGBALL) + Buffs::tickDrain(self, buff); // drain }, &passiveBuff)) return SkillResult(); // no result if already buffed diff --git a/src/Buffs.cpp b/src/Buffs.cpp index c5fd82a..d407fac 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -95,6 +95,12 @@ int Buff::getValue(BuffValueSelector selector) { return value; } +EntityRef Buff::getLastSource() { + if(stacks.empty()) + return self; + return stacks.back().source; +} + bool Buff::isStale() { return stacks.empty(); } @@ -142,6 +148,7 @@ void Buffs::timeBuffTick(EntityRef self, Buff* buff) { return; // not implemented Entity* entity = self.getEntity(); ICombatant* combatant = dynamic_cast(entity); + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt); pkt.eCT = combatant->getCharType(); pkt.iID = combatant->getID(); @@ -161,4 +168,30 @@ void Buffs::timeBuffTimeout(EntityRef self) { pkt.iConditionBitFlag = combatant->getCompositeCondition(); NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } + +void Buffs::tickDrain(EntityRef self, Buff* buff) { + if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) + return; // not implemented + Entity* entity = self.getEntity(); + ICombatant* combatant = dynamic_cast(entity); + int damage = combatant->takeDamage(buff->getLastSource(), combatant->getMaxHP() / 100); + + size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); + assert(resplen < CN_PACKET_BUFFER_SIZE - 8); + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + memset(respbuf, 0, resplen); + + sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; + pkt->iID = self.id; + pkt->eCT = combatant->getCharType(); + pkt->iTB_ID = ECSB_BOUNDINGBALL; + + sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); + drain->iDamage = damage; + drain->iHP = combatant->getCurrentHP(); + drain->eCT = pkt->eCT; + drain->iID = pkt->iID; + + NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); +} #pragma endregion diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 95718af..8f395e1 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -66,6 +66,7 @@ public: BuffClass maxClass(); int getValue(BuffValueSelector selector); + EntityRef getLastSource(); /* * In general, a Buff object won't exist @@ -88,4 +89,5 @@ namespace Buffs { void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); void timeBuffTick(EntityRef self, Buff* buff); void timeBuffTimeout(EntityRef self); + void tickDrain(EntityRef self, Buff* buff); } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 70a8d8e..bcd6a85 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -553,17 +553,6 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { return; } - // drain TODO abilities - if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000) - && self->hasBuff(ECSB_BOUNDINGBALL)) { - drainMobHP(self, self->maxHealth / 20); // lose 5% every second - self->lastDrainTime = currTime; - } - - // if drain killed the mob, return early - if (self->hp <= 0) - return; - // tick buffs auto it = npc->buffs.begin(); while(it != npc->buffs.end()) { @@ -578,6 +567,10 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { else it++; } + // if debuffs killed the mob, return early + if (self->hp <= 0) + return; + // skip attack if stunned or asleep if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) { self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. From 93ec6380c1caf3a7b909736964ff0df8e7f3362d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 13 Aug 2023 10:50:50 -0700 Subject: [PATCH 080/104] Tighten parameter for removeBuff --- src/Combat.cpp | 8 ++++---- src/Entities.hpp | 4 ++-- src/Player.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 8b96845..4b1f224 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -49,9 +49,9 @@ void Player::removeBuff(int buffId) { } } -void Player::removeBuff(int buffId, int buffClass) { +void Player::removeBuff(int buffId, BuffClass buffClass) { if(hasBuff(buffId)) { - buffs[buffId]->clear((BuffClass)buffClass); + buffs[buffId]->clear(buffClass); // buff might not be stale since another buff class might remain if(buffs[buffId]->isStale()) { delete buffs[buffId]; @@ -203,9 +203,9 @@ void CombatNPC::removeBuff(int buffId) { } } -void CombatNPC::removeBuff(int buffId, int buffClass) { +void CombatNPC::removeBuff(int buffId, BuffClass buffClass) { if(hasBuff(buffId)) { - buffs[buffId]->clear((BuffClass)buffClass); + buffs[buffId]->clear(buffClass); // buff might not be stale since another buff class might remain if(buffs[buffId]->isStale()) { delete buffs[buffId]; diff --git a/src/Entities.hpp b/src/Entities.hpp index 9dffe5d..2867801 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -47,7 +47,7 @@ public: virtual bool addBuff(int, BuffCallback, BuffCallback, BuffStack*) = 0; virtual Buff* getBuff(int) = 0; virtual void removeBuff(int) = 0; - virtual void removeBuff(int, int) = 0; + virtual void removeBuff(int, BuffClass) = 0; virtual void clearBuffs(bool) = 0; virtual bool hasBuff(int) = 0; virtual int getCompositeCondition() = 0; @@ -125,7 +125,7 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback 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 void removeBuff(int buffId, BuffClass buffClass) override; virtual void clearBuffs(bool force) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; diff --git a/src/Player.hpp b/src/Player.hpp index 5ca112c..14019ab 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -92,7 +92,7 @@ struct Player : public Entity, public ICombatant { virtual bool addBuff(int buffId, BuffCallback onUpdate, BuffCallback 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 void removeBuff(int buffId, BuffClass buffClass) override; virtual void clearBuffs(bool force) override; virtual bool hasBuff(int buffId) override; virtual int getCompositeCondition() override; From 36b6aeeb00a573256c07cae34ed202be1f356b19 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 13 Aug 2023 11:55:25 -0700 Subject: [PATCH 081/104] Fix segv: Halt buff iteration if mob is dead --- src/Abilities.cpp | 1 - src/MobAI.cpp | 35 +++++------------------------------ 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index cc99c10..8646dff 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -108,7 +108,6 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour ICombatant* combatant = dynamic_cast(self.getEntity()); combatant->takeDamage(buff->getLastSource(), 0); // aggro } - Buffs::timeBuffUpdate(self, buff, status, stack); if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL) Buffs::timeBuffTimeout(self); }, diff --git a/src/MobAI.cpp b/src/MobAI.cpp index bcd6a85..7a31ed3 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -441,32 +441,6 @@ static void useAbilities(Mob *mob, time_t currTime) { return; } -// TODO abiilities -static void drainMobHP(Mob *mob, int amount) { - size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); - assert(resplen < CN_PACKET_BUFFER_SIZE - 8); - uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - - memset(respbuf, 0, resplen); - - sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; - sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - - pkt->iID = mob->id; - pkt->eCT = 4; // mob - pkt->iTB_ID = ECSB_BOUNDINGBALL; - - drain->eCT = 4; - drain->iID = mob->id; - drain->iDamage = amount; - drain->iHP = mob->hp -= amount; - - NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); - - if (mob->hp <= 0) - mob->transition(AIState::DEAD, mob->target); -} - void MobAI::incNextMovement(Mob* mob, time_t currTime) { if (currTime == 0) currTime = getTime(); @@ -558,6 +532,11 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { while(it != npc->buffs.end()) { Buff* buff = (*it).second; buff->combatTick(currTime); + + // if mob state changed, end the step + if(self->state != AIState::COMBAT) + return; + buff->tick(currTime); if(buff->isStale()) { // garbage collect @@ -567,10 +546,6 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { else it++; } - // if debuffs killed the mob, return early - if (self->hp <= 0) - return; - // skip attack if stunned or asleep if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) { self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. From b04c2922729bcdea4678bbd53d69c2802bddbf7a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 13 Aug 2023 14:26:05 -0700 Subject: [PATCH 082/104] Break if player dies from buff combat tick --- src/Combat.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Combat.cpp b/src/Combat.cpp index 4b1f224..7a46cfa 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -169,6 +169,8 @@ void Player::step(time_t currTime) { // buffs for(auto buffEntry : buffs) { buffEntry.second->combatTick(currTime); + if(!isAlive()) + break; // unsafe to keep ticking if we're dead } } #pragma endregion @@ -957,6 +959,7 @@ static void playerTick(CNServer *serv, time_t currTime) { auto it = plr->buffs.begin(); while(it != plr->buffs.end()) { Buff* buff = (*it).second; + //buff->combatTick() gets called in Player::step buff->tick(currTime); if(buff->isStale()) { // garbage collect From e64bcf91a594a4aa0ac2679e633cd0f95a62d5f2 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 13 Aug 2023 18:26:30 -0700 Subject: [PATCH 083/104] Make `clearBuffs` memory-safe improperly erasing during iteration oops --- src/Combat.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 7a46cfa..aa8b912 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -61,14 +61,13 @@ void Player::removeBuff(int buffId, BuffClass buffClass) { } void Player::clearBuffs(bool force) { - for(auto buff : buffs) { - if(!force) { - removeBuff(buff.first); - } else { - delete buff.second; - } + auto it = buffs.begin(); + while(it != buffs.end()) { + Buff* buff = (*it).second; + if(!force) buff->clear(); + delete buff; + it = buffs.erase(it); } - buffs.clear(); } bool Player::hasBuff(int buffId) { @@ -217,14 +216,13 @@ void CombatNPC::removeBuff(int buffId, BuffClass buffClass) { } void CombatNPC::clearBuffs(bool force) { - for(auto buff : buffs) { - if(!force) { - removeBuff(buff.first); - } else { - delete buff.second; - } + auto it = buffs.begin(); + while(it != buffs.end()) { + Buff* buff = (*it).second; + if(!force) buff->clear(); + delete buff; + it = buffs.erase(it); } - buffs.clear(); } bool CombatNPC::hasBuff(int buffId) { From ff74c8ede18afc7f6c7dbf3b981c1eda9ead5a8f Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 13 Aug 2023 18:27:18 -0700 Subject: [PATCH 084/104] Implement leech --- src/Abilities.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 8646dff..5087a15 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -85,8 +85,26 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat } static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) { - // TODO abilities - return SkillResult(); + EntityRef sourceRef = source->getRef(); + int heal = skill->values[0][power]; + int healed = source->heal(sourceRef, heal); + int damage = heal * 2; + int dealt = target->takeDamage(sourceRef, damage); + + sSkillResult_Leech result{}; + + result.Damage.eCT = target->getCharType(); + result.Damage.iID = target->getID(); + result.Damage.bProtected = dealt <= 0; + result.Damage.iDamage = dealt; + result.Damage.iHP = target->getCurrentHP(); + + result.Heal.eCT = result.Damage.eCT; + result.Heal.iID = result.Damage.iID; + result.Heal.iHealHP = healed; + result.Heal.iHP = source->getCurrentHP(); + + return SkillResult(sizeof(sSkillResult_Leech), &result); } static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) { From d416763ae050fd5ad557895dbb47429d384366d8 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 14 Aug 2023 17:52:40 -0700 Subject: [PATCH 085/104] Move eruption attacks to new system --- src/Abilities.cpp | 7 +++++++ src/MobAI.cpp | 16 ++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 5087a15..a3b3f86 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -6,6 +6,7 @@ #include "PlayerManager.hpp" #include "Buffs.hpp" #include "Nanos.hpp" +#include "MobAI.hpp" using namespace Abilities; @@ -364,6 +365,12 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector pkt->iSkillID = skillID; pkt->eST = (int32_t)skill->skillType; pkt->iTargetCnt = (int32_t)results.size(); + if(npc.kind == EntityKind::MOB) { + Mob* mob = dynamic_cast(entity); + pkt->iValue1 = mob->hitX; + pkt->iValue2 = mob->hitY; + pkt->iValue3 = mob->hitZ; + } attachSkillResults(results, (uint8_t*)(pkt + 1)); NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 7a31ed3..bddcf82 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -346,7 +346,7 @@ static void useAbilities(Mob *mob, time_t currTime) { if (mob->skillStyle == -2) { // eruption hit int skillID = (int)mob->data["m_iMegaType"]; - std::vector targetData = {0, 0, 0, 0, 0}; + std::vector targets{}; // find the players within range of eruption for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) { @@ -356,26 +356,22 @@ static void useAbilities(Mob *mob, time_t currTime) { if (ref.kind != EntityKind::PLAYER) continue; - CNSocket *s= ref.sock; + CNSocket *s = ref.sock; Player *plr = PlayerManager::getPlayer(s); - if (plr->HP <= 0) + if (!plr->isAlive()) continue; int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y); if (distance < Abilities::SkillTable[skillID].effectArea) { - targetData[0] += 1; - targetData[targetData[0]] = plr->iID; - if (targetData[0] > 3) // make sure not to have more than 4 + targets.push_back(plr); + if (targets.size() > 3) // make sure not to have more than 4 break; } } } - // TODO ABILITIES - /*for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/ + Abilities::useNPCSkill(mob->id, skillID, targets); mob->skillStyle = -3; // eruption cooldown mob->nextAttack = currTime + 1000; return; From c0a9ec6b7cf401c7a25b2426ad51a446f4ade836 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 14 Aug 2023 19:28:45 -0700 Subject: [PATCH 086/104] Move mob active skills to new system --- src/MobAI.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index bddcf82..7f18ec3 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -326,12 +326,6 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i } static void useAbilities(Mob *mob, time_t currTime) { - /* - * targetData approach - * first integer is the count - * second to fifth integers are IDs, these can be either player iID or mob's iID - * whether the skill targets players or mobs is determined by the skill packet being fired - */ Player *plr = PlayerManager::getPlayer(mob->target); if (mob->skillStyle >= 0) { // corruption hit @@ -389,14 +383,11 @@ static void useAbilities(Mob *mob, time_t currTime) { if (random < prob1) { // active skill hit int skillID = (int)mob->data["m_iActiveSkill1"]; - // TODO ABILITIES - //std::vector targetData = {1, plr->iID, 0, 0, 0}; - //for (auto& pwr : Abilities::Powers) - // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { - // if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) - // return; // prevent debuffing a player twice - // pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]); - // } + SkillData* skill = &Abilities::SkillTable[skillID]; + int debuffID = Abilities::getCSTBFromST(skill->skillType); + if(plr->hasBuff(debuffID)) + return; // prevent debuffing a player twice + Abilities::useNPCSkill(mob->getRef(), skillID, { plr }); mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; return; } From 522b9ab7b8e635f2b5f47c12a1dc5bb8ce08f36f Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 14 Aug 2023 19:44:15 -0700 Subject: [PATCH 087/104] Passive mob buffs to new system --- src/MobAI.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 7f18ec3..ceb4faa 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -762,12 +762,9 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) { self->roamY = self->y; self->roamZ = self->z; - int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive - // TODO ABILITIES - /*std::vector targetData = { 1, self->id, 0, 0, 0 }; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[skillID].skillType) - pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/ + int skillID = (int)self->data["m_iPassiveBuff"]; + if(skillID != 0) // cast passive + Abilities::useNPCSkill(npc->getRef(), skillID, { npc }); } void MobAI::onRetreat(CombatNPC* npc, EntityRef src) { From 52c1ed4480ea5572fb857f1a1ff01b38d7b81296 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 14 Aug 2023 19:49:39 -0700 Subject: [PATCH 088/104] Return home heal to new system --- src/MobAI.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index ceb4faa..f5fb205 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -741,11 +741,8 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) { self->nextAttack = 0; // cast a return home heal spell, this is the right way(tm) - // TODO ABILITIES - /*std::vector targetData = { 1, 0, 0, 0, 0 }; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == Abilities::SkillTable[110].skillType) - pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/ + Abilities::useNPCSkill(npc->getRef(), 110, { npc }); + // clear outlying debuffs clearDebuff(self); } From 249e5b081fc2a8bb0e5dae92e4996304f8284a25 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 16 Aug 2023 16:43:57 -0700 Subject: [PATCH 089/104] Move corruption block to new system --- src/MobAI.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index f5fb205..1d5b8f2 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -295,11 +295,13 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->Nanos[plr->activeNano].iStamina > 150) respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; // fire damage power disguised as a corruption attack back at the enemy - // TODO ABILITIES - /*std::vector targetData2 = {1, mob->id, 0, 0, 0}; - for (auto& pwr : Abilities::Powers) - if (pwr.skillType == EST_DAMAGE) - pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/ + SkillData skill = Abilities::SkillTable[skillID]; + skill.durationTime[0] = 0; + skill.values[0][0] = 200; // have to set + skill.values[0][1] = 200; // all of these + skill.values[0][2] = 200; // because the player might + skill.values[0][3] = 200; // have a boost + Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob }); } else { respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; From d936d0b6211ccccb39b0e8194cb7473e232b984e Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 11:00:09 -0700 Subject: [PATCH 090/104] Updater contributer guide --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8d819d..2d76f31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them. -## Dirty pull requests +### Dirty pull requests Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge. These are generally either: @@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly. So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues. -## The details +### The details A git commit is uniquely identified by its SHA1 hash. Its hash is generated from its contents, as well as the hash(es) of its parent commit(s). @@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased). So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits. -## The solution +### The solution If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple: @@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend. (You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`) -## Avoiding the problem +### Avoiding the problem When working on a changeset you want to submit back upstream, don't do it on the main branch. Create a work branch just for your changeset with `git checkout -b work`. @@ -81,3 +81,34 @@ That way you can always keep master in sync with upstream with `git pull --ff-on Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`. For moving uncommited changes around between branches, `git stash` is a real blessing. + +## Code guidelines + +Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow: + +### Match the styling + +Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc. + +### Prefer short-circuiting + +To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement. + +### Follow the include convention + +This one matters a lot as it can cause cyclic dependencies and other code-breaking issues. + +FOR HEADER FILES (.hpp): +- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp +- you may NOT include ANYTHING ELSE + +FOR SOURCE FILES (.cpp): +- you can #include whatever you want as long as the partner header is included first +- anything that gets included by another include is fair game +- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean. + +The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues. + +## When in doubt, ask + +If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub. From 94b08659d237b881f4544d8c3a76c88002967900 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 14:21:04 -0700 Subject: [PATCH 091/104] Fix battery drain block --- src/Abilities.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a3b3f86..7e1112b 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -150,19 +150,24 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata Player* plr = dynamic_cast(target); const double scalingFactor = (18 + source->getLevel()) / 36.0; + const bool blocked = target->hasBuff(ECSB_PROTECT_BATTERY); - int boostDrain = (int)(skill->values[0][power] * scalingFactor); - if(boostDrain > plr->batteryW) boostDrain = plr->batteryW; - plr->batteryW -= boostDrain; + int boostDrain = 0; + int potionDrain = 0; + if(blocked) { + 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; + 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.bProtected = blocked; result.iDrainW = boostDrain; result.iBatteryW = plr->batteryW; result.iDrainN = potionDrain; From 874bdefb13e5e6afce849d9951e4c5e02323e12d Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 14:46:35 -0700 Subject: [PATCH 092/104] Fix passive powers --- src/Abilities.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 7e1112b..76ed83f 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -127,6 +127,7 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour ICombatant* combatant = dynamic_cast(self.getEntity()); combatant->takeDamage(buff->getLastSource(), 0); // aggro } + Buffs::timeBuffUpdate(self, buff, status, stack); if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL) Buffs::timeBuffTimeout(self); }, From efb3887e3eff84c09ad2e2b081f13a729b8fef5f Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 14:54:18 -0700 Subject: [PATCH 093/104] Fix corruption attack rebound --- src/Abilities.cpp | 3 +++ src/MobAI.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 76ed83f..6d7a11b 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -215,6 +215,9 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat switch(skill->skillType) { + case SkillType::CORRUPTIONATTACK: + case SkillType::CORRUPTIONATTACKLOSE: + case SkillType::CORRUPTIONATTACKWIN: case SkillType::DAMAGE: skillHandler = handleSkillDamage; break; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 1d5b8f2..e65c0eb 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -227,7 +227,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { return false; } -static void dealCorruption(Mob *mob, std::vector targetData, int skillID, int style) { +static void dealCorruption(Mob *mob, std::vector targetData, int skillID, int mobStyle) { Player *plr = PlayerManager::getPlayer(mob->target); size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult); @@ -246,7 +246,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i resp->iNPC_ID = mob->id; resp->iSkillID = skillID; - resp->iStyle = style; + resp->iStyle = mobStyle; resp->iValue1 = plr->x; resp->iValue2 = plr->y; resp->iValue3 = plr->z; @@ -280,15 +280,15 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i respdata[i].iActiveNanoSlotNum = n; respdata[i].iNanoID = plr->activeNano; - int style2 = Nanos::nanoStyle(plr->activeNano); - if (style2 == -1) { // no nano + int nanoStyle = Nanos::nanoStyle(plr->activeNano); + if (nanoStyle == -1) { // no nano respdata[i].iHitFlag = HF_BIT_STYLE_TIE; respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; - } else if (style == style2) { + } else if (mobStyle == nanoStyle) { respdata[i].iHitFlag = HF_BIT_STYLE_TIE; respdata[i].iDamage = 0; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina; - } else if (style - style2 == 1 || style2 - style == 2) { + } else if (mobStyle - nanoStyle == 1 || nanoStyle - mobStyle == 2) { respdata[i].iHitFlag = HF_BIT_STYLE_WIN; respdata[i].iDamage = 0; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45; From 0fcf41cbfe1c8d6dd04b7140690fc909fc0096c2 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 15:03:16 -0700 Subject: [PATCH 094/104] Fix guard FOR REAL --- src/Abilities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 6d7a11b..d2769e3 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -155,7 +155,7 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata int boostDrain = 0; int potionDrain = 0; - if(blocked) { + if(!blocked) { boostDrain = (int)(skill->values[0][power] * scalingFactor); if(boostDrain > plr->batteryW) boostDrain = plr->batteryW; plr->batteryW -= boostDrain; From 0eff236512f8549a5ed50b3be2f61f1d99dd6487 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 19 Aug 2023 15:26:56 -0700 Subject: [PATCH 095/104] Fix corruption reflection --- src/Abilities.cpp | 2 ++ src/MobAI.cpp | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index d2769e3..bed931b 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -218,6 +218,8 @@ static std::vector handleSkill(SkillData* skill, int power, ICombat case SkillType::CORRUPTIONATTACK: case SkillType::CORRUPTIONATTACKLOSE: case SkillType::CORRUPTIONATTACKWIN: + // skillHandler = handleSkillCorruptionReflect; + // break; case SkillType::DAMAGE: skillHandler = handleSkillDamage; break; diff --git a/src/MobAI.cpp b/src/MobAI.cpp index e65c0eb..ae29a20 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -295,12 +295,22 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->Nanos[plr->activeNano].iStamina > 150) respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; // fire damage power disguised as a corruption attack back at the enemy - SkillData skill = Abilities::SkillTable[skillID]; - skill.durationTime[0] = 0; - skill.values[0][0] = 200; // have to set - skill.values[0][1] = 200; // all of these - skill.values[0][2] = 200; // because the player might - skill.values[0][3] = 200; // have a boost + SkillData skill = { + SkillType::DAMAGE, // skillType + SkillEffectTarget::POINT, // effectTarget + 1, // effectType + SkillTargetType::MOBS, // targetType + SkillDrainType::ACTIVE, // drainType + 0, // effectArea + {0, 0, 0, 0}, // batteryUse + {0, 0, 0, 0}, // durationTime + {0, 0, 0}, // valueTypes (unused) + { + {200, 200, 200, 200}, + {200, 200, 200, 200}, + {200, 200, 200, 200}, + } + }; Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob }); } else { respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; From 97dca62fd0f2db44a3fbf8d4fe92b89e972ff484 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Aug 2023 18:07:23 -0700 Subject: [PATCH 096/104] Fix egg skill handling --- src/Eggs.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 04e276d..73f22f6 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -26,6 +26,7 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { return; } + SkillResult result = SkillResult(); SkillData* skill = &Abilities::SkillTable[skillId]; if(skill->drainType == SkillDrainType::PASSIVE) { // apply buff @@ -50,12 +51,57 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { // no-op }, &eggBuff); + + sSkillResult_Buff resultBuff{}; + resultBuff.eCT = plr->getCharType(); + resultBuff.iID = plr->getID(); + resultBuff.bProtected = false; + resultBuff.iConditionBitFlag = plr->getCompositeCondition(); + result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff); + } else { + int value = plr->getMaxHP() * skill->values[0][0] / 1000; + sSkillResult_Damage resultDamage{}; + sSkillResult_Heal_HP resultHeal{}; + switch(skill->skillType) + { + case SkillType::DAMAGE: + resultDamage.bProtected = false; + resultDamage.eCT = plr->getCharType(); + resultDamage.iID = plr->getID(); + resultDamage.iDamage = plr->takeDamage(src, value); + resultDamage.iHP = plr->getCurrentHP(); + result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage); + break; + case SkillType::HEAL_HP: + resultHeal.eCT = plr->getCharType(); + resultHeal.iID = plr->getID(); + resultHeal.iHealHP = plr->heal(src, value); + resultHeal.iHP = plr->getCurrentHP(); + result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal); + break; + default: + std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled"; + return; + } } - // use skill - std::vector targets; - targets.push_back(dynamic_cast(plr)); - Abilities::useNPCSkill(src, skillId, targets); + // initialize response struct + size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; + 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 = eggId; + pkt->iSkillID = skillId; + pkt->eST = (int32_t)skill->skillType; + pkt->iTargetCnt = 1; + + if(result.size > 0) { + void* attached = (void*)(pkt + 1); + memcpy(attached, result.payload, result.size); + } + + NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen); } static void eggStep(CNServer* serv, time_t currTime) { From f2447cd94089b3cb194278f0fa73f5f7878df4ed Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 20 Aug 2023 18:16:14 -0700 Subject: [PATCH 097/104] Clear player buffs on death if not revived --- src/PlayerManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index c07df89..db6a38a 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -416,6 +416,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { default: // plain respawn plr->HP = PC_MAXHEALTH(plr->level) / 2; + plr->clearBuffs(false); // fallthrough case ePCRegenType::Unstick: // warp away move = true; From ea033a97dd229d2ca715490a74847b50b482677a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sat, 9 Sep 2023 11:59:13 -0700 Subject: [PATCH 098/104] reduce drain tickrate --- src/Abilities.cpp | 9 ++++++--- src/Abilities.hpp | 1 + src/Buffs.cpp | 7 ++++--- src/Buffs.hpp | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index bed931b..0859220 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -120,6 +120,7 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour int timeBuffId = Abilities::getCSTBFromST(skill->skillType); SkillDrainType drainType = skill->drainType; + int combatLifetime = 0; if(!target->addBuff(timeBuffId, [drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) { if(buff->id == ECSB_BOUNDINGBALL) { @@ -131,9 +132,11 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL) Buffs::timeBuffTimeout(self); }, - [](EntityRef self, Buff* buff, time_t currTime) { - if(buff->id == ECSB_BOUNDINGBALL) - Buffs::tickDrain(self, buff); // drain + [combatLifetime](EntityRef self, Buff* buff, time_t currTime) mutable { + if(buff->id == ECSB_BOUNDINGBALL && + combatLifetime % COMBAT_TICKS_PER_DRAIN_PROC == 0) + Buffs::tickDrain(self, buff, COMBAT_TICKS_PER_DRAIN_PROC); // drain + combatLifetime++; }, &passiveBuff)) return SkillResult(); // no result if already buffed diff --git a/src/Abilities.hpp b/src/Abilities.hpp index ef703b1..2470516 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -9,6 +9,7 @@ #include #include +const int COMBAT_TICKS_PER_DRAIN_PROC = 2; constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); enum class SkillType { diff --git a/src/Buffs.cpp b/src/Buffs.cpp index d407fac..179f1c6 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -169,12 +169,13 @@ void Buffs::timeBuffTimeout(EntityRef self) { NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); } -void Buffs::tickDrain(EntityRef self, Buff* buff) { +void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) { if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) return; // not implemented Entity* entity = self.getEntity(); ICombatant* combatant = dynamic_cast(entity); - int damage = combatant->takeDamage(buff->getLastSource(), combatant->getMaxHP() / 100); + int damage = combatant->getMaxHP() / 100 * mult; + int dealt = combatant->takeDamage(buff->getLastSource(), damage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); @@ -187,7 +188,7 @@ void Buffs::tickDrain(EntityRef self, Buff* buff) { pkt->iTB_ID = ECSB_BOUNDINGBALL; sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - drain->iDamage = damage; + drain->iDamage = dealt; drain->iHP = combatant->getCurrentHP(); drain->eCT = pkt->eCT; drain->iID = pkt->iID; diff --git a/src/Buffs.hpp b/src/Buffs.hpp index 8f395e1..178efb0 100644 --- a/src/Buffs.hpp +++ b/src/Buffs.hpp @@ -89,5 +89,5 @@ namespace Buffs { void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); void timeBuffTick(EntityRef self, Buff* buff); void timeBuffTimeout(EntityRef self); - void tickDrain(EntityRef self, Buff* buff); + void tickDrain(EntityRef self, Buff* buff, int mult); } From 278818dd376992dd3aba0db8f84bef6f641bec79 Mon Sep 17 00:00:00 2001 From: dongresource Date: Sun, 10 Sep 2023 19:24:19 +0200 Subject: [PATCH 099/104] Quick fix for Fuse boss fight NPCEvent logic Will be replaced with a proper rework immediately. --- src/Combat.cpp | 14 ++++++++------ src/NPCManager.cpp | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index aa8b912..48598b1 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -307,19 +307,21 @@ void CombatNPC::step(time_t currTime) { } void CombatNPC::transition(AIState newState, EntityRef src) { - state = newState; + if (transitionHandlers.find(newState) != transitionHandlers.end()) transitionHandlers[newState](this, src); else { std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl; transition(AIState::INACTIVE, id); } - /* TODO: fire any triggered events - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == type) - event.handler(src, this); - */ + + // TODO: Properly refactor this + if (newState == AIState::DEAD && src.kind == EntityKind::PLAYER) { + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.trigger == ON_KILLED && event.npcType == type) + event.handler(src.sock, this); + } } #pragma endregion diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index cba6656..b0b8963 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -306,18 +306,18 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { // Fuse doesn't move // Blastons, Heal - Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467); + Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467); newbody->angle = oldbody->angle; - NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z, - plr->instanceID, oldbody->angle); + NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + oldbody->instanceID, oldbody->angle); // right arm, Adaptium, Stun - Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469); + Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469); arm->angle = oldbody->angle; - NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z, - plr->instanceID, oldbody->angle); + NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + oldbody->instanceID, oldbody->angle); } // summon left arm and stage 3 body @@ -328,18 +328,18 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { std::cout << "Lord Fuse stage three" << std::endl; // Cosmix, Damage Point - Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468); + Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468); newbody->angle = oldbody->angle; - NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z, - plr->instanceID, oldbody->angle); + NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, + newbody->instanceID, oldbody->angle); // Blastons, Heal - Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470); + Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470); arm->angle = oldbody->angle; - NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z, - plr->instanceID, oldbody->angle); + NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, + arm->instanceID, oldbody->angle); } std::vector NPCManager::NPCEvents = { From 208a301a4812faefac3e949bebb3c793f3e936d6 Mon Sep 17 00:00:00 2001 From: dongresource Date: Sun, 10 Sep 2023 19:47:42 +0200 Subject: [PATCH 100/104] Rename coord args in summonNPC() and constructors to clarify purpose This makes it clearer that the real coords aren't set until the first call to updateNPCPosition(). --- src/Entities.hpp | 8 ++++---- src/MobAI.hpp | 10 +++++----- src/NPCManager.cpp | 5 ++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Entities.hpp b/src/Entities.hpp index 2867801..22eb50a 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -106,11 +106,11 @@ struct CombatNPC : public BaseNPC, public ICombatant { std::unordered_map buffs = {}; - CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) + CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { - spawnX = x; - spawnY = y; - spawnZ = z; + this->spawnX = spawnX; + this->spawnY = spawnY; + this->spawnZ = spawnZ; kind = EntityKind::COMBAT_NPC; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 3e74d3f..a55f565 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -50,8 +50,8 @@ struct Mob : public CombatNPC { // temporary; until we're sure what's what nlohmann::json data = {}; - Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) - : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), + Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) + : CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]), sightRange(d["m_iSightRange"]) { state = AIState::ROAMING; @@ -62,9 +62,9 @@ struct Mob : public CombatNPC { idleRange = (int)data["m_iIdleRange"]; level = data["m_iNpcLevel"]; - roamX = x; - roamY = y; - roamZ = z; + roamX = spawnX; + roamY = spawnY; + roamZ = spawnZ; offsetX = 0; offsetY = 0; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index b0b8963..8a368bf 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -122,16 +122,15 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) { } // type must already be checked and updateNPCPosition() must be called on the result -BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) { +BaseNPC *NPCManager::summonNPC(int spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) { uint64_t inst = baseInstance ? MAPNUM(instance) : instance; - //assert(nextId < INT32_MAX); int id = nextId--; int team = NPCData[type]["m_iTeam"]; BaseNPC *npc = nullptr; if (team == 2) { - npc = new Mob(x, y, z, inst, type, NPCData[type], id); + npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id); // re-enable respawning, if desired ((Mob*)npc)->summoned = !respawn; From c113ea4a6cabccd60bb9af560d93b0375d2379ba Mon Sep 17 00:00:00 2001 From: dongresource Date: Sun, 10 Sep 2023 20:02:52 +0200 Subject: [PATCH 101/104] Refactor and generalize NPCEvent logic --- src/Combat.cpp | 10 ++++------ src/NPCManager.cpp | 12 +++++------- src/NPCManager.hpp | 13 ++++--------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 48598b1..80fbab1 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -316,12 +316,10 @@ void CombatNPC::transition(AIState newState, EntityRef src) { transition(AIState::INACTIVE, id); } - // TODO: Properly refactor this - if (newState == AIState::DEAD && src.kind == EntityKind::PLAYER) { - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == type) - event.handler(src.sock, this); - } + // trigger special NPCEvents, if applicable + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.triggerState == newState && event.npcType == type) + event.handler(this); } #pragma endregion diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 8a368bf..1d80a93 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -293,13 +293,12 @@ BaseNPC* NPCManager::getNearestNPC(std::set* chunks, int X, int Y, int Z return npc; } -// TODO: Move this to MobAI, possibly +// TODO: Move this to separate file in ai/ subdir when implementing more events #pragma region NPCEvents // summon right arm and stage 2 body -static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { +static void lordFuseStageTwo(CombatNPC *npc) { Mob *oldbody = (Mob*)npc; // adaptium, stun - Player *plr = PlayerManager::getPlayer(sock); std::cout << "Lord Fuse stage two" << std::endl; @@ -320,9 +319,8 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { } // summon left arm and stage 3 body -static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { +static void lordFuseStageThree(CombatNPC *npc) { Mob *oldbody = (Mob*)npc; - Player *plr = PlayerManager::getPlayer(sock); std::cout << "Lord Fuse stage three" << std::endl; @@ -342,8 +340,8 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { } std::vector NPCManager::NPCEvents = { - NPCEvent(2466, ON_KILLED, lordFuseStageTwo), - NPCEvent(2467, ON_KILLED, lordFuseStageThree), + NPCEvent(2466, AIState::DEAD, lordFuseStageTwo), + NPCEvent(2467, AIState::DEAD, lordFuseStageThree), }; #pragma endregion NPCEvents diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index c8738d0..ec497d3 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -14,20 +14,15 @@ #define RESURRECT_HEIGHT 400 -enum Trigger { - ON_KILLED, - ON_COMBAT -}; - -typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*); +typedef void (*NPCEventHandler)(CombatNPC*); struct NPCEvent { int32_t npcType; - int trigger; + AIState triggerState; NPCEventHandler handler; - NPCEvent(int32_t t, int tr, NPCEventHandler hndlr) - : npcType(t), trigger(tr), handler(hndlr) {} + NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr) + : npcType(t), triggerState(tr), handler(hndlr) {} }; namespace NPCManager { From 1800334bb75442df2bfcb406d5ceaa6a51a8016c Mon Sep 17 00:00:00 2001 From: dongresource Date: Wed, 13 Sep 2023 03:01:18 +0200 Subject: [PATCH 102/104] Fix buffs applying to non-COMBAT, non-ROAMING mobs --- src/Abilities.cpp | 4 ++-- src/Combat.cpp | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 0859220..58bfb45 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -123,7 +123,7 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour int combatLifetime = 0; if(!target->addBuff(timeBuffId, [drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) { - if(buff->id == ECSB_BOUNDINGBALL) { + if(buff->id == ECSB_BOUNDINGBALL && status == ETBU_ADD) { // drain ICombatant* combatant = dynamic_cast(self.getEntity()); combatant->takeDamage(buff->getLastSource(), 0); // aggro @@ -138,7 +138,7 @@ static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* sour Buffs::tickDrain(self, buff, COMBAT_TICKS_PER_DRAIN_PROC); // drain combatLifetime++; }, - &passiveBuff)) return SkillResult(); // no result if already buffed + &passiveBuff)) return SkillResult(); sSkillResult_Buff result{}; result.eCT = target->getCharType(); diff --git a/src/Combat.cpp b/src/Combat.cpp index 80fbab1..d204f99 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -179,6 +179,9 @@ bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, Buff if(!isAlive()) return false; + if (this->state != AIState::COMBAT && this->state != AIState::ROAMING) + return false; + if(!hasBuff(buffId)) { buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); return true; From 46296b76e9fa6d1bce842bcbd2a2b5833cfc923e Mon Sep 17 00:00:00 2001 From: dongresource Date: Wed, 13 Sep 2023 03:03:58 +0200 Subject: [PATCH 103/104] Clean up some random comments --- src/Abilities.hpp | 8 ++++---- src/Combat.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 2470516..100dadf 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -15,12 +15,12 @@ constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); enum class SkillType { DAMAGE = 1, HEAL_HP = 2, - KNOCKDOWN = 3, // dnd - SLEEP = 4, // dnd - SNARE = 5, // dnd + KNOCKDOWN = 3, // uses DamageNDebuff + SLEEP = 4, // uses DamageNDebuff + SNARE = 5, // uses DamageNDebuff HEAL_STAMINA = 6, STAMINA_SELF = 7, - STUN = 8, // dnd + STUN = 8, // uses DamageNDebuff WEAPONSLOW = 9, JUMP = 10, RUN = 11, diff --git a/src/Combat.cpp b/src/Combat.cpp index d204f99..87c10a7 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -175,7 +175,7 @@ void Player::step(time_t currTime) { #pragma endregion #pragma region CombatNPC -bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { /* stubbed */ +bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, BuffCallback onTick, BuffStack* stack) { if(!isAlive()) return false; @@ -192,7 +192,7 @@ bool CombatNPC::addBuff(int buffId, BuffCallback onUpdate, Buff return false; } -Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ +Buff* CombatNPC::getBuff(int buffId) { if(hasBuff(buffId)) { return buffs[buffId]; } From a14354ca4bdfd32f226dbaafd1b9dfc7cba18778 Mon Sep 17 00:00:00 2001 From: dongresource Date: Wed, 13 Sep 2023 03:45:52 +0200 Subject: [PATCH 104/104] Fix visual bug with Nano stamina while blocking Stun with Freedom --- src/Abilities.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 58bfb45..69b4326 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -76,12 +76,20 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat } sSkillResult_Damage_N_Debuff result{}; + result.iDamage = duration / 10; // we use the duration as the damage number (why?) result.iHP = target->getCurrentHP(); result.eCT = target->getCharType(); result.iID = target->getID(); result.bProtected = blocked; result.iConditionBitFlag = target->getCompositeCondition(); + + // for player targets, make sure to update Nano stamina + if (target->getCharType() == 1) { + Player *plr = dynamic_cast(target); + result.iStamina = plr->getActiveNano()->iStamina; + } + return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result); }