diff --git a/Makefile b/Makefile index f94e9af..b6541dc 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,7 @@ CXXHDR=\ src/settings.hpp\ src/Transport.hpp\ src/TableData.hpp\ + src/Bucket.hpp\ src/Chunking.hpp\ src/Buddies.hpp\ src/Groups.hpp\ diff --git a/src/Bucket.hpp b/src/Bucket.hpp new file mode 100644 index 0000000..fc3f52b --- /dev/null +++ b/src/Bucket.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +template +class Bucket { + std::array buf; + size_t sz; +public: + Bucket() { + sz = 0; + } + + void add(const T& item) { + if (sz < N) { + buf[sz++] = item; + } + } + + std::optional get(size_t idx) const { + if (idx < sz) { + return buf[idx]; + } + return std::nullopt; + } + + size_t size() const { + return sz; + } + + bool isFull() const { + return sz == N; + } + + void clear() { + sz = 0; + } +}; diff --git a/src/Chunking.cpp b/src/Chunking.cpp index 65231fe..fec1cdc 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -1,7 +1,9 @@ #include "Chunking.hpp" +#include "Player.hpp" #include "MobAI.hpp" #include "NPCManager.hpp" +#include "Bucket.hpp" #include @@ -11,6 +13,11 @@ using namespace Chunking; * The initial chunkPos value before a player is placed into the world. */ const ChunkPos Chunking::INVALID_CHUNK = {}; +constexpr size_t MAX_PC_PER_AROUND = (CN_PACKET_BUFFER_SIZE - sizeof(int32_t)) / sizeof(sPCAppearanceData); +constexpr size_t MAX_NPC_PER_AROUND = (CN_PACKET_BUFFER_SIZE - sizeof(int32_t)) / sizeof(sNPCAppearanceData); +constexpr size_t MAX_SHINY_PER_AROUND = (CN_PACKET_BUFFER_SIZE - sizeof(int32_t)) / sizeof(sShinyAppearanceData); +constexpr size_t MAX_TRANSPORTATION_PER_AROUND = (CN_PACKET_BUFFER_SIZE - sizeof(int32_t)) / sizeof(sTransportationAppearanceData); +constexpr size_t MAX_IDS_PER_AROUND_DEL = (CN_PACKET_BUFFER_SIZE - sizeof(int32_t)) / sizeof(int32_t); std::map Chunking::chunks; @@ -75,11 +82,82 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { deleteChunk(chunkPos); } +template +static void sendAroundPacket(const EntityRef recipient, std::vector>& buckets, uint32_t packetId) { + assert(recipient.kind == EntityKind::PLAYER); + + uint8_t pktBuf[CN_PACKET_BUFFER_SIZE]; + for (const auto& bucket : buckets) { + memset(pktBuf, 0, CN_PACKET_BUFFER_SIZE); + int count = bucket.size(); + *((int32_t*)pktBuf) = count; + T* data = (T*)(pktBuf + sizeof(int32_t)); + for (size_t i = 0; i < count; i++) { + data[i] = bucket.get(i).value(); + } + recipient.sock->sendPacket(pktBuf, packetId, sizeof(int32_t) + (count * sizeof(T))); + } +} + +template +static void sendAroundDelPacket(const EntityRef recipient, std::vector>& buckets, bool isTransportation, uint32_t packetId) { + assert(recipient.kind == EntityKind::PLAYER); + + uint8_t pktBuf[CN_PACKET_BUFFER_SIZE]; + for (const auto& bucket : buckets) { + memset(pktBuf, 0, CN_PACKET_BUFFER_SIZE); + int count = bucket.size(); + assert(count <= N); + + size_t baseSize; + if (isTransportation) { + sP_FE2CL_AROUND_DEL_TRANSPORTATION* pkt = (sP_FE2CL_AROUND_DEL_TRANSPORTATION*)pktBuf; + pkt->eTT = 3; + pkt->iCnt = count; + baseSize = sizeof(sP_FE2CL_AROUND_DEL_TRANSPORTATION); + } else { + *((int32_t*)pktBuf) = count; + baseSize = sizeof(int32_t); + } + int32_t* ids = (int32_t*)(pktBuf + baseSize); + + for (size_t i = 0; i < count; i++) { + ids[i] = bucket.get(i).value(); + } + recipient.sock->sendPacket(pktBuf, packetId, baseSize + (count * sizeof(int32_t))); + } +} + +template +static void bufferAppearanceData(std::vector>& buckets, const T& data) { + if (buckets.empty()) + buckets.push_back(Bucket()); + Bucket& bucket = buckets[buckets.size() - 1]; + assert(!bucket.isFull()); + bucket.add(data); + if (bucket.isFull()) + buckets.push_back(Bucket()); +} + +template +static void bufferIdForDisappearance(std::vector>& buckets, int32_t id) { + if (buckets.empty()) + buckets.push_back(Bucket()); + Bucket& bucket = buckets[buckets.size() - 1]; + assert(!bucket.isFull()); + bucket.add(id); + if (bucket.isFull()) + buckets.push_back(Bucket()); +} + void Chunking::addEntityToChunks(std::set chnks, const EntityRef ref) { Entity *ent = ref.getEntity(); bool alive = ent->isExtant(); - // TODO: maybe optimize this, potentially using AROUND packets? + std::vector> pcAppearances; + std::vector> npcAppearances; + std::vector> shinyAppearances; + std::vector> transportationAppearances; for (Chunk *chunk : chnks) { for (const EntityRef otherRef : chunk->entities) { // skip oneself @@ -95,7 +173,39 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef ref) { // notify this *player* of the existence of all visible Entities if (ref.kind == EntityKind::PLAYER && other->isExtant()) { - other->enterIntoViewOf(ref.sock); + sPCAppearanceData pcData; + sNPCAppearanceData npcData; + sShinyAppearanceData eggData; + sTransportationAppearanceData busData; + switch(otherRef.kind) + { + case EntityKind::PLAYER: + pcData = dynamic_cast(other)->getAppearanceData(); + bufferAppearanceData(pcAppearances, pcData); + break; + case EntityKind::SIMPLE_NPC: + npcData = dynamic_cast(other)->getAppearanceData(); + bufferAppearanceData(npcAppearances, npcData); + break; + case EntityKind::COMBAT_NPC: + npcData = dynamic_cast(other)->getAppearanceData(); + bufferAppearanceData(npcAppearances, npcData); + break; + case EntityKind::MOB: + npcData = dynamic_cast(other)->getAppearanceData(); + bufferAppearanceData(npcAppearances, npcData); + break; + case EntityKind::EGG: + eggData = dynamic_cast(other)->getShinyAppearanceData(); + bufferAppearanceData(shinyAppearances, eggData); + break; + case EntityKind::BUS: + busData = dynamic_cast(other)->getTransportationAppearanceData(); + bufferAppearanceData(transportationAppearances, busData); + break; + default: + break; + } } // for mobs, increment playersInView @@ -105,13 +215,28 @@ void Chunking::addEntityToChunks(std::set chnks, const EntityRef ref) { ((Mob*)other)->playersInView++; } } + + if (ref.kind != EntityKind::PLAYER) + return; // nothing to send + + if (!pcAppearances.empty()) + sendAroundPacket(ref, pcAppearances, P_FE2CL_PC_AROUND); + if (!npcAppearances.empty()) + sendAroundPacket(ref, npcAppearances, P_FE2CL_NPC_AROUND); + if (!shinyAppearances.empty()) + sendAroundPacket(ref, shinyAppearances, P_FE2CL_SHINY_AROUND); + if (!transportationAppearances.empty()) + sendAroundPacket(ref, transportationAppearances, P_FE2CL_TRANSPORTATION_AROUND); } void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef ref) { Entity *ent = ref.getEntity(); bool alive = ent->isExtant(); - // TODO: same as above + std::vector> pcDisappearances; + std::vector> npcDisappearances; + std::vector> shinyDisappearances; + std::vector> transportationDisappearances; for (Chunk *chunk : chnks) { for (const EntityRef otherRef : chunk->entities) { // skip oneself @@ -127,7 +252,30 @@ void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef re // notify this *player* of the departure of all visible Entities if (ref.kind == EntityKind::PLAYER && other->isExtant()) { - other->disappearFromViewOf(ref.sock); + int32_t id; + switch(otherRef.kind) + { + case EntityKind::PLAYER: + id = dynamic_cast(other)->iID; + bufferIdForDisappearance(pcDisappearances, id); + break; + case EntityKind::SIMPLE_NPC: + case EntityKind::COMBAT_NPC: + case EntityKind::MOB: + id = dynamic_cast(other)->id; + bufferIdForDisappearance(npcDisappearances, id); + break; + case EntityKind::EGG: + id = dynamic_cast(other)->id; + bufferIdForDisappearance(shinyDisappearances, id); + break; + case EntityKind::BUS: + id = dynamic_cast(other)->id; + bufferIdForDisappearance(transportationDisappearances, id); + break; + default: + break; + } } // for mobs, decrement playersInView @@ -137,6 +285,18 @@ void Chunking::removeEntityFromChunks(std::set chnks, const EntityRef re ((Mob*)other)->playersInView--; } } + + if (ref.kind != EntityKind::PLAYER) + return; // nothing to send + + if (!pcDisappearances.empty()) + sendAroundDelPacket(ref, pcDisappearances, false, P_FE2CL_AROUND_DEL_PC); + if (!npcDisappearances.empty()) + sendAroundDelPacket(ref, npcDisappearances, false, P_FE2CL_AROUND_DEL_NPC); + if (!shinyDisappearances.empty()) + sendAroundDelPacket(ref, shinyDisappearances, false, P_FE2CL_AROUND_DEL_SHINY); + if (!transportationDisappearances.empty()) + sendAroundDelPacket(ref, transportationDisappearances, true, P_FE2CL_AROUND_DEL_TRANSPORTATION); } static void emptyChunk(ChunkPos chunkPos) { diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 73f22f6..8cb0912 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -126,15 +126,6 @@ static void eggStep(CNServer* serv, time_t currTime) { } -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); diff --git a/src/Eggs.hpp b/src/Eggs.hpp index 9c34ec9..8bb43a8 100644 --- a/src/Eggs.hpp +++ b/src/Eggs.hpp @@ -15,5 +15,4 @@ namespace Eggs { void init(); void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); - void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg); } diff --git a/src/Entities.cpp b/src/Entities.cpp index 63a4a64..c3532de 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -70,11 +70,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt); // TODO: Potentially decouple this from BaseNPC? - pkt.AppearanceData = { - 3, id, type, - x, y, z - }; - + pkt.AppearanceData = getTransportationAppearanceData(); sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER); } @@ -82,12 +78,22 @@ void Egg::enterIntoViewOf(CNSocket *sock) { INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); // TODO: Potentially decouple this from BaseNPC? - pkt.ShinyAppearanceData = { + pkt.ShinyAppearanceData = getShinyAppearanceData(); + sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); +} + +sTransportationAppearanceData Bus::getTransportationAppearanceData() { + return sTransportationAppearanceData { + 3, id, type, + x, y, z + }; +} + +sShinyAppearanceData Egg::getShinyAppearanceData() { + return sShinyAppearanceData { id, type, 0, // client doesn't care about map num x, y, z }; - - sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); } sNano* Player::getActiveNano() { diff --git a/src/Entities.hpp b/src/Entities.hpp index 22eb50a..5d55408 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -161,6 +161,8 @@ struct Egg : public BaseNPC { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + + sShinyAppearanceData getShinyAppearanceData(); }; struct Bus : public BaseNPC { @@ -172,4 +174,6 @@ struct Bus : public BaseNPC { virtual void enterIntoViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override; + + sTransportationAppearanceData getTransportationAppearanceData(); }; diff --git a/src/core/Defines.hpp b/src/core/Defines.hpp index 7939d14..cb14b29 100644 --- a/src/core/Defines.hpp +++ b/src/core/Defines.hpp @@ -1,6 +1,8 @@ /* enum definitions from the client */ #pragma once +#include "core/CNStructs.hpp" + // floats const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f; const float CN_EP_RANK_1 = 0.8f; @@ -410,7 +412,13 @@ enum { SEND_ANYCAST_NEW = 3, SEND_BROADCAST = 4, +#if PROTOCOL_VERSION == 728 + CN_PACKET_BUFFER_SIZE = 8192, +#elif PROTOCOL_VERSION == 1013 + CN_PACKET_BUFFER_SIZE = 8192, +#else CN_PACKET_BUFFER_SIZE = 4096, +#endif P_CL2LS_REQ_LOGIN = 0x12000001, // 301989889 P_CL2LS_REQ_CHECK_CHAR_NAME = 0x12000002, // 301989890 diff --git a/src/core/Packets.cpp b/src/core/Packets.cpp index 735c7ad..df9f919 100644 --- a/src/core/Packets.cpp +++ b/src/core/Packets.cpp @@ -235,7 +235,7 @@ std::map Packets::packets = { PACKET(P_FE2CL_REP_PC_EXIT_FAIL), PACKET(P_FE2CL_REP_PC_EXIT_SUCC), PACKET(P_FE2CL_PC_EXIT), - PACKET(P_FE2CL_PC_AROUND), + VAR_PACKET(P_FE2CL_PC_AROUND, iPCCnt, sPCAppearanceData), PACKET(P_FE2CL_PC_MOVE), PACKET(P_FE2CL_PC_STOP), PACKET(P_FE2CL_PC_JUMP), @@ -243,9 +243,9 @@ std::map Packets::packets = { PACKET(P_FE2CL_NPC_EXIT), PACKET(P_FE2CL_NPC_MOVE), PACKET(P_FE2CL_NPC_NEW), - PACKET(P_FE2CL_NPC_AROUND), - PACKET(P_FE2CL_AROUND_DEL_PC), - PACKET(P_FE2CL_AROUND_DEL_NPC), + VAR_PACKET(P_FE2CL_NPC_AROUND, iNPCCnt, sNPCAppearanceData), + VAR_PACKET(P_FE2CL_AROUND_DEL_PC, iPCCnt, int32_t), + VAR_PACKET(P_FE2CL_AROUND_DEL_NPC, iNPCCnt, int32_t), PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC), PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_FAIL), VAR_PACKET(P_FE2CL_PC_ATTACK_NPCs_SUCC, iNPCCnt, sAttackResult), @@ -387,8 +387,8 @@ std::map Packets::packets = { PACKET(P_FE2CL_TRANSPORTATION_EXIT), PACKET(P_FE2CL_TRANSPORTATION_MOVE), PACKET(P_FE2CL_TRANSPORTATION_NEW), - PACKET(P_FE2CL_TRANSPORTATION_AROUND), - PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION), + VAR_PACKET(P_FE2CL_TRANSPORTATION_AROUND, iCnt, sTransportationAppearanceData), + VAR_PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION, iCnt, int32_t), PACKET(P_FE2CL_REP_EP_RANK_LIST), PACKET(P_FE2CL_REP_EP_RANK_DETAIL), PACKET(P_FE2CL_REP_EP_RANK_PC_INFO), @@ -404,8 +404,8 @@ std::map Packets::packets = { PACKET(P_FE2CL_SHINY_ENTER), PACKET(P_FE2CL_SHINY_EXIT), PACKET(P_FE2CL_SHINY_NEW), - PACKET(P_FE2CL_SHINY_AROUND), - PACKET(P_FE2CL_AROUND_DEL_SHINY), + VAR_PACKET(P_FE2CL_SHINY_AROUND, iShinyCnt, sShinyAppearanceData), + VAR_PACKET(P_FE2CL_AROUND_DEL_SHINY, iShinyCnt, int32_t), PACKET(P_FE2CL_REP_SHINY_PICKUP_FAIL), PACKET(P_FE2CL_REP_SHINY_PICKUP_SUCC), PACKET(P_FE2CL_PC_MOVETRANSPORTATION),