#include "Eggs.hpp" #include "servers/CNShardServer.hpp" #include "Player.hpp" #include "PlayerManager.hpp" #include "Abilities.hpp" #include "NPCManager.hpp" #include "Entities.hpp" #include "Items.hpp" #include using namespace Eggs; std::unordered_map Eggs::EggTypes; void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* plr = PlayerManager::getPlayer(sock); // 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; } SkillData* skill = &Abilities::SkillTable[skillId]; if(skill->drainType == SkillDrainType::PASSIVE) { // apply buff if(skill->targetType != SkillTargetType::PLAYERS) { std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl; } 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); } // use skill std::vector targets; targets.push_back(dynamic_cast(plr)); Abilities::useNPCSkill(src, skillId, targets); } static void eggStep(CNServer* serv, time_t currTime) { // check dead eggs and eggs in inactive chunks for (auto npc : NPCManager::NPCs) { if (npc.second->kind != EntityKind::EGG) continue; auto egg = (Egg*)npc.second; if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks)) continue; if (egg->deadUntil <= currTime) { // respawn it egg->dead = false; egg->deadUntil = 0; egg->hp = 400; Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); } } } void Eggs::npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg) { egg->iX = x; egg->iY = y; egg->iZ = z; // client doesn't care about egg->iMapNum egg->iShinyType = npc->iNPCType; egg->iShiny_ID = npc->iNPC_ID; } static void eggPickup(CNSocket* sock, CNPacketData* data) { auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; Player* plr = PlayerManager::getPlayer(sock); EntityRef eggRef = {pickup->iShinyID}; if (!eggRef.isValid()) { std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl; return; } auto egg = (Egg*)eggRef.getEntity(); if (egg->kind != EntityKind::EGG) { std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; return; } if (egg->dead) { std::cout << "[WARN] Player tried to open a dead egg?!" << std::endl; return; } /* this has some issues with position desync, leaving it out for now if (abs(egg->x - plr->x)>500 || abs(egg->y - plr->y) > 500) { std::cout << "[WARN] Player tried to open an egg isn't nearby?!" << std::endl; return; } */ int typeId = egg->type; if (EggTypes.find(typeId) == EggTypes.end()) { std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl; return; } EggType* type = &EggTypes[typeId]; /* * 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; // in general client finds correct icon on it's own, // but for damage we have to supply correct CSTB if (resp.iSkillID == 183) resp.eCSTB = ECSB_INFECTION; sock->sendPacket(resp, P_FE2CL_REP_SHINY_PICKUP_SUCC); } // 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 = Items::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) NPCManager::destroyNPC(eggRef.id); else { Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); egg->dead = true; egg->deadUntil = getTime() + (time_t)type->regen * 1000; egg->hp = 0; } } void Eggs::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup); REGISTER_SHARD_TIMER(eggStep, 1000); }