mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-12-23 03:40:05 +00:00
374 lines
13 KiB
C++
374 lines
13 KiB
C++
#include "Nanos.hpp"
|
|
|
|
#include "servers/CNShardServer.hpp"
|
|
|
|
#include "PlayerManager.hpp"
|
|
#include "Missions.hpp"
|
|
#include "Abilities.hpp"
|
|
#include "NPCManager.hpp"
|
|
|
|
#include <cmath>
|
|
|
|
using namespace Nanos;
|
|
|
|
std::map<int32_t, NanoData> Nanos::NanoTable;
|
|
std::map<int32_t, NanoTuning> 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<ICombatant*>(plr);
|
|
int32_t targets[] = { plr->iID };
|
|
std::vector<ICombatant*> 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<ICombatant*>(plr);
|
|
std::vector<ICombatant*> 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);
|
|
}
|