#include "Nanos.hpp" #include "servers/CNShardServer.hpp" #include "PlayerManager.hpp" #include "Missions.hpp" #include "Abilities.hpp" #include "NPCManager.hpp" #include using namespace Nanos; std::map Nanos::NanoTable; std::map Nanos::NanoTunings; #pragma region Helper methods void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm) { if (nanoID <= 0 || nanoID >= NANO_COUNT) return; Player *plr = PlayerManager::getPlayer(sock); int level = plr->level; #ifndef ACADEMY level = nanoID < plr->level ? plr->level : nanoID; /* * Spend the necessary Fusion Matter. * Note the use of the not-yet-incremented plr->level as opposed to level. * Doing it the other way always leaves the FM at 0. Jade totally called it. */ plr->level = level; if (spendfm) Missions::updateFusionMatter(sock, -(int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]); #endif // Send to client INITSTRUCT(sP_FE2CL_REP_PC_NANO_CREATE_SUCC, resp); resp.Nano.iID = nanoID; resp.Nano.iStamina = 150; resp.iQuestItemSlotNum = slot; resp.iPC_Level = level; resp.iPC_FusionMatter = plr->fusionmatter; if (plr->activeNano > 0 && plr->activeNano == nanoID) summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs // Update player plr->Nanos[nanoID] = resp.Nano; sock->sendPacket(resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC); /* * iPC_Level in NANO_CREATE_SUCC sets the player's level. * Other players must be notified of the change as well. Both P_FE2CL_REP_PC_NANO_CREATE and * P_FE2CL_REP_PC_CHANGE_LEVEL appear to play the same animation, but only the latter affects * the other player's displayed level. */ INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp2); resp2.iPC_ID = plr->iID; resp2.iPC_Level = level; // Update other players' perception of the player's level PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL); } void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp); resp.iActiveNanoSlotNum = slot; Player *plr = PlayerManager::getPlayer(sock); if (slot > 2 || slot < -1) return; // sanity check int16_t nanoID = slot == -1 ? 0 : plr->equippedNanos[slot]; if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0) return; // prevent powerless nanos from summoning if (nanoID >= NANO_COUNT || nanoID < 0) return; // sanity check plr->activeNano = nanoID; sNano& nano = plr->Nanos[nanoID]; 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; 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 sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC); // 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 = nano; PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE); } static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) { if (skill == nullptr || skill->iNanoID >= NANO_COUNT || skill->iNanoID < 0) return; Player *plr = PlayerManager::getPlayer(sock); if (plr->activeNano > 0 && plr->activeNano == skill->iNanoID) summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs sNano nano = plr->Nanos[skill->iNanoID]; nano.iSkillID = skill->iTuneID; plr->Nanos[skill->iNanoID] = nano; // Send to client INITSTRUCT(sP_FE2CL_REP_NANO_TUNE_SUCC, resp); resp.iNanoID = skill->iNanoID; resp.iSkillID = skill->iTuneID; resp.iPC_FusionMatter = plr->fusionmatter; resp.aItem[9] = plr->Inven[0]; // quick fix to make sure item in slot 0 doesn't get yeeted by default // check if there's any garbage in the item slot array (this'll happen when a nano station isn't used) for (int i = 0; i < 10; i++) { if (skill->aiNeedItemSlotNum[i] < 0 || skill->aiNeedItemSlotNum[i] >= AINVEN_COUNT) { sock->sendPacket(resp, P_FE2CL_REP_NANO_TUNE_SUCC); return; // stop execution, don't run consumption logic } } #ifndef ACADEMY if (plr->fusionmatter < (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]) // sanity check return; #endif plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]; int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount; int reqItemID = NanoTunings[skill->iTuneID].reqItems; int i = 0; while (reqItemCount > 0 && i < 10) { sItemBase& item = plr->Inven[skill->aiNeedItemSlotNum[i]]; if (item.iType == 7 && item.iID == reqItemID) { if (item.iOpt > reqItemCount) { item.iOpt -= reqItemCount; reqItemCount = 0; } else { reqItemCount -= item.iOpt; item.iID = 0; item.iType = 0; item.iOpt = 0; } } i++; // next slot } resp.iPC_FusionMatter = plr->fusionmatter; // update fusion matter in packet // update items clientside for (int i = 0; i < 10; i++) { if (skill->aiNeedItemSlotNum[i]) { // non-zero check resp.aItem[i] = plr->Inven[skill->aiNeedItemSlotNum[i]]; resp.aiItemSlotNum[i] = skill->aiNeedItemSlotNum[i]; } } sock->sendPacket(resp, P_FE2CL_REP_NANO_TUNE_SUCC); DEBUGLOG( std::cout << PlayerManager::getPlayerName(plr) << " set skill id " << skill->iTuneID << " for nano: " << skill->iNanoID << std::endl; ) } // 0=A 1=B 2=C -1=Not found int Nanos::nanoStyle(int nanoID) { if (nanoID < 1 || nanoID >= (int)NanoTable.size()) return -1; return NanoTable[nanoID].style; } bool Nanos::getNanoBoost(Player* plr) { for (int i = 0; i < 3; i++) if (plr->equippedNanos[i] == plr->activeNano) if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i)) return true; return false; } #pragma endregion static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { auto nano = (sP_CL2FE_REQ_NANO_EQUIP*)data->buf; INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp); Player *plr = PlayerManager::getPlayer(sock); // sanity checks if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0) return; if (nano->iNanoID < 0 || nano->iNanoID >= NANO_COUNT) return; resp.iNanoID = nano->iNanoID; resp.iNanoSlotNum = nano->iNanoSlotNum; // Update player plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID; // unsummon nano if replaced if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) summonNano(sock, -1); sock->sendPacket(resp, P_FE2CL_REP_NANO_EQUIP_SUCC); } static void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) { auto nano = (sP_CL2FE_REQ_NANO_UNEQUIP*)data->buf; INITSTRUCT(sP_FE2CL_REP_NANO_UNEQUIP_SUCC, resp); Player *plr = PlayerManager::getPlayer(sock); // sanity check if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0) return; resp.iNanoSlotNum = nano->iNanoSlotNum; // unsummon nano if removed if (plr->equippedNanos[nano->iNanoSlotNum] == plr->activeNano) summonNano(sock, -1); // update player plr->equippedNanos[nano->iNanoSlotNum] = 0; sock->sendPacket(resp, P_FE2CL_REP_NANO_UNEQUIP_SUCC); } static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf; Player *plr = PlayerManager::getPlayer(sock); summonNano(sock, pkt->iNanoSlotNum); DEBUGLOG( std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano slot: " << pkt->iNanoSlotNum << std::endl; ) } static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); // 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; } 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; ) 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) summonNano(sock, -1); } static void nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) { auto skill = (sP_CL2FE_REQ_NANO_TUNE*)data->buf; setNanoSkill(sock, skill); } static void nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data) { auto skillGM = (sP_CL2FE_REQ_NANO_TUNE*)data->buf; setNanoSkill(sock, skillGM); } static void nanoRecallRegisterHandler(CNSocket* sock, CNPacketData* data) { auto recallData = (sP_CL2FE_REQ_REGIST_RXCOM*)data->buf; if (NPCManager::NPCs.find(recallData->iNPCID) == NPCManager::NPCs.end()) return; Player* plr = PlayerManager::getPlayer(sock); BaseNPC *npc = NPCManager::NPCs[recallData->iNPCID]; INITSTRUCT(sP_FE2CL_REP_REGIST_RXCOM, response); response.iMapNum = plr->recallInstance = (int32_t)npc->instanceID; // Never going to recall into a Fusion Lair response.iX = plr->recallX = npc->x; response.iY = plr->recallY = npc->y; response.iZ = plr->recallZ = npc->z; sock->sendPacket(response, P_FE2CL_REP_REGIST_RXCOM); } static void nanoRecallHandler(CNSocket* sock, CNPacketData* data) { auto recallData = (sP_CL2FE_REQ_WARP_USE_RECALL*)data->buf; Player* plr = PlayerManager::getPlayer(sock); Player* otherPlr = PlayerManager::getPlayerFromID(recallData->iGroupMemberID); if (otherPlr == nullptr) return; // ensure the group member is still in the same IZ if (otherPlr->instanceID != plr->instanceID) return; // do not allow hypothetical recall points in lairs to mess with the respawn logic if (PLAYERID(plr->instanceID) != 0) return; if ((int32_t)plr->instanceID == otherPlr->recallInstance) PlayerManager::sendPlayerTo(sock, otherPlr->recallX, otherPlr->recallY, otherPlr->recallZ, otherPlr->recallInstance); else { INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response) sock->sendPacket(response, P_FE2CL_REP_WARP_USE_RECALL_FAIL); } } static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) { Player* player = PlayerManager::getPlayer(sock); // sanity checks if (player->activeNano == -1 || player->batteryN == 0) return; sNano nano = player->Nanos[player->activeNano]; int difference = 150 - nano.iStamina; if (player->batteryN < difference) difference = player->batteryN; if (difference == 0) return; INITSTRUCT(sP_FE2CL_REP_CHARGE_NANO_STAMINA, response); response.iNanoID = nano.iID; response.iNanoStamina = nano.iStamina + difference; response.iBatteryN = player->batteryN - difference; sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA); // now update serverside player->batteryN -= difference; player->Nanos[nano.iID].iStamina += difference; } void Nanos::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_ACTIVE, nanoSummonHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_EQUIP, nanoEquipHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_UNEQUIP, nanoUnEquipHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_TUNE, nanoSkillSetHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO_SKILL, nanoSkillSetGMHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_SKILL_USE, nanoSkillUseHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_RXCOM, nanoRecallRegisterHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_WARP_USE_RECALL, nanoRecallHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_CHARGE_NANO_STAMINA, nanoPotionHandler); }