From 224ffe05e74f1a8915fb373f91078ded084f470b Mon Sep 17 00:00:00 2001 From: dongresource Date: Sun, 21 Mar 2021 02:42:45 +0100 Subject: [PATCH] [WIP] Convert most of Chunking to Entity-based system Player and all NPCs now have a common superclass, with virtual functions so smooth over shared behavior. EntityRef is a simple class that points to an arbitrary Entity. This commit is not yet functional. --- Makefile | 2 + src/Chunking.cpp | 358 +++++++++-------------------------------- src/Chunking.hpp | 32 ++-- src/CustomCommands.cpp | 7 +- src/Eggs.cpp | 4 +- src/Eggs.hpp | 14 +- src/Entities.cpp | 120 ++++++++++++++ src/Entities.hpp | 144 +++++++++++++++++ src/NPC.hpp | 35 +--- src/NPCManager.cpp | 9 +- src/Player.hpp | 16 +- src/PlayerManager.cpp | 16 +- src/Transport.cpp | 2 +- 13 files changed, 394 insertions(+), 365 deletions(-) create mode 100644 src/Entities.cpp create mode 100644 src/Entities.hpp diff --git a/Makefile b/Makefile index 6197aec..bb39380 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ CXXSRC=\ src/db/email.cpp\ src/Chat.cpp\ src/CustomCommands.cpp\ + src/Entities.cpp\ src/Email.cpp\ src/Eggs.cpp\ src/main.cpp\ @@ -93,6 +94,7 @@ CXXHDR=\ vendor/JSON.hpp\ src/Chat.hpp\ src/CustomCommands.hpp\ + src/Entities.hpp\ src/Email.hpp\ src/Eggs.hpp\ src/Missions.hpp\ diff --git a/src/Chunking.cpp b/src/Chunking.cpp index cdd52a6..d2d0cdb 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -16,20 +16,13 @@ static void newChunk(ChunkPos pos) { } Chunk *chunk = new Chunk(); - - chunk->players = std::set(); - chunk->NPCs = std::set(); - chunks[pos] = chunk; // add the chunk to the cache of all players and NPCs in the surrounding chunks std::set surroundings = getViewableChunks(pos); - for (Chunk* c : surroundings) { - for (CNSocket* sock : c->players) - PlayerManager::getPlayer(sock)->viewableChunks.insert(chunk); - for (int32_t id : c->NPCs) - NPCManager::NPCs[id]->viewableChunks.insert(chunk); - } + for (Chunk* c : surroundings) + for (const EntityRef& ref : c->entities) + ref.getEntity()->viewableChunks.insert(chunk); } static void deleteChunk(ChunkPos pos) { @@ -43,260 +36,89 @@ static void deleteChunk(ChunkPos pos) { // remove the chunk from the cache of all players and NPCs in the surrounding chunks std::set surroundings = getViewableChunks(pos); for(Chunk* c : surroundings) - { - for (CNSocket* sock : c->players) - PlayerManager::getPlayer(sock)->viewableChunks.erase(chunk); - for (int32_t id : c->NPCs) - NPCManager::NPCs[id]->viewableChunks.erase(chunk); - } + for (const EntityRef& ref : c->entities) + ref.getEntity()->viewableChunks.erase(chunk); chunks.erase(pos); // remove from map delete chunk; // free from memory } -void Chunking::trackPlayer(ChunkPos chunkPos, CNSocket* sock) { +void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { if (!chunkExists(chunkPos)) return; // shouldn't happen - chunks[chunkPos]->players.insert(sock); + chunks[chunkPos]->entities.insert(ref); + + if (ref.type == EntityType::PLAYER) + chunks[chunkPos]->nplayers++; } -void Chunking::trackNPC(ChunkPos chunkPos, int32_t id) { - if (!chunkExists(chunkPos)) - return; // shouldn't happen - - chunks[chunkPos]->NPCs.insert(id); -} - -void Chunking::untrackPlayer(ChunkPos chunkPos, CNSocket* sock) { +void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { if (!chunkExists(chunkPos)) return; // do nothing if chunk doesn't even exist Chunk* chunk = chunks[chunkPos]; - chunk->players.erase(sock); // gone + chunk->entities.erase(ref); // gone - // if chunk is empty, free it - if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) + if (ref.type == EntityType::PLAYER) + chunks[chunkPos]->nplayers--; + assert(chunks[chunkPos]->nplayers >= 0); + + // if chunk is completely empty, free it + if (chunk->entities.size() == 0) deleteChunk(chunkPos); } -void Chunking::untrackNPC(ChunkPos chunkPos, int32_t id) { - if (!chunkExists(chunkPos)) - return; // do nothing if chunk doesn't even exist +void Chunking::addEntityToChunks(std::set chnks, const EntityRef& ref) { + Entity *ent = ref.getEntity(); + bool alive = ent->isAlive(); - Chunk* chunk = chunks[chunkPos]; - - chunk->NPCs.erase(id); // gone - - // if chunk is empty, free it - if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) - deleteChunk(chunkPos); -} - -void Chunking::addPlayerToChunks(std::set chnks, CNSocket* sock) { - INITSTRUCT(sP_FE2CL_PC_NEW, newPlayer); - - for (Chunk* chunk : chnks) { - // add npcs - for (int32_t id : chunk->NPCs) { - BaseNPC* npc = NPCManager::NPCs[id]; - npc->playersInView++; - - if (npc->appearanceData.iHP <= 0) + // TODO: maybe optimize this, potentially using AROUND packets? + for (Chunk *chunk : chnks) { + for (const EntityRef& otherRef : chunk->entities) { + // skip oneself + if (ref == otherRef) continue; - switch (npc->npcClass) { - case NPC_BUS: - INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData); - 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); - Eggs::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; - sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); - break; + Entity *other = otherRef.getEntity(); + + // notify all visible players of the existence of this Entity + if (alive && otherRef.type == EntityType::PLAYER) { + ent->enterIntoViewOf(otherRef.sock); } - } - // add players - for (CNSocket* otherSock : chunk->players) { - if (sock == otherSock) - continue; // that's us :P - - Player* otherPlr = PlayerManager::getPlayer(otherSock); - Player* plr = PlayerManager::getPlayer(sock); - - newPlayer.PCAppearanceData.iID = plr->iID; - newPlayer.PCAppearanceData.iHP = plr->HP; - newPlayer.PCAppearanceData.iLv = plr->level; - newPlayer.PCAppearanceData.iX = plr->x; - newPlayer.PCAppearanceData.iY = plr->y; - newPlayer.PCAppearanceData.iZ = plr->z; - newPlayer.PCAppearanceData.iAngle = plr->angle; - newPlayer.PCAppearanceData.PCStyle = plr->PCStyle; - newPlayer.PCAppearanceData.Nano = plr->Nanos[plr->activeNano]; - newPlayer.PCAppearanceData.iPCState = plr->iPCState; - newPlayer.PCAppearanceData.iSpecialState = plr->iSpecialState; - memcpy(newPlayer.PCAppearanceData.ItemEquip, plr->Equip, sizeof(sItemBase) * AEQUIP_COUNT); - - otherSock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW)); - - newPlayer.PCAppearanceData.iID = otherPlr->iID; - newPlayer.PCAppearanceData.iHP = otherPlr->HP; - newPlayer.PCAppearanceData.iLv = otherPlr->level; - newPlayer.PCAppearanceData.iX = otherPlr->x; - newPlayer.PCAppearanceData.iY = otherPlr->y; - newPlayer.PCAppearanceData.iZ = otherPlr->z; - newPlayer.PCAppearanceData.iAngle = otherPlr->angle; - newPlayer.PCAppearanceData.PCStyle = otherPlr->PCStyle; - newPlayer.PCAppearanceData.Nano = otherPlr->Nanos[otherPlr->activeNano]; - newPlayer.PCAppearanceData.iPCState = otherPlr->iPCState; - newPlayer.PCAppearanceData.iSpecialState = otherPlr->iSpecialState; - memcpy(newPlayer.PCAppearanceData.ItemEquip, otherPlr->Equip, sizeof(sItemBase) * AEQUIP_COUNT); - - sock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW)); + // notify this *player* of the existence of all visible Entities + if (ref.type == EntityType::PLAYER && other->isAlive()) { + other->enterIntoViewOf(ref.sock); + } } } } -void Chunking::addNPCToChunks(std::set chnks, int32_t id) { - BaseNPC* npc = NPCManager::NPCs[id]; +void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef& ref) { + Entity *ent = ref.getEntity(); + bool alive = ent->isAlive(); - switch (npc->npcClass) { - case NPC_BUS: - INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData); - enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ }; + // TODO: same as above + for (Chunk *chunk : chnks) { + for (const EntityRef& otherRef : chunk->entities) { + // skip oneself + if (ref == otherRef) + continue; - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER)); - npc->playersInView++; + Entity *other = otherRef.getEntity(); + + // notify all visible players of the departure of this Entity + if (alive && otherRef.type == EntityType::PLAYER) { + ent->disappearFromViewOf(otherRef.sock); + } + + // notify this *player* of the departure of all visible Entities + if (ref.type == EntityType::PLAYER && other->isAlive()) { + other->disappearFromViewOf(ref.sock); } } - break; - case NPC_EGG: - INITSTRUCT(sP_FE2CL_SHINY_ENTER, enterEggData); - Eggs::npcDataToEggData(&npc->appearanceData, &enterEggData.ShinyAppearanceData); - - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&enterEggData, P_FE2CL_SHINY_ENTER, sizeof(sP_FE2CL_SHINY_ENTER)); - npc->playersInView++; - } - } - break; - default: - // create struct - INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); - enterData.NPCAppearanceData = npc->appearanceData; - - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); - npc->playersInView++; - } - } - break; - } -} - -void Chunking::removePlayerFromChunks(std::set chnks, CNSocket* sock) { - INITSTRUCT(sP_FE2CL_PC_EXIT, exitPlayer); - - // for chunks that need the player to be removed from - for (Chunk* chunk : chnks) { - - // remove NPCs from view - for (int32_t id : chunk->NPCs) { - BaseNPC* npc = NPCManager::NPCs[id]; - npc->playersInView--; - - switch (npc->npcClass) { - case NPC_BUS: - INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData); - exitBusData.eTT = 3; - 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; - sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); - break; - } - } - - // remove players from eachother's views - for (CNSocket* otherSock : chunk->players) { - if (sock == otherSock) - continue; // that's us :P - exitPlayer.iID = PlayerManager::getPlayer(sock)->iID; - otherSock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT)); - exitPlayer.iID = PlayerManager::getPlayer(otherSock)->iID; - sock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT)); - } - } - -} - -void Chunking::removeNPCFromChunks(std::set chnks, int32_t id) { - BaseNPC* npc = NPCManager::NPCs[id]; - - switch (npc->npcClass) { - case NPC_BUS: - INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData); - exitBusData.eTT = 3; - exitBusData.iT_ID = id; - - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT)); - npc->playersInView--; - } - } - break; - case NPC_EGG: - INITSTRUCT(sP_FE2CL_SHINY_EXIT, exitEggData); - exitEggData.iShinyID = id; - - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&exitEggData, P_FE2CL_SHINY_EXIT, sizeof(sP_FE2CL_SHINY_EXIT)); - npc->playersInView--; - } - } - break; - default: - // create struct - INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); - exitData.iNPC_ID = id; - - // remove it from the clients - for (Chunk* chunk : chnks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); - npc->playersInView--; - } - } - break; } } @@ -308,29 +130,32 @@ static void emptyChunk(ChunkPos chunkPos) { Chunk* chunk = chunks[chunkPos]; - if (chunk->players.size() > 0) { + if (chunk->nplayers > 0) { std::cout << "[WARN] Tried to empty chunk that still had players\n"; return; // chunk doesn't exist, we don't need to do anything } // unspawn all of the mobs/npcs - std::set npcIDs(chunk->NPCs); - for (uint32_t id : npcIDs) { + std::set refs(chunk->entities); + for (const EntityRef& ref : refs) { + if (ref.type == EntityType::PLAYER) + assert(0); + // every call of this will check if the chunk is empty and delete it if so - NPCManager::destroyNPC(id); + NPCManager::destroyNPC(ref.id); } } -void Chunking::updatePlayerChunk(CNSocket* sock, ChunkPos from, ChunkPos to) { - Player* plr = PlayerManager::getPlayer(sock); +void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) { + Entity* ent = ref.getEntity(); // if the new chunk doesn't exist, make it first if (!chunkExists(to)) newChunk(to); // move to other chunk's player set - untrackPlayer(from, sock); // this will delete the chunk if it's empty - trackPlayer(to, sock); + untrackEntity(from, ref); // this will delete the chunk if it's empty + trackEntity(to, ref); // calculate viewable chunks from both points std::set oldViewables = getViewableChunks(from); @@ -348,49 +173,13 @@ void Chunking::updatePlayerChunk(CNSocket* sock, ChunkPos from, ChunkPos to) { std::inserter(toEnter, toEnter.end())); // chunks we must be ENTERed into (new - old) // update views - removePlayerFromChunks(toExit, sock); - addPlayerToChunks(toEnter, sock); + removeEntityFromChunks(toExit, ref); + addEntityToChunks(toEnter, ref); - plr->chunkPos = to; // update cached chunk position + ent->chunkPos = to; // update cached chunk position // updated cached viewable chunks - plr->viewableChunks.clear(); - plr->viewableChunks.insert(newViewables.begin(), newViewables.end()); -} - -void Chunking::updateNPCChunk(int32_t id, ChunkPos from, ChunkPos to) { - BaseNPC* npc = NPCManager::NPCs[id]; - - // if the new chunk doesn't exist, make it first - if (!chunkExists(to)) - newChunk(to); - - // move to other chunk's player set - untrackNPC(from, id); // this will delete the chunk if it's empty - trackNPC(to, id); - - // calculate viewable chunks from both points - std::set oldViewables = getViewableChunks(from); - std::set newViewables = getViewableChunks(to); - std::set toExit, toEnter; - - /* - * Calculate diffs. This is done to prevent phasing on chunk borders. - * toExit will contain old viewables - new viewables, so the player will only be exited in chunks that are out of sight. - * toEnter contains the opposite: new viewables - old viewables, chunks where we previously weren't visible from before. - */ - std::set_difference(oldViewables.begin(), oldViewables.end(), newViewables.begin(), newViewables.end(), - std::inserter(toExit, toExit.end())); // chunks we must be EXITed from (old - new) - std::set_difference(newViewables.begin(), newViewables.end(), oldViewables.begin(), oldViewables.end(), - std::inserter(toEnter, toEnter.end())); // chunks we must be ENTERed into (new - old) - - // update views - removeNPCFromChunks(toExit, id); - addNPCToChunks(toEnter, id); - - npc->chunkPos = to; // update cached chunk position - // updated cached viewable chunks - npc->viewableChunks.clear(); - npc->viewableChunks.insert(newViewables.begin(), newViewables.end()); + ent->viewableChunks.clear(); + ent->viewableChunks.insert(newViewables.begin(), newViewables.end()); } bool Chunking::chunkExists(ChunkPos chunk) { @@ -441,9 +230,8 @@ static std::vector getChunksInMap(uint64_t mapNum) { * Used only for eggs; use npc->playersInView for everything visible */ bool Chunking::inPopulatedChunks(std::set* chnks) { - for (auto it = chnks->begin(); it != chnks->end(); it++) { - if (!(*it)->players.empty()) + if ((*it)->nplayers > 0) return true; } @@ -451,7 +239,6 @@ bool Chunking::inPopulatedChunks(std::set* chnks) { } void Chunking::createInstance(uint64_t instanceID) { - std::vector templateChunks = getChunksInMap(MAPNUM(instanceID)); // base instance chunks if (getChunksInMap(instanceID).size() == 0) { // only instantiate if the instance doesn't exist already std::cout << "Creating instance " << instanceID << std::endl; @@ -510,7 +297,6 @@ void Chunking::createInstance(uint64_t instanceID) { } static void destroyInstance(uint64_t instanceID) { - std::vector instanceChunks = getChunksInMap(instanceID); std::cout << "Deleting instance " << instanceID << " (" << instanceChunks.size() << " chunks)" << std::endl; for (ChunkPos& coords : instanceChunks) { @@ -527,7 +313,7 @@ void Chunking::destroyInstanceIfEmpty(uint64_t instanceID) { for (ChunkPos& coords : sourceChunkCoords) { Chunk* chunk = chunks[coords]; - if (chunk->players.size() > 0) + if (chunk->nplayers > 0) return; // there are still players inside } diff --git a/src/Chunking.hpp b/src/Chunking.hpp index 248181a..b63c536 100644 --- a/src/Chunking.hpp +++ b/src/Chunking.hpp @@ -1,6 +1,7 @@ #pragma once #include "core/Core.hpp" +#include "Entities.hpp" #include #include @@ -12,6 +13,8 @@ class Chunk { public: std::set players; std::set NPCs; + std::set entities; + int nplayers = 0; }; enum { @@ -23,18 +26,13 @@ enum { namespace Chunking { extern std::map chunks; - void updatePlayerChunk(CNSocket* sock, ChunkPos from, ChunkPos to); - void updateNPCChunk(int32_t id, ChunkPos from, ChunkPos to); + void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to); - void trackPlayer(ChunkPos chunkPos, CNSocket* sock); - void trackNPC(ChunkPos chunkPos, int32_t id); - void untrackPlayer(ChunkPos chunkPos, CNSocket* sock); - void untrackNPC(ChunkPos chunkPos, int32_t id); + void trackEntity(ChunkPos chunkPos, const EntityRef& ref); + void untrackEntity(ChunkPos chunkPos, const EntityRef& ref); - void addPlayerToChunks(std::set chnks, CNSocket* sock); - void addNPCToChunks(std::set chnks, int32_t id); - void removePlayerFromChunks(std::set chnks, CNSocket* sock); - void removeNPCFromChunks(std::set chnks, int32_t id); + void addEntityToChunks(std::set chnks, const EntityRef& ref); + void removeEntityFromChunks(std::set chnks, const EntityRef& ref); bool chunkExists(ChunkPos chunk); ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID); @@ -43,4 +41,18 @@ namespace Chunking { bool inPopulatedChunks(std::set* chnks); void createInstance(uint64_t); void destroyInstanceIfEmpty(uint64_t); + + // death row below this point + //void updatePlayerChunk(CNSocket* sock, ChunkPos from, ChunkPos to); + //void updateNPCChunk(int32_t id, ChunkPos from, ChunkPos to); + + //void trackPlayer(ChunkPos chunkPos, CNSocket* sock); + //void trackNPC(ChunkPos chunkPos, int32_t id); + //void untrackPlayer(ChunkPos chunkPos, CNSocket* sock); + //void untrackNPC(ChunkPos chunkPos, int32_t id); + + //void addPlayerToChunks(std::set chnks, CNSocket* sock); + //void addNPCToChunks(std::set chnks, int32_t id); + //void removePlayerFromChunks(std::set chnks, CNSocket* sock); + //void removeNPCFromChunks(std::set chnks, int32_t id); } diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 2654f02..825b968 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -361,11 +361,12 @@ static void npcRotateCommand(std::string full, std::vector& args, C } static void refreshCommand(std::string full, std::vector& args, CNSocket* sock) { - Player* plr = PlayerManager::getPlayer(sock); + EntityRef ref = {sock}; + Entity* plr = ref.getEntity(); ChunkPos currentChunk = plr->chunkPos; ChunkPos nullChunk = std::make_tuple(0, 0, 0); - Chunking::updatePlayerChunk(sock, currentChunk, nullChunk); - Chunking::updatePlayerChunk(sock, nullChunk, currentChunk); + Chunking::updateEntityChunk(ref, currentChunk, nullChunk); + Chunking::updateEntityChunk(ref, nullChunk, currentChunk); } static void instanceCommand(std::string full, std::vector& args, CNSocket* sock) { diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 47805f4..5b8d58c 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -133,7 +133,7 @@ static void eggStep(CNServer* serv, time_t currTime) { egg.second->deadUntil = 0; egg.second->appearanceData.iHP = 400; - Chunking::addNPCToChunks(Chunking::getViewableChunks(egg.second->chunkPos), egg.first); + Chunking::addEntityToChunks(Chunking::getViewableChunks(egg.second->chunkPos), {egg.first}); } } @@ -246,7 +246,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { if (egg->summoned) NPCManager::destroyNPC(eggId); else { - Chunking::removeNPCFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggId); + Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), {eggId}); 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 9cf58c9..9092133 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -1,19 +1,7 @@ #pragma once #include "core/Core.hpp" -#include "NPC.hpp" - -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; - } -}; +#include "Entities.hpp" struct EggType { int dropCrateId; diff --git a/src/Entities.cpp b/src/Entities.cpp new file mode 100644 index 0000000..65bf024 --- /dev/null +++ b/src/Entities.cpp @@ -0,0 +1,120 @@ +#include "core/Core.hpp" +#include "Entities.hpp" +#include "Chunking.hpp" +#include "PlayerManager.hpp" +#include "NPCManager.hpp" +#include "Eggs.hpp" + +#include + +static_assert(std::is_standard_layout::value); +static_assert(std::is_trivially_copyable::value); + +EntityRef::EntityRef(CNSocket *s) { + type = EntityType::PLAYER; + sock = s; +} + +EntityRef::EntityRef(int32_t i) { + id = i; + + assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); + type = NPCManager::NPCs[id]->type; +} + +bool EntityRef::isValid() const { + if (type == EntityType::PLAYER) + return PlayerManager::players.find(sock) != PlayerManager::players.end(); + + return NPCManager::NPCs.find(id) != NPCManager::NPCs.end(); +} + +Entity *EntityRef::getEntity() const { + assert(isValid()); + + if (type == EntityType::PLAYER) + return PlayerManager::getPlayer(sock); + + return NPCManager::NPCs[id]; +} + +/* + * Entity coming into view. + */ +void BaseNPC::enterIntoViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); + pkt.NPCAppearanceData = appearanceData; + sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); + + playersInView++; +} + +void Bus::enterIntoViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt); + + // TODO: Potentially decouple this from BaseNPC? + pkt.AppearanceData = { + 3, appearanceData.iNPC_ID, appearanceData.iNPCType, + appearanceData.iX, appearanceData.iY, appearanceData.iZ + }; + + sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER); +} + +void Egg::enterIntoViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); + + Eggs::npcDataToEggData(&appearanceData, &pkt.ShinyAppearanceData); + + sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); +} + +// TODO: this is less effiecient than it was, because of memset() +void Player::enterIntoViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_PC_NEW, pkt); + + pkt.PCAppearanceData.iID = iID; + pkt.PCAppearanceData.iHP = HP; + pkt.PCAppearanceData.iLv = level; + pkt.PCAppearanceData.iX = x; + pkt.PCAppearanceData.iY = y; + pkt.PCAppearanceData.iZ = z; + pkt.PCAppearanceData.iAngle = angle; + pkt.PCAppearanceData.PCStyle = PCStyle; + pkt.PCAppearanceData.Nano = Nanos[activeNano]; + pkt.PCAppearanceData.iPCState = iPCState; + pkt.PCAppearanceData.iSpecialState = iSpecialState; + memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT); + + sock->sendPacket(pkt, P_FE2CL_PC_NEW); +} + +/* + * Entity leaving view. + */ +void BaseNPC::disappearFromViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); + pkt.iNPC_ID = appearanceData.iNPC_ID; + sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); + + playersInView--; +} + +void Bus::disappearFromViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt); + pkt.eTT = 3; + pkt.iT_ID = appearanceData.iNPC_ID; + sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT); +} + +void Egg::disappearFromViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt); + pkt.iShinyID = appearanceData.iNPC_ID; + sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT); +} + +void Player::disappearFromViewOf(CNSocket *sock) { + INITSTRUCT(sP_FE2CL_PC_EXIT, pkt); + pkt.iID = iID; + sock->sendPacket(pkt, P_FE2CL_PC_EXIT); +} diff --git a/src/Entities.hpp b/src/Entities.hpp new file mode 100644 index 0000000..0f0c465 --- /dev/null +++ b/src/Entities.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "core/Core.hpp" + +#include +#include + +enum class EntityType { + PLAYER, + SIMPLE_NPC, + COMBAT_NPC, + EGG, + BUS +}; + +class Chunk; + +struct Entity { + EntityType type; + int x, y, z; + uint64_t instanceID; + ChunkPos chunkPos; + std::set viewableChunks; + + // destructor must be virtual, apparently + virtual ~Entity() {} + + virtual bool isAlive() { return true; } + + // stubs + virtual void enterIntoViewOf(CNSocket *sock) {} + virtual void disappearFromViewOf(CNSocket *sock) {} +}; + +struct EntityRef { + EntityType type; + union { + CNSocket *sock; + int32_t id; + }; + + EntityRef(CNSocket *s); + EntityRef(int32_t i); + + bool isValid() const; + Entity *getEntity() const; + + bool operator==(const EntityRef& other) const { + if (type != other.type) + return false; + + if (type == EntityType::PLAYER) + return sock == other.sock; + + return id == other.id; + } + + // arbitrary ordering + bool operator<(const EntityRef& other) const { + if (type == other.type) { + if (type == EntityType::PLAYER) + return sock < other.sock; + else + return id < other.id; + } + + return type < other.type; + } +}; + +class BaseNPC : public Entity { +public: + sNPCAppearanceData appearanceData; + NPCClass npcClass; + + int playersInView; + + BaseNPC() {}; + BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id) { // XXX + appearanceData.iX = x; + appearanceData.iY = y; + appearanceData.iZ = z; + appearanceData.iNPCType = type; + appearanceData.iHP = 400; + appearanceData.iAngle = angle; + appearanceData.iConditionBitFlag = 0; + appearanceData.iBarkerType = 0; + appearanceData.iNPC_ID = id; + + npcClass = NPCClass::NPC_BASE; + + instanceID = iID; + + chunkPos = std::make_tuple(0, 0, 0); + playersInView = 0; + }; + BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id, NPCClass classType) : BaseNPC(x, y, z, angle, iID, type, id) { + npcClass = classType; + } + + // 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; +}; + +// TODO: decouple from BaseNPC +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; + } + + virtual bool isAlive() override { return !dead; } + + virtual void enterIntoViewOf(CNSocket *sock) override; + virtual void disappearFromViewOf(CNSocket *sock) override; +}; + +// TODO: decouple from BaseNPC +struct Bus : public BaseNPC { + virtual void enterIntoViewOf(CNSocket *sock) override; + virtual void disappearFromViewOf(CNSocket *sock) override; +}; + +#if 0 +struct NPC : public Entity { + void (*_stepAI)(); + + virtual void stepAI() {} +}; + +struct CombatNPC : public Entity { +}; + +struct Mob : public CombatNPC { +}; +#endif diff --git a/src/NPC.hpp b/src/NPC.hpp index 6d117b1..b5d57dd 100644 --- a/src/NPC.hpp +++ b/src/NPC.hpp @@ -1,37 +1,4 @@ #pragma once #include "Chunking.hpp" - -class BaseNPC { -public: - sNPCAppearanceData appearanceData; - NPCClass npcClass; - uint64_t instanceID; - ChunkPos chunkPos; - std::set viewableChunks; - - int playersInView; - - BaseNPC() {}; - BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id) { - appearanceData.iX = x; - appearanceData.iY = y; - appearanceData.iZ = z; - appearanceData.iNPCType = type; - appearanceData.iHP = 400; - appearanceData.iAngle = angle; - appearanceData.iConditionBitFlag = 0; - appearanceData.iBarkerType = 0; - appearanceData.iNPC_ID = id; - - npcClass = NPCClass::NPC_BASE; - - instanceID = iID; - - chunkPos = std::make_tuple(0, 0, 0); - playersInView = 0; - }; - BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id, NPCClass classType) : BaseNPC(x, y, z, angle, iID, type, id) { - npcClass = classType; - } -}; +#include "Entities.hpp" diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 873e40c..aa07692 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -52,10 +52,11 @@ void NPCManager::destroyNPC(int32_t id) { } // remove NPC from the chunk - Chunking::untrackNPC(entity->chunkPos, id); + EntityRef ref = {id}; + Chunking::untrackEntity(entity->chunkPos, ref); // remove from viewable chunks - Chunking::removeNPCFromChunks(Chunking::getViewableChunks(entity->chunkPos), id); + Chunking::removeEntityFromChunks(Chunking::getViewableChunks(entity->chunkPos), ref); // remove from mob manager if (MobAI::Mobs.find(id) != MobAI::Mobs.end()) @@ -81,7 +82,7 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, npc->instanceID = I; if (oldChunk == newChunk) return; // didn't change chunks - Chunking::updateNPCChunk(id, oldChunk, newChunk); + Chunking::updateEntityChunk({id}, oldChunk, newChunk); } void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) { @@ -251,7 +252,7 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { Missions::failInstancedMissions(sock); // fail any instanced missions sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC); - Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks + Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks PlayerManager::updatePlayerPosition(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD, plr->angle); // remove the player's ongoing race, if any diff --git a/src/Player.hpp b/src/Player.hpp index a8f1693..368d546 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -5,12 +5,13 @@ #include "core/Core.hpp" #include "Chunking.hpp" +#include "Entities.hpp" #define ACTIVE_MISSION_COUNT 6 #define PC_MAXHEALTH(level) (925 + 75 * (level)) -struct Player { +struct Player : public Entity { int accountId; int accountLevel; // permission level (see CN_ACCOUNT_LEVEL enums) int64_t SerialKey; @@ -37,10 +38,11 @@ struct Player { int32_t iSelfConditionBitFlag; int8_t iSpecialState; - int x, y, z, angle; + //int x, y, z; in superclass + int angle; int lastX, lastY, lastZ, lastAngle; int recallX, recallY, recallZ, recallInstance; // also Lair entrances - uint64_t instanceID; + //uint64_t instanceID; in superclass sItemBase Equip[AEQUIP_COUNT]; sItemBase Inven[AINVEN_COUNT]; sItemBase Bank[ABANK_COUNT]; @@ -82,11 +84,15 @@ struct Player { uint64_t iFirstUseFlag[2]; - ChunkPos chunkPos; - std::set viewableChunks; + // in superclass: + //ChunkPos chunkPos; + //std::set viewableChunks; time_t lastHeartbeat; int suspicionRating; time_t lastShot; std::vector buyback; + + virtual void enterIntoViewOf(CNSocket *sock) override; + virtual void disappearFromViewOf(CNSocket *sock) override; }; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index f7e1915..c3b1d79 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -31,10 +31,11 @@ std::map PlayerManager::players; static void addPlayer(CNSocket* key, Player plr) { Player *p = new Player(); - memcpy(p, &plr, sizeof(Player)); + // copy object into heap memory + *p = plr; players[key] = p; - p->chunkPos = std::make_tuple(0, 0, 0); + p->chunkPos = std::make_tuple(0, 0, 0); // TODO: maybe replace with specialized "no chunk" value p->lastHeartbeat = 0; std::cout << getPlayerName(p) << " has joined!" << std::endl; @@ -56,9 +57,10 @@ void PlayerManager::removePlayer(CNSocket* key) { // save player to DB Database::updatePlayer(plr); + EntityRef ref = {key}; // remove player visually and untrack - Chunking::removePlayerFromChunks(Chunking::getViewableChunks(plr->chunkPos), key); - Chunking::untrackPlayer(plr->chunkPos, key); + Chunking::removeEntityFromChunks(Chunking::getViewableChunks(plr->chunkPos), ref); + Chunking::untrackEntity(plr->chunkPos, ref); std::cout << getPlayerName(plr) << " has left!" << std::endl; @@ -92,7 +94,7 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui plr->instanceID = I; if (oldChunk == newChunk) return; // didn't change chunks - Chunking::updatePlayerChunk(sock, oldChunk, newChunk); + Chunking::updateEntityChunk({sock}, oldChunk, newChunk); } void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) { @@ -146,7 +148,7 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I pkt2.iZ = Z; sock->sendPacket(pkt2, P_FE2CL_REP_PC_GOTO_SUCC); - Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks + Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks updatePlayerPosition(sock, X, Y, Z, I, plr->angle); // post-warp: check if the source instance has no more players in it and delete it if so @@ -472,7 +474,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { if (!move) return; - Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks + Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks updatePlayerPosition(sock, x, y, z, plr->instanceID, plr->angle); } diff --git a/src/Transport.cpp b/src/Transport.cpp index d30841d..5d32eaf 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -165,7 +165,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) { if (target == nullptr) return; // we warped; update position and chunks - Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks + Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks PlayerManager::updatePlayerPosition(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD, plr->angle); }