diff --git a/src/Abilities.cpp b/src/Abilities.cpp index 9bb14b0..63586a9 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; @@ -35,7 +33,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(); @@ -46,16 +45,67 @@ 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); } +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) { + int duration = skill->durationTime[power]; + int strength = skill->values[0][power]; + BuffStack passiveBuff = { + 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 + sSkillResult_Buff result{}; result.eCT = target->getCharType(); result.iID = target->getID(); @@ -96,7 +146,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); + 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(); @@ -118,63 +174,70 @@ 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 EST_DAMAGE: - resultSize = sizeof(sSkillResult_Damage); + case SkillType::DAMAGE: skillHandler = handleSkillDamage; break; - case EST_HEAL_HP: - case EST_RETURNHOMEHEAL: - resultSize = sizeof(sSkillResult_Heal_HP); + case SkillType::HEAL_HP: + case SkillType::RETURNHOMEHEAL: skillHandler = handleSkillHealHP; break; - case EST_JUMP: - case EST_RUN: - case EST_FREEDOM: - case EST_PHOENIX: - case EST_INVULNERABLE: - case EST_MINIMAPENEMY: - case EST_MINIMAPTRESURE: - case EST_NANOSTIMPAK: - case EST_PROTECTBATTERY: - case EST_PROTECTINFECTION: - case EST_REWARDBLOB: - case EST_REWARDCASH: - case EST_STAMINA_SELF: - case EST_STEALTH: - resultSize = sizeof(sSkillResult_Buff); + case SkillType::KNOCKDOWN: + case SkillType::SLEEP: + case SkillType::SNARE: + case SkillType::STUN: + skillHandler = handleSkillDamageNDebuff; + break; + case SkillType::JUMP: + case SkillType::RUN: + case SkillType::STEALTH: + case SkillType::MINIMAPENEMY: + case SkillType::MINIMAPTRESURE: + 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::NANOSTIMPAK: + case SkillType::BUFFHEAL: skillHandler = handleSkillBuff; break; - case EST_BATTERYDRAIN: - resultSize = sizeof(sSkillResult_BatteryDrain); - skillHandler = handleSkillBatteryDrain; + case SkillType::BLOODSUCKING: + skillHandler = handleSkillLeech; break; - case EST_RECALL: - case EST_RECALL_GROUP: - resultSize = sizeof(sSkillResult_Move); - skillHandler = handleSkillMove; - break; - case EST_PHOENIX_GROUP: - resultSize = sizeof(sSkillResult_Resurrect); + case SkillType::RETROROCKET_SELF: + // no-op + return results; + case SkillType::PHOENIX_GROUP: skillHandler = handleSkillResurrect; break; + case SkillType::RECALL: + case SkillType::RECALL_GROUP: + skillHandler = handleSkillMove; + break; + case SkillType::BATTERYDRAIN: + skillHandler = handleSkillBatteryDrain; + break; default: - std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl; + std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl; return results; } - assert(skillHandler != nullptr); for(ICombatant* target : targets) { assert(target != nullptr); SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target); if(result.size == 0) continue; // skill not applicable - if(result.size != resultSize) { - std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl; + if(result.size > MAX_SKILLRESULT_SIZE) { + std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl; continue; } results.push_back(result); @@ -182,30 +245,42 @@ 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, 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); + ICombatant* combatant = dynamic_cast(plr); - 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; - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) { + if(skill->drainType == SkillDrainType::ACTIVE) { + nano.iStamina -= skill->batteryUse[boost]; + if (nano.iStamina <= 0) + nano.iStamina = 0; + } + + std::vector results = handleSkill(skill, boost, combatant, affected); + if(results.empty()) return; // no effect; no need for confirmation packets + + // 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); @@ -215,12 +290,17 @@ void Abilities::useNanoSkill(CNSocket* sock, sNano& nano, std::vectoriSkillID = 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)); + attachSkillResults(results, (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); } void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector affected) { @@ -235,43 +315,73 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector 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(results.empty()) return; // no effect; no need for confirmation packets - 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); 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)); + attachSkillResults(results, (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) { +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 targets; +std::vector Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) { + 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) + return {src}; // client sends 0 targets for certain self-targeting skills (recall) + + // individuals + 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::PLAYERS) { + // player + 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"; } } @@ -279,64 +389,66 @@ std::vector Abilities::matchTargets(SkillData* skill, int count, int3 } /* 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; + default: + break; } return result; } diff --git a/src/Abilities.hpp b/src/Abilities.hpp index 275c2c5..ef703b1 100644 --- a/src/Abilities.hpp +++ b/src/Abilities.hpp @@ -7,9 +7,51 @@ #include #include +#include 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 + HEAL_STAMINA = 6, + STAMINA_SELF = 7, + STUN = 8, // dnd + 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, @@ -21,7 +63,7 @@ enum class SkillEffectTarget { enum class SkillTargetType { MOBS = 1, - SELF = 2, + PLAYERS = 2, GROUP = 3 }; @@ -34,6 +76,7 @@ struct SkillResult { size_t size; uint8_t payload[MAX_SKILLRESULT_SIZE]; SkillResult(size_t len, void* dat) { + assert(len <= MAX_SKILLRESULT_SIZE); size = len; memcpy(payload, dat, len); } @@ -43,7 +86,7 @@ struct SkillResult { }; struct SkillData { - int skillType; // eST + SkillType skillType; // eST SkillEffectTarget effectTarget; int effectType; // always 1? SkillTargetType targetType; @@ -60,9 +103,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*); - int getCSTBFromST(int eSkillType); + std::vector matchTargets(ICombatant*, SkillData*, int, int32_t*); + int getCSTBFromST(SkillType skillType); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 4220b1e..93e3542 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)) { @@ -51,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); @@ -58,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(); @@ -173,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; } @@ -874,9 +891,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/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/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/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 28a47c0..9dffe5d 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; @@ -72,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) { @@ -80,7 +80,6 @@ public: type = t; hp = 400; angle = _A; - cbf = 0; id = _id; instanceID = iID; }; @@ -88,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 { @@ -105,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; @@ -117,12 +118,15 @@ 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; 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/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/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/MobAI.cpp b/src/MobAI.cpp index 26552f1..53b8e2b 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)); } @@ -442,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); @@ -553,9 +553,9 @@ 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->cbf & CSB_BIT_BOUNDINGBALL) { + && self->hasBuff(ECSB_BOUNDINGBALL)) { drainMobHP(self, self->maxHealth / 20); // lose 5% every second self->lastDrainTime = currTime; } @@ -564,27 +564,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 + 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 +595,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 +701,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 +774,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 +818,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; diff --git a/src/Nanos.cpp b/src/Nanos.cpp index f587b68..8fa9629 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -69,52 +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); - 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; @@ -139,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, 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 @@ -309,28 +265,19 @@ 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; + ICombatant* plrCombatant = dynamic_cast(plr); + std::vector targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); + Abilities::useNanoSkill(sock, skillData, nano, targetData); - 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]);*/ - - 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); } 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; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index a4c4865..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++) { @@ -401,12 +409,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 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 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,