From cb94018e46e68a7ed8cebe61671be8f3b03e711e Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 25 Jul 2022 23:01:20 -0700 Subject: [PATCH] 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 2d6f102..3b58424 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 90df959..d20a633 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -93,14 +93,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 ec50fa5..f6d1216 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;