diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 128cd17..998c125 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -7,6 +7,7 @@ #include "NanoManager.hpp" #include +#define M_PI 3.14159265358979323846 #include #include #include @@ -21,6 +22,8 @@ std::map NPCManager::Warps; std::vector NPCManager::RespawnPoints; /// sock, CBFlag -> until std::map, time_t> NPCManager::EggBuffs; +std::unordered_map NPCManager::EggTypes; +std::unordered_map NPCManager::Eggs; nlohmann::json NPCManager::NPCData; /* @@ -43,8 +46,10 @@ void NPCManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_SUMMON, eggSummon); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup); - REGISTER_SHARD_TIMER(buffStep, 1000); + REGISTER_SHARD_TIMER(eggStep, 1000); } void NPCManager::removeNPC(std::vector viewableChunks, int32_t id) { @@ -63,6 +68,17 @@ void NPCManager::removeNPC(std::vector viewableChunks, int32_t id) { } } break; + case NPC_EGG: + INITSTRUCT(sP_FE2CL_SHINY_EXIT, exitEggData); + exitEggData.iShinyID = id; + + for (Chunk* chunk : viewableChunks) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&exitEggData, P_FE2CL_SHINY_EXIT, sizeof(sP_FE2CL_SHINY_EXIT)); + } + } + break; default: // create struct INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); @@ -94,6 +110,17 @@ void NPCManager::addNPC(std::vector viewableChunks, int32_t id) { } } break; + case NPC_EGG: + INITSTRUCT(sP_FE2CL_SHINY_ENTER, enterEggData); + npcDataToEggData(&npc->appearanceData, &enterEggData.ShinyAppearanceData); + + for (Chunk* chunk : viewableChunks) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&enterEggData, P_FE2CL_SHINY_ENTER, sizeof(sP_FE2CL_SHINY_ENTER)); + } + } + break; default: // create struct INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); @@ -135,6 +162,10 @@ void NPCManager::destroyNPC(int32_t id) { if (MobManager::Mobs.find(id) != MobManager::Mobs.end()) MobManager::Mobs.erase(id); + // remove from eggs + if (Eggs.find(id) != Eggs.end()) + Eggs.erase(id); + // finally, remove it from the map and free it NPCs.erase(id); delete entity; @@ -654,7 +685,6 @@ BaseNPC* NPCManager::getNearestNPC(std::vector chunks, int X, int Y, int return npc; } - int NPCManager::eggBuffPlayer(CNSocket* sock, int skillId, int duration) { Player* plr = PlayerManager::getPlayer(sock); @@ -702,12 +732,12 @@ int NPCManager::eggBuffPlayer(CNSocket* sock, int skillId, int duration) { return 0; } -void NPCManager::buffStep(CNServer* serv, time_t currTime) { - +void NPCManager::eggStep(CNServer* serv, time_t currTime) { + // tick buffs auto it = EggBuffs.begin(); while (it != EggBuffs.end()) { // check remaining time - if (it->second > getTimestamp()) + if (it->second > currTime) it++; // if time reached 0 @@ -747,4 +777,148 @@ void NPCManager::buffStep(CNServer* serv, time_t currTime) { } } + + // check dead eggs + for (auto egg : Eggs) { + if (!egg.second->dead) + return; + if (egg.second->deadUntil <= currTime) { + // respawn it + addNPC(egg.second->currentChunks, egg.first); + egg.second->dead = false; + } + } + +} + +void NPCManager::npcDataToEggData(sNPCAppearanceData* npc, sShinyAppearanceData* egg) { + egg->iX = npc->iX; + egg->iY = npc->iY; + egg->iZ = npc->iZ; + // client doesn't care about egg->iMapNum + egg->iShinyType = npc->iNPCType; + egg->iShiny_ID = npc->iNPC_ID; +} + +void NPCManager::eggSummon(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_SHINY_SUMMON)) + return; // malformed packet + + sP_CL2FE_REQ_SHINY_SUMMON* summon = (sP_CL2FE_REQ_SHINY_SUMMON*)data->buf; + + assert(NPCManager::nextId < INT32_MAX); + int id = NPCManager::nextId++; + + Player* plr = PlayerManager::getPlayer(sock); + + if (plr == nullptr) + return; + + /* + * the packet sends us player position with a random offset, + * instead we're using some math to place the egg right in front of the player + */ + + int addX = -500.0f * sin(plr->angle / 180.0f * M_PI); + int addY = -500.0f * cos(plr->angle / 180.0f * M_PI); + + Egg* egg = new Egg (plr->x + addX, plr->y + addY, plr->z, plr->instanceID, summon->iShinyType, id, false); + NPCManager::NPCs[id] = egg; + NPCManager::Eggs[id] = egg; + NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID); + +} + +void NPCManager::eggPickup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_SHINY_PICKUP)) + return; // malformed packet + + sP_CL2FE_REQ_SHINY_PICKUP* pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + if (plr == nullptr) + return; + + int eggId = pickup->iShinyID; + + if (Eggs.find(eggId) == Eggs.end()) { + std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl; + return; + } + Egg* egg = Eggs[eggId]; + + if (egg->dead) { + std::cout << "[WARN] Player tried to open a dead egg?!" << std::endl; + return; + } + + int typeId = egg->appearanceData.iNPCType; + if (EggTypes.find(typeId) == EggTypes.end()) { + if (egg->npcClass != NPCClass::NPC_EGG) { + std::cout << "[WARN] Egg Type "<< typeId <<" not found!" << std::endl; + return; + } + } + EggType* type = &EggTypes[typeId]; + + // response packet + INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp); + resp.iSkillID = type->effectId; + if (resp.iSkillID == 0) + resp.iSkillID = -1; + + // this packet is only responsible for showing UI effect stuff + sock->sendPacket((void*)&resp, P_FE2CL_REP_SHINY_PICKUP_SUCC, sizeof(sP_FE2CL_REP_SHINY_PICKUP_SUCC)); + + // buff + if (type->effectId != 0) + eggBuffPlayer(sock, type->effectId, type->duration); + + // drop + if (type->dropCrateId != 0) { + const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); + assert(resplen < CN_PACKET_BUFFER_SIZE - 8); + // we know it's only one trailing struct, so we can skip full validation + + uint8_t respbuf[resplen]; // not a variable length array, don't worry + sP_FE2CL_REP_REWARD_ITEM* reward = (sP_FE2CL_REP_REWARD_ITEM*)respbuf; + sItemReward* item = (sItemReward*)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); + + // don't forget to zero the buffer! + memset(respbuf, 0, resplen); + + // send back player's stats + reward->m_iCandy = plr->money; + reward->m_iFusionMatter = plr->fusionmatter; + reward->m_iBatteryN = plr->batteryN; + reward->m_iBatteryW = plr->batteryW; + reward->iFatigue = 100; // prevents warning message + reward->iFatigue_Level = 1; + reward->iItemCnt = 1; // remember to update resplen if you change this + + int slot = ItemManager::findFreeSlot(plr); + + // no space for drop + if (slot != -1) { + + // item reward + item->sItem.iType = 9; + item->sItem.iOpt = 1; + item->sItem.iID = type->dropCrateId; + item->iSlotNum = slot; + item->eIL = 1; // Inventory Location. 1 means player inventory. + + // update player + plr->Inven[slot] = item->sItem; + sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); + } + } + + if (egg->summoned) + destroyNPC(eggId); + else { + removeNPC(egg->currentChunks, eggId); + egg->dead = true; + egg->deadUntil = getTimestamp() + type->regen; + } } diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 6a0d593..fa19eb3 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -7,6 +7,7 @@ #include "contrib/JSON.hpp" #include +#include #include #define RESURRECT_HEIGHT 400 @@ -16,11 +17,32 @@ struct WarpLocation { int x, y, z, instanceID, isInstance, limitTaskID, npcID; }; +struct Egg : public BaseNPC { + bool summoned; + bool dead = false; + time_t deadUntil; + + Egg(int x, int y, int z, uint64_t iID, int type, int32_t id, bool summon) + : BaseNPC(x, y, z, 0, iID, type, id) { + summoned = summon; + npcClass = NPCClass::NPC_EGG; + } +}; + +struct EggType { + int dropCrateId; + int effectId; + int duration; + int regen; +}; + namespace NPCManager { extern std::map NPCs; extern std::map Warps; - extern std::vector RespawnPoints; + extern std::vector RespawnPoints; + extern std::unordered_map Eggs; extern std::map, time_t> EggBuffs; + extern std::unordered_map EggTypes; extern nlohmann::json NPCData; extern int32_t nextId; void init(); @@ -54,5 +76,8 @@ namespace NPCManager { /// returns -1 on fail int eggBuffPlayer(CNSocket* sock, int skillId, int duration); - void buffStep(CNServer* serv, time_t currTime); + void eggStep(CNServer* serv, time_t currTime); + void npcDataToEggData(sNPCAppearanceData* npc, sShinyAppearanceData* egg); + void eggSummon(CNSocket* sock, CNPacketData* data); + void eggPickup(CNSocket* sock, CNPacketData* data); } diff --git a/src/NanoManager.cpp b/src/NanoManager.cpp index 2a153cc..9b9ab71 100644 --- a/src/NanoManager.cpp +++ b/src/NanoManager.cpp @@ -23,15 +23,15 @@ std::set SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94}; // passive powers std::set ScavengePowers = {3, 50, 99}; -std::set RunPowers = {4, 68, 86}; +std::set RunPowers = {4, 68, 86, 132}; std::set GroupRunPowers = {8, 62, 73}; std::set BonusPowers = {6, 54, 104}; -std::set GuardPowers = {9, 57, 76}; -std::set RadarPowers = {11, 67, 95}; -std::set AntidotePowers = {14, 58, 102}; -std::set FreedomPowers = {31, 39, 107}; +std::set GuardPowers = {9, 57, 76, 157}; +std::set RadarPowers = {11, 67, 95, 156}; +std::set AntidotePowers = {14, 58, 102, 159}; +std::set FreedomPowers = {31, 39, 107, 158}; std::set GroupFreedomPowers = {15, 55, 77}; -std::set JumpPowers = {16, 44, 88}; +std::set JumpPowers = {16, 44, 88, 154}; std::set GroupJumpPowers = {35, 60, 100}; std::set SelfRevivePowers = {22, 48, 84}; std::set SneakPowers = {29, 72, 80}; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index db51186..f00ded7 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -117,6 +117,11 @@ bool PlayerManager::removePlayerFromChunks(std::vector chunks, CNSocket* exitBusData.iT_ID = id; sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT)); break; + case NPC_EGG: + INITSTRUCT(sP_FE2CL_SHINY_EXIT, exitEggData); + exitEggData.iShinyID = id; + sock->sendPacket((void*)&exitEggData, P_FE2CL_SHINY_EXIT, sizeof(sP_FE2CL_SHINY_EXIT)); + break; default: INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); exitData.iNPC_ID = id; @@ -151,6 +156,11 @@ void PlayerManager::addPlayerToChunks(std::vector chunks, CNSocket* sock enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ }; sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER)); break; + case NPC_EGG: + INITSTRUCT(sP_FE2CL_SHINY_ENTER, enterEggData); + NPCManager::npcDataToEggData(&npc->appearanceData, &enterEggData.ShinyAppearanceData); + sock->sendPacket((void*)&enterEggData, P_FE2CL_SHINY_ENTER, sizeof(sP_FE2CL_SHINY_ENTER)); + break; default: INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); enterData.NPCAppearanceData = NPCManager::NPCs[id]->appearanceData; diff --git a/src/TableData.cpp b/src/TableData.cpp index 3bae11c..0f4d0d8 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -223,6 +223,8 @@ void TableData::init() { loadDrops(); + loadEggs(); + loadPaths(&nextId); // load paths loadGruntwork(&nextId); @@ -433,6 +435,35 @@ void TableData::loadDrops() { } } +void TableData::loadEggs() { + try { + std::ifstream inFile(settings::EGGSJSON); + nlohmann::json eggData; + + // read file into json + inFile >> eggData; + + // EggTypes + nlohmann::json eggTypes = eggData["EggTypes"]; + for (nlohmann::json::iterator _eggType = eggTypes.begin(); _eggType != eggTypes.end(); _eggType++) { + auto eggType = _eggType.value(); + EggType toAdd = {}; + toAdd.dropCrateId = (int)eggType["DropCrateId"]; + toAdd.effectId = (int)eggType["EffectId"]; + toAdd.duration = (int)eggType["Duration"]; + toAdd.regen= (int)eggType["Regen"]; + NPCManager::EggTypes[(int)eggType["Id"]] = toAdd; + } + + std::cout << "[INFO] Loaded Egg Data" <