Skip to content

Commit e696f2b

Browse files
receive entity spawn/update/despawn messages
1 parent 92718aa commit e696f2b

7 files changed

Lines changed: 180 additions & 8 deletions

File tree

include/game/GameLogic.hpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@
22

33
#include <cmath>
44
#include <memory>
5+
#include <unordered_map>
56

67
#include <glm/glm.hpp>
78
#include <nlohmann/json.hpp>
89

9-
// #include "config/ConfigManager.hpp"
10-
// #include "logging/Logger.hpp"
11-
// #include "network/ConnectionManager.hpp"
12-
// #include "database/DbManager.hpp"
10+
#include "network/PredictionSystem.hpp"
1311

1412
#include "game/LogicCore.hpp"
1513
#include "game/PlayerManager.hpp"
1614
#include "game/InventorySystem.hpp"
1715
#include "game/LootTableManager.hpp"
18-
//#include "game/MobSystem.hpp"
1916
#include "game/SkillSystem.hpp"
2017
#include "game/QuestManager.hpp"
2118
#include "game/EntityManager.hpp"
@@ -109,6 +106,13 @@ class GameLogic : public LogicCore
109106
void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector<uint8_t>& data);
110107
void BroadcastToPlayers(const std::vector<uint64_t>& sessionIds, const nlohmann::json& message);
111108

109+
// Broadcast entity spawn to nearby players
110+
void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position,
111+
float yaw, const std::string& name);
112+
void SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity);
113+
void BroadcastPlayerState(uint64_t playerId, const ServerState& state);
114+
void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position);
115+
112116
private:
113117
GameLogic();
114118
~GameLogic();
@@ -125,6 +129,9 @@ class GameLogic : public LogicCore
125129
// Connection manager for broadcasting
126130
std::shared_ptr<ConnectionManager> connectionManager_;
127131

132+
std::unordered_map<uint64_t, PredictionSystem> playerPrediction_;
133+
std::mutex predictionMutex_;
134+
128135
// Thread functions
129136
void GameLoop();
130137
void SpawnerLoop();

include/game/Player.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@ class Player : public GameEntity {
329329
nlohmann::json JsonGetInventory() const;
330330
nlohmann::json ToJson() const;
331331

332+
// =============== Movement state getters/setters ===============
333+
bool IsOnGround() const { return onGround_; }
334+
void SetOnGround(bool g) { onGround_ = g; }
335+
332336
private:
333337
uint64_t id_;
334338
std::string username_;
@@ -362,6 +366,8 @@ class Player : public GameEntity {
362366
std::unordered_map<std::string, nlohmann::json> completed_quests_;
363367
std::vector<std::string> achievements_;
364368

369+
bool onGround_{true};
370+
365371
// Active buffs/debuffs
366372
struct ActiveBuff {
367373
std::string buff_id;

include/network/GameSession.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ class GameSession : public IConnection, public std::enable_shared_from_this<Game
315315
const glm::vec3& velocity, uint32_t last_input_id);
316316
void SendPositionCorrection(const glm::vec3& position, const glm::vec3& velocity);
317317

318+
void SetPlayerStateHandler(std::function<void(const ClientInput&)> handler);
319+
318320
private:
319321
// Core networking
320322
asio::ip::tcp::socket socket_;
@@ -399,6 +401,8 @@ class GameSession : public IConnection, public std::enable_shared_from_this<Game
399401
// Queue management
400402
size_t max_write_queue_size_{1000};
401403

404+
std::function<void(const ClientInput&)> player_state_handler_;
405+
402406
// Private methods
403407
void StartHeartbeat();
404408
void CheckHeartbeat();

src/game/GameLogic.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 ===============
414422
void 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) {
507520
void 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+
595683
void 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+
777878
void 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+
}

src/game/Player.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ race_(PlayerRace::HUMAN),
231231
status_(PlayerStatus::IDLE),
232232
inventory_system_(InventorySystem::GetInstance()),
233233
skill_system_(SkillSystem::GetInstance()),
234-
quest_manager_(QuestManager::GetInstance())
234+
quest_manager_(QuestManager::GetInstance()),
235+
onGround_(true)
235236
{
236237
ApplyRaceBonuses();
237238
ApplyClassBonuses();
@@ -250,7 +251,8 @@ race_(PlayerRace::HUMAN),
250251
status_(PlayerStatus::IDLE),
251252
inventory_system_(InventorySystem::GetInstance()),
252253
skill_system_(SkillSystem::GetInstance()),
253-
quest_manager_(QuestManager::GetInstance())
254+
quest_manager_(QuestManager::GetInstance()),
255+
onGround_(true)
254256
{
255257
ApplyRaceBonuses();
256258
ApplyClassBonuses();
@@ -270,7 +272,8 @@ race_(race),
270272
status_(PlayerStatus::IDLE),
271273
inventory_system_(InventorySystem::GetInstance()),
272274
skill_system_(SkillSystem::GetInstance()),
273-
quest_manager_(QuestManager::GetInstance())
275+
quest_manager_(QuestManager::GetInstance()),
276+
onGround_(true)
274277
{
275278
ApplyRaceBonuses();
276279
ApplyClassBonuses();

src/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool*
113113
auto session = std::make_shared<GameSession>(std::move(socket), sslCtx);
114114
Logger::Debug("Worker {} created new game session {}",
115115
workerId, session->GetSessionId());
116+
116117
// Message handler - simplified for demonstration
117118
session->SetMessageHandler([session, workerId, processPool](const nlohmann::json& msg) {
118119
try {
@@ -140,6 +141,13 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool*
140141
session->SendError("Internal server error", 500);
141142
}
142143
});
144+
145+
// --- Binary message handler ---
146+
session->SetDefaultBinaryMessageHandler([session, workerId](uint16_t type,
147+
const std::vector<uint8_t>& data) {
148+
GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data);
149+
});
150+
143151
session->SetCloseHandler([session, workerId]() { // Close handler
144152
Logger::Info("Worker {} session {} closing", workerId, session->GetSessionId());
145153
GameLogic::GetInstance().OnPlayerDisconnected(session->GetSessionId());

src/network/GameSession.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ void GameSession::StartProtocolNegotiation() {
101101
// Send protocol capabilities to client
102102
SendProtocolCapabilities();
103103

104+
// Setup default binary handlers
105+
SetupDefaultHandlers();
106+
104107
// Start reading messages
105108
DoBinaryRead();
106109

@@ -1608,3 +1611,24 @@ void GameSession::HandleFamiliarCommand(const nlohmann::json& data) {
16081611
handler_it->second(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, writer.GetBuffer());
16091612
}
16101613
}
1614+
1615+
void GameSession::SetPlayerStateHandler(std::function<void(const ClientInput&)> handler) {
1616+
player_state_handler_ = std::move(handler);
1617+
}
1618+
1619+
void GameSession::SetupDefaultHandlers() {
1620+
SetBinaryMessageHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE,
1621+
[this](uint16_t type, const std::vector<uint8_t>& data) {
1622+
(void)type;
1623+
try {
1624+
ClientInput input = ClientInput::Deserialize(data.data(), data.size());
1625+
if (player_state_handler_) {
1626+
player_state_handler_(input);
1627+
} else {
1628+
Logger::Warn("Session {}: no player state handler registered", sessionId_);
1629+
}
1630+
} catch (const std::exception& e) {
1631+
Logger::Error("Session {}: failed to deserialize player state: {}", sessionId_, e.what());
1632+
}
1633+
});
1634+
}

0 commit comments

Comments
 (0)