diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index be0a746..47c55e5 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -499,7 +499,7 @@ static void eggCommand(std::string full, std::vector& args, CNSocke } char* tmp; - int eggType = std::strtol(args[1].c_str(), &tmp, 10); + int eggType = std::strtol(args[1].c_str(), &tmp, 10); if (*tmp) return; @@ -520,7 +520,6 @@ static void eggCommand(std::string full, std::vector& args, CNSocke Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork NPCManager::NPCs[id] = egg; - Eggs::Eggs[id] = egg; NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle); // add to template diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 5b8d58c..49c4c87 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -13,7 +13,6 @@ using namespace Eggs; /// sock, CBFlag -> until std::map, time_t> Eggs::EggBuffs; std::unordered_map Eggs::EggTypes; -std::unordered_map Eggs::Eggs; int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* plr = PlayerManager::getPlayer(sock); @@ -35,10 +34,10 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; - sP_FE2CL_NPC_SKILL_HIT* skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; + auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; if (skillId == 183) { // damage egg - sSkillResult_Damage* skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); + auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; @@ -48,7 +47,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { plr->HP = 0; skill->iHP = plr->HP; } else if (skillId == 150) { // heal egg - sSkillResult_Heal_HP* skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); + auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; @@ -58,7 +57,7 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { plr->HP = PC_MAXHEALTH(plr->level); skill->iHP = plr->HP; } else { // regular buff egg - sSkillResult_Buff* skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); + auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); memset(respbuf, 0, resplen); skill->eCT = 1; skill->iID = plr->iID; @@ -92,9 +91,9 @@ static void eggStep(CNServer* serv, time_t currTime) { auto it = EggBuffs.begin(); while (it != EggBuffs.end()) { // check remaining time - if (it->second > timeStamp) + if (it->second > timeStamp) { it++; - else { // if time reached 0 + } else { // if time reached 0 CNSocket* sock = it->first.first; int32_t CBFlag = it->first.second; Player* plr = PlayerManager::getPlayer(sock); @@ -109,13 +108,13 @@ static void eggStep(CNServer* serv, time_t currTime) { resp.eTBT = 3; // for egg buffs plr->iConditionBitFlag &= ~CBFlag; resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag; - sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); + sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players resp2.eCT = 1; resp2.iID = plr->iID; resp2.iConditionBitFlag = plr->iConditionBitFlag; - PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); } } // remove buff from the map @@ -124,16 +123,21 @@ static void eggStep(CNServer* serv, time_t currTime) { } // check dead eggs and eggs in inactive chunks - for (auto egg : Eggs::Eggs) { - if (!egg.second->dead || !Chunking::inPopulatedChunks(&egg.second->viewableChunks)) + for (auto npc : NPCManager::NPCs) { + if (npc.second->type != EntityType::EGG) continue; - if (egg.second->deadUntil <= timeStamp) { + + auto egg = (Egg*)npc.second; + if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks)) + continue; + + if (egg->deadUntil <= timeStamp) { // respawn it - egg.second->dead = false; - egg.second->deadUntil = 0; - egg.second->appearanceData.iHP = 400; + egg->dead = false; + egg->deadUntil = 0; + egg->appearanceData.iHP = 400; - Chunking::addEntityToChunks(Chunking::getViewableChunks(egg.second->chunkPos), {egg.first}); + Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); } } @@ -149,16 +153,20 @@ void Eggs::npcDataToEggData(sNPCAppearanceData* npc, sShinyAppearanceData* egg) } static void eggPickup(CNSocket* sock, CNPacketData* data) { - sP_CL2FE_REQ_SHINY_PICKUP* pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; + auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; Player* plr = PlayerManager::getPlayer(sock); - int eggId = pickup->iShinyID; + EntityRef eggRef = {pickup->iShinyID}; - if (Eggs::Eggs.find(eggId) == Eggs::Eggs.end()) { + if (!eggRef.isValid()) { std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl; return; } - Egg* egg = Eggs::Eggs[eggId]; + auto egg = (Egg*)eggRef.getEntity(); + if (egg->type != EntityType::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; @@ -167,7 +175,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { /* this has some issues with position desync, leaving it out for now if (abs(egg->appearanceData.iX - plr->x)>500 || abs(egg->appearanceData.iY - plr->y) > 500) { - std::cout << "[WARN] Player tried to open an egg from the other chunk?!" << std::endl; + std::cout << "[WARN] Player tried to open an egg isn't nearby?!" << std::endl; return; } */ @@ -182,16 +190,14 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { // buff the player if (type->effectId != 0) - eggBuffPlayer(sock, type->effectId, eggId, type->duration); + 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) - { + if (type->effectId != 0) { INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp); resp.iSkillID = type->effectId; @@ -200,7 +206,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { if (resp.iSkillID == 183) resp.eCSTB = ECSB_INFECTION; - sock->sendPacket((void*)&resp, P_FE2CL_REP_SHINY_PICKUP_SUCC, sizeof(sP_FE2CL_REP_SHINY_PICKUP_SUCC)); + sock->sendPacket(resp, P_FE2CL_REP_SHINY_PICKUP_SUCC); } // drop @@ -244,9 +250,9 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { } if (egg->summoned) - NPCManager::destroyNPC(eggId); + NPCManager::destroyNPC(eggRef.id); else { - Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), {eggId}); + Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); egg->dead = true; egg->deadUntil = getTime() + (time_t)type->regen * 1000; egg->appearanceData.iHP = 0; diff --git a/src/Eggs.hpp b/src/Eggs.hpp index 9092133..2e5a782 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -11,7 +11,6 @@ struct EggType { }; namespace Eggs { - extern std::unordered_map Eggs; extern std::map, time_t> EggBuffs; extern std::unordered_map EggTypes; diff --git a/src/Entities.cpp b/src/Entities.cpp index 65bf024..9c286f1 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -4,6 +4,7 @@ #include "PlayerManager.hpp" #include "NPCManager.hpp" #include "Eggs.hpp" +#include "MobAI.hpp" #include @@ -45,7 +46,10 @@ void BaseNPC::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); pkt.NPCAppearanceData = appearanceData; sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); +} +void Mob::enterIntoViewOf(CNSocket *sock) { + this->BaseNPC::enterIntoViewOf(sock); playersInView++; } @@ -96,7 +100,10 @@ void BaseNPC::disappearFromViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); pkt.iNPC_ID = appearanceData.iNPC_ID; sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); +} +void Mob::disappearFromViewOf(CNSocket *sock) { + this->BaseNPC::disappearFromViewOf(sock); playersInView--; } diff --git a/src/Entities.hpp b/src/Entities.hpp index f69fc4a..52cdf09 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -5,7 +5,7 @@ #include #include -enum class EntityType { +enum class EntityType : uint8_t { INVALID, PLAYER, SIMPLE_NPC, @@ -32,6 +32,10 @@ struct Entity { // stubs virtual void enterIntoViewOf(CNSocket *sock) {} virtual void disappearFromViewOf(CNSocket *sock) {} + + // we don't want objects of this base class to exist +protected: + Entity() {} }; struct EntityRef { @@ -76,9 +80,6 @@ struct EntityRef { class BaseNPC : public Entity { public: sNPCAppearanceData appearanceData = {}; - //NPCClass npcClass; - - int playersInView = 0; BaseNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id) { // XXX appearanceData.iX = x; @@ -91,19 +92,8 @@ public: appearanceData.iBarkerType = 0; appearanceData.iNPC_ID = id; - type = EntityType::SIMPLE_NPC; - instanceID = iID; - - chunkPos = std::make_tuple(0, 0, 0); - playersInView = 0; }; - BaseNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, EntityType entityType) : BaseNPC(x, y, z, angle, iID, t, id) { - type = entityType; - } - - // XXX: move to CombatNPC, probably - virtual bool isAlive() override { return appearanceData.iHP > 0; } virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; @@ -121,13 +111,14 @@ struct CombatNPC : public BaseNPC { // XXX CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : BaseNPC(x, y, z, angle, iID, t, id), - maxHealth(maxHP) - {} + maxHealth(maxHP) {} virtual void stepAI() { if (_stepAI != nullptr) _stepAI(); } + + virtual bool isAlive() override { return appearanceData.iHP > 0; } }; // Mob is in MobAI.hpp, Player is in Player.hpp @@ -152,6 +143,11 @@ struct Egg : public BaseNPC { // TODO: decouple from BaseNPC struct Bus : public BaseNPC { + Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : + BaseNPC(x, y, z, angle, iID, t, id) { + type = EntityType::BUS; + } + virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; }; diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 789239f..066d010 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -45,6 +45,9 @@ struct Mob : public CombatNPC { int offsetX = 0, offsetY = 0; int groupMember[4] = {}; + // for optimizing away AI in empty chunks + int playersInView = 0; + // temporary; until we're sure what's what nlohmann::json data = {}; @@ -86,6 +89,9 @@ struct Mob : public CombatNPC { auto operator[](std::string s) { return data[s]; } + + virtual void enterIntoViewOf(CNSocket *sock) override; + virtual void disappearFromViewOf(CNSocket *sock) override; }; namespace MobAI { diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index dce89ff..9f244ea 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -10,7 +10,6 @@ #include "Racing.hpp" #include "Vendor.hpp" #include "Abilities.hpp" -#include "Eggs.hpp" #include #include @@ -62,10 +61,6 @@ void NPCManager::destroyNPC(int32_t id) { if (MobAI::Mobs.find(id) != MobAI::Mobs.end()) MobAI::Mobs.erase(id); - // remove from eggs - if (Eggs::Eggs.find(id) != Eggs::Eggs.end()) - Eggs::Eggs.erase(id); - // finally, remove it from the map and free it NPCs.erase(id); delete entity; diff --git a/src/TableData.cpp b/src/TableData.cpp index fc5477d..047e550 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -139,7 +139,7 @@ static void loadPaths(int* nextId) { if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly passedDistance -= SLIDER_GAP_SIZE; // step down // spawn a slider - BaseNPC* slider = new BaseNPC(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)++, EntityType::BUS); + Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)++); NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider; NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->appearanceData.iX, slider->appearanceData.iY, slider->appearanceData.iZ, INSTANCE_OVERWORLD, 0); Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route; @@ -384,6 +384,7 @@ static void loadEggs(int32_t* nextId) { // Egg instances auto eggs = eggData["Eggs"]; + int eggCount = 0; for (auto _egg = eggs.begin(); _egg != eggs.end(); _egg++) { auto egg = _egg.value(); int id = (*nextId)++; @@ -391,11 +392,11 @@ static void loadEggs(int32_t* nextId) { Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); NPCManager::NPCs[id] = addEgg; - Eggs::Eggs[id] = addEgg; + eggCount++; NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); } - std::cout << "[INFO] Loaded " <appearanceData.iX; egg["iY"] = npc->appearanceData.iY; egg["iZ"] = npc->appearanceData.iZ;