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); }