diff --git a/CastServer/include/CastServer.h b/CastServer/include/CastServer.h index 05c548fc..5ab4010d 100644 --- a/CastServer/include/CastServer.h +++ b/CastServer/include/CastServer.h @@ -24,6 +24,7 @@ namespace Cast std::uint16_t m_serverId; Cast::Network::SessionsManager m_sessionsManager{}; static inline Cast::Classes::RoomsManager m_roomsManager{}; + std::shared_ptr m_positionTimer; tcp::acceptor m_mainServerAcceptor; std::optional m_mainSocket; @@ -34,6 +35,7 @@ namespace Cast CastServer(ioContext& io_context, const std::string& serverIp, std::uint16_t port, std::uint16_t mainPort, std::uint16_t serverId); void asyncAccept(); void asyncAcceptMainServer(); + void tickPositionFlush(); }; } diff --git a/CastServer/include/Classes/Room.h b/CastServer/include/Classes/Room.h index 2db72b10..64b29bf2 100644 --- a/CastServer/include/Classes/Room.h +++ b/CastServer/include/Classes/Room.h @@ -27,9 +27,13 @@ namespace Cast std::uint32_t m_roomNumber = -1; std::uint32_t m_serverId{}; + std::vector m_pendingPositions; + public: bool m_hasMatchStarted{}; bool m_isInvisible{}; + std::uint32_t m_roomTick{}; + // Arena Mode bool m_isArenaMode{}; @@ -124,6 +128,10 @@ namespace Cast void shuffleCoordinates(); void respawnEveryoneArena(); + + void enqueuePosition(Common::Network::UnecryptedPacket&& pkt); + + void flushPendingPositions(); }; } } diff --git a/CastServer/include/Classes/RoomsManager.h b/CastServer/include/Classes/RoomsManager.h index 661892da..044b2105 100644 --- a/CastServer/include/Classes/RoomsManager.h +++ b/CastServer/include/Classes/RoomsManager.h @@ -14,8 +14,14 @@ namespace Cast { private: std::array, Common::Constants::maxSessionsPerServer + 1> m_playerSessionIdToRoom{}; + std::vector> m_rooms{}; public: + const auto& getAllRooms() const + { + return m_rooms; + } + void addRoom(std::shared_ptr room, std::uint64_t playerId); void switchRoomJoinOrExit(std::shared_ptr session, std::uint64_t hostSessionId = -1); @@ -32,6 +38,8 @@ namespace Cast void playerForwardToHost(std::uint64_t hostSessionId, std::uint64_t senderSessionId, Common::Network::UnecryptedPacket& packet); + void setRoomTick(std::uint64_t hostSessionId, std::uint64_t tick); + void hostForwardToPlayer(std::uint64_t hostSessionId, std::uint64_t receiverSessionId, Common::Network::UnecryptedPacket& packet, bool useHostSessionIdInTcpHeader = true); void setMapFor(std::uint64_t playerId, std::uint32_t map); diff --git a/CastServer/include/Handlers/PlayerPositionHandler.h b/CastServer/include/Handlers/PlayerPositionHandler.h index 9a9bd656..db1c324a 100644 --- a/CastServer/include/Handlers/PlayerPositionHandler.h +++ b/CastServer/include/Handlers/PlayerPositionHandler.h @@ -17,9 +17,14 @@ namespace Cast { namespace Handlers { + std::uint32_t getPositionTickMs(std::uint64_t timeSinceLastRestart, std::uint64_t roomTick) + { + return Common::Utils::getCurrentTimestampMs() - timeSinceLastRestart; + } + inline void handlePlayerPosition(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, Cast::Classes::RoomsManager& roomsManager, std::uint32_t serverId, Cast::Network::SessionsManager& sm, - Ac::AntiCheatManager& acManager) + Ac::AntiCheatManager& acManager, std::uint64_t timeSinceLastRestart) { using namespace Cast::Structures; @@ -29,6 +34,8 @@ namespace Cast Cast::Structures::ClientPlayerInfoBasic playerPositionFromClient = Cast::Details::parseData(request); if (playerPositionFromClient.isBad()) return; + room->m_roomTick = playerPositionFromClient.matchTick; + if (room->m_isInvisible || session->m_isInvisible) { @@ -57,20 +64,17 @@ namespace Cast const auto fullSize = request.getFullSize(); - if (fullSize == 36) + // Note: melee worked without warping, and the only thing melee doesn't have is bullets! That may be the issue with all-weapons FFA warps + if (fullSize == 36) { Cast::Structures::ClientPlayerInfoBullet playerPositionBullet{}; std::memcpy(&playerPositionBullet, request.getData(), sizeof(playerPositionBullet)); - if (playerPositionBullet.isBad()) - { - ::Utils::Logger::log("Bad Player Respawn Position", ::Utils::LogType::Warning, "Cast::handlePlayerPosition"); - return; - } + if (playerPositionBullet.isBad()) return; PlayerInfoResponseWithBullets playerInfoResponseWithBullets; + // playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; playerInfoResponseWithBullets.specificInfo.enableBullet = true; - - playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; + playerInfoResponseWithBullets.specificInfo.enableJump = false; playerInfoResponseWithBullets.position = playerPositionFromClient.position; playerInfoResponseWithBullets.direction = playerPositionFromClient.direction; playerInfoResponseWithBullets.specificInfo.animation1 = playerPositionFromClient.animation1; @@ -83,9 +87,8 @@ namespace Cast playerInfoResponseWithBullets.currentWeapon = playerPositionBullet.bulletStruct.bullet4; response.setData(reinterpret_cast(&playerInfoResponseWithBullets), sizeof(playerInfoResponseWithBullets)); - } - else if (fullSize == 40) + else if (fullSize == 40) // bullet+jump+movement+rotation (simplified) { Cast::Structures::ClientPlayerInfoComplete playerPositionComplete{}; std::memcpy(&playerPositionComplete, request.getData(), request.getDataSize()); @@ -97,8 +100,9 @@ namespace Cast PlayerInfoResponseWithBullets playerInfoResponseWithBullets; playerInfoResponseWithBullets.specificInfo.enableBullet = true; + playerInfoResponseWithBullets.specificInfo.enableJump = true; - playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; + // playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; playerInfoResponseWithBullets.position = playerPositionFromClient.position; playerInfoResponseWithBullets.direction = playerPositionFromClient.direction; playerInfoResponseWithBullets.specificInfo.animation1 = playerPositionFromClient.animation1; @@ -109,9 +113,7 @@ namespace Cast playerInfoResponseWithBullets.specificInfo.sessionId = static_cast(session->getId()); playerInfoResponseWithBullets.bullets = playerPositionBullet.bulletStruct; playerInfoResponseWithBullets.currentWeapon = playerPositionBullet.bulletStruct.bullet4; - PlayerInfoResponseComplete playerInfoResponseComplete{ playerInfoResponseWithBullets }; - playerInfoResponseComplete.playerInfoBasicResponse.specificInfo.enableJump = true; playerInfoResponseComplete.jump = playerPositionComplete.jumpStruct; response.setData(reinterpret_cast(&playerInfoResponseComplete), sizeof(playerInfoResponseComplete)); @@ -119,7 +121,7 @@ namespace Cast else { PlayerInfoBasicResponse playerInfoBasicResponse; - playerInfoBasicResponse.tick = playerPositionFromClient.matchTick; + // playerInfoBasicResponse.tick = playerPositionFromClient.matchTick; playerInfoBasicResponse.position = playerPositionFromClient.position; playerInfoBasicResponse.direction = playerPositionFromClient.direction; playerInfoBasicResponse.currentWeapon = playerPositionFromClient.weapon; @@ -129,7 +131,8 @@ namespace Cast playerInfoBasicResponse.rotation2 = request.getOption(); playerInfoBasicResponse.rotation3 = playerPositionFromClient.rotation; playerInfoBasicResponse.specificInfo.sessionId = static_cast(session->getId()); - + playerInfoBasicResponse.specificInfo.enableJump = false; + playerInfoBasicResponse.specificInfo.enableBullet = false; if (fullSize == 28) { @@ -139,7 +142,6 @@ namespace Cast { playerInfoBasicResponse.specificInfo.enableJump = true; PlayerInfoResponseWithJump playerInfoResponseWithJump{ playerInfoBasicResponse }; - Cast::Structures::ClientPlayerInfoJump playerPositionJump{}; std::memcpy(&playerPositionJump, request.getData(), request.getDataSize()); @@ -156,7 +158,8 @@ namespace Cast } // reminder: this is correct, dont use exceptSelf as that causes log errors in SysLog (host receives [322] command not found) - room->broadcastToRoom(response); + // room->broadcastToRoom(response); + room->enqueuePosition(std::move(response)); } } } diff --git a/CastServer/include/Network/CastSession.h b/CastServer/include/Network/CastSession.h index 211339ac..4a17e14f 100644 --- a/CastServer/include/Network/CastSession.h +++ b/CastServer/include/Network/CastSession.h @@ -30,6 +30,10 @@ namespace Cast bool m_isInMatch{ 0 }; std::string m_nickname; bool m_isInvisible{}; + Cast::Structures::ClientPlayerInfoBasic m_lastClientPosition{}; + std::uint32_t m_lastRotation1{}; + std::uint32_t m_lastRotation2{}; + std::uint32_t m_lastRotation3{}; public: explicit Session(asio::ip::tcp::socket&& socket, std::function fnct) diff --git a/CastServer/include/Structures/PlayerPositionFromClient.h b/CastServer/include/Structures/PlayerPositionFromClient.h index f758a1d5..3bf65c77 100644 --- a/CastServer/include/Structures/PlayerPositionFromClient.h +++ b/CastServer/include/Structures/PlayerPositionFromClient.h @@ -133,6 +133,13 @@ PACK_PUSH(1) { return position.isBadPos() || direction.isBadDir(); } + + bool hasMoved(const PositionStruct& newPos) const + { + return position.positionX != newPos.positionX || + position.positionY != newPos.positionY || + position.positionZ != newPos.positionZ; + } }; PACK_POP() diff --git a/CastServer/include/Structures/PlayerPositionFromServer.h b/CastServer/include/Structures/PlayerPositionFromServer.h index 930c8543..83f2e53e 100644 --- a/CastServer/include/Structures/PlayerPositionFromServer.h +++ b/CastServer/include/Structures/PlayerPositionFromServer.h @@ -28,7 +28,7 @@ PACK_POP() PACK_PUSH(1) struct PlayerInfoBasicResponse { - std::uint32_t tick{}; + // std::uint32_t tick{}; SpecificInfo specificInfo{}; Cast::Structures::PositionStruct position; Cast::Structures::DirectionStruct direction; @@ -50,7 +50,7 @@ PACK_POP() PACK_PUSH(1) struct PlayerInfoResponseWithBullets { - std::uint32_t tick{}; + // std::uint32_t tick{}; SpecificInfo specificInfo{}; Cast::Structures::PositionStruct position; Cast::Structures::DirectionStruct direction; diff --git a/CastServer/include/Utils/Utilities.h b/CastServer/include/Utils/Utilities.h index 71f43de5..6a03b214 100644 --- a/CastServer/include/Utils/Utilities.h +++ b/CastServer/include/Utils/Utilities.h @@ -73,7 +73,7 @@ namespace Cast return parseDataImpl(request, offset, location); } - bool mustBroadcastDeath(std::uint32_t mode) + inline bool mustBroadcastDeath(std::uint32_t mode) { return (mode == Common::Enums::Elimination || mode == Common::Enums::CaptureTheBattery || mode == Common::Enums::BombBattle || mode == Common::Enums::Clan_BombBattle diff --git a/CastServer/src/CastServer.cpp b/CastServer/src/CastServer.cpp index 6eacc6f5..aa50a4fa 100644 --- a/CastServer/src/CastServer.cpp +++ b/CastServer/src/CastServer.cpp @@ -4,9 +4,20 @@ #include "../include/Handlers/PlayerPositionHandler.h" #include "../include/Handlers/IpcMainHandlers.h" #include "../include/Handlers/WeaponKillHandlers.h" +#include namespace Cast { + void CastServer::tickPositionFlush() + { + for (auto& room : m_roomsManager.getAllRooms()) + { + room->flushPendingPositions(); + } + + m_positionTimer->expires_after(std::chrono::milliseconds(50)); + m_positionTimer->async_wait([this](auto) { tickPositionFlush(); }); + } CastServer::CastServer(ioContext& io_context, const std::string& serverIp, std::uint16_t port, std::uint16_t mainPort, std::uint16_t serverId) : m_io_context{ io_context } @@ -14,6 +25,12 @@ namespace Cast , m_serverId{ serverId } , m_mainServerAcceptor{ io_context, tcp::endpoint(asio::ip::address::from_string(Common::Utils::SetupParser::getInstance().getSelfCastServerInfo().ip), mainPort) } { + using namespace std::chrono; + + m_sessionsManager.setRoomsManager(&m_roomsManager); + m_positionTimer = std::make_shared(m_io_context); + tickPositionFlush(); + namespace CN = Common::Network; using namespace Cast::Network; @@ -109,7 +126,7 @@ namespace Cast Common::Network::Session::addCallback(281, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { Cast::Handlers::handlePlayerPosition(request, session, m_roomsManager, m_serverId, m_sessionsManager, - m_acManager); }); + m_acManager, m_timeSinceLastRestart); }); Common::Network::Session::addCallback(253, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { Cast::Handlers::handleCrash(request, session, m_roomsManager, m_serverId); }); @@ -153,9 +170,13 @@ namespace Cast // Room tick sync request: // When Option==9 in packet order 257: the non host's client sends packet 79 to the server, which dispatches to the host // This packet asks the host to provide the room sync to the non-host - Common::Network::Session::addCallback(79, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), - const_cast(request)); }); + Common::Network::Session::addCallback( + 79, + [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { + m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); + } + ); + // In-room info request, apparenly contains "isDeath" data?? Common::Network::Session::addCallback(306, [&](const Common::Network::UnecryptedPacket& request, @@ -215,8 +236,12 @@ namespace Cast // After the host client receives packet 78 from the non-host, it provides the non-host with the updated room tick // Without this handler, the player never respawns (not even TAB shows anything) => The player keeps being in a waiting initial state Common::Network::Session::addCallback(408, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), - const_cast(request)); }); + std::shared_ptr session) { + m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), + const_cast(request)); + } + ); + // Items respawning Common::Network::Session::addCallback(260, [&](const Common::Network::UnecryptedPacket& request, @@ -336,8 +361,6 @@ namespace Cast m_socket.emplace(m_io_context); m_acceptor.async_accept(*m_socket, [&](asio::error_code error) { - m_sessionsManager.setRoomsManager(&m_roomsManager); - auto client = std::make_shared(std::move(*CastServer::m_socket), std::bind(&Cast::Network::SessionsManager::removeSession, &m_sessionsManager, std::placeholders::_1)); client->m_checkValidSession = true; diff --git a/CastServer/src/Classes/Room.cpp b/CastServer/src/Classes/Room.cpp index 16d72f43..7f8c7de6 100644 --- a/CastServer/src/Classes/Room.cpp +++ b/CastServer/src/Classes/Room.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace Cast { @@ -385,7 +387,41 @@ namespace Cast broadcastToMatch(response); } } - } + + void Room::enqueuePosition(Common::Network::UnecryptedPacket&& pkt) + { + m_pendingPositions.push_back(std::move(pkt)); + } + + void Room::flushPendingPositions() + { + if (m_pendingPositions.empty()) return; + + static std::array batchBuffer; + std::size_t totalSize = 0; + std::size_t count = 0; + + for (auto& pkt : m_pendingPositions) + { + const auto size = pkt.getDataSize(); + if (totalSize + size > 2036) break; // 8 bytes header + 4 bytes roomtick + std::memcpy(batchBuffer.data() + totalSize, pkt.getData(), size); + totalSize += size; + ++count; + } + + //const std::uint32_t serverTick = m_roomTick - (((Common::Utils::getCurrentTimestampMs() - timeSinceLastRestart) / 10) - m_roomTick); + std::memmove(batchBuffer.data() + sizeof(m_roomTick), batchBuffer.data(), totalSize); + std::memcpy(batchBuffer.data(), &m_roomTick, sizeof(m_roomTick)); + totalSize += sizeof(m_roomTick); + static Common::Network::UnecryptedPacket batch(2048, 322, static_cast(count)); + batch.setOption(static_cast(count)); + batch.setData(batchBuffer.data(), static_cast(totalSize)); + + broadcastToRoom(batch); + m_pendingPositions.clear(); + } + }; } diff --git a/CastServer/src/Classes/RoomsManager.cpp b/CastServer/src/Classes/RoomsManager.cpp index 952ce803..dacb3c01 100644 --- a/CastServer/src/Classes/RoomsManager.cpp +++ b/CastServer/src/Classes/RoomsManager.cpp @@ -13,6 +13,7 @@ namespace Cast if (playerId < m_playerSessionIdToRoom.size()) { m_playerSessionIdToRoom[playerId] = room; + m_rooms.push_back(std::move(room)); } } @@ -308,6 +309,8 @@ namespace Cast if (mustRoomBeRemoved) { + m_rooms.erase(std::remove(m_rooms.begin(), m_rooms.end(), roomToRemove), m_rooms.end()); + for (auto& roomSlot : m_playerSessionIdToRoom) { if (roomSlot == roomToRemove)