diff --git a/src/ChunkManager.cpp b/src/ChunkManager.cpp index dd689dd..dcb9a1e 100644 --- a/src/ChunkManager.cpp +++ b/src/ChunkManager.cpp @@ -56,6 +56,17 @@ void ChunkManager::removePlayer(std::pair chunkPos, CNSocket* sock) { // TODO: if players and NPCs are empty, free chunk and remove it from surrounding views } +void ChunkManager::removeNPC(std::pair chunkPos, int32_t id) { + if (!checkChunk(chunkPos)) + return; // do nothing if chunk doesn't even exist + + Chunk* chunk = chunks[chunkPos]; + + chunk->NPCs.erase(id); // gone + + // TODO: if players and NPCs are empty, free chunk and remove it from surrounding views +} + bool ChunkManager::checkChunk(std::pair chunk) { return chunks.find(chunk) != chunks.end(); } diff --git a/src/ChunkManager.hpp b/src/ChunkManager.hpp index 3e08ee8..97d061a 100644 --- a/src/ChunkManager.hpp +++ b/src/ChunkManager.hpp @@ -22,6 +22,7 @@ namespace ChunkManager { void addNPC(int posX, int posY, int32_t id); void addPlayer(int posX, int posY, CNSocket* sock); void removePlayer(std::pair chunkPos, CNSocket* sock); + void removeNPC(std::pair chunkPos, int32_t id); bool checkChunk(std::pair chunk); std::pair grabChunk(int posX, int posY); std::vector grabChunks(std::pair chunkPos); diff --git a/src/Defines.hpp b/src/Defines.hpp index fa8555a..fc782dc 100644 --- a/src/Defines.hpp +++ b/src/Defines.hpp @@ -16,6 +16,14 @@ const float CN_EP_RANK_3 = 0.5f; const float CN_EP_RANK_4 = 0.3f; const float CN_EP_RANK_5 = 0.29f; +// NPC classes +enum NPCClass { + NPC_BASE = 0, + NPC_MOB = 1, + NPC_BUS = 2, + NPC_EGG = 3 +}; + // nano powers enum { EST_NONE = 0, diff --git a/src/NPC.hpp b/src/NPC.hpp index 371d420..c2db348 100644 --- a/src/NPC.hpp +++ b/src/NPC.hpp @@ -5,6 +5,7 @@ class BaseNPC { public: sNPCAppearanceData appearanceData; + NPCClass npcClass; BaseNPC() {}; BaseNPC(int x, int y, int z, int type) { @@ -20,4 +21,7 @@ public: // hopefully no collisions happen :eyes: appearanceData.iNPC_ID = (int32_t)rand(); }; + BaseNPC(int x, int y, int z, int type, NPCClass classType) : BaseNPC(x, y, z, type) { + npcClass = classType; + } }; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 346de25..45249f1 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -33,15 +33,30 @@ void NPCManager::init() { void NPCManager::addNPC(std::vector viewableChunks, int32_t id) { BaseNPC* npc = NPCs[id]; - // create struct - INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); - enterData.NPCAppearanceData = npc->appearanceData; + 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 }; - for (Chunk* chunk : viewableChunks) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); + for (Chunk* chunk : viewableChunks) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER)); + } } + break; + default: + // create struct + INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); + enterData.NPCAppearanceData = npc->appearanceData; + + for (Chunk* chunk : viewableChunks) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); + } + } + break; } } @@ -66,21 +81,38 @@ void NPCManager::removeNPC(int32_t id) { Chunk* chunk = ChunkManager::chunks[pos]; chunk->NPCs.erase(id); - // create struct - INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); - exitData.iNPC_ID = id; + switch (entity->npcClass) { + case NPC_BUS: + INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData); + exitBusData.eTT = 3; + exitBusData.iT_ID = id; - // remove it from the clients - for (Chunk* chunk : ChunkManager::grabChunks(pos)) { - for (CNSocket* sock : chunk->players) { - // send to socket - sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + for (Chunk* chunk : ChunkManager::grabChunks(pos)) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT)); + } } - } + break; + case NPC_MOB: + // remove from mob list if it's a mob + if (MobManager::Mobs.find(id) != MobManager::Mobs.end()) + MobManager::Mobs.erase(id); + // fall through + default: + // create struct + INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); + exitData.iNPC_ID = id; - // remove from mob list if it's a mob - if (MobManager::Mobs.find(id) != MobManager::Mobs.end()) - MobManager::Mobs.erase(id); + // remove it from the clients + for (Chunk* chunk : ChunkManager::grabChunks(pos)) { + for (CNSocket* sock : chunk->players) { + // send to socket + sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + } + } + break; + } // finally, remove it from the map and free it NPCs.erase(id); @@ -89,6 +121,24 @@ void NPCManager::removeNPC(int32_t id) { std::cout << "npc removed!" << std::endl; } +void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z) { + BaseNPC* npc = NPCs[id]; + + std::pair oldPos = ChunkManager::grabChunk(npc->appearanceData.iX, npc->appearanceData.iY); + npc->appearanceData.iX = X; + npc->appearanceData.iY = Y; + npc->appearanceData.iZ = Z; + std::pair newPos = ChunkManager::grabChunk(X, Y); + + // nothing to be done + if (newPos == oldPos) + return; + + // pull NPC from old chunk and add to new chunk + ChunkManager::removeNPC(oldPos, npc->appearanceData.iNPC_ID); + ChunkManager::addNPC(X, Y, npc->appearanceData.iNPC_ID); +} + void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY)) return; // malformed packet diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 9373cf3..aee6553 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -22,6 +22,7 @@ namespace NPCManager { void addNPC(std::vector viewableChunks, int32_t); void removeNPC(int32_t); + void updateNPCPosition(int32_t, int X, int Y, int Z); void npcBarkHandler(CNSocket* sock, CNPacketData* data); void npcSummonHandler(CNSocket* sock, CNPacketData* data); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index cbd499f..5fd1e5f 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -27,6 +27,7 @@ void PlayerManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LAUNCHER, PlayerManager::launchPlayer); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ZIPLINE, PlayerManager::ziplinePlayer); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVEPLATFORM, PlayerManager::movePlatformPlayer); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVETRANSPORTATION, PlayerManager::moveSliderPlayer); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SLOPE, PlayerManager::moveSlopePlayer); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, PlayerManager::gotoPlayer); REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, PlayerManager::setSpecialPlayer); @@ -79,7 +80,6 @@ void PlayerManager::removePlayer(CNSocket* key) { } void PlayerManager::removePlayerFromChunks(std::vector chunks, CNSocket* sock) { - INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData); INITSTRUCT(sP_FE2CL_PC_EXIT, exitPlayer); // for chunks that need the player to be removed from @@ -87,8 +87,20 @@ void PlayerManager::removePlayerFromChunks(std::vector chunks, CNSocket* // remove NPCs for (int32_t id : chunk->NPCs) { - exitData.iNPC_ID = id; - sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + BaseNPC* npc = NPCManager::NPCs[id]; + 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; + 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 @@ -105,14 +117,24 @@ void PlayerManager::removePlayerFromChunks(std::vector chunks, CNSocket* } void PlayerManager::addPlayerToChunks(std::vector chunks, CNSocket* sock) { - INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); INITSTRUCT(sP_FE2CL_PC_NEW, newPlayer); for (Chunk* chunk : chunks) { // add npcs for (int32_t id : chunk->NPCs) { - enterData.NPCAppearanceData = NPCManager::NPCs[id]->appearanceData; - sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); + BaseNPC* npc = NPCManager::NPCs[id]; + 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; + 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; + } } // add players @@ -528,6 +550,37 @@ void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) { sendToViewable(sock, (void*)&platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM)); } +void PlayerManager::moveSliderPlayer(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_MOVETRANSPORTATION)) + return; // ignore the malformed packet + + sP_CL2FE_REQ_PC_MOVETRANSPORTATION* sliderData = (sP_CL2FE_REQ_PC_MOVETRANSPORTATION*)data->buf; + updatePlayerPosition(sock, sliderData->iX, sliderData->iY, sliderData->iZ, sliderData->iAngle); + + uint64_t tm = getTime(); + + INITSTRUCT(sP_FE2CL_PC_MOVETRANSPORTATION, sliderResponse); + + sliderResponse.iPC_ID = players[sock].plr->iID; + sliderResponse.iCliTime = sliderData->iCliTime; + sliderResponse.iSvrTime = tm; + sliderResponse.iX = sliderData->iX; + sliderResponse.iY = sliderData->iY; + sliderResponse.iZ = sliderData->iZ; + sliderResponse.iAngle = sliderData->iAngle; + sliderResponse.fVX = sliderData->fVX; + sliderResponse.fVY = sliderData->fVY; + sliderResponse.fVZ = sliderData->fVZ; + sliderResponse.iLcX = sliderData->iLcX; + sliderResponse.iLcY = sliderData->iLcY; + sliderResponse.iLcZ = sliderData->iLcZ; + sliderResponse.iSpeed = sliderData->iSpeed; + sliderResponse.cKeyValue = sliderData->cKeyValue; + sliderResponse.iT_ID = sliderData->iT_ID; + + sendToViewable(sock, (void*)&sliderResponse, P_FE2CL_PC_MOVETRANSPORTATION, sizeof(sP_FE2CL_PC_MOVETRANSPORTATION)); +} + void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_SLOPE)) return; // ignore the malformed packet diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 0598e71..b67b7ac 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -44,6 +44,7 @@ namespace PlayerManager { void launchPlayer(CNSocket* sock, CNPacketData* data); void ziplinePlayer(CNSocket* sock, CNPacketData* data); void movePlatformPlayer(CNSocket* sock, CNPacketData* data); + void moveSliderPlayer(CNSocket* sock, CNPacketData* data); void moveSlopePlayer(CNSocket* sock, CNPacketData* data); void gotoPlayer(CNSocket* sock, CNPacketData* data); void setSpecialPlayer(CNSocket* sock, CNPacketData* data); diff --git a/src/TableData.cpp b/src/TableData.cpp index dfc0ae0..f1a5312 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -263,19 +263,7 @@ void TableData::constructPath(nlohmann::json::iterator _pathData) { for (_point++; _point != pathPoints.end(); _point++) { point = _point.value(); WarpLocation coords = { point["iX"] , point["iY"] , point["iZ"] }; - // Calculate distance between this point and the last - int dXY = hypot(last.x - coords.x, last.y - coords.y); // XY plane distance - int distanceBetween = hypot(dXY, last.z - coords.z); // total distance - int lerps = distanceBetween / (int)pathData["iMonkeySpeed"]; // integer division to ensure a whole number of in-between points - for (int i = 0; i < lerps; i++) { - WarpLocation lerp; - // lerp math - float frac = (i + 1) * 1.0f / (lerps + 1); - lerp.x = (last.x * (1.0f - frac)) + (coords.x * frac); - lerp.y = (last.y * (1.0f - frac)) + (coords.y * frac); - lerp.z = (last.z * (1.0f - frac)) + (coords.z * frac); - points.push(lerp); // add lerp'd point to the queue - } + TransportManager::lerp(&points, last, coords, pathData["iMonkeySpeed"]); points.push(coords); // add keyframe to the queue last = coords; // update start pos } diff --git a/src/TransportManager.cpp b/src/TransportManager.cpp index 82dfe73..67f97ae 100644 --- a/src/TransportManager.cpp +++ b/src/TransportManager.cpp @@ -4,17 +4,31 @@ #include "TransportManager.hpp" #include +#include std::map TransportManager::Routes; std::map TransportManager::Locations; std::map> TransportManager::SkywayPaths; std::unordered_map> TransportManager::SkywayQueues; +std::unordered_map> TransportManager::NPCQueues; void TransportManager::init() { - REGISTER_SHARD_TIMER(tickSkywaySystem, 1000); + REGISTER_SHARD_TIMER(tickTransportationSystem, 1000); REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler); + + BaseNPC* bus = new BaseNPC(220447, 162431, -3650, 1, NPC_BUS); + NPCManager::NPCs[bus->appearanceData.iNPC_ID] = bus; + ChunkManager::addNPC(bus->appearanceData.iX, bus->appearanceData.iY, bus->appearanceData.iNPC_ID); + std::queue busPoints; + WarpLocation start = { bus->appearanceData.iX, bus->appearanceData.iY, -3650 }; + WarpLocation end = { 220441, 188102, -3653 }; + busPoints.push(start); + TransportManager::lerp(&busPoints, start, end, 1000); + busPoints.push(end); + TransportManager::lerp(&busPoints, end, start, 1000); + NPCQueues[bus->appearanceData.iNPC_ID] = busPoints; } void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) { @@ -174,13 +188,17 @@ void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data) sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC)); } +void TransportManager::tickTransportationSystem(CNServer* serv, time_t currTime) { + stepNPCPathing(); + stepSkywaySystem(); +} + /* * Go through every socket that has broomstick points queued up, and advance to the next point. * If the player has disconnected or finished the route, clean up and remove them from the queue. */ -void TransportManager::tickSkywaySystem(CNServer* serv, time_t currTime) { +void TransportManager::stepSkywaySystem() { - //std::cout << SkywayQueue.size(); // using an unordered map so we can remove finished players in one iteration std::unordered_map>::iterator it = SkywayQueues.begin(); while (it != SkywayQueues.end()) { @@ -228,3 +246,94 @@ void TransportManager::tickSkywaySystem(CNServer* serv, time_t currTime) { } } } + +void TransportManager::stepNPCPathing() { + + // all NPC pathing queues + std::unordered_map>::iterator it = NPCQueues.begin(); + while (it != NPCQueues.end()) { + + std::queue* queue = &it->second; + + BaseNPC* npc = nullptr; + if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end()) + npc = NPCManager::NPCs[it->first]; + + if (npc == nullptr) { + // pluck out dead path + update iterator + it = NPCQueues.erase(it); + continue; + } + + WarpLocation point = queue->front(); // get point + queue->pop(); // remove point from front of queue + + // calculate displacement + int dXY = hypot(point.x - npc->appearanceData.iX, point.y - npc->appearanceData.iY); // XY plane distance + int distanceBetween = hypot(dXY, point.z - npc->appearanceData.iZ); // total distance + + // update NPC location to update viewables + NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z); + + // get chunks in view + auto chunk = ChunkManager::grabChunk(npc->appearanceData.iX, npc->appearanceData.iY); + auto chunks = ChunkManager::grabChunks(chunk); + + switch (npc->npcClass) { + case NPC_BUS: + INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); + busMove.eTT = 3; + busMove.iT_ID = npc->appearanceData.iNPC_ID; + busMove.iMoveStyle = 0; // ??? + busMove.iToX = point.x; + busMove.iToY = point.y; + busMove.iToZ = point.z; + busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work + + // send packet to players in view + for (Chunk* chunk : chunks) { + for (CNSocket* s : chunk->players) { + s->sendPacket(&busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE)); + } + } + break; + default: + INITSTRUCT(sP_FE2CL_NPC_MOVE, move); + move.iNPC_ID = npc->appearanceData.iNPC_ID; + move.iMoveStyle = 0; // ??? + move.iToX = point.x; + move.iToY = point.y; + move.iToZ = point.z; + move.iSpeed = 600; // TODO: figure out a way to make this variable + + // send packet to players in view + for (Chunk* chunk : chunks) { + for (CNSocket* s : chunk->players) { + s->sendPacket(&move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); + } + } + break; + } + + queue->push(point); // move processed point to the back to maintain cycle + it++; // go to next entry in map + } +} + +/* + * Linearly interpolate between two points and insert the results into a queue. + */ +void TransportManager::lerp(std::queue* queue, WarpLocation start, WarpLocation end, int gapSize) { + int dXY = hypot(end.x - start.x, end.y - start.y); // XY plane distance + int distanceBetween = hypot(dXY, end.z - start.z); // total distance + int lerps = distanceBetween / gapSize; // integer division to ensure a whole number of in-between points + for (int i = 0; i < lerps; i++) { + WarpLocation lerp; + // lerp math + float frac = (i + 1) * 1.0f / (lerps + 1); + lerp.x = (start.x * (1.0f - frac)) + (end.x * frac); + lerp.y = (start.y * (1.0f - frac)) + (end.y * frac); + lerp.z = (start.z * (1.0f - frac)) + (end.z * frac); + queue->push(lerp); // add lerp'd point + } +} diff --git a/src/TransportManager.hpp b/src/TransportManager.hpp index 0f0f67d..0d62a33 100644 --- a/src/TransportManager.hpp +++ b/src/TransportManager.hpp @@ -20,11 +20,16 @@ namespace TransportManager { extern std::map Locations; extern std::map> SkywayPaths; // predefined skyway paths with points extern std::unordered_map> SkywayQueues; // player sockets with queued broomstick points + extern std::unordered_map> NPCQueues; // NPC ids with queued pathing points void init(); - void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data); - void transportWarpHandler(CNSocket* sock, CNPacketData* data); + void transportRegisterLocationHandler(CNSocket*, CNPacketData*); + void transportWarpHandler(CNSocket*, CNPacketData*); - void tickSkywaySystem(CNServer* serv, time_t currTime); + void tickTransportationSystem(CNServer*, time_t); + void stepNPCPathing(); + void stepSkywaySystem(); + + void lerp(std::queue*, WarpLocation, WarpLocation, int); }