@@ -410,8 +410,21 @@ void GameLogic::HandleGoldTransaction(uint64_t sessionId, const nlohmann::json&
410410 }
411411}
412412
413+ void GameLogic::SendPositionCorrection (uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity) {
414+ BinaryProtocol::BinaryWriter writer;
415+ writer.WriteVector3 (position);
416+ writer.WriteVector3 (velocity);
417+ writer.WriteUInt64 (GetCurrentTimestamp ());
418+ SendBinaryToSession (sessionId, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer ());
419+ }
420+
413421// =============== World Message Handlers ===============
414422void GameLogic::RegisterWorldHandlers () {
423+ RegisterBinaryHandler (BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE,
424+ [this ](uint64_t sessionId, uint16_t /* messageType*/ , const std::vector<uint8_t >& data) {
425+ HandlePlayerState (sessionId, data);
426+ });
427+
415428 RegisterHandler (" world_chunk_request" , [this ](uint64_t sessionId, const nlohmann::json& data) {
416429 HandleWorldChunkRequest (sessionId, data);
417430 });
@@ -507,6 +520,10 @@ void GameLogic::OnPlayerConnected(uint64_t sessionId, uint64_t playerId) {
507520void GameLogic::OnPlayerDisconnected (uint64_t sessionId) {
508521 // Capture player ID before base class removes the mapping
509522 uint64_t playerId = GetPlayerIdBySession (sessionId);
523+ {
524+ std::lock_guard<std::mutex> lock (predictionMutex_);
525+ playerPrediction_.erase (playerId);
526+ }
510527 Logger::Info (" GameLogic: Player {} disconnected from session {}" , playerId, sessionId);
511528 FirePythonEvent (" player_disconnected" , {
512529 {" sessionId" , sessionId},
@@ -592,6 +609,77 @@ void GameLogic::HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::j
592609 }
593610}
594611
612+ void GameLogic::HandlePlayerState (uint64_t sessionId, const std::vector<uint8_t >& data) {
613+ try {
614+ // Deserialize client input
615+ ClientInput input = ClientInput::Deserialize (data.data (), data.size ());
616+ if (!input.IsValid ()) {
617+ Logger::Warn (" Invalid client input from session {}" , sessionId);
618+ return ;
619+ }
620+
621+ uint64_t playerId = GetPlayerIdBySession (sessionId);
622+ if (playerId == 0 ) {
623+ Logger::Warn (" No player for session {}" , sessionId);
624+ return ;
625+ }
626+
627+ // Store input in the player's prediction system
628+ {
629+ std::lock_guard<std::mutex> lock (predictionMutex_);
630+ playerPrediction_[playerId].StoreClientInput (input);
631+ }
632+
633+ // Get authoritative player state
634+ auto player = GetPlayer (playerId);
635+ if (!player) return ;
636+
637+ // Simulate movement using the prediction system
638+ // (We'll compute the authoritative state from the last confirmed state plus unprocessed inputs)
639+ PredictionSystem* pred = &playerPrediction_[playerId];
640+ auto currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(
641+ std::chrono::steady_clock::now ().time_since_epoch ()).count ();
642+
643+ // Get the last confirmed state from prediction system (or from player)
644+ ServerState authState;
645+ authState.last_processed_input = 0 ; // TODO: track last processed input
646+ authState.timestamp = currentTime;
647+ authState.position = player->GetPosition ();
648+ authState.velocity = player->GetVelocity (); // assuming Player has velocity
649+ authState.rotation = player->GetRotation (); // assuming Player has rotation
650+ authState.on_ground = player->IsOnGround (); // assuming Player has onGround
651+
652+ // Simulate with unprocessed inputs
653+ auto unprocessed = pred->GetUnprocessedInputs (authState.last_processed_input );
654+ if (!unprocessed.empty ()) {
655+ float deltaTime = 1 .0f / 30 .0f ; // or compute based on time difference
656+ authState = pred->SimulateMovement (authState, unprocessed, deltaTime);
657+ }
658+
659+ // Update authoritative player state
660+ player->SetPosition (authState.position );
661+ player->SetVelocity (authState.velocity );
662+ player->SetRotation (authState.rotation );
663+ player->SetOnGround (authState.on_ground );
664+
665+ // Broadcast authoritative state to nearby players (optional)
666+ BroadcastPlayerState (playerId, authState);
667+
668+ // Check if client needs a correction (compare with client's last reported position)
669+ // We need to store the client's last reported position; for simplicity we can compare
670+ // with the position that the client sent (which is not directly available here, but we
671+ // could pass it in the input). For now, we'll just send a correction if the simulation
672+ // moved significantly from the last confirmed state.
673+ static float correctionThreshold = 0 .5f ; // 0.5 meters
674+ if (glm::distance (authState.position , player->GetPosition ()) > correctionThreshold) {
675+ SendPositionCorrection (sessionId, authState.position , authState.velocity );
676+ }
677+
678+ } catch (const std::exception& e) {
679+ Logger::Error (" HandlePlayerState error: {}" , e.what ());
680+ }
681+ }
682+
595683void GameLogic::HandleNPCInteraction (uint64_t sessionId, const nlohmann::json& data) {
596684 try {
597685 uint64_t npcId = data.value (" npcId" , 0ULL );
@@ -774,6 +862,19 @@ void GameLogic::HandleEntitySpawnRequest(uint64_t sessionId, const nlohmann::jso
774862}
775863
776864// =============== Broadcasting ===============
865+ void GameLogic::BroadcastEntitySpawn (uint64_t entityId, EntityType type, const glm::vec3& position,
866+ float yaw, const std::string& name) {
867+ BinaryProtocol::BinaryWriter writer;
868+ writer.WriteUInt64 (entityId);
869+ writer.WriteUInt8 (static_cast <uint8_t >(type));
870+ writer.WriteString (name);
871+ writer.WriteVector3 (position);
872+ writer.WriteFloat (yaw);
873+ writer.WriteUInt64 (GetCurrentTimestamp ());
874+
875+ BroadcastToNearbyPlayers (position, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer (), 100 .0f );
876+ }
877+
777878void GameLogic::BroadcastBinaryToNearbyPlayers (const glm::vec3& position, uint16_t messageType,
778879 const std::vector<uint8_t >& data, float radius) {
779880 if (!connectionManager_) return ;
@@ -1137,3 +1238,22 @@ void GameLogic::BroadcastToPlayers(const std::vector<uint64_t>& sessionIds, cons
11371238 Logger::Error (" Error broadcasting to specific players: {}" , e.what ());
11381239 }
11391240}
1241+
1242+ void GameLogic::BroadcastPlayerState (uint64_t playerId, const ServerState& state) {
1243+ // Create a binary update message (ENTITY_UPDATE or PLAYER_STATE) for other players
1244+ BinaryProtocol::BinaryWriter writer;
1245+ writer.WriteUInt64 (playerId);
1246+ writer.WriteVector3 (state.position );
1247+ writer.WriteVector3 (state.rotation );
1248+ writer.WriteVector3 (state.velocity );
1249+ writer.WriteUInt64 (state.timestamp );
1250+ BroadcastToNearbyPlayers (state.position , BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer (), 100 .0f );
1251+ }
1252+
1253+ void GameLogic::BroadcastEntityDespawn (uint64_t entityId, const glm::vec3& position) {
1254+ BinaryProtocol::BinaryWriter writer;
1255+ writer.WriteUInt64 (entityId);
1256+ writer.WriteUInt64 (GetCurrentTimestamp ());
1257+
1258+ BroadcastToNearbyPlayers (position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer (), 100 .0f );
1259+ }
0 commit comments