From 3b50d474dcb6700518abca625857f69f57777654 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:03:09 +0300 Subject: [PATCH 01/14] fixed GenerateAcceptKey (websocket protocol) --- build.sh | 1 - include/game/GameLogic.hpp | 1 + include/network/WebSocketProtocol.hpp | 9 +++++ src/game/GameLogic.cpp | 53 ++++++++++++++++----------- src/main.cpp | 2 +- src/network/WebSocketProtocol.cpp | 33 +++++++++++------ src/process/ProcessPool.cpp | 4 +- 7 files changed, 66 insertions(+), 37 deletions(-) diff --git a/build.sh b/build.sh index 6795121..f1d025a 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,4 @@ #!/bin/bash -# build.sh # Clone dependencies mkdir -p thirdparty diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 116a959..ecfe449 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -8,6 +8,7 @@ #include #include "network/PredictionSystem.hpp" +#include "network/WebSocketProtocol.hpp" #include "game/LogicCore.hpp" #include "game/PlayerManager.hpp" diff --git a/include/network/WebSocketProtocol.hpp b/include/network/WebSocketProtocol.hpp index ac76515..25ab1ab 100644 --- a/include/network/WebSocketProtocol.hpp +++ b/include/network/WebSocketProtocol.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,14 @@ namespace WebSocketProtocol { OP_PONG = 0xA }; + static const std::unordered_map IPCMessageTypes = { + {"welcome", 1}, + {"heartbeat", 2}, + {"broadcast", 3}, + {"shutdown", 4}, + {"reload_config", 5} + }; + // WebSocket frame structure struct WebSocketFrame { bool fin{true}; diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 7846ba5..96b0e89 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -1104,25 +1104,36 @@ void GameLogic::PerformMaintenance() { void GameLogic::HandleIPCMessage(const nlohmann::json& message) { try { std::string msgType = message.value("type", ""); - Logger::Debug("GameLogic handling IPC message type: {}", msgType); - - if (msgType == "welcome") { - Logger::Info("Received welcome message from master: {}", message.value("message", "")); - } else if (msgType == "heartbeat") { - // Handle heartbeat from master - int count = message.value("count", 0); - Logger::Debug("Received heartbeat #{} from master", count); - } else if (msgType == "broadcast") { - // Broadcast message to all players - if (message.contains("data")) { - BroadcastToAllPlayers(message["data"]); - } - } else if (msgType == "shutdown") { - Logger::Info("Received shutdown command from master"); - Shutdown(); - } else if (msgType == "reload_config") { - Logger::Info("Received config reload command from master"); - // Reload configuration if needed + auto it = WebSocketProtocol::IPCMessageTypes.find(msgType); + if (it == WebSocketProtocol::IPCMessageTypes.end()) { + Logger::Warn("Unknown IPC message type: {}", msgType); + return; + } + int typeCode = it->second; + switch (typeCode) { + case 1: // welcome + //Logger::Info("Received welcome message from master: {}", message.value("message", "")); + break; + case 2: // heartbeat + //int count = message.value("count", 0); + //Logger::Debug("Received heartbeat #{} from master", count); + break; + case 3: // broadcast + if (message.contains("data")) { + BroadcastToAllPlayers(message["data"]); + } + break; + case 4: // shutdown + Logger::Info("Received shutdown command from master"); + Shutdown(); + break; + case 5: // reload_config + Logger::Info("Received config reload command from master"); + // TODO: Reload configuration if needed + break; + default: + Logger::Warn("Unhandled IPC message code: {}", typeCode); + break; } } catch (const std::exception& e) { Logger::Error("Error handling IPC message: {}", e.what()); @@ -1144,7 +1155,7 @@ void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { auto sessions = connectionManager_->GetAllSessions(); if (sessions.empty()) { - Logger::Debug("No active sessions to broadcast to"); + //Logger::Debug("No active sessions to broadcast to"); return; } @@ -1180,7 +1191,7 @@ void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vec auto sessions = connectionManager_->GetAllSessions(); if (sessions.empty()) { - Logger::Debug("No active sessions to broadcast binary to"); + //Logger::Debug("No active sessions to broadcast binary to"); return; } diff --git a/src/main.cpp b/src/main.cpp index 9895816..e491203 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -219,7 +219,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* try { // Parse JSON message auto jsonMsg = nlohmann::json::parse(message); - Logger::Debug("Worker {} received IPC message: {}", workerId, jsonMsg.dump()); + //Logger::Debug("Worker {} received IPC message: {}", workerId, jsonMsg.dump()); // Handle IPC message in game logic gameLogic.HandleIPCMessage(jsonMsg); diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index d670e91..96414de 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -1176,31 +1176,40 @@ std::string GenerateWebSocketKey() { std::string GenerateAcceptKey(const std::string& key) { const std::string magic_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; std::string combined = key + magic_guid; - - unsigned char hash[SHA_DIGEST_LENGTH]; + unsigned char hash[SHA_DIGEST_LENGTH]; // 20 bytes SHA1(reinterpret_cast(combined.c_str()), combined.size(), hash); - // Base64 encode + // Base64 encode according to RFC 4648 const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string result; int i = 0; - while (i < SHA_DIGEST_LENGTH) { - uint32_t octet_a = i < SHA_DIGEST_LENGTH ? hash[i++] : 0; - uint32_t octet_b = i < SHA_DIGEST_LENGTH ? hash[i++] : 0; - uint32_t octet_c = i < SHA_DIGEST_LENGTH ? hash[i++] : 0; - - uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; + // Process full 3‑byte groups + while (i < SHA_DIGEST_LENGTH - 2) { + uint32_t triple = (hash[i] << 16) | (hash[i+1] << 8) | hash[i+2]; result += base64_chars[(triple >> 18) & 0x3F]; result += base64_chars[(triple >> 12) & 0x3F]; result += base64_chars[(triple >> 6) & 0x3F]; result += base64_chars[triple & 0x3F]; + i += 3; } - // Remove padding - result = result.substr(0, 28); + // Handle remaining bytes with correct padding + int remaining = SHA_DIGEST_LENGTH - i; + if (remaining == 1) { + uint32_t triple = (hash[i] << 16); + result += base64_chars[(triple >> 18) & 0x3F]; + result += base64_chars[(triple >> 12) & 0x3F]; + result += "=="; + } else if (remaining == 2) { + uint32_t triple = (hash[i] << 16) | (hash[i+1] << 8); + result += base64_chars[(triple >> 18) & 0x3F]; + result += base64_chars[(triple >> 12) & 0x3F]; + result += base64_chars[(triple >> 6) & 0x3F]; + result += "="; + } return result; } diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index 51de7fb..a76e351 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -466,7 +466,7 @@ bool ProcessPool::SendToWorker(int workerId, const std::string& message) { Logger::Error("Failed to send message content to worker {}", workerId); return false; } - Logger::Debug("Sent {} bytes to worker {}", message.length(), workerId); + //Logger::Debug("Sent {} bytes to worker {}", message.length(), workerId); return true; } @@ -500,7 +500,7 @@ std::string ProcessPool::ReceiveFromMaster() { } buffer[msg_len] = '\0'; std::string message(buffer.data(), msg_len); - Logger::Debug("Received {} bytes from master", msg_len); + //Logger::Debug("Received {} bytes from master", msg_len); return message; } From a017c85f39eee05efe944861951e23409aa44138 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:55:30 +0300 Subject: [PATCH 02/14] fix database schema, refactor protocols --- dbschema/postgres.sql | 10 +- dbschema/sqlite.sql | 12 +- include/game/GameLogic.hpp | 4 + include/game/Player.hpp | 5 +- include/game/PlayerManager.hpp | 4 +- include/process/ProcessPool.hpp | 26 +-- src/game/GameLogic.cpp | 291 +++++++++++++++++++----------- src/game/LogicCore.cpp | 2 +- src/game/Player.cpp | 63 +++---- src/game/PlayerManager.cpp | 68 ++----- src/main.cpp | 133 +++----------- src/network/GameServer.cpp | 3 + src/network/WebSocketProtocol.cpp | 244 ++----------------------- src/network/WebSocketSession.cpp | 3 +- src/process/ProcessPool.cpp | 54 ++---- 15 files changed, 329 insertions(+), 593 deletions(-) diff --git a/dbschema/postgres.sql b/dbschema/postgres.sql index 2f17f85..547bf50 100644 --- a/dbschema/postgres.sql +++ b/dbschema/postgres.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS players ( id BIGINT PRIMARY KEY, data JSONB NOT NULL, + password_hash TEXT NOT NULL, position_x REAL DEFAULT 0, position_y REAL DEFAULT 0, position_z REAL DEFAULT 0, @@ -79,8 +80,8 @@ CREATE TABLE IF NOT EXISTS schema_migrations ( ); -- [save_player_data] -INSERT INTO players (id, data, updated_at) VALUES ($1, $2, NOW()) -ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data, updated_at = NOW(); +INSERT INTO players (id, data, password_hash, updated_at) VALUES ($1, $2, $3, NOW()) +ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data, password_hash = EXCLUDED.password_hash, updated_at = NOW(); -- [load_player_data] SELECT data FROM players WHERE id = $1; @@ -97,6 +98,9 @@ SELECT level, experience, health, max_health, mana, max_mana, currency_gold, cur -- [get_player] SELECT * FROM players WHERE id = $1; +-- [get_player_by_username] +SELECT id, password_hash FROM players WHERE data->>'username' = $1; + -- [save_game_state] INSERT INTO game_state (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW(); @@ -159,4 +163,4 @@ DELETE FROM schema_migrations WHERE version = $1; CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- [disable_pg_stat_statements] -DROP EXTENSION IF EXISTS pg_stat_statements; \ No newline at end of file +DROP EXTENSION IF EXISTS pg_stat_statements; diff --git a/dbschema/sqlite.sql b/dbschema/sqlite.sql index 11f57d1..ef60a2d 100644 --- a/dbschema/sqlite.sql +++ b/dbschema/sqlite.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS players ( id INTEGER PRIMARY KEY, data TEXT NOT NULL, + password_hash TEXT NOT NULL, position_x REAL DEFAULT 0, position_y REAL DEFAULT 0, position_z REAL DEFAULT 0, @@ -81,7 +82,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations ( ); -- [save_player_data] -INSERT OR REPLACE INTO players (id, data, updated_at) VALUES (?, ?, datetime('now')); +INSERT OR REPLACE INTO players (id, data, password_hash, updated_at) VALUES (?, ?, ?, datetime('now')); -- [load_player_data] SELECT data FROM players WHERE id = ?; @@ -95,13 +96,12 @@ SELECT 1 FROM players WHERE id = ? LIMIT 1; -- [get_player_stats] SELECT level, experience, health, max_health, mana, max_mana, currency_gold, currency_gems, total_playtime FROM players WHERE id = ?; --- [update_player_stats] (generic update, we'll build the SET part in code, but we can use a parameterized query; here we provide a template) --- This is a special case; we'll keep the SQL building in C++ for flexibility. --- For now, we leave it out; the client will continue to build the UPDATE dynamically. - -- [get_player] SELECT * FROM players WHERE id = ?; +-- [get_player_by_username] +SELECT id, password_hash FROM players WHERE json_extract(data, '$.username') = ?; + -- [save_game_state] INSERT OR REPLACE INTO game_state (key, value, updated_at) VALUES (?, ?, datetime('now')); @@ -154,4 +154,4 @@ ROLLBACK; SELECT MAX(version) as current_version FROM schema_migrations; -- [delete_migration] -DELETE FROM schema_migrations WHERE version = ?; \ No newline at end of file +DELETE FROM schema_migrations WHERE version = ?; diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index ecfe449..5118fc0 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -83,6 +83,7 @@ class GameLogic : public LogicCore // World message handlers void HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json& data); + void HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data); void HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::json& data); void HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& data); void HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& data); @@ -116,6 +117,9 @@ class GameLogic : public LogicCore void BroadcastPlayerState(uint64_t playerId, const ServerState& state); void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); + void HandleAuthentication(uint64_t sessionId, const std::vector& data); + void HandleAuthentication(uint64_t sessionId, const std::string& username, const std::string& password); + private: GameLogic(); ~GameLogic(); diff --git a/include/game/Player.hpp b/include/game/Player.hpp index ed081a7..3864172 100644 --- a/include/game/Player.hpp +++ b/include/game/Player.hpp @@ -16,7 +16,7 @@ #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" -//#include "database/DbManager.hpp" +#include "utils/Passwords.hpp" #include "game/GameEntity.hpp" #include "game/InventorySystem.hpp" @@ -152,7 +152,7 @@ struct PlayerSettings { class Player : public GameEntity { public: - Player(uint64_t id, const std::string& username); + Player(uint64_t id, const std::string& username, const std::string& password=""); Player(const glm::vec3& position); Player(const glm::vec3& position, PlayerClass player_class, PlayerRace race); virtual ~Player(); @@ -336,6 +336,7 @@ class Player : public GameEntity { private: uint64_t id_; std::string username_; + std::string password_hash_; bool onGround_{true}; diff --git a/include/game/PlayerManager.hpp b/include/game/PlayerManager.hpp index 8799029..cd397ee 100644 --- a/include/game/PlayerManager.hpp +++ b/include/game/PlayerManager.hpp @@ -54,13 +54,13 @@ class PlayerManager { public: static PlayerManager& GetInstance(); - std::shared_ptr CreatePlayer(const std::string& username); + std::shared_ptr CreatePlayer(const std::string& username, const std::string& password=""); std::shared_ptr GetPlayer(int64_t playerId); std::shared_ptr GetPlayerBySession(uint64_t sessionId); std::shared_ptr GetPlayerByUsername(const std::string& username); uint64_t GetSessionIdByPlayerId(int64_t playerId) const; - bool AuthenticatePlayer(const std::string& username, const std::string& password); + bool AuthenticatePlayer(const std::string& username, const std::string& password=""); void PlayerConnected(uint64_t sessionId, int64_t playerId); void PlayerDisconnected(uint64_t sessionId); diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index 32c81d6..85646ea 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,6 @@ class ProcessPool { WORKER }; - // New constructor: takes a list of worker groups ProcessPool(const std::vector& groups); ~ProcessPool(); @@ -51,19 +51,15 @@ class ProcessPool { pid_t GetMasterPid() const { return masterPid_; } int GetTotalWorkerCount() const { return totalWorkers_; } - // Callback for worker process – now receives both global ID and group config using WorkerMainFunc = std::function; void SetWorkerMain(WorkerMainFunc workerMainFunc); - // Inter-process communication with message length prefix bool SendToWorker(int workerId, const std::string& message); std::string ReceiveFromMaster(); - // Process health monitoring bool IsWorkerAlive(int workerId) const; void RestartWorker(int workerId); - // Configuration methods void SetMaxMessageSize(uint32_t maxSize) { maxMessageSize_ = maxSize; } uint32_t GetMaxMessageSize() const { return maxMessageSize_; } void SetReceiveTimeout(uint32_t timeoutMs) { receiveTimeoutMs_ = timeoutMs; } @@ -75,8 +71,8 @@ class ProcessPool { private: struct WorkerInfo { pid_t pid; - int groupIdx; // index in groups_ vector - int localWorkerId; // index within the group (0..group.count-1) + int groupIdx; + int localWorkerId; WorkerGroupConfig config; }; @@ -87,34 +83,30 @@ class ProcessPool { void CloseAllPipes(); void CreateWorkerPipe(int globalWorkerId); - // Helper functions for message protocol bool WriteAll(int fd, const void* buffer, size_t count); bool ReadAll(int fd, void* buffer, size_t count, bool nonBlocking = false); bool DrainPipe(int fd, size_t bytesToDrain); - // Group configuration std::vector groups_; int totalWorkers_; ProcessRole role_{ProcessRole::MASTER}; - int workerId_{-1}; // global ID (for worker) - WorkerGroupConfig groupConfig_; // config for this worker (filled after fork) + int workerId_{-1}; + WorkerGroupConfig groupConfig_; pid_t masterPid_{-1}; std::thread masterThread_; std::atomic running_{false}; std::atomic shutdownRequested_{false}; - std::vector workers_; // indexed by global worker ID - std::vector workerPipes_; // [read_fd, write_fd] for each global worker + std::vector workers_; + std::vector workerPipes_; WorkerMainFunc workerMainFunc_; - // Health monitoring (keyed by global worker ID) mutable std::mutex healthMutex_; std::unordered_map> workerHealth_; - // Message protocol configuration - uint32_t maxMessageSize_{1024 * 1024}; // 1MB default - uint32_t receiveTimeoutMs_{1000}; // 1 second default + uint32_t maxMessageSize_{1024 * 1024}; + uint32_t receiveTimeoutMs_{1000}; }; diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 96b0e89..6406977 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -1,10 +1,9 @@ #include "game/GameLogic.hpp" -// =============== Static Members =============== + std::mutex GameLogic::instanceMutex_; GameLogic* GameLogic::instance_ = nullptr; -// =============== Singleton Access =============== GameLogic& GameLogic::GetInstance() { std::lock_guard lock(instanceMutex_); if (!instance_) { @@ -13,9 +12,7 @@ GameLogic& GameLogic::GetInstance() { return *instance_; } -// =============== Constructor and Destructor =============== GameLogic::GameLogic() -// : playerManager_(PlayerManager::GetInstance()) { Logger::Debug("GameLogic created"); } @@ -26,7 +23,6 @@ GameLogic::~GameLogic() { } } -// =============== Initialization and Shutdown =============== void GameLogic::Initialize() { if (instance_) { Logger::Warn("GameLogic already initialized"); @@ -37,7 +33,6 @@ void GameLogic::Initialize() { auto& config = ConfigManager::GetInstance(); - // Initialize world configuration WorldConfig worldConfig; worldConfig.seed = config.GetInt("world.seed", 12345); worldConfig.viewDistance = config.GetInt("world.view_distance", 4); @@ -50,16 +45,21 @@ void GameLogic::Initialize() { SetWorldConfig(worldConfig); - // Initialize component systems LogicWorld::GetInstance().Initialize(worldConfig); + + int preloadRadius = 10; + for (int x = -preloadRadius; x <= preloadRadius; ++x) { + for (int z = -preloadRadius; z <= preloadRadius; ++z) { + GetOrCreateChunk(x, z); + } + } + LogicEntity::GetInstance().Initialize(); - // Load game data if (!LoadGameData()) { Logger::Error("Failed to load game data"); } - // Register handlers RegisterWorldHandlers(); Logger::Info("GameLogic world system initialized successfully"); @@ -69,17 +69,12 @@ void GameLogic::Shutdown() { if (!instance_) { return; } - Logger::Info("Shutting down GameLogic world system..."); - - // Shutdown component systems LogicEntity::GetInstance().Shutdown(); LogicWorld::GetInstance().Shutdown(); - Logger::Info("GameLogic world system shutdown complete"); } -// =============== World Configuration =============== void GameLogic::SetWorldConfig(const WorldConfig& config) { LogicWorld::GetInstance().SetConfig(config); } @@ -88,15 +83,12 @@ const GameLogic::WorldConfig& GameLogic::GetWorldConfig() const { return static_cast(LogicWorld::GetInstance().GetConfig()); } -// =============== World Methods =============== std::shared_ptr GameLogic::GetOrCreateChunk(int chunkX, int chunkZ) { return LogicWorld::GetInstance().GetOrCreateChunk(chunkX, chunkZ); } void GameLogic::GenerateWorldAroundPlayer(uint64_t playerId, const glm::vec3& position) { LogicWorld::GetInstance().GenerateWorldAroundPlayer(position, GetWorldConfig().viewDistance); - - // Sync entities to player SyncNearbyEntitiesToPlayer(GetSessionIdByPlayer(playerId), position); } @@ -112,7 +104,6 @@ BiomeType GameLogic::GetBiomeAt(float x, float z) const { return LogicWorld::GetInstance().GetBiomeAt(x, z); } -// =============== Entity Methods =============== uint64_t GameLogic::SpawnNPC(NPCType type, const glm::vec3& position, uint64_t ownerId) { return LogicEntity::GetInstance().SpawnNPC(type, position, ownerId); } @@ -133,7 +124,6 @@ std::shared_ptr GameLogic::GetPlayer(uint64_t playerId) { return PlayerManager::GetInstance().GetPlayer(playerId); } -// =============== Collision Methods =============== CollisionResult GameLogic::CheckCollision(const glm::vec3& position, float radius, uint64_t excludeEntityId) { return LogicEntity::GetInstance().CheckCollision(position, radius, excludeEntityId); } @@ -142,9 +132,7 @@ bool GameLogic::Raycast(const glm::vec3& origin, const glm::vec3& direction, flo return LogicEntity::GetInstance().Raycast(origin, direction, maxDistance, hit); } -// =============== Loot Methods =============== void GameLogic::CreateLootEntity(const glm::vec3& position, std::shared_ptr item, int quantity) { - // If no specific item is given, generate a random one from a default loot table if (!item) { auto& lootTables = LootTableManager::GetInstance(); auto loot = lootTables.GenerateLoot("default_loot_table"); // configurable @@ -183,10 +171,8 @@ void GameLogic::HandleLootPickup(uint64_t sessionId, const nlohmann::json& data) SendError(sessionId, "Too far to loot"); return; } - // Add to player's inventory auto& inv = InventorySystem::GetInstance(); if (inv.AddItem(playerId, LootItem(lootId, lootEntity->GetName()), quantity)) { - // Destroy the loot entity EntityManager::GetInstance().DestroyEntity(lootId); SendSuccess(sessionId, "Loot collected"); FirePythonEvent("loot_pickup", { @@ -209,12 +195,10 @@ void GameLogic::HandleInventoryMove(uint64_t sessionId, const nlohmann::json& da uint64_t playerId = GetPlayerIdBySession(sessionId); int fromSlot = data.value("fromSlot", -1); int toSlot = data.value("toSlot", -1); - if (fromSlot < 0 || toSlot < 0) { SendError(sessionId, "Invalid slot indices"); return; } - auto& inv = InventorySystem::GetInstance(); if (inv.MoveItem(playerId, fromSlot, toSlot)) { nlohmann::json response = { @@ -239,7 +223,6 @@ void GameLogic::HandleItemUse(uint64_t sessionId, const nlohmann::json& data) { uint64_t playerId = GetPlayerIdBySession(sessionId); int slot = data.value("slot", -1); uint64_t itemId = data.value("itemId", 0); - auto& inv = InventorySystem::GetInstance(); std::shared_ptr item; if (slot >= 0) { @@ -255,18 +238,12 @@ void GameLogic::HandleItemUse(uint64_t sessionId, const nlohmann::json& data) { } } } - if (!item) { SendError(sessionId, "Item not found"); return; } - - // Use the item (e.g., consume, apply effect) - // For now, assume consumable and remove one if (item->IsConsumable()) { if (inv.RemoveItem(playerId, itemId, 1)) { - // Trigger any game effect (skill, buff, etc.) - // Could integrate with SkillSystem if item grants a skill SendSuccess(sessionId, "Item used"); FirePythonEvent("item_used", { {"playerId", playerId}, @@ -277,7 +254,6 @@ void GameLogic::HandleItemUse(uint64_t sessionId, const nlohmann::json& data) { SendError(sessionId, "Failed to use item"); } } else { - // Non-consumable items might be equipped or activated SendError(sessionId, "Item cannot be used this way"); } } catch (const std::exception& e) { @@ -291,22 +267,17 @@ void GameLogic::HandleItemDrop(uint64_t sessionId, const nlohmann::json& data) { uint64_t playerId = GetPlayerIdBySession(sessionId); int slot = data.value("slot", -1); int quantity = data.value("quantity", 1); - if (slot < 0) { SendError(sessionId, "Invalid slot"); return; } - auto& inv = InventorySystem::GetInstance(); auto item = inv.GetItem(playerId, slot); if (!item) { SendError(sessionId, "No item in that slot"); return; } - - // Remove from inventory if (inv.RemoveItem(playerId, item->GetId(), quantity)) { - // Create loot entity at player's feet auto player = GetPlayer(playerId); if (player) { CreateLootEntity(player->GetPosition(), item, quantity); @@ -326,15 +297,11 @@ void GameLogic::HandleTradeRequest(uint64_t sessionId, const nlohmann::json& dat uint64_t playerId = GetPlayerIdBySession(sessionId); uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); std::string action = data.value("action", "request"); // request, accept, decline, cancel - if (targetPlayerId == 0) { SendError(sessionId, "Invalid target player"); return; } - - // Simple trade state machine (simplified) if (action == "request") { - // Send trade request to target player uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); if (targetSession == 0) { SendError(sessionId, "Target player not online"); @@ -349,7 +316,6 @@ void GameLogic::HandleTradeRequest(uint64_t sessionId, const nlohmann::json& dat SendToSession(targetSession, request); SendSuccess(sessionId, "Trade request sent"); } else if (action == "accept") { - // Start trade session uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); nlohmann::json acceptMsg = { {"type", "trade_start"}, @@ -374,12 +340,10 @@ void GameLogic::HandleGoldTransaction(uint64_t sessionId, const nlohmann::json& uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); int64_t amount = data.value("amount", 0); std::string type = data.value("type", "transfer"); // transfer, gift, etc. - if (targetPlayerId == 0 || amount <= 0) { SendError(sessionId, "Invalid target or amount"); return; } - auto& inv = InventorySystem::GetInstance(); if (type == "transfer") { if (inv.GetGold(playerId) < amount) { @@ -418,15 +382,25 @@ void GameLogic::SendPositionCorrection(uint64_t sessionId, const glm::vec3& posi SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer()); } -// =============== World Message Handlers =============== void GameLogic::RegisterWorldHandlers() { + RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, + [this](uint64_t sessionId, uint16_t /*type*/, const std::vector& data) { + HandleAuthentication(sessionId, data); + }); + + RegisterHandler("authentication", [this](uint64_t sessionId, const nlohmann::json& data) { + std::string username = data.value("login", ""); + std::string password = data.value("password", ""); + HandleAuthentication(sessionId, username, password); + }); + RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, [this](uint64_t sessionId, uint16_t /*messageType*/, const std::vector& data) { HandlePlayerState(sessionId, data); }); RegisterHandler("world_chunk_request", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleWorldChunkRequest(sessionId, data); + HandleWorldChunkRequestJson(sessionId, data); }); RegisterHandler("player_position_update", [this](uint64_t sessionId, const nlohmann::json& data) { @@ -449,7 +423,6 @@ void GameLogic::RegisterWorldHandlers() { HandleEntitySpawnRequest(sessionId, data); }); - // Loot handlers RegisterHandler("loot_pickup", [this](uint64_t sessionId, const nlohmann::json& data) { HandleLootPickup(sessionId, data); }); @@ -469,7 +442,6 @@ void GameLogic::RegisterWorldHandlers() { HandleGoldTransaction(sessionId, data); }); - // Binary handlers RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, [this](uint64_t sessionId, uint16_t /*messageType*/, const std::vector& data) { BinaryProtocol::BinaryReader reader(data.data(), data.size()); @@ -500,12 +472,47 @@ void GameLogic::RegisterWorldHandlers() { } void GameLogic::HandleMessage(uint64_t sessionId, const nlohmann::json& message) { - Logger::Debug("GameLogic handling message from session {}", sessionId); + Logger::Debug("GameLogic handling from session: {}; message: {}", sessionId, message.dump()); FirePythonEvent("game_message", { {"sessionId", sessionId}, {"message", message} }); - LogicCore::HandleMessage(sessionId, message); + + std::string type = message.value("type", ""); + + // Handle known message types directly + if (type == "world_chunk_request") { + HandleWorldChunkRequestJson(sessionId, message); + } else if (type == "authentication") { + std::string username = message.value("login", ""); + std::string password = message.value("password", ""); + HandleAuthentication(sessionId, username, password); + } else if (type == "player_position_update") { + HandlePlayerPositionUpdate(sessionId, message); + } else if (type == "npc_interaction") { + HandleNPCInteraction(sessionId, message); + } else if (type == "collision_check") { + HandleCollisionCheck(sessionId, message); + } else if (type == "familiar_command") { + HandleFamiliarCommand(sessionId, message); + } else if (type == "entity_spawn_request") { + HandleEntitySpawnRequest(sessionId, message); + } else if (type == "loot_pickup") { + HandleLootPickup(sessionId, message); + } else if (type == "inventory_move") { + HandleInventoryMove(sessionId, message); + } else if (type == "item_use") { + HandleItemUse(sessionId, message); + } else if (type == "item_drop") { + HandleItemDrop(sessionId, message); + } else if (type == "trade_request") { + HandleTradeRequest(sessionId, message); + } else if (type == "gold_transaction") { + HandleGoldTransaction(sessionId, message); + } else { + // Fallback to base class for unknown types + LogicCore::HandleMessage(sessionId, message); + } } void GameLogic::OnPlayerConnected(uint64_t sessionId, uint64_t playerId) { @@ -518,7 +525,6 @@ void GameLogic::OnPlayerConnected(uint64_t sessionId, uint64_t playerId) { } void GameLogic::OnPlayerDisconnected(uint64_t sessionId) { - // Capture player ID before base class removes the mapping uint64_t playerId = GetPlayerIdBySession(sessionId); { std::lock_guard lock(predictionMutex_); @@ -537,64 +543,82 @@ void GameLogic::HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json int chunkX = data.value("chunkX", 0); int chunkZ = data.value("chunkZ", 0); int lod = data.value("lod", 0); - Logger::Debug("World chunk request: [{}, {}] LOD: {}", chunkX, chunkZ, lod); - auto chunk = GetOrCreateChunk(chunkX, chunkZ); if (!chunk) { SendError(sessionId, "Failed to generate chunk", 404); return; } - BinaryProtocol::BinaryWriter writer; writer.WriteInt32(chunkX); writer.WriteInt32(chunkZ); writer.WriteUInt8(static_cast(lod)); writer.WriteJson(chunk->Serialize()); writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); - } catch (const std::exception& e) { Logger::Error("Error handling world chunk request: {}", e.what()); SendError(sessionId, "Failed to process chunk request", 500); } } +void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data) { + try { + Logger::Debug("JSON chunk request received: {}", data.dump()); + int chunkX = data.value("chunkX", 0); + int chunkZ = data.value("chunkZ", 0); + int lod = data.value("lod", 0); + Logger::Debug("JSON world chunk request: [{}, {}] LOD: {}", chunkX, chunkZ, lod); + auto start = std::chrono::steady_clock::now(); + auto chunk = GetOrCreateChunk(chunkX, chunkZ); + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + Logger::Debug("Chunk ({},{}) generated in {} ms", chunkX, chunkZ, elapsed.count()); + if (!chunk) { + SendError(sessionId, "Failed to generate chunk", 404); + return; + } + nlohmann::json response = { + {"type", "world_chunk"}, + {"chunkX", chunkX}, + {"chunkZ", chunkZ}, + {"lod", lod}, + {"data", chunk->Serialize()}, + {"timestamp", GetCurrentTimestamp()} + }; + SendToSession(sessionId, response); + Logger::Debug("Chunk response sent for ({},{})", chunkX, chunkZ); + } catch (const std::exception& e) { + Logger::Error("Exception in HandleWorldChunkRequestJson: {}", e.what()); + SendError(sessionId, "Internal server error", 500); + } +} + void GameLogic::HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::json& data) { try { float x = data.value("x", 0.0f); float y = data.value("y", 0.0f); float z = data.value("z", 0.0f); glm::vec3 position(x, y, z); - uint64_t playerId = GetPlayerIdBySession(sessionId); if (playerId == 0) { return; } - std::shared_ptr player = GetPlayer(playerId); if (player) { float collisionRadius = 0.5f; CollisionResult collision = CheckCollision(position, collisionRadius, playerId); - if (collision.collided) { position += collision.resolution; } - player->SetPosition(position); GenerateWorldAroundPlayer(playerId, position); - - // Broadcast position BinaryProtocol::BinaryWriter positionWriter; positionWriter.WriteUInt64(playerId); positionWriter.WriteVector3(position); positionWriter.WriteVector3(glm::vec3(0, 0, 0)); positionWriter.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, positionWriter.GetBuffer(), 100.0f); - FirePythonEvent("player_move_3d", { {"player_id", playerId}, {"x", x}, @@ -603,7 +627,6 @@ void GameLogic::HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::j {"session_id", sessionId} }); } - } catch (const std::exception& e) { Logger::Error("Error handling player position update: {}", e.what()); } @@ -611,36 +634,25 @@ void GameLogic::HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::j void GameLogic::HandlePlayerState(uint64_t sessionId, const std::vector& data) { try { - // Deserialize client input ClientInput input = ClientInput::Deserialize(data.data(), data.size()); if (!input.IsValid()) { Logger::Warn("Invalid client input from session {}", sessionId); return; } - uint64_t playerId = GetPlayerIdBySession(sessionId); if (playerId == 0) { Logger::Warn("No player for session {}", sessionId); return; } - - // Store input in the player's prediction system { std::lock_guard lock(predictionMutex_); playerPrediction_[playerId].StoreClientInput(input); } - - // Get authoritative player state auto player = GetPlayer(playerId); if (!player) return; - - // Simulate movement using the prediction system - // (We'll compute the authoritative state from the last confirmed state plus unprocessed inputs) PredictionSystem* pred = &playerPrediction_[playerId]; auto currentTime = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); - - // Get the last confirmed state from prediction system (or from player) ServerState authState; authState.last_processed_input = 0; // TODO: track last processed input authState.timestamp = currentTime; @@ -648,33 +660,20 @@ void GameLogic::HandlePlayerState(uint64_t sessionId, const std::vector authState.velocity = player->GetVelocity(); // assuming Player has velocity authState.rotation = player->GetRotation(); // assuming Player has rotation authState.on_ground = player->IsOnGround(); // assuming Player has onGround - - // Simulate with unprocessed inputs auto unprocessed = pred->GetUnprocessedInputs(authState.last_processed_input); if (!unprocessed.empty()) { float deltaTime = 1.0f / 30.0f; // or compute based on time difference authState = pred->SimulateMovement(authState, unprocessed, deltaTime); } - - // Update authoritative player state player->SetPosition(authState.position); player->SetVelocity(authState.velocity); player->SetRotation(authState.rotation); player->SetOnGround(authState.on_ground); - - // Broadcast authoritative state to nearby players (optional) BroadcastPlayerState(playerId, authState); - - // Check if client needs a correction (compare with client's last reported position) - // We need to store the client's last reported position; for simplicity we can compare - // with the position that the client sent (which is not directly available here, but we - // could pass it in the input). For now, we'll just send a correction if the simulation - // moved significantly from the last confirmed state. static float correctionThreshold = 0.5f; // 0.5 meters if (glm::distance(authState.position, player->GetPosition()) > correctionThreshold) { SendPositionCorrection(sessionId, authState.position, authState.velocity); } - } catch (const std::exception& e) { Logger::Error("HandlePlayerState error: {}", e.what()); } @@ -684,41 +683,33 @@ void GameLogic::HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& d try { uint64_t npcId = data.value("npcId", 0ULL); std::string interactionType = data.value("interaction", ""); - if (npcId == 0 || interactionType.empty()) { SendError(sessionId, "Invalid NPC interaction", 400); return; } - NPCEntity* npc = GetNPCEntity(npcId); if (!npc) { SendError(sessionId, "NPC not found", 404); return; } - uint64_t playerId = GetPlayerIdBySession(sessionId); std::shared_ptr player = GetPlayer(playerId); if (!player) { SendError(sessionId, "Player not found", 404); return; } - float distance = glm::distance(player->GetPosition(), npc->GetPosition()); if (distance > 15.0f) { SendError(sessionId, "Too far from NPC", 400); return; } - if (interactionType == "attack") { float damage = 10.0f; npc->TakeDamage(damage, playerId); - bool isDead = npc->IsDead(); if (isDead) { - // Handle mob death via MobSystem MobSystem::GetInstance().OnMobDeath(npcId, playerId); } - BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(playerId); writer.WriteUInt64(npcId); @@ -726,10 +717,8 @@ void GameLogic::HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& d writer.WriteFloat(npc->GetStats().health); writer.WriteUInt8(isDead ? 1 : 0); writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_COMBAT_EVENT, writer.GetBuffer()); } else if (interactionType == "talk") { - // Could trigger quest dialogue via QuestManager auto& questMgr = QuestManager::GetInstance(); auto quests = questMgr.GetQuestsFromNPC(playerId, npc->GetId()); nlohmann::json response = { @@ -740,7 +729,6 @@ void GameLogic::HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& d }; SendToSession(sessionId, response); } - } catch (const std::exception& e) { Logger::Error("Error handling NPC interaction: {}", e.what()); SendError(sessionId, "Failed to process NPC interaction", 500); @@ -1248,7 +1236,6 @@ void GameLogic::BroadcastToPlayers(const std::vector& sessionIds, cons } void GameLogic::BroadcastPlayerState(uint64_t playerId, const ServerState& state) { - // Create a binary update message (ENTITY_UPDATE or PLAYER_STATE) for other players BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(playerId); writer.WriteVector3(state.position); @@ -1262,6 +1249,104 @@ void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& posit BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); writer.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); } + +void GameLogic::HandleAuthentication(uint64_t sessionId, const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + std::string username = reader.ReadString(); + std::string password = reader.ReadString(); + auto& pm = PlayerManager::GetInstance(); + bool authenticated = false; + std::string message; + if (pm.PlayerExists(username)) { + if (password.empty() || pm.AuthenticatePlayer(username, password)) { + authenticated = true; + message = "Welcome back, " + username; + } else { + message = "Invalid password"; + } + } else { + if (password.empty()) { + auto player = pm.CreatePlayer(username); + if (player) { + authenticated = true; + message = "Welcome, " + username; + } else { + message = "Failed to create player"; + } + } else { + message = "Player does not exist"; + } + } + if (authenticated) { + auto player = pm.GetPlayerByUsername(username); + if (player) { + pm.PlayerConnected(sessionId, player->GetId()); + auto session = connectionManager_->GetSession(sessionId); + if (session) { + session->SetPlayerId(player->GetId()); + session->Authenticate(password); + } + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt8(1); + writer.WriteString(message); + SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); + } else { + authenticated = false; + message = "Internal error"; + } + } + if (!authenticated) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt8(0); + writer.WriteString(message); + SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); + } +} + +void GameLogic::HandleAuthentication(uint64_t sessionId, const std::string& username, const std::string& password) { + bool authenticated = false; + std::string message; + auto& pm = PlayerManager::GetInstance(); + if (pm.PlayerExists(username)) { + if (password.empty() || pm.AuthenticatePlayer(username, password)) { + authenticated = true; + message = "Welcome back, " + username; + } else { + message = "Invalid password"; + } + } else { + if (password.empty()) { + auto player = pm.CreatePlayer(username); + if (player) { + authenticated = true; + message = "Welcome, " + username; + } else { + message = "Failed to create player"; + } + } else { + message = "Player does not exist"; + } + } + if (authenticated) { + auto player = pm.GetPlayerByUsername(username); + if (player) { + pm.PlayerConnected(sessionId, player->GetId()); + auto session = connectionManager_->GetSession(sessionId); + if (session) { + session->SetPlayerId(player->GetId()); + session->Authenticate(password); + } + } else { + authenticated = false; + message = "Internal error"; + } + } + nlohmann::json response = { + {"type", "authentication_response"}, + {"success", authenticated}, + {"message", message} + }; + SendToSession(sessionId, response); +} diff --git a/src/game/LogicCore.cpp b/src/game/LogicCore.cpp index f11fbde..921119e 100644 --- a/src/game/LogicCore.cpp +++ b/src/game/LogicCore.cpp @@ -700,5 +700,5 @@ void LogicCore::SaveGameState() { } void LogicCore::CleanupOldData() { - Logger::Debug("Cleaning up old data"); + //Logger::Debug("Cleaning up old data"); } diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 544ffb0..91cb58e 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -221,10 +221,11 @@ void PlayerSettings::Deserialize(const nlohmann::json& data) { // =============== Player Implementation =============== -Player::Player(uint64_t id, const std::string& username) +Player::Player(uint64_t id, const std::string& username, const std::string& password) : GameEntity(EntityType::PLAYER, glm::vec3(0.0f, 0.0f, 0.0f)), id_(id), username_(username), +password_hash_(Passwords::HashPassword(password)), onGround_(true), last_movement_(std::chrono::system_clock::now()), player_class_(PlayerClass::WARRIOR), @@ -245,6 +246,7 @@ Player::Player(const glm::vec3& position) : GameEntity(EntityType::PLAYER, position), id_(0), username_(""), +password_hash_(""), onGround_(true), last_movement_(std::chrono::system_clock::now()), player_class_(PlayerClass::WARRIOR), @@ -266,6 +268,7 @@ Player::Player(const glm::vec3& position, PlayerClass player_class, PlayerRace r : GameEntity(EntityType::PLAYER, position), id_(0), username_(""), +password_hash_(""), onGround_(true), last_movement_(std::chrono::system_clock::now()), player_class_(player_class), @@ -509,17 +512,6 @@ void Player::CalculateExperienceToNextLevel() { } // =============== Level Up Handler =============== -// void Player::OnLevelUp() { -// SetMaxHealth(max_health_ + 20); -// SetMaxMana(max_mana_ + 15); -// SetHealth(max_health_); // Full heal on level up -// SetMana(max_mana_); // Full mana on level up -// attributes_["strength"] = attributes_.value("strength", 10) + 1; -// attributes_["vitality"] = attributes_.value("vitality", 10) + 1; -// AddAchievement("level_" + std::to_string(level_)); -// Logger::Info("Player {} leveled up to level {}", id_, level_); -// } - void Player::OnLevelUp() { // Increase stats stats_.max_health += 20; @@ -1263,21 +1255,17 @@ bool Player::SaveToDatabase() { Logger::Error("No database backend available for player {}", GetId()); return false; } - - // Serialize player data to JSON - nlohmann::json player_data = Serialize(); - std::string player_json = player_data.dump(); - std::string escaped = backend->EscapeString(player_json); - - int shardId = dbManager.GetShardId(GetId()); - std::string query = - "INSERT INTO player_data (player_id, data) " - "VALUES (" + std::to_string(GetId()) + ", '" + escaped + "') " - "ON CONFLICT (player_id) DO UPDATE SET " - "data = EXCLUDED.data, " - "last_updated = NOW()"; - - bool success = backend->ExecuteShard(shardId, query); + nlohmann::json data = Serialize(); + data["username"] = username_; + std::string data_json = data.dump(); + std::string sql = dbManager.GetSQLProvider().GetQuery("save_player_data"); + if (sql.empty()) { + Logger::Error("Missing SQL: save_player_data"); + return false; + } + bool success = backend->ExecuteWithParams(sql, { + std::to_string(GetId()), data_json, password_hash_ + }); if (success) { Logger::Debug("Player {} data saved to database", GetId()); } else { @@ -1298,22 +1286,19 @@ bool Player::LoadFromDatabase() { Logger::Error("No database backend available for player {}", GetId()); return false; } - - int shardId = dbManager.GetShardId(GetId()); - std::string query = - "SELECT data FROM player_data WHERE player_id = " + - std::to_string(GetId()); - - nlohmann::json result = backend->QueryShard(shardId, query); - + std::string sql = dbManager.GetSQLProvider().GetQuery("load_player_data"); + if (sql.empty()) { + Logger::Error("Missing SQL: load_player_data"); + return false; + } + auto result = backend->QueryWithParams(sql, { std::to_string(GetId()) }); if (!result.empty() && result[0].contains("data")) { - std::string player_json = result[0]["data"]; - nlohmann::json data = nlohmann::json::parse(player_json); - Deserialize(data); + std::string data = result[0]["data"]; + nlohmann::json data_json = nlohmann::json::parse(data); + Deserialize(data_json); Logger::Debug("Player {} data loaded from database", GetId()); return true; } - Logger::Warn("No data found for player {}", GetId()); return false; } catch (const std::exception& e) { diff --git a/src/game/PlayerManager.cpp b/src/game/PlayerManager.cpp index b947767..7f36275 100644 --- a/src/game/PlayerManager.cpp +++ b/src/game/PlayerManager.cpp @@ -1,7 +1,7 @@ #include "game/PlayerManager.hpp" PlayerManager& PlayerManager::GetInstance() { - static PlayerManager instance; // Meyer's singleton – no static mutex needed + static PlayerManager instance; return instance; } @@ -14,7 +14,6 @@ PlayerManager::PlayerManager() Logger::Info("PlayerManager initialized"); - // Start background threads using RAIIThread saveThread_ = RAIIThread([this]() { SaveLoop(); }); cleanupThread_ = RAIIThread([this]() { CleanupLoop(); }); } @@ -40,13 +39,11 @@ void PlayerManager::Shutdown() { Logger::Info("PlayerManager shutdown complete"); } -// =============== Player Creation / Retrieval =============== - -std::shared_ptr PlayerManager::CreatePlayer(const std::string& username) { +std::shared_ptr PlayerManager::CreatePlayer(const std::string& username, const std::string& password) { static std::atomic nextPlayerId{1000000}; int64_t playerId = nextPlayerId++; - auto player = std::make_shared(playerId, username); + auto player = std::make_shared(playerId, username, password); { std::unique_lock lock(playersMutex_); @@ -120,29 +117,19 @@ uint64_t PlayerManager::GetSessionIdByPlayerId(int64_t playerId) const { return (it != playerToSession_.end()) ? it->second : 0; } -// =============== Authentication / Connection =============== - - bool PlayerManager::AuthenticatePlayer(const std::string& username, const std::string& password) { try { - // Safely escape the username to prevent SQL injection. - // Assume dbManager_ has an EscapeString method (or use a dedicated escaping function). - std::string escapedUsername = dbManager_.EscapeString(username); - - // Use the existing Query method with escaped input. - auto result = dbManager_.Query( - "SELECT password_hash FROM players WHERE username = '" + escapedUsername + "'" - ); - + std::string sql = dbManager_.GetSQLProvider().GetQuery("get_player_by_username"); + if (sql.empty()) { + Logger::Error("Missing SQL: get_player_by_username"); + return false; + } + auto result = dbManager_.QueryWithParams(sql, { username }); if (result.empty()) { Logger::Debug("Authentication failed: username '{}' not found", username); return false; } - std::string storedHash = result[0]["password_hash"].get(); - - // Verify the password using a secure hashing algorithm. - // Replace with your actual password verification function. if (Passwords::VerifyPassword(password, storedHash)) { Logger::Debug("Authentication successful for user '{}'", username); return true; @@ -150,7 +137,6 @@ bool PlayerManager::AuthenticatePlayer(const std::string& username, const std::s Logger::Debug("Authentication failed: invalid password for user '{}'", username); return false; } - } catch (const std::exception& e) { Logger::Error("Authentication error for {}: {}", username, e.what()); return false; @@ -215,8 +201,6 @@ void PlayerManager::UpdateConnectionStats(int64_t playerId, bool connected) { } } -// =============== Messaging & Broadcasting =============== - void PlayerManager::BroadcastToNearbyPlayers(int64_t playerId, const nlohmann::json& message) { auto nearby = GetNearbyPlayers(playerId, DEFAULT_BROADCAST_RANGE); auto& connMgr = ConnectionManager::GetInstance(); @@ -252,8 +236,6 @@ std::vector> PlayerManager::GetPlayersInRadius(const glm return result; } -// =============== Database Persistence =============== - void PlayerManager::SaveAllPlayers() { Logger::Info("Saving all players to database..."); std::vector> toSave; @@ -304,9 +286,14 @@ std::shared_ptr PlayerManager::LoadPlayer(int64_t playerId) { std::shared_ptr PlayerManager::LoadPlayerByUsername(const std::string& username) { try { - auto result = dbManager_.Query("SELECT player_id FROM players WHERE username = '" + username + "'"); + std::string sql = dbManager_.GetSQLProvider().GetQuery("get_player_by_username"); + if (sql.empty()) { + Logger::Error("Missing SQL: get_player_by_username"); + return nullptr; + } + auto result = dbManager_.QueryWithParams(sql, { username }); if (result.empty()) return nullptr; - int64_t playerId = result[0]["player_id"].get(); + int64_t playerId = result[0]["id"].get(); return LoadPlayer(playerId); } catch (const std::exception& e) { Logger::Error("Failed to load player by username {}: {}", username, e.what()); @@ -314,8 +301,6 @@ std::shared_ptr PlayerManager::LoadPlayerByUsername(const std::string& u } } -// =============== Background Threads =============== - void PlayerManager::SaveLoop() { Logger::Info("Player save loop started"); while (running_) { @@ -361,7 +346,6 @@ void PlayerManager::CleanupInactivePlayers() { if (it == players_.end()) continue; it->second->SaveToDatabase(); - // Remove from username map { std::unique_lock ulock(usernameMutex_); for (auto uit = usernameToId_.begin(); uit != usernameToId_.end(); ) { @@ -369,7 +353,6 @@ void PlayerManager::CleanupInactivePlayers() { else ++uit; } } - // Remove from sessions map { std::unique_lock slock(sessionsMutex_); for (auto sit = sessionToPlayer_.begin(); sit != sessionToPlayer_.end(); ) { @@ -378,7 +361,6 @@ void PlayerManager::CleanupInactivePlayers() { } playerToSession_.erase(id); } - // Remove stats { std::lock_guard slock(statsMutex_); playerStats_.erase(id); @@ -389,8 +371,6 @@ void PlayerManager::CleanupInactivePlayers() { Logger::Info("Cleaned up {} inactive players", toRemove.size()); } -// =============== Player Stats =============== - PlayerStats PlayerManager::GetPlayerStats(int64_t playerId) const { std::lock_guard lock(statsMutex_); auto it = playerStats_.find(playerId); @@ -445,8 +425,6 @@ void PlayerManager::PrintStats() { Logger::Info("================================="); } -// =============== Search & Queries =============== - std::vector PlayerManager::SearchPlayers(const std::string& query, int limit) { std::vector result; std::shared_lock lock(usernameMutex_); @@ -471,8 +449,6 @@ std::vector PlayerManager::GetPlayersByLevelRange(int minLevel, int max return result; } -// =============== Parties =============== - void PlayerManager::CreateParty(int64_t leaderId, const std::string& partyName) { std::lock_guard lock(partyMutex_); Party party; @@ -544,8 +520,6 @@ int64_t PlayerManager::GeneratePartyId() { return nextPartyId++; } -// =============== Messaging =============== - void PlayerManager::SendToPlayer(int64_t playerId, const nlohmann::json& message) { if (auto sessionId = GetSessionIdByPlayerId(playerId)) { auto& connMgr = ConnectionManager::GetInstance(); @@ -566,8 +540,6 @@ void PlayerManager::SendToPlayers(const std::vector& playerIds, const n } } -// =============== Moderation =============== - void PlayerManager::BanPlayer(int64_t playerId, const std::string& reason, int64_t durationSeconds) { auto player = GetPlayer(playerId); if (!player) { @@ -604,8 +576,6 @@ void PlayerManager::UnbanPlayer(int64_t playerId) { } } -// =============== Teleportation =============== - void PlayerManager::TeleportPlayer(int64_t playerId, float x, float y, float z) { auto player = GetPlayer(playerId); if (player) { @@ -619,8 +589,6 @@ void PlayerManager::TeleportPlayer(int64_t playerId, float x, float y, float z) } } -// =============== Inventory =============== - bool PlayerManager::GiveItemToPlayer(int64_t playerId, const std::string& itemId, int count) { auto player = GetPlayer(playerId); if (!player) return false; @@ -648,8 +616,6 @@ bool PlayerManager::TakeItemFromPlayer(int64_t playerId, const std::string& item return true; } -// =============== Achievements =============== - void PlayerManager::AddAchievementToPlayer(int64_t playerId, const std::string& achievementId) { auto player = GetPlayer(playerId); if (player) { @@ -663,8 +629,6 @@ void PlayerManager::AddAchievementToPlayer(int64_t playerId, const std::string& } } -// =============== Utility =============== - std::vector> PlayerManager::GetAllPlayers() const { std::shared_lock lock(playersMutex_); std::vector> result; diff --git a/src/main.cpp b/src/main.cpp index e491203..b9c60a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,28 +23,23 @@ void SignalHandler(int signal) { g_shutdown.store(true); } -// New worker main signature: receives group config +// Worker main signature: receives group config void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* processPool = nullptr, const std::string& path_config = "config.json") { try { - // Initialize logging with worker-specific configuration auto& config = ConfigManager::GetInstance(); - // Use worker-specific logger initialization Logger::InitializeWithWorkerId(workerId); Logger::Info("Worker {} starting game world system for group: {} ({}:{})", workerId, groupConfig.protocol, groupConfig.host, groupConfig.port); - // Initialize configuration if (!config.LoadConfig(path_config)) { Logger::Critical("Worker {} failed to load configuration", workerId); return; } - // Configure ProcessPool message protocol if processPool is provided if (processPool) { - // Set message protocol configuration from config - uint32_t maxMessageSize = config.GetInt("process.max_message_size", 1048576); // 1MB default - uint32_t receiveTimeout = config.GetInt("process.receive_timeout_ms", 1000); // 1 second default + uint32_t maxMessageSize = config.GetInt("process.max_message_size", 1048576); + uint32_t receiveTimeout = config.GetInt("process.receive_timeout_ms", 1000); processPool->SetMaxMessageSize(maxMessageSize); processPool->SetReceiveTimeout(receiveTimeout); @@ -55,10 +50,8 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Warn("Worker {}: ProcessPool not available for configuration", workerId); } - // Initialize database using the new DbManager interface auto& dbManager = DbManager::GetInstance(); - // Create configuration JSON object nlohmann::json dbConfig; dbConfig["host"] = config.GetDatabaseHost(); dbConfig["port"] = config.GetDatabasePort(); @@ -66,7 +59,6 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* dbConfig["user"] = config.GetDatabaseUser(); dbConfig["password"] = config.GetDatabasePassword(); - // Add connection pool settings dbConfig["max_connections"] = config.GetInt("database.max_connections", 20); dbConfig["min_connections"] = config.GetInt("database.min_connections", 5); dbConfig["connection_timeout_ms"] = config.GetInt("database.connection_timeout_ms", 5000); @@ -76,7 +68,6 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* return; } - // Connect to the database if (!dbManager.Connect()) { Logger::Error("Worker {} failed to connect to database", workerId); return; @@ -85,10 +76,8 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Info("Worker {} database connection established successfully using backend: {}", workerId, config.GetDatabaseBackend()); - // Initialize game logic with 3D world system auto& gameLogic = GameLogic::GetInstance(); - // Configure world settings using the newly added methods GameLogic::WorldConfig worldConfig; worldConfig.seed = config.GetWorldSeed() + workerId; worldConfig.viewDistance = config.GetViewDistance(); @@ -100,11 +89,8 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* gameLogic.SetWorldConfig(worldConfig); - // Initialize and run server with group configuration - // Note: GameServer constructor must be updated to accept WorkerGroupConfig - GameServer server(groupConfig, config); // Pass group config and global config + GameServer server(groupConfig, config); - // Set session factory - using lambda with necessary captures if (groupConfig.protocol == "binary") { server.SetSessionFactory([workerId, processPool, &groupConfig] (asio::ip::tcp::socket socket, @@ -114,17 +100,14 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Debug("Worker {} created new game session {}", workerId, session->GetSessionId()); - // Message handler - simplified for demonstration session->SetMessageHandler([session, workerId, processPool](const nlohmann::json& msg) { try { std::string msgType = msg.value("type", ""); Logger::Debug("Worker {} processing message type: {}", workerId, msgType); - // Check if message is for inter-process communication - if (msgType == "ipc_message" && processPool) { // Extract IPC message details + if (msgType == "ipc_message" && processPool) { if (msg.contains("target_worker") && msg.contains("payload")) { int targetWorker = msg["target_worker"]; std::string payload = msg["payload"].dump(); - // Send to another worker via master using new protocol if (processPool->SendToWorker(targetWorker, payload)) { Logger::Debug("Worker {} sent IPC message to worker {}", workerId, targetWorker); @@ -133,7 +116,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* workerId, targetWorker); } } - } else { // Regular game message - delegate to game logic + } else { GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); } } catch (const std::exception& e) { @@ -142,13 +125,12 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* } }); - // --- Binary message handler --- session->SetDefaultBinaryMessageHandler([session, workerId](uint16_t type, const std::vector& data) { GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data); }); - session->SetCloseHandler([session, workerId]() { // Close handler + session->SetCloseHandler([session, workerId]() { Logger::Info("Worker {} session {} closing", workerId, session->GetSessionId()); GameLogic::GetInstance().OnPlayerDisconnected(session->GetSessionId()); Logger::Debug("Worker {} session {} cleanup complete", workerId, session->GetSessionId()); @@ -156,38 +138,25 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* return session; }); } else if (groupConfig.protocol == "websocket") { - server.SetWebSocketConnectionFactory([workerId, processPool, &groupConfig](asio::ip::tcp::socket socket, std::shared_ptr sslCtx) { - // Create a WebSocketConnection (which handles the upgrade) + server.SetWebSocketConnectionFactory([workerId, processPool, &groupConfig](asio::ip::tcp::socket socket, std::shared_ptr /*sslCtx*/) { auto wsConn = std::make_shared(std::move(socket)); - if (sslCtx) { - // For wss, we need to do SSL handshake before WebSocket upgrade. - // This is more complex; we might need a separate SSL stream. - // For now, assume SSL is handled by the acceptor (e.g., using asio::ssl::stream). - // We'll need to extend WebSocketConnection to accept an SSL stream. - // This is a more advanced integration; for simplicity, we can require SSL to be handled by the listener (i.e., wss://) which will already have an SSL stream. - // The current WebSocketConnection doesn't support SSL; we may need to create an SSL version. - // As a simplification, we can defer SSL WebSocket support. - } + // SSL handling would be added later if needed return wsConn; }); } - // Pass connection manager to game logic before initialization gameLogic.SetConnectionManager(ConnectionManager::GetInstancePtr()); gameLogic.Initialize(); - // Preload world data if configured if (config.ShouldPreloadWorld()) { Logger::Info("Worker {} preloading world data...", workerId); gameLogic.PreloadWorldData(config.GetWorldPreloadRadius()); } - // Initialize and run server if (server.Initialize()) { Logger::Info("Worker {} game server initialized on {}:{} (protocol: {})", workerId, groupConfig.host, groupConfig.port, groupConfig.protocol); - // Start background world maintenance thread std::atomic worldMaintenanceRunning{true}; std::thread worldMaintenanceThread([&gameLogic, &worldMaintenanceRunning, workerId, processPool]() { Logger::Info("Worker {} starting world maintenance thread", workerId); @@ -198,34 +167,26 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* while (worldMaintenanceRunning && !g_shutdown.load()) { auto currentTime = std::chrono::steady_clock::now(); - // World maintenance every 30 seconds auto elapsedWorld = std::chrono::duration_cast( currentTime - lastCleanupTime); - if (elapsedWorld.count() >= 30) { - Logger::Debug("Worker {} performing periodic world maintenance", workerId); gameLogic.PerformMaintenance(); lastCleanupTime = currentTime; } - // Check for IPC messages from master every 100ms + // Check for IPC messages every 10 ms and drain all pending auto elapsedIPC = std::chrono::duration_cast( currentTime - lastIPCCheckTime); - - if (elapsedIPC.count() >= 100 && processPool) { - // Use new ReceiveFromMaster with message length protocol + if (elapsedIPC.count() >= 10 && processPool) { std::string message = processPool->ReceiveFromMaster(); - if (!message.empty()) { + while (!message.empty()) { try { - // Parse JSON message auto jsonMsg = nlohmann::json::parse(message); - //Logger::Debug("Worker {} received IPC message: {}", workerId, jsonMsg.dump()); - - // Handle IPC message in game logic gameLogic.HandleIPCMessage(jsonMsg); } catch (const std::exception& e) { Logger::Error("Worker {} failed to parse IPC message: {}", workerId, e.what()); } + message = processPool->ReceiveFromMaster(); } lastIPCCheckTime = currentTime; } @@ -236,11 +197,9 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Info("Worker {} world maintenance thread stopped", workerId); }); - // Start the server (blocks until shutdown) Logger::Info("Worker {} starting server loop", workerId); server.Run(); - // Stop maintenance thread worldMaintenanceRunning = false; if (worldMaintenanceThread.joinable()) { worldMaintenanceThread.join(); @@ -250,11 +209,9 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Critical("Worker {} failed to initialize server", workerId); } - // Cleanup Logger::Info("Worker {} beginning cleanup...", workerId); gameLogic.Shutdown(); - // Disconnect and shutdown database connections dbManager.Disconnect(); dbManager.Shutdown(); @@ -262,7 +219,6 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* } catch (const std::exception& e) { Logger::Critical("Worker {} caught unhandled exception: {}", workerId, e.what()); - // Allow logs to flush std::this_thread::sleep_for(std::chrono::milliseconds(100)); } catch (...) { @@ -273,30 +229,23 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* int main(int argc, char* argv[]) { - // Set up signal handlers std::signal(SIGINT, SignalHandler); std::signal(SIGTERM, SignalHandler); - // Initialize a default logger (no config needed) Logger::InitializeDefaults(); - // Load configuration std::string conf_path = "config/core.json"; auto& config = ConfigManager::GetInstance(); if (!config.LoadConfig(conf_path)) { - //std::cerr << "Failed to load configuration" << std::endl; Logger::Critical("Failed to load configuration."); return 1; } else { - //std::cout << "Success to load configuration" << std::endl; Logger::Info("Success to load configuration."); } - // Initialize logger with the actual config settings Logger::Initialize(); - // Ensure the target database exists (master process only) DbManager& dbManager = DbManager::GetInstance(); bool db_check = dbManager.EnsureDatabaseExists(conf_path); dbManager.Disconnect(); @@ -318,19 +267,16 @@ int main(int argc, char* argv[]) { } Logger::Info("{} commands ({})", argc, cmdline); - // Get worker groups from config auto groups = config.GetWorkerGroups(); if (groups.empty()) { Logger::Critical("No worker groups configured"); return 1; } - // Create process pool with groups ProcessPool processPool(groups); - // Configure process pool message protocol from config - uint32_t maxMessageSize = config.GetInt("process.max_message_size", 1048576); // 1MB default - uint32_t receiveTimeout = config.GetInt("process.receive_timeout_ms", 1000); // 1 second default + uint32_t maxMessageSize = config.GetInt("process.max_message_size", 1048576); + uint32_t receiveTimeout = config.GetInt("process.receive_timeout_ms", 1000); processPool.SetMaxMessageSize(maxMessageSize); processPool.SetReceiveTimeout(receiveTimeout); @@ -338,29 +284,23 @@ int main(int argc, char* argv[]) { Logger::Info("Process pool configured: max message size = {} bytes, timeout = {}ms", maxMessageSize, receiveTimeout); - // Create a lambda that captures processPool pointer and group configs (though group config will be passed by worker) - // But we need to pass the global config path. The worker will load it itself. auto workerMainWithPool = [&processPool, &conf_path](int workerId, const WorkerGroupConfig& groupConfig) { WorkerMain(workerId, groupConfig, &processPool, conf_path); }; - // Set worker main function with the new signature processPool.SetWorkerMain(workerMainWithPool); - // Initialize and run process pool (will fork workers) Logger::Info("Starting {} worker processes", processPool.GetTotalWorkerCount()); processPool.Run(); - // Send test messages to workers using new protocol + // Master messaging thread – reduced frequency to avoid pipe overload std::thread masterMessagingThread([&processPool]() { std::this_thread::sleep_for(std::chrono::seconds(3)); // Wait for workers to start Logger::Info("Master process starting IPC message test"); - // Send a test message to each worker int totalWorkers = processPool.GetTotalWorkerCount(); for (int i = 0; i < totalWorkers; i++) { - // Skip dead workers if (!processPool.IsWorkerAlive(i)) { Logger::Warn("Master skipping welcome message to dead worker {}", i); continue; @@ -387,10 +327,8 @@ int main(int argc, char* argv[]) { Logger::Info("Master process IPC message test complete"); }); - // Wait for shutdown signal Logger::Info("Master process waiting for shutdown signal..."); while (!g_shutdown.load()) { - // Periodically check worker health int totalWorkers = processPool.GetTotalWorkerCount(); for (int i = 0; i < totalWorkers; i++) { if (!processPool.IsWorkerAlive(i)) { @@ -398,45 +336,36 @@ int main(int argc, char* argv[]) { } } - // Short sleep to allow quick response to shutdown - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // If shutdown requested during sleep, exit loop immediately + // Sleep longer (2 seconds) to reduce master message rate + std::this_thread::sleep_for(std::chrono::seconds(2)); if (g_shutdown.load()) break; - // Send periodic heartbeat to all workers + // Send heartbeat every 10 seconds instead of every second static int heartbeatCount = 0; heartbeatCount++; + if (heartbeatCount % 5 == 0) { // every 10 seconds (since sleep 2 sec) + for (int i = 0; i < totalWorkers; i++) { + if (g_shutdown.load()) break; + if (!processPool.IsWorkerAlive(i)) continue; - for (int i = 0; i < totalWorkers; i++) { - // Stop sending if shutdown requested - if (g_shutdown.load()) break; + nlohmann::json heartbeat; + heartbeat["type"] = "heartbeat"; + heartbeat["count"] = heartbeatCount; + heartbeat["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); - // Skip dead workers - if (!processPool.IsWorkerAlive(i)) { - Logger::Debug("Worker {} is dead, skipping heartbeat", i); - continue; + processPool.SendToWorker(i, heartbeat.dump()); } - - nlohmann::json heartbeat; - heartbeat["type"] = "heartbeat"; - heartbeat["count"] = heartbeatCount; - heartbeat["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); - - processPool.SendToWorker(i, heartbeat.dump()); } - // Broadcast server status to all players via workers + // Broadcast server status every 30 seconds (reduced from 10) static int statusUpdateCount = 0; statusUpdateCount++; - - if (statusUpdateCount % 10 == 0) { // Every 10 seconds (since sleep is 1 sec) + if (statusUpdateCount % 15 == 0) { // every 30 seconds nlohmann::json serverStatus; serverStatus["type"] = "server_status"; serverStatus["online_workers"] = totalWorkers; serverStatus["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); - // Send broadcast command to all workers nlohmann::json broadcastMsg; broadcastMsg["type"] = "broadcast"; broadcastMsg["data"] = serverStatus; @@ -452,12 +381,10 @@ int main(int argc, char* argv[]) { } } - // Stop messaging thread if (masterMessagingThread.joinable()) { masterMessagingThread.join(); } - // Shutdown process pool gracefully Logger::Info("Initiating graceful shutdown..."); processPool.Shutdown(); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index c940381..494c596 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -105,6 +105,9 @@ void GameServer::DoAccept() { session->SetBinaryMessageHandler([session](uint16_t type, const std::vector& data) { GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data); }); + session->SetMessageHandler([session](const nlohmann::json& msg) { + GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); + }); ConnectionManager::GetInstance().Start(session); session->Start(); } else { diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index 96414de..ecbb314 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -384,8 +384,6 @@ std::string HandshakeResponse::GetHeader(const std::string& name) const { return ""; } -// =============== WebSocketConnection Implementation =============== - std::atomic WebSocketConnection::next_connection_id_{1}; WebSocketConnection::WebSocketConnection(asio::ip::tcp::socket socket) @@ -402,7 +400,6 @@ void WebSocketConnection::Start() { if (state_ != State::HANDSHAKE) { return; } - HandleHandshake(); } @@ -412,27 +409,20 @@ void WebSocketConnection::HandleHandshake() { void WebSocketConnection::ReadHandshake() { auto self = shared_from_this(); - asio::async_read_until(socket_, read_buffer_, "\r\n\r\n", - [self](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::ReadHandshake asio::async_read_until {}", bytes_transferred); + [self](std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::ReadHandshake asio::async_read_until {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - - // Extract handshake request std::istream stream(&self->read_buffer_); std::string request_str; std::getline(stream, request_str, '\0'); - try { HandshakeRequest request = HandshakeRequest::Parse(request_str); - - // Generate and send response HandshakeResponse response = HandshakeResponse::GenerateResponse(request); self->WriteHandshakeResponse(response); - } catch (const std::exception& e) { Logger::Error("WebSocket handshake error: {}", e.what()); self->Close(1002, "Protocol error"); @@ -443,20 +433,15 @@ void WebSocketConnection::ReadHandshake() { void WebSocketConnection::WriteHandshakeResponse(const HandshakeResponse& response) { auto self = shared_from_this(); std::string response_str = response.Serialize(); - asio::async_write(socket_, asio::buffer(response_str), - [self](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::WriteHandshakeResponse asio::async_write {}", bytes_transferred); + [self](std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::WriteHandshakeResponse asio::async_write {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - - // Handshake complete, switch to OPEN state self->state_ = State::OPEN; Logger::Info("WebSocketConnection {} handshake complete", self->connection_id_); - - // Start reading frames self->ReadFrame(); }); } @@ -465,59 +450,42 @@ void WebSocketConnection::ReadFrame() { if (state_ != State::OPEN && state_ != State::CLOSING) { return; } - auto self = shared_from_this(); - - // Read frame header (minimum 2 bytes) asio::async_read(socket_, read_buffer_, asio::transfer_exactly(2), - [self](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); + [self](std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - - // Parse basic header auto buffers = self->read_buffer_.data(); auto it = asio::buffers_begin(buffers); - - // Check we have at least 2 bytes if (std::distance(it, asio::buffers_end(buffers)) < 2) { Logger::Error("Insufficient data for frame header"); throw std::runtime_error("Insufficient data for frame header"); } - uint8_t first_byte = *it; uint8_t second_byte = *(++it); - bool fin = (first_byte & 0x80) != 0; uint8_t opcode = first_byte & 0x0F; bool masked = (second_byte & 0x80) != 0; uint64_t payload_length = second_byte & 0x7F; - size_t header_size = 2; - - // Determine extended payload length if (payload_length == 126) { header_size += 2; } else if (payload_length == 127) { header_size += 8; } - - // Add masking key size if present if (masked) { header_size += 4; } - - // Read remaining header bytes if (header_size > 2) { self->read_buffer_.consume(2); // Remove the 2 bytes we already read - asio::async_read(self->socket_, self->read_buffer_, asio::transfer_exactly(header_size - 2), [self, fin, opcode, masked, payload_length, header_size] - (std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); + (std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); if (ec) { self->HandleError(ec); return; @@ -533,18 +501,15 @@ void WebSocketConnection::ReadFrame() { void WebSocketConnection::ReadFramePayload(bool fin, uint8_t opcode, bool masked, uint64_t payload_length, size_t header_size) { auto self = shared_from_this(); - - // Read payload if present if (payload_length > 0) { asio::async_read(socket_, read_buffer_, asio::transfer_exactly(payload_length), [self, fin, opcode, masked, payload_length, header_size] - (std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::ReadFramePayload asio::async_read {}", bytes_transferred); + (std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::ReadFramePayload asio::async_read {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - self->ProcessFrameData(fin, opcode, masked, payload_length, header_size); }); } else { @@ -557,31 +522,22 @@ void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked (void)fin; (void)opcode; (void)masked; - // Allocate destination buffer size_t total_frame_size = header_size + payload_length; - // Validate size before allocation constexpr size_t MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16MB limit if (total_frame_size > MAX_FRAME_SIZE) { throw std::runtime_error("Frame too large"); } - std::vector frame_data(total_frame_size); auto buffers = read_buffer_.data(); - - // Use Asio's safe buffer_copy size_t bytes_copied = asio::buffer_copy( asio::buffer(frame_data), // destination buffers, // source buffer sequence total_frame_size // maximum bytes to copy ); - if (bytes_copied != total_frame_size) { throw std::runtime_error("Incomplete frame data in buffer"); } - - // Consume the data from the read buffer read_buffer_.consume(bytes_copied); - try { WebSocketFrame frame = WebSocketFrame::Deserialize(frame_data); HandleFrame(frame); @@ -590,28 +546,22 @@ void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked Close(1002, "Protocol error"); return; } - - // Continue reading next frame if (state_ == State::OPEN || state_ == State::CLOSING) { ReadFrame(); } } void WebSocketConnection::HandleFrame(const WebSocketFrame& frame) { - // Update statistics { std::lock_guard lock(stats_mutex_); stats_.messages_received++; stats_.bytes_received += frame.payload_data.size(); } - - // Handle control frames if (frame.IsControlFrame()) { switch (frame.opcode) { case OP_CLOSE: { uint16_t close_code = 1000; std::string close_reason; - if (frame.payload_length >= 2) { close_code = (frame.payload_data[0] << 8) | frame.payload_data[1]; if (frame.payload_length > 2) { @@ -621,13 +571,10 @@ void WebSocketConnection::HandleFrame(const WebSocketFrame& frame) { ); } } - HandleClose(close_code, close_reason); break; } - case OP_PING: { - // Send pong response SendPong(frame.payload_data); { std::lock_guard lock(stats_mutex_); @@ -635,43 +582,32 @@ void WebSocketConnection::HandleFrame(const WebSocketFrame& frame) { } break; } - case OP_PONG: { - // Update last pong time { std::lock_guard lock(stats_mutex_); stats_.pong_count++; } break; } - default: - // Should not happen for control frames Close(1002, "Protocol error"); break; } return; } - - // Handle data frames ProcessMessageData(frame); } void WebSocketConnection::ProcessMessageData(const WebSocketFrame& frame) { if (frame.opcode == OP_TEXT || frame.opcode == OP_BINARY) { - // Start of a new message current_message_ = WebSocketMessage(); current_message_.opcode = frame.opcode; } - - // Append data current_message_.data.insert( current_message_.data.end(), frame.payload_data.begin(), frame.payload_data.end() ); - - // Check if message is complete if (frame.fin) { current_message_.complete = true; CompleteCurrentMessage(); @@ -679,44 +615,34 @@ void WebSocketConnection::ProcessMessageData(const WebSocketFrame& frame) { } void WebSocketConnection::CompleteCurrentMessage() { - // Call appropriate handlers if (message_handler_) { message_handler_(current_message_); } - if (current_message_.opcode == OP_TEXT && text_handler_) { text_handler_(current_message_.GetText()); } else if (current_message_.opcode == OP_BINARY && binary_handler_) { binary_handler_(current_message_.data); } - - // Reset for next message current_message_ = WebSocketMessage(); } void WebSocketConnection::SendFrame(const WebSocketFrame& frame) { std::vector frame_data = frame.Serialize(); - { std::lock_guard lock(write_mutex_); write_buffer_.insert(write_buffer_.end(), frame_data.begin(), frame_data.end()); - - // Update statistics stats_.messages_sent++; stats_.bytes_sent += frame.payload_data.size(); } - - // Start writing if not already writing DoWrite(); } void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { auto self = shared_from_this(); std::vector frame_data = frame.Serialize(); - asio::async_write(socket_, asio::buffer(frame_data), - [self, frame_data](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::SendFrameAsync asio::async_write {}", bytes_transferred); + [self, frame_data](std::error_code ec, size_t /*bytes_transferred*/) { + //Logger::Debug("WebSocketConnection::SendFrameAsync asio::async_write {}", bytes_transferred); if (ec) { self->HandleError(ec); } else { @@ -729,25 +655,20 @@ void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { void WebSocketConnection::DoWrite() { auto self = shared_from_this(); - std::lock_guard lock(write_mutex_); if (write_buffer_.empty()) { return; } - asio::async_write(socket_, asio::buffer(write_buffer_), [self](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); + //Logger::Debug("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - std::lock_guard lock(self->write_mutex_); self->write_buffer_.erase(self->write_buffer_.begin(), self->write_buffer_.begin() + bytes_transferred); - - // Continue writing if more data if (!self->write_buffer_.empty()) { self->DoWrite(); } @@ -822,7 +743,6 @@ void WebSocketConnection::HandleClose(uint16_t code, const std::string& reason) if (state_ == State::OPEN) { SendFrameAsync(WebSocketFrame::CreateCloseFrame(code, reason)); } - Close(code, reason); } @@ -842,13 +762,10 @@ WebSocketConnection::Statistics WebSocketConnection::GetStatistics() const { return stats_; } -// =============== WebSocketServer Implementation =============== - WebSocketServer::WebSocketServer(asio::io_context& io_context, uint16_t port) : io_context_(io_context) , acceptor_(io_context_, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) , port_(port) { - Logger::Info("WebSocketServer created on port {}", port); } @@ -860,10 +777,8 @@ void WebSocketServer::Start() { if (running_) { return; } - running_ = true; DoAccept(); - Logger::Info("WebSocketServer started on port {}", port_); } @@ -871,19 +786,14 @@ void WebSocketServer::Stop() { if (!running_) { return; } - running_ = false; - std::error_code ec; acceptor_.close(ec); - - // Close all connections std::lock_guard lock(connections_mutex_); for (auto& connection : connections_) { connection->Close(1001, "Server going away"); } connections_.clear(); - Logger::Info("WebSocketServer stopped"); } @@ -891,27 +801,18 @@ void WebSocketServer::DoAccept() { if (!running_) { return; } - acceptor_.async_accept([this](std::error_code ec, asio::ip::tcp::socket socket) { if (!ec) { - // Create connection WebSocketConnection::Pointer connection; if (connection_factory_) { connection = connection_factory_(std::move(socket)); } else { connection = std::make_shared(std::move(socket)); } - - // Add to connections list AddConnection(connection); - - // Start the connection connection->Start(); - Logger::Debug("WebSocket connection accepted"); } - - // Continue accepting if (running_) { DoAccept(); } @@ -921,8 +822,6 @@ void WebSocketServer::DoAccept() { void WebSocketServer::AddConnection(WebSocketConnection::Pointer connection) { std::lock_guard lock(connections_mutex_); connections_.push_back(connection); - - // Set up removal handler auto weak_connection = std::weak_ptr(connection); connection->SetCloseHandler([this, weak_connection](uint16_t code, const std::string& reason) { Logger::Debug("WebSocketConnection::AddConnection WebSocketConnection::SetCloseHandler({}, {})", code, reason); @@ -974,8 +873,6 @@ size_t WebSocketServer::GetConnectionCount() const { return connections_.size(); } -// =============== WebSocketClient Implementation =============== - WebSocketClient::WebSocketClient(asio::io_context& io_context) : WebSocketConnection(asio::ip::tcp::socket(io_context)) , io_context_(io_context) { @@ -985,7 +882,6 @@ void WebSocketClient::Connect(const std::string& host, uint16_t port, const std: host_ = host; port_ = port; path_ = path; - ResolveAndConnect(); } @@ -994,11 +890,9 @@ void WebSocketClient::Connect(const std::string& url) { host_ = parsed_url.host; port_ = parsed_url.port; path_ = parsed_url.path; - if (parsed_url.protocol == "wss") { UseSSL(true); } - ResolveAndConnect(); } @@ -1014,7 +908,6 @@ void WebSocketClient::UseSSL(bool enable) { void WebSocketClient::ResolveAndConnect() { auto resolver = std::make_shared(io_context_); - resolver->async_resolve(host_, std::to_string(port_), [this, resolver](const std::error_code& ec, asio::ip::tcp::resolver::results_type endpoints) { @@ -1028,12 +921,9 @@ void WebSocketClient::HandleResolve(const std::error_code& ec, HandleError(ec); return; } - - // Connect to the first endpoint if (ssl_context_) { ssl_stream_ = std::make_unique>( io_context_, *ssl_context_); - asio::async_connect(ssl_stream_->lowest_layer(), endpoints, [this](const std::error_code& ec, const asio::ip::tcp::endpoint& endpoint) { HandleConnect(ec, endpoint); @@ -1051,11 +941,8 @@ void WebSocketClient::HandleConnect(const std::error_code& ec, const asio::ip::t HandleError(ec); return; } - Logger::Debug("WebSocketClient connected to {}:{}", endpoint.address().to_string(), endpoint.port()); - if (ssl_stream_) { - // Perform SSL handshake ssl_stream_->async_handshake(asio::ssl::stream_base::client, [this](const std::error_code& ec) { if (ec) { @@ -1081,20 +968,14 @@ void WebSocketClient::SendHandshakeRequest() { request.SetHeader("Connection", "Upgrade"); request.SetHeader("Sec-WebSocket-Key", GenerateWebSocketKey()); request.SetHeader("Sec-WebSocket-Version", "13"); - std::string request_str = request.Serialize(); - auto self = std::static_pointer_cast(shared_from_this()); - - // Send handshake request auto write_handler = [self, request](std::error_code ec, size_t bytes_transferred) { Logger::Debug("WebSocketConnection::SendHandshakeRequest write_handler {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } - - // Read handshake response asio::async_read_until(self->socket_, self->read_buffer_, "\r\n\r\n", [self, request](std::error_code ec, size_t bytes_transferred) { Logger::Debug("WebSocketConnection::SendHandshakeRequest asio::async_read_until {}", bytes_transferred); @@ -1102,33 +983,23 @@ void WebSocketClient::SendHandshakeRequest() { self->HandleError(ec); return; } - - // Parse response std::istream stream(&self->read_buffer_); std::string response_str; std::getline(stream, response_str, '\0'); - try { HandshakeResponse response = HandshakeResponse::Parse(response_str); - if (!response.Validate(request)) { throw std::runtime_error("Invalid handshake response"); } - - // Handshake complete self->state_ = State::OPEN; Logger::Info("WebSocketClient handshake complete"); - - // Start reading frames self->ReadFrame(); - } catch (const std::exception& e) { Logger::Error("WebSocket handshake error: {}", e.what()); self->Close(1002, "Protocol error"); } }); }; - if (ssl_stream_) { asio::async_write(*ssl_stream_, asio::buffer(request_str), write_handler); } else { @@ -1136,40 +1007,29 @@ void WebSocketClient::SendHandshakeRequest() { } } -// =============== Utility Functions =============== - std::string GenerateWebSocketKey() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 255); - std::array random_bytes; for (int i = 0; i < 16; ++i) { random_bytes[i] = static_cast(dis(gen)); } - - // Base64 encode const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - std::string result; int i = 0; while (i < 16) { uint32_t octet_a = i < 16 ? random_bytes[i++] : 0; uint32_t octet_b = i < 16 ? random_bytes[i++] : 0; uint32_t octet_c = i < 16 ? random_bytes[i++] : 0; - uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; - result += base64_chars[(triple >> 18) & 0x3F]; result += base64_chars[(triple >> 12) & 0x3F]; result += base64_chars[(triple >> 6) & 0x3F]; result += base64_chars[triple & 0x3F]; } - - // Remove padding result = result.substr(0, 24); - return result; } @@ -1178,15 +1038,10 @@ std::string GenerateAcceptKey(const std::string& key) { std::string combined = key + magic_guid; unsigned char hash[SHA_DIGEST_LENGTH]; // 20 bytes SHA1(reinterpret_cast(combined.c_str()), combined.size(), hash); - - // Base64 encode according to RFC 4648 const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - std::string result; int i = 0; - - // Process full 3‑byte groups while (i < SHA_DIGEST_LENGTH - 2) { uint32_t triple = (hash[i] << 16) | (hash[i+1] << 8) | hash[i+2]; result += base64_chars[(triple >> 18) & 0x3F]; @@ -1195,8 +1050,6 @@ std::string GenerateAcceptKey(const std::string& key) { result += base64_chars[triple & 0x3F]; i += 3; } - - // Handle remaining bytes with correct padding int remaining = SHA_DIGEST_LENGTH - i; if (remaining == 1) { uint32_t triple = (hash[i] << 16); @@ -1210,7 +1063,6 @@ std::string GenerateAcceptKey(const std::string& key) { result += base64_chars[(triple >> 6) & 0x3F]; result += "="; } - return result; } @@ -1224,20 +1076,16 @@ bool IsControlOpcode(uint8_t opcode) { } size_t GetFrameHeaderSize(const WebSocketFrame& frame) { - size_t size = 2; // Basic header - + size_t size = 2; if (frame.payload_length <= 125) { - // Already accounted for } else if (frame.payload_length <= 65535) { size += 2; } else { size += 8; } - if (frame.masked) { size += 4; } - return size; } @@ -1280,22 +1128,16 @@ std::string GetCloseReason(uint16_t code) { } } -// =============== WebSocketURL Implementation =============== - WebSocketURL WebSocketURL::Parse(const std::string& url) { WebSocketURL result; - - // Parse protocol size_t protocol_end = url.find("://"); if (protocol_end != std::string::npos) { result.protocol = url.substr(0, protocol_end); - protocol_end += 3; // Skip "://" + protocol_end += 3; } else { protocol_end = 0; - result.protocol = "ws"; // Default + result.protocol = "ws"; } - - // Parse host and port size_t path_start = url.find('/', protocol_end); if (path_start == std::string::npos) { path_start = url.length(); @@ -1303,10 +1145,8 @@ WebSocketURL WebSocketURL::Parse(const std::string& url) { } else { result.path = url.substr(path_start); } - std::string host_port = url.substr(protocol_end, path_start - protocol_end); size_t colon_pos = host_port.find(':'); - if (colon_pos != std::string::npos) { result.host = host_port.substr(0, colon_pos); std::string port_str = host_port.substr(colon_pos + 1); @@ -1315,36 +1155,27 @@ WebSocketURL WebSocketURL::Parse(const std::string& url) { result.host = host_port; result.port = (result.protocol == "wss") ? 443 : 80; } - - // Parse query string size_t query_start = result.path.find('?'); if (query_start != std::string::npos) { result.query = result.path.substr(query_start + 1); result.path = result.path.substr(0, query_start); } - return result; } std::string WebSocketURL::ToString() const { std::stringstream ss; ss << protocol << "://" << host; - if ((protocol == "ws" && port != 80) || (protocol == "wss" && port != 443)) { ss << ":" << port; } - ss << path; - if (!query.empty()) { ss << "?" << query; } - return ss.str(); } -// =============== CompressionContext Implementation =============== - CompressionContext::CompressionContext() = default; CompressionContext::~CompressionContext() { @@ -1355,28 +1186,21 @@ bool CompressionContext::Initialize(bool server, int compression_level) { if (initialized_) { Cleanup(); } - server_ = server; - - // Initialize deflate context for compression deflate_context_ = malloc(sizeof(z_stream)); if (!deflate_context_) { return false; } - z_stream* deflate_stream = static_cast(deflate_context_); deflate_stream->zalloc = Z_NULL; deflate_stream->zfree = Z_NULL; deflate_stream->opaque = Z_NULL; - if (deflateInit2(deflate_stream, compression_level, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) { free(deflate_context_); deflate_context_ = nullptr; return false; } - - // Initialize inflate context for decompression inflate_context_ = malloc(sizeof(z_stream)); if (!inflate_context_) { deflateEnd(deflate_stream); @@ -1384,12 +1208,10 @@ bool CompressionContext::Initialize(bool server, int compression_level) { deflate_context_ = nullptr; return false; } - z_stream* inflate_stream = static_cast(inflate_context_); inflate_stream->zalloc = Z_NULL; inflate_stream->zfree = Z_NULL; inflate_stream->opaque = Z_NULL; - if (inflateInit2(inflate_stream, -MAX_WBITS) != Z_OK) { free(inflate_context_); inflate_context_ = nullptr; @@ -1398,7 +1220,6 @@ bool CompressionContext::Initialize(bool server, int compression_level) { deflate_context_ = nullptr; return false; } - initialized_ = true; return true; } @@ -1407,19 +1228,15 @@ std::vector CompressionContext::Compress(const std::vector& da if (!initialized_ || !deflate_context_) { return data; } - z_stream* stream = static_cast(deflate_context_); stream->next_in = const_cast(data.data()); stream->avail_in = static_cast(data.size()); - std::vector compressed(data.size()); // Start with same size stream->next_out = compressed.data(); stream->avail_out = static_cast(compressed.size()); - if (deflate(stream, Z_SYNC_FLUSH) != Z_OK) { return data; } - compressed.resize(compressed.size() - stream->avail_out); return compressed; } @@ -1428,19 +1245,15 @@ std::vector CompressionContext::Decompress(const std::vector& if (!initialized_ || !inflate_context_) { return compressed_data; } - z_stream* stream = static_cast(inflate_context_); stream->next_in = const_cast(compressed_data.data()); stream->avail_in = static_cast(compressed_data.size()); - - std::vector decompressed(compressed_data.size() * 2); // Start with double size + std::vector decompressed(compressed_data.size() * 2); stream->next_out = decompressed.data(); stream->avail_out = static_cast(decompressed.size()); - if (inflate(stream, Z_SYNC_FLUSH) != Z_OK) { return compressed_data; } - decompressed.resize(decompressed.size() - stream->avail_out); return decompressed; } @@ -1452,25 +1265,20 @@ void CompressionContext::Cleanup() { free(deflate_context_); deflate_context_ = nullptr; } - if (inflate_context_) { z_stream* stream = static_cast(inflate_context_); inflateEnd(stream); free(inflate_context_); inflate_context_ = nullptr; } - initialized_ = false; } -// =============== MessageFragmenter Implementation =============== - MessageFragmenter::MessageFragmenter(size_t max_frame_size) : max_frame_size_(max_frame_size) {} std::vector MessageFragmenter::FragmentMessage(const WebSocketMessage& message) { if (message.data.size() <= max_frame_size_) { - // Single frame WebSocketFrame frame; frame.opcode = message.opcode; frame.fin = true; @@ -1478,33 +1286,25 @@ std::vector MessageFragmenter::FragmentMessage(const WebSocketMe frame.payload_data = message.data; return {frame}; } - - // Multiple frames std::vector frames; size_t offset = 0; bool first_frame = true; - while (offset < message.data.size()) { WebSocketFrame frame; - if (first_frame) { frame.opcode = message.opcode; first_frame = false; } else { frame.opcode = OP_CONTINUATION; } - size_t chunk_size = std::min(max_frame_size_, message.data.size() - offset); frame.payload_length = chunk_size; frame.payload_data.assign(message.data.begin() + offset, message.data.begin() + offset + chunk_size); - offset += chunk_size; frame.fin = (offset >= message.data.size()); - frames.push_back(frame); } - return frames; } @@ -1521,8 +1321,6 @@ std::vector MessageFragmenter::FragmentBinary(const std::vector< return FragmentMessage(msg); } -// =============== WebSocketRateLimiter Implementation =============== - WebSocketRateLimiter::WebSocketRateLimiter(size_t messages_per_second, size_t burst_size) : messages_per_second_(messages_per_second) , burst_size_(burst_size) @@ -1531,34 +1329,26 @@ WebSocketRateLimiter::WebSocketRateLimiter(size_t messages_per_second, size_t bu bool WebSocketRateLimiter::CheckLimit() { std::lock_guard lock(mutex_); - - // Refill tokens based on elapsed time auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast( now - last_refill_); - size_t refill = (elapsed.count() * messages_per_second_) / 1000; if (refill > 0) { tokens_ = std::min(burst_size_, tokens_ + refill); last_refill_ = now; } - - // Check if we have tokens if (tokens_ > 0) { tokens_--; return true; } - return false; } void WebSocketRateLimiter::Update() { std::lock_guard lock(mutex_); - auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast( now - last_refill_); - size_t refill = (elapsed.count() * messages_per_second_) / 1000; if (refill > 0) { tokens_ = std::min(burst_size_, tokens_ + refill); diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index e66f5c9..5e449a7 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -135,6 +135,8 @@ std::string WebSocketSession::GetRemoteAddress() const { } void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { + Logger::Debug("WebSocket received {} bytes, opcode: {}", msg.data.size(), (int)msg.opcode); + Logger::Debug("WebSocketSession {} received text: {}", sessionId_, msg.GetText()); if (msg.opcode == WebSocketProtocol::OP_TEXT) { if (messageHandler_) { try { @@ -145,7 +147,6 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) } } } else if (msg.opcode == WebSocketProtocol::OP_BINARY) { - // Deserialize as BinaryMessage try { auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize( msg.data.data(), msg.data.size()); diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index a76e351..4629919 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -5,13 +5,11 @@ extern std::atomic g_shutdown; ProcessPool::ProcessPool(const std::vector& groups) : groups_(groups) { - // Compute total number of workers totalWorkers_ = 0; for (const auto& g : groups_) { totalWorkers_ += g.count; } - // Pre-allocate vectors workers_.resize(totalWorkers_); workerPipes_.resize(totalWorkers_ * 2, -1); } @@ -31,7 +29,6 @@ bool ProcessPool::Initialize() { running_.store(true); shutdownRequested_.store(false); - // Create pipes for each worker for (int i = 0; i < totalWorkers_; ++i) { CreateWorkerPipe(i); } @@ -46,6 +43,13 @@ void ProcessPool::CreateWorkerPipe(int globalWorkerId) { return; } +#ifdef __linux__ + int bufferSize = 1024 * 1024; + if (fcntl(pipefd[0], F_SETPIPE_SZ, bufferSize) == -1) { + Logger::Warn("Failed to set pipe buffer size: {}", strerror(errno)); + } +#endif + int read_idx = globalWorkerId * 2; int write_idx = globalWorkerId * 2 + 1; @@ -68,13 +72,6 @@ void ProcessPool::Run() { if (getpid() == masterPid_) { masterThread_ = std::thread(&ProcessPool::MasterProcess, this); } else { - // Worker process: we need to know which global worker ID and group config we have. - // This is set in the child before calling Run(), but here we need to retrieve it. - // We'll set a member variable in the child branch before calling Run. - // However, the constructor only runs in the master; for workers, we need to - // set these after fork. The child branch of MasterProcess will set them. - // So this branch should never be executed directly because we call WorkerProcess - // explicitly from the child after fork. Logger::Error("Worker process started without proper initialization"); } } @@ -85,7 +82,6 @@ void ProcessPool::MasterProcess() { sigset_t oldset; BlockSignals(&oldset); - // Spawn workers in order of groups int globalWorkerId = 0; for (size_t gidx = 0; gidx < groups_.size(); ++gidx) { const auto& group = groups_[gidx]; @@ -93,33 +89,26 @@ void ProcessPool::MasterProcess() { pid_t pid = fork(); if (pid == 0) { - // Child process signal(SIGINT, SIG_IGN); - // SIGTERM handler: sets the global shutdown flag (async‑safe) signal(SIGTERM, [](int) { g_shutdown.store(true, std::memory_order_relaxed); }); UnblockSignals(&oldset); - // Permanently block SIGINT only (SIGTERM remains unblocked) sigset_t block_int; sigemptyset(&block_int); sigaddset(&block_int, SIGINT); pthread_sigmask(SIG_BLOCK, &block_int, nullptr); - // Close all pipe ends except the one for this worker for (int i = 0; i < totalWorkers_ * 2; ++i) { if (i != globalWorkerId * 2) { if (workerPipes_[i] != -1) close(workerPipes_[i]); } } - // Set worker process name std::string processName = "game_worker_" + std::to_string(globalWorkerId); prctl(PR_SET_NAME, processName.c_str(), 0, 0, 0); - // Store config for this worker groupConfig_ = group; - role_ = ProcessRole::WORKER; Logger::Info("Worker {} started (PID: {}) for group {} ({}:{})", @@ -127,22 +116,18 @@ void ProcessPool::MasterProcess() { WorkerProcess(globalWorkerId, group); - // Cleanup: close read end of pipe if (workerPipes_[globalWorkerId * 2] != -1) close(workerPipes_[globalWorkerId * 2]); _exit(0); } else if (pid > 0) { - // Master: record worker info workers_[globalWorkerId] = {pid, static_cast(gidx), w, group}; - // Close the read end in master if (workerPipes_[globalWorkerId * 2] != -1) { close(workerPipes_[globalWorkerId * 2]); workerPipes_[globalWorkerId * 2] = -1; } - // Health tracking { std::lock_guard lock(healthMutex_); workerHealth_[globalWorkerId] = {pid, time(nullptr)}; @@ -157,7 +142,6 @@ void ProcessPool::MasterProcess() { UnblockSignals(&oldset); - // Main loop: monitor workers and check for shutdown while (running_.load() && !shutdownRequested_.load()) { CleanupDeadWorkers(); for (int i = 0; i < 10 && running_.load() && !shutdownRequested_.load(); ++i) { @@ -165,7 +149,6 @@ void ProcessPool::MasterProcess() { } } - // Send SIGTERM to all workers for (int i = 0; i < totalWorkers_; ++i) { if (workers_[i].pid > 0) { Logger::Info("Terminating worker {} (PID: {})", i, workers_[i].pid); @@ -173,7 +156,6 @@ void ProcessPool::MasterProcess() { } } - // Wait for all workers to exit int status; for (int i = 0; i < totalWorkers_; ++i) { if (workers_[i].pid > 0) { @@ -234,11 +216,9 @@ void ProcessPool::CloseAllPipes() { } void ProcessPool::RestartWorker(int globalWorkerId) { - // Find which group this worker belongs to const auto& oldInfo = workers_[globalWorkerId]; const auto& group = oldInfo.config; - // Create new pipe for this worker int pipefd[2]; if (pipe(pipefd) == -1) { Logger::Error("Failed to create pipe for restarting worker {}: {}", globalWorkerId, strerror(errno)); @@ -256,19 +236,16 @@ void ProcessPool::RestartWorker(int globalWorkerId) { pid_t pid = fork(); if (pid == 0) { - // Child signal(SIGINT, SIG_IGN); signal(SIGTERM, [](int) { g_shutdown.store(true, std::memory_order_relaxed); }); UnblockSignals(&oldset); - // Block SIGINT permanently sigset_t block_int; sigemptyset(&block_int); sigaddset(&block_int, SIGINT); pthread_sigmask(SIG_BLOCK, &block_int, nullptr); - // Close all pipe ends except this worker's read end for (int i = 0; i < totalWorkers_ * 2; ++i) { if (i != globalWorkerId * 2) { if (workerPipes_[i] != -1) close(workerPipes_[i]); @@ -289,16 +266,13 @@ void ProcessPool::RestartWorker(int globalWorkerId) { _exit(0); } else if (pid > 0) { - // Master workers_[globalWorkerId].pid = pid; - // Close read end in master if (workerPipes_[globalWorkerId * 2] != -1) { close(workerPipes_[globalWorkerId * 2]); workerPipes_[globalWorkerId * 2] = -1; } - // Set non‑blocking for write end fcntl(workerPipes_[globalWorkerId * 2 + 1], F_SETFL, O_NONBLOCK); { @@ -466,7 +440,6 @@ bool ProcessPool::SendToWorker(int workerId, const std::string& message) { Logger::Error("Failed to send message content to worker {}", workerId); return false; } - //Logger::Debug("Sent {} bytes to worker {}", message.length(), workerId); return true; } @@ -479,6 +452,15 @@ std::string ProcessPool::ReceiveFromMaster() { if (read_fd == -1) { return ""; } + + fd_set fds; + FD_ZERO(&fds); + FD_SET(read_fd, &fds); + struct timeval tv = {0, 0}; + if (select(read_fd + 1, &fds, nullptr, nullptr, &tv) <= 0) { + return ""; + } + uint32_t net_len = 0; if (!ReadAll(read_fd, &net_len, sizeof(net_len), true)) { return ""; @@ -499,9 +481,7 @@ std::string ProcessPool::ReceiveFromMaster() { return ""; } buffer[msg_len] = '\0'; - std::string message(buffer.data(), msg_len); - //Logger::Debug("Received {} bytes from master", msg_len); - return message; + return std::string(buffer.data(), msg_len); } bool ProcessPool::IsWorkerAlive(int workerId) const { From ed84dc92124b0396309a7f50721634469dad1973 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Sat, 18 Apr 2026 03:18:37 +0300 Subject: [PATCH 03/14] fixed websocket answers --- CMakeLists.txt | 19 ++++++ build.sh | 28 ++++++--- include/game/LogicWorld.hpp | 3 +- include/game/WorldGenerator.hpp | 4 +- include/network/WebSocketSession.hpp | 2 + src/game/GameLogic.cpp | 90 ++++++++++++++++------------ src/game/LogicWorld.cpp | 29 ++++++--- src/game/WorldChunk.cpp | 2 - src/game/WorldGenerator.cpp | 56 ++++++++++------- src/main.cpp | 13 ++++ src/network/GameServer.cpp | 37 +++++++++--- src/network/WebSocketProtocol.cpp | 14 ++++- src/network/WebSocketSession.cpp | 40 ++++++++++--- 13 files changed, 240 insertions(+), 97 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52c15b4..7f9d456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,8 @@ include_directories( ${OPENSSL_INCLUDE_DIR} ) +option(ENABLE_ASAN "Enable AddressSanitizer and UndefinedBehaviorSanitizer" OFF) + # Main executable add_executable(gameserver src/main.cpp @@ -177,6 +179,23 @@ add_executable(gameserver ${DATABASE_SOURCES} ) +if(ENABLE_ASAN) + message(STATUS "Enabling AddressSanitizer and UndefinedBehaviorSanitizer") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(gameserver PRIVATE + -fsanitize=address,undefined + -fno-omit-frame-pointer + -g + -O1 + ) + target_link_options(gameserver PRIVATE + -fsanitize=address,undefined + ) + elseif(MSVC) + target_compile_options(gameserver PRIVATE /fsanitize=address) + endif() +endif() + # Add target-specific preprocessor definitions if(USE_CITUS) target_compile_definitions(gameserver PRIVATE USE_CITUS=1) diff --git a/build.sh b/build.sh index f1d025a..c68b285 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,8 @@ #!/bin/bash +# save original home directory path +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # Clone dependencies mkdir -p thirdparty cd thirdparty @@ -34,9 +37,10 @@ sudo apt-get install -y \ libspdlog-dev \ nlohmann-json3-dev -# Parse command line arguments for optional database backends +# Parse command line arguments USE_CITUS=OFF USE_SQLITE=OFF +ENABLE_ASAN=OFF for arg in "$@"; do case $arg in @@ -50,14 +54,17 @@ for arg in "$@"; do sudo apt-get install -y libsqlite3-dev USE_SQLITE=ON ;; + --with-asan) + echo "Enabling AddressSanitizer and UndefinedBehaviorSanitizer" + ENABLE_ASAN=ON + ;; *) - # ignore unknown ;; esac done # Build configuration -echo "Building with Citus: $USE_CITUS, SQLite: $USE_SQLITE" +echo "Building with Citus: $USE_CITUS, SQLite: $USE_SQLITE, ASan: $ENABLE_ASAN" # Clean previous build artifacts rm -f CMakeCache.txt Makefile cmake_install.cmake @@ -65,25 +72,22 @@ rm -rf CMakeFiles # Create build directory and copy related folders mkdir -p build -rsync -a --delete config/ build/config/ -rsync -a --delete dbschema/ build/dbschema/ cd build +find . -mindepth 1 -maxdepth 1 ! -name "certs" -exec rm -rf {} + # Run CMake cmake .. -B . \ -DUSE_CITUS=${USE_CITUS} \ -DUSE_SQLITE=${USE_SQLITE} \ + -DENABLE_ASAN=${ENABLE_ASAN} \ -DCMAKE_BUILD_TYPE=Debug # Build make -j$(nproc) # ========== SSL Certificate Generation ========== -# Generate self-signed SSL certificates if missing if command -v openssl &> /dev/null; then - # Create certs directory if needed mkdir -p certs - # Generate server certificate and key if not present if [ ! -f "certs/server.crt" ] || [ ! -f "certs/server.key" ]; then echo "Generating self-signed SSL certificate..." openssl req -x509 -newkey rsa:4096 \ @@ -93,7 +97,6 @@ if command -v openssl &> /dev/null; then -subj "/CN=localhost" echo "SSL certificate and key created in certs/" fi - # Generate DH parameters if not present (optional but may be used) if [ ! -f "certs/dhparam.pem" ]; then echo "Generating DH parameters (this may take a moment)..." openssl dhparam -out certs/dhparam.pem 2048 @@ -114,3 +117,10 @@ fi # create default database user (commented out by default) #sudo -u postgres psql -c "DROP USER IF EXISTS gameuser;" #sudo -u postgres psql -c "CREATE USER gameuser WITH PASSWORD 'password' SUPERUSER;" + +echo "Go to $SCRIPT_DIR" +cd "$SCRIPT_DIR" || exit +echo "Copy config to build folder ..." +rsync -a --delete config/ build/config/ +echo "Copy dbschema to build folder ..." +rsync -a --delete dbschema/ build/dbschema/ diff --git a/include/game/LogicWorld.hpp b/include/game/LogicWorld.hpp index 70aeabd..575e6d6 100644 --- a/include/game/LogicWorld.hpp +++ b/include/game/LogicWorld.hpp @@ -78,4 +78,5 @@ class LogicWorld { std::unordered_map> entities_; mutable std::mutex entitiesMutex_; std::atomic currentTimeOfDay_{0.0f}; // 0.0 to 1.0 -}; \ No newline at end of file + uint32_t canary_ = 0xDEADBEEF; +}; diff --git a/include/game/WorldGenerator.hpp b/include/game/WorldGenerator.hpp index 244d0d0..40e347a 100644 --- a/include/game/WorldGenerator.hpp +++ b/include/game/WorldGenerator.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ class WorldGenerator { GenerationConfig config_; std::mt19937 rng_; std::uniform_real_distribution dist_; + mutable std::recursive_mutex rngMutex_; // recursive to allow nested locks float Noise(float x, float y); float FractalNoise(float x, float y); @@ -56,4 +58,4 @@ class WorldGenerator { void GenerateMountainFeatures(WorldChunk& chunk); void GenerateDesertFeatures(WorldChunk& chunk); void GeneratePlainsFeatures(WorldChunk& chunk); -}; +}; \ No newline at end of file diff --git a/include/network/WebSocketSession.hpp b/include/network/WebSocketSession.hpp index f9f231c..9beb56b 100644 --- a/include/network/WebSocketSession.hpp +++ b/include/network/WebSocketSession.hpp @@ -22,6 +22,8 @@ class WebSocketSession : public IConnection, public std::enable_shared_from_this bool IsConnected() const override; uint64_t GetSessionId() const override; + void SendError(const std::string& message, int code = 500); + void Send(const nlohmann::json& message) override; void SendRaw(const std::string& data) override; void SendBinary(uint16_t message_type, const std::vector& data) override; diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 6406977..f084cea 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -28,11 +28,8 @@ void GameLogic::Initialize() { Logger::Warn("GameLogic already initialized"); return; } - Logger::Info("Initializing GameLogic with world system..."); - auto& config = ConfigManager::GetInstance(); - WorldConfig worldConfig; worldConfig.seed = config.GetInt("world.seed", 12345); worldConfig.viewDistance = config.GetInt("world.view_distance", 4); @@ -42,26 +39,19 @@ void GameLogic::Initialize() { worldConfig.maxTerrainHeight = config.GetFloat("world.max_terrain_height", 50.0f); worldConfig.waterLevel = config.GetFloat("world.water_level", 10.0f); worldConfig.chunkUnloadDistance = config.GetFloat("world.chunk_unload_distance", 200.0f); - SetWorldConfig(worldConfig); - LogicWorld::GetInstance().Initialize(worldConfig); - - int preloadRadius = 10; + int preloadRadius = 1; for (int x = -preloadRadius; x <= preloadRadius; ++x) { for (int z = -preloadRadius; z <= preloadRadius; ++z) { GetOrCreateChunk(x, z); } } - LogicEntity::GetInstance().Initialize(); - if (!LoadGameData()) { Logger::Error("Failed to load game data"); } - RegisterWorldHandlers(); - Logger::Info("GameLogic world system initialized successfully"); } @@ -472,45 +462,55 @@ void GameLogic::RegisterWorldHandlers() { } void GameLogic::HandleMessage(uint64_t sessionId, const nlohmann::json& message) { - Logger::Debug("GameLogic handling from session: {}; message: {}", sessionId, message.dump()); - FirePythonEvent("game_message", { - {"sessionId", sessionId}, - {"message", message} - }); - + Logger::Debug("GameLogic::HandleMessage called for session {}", sessionId); + Logger::Debug("Message content: {}", message.dump()); std::string type = message.value("type", ""); - - // Handle known message types directly + Logger::Debug("Message type: '{}'", type); if (type == "world_chunk_request") { + Logger::Debug("Dispatching to HandleWorldChunkRequestJson"); HandleWorldChunkRequestJson(sessionId, message); - } else if (type == "authentication") { + } + else if (type == "authentication") { std::string username = message.value("login", ""); std::string password = message.value("password", ""); + Logger::Debug("Dispatching authentication for user '{}'", username); HandleAuthentication(sessionId, username, password); - } else if (type == "player_position_update") { + } + else if (type == "player_position_update") { HandlePlayerPositionUpdate(sessionId, message); - } else if (type == "npc_interaction") { + } + else if (type == "npc_interaction") { HandleNPCInteraction(sessionId, message); - } else if (type == "collision_check") { + } + else if (type == "collision_check") { HandleCollisionCheck(sessionId, message); - } else if (type == "familiar_command") { + } + else if (type == "familiar_command") { HandleFamiliarCommand(sessionId, message); - } else if (type == "entity_spawn_request") { + } + else if (type == "entity_spawn_request") { HandleEntitySpawnRequest(sessionId, message); - } else if (type == "loot_pickup") { + } + else if (type == "loot_pickup") { HandleLootPickup(sessionId, message); - } else if (type == "inventory_move") { + } + else if (type == "inventory_move") { HandleInventoryMove(sessionId, message); - } else if (type == "item_use") { + } + else if (type == "item_use") { HandleItemUse(sessionId, message); - } else if (type == "item_drop") { + } + else if (type == "item_drop") { HandleItemDrop(sessionId, message); - } else if (type == "trade_request") { + } + else if (type == "trade_request") { HandleTradeRequest(sessionId, message); - } else if (type == "gold_transaction") { + } + else if (type == "gold_transaction") { HandleGoldTransaction(sessionId, message); - } else { - // Fallback to base class for unknown types + } + else { + Logger::Warn("Unknown message type '{}' from session {}", type, sessionId); LogicCore::HandleMessage(sessionId, message); } } @@ -563,8 +563,9 @@ void GameLogic::HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json } void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data) { + Logger::Debug("HandleWorldChunkRequestJson called for session {}", sessionId); + Logger::Debug("Request data: {}", data.dump()); try { - Logger::Debug("JSON chunk request received: {}", data.dump()); int chunkX = data.value("chunkX", 0); int chunkZ = data.value("chunkZ", 0); int lod = data.value("lod", 0); @@ -572,8 +573,9 @@ void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann:: auto start = std::chrono::steady_clock::now(); auto chunk = GetOrCreateChunk(chunkX, chunkZ); auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); - Logger::Debug("Chunk ({},{}) generated in {} ms", chunkX, chunkZ, elapsed.count()); + Logger::Debug("GetOrCreateChunk took {} ms", elapsed.count()); if (!chunk) { + Logger::Error("Failed to get or create chunk ({},{})", chunkX, chunkZ); SendError(sessionId, "Failed to generate chunk", 404); return; } @@ -585,11 +587,23 @@ void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann:: {"data", chunk->Serialize()}, {"timestamp", GetCurrentTimestamp()} }; + Logger::Debug("Sending chunk response: {}", response.dump()); SendToSession(sessionId, response); Logger::Debug("Chunk response sent for ({},{})", chunkX, chunkZ); - } catch (const std::exception& e) { - Logger::Error("Exception in HandleWorldChunkRequestJson: {}", e.what()); - SendError(sessionId, "Internal server error", 500); + } catch (const std::exception& err) { + Logger::Error("Exception in HandleWorldChunkRequestJson: {}", err.what()); + try { + SendError(sessionId, "Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", sessionId); + } + } catch (...) { + Logger::Error("Unknown exception in HandleWorldChunkRequestJson"); + try { + SendError(sessionId, "Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", sessionId); + } } } diff --git a/src/game/LogicWorld.cpp b/src/game/LogicWorld.cpp index c9c1a1e..8130eb5 100644 --- a/src/game/LogicWorld.cpp +++ b/src/game/LogicWorld.cpp @@ -45,15 +45,29 @@ std::string LogicWorld::GetChunkKey(int chunkX, int chunkZ) const { } std::shared_ptr LogicWorld::GetOrCreateChunk(int chunkX, int chunkZ) { - std::string chunkKey = GetChunkKey(chunkX, chunkZ); - std::lock_guard lock(chunksMutex_); + Logger::Debug("worldGenerator_ raw pointer: {}", static_cast(worldGenerator_.get())); + if (canary_ != 0xDEADBEEF) { + Logger::Critical("LogicWorld memory corrupted! canary=0x{:x}", canary_); + std::abort(); + } + + // --- Workaround: reinitialize generator if lost --- + if (!worldGenerator_) { + Logger::Error("World generator was null! Reinitializing..."); + GenerationConfig genConfig; + genConfig.seed = worldConfig_.seed; + genConfig.terrainScale = worldConfig_.terrainScale; + genConfig.terrainHeight = worldConfig_.maxTerrainHeight; + genConfig.waterLevel = worldConfig_.waterLevel; + worldGenerator_ = std::make_unique(genConfig); + } + std::string chunkKey = GetChunkKey(chunkX, chunkZ); auto it = loadedChunks_.find(chunkKey); if (it != loadedChunks_.end()) { return it->second; } - if (activeChunkCount_ >= worldConfig_.maxActiveChunks) { if (!loadedChunks_.empty()) { auto first = loadedChunks_.begin(); @@ -61,17 +75,18 @@ std::shared_ptr LogicWorld::GetOrCreateChunk(int chunkX, int chunkZ) activeChunkCount_--; } } - + if (!worldGenerator_) { + Logger::Error("World generator is null! Cannot generate chunk ({},{})", chunkX, chunkZ); + return nullptr; + } std::unique_ptr uniqueChunk = worldGenerator_->GenerateChunk(chunkX, chunkZ); if (!uniqueChunk) { Logger::Error("Failed to generate chunk [{}, {}]", chunkX, chunkZ); return nullptr; } - std::shared_ptr chunk = std::move(uniqueChunk); loadedChunks_[chunkKey] = chunk; activeChunkCount_++; - Logger::Debug("Generated chunk [{}, {}], total: {}", chunkX, chunkZ, activeChunkCount_.load()); return chunk; } @@ -186,4 +201,4 @@ void LogicWorld::SetTimeOfDay(float time) { float LogicWorld::GetTimeOfDay() const { return currentTimeOfDay_.load(std::memory_order_relaxed); -} \ No newline at end of file +} diff --git a/src/game/WorldChunk.cpp b/src/game/WorldChunk.cpp index 9d617cc..e6d02e0 100644 --- a/src/game/WorldChunk.cpp +++ b/src/game/WorldChunk.cpp @@ -14,7 +14,6 @@ BlockType WorldChunk::GetBlock(int x, int y, int z) const { if (x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE) { return BlockType::AIR; } - int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; return blocks_[index]; } @@ -23,7 +22,6 @@ void WorldChunk::SetBlock(int x, int y, int z, BlockType type) { if (x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE) { return; } - int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; blocks_[index] = type; } diff --git a/src/game/WorldGenerator.cpp b/src/game/WorldGenerator.cpp index 976acc9..5570408 100644 --- a/src/game/WorldGenerator.cpp +++ b/src/game/WorldGenerator.cpp @@ -1,8 +1,5 @@ #include "game/WorldGenerator.hpp" -// Initialize static members -//const float WorldChunk::BLOCK_SIZE = 1.0f; -//const float WorldChunk::CHUNK_WIDTH = WorldChunk::CHUNK_SIZE * WorldChunk::BLOCK_SIZE; WorldGenerator::WorldGenerator(const GenerationConfig& config) : config_(config), rng_(config.seed), dist_(-1.0f, 1.0f) { @@ -16,7 +13,7 @@ std::unique_ptr WorldGenerator::GenerateChunk(int chunkX, int chunkZ // Generate blocks based on heightmap const int chunkSize = WorldChunk::CHUNK_SIZE; - const int worldSize = 256; // Arbitrary world height + const int worldHeight = chunkSize; // Use chunk height (can be increased later) for (int x = 0; x < chunkSize; ++x) { for (int z = 0; z < chunkSize; ++z) { @@ -31,8 +28,8 @@ std::unique_ptr WorldGenerator::GenerateChunk(int chunkX, int chunkZ BiomeType biome = GetBiomeAt(worldX, worldZ); chunk->SetBiome(biome); - // Generate column of blocks - for (int y = 0; y < worldSize; ++y) { + // Generate column of blocks (only up to chunk height) + for (int y = 0; y < worldHeight; ++y) { if (y < height) { // Below ground - generate appropriate block type BlockType type = BlockType::STONE; @@ -68,7 +65,7 @@ std::unique_ptr WorldGenerator::GenerateChunk(int chunkX, int chunkZ } } - // Add biome-specific features + // Add biome-specific features (locking is handled inside each function) switch (chunk->GetBiome()) { case BiomeType::FOREST: GenerateForestFeatures(*chunk); @@ -100,6 +97,8 @@ std::unique_ptr WorldGenerator::GenerateChunk(int chunkX, int chunkZ BiomeType WorldGenerator::GetBiomeAt(float x, float z) { // Use noise to determine biome float noiseValue = FractalNoise(x / 1000.0f, z / 1000.0f); + // Avoid division by near-zero + if (std::abs(noiseValue) < 0.001f) noiseValue = 0.001f; float temperature = FractalNoise(x / noiseValue * 8.0f, z / noiseValue * 8.0f); float humidity = FractalNoise(x / noiseValue * 7.0f, z / noiseValue * 7.0f); @@ -145,6 +144,7 @@ float WorldGenerator::GetTerrainHeight(float x, float z) { } void WorldGenerator::SetSeed(int seed) { + std::lock_guard lock(rngMutex_); config_.seed = seed; rng_.seed(seed); } @@ -192,18 +192,17 @@ void WorldGenerator::GenerateLowPolyTerrain(WorldChunk& chunk, int chunkX, int c // This function populates the chunk's heightmap const int chunkSize = WorldChunk::CHUNK_SIZE; - for (int x = 0; x <= chunkSize; ++x) { - for (int z = 0; z <= chunkSize; ++z) { + // Loop over the grid size that matches heightmap_ dimensions + for (int x = 0; x < chunkSize; ++x) { + for (int z = 0; z < chunkSize; ++z) { float worldX = (chunkX * chunkSize + x) * WorldChunk::BLOCK_SIZE; float worldZ = (chunkZ * chunkSize + z) * WorldChunk::BLOCK_SIZE; float height = GetTerrainHeight(worldX, worldZ); - // Store in heightmap (need to convert to 1D index) - int index = z * (chunkSize + 1) + x; - if (index < chunkSize * chunkSize) { - chunk.heightmap_[index] = height; - } + // Corrected 1D index: heightmap_ is sized chunkSize * chunkSize + int index = z * chunkSize + x; + chunk.heightmap_[index] = height; } } } @@ -211,6 +210,8 @@ void WorldGenerator::GenerateLowPolyTerrain(WorldChunk& chunk, int chunkX, int c void WorldGenerator::AddTrees(WorldChunk& chunk, BiomeType biome) { if (biome != BiomeType::FOREST) return; + std::lock_guard lock(rngMutex_); + const int chunkSize = WorldChunk::CHUNK_SIZE; std::uniform_int_distribution treeDist(0, 10); @@ -230,7 +231,7 @@ void WorldGenerator::AddTrees(WorldChunk& chunk, BiomeType biome) { int baseY = static_cast(height); for (int y = 0; y < treeHeight; ++y) { - if (baseY + y < 256) { // World height limit + if (baseY + y < chunkSize) { chunk.SetBlock(x, baseY + y, z, BlockType::WOOD); } } @@ -250,7 +251,7 @@ void WorldGenerator::AddTrees(WorldChunk& chunk, BiomeType biome) { if (leafX >= 0 && leafX < chunkSize && leafZ >= 0 && leafZ < chunkSize && - leafY < 256) { + leafY < chunkSize) { chunk.SetBlock(leafX, leafY, leafZ, BlockType::LEAVES); } } @@ -266,6 +267,8 @@ void WorldGenerator::AddTrees(WorldChunk& chunk, BiomeType biome) { void WorldGenerator::AddRocks(WorldChunk& chunk, BiomeType biome) { if (biome != BiomeType::MOUNTAIN && biome != BiomeType::DESERT) return; + std::lock_guard lock(rngMutex_); + const int chunkSize = WorldChunk::CHUNK_SIZE; std::uniform_int_distribution rockDist(0, 20); @@ -282,7 +285,7 @@ void WorldGenerator::AddRocks(WorldChunk& chunk, BiomeType biome) { int baseY = static_cast(height); // Place a 1x1x1 rock - if (baseY < 255) { + if (baseY < chunkSize - 1) { chunk.SetBlock(x, baseY, z, BlockType::STONE); } } @@ -296,7 +299,7 @@ void WorldGenerator::AddWaterPlane(WorldChunk& chunk) { int waterY = static_cast(config_.waterLevel); // Only add water plane if water level is within chunk bounds - if (waterY >= 0 && waterY < 256) { + if (waterY >= 0 && waterY < chunkSize) { for (int x = 0; x < chunkSize; ++x) { for (int z = 0; z < chunkSize; ++z) { // Check if this position should have water @@ -314,6 +317,7 @@ void WorldGenerator::AddWaterPlane(WorldChunk& chunk) { } void WorldGenerator::GenerateForestFeatures(WorldChunk& chunk) { + // Locking is done inside AddTrees and AddRocks AddTrees(chunk, BiomeType::FOREST); AddRocks(chunk, BiomeType::FOREST); } @@ -321,6 +325,8 @@ void WorldGenerator::GenerateForestFeatures(WorldChunk& chunk) { void WorldGenerator::GenerateMountainFeatures(WorldChunk& chunk) { AddRocks(chunk, BiomeType::MOUNTAIN); + std::lock_guard lock(rngMutex_); + // Add snow on high mountains const int chunkSize = WorldChunk::CHUNK_SIZE; int snowLevel = static_cast(config_.waterLevel + 20); @@ -334,7 +340,7 @@ void WorldGenerator::GenerateMountainFeatures(WorldChunk& chunk) { if (height > snowLevel) { int snowY = static_cast(height); - if (snowY < 256) { + if (snowY < chunkSize) { // Replace top block with snow chunk.SetBlock(x, snowY, z, BlockType::SNOW); } @@ -346,6 +352,8 @@ void WorldGenerator::GenerateMountainFeatures(WorldChunk& chunk) { void WorldGenerator::GenerateDesertFeatures(WorldChunk& chunk) { AddRocks(chunk, BiomeType::DESERT); + std::lock_guard lock(rngMutex_); + // Add occasional cactus const int chunkSize = WorldChunk::CHUNK_SIZE; std::uniform_int_distribution cactusDist(0, 30); @@ -363,7 +371,7 @@ void WorldGenerator::GenerateDesertFeatures(WorldChunk& chunk) { int cactusHeight = 2 + (rng_() % 3); for (int y = 0; y < cactusHeight; ++y) { - if (baseY + y < 256) { + if (baseY + y < chunkSize) { // Use wood block as placeholder for cactus chunk.SetBlock(x, baseY + y, z, BlockType::WOOD); } @@ -375,6 +383,8 @@ void WorldGenerator::GenerateDesertFeatures(WorldChunk& chunk) { } void WorldGenerator::GeneratePlainsFeatures(WorldChunk& chunk) { + std::unique_lock lock(rngMutex_); + // Plains have few features - just occasional grass/trees const int chunkSize = WorldChunk::CHUNK_SIZE; std::uniform_int_distribution featureDist(0, 50); @@ -392,14 +402,16 @@ void WorldGenerator::GeneratePlainsFeatures(WorldChunk& chunk) { // Small chance for a tree if (rng_() % 10 == 0) { + lock.unlock(); AddTrees(chunk, BiomeType::FOREST); + lock.lock(); } // Otherwise just place a tall grass block (using leaves as placeholder) - else if (baseY < 255) { + else if (baseY < chunkSize - 1) { chunk.SetBlock(x, baseY, z, BlockType::LEAVES); } } } } } -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b9c60a0..3903da2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,9 @@ #include #include +#include +//#include + #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" @@ -23,6 +26,14 @@ void SignalHandler(int signal) { g_shutdown.store(true); } +void crash_handler(int sig) { + void* array[20]; + size_t size = backtrace(array, 20); + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + _exit(1); +} + // Worker main signature: receives group config void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* processPool = nullptr, const std::string& path_config = "config.json") { try { @@ -231,6 +242,8 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* int main(int argc, char* argv[]) { std::signal(SIGINT, SignalHandler); std::signal(SIGTERM, SignalHandler); + //std::signal(SIGSEGV, crash_handler); + //std::signal(SIGABRT, crash_handler); Logger::InitializeDefaults(); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index 494c596..9983dc3 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -63,11 +63,15 @@ void GameServer::Run() { StartWorkerThreads(); Logger::Info("GameServer started with {} IO threads for protocol '{}'", ioThreads_, groupConfig_.protocol); - ioContext_.run(); + try { + ioContext_.run(); + } catch (const std::exception& err) { + Logger::Critical("Unhandled exception in IO context: {}", err.what()); + } catch (...) { + Logger::Critical("Unknown exception in IO context"); + } for (auto& thread : workerThreads_) { - if (thread.joinable()) { - thread.join(); - } + if (thread.joinable()) thread.join(); } Logger::Info("GameServer run finished for protocol '{}'", groupConfig_.protocol); } @@ -76,7 +80,6 @@ void GameServer::DoAccept() { acceptor_.async_accept( [this](std::error_code ec, asio::ip::tcp::socket socket) { if (!ec) { - // Apply socket options if (groupConfig_.tcp_nodelay) { asio::ip::tcp::no_delay option(true); socket.set_option(option); @@ -89,7 +92,6 @@ void GameServer::DoAccept() { asio::socket_base::receive_buffer_size option(groupConfig_.receive_buffer_size); socket.set_option(option); } - if (groupConfig_.protocol == "binary") { if (sessionFactory_) { auto session = sessionFactory_(std::move(socket), sslContext_); @@ -106,8 +108,29 @@ void GameServer::DoAccept() { GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data); }); session->SetMessageHandler([session](const nlohmann::json& msg) { - GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); + try { + GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); + } catch (const std::exception& err) { + Logger::Error("Exception in message handler for session {}: {}", + session->GetSessionId(), err.what()); + try { + session->SendError("Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", + session->GetSessionId()); + } + } catch (...) { + Logger::Error("Unknown exception in message handler for session {}", + session->GetSessionId()); + try { + session->SendError("Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", + session->GetSessionId()); + } + } }); + Logger::Debug("WebSocket message handlers set for session {}", session->GetSessionId()); ConnectionManager::GetInstance().Start(session); session->Start(); } else { diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index ecbb314..084ebb9 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -660,8 +660,9 @@ void WebSocketConnection::DoWrite() { return; } asio::async_write(socket_, asio::buffer(write_buffer_), - [self](std::error_code ec, size_t bytes_transferred) { - //Logger::Debug("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); + [self](std::error_code ec, size_t bytes_transferred) { + Logger::Debug("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); + try { if (ec) { self->HandleError(ec); return; @@ -672,7 +673,14 @@ void WebSocketConnection::DoWrite() { if (!self->write_buffer_.empty()) { self->DoWrite(); } - }); + } catch (const std::exception& err) { + Logger::Critical("Exception in WebSocket write handler: {}", err.what()); + self->Close(1011, "Internal error"); + } catch (...) { + Logger::Critical("Unknown exception in WebSocket write handler"); + self->Close(1011, "Internal error"); + } + }); } void WebSocketConnection::SendText(const std::string& text) { diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index 5e449a7..181a5df 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -20,6 +20,23 @@ void WebSocketSession::Disconnect() { wsConn_->Close(1000, "Disconnect"); } bool WebSocketSession::IsConnected() const { return wsConn_->IsOpen(); } uint64_t WebSocketSession::GetSessionId() const { return sessionId_; } +void WebSocketSession::SendError(const std::string& message, int code) { + try { + nlohmann::json error = { + {"type", "error"}, + {"code", code}, + {"message", message}, + {"timestamp", std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count()} + }; + Send(error); + } catch (const std::exception& err) { + Logger::Error("WebSocketSession::SendError failed: {}", err.what()); + } catch (...) { + Logger::Error("WebSocketSession::SendError failed with unknown exception"); + } +} + void WebSocketSession::Send(const nlohmann::json& message) { wsConn_->SendJson(message); } @@ -135,21 +152,30 @@ std::string WebSocketSession::GetRemoteAddress() const { } void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { - Logger::Debug("WebSocket received {} bytes, opcode: {}", msg.data.size(), (int)msg.opcode); - Logger::Debug("WebSocketSession {} received text: {}", sessionId_, msg.GetText()); + Logger::Debug("WebSocketSession {} received {} bytes, opcode: {}", + sessionId_, msg.data.size(), (int)msg.opcode); + if (msg.opcode == WebSocketProtocol::OP_TEXT) { + std::string text = msg.GetText(); + Logger::Debug("WebSocketSession {} received TEXT: {}", sessionId_, text); + if (messageHandler_) { try { - auto json = nlohmann::json::parse(msg.GetText()); + auto json = nlohmann::json::parse(text); + Logger::Debug("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); messageHandler_(json); } catch (const std::exception& e) { - Logger::Error("WebSocketSession: invalid JSON: {}", e.what()); + Logger::Error("WebSocketSession {} invalid JSON: {}", sessionId_, e.what()); } + } else { + Logger::Error("WebSocketSession {} has no messageHandler_ set!", sessionId_); } - } else if (msg.opcode == WebSocketProtocol::OP_BINARY) { + } + else if (msg.opcode == WebSocketProtocol::OP_BINARY) { + Logger::Debug("WebSocketSession {} received BINARY ({} bytes)", sessionId_, msg.data.size()); try { - auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize( - msg.data.data(), msg.data.size()); + auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize(msg.data.data(), msg.data.size()); + Logger::Debug("WebSocketSession {} binary message type: {}", sessionId_, binaryMsg.header.message_type); if (binary_handler_) { binary_handler_(binaryMsg.header.message_type, binaryMsg.data); } else if (default_binary_handler_) { From 3a7c10d375dd1473f33e05d08748f2079d4c7bca Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Sat, 18 Apr 2026 05:49:05 +0300 Subject: [PATCH 04/14] fixed sqlite threaded access --- include/game/GameLogic.hpp | 1 + include/game/WorldChunk.hpp | 1 + src/database/SQLiteClient.cpp | 73 ++++++++++++++++++++++++++++++++++- src/game/GameLogic.cpp | 46 +++++++++++++++++++--- src/game/WorldChunk.cpp | 54 ++++++++++++++++++++------ 5 files changed, 157 insertions(+), 18 deletions(-) diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 5118fc0..298d8ce 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -84,6 +84,7 @@ class GameLogic : public LogicCore // World message handlers void HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json& data); void HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data); + void HandleWorldChunkHMapRequest(uint64_t sessionId, const nlohmann::json& data); void HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::json& data); void HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& data); void HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& data); diff --git a/include/game/WorldChunk.hpp b/include/game/WorldChunk.hpp index e8bedb2..15049a9 100644 --- a/include/game/WorldChunk.hpp +++ b/include/game/WorldChunk.hpp @@ -99,6 +99,7 @@ class WorldChunk { // Serialization virtual nlohmann::json Serialize() const; virtual void Deserialize(const nlohmann::json& data); + virtual nlohmann::json SerializeHeightmap() const; // Geometry generation virtual void GenerateGeometry() { GenerateLowPolyGeometry(); } diff --git a/src/database/SQLiteClient.cpp b/src/database/SQLiteClient.cpp index 78134f6..045e4f5 100644 --- a/src/database/SQLiteClient.cpp +++ b/src/database/SQLiteClient.cpp @@ -48,7 +48,16 @@ bool SQLiteClient::Connect() { return false; } + // --- New: Set busy timeout to 5 seconds --- + sqlite3_busy_timeout(db_, 5000); + char* errMsg = nullptr; + rc = sqlite3_exec(db_, "PRAGMA journal_mode=WAL;", nullptr, nullptr, &errMsg); + if (rc != SQLITE_OK) { + Logger::Warn("Failed to enable WAL mode: {}", errMsg ? errMsg : "unknown error"); + sqlite3_free(errMsg); + } + rc = sqlite3_exec(db_, "PRAGMA foreign_keys = ON;", nullptr, nullptr, &errMsg); if (rc != SQLITE_OK) { Logger::Warn("Failed to enable foreign keys: {}", errMsg ? errMsg : "unknown error"); @@ -541,11 +550,73 @@ std::vector SQLiteClient::ListGameStates() { // =============== World Data Operations =============== bool SQLiteClient::SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& chunkData) { + std::lock_guard lock(dbMutex_); + if (!db_) { + Logger::Error("SaveChunkData: database not connected"); + return false; + } + std::string sql = sqlProvider_.GetQuery("save_chunk_data"); if (sql.empty()) { sql = "INSERT OR REPLACE INTO world_chunks (chunk_x, chunk_z, biome, data, last_updated) VALUES (?, ?, ?, ?, datetime('now'));"; } - return ExecuteWithParams(sql, { std::to_string(chunkX), std::to_string(chunkZ), "0", chunkData.dump() }); + + // Begin immediate transaction + char* errMsg = nullptr; + int rc = sqlite3_exec(db_, "BEGIN IMMEDIATE;", nullptr, nullptr, &errMsg); + if (rc != SQLITE_OK) { + Logger::Error("Failed to begin transaction: {}", errMsg ? errMsg : "unknown"); + sqlite3_free(errMsg); + return false; + } + + // Prepare and execute the INSERT statement + sqlite3_stmt* stmt = nullptr; + rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + Logger::Error("Failed to prepare chunk save statement: {}", sqlite3_errmsg(db_)); + sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); + return false; + } + + std::string chunkXStr = std::to_string(chunkX); + std::string chunkZStr = std::to_string(chunkZ); + std::string dataStr = chunkData.dump(); + + sqlite3_bind_text(stmt, 1, chunkXStr.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, chunkZStr.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 3, 0); // biome default + sqlite3_bind_text(stmt, 4, dataStr.c_str(), -1, SQLITE_TRANSIENT); + + rc = sqlite3_step(stmt); + bool success = (rc == SQLITE_DONE); + if (!success) { + Logger::Error("Failed to execute chunk save: {}", sqlite3_errmsg(db_)); + } + + sqlite3_finalize(stmt); + + // Commit or rollback + if (success) { + rc = sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, &errMsg); + if (rc != SQLITE_OK) { + Logger::Error("Failed to commit chunk save: {}", errMsg ? errMsg : "unknown"); + sqlite3_free(errMsg); + success = false; + } + } else { + sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); + } + + if (success) { + lastInsertId_ = sqlite3_last_insert_rowid(db_); + affectedRows_ = sqlite3_changes(db_); + stats_.totalQueries++; + } else { + stats_.failedQueries++; + } + + return success; } nlohmann::json SQLiteClient::LoadChunkData(int chunkX, int chunkZ) { diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index f084cea..f7fdd4e 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -563,17 +563,53 @@ void GameLogic::HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json } void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data) { - Logger::Debug("HandleWorldChunkRequestJson called for session {}", sessionId); - Logger::Debug("Request data: {}", data.dump()); try { int chunkX = data.value("chunkX", 0); int chunkZ = data.value("chunkZ", 0); int lod = data.value("lod", 0); - Logger::Debug("JSON world chunk request: [{}, {}] LOD: {}", chunkX, chunkZ, lod); auto start = std::chrono::steady_clock::now(); auto chunk = GetOrCreateChunk(chunkX, chunkZ); auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); - Logger::Debug("GetOrCreateChunk took {} ms", elapsed.count()); + if (!chunk) { + Logger::Error("Failed to get or create chunk ({},{})", chunkX, chunkZ); + SendError(sessionId, "Failed to generate chunk", 404); + return; + } + chunk->GenerateLowPolyGeometry(); + nlohmann::json response = { + {"type", "world_chunk"}, + {"chunkX", chunkX}, + {"chunkZ", chunkZ}, + {"lod", lod}, + {"data", chunk->Serialize()}, + {"timestamp", GetCurrentTimestamp()} + }; + SendToSession(sessionId, response); + } catch (const std::exception& err) { + Logger::Error("Exception in HandleWorldChunkRequestJson: {}", err.what()); + try { + SendError(sessionId, "Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", sessionId); + } + } catch (...) { + Logger::Error("Unknown exception in HandleWorldChunkRequestJson"); + try { + SendError(sessionId, "Internal server error", 500); + } catch (...) { + Logger::Error("Failed to send error response to session {}", sessionId); + } + } +} + +void GameLogic::HandleWorldChunkHMapRequest(uint64_t sessionId, const nlohmann::json& data) { + try { + int chunkX = data.value("chunkX", 0); + int chunkZ = data.value("chunkZ", 0); + int lod = data.value("lod", 0); + auto start = std::chrono::steady_clock::now(); + auto chunk = GetOrCreateChunk(chunkX, chunkZ); + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); if (!chunk) { Logger::Error("Failed to get or create chunk ({},{})", chunkX, chunkZ); SendError(sessionId, "Failed to generate chunk", 404); @@ -587,9 +623,7 @@ void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann:: {"data", chunk->Serialize()}, {"timestamp", GetCurrentTimestamp()} }; - Logger::Debug("Sending chunk response: {}", response.dump()); SendToSession(sessionId, response); - Logger::Debug("Chunk response sent for ({},{})", chunkX, chunkZ); } catch (const std::exception& err) { Logger::Error("Exception in HandleWorldChunkRequestJson: {}", err.what()); try { diff --git a/src/game/WorldChunk.cpp b/src/game/WorldChunk.cpp index e6d02e0..57bbe6d 100644 --- a/src/game/WorldChunk.cpp +++ b/src/game/WorldChunk.cpp @@ -208,19 +208,26 @@ nlohmann::json WorldChunk::Serialize() const { data["lod"] = static_cast(lod_); data["biome"] = static_cast(biome_); - // Serialize heightmap - nlohmann::json heightmapArray = nlohmann::json::array(); - for (float height : heightmap_) { - heightmapArray.push_back(height); + // Serialize vertices (position + normal as flat float array) + nlohmann::json verticesArray = nlohmann::json::array(); + for (const auto& v : vertices_) { + verticesArray.push_back(v.position.x); + verticesArray.push_back(v.position.y); + verticesArray.push_back(v.position.z); + verticesArray.push_back(v.normal.x); + verticesArray.push_back(v.normal.y); + verticesArray.push_back(v.normal.z); } - data["heightmap"] = heightmapArray; - - // Serialize blocks (simplified) - nlohmann::json blocksArray = nlohmann::json::array(); - for (BlockType block : blocks_) { - blocksArray.push_back(static_cast(block)); + data["vertices"] = verticesArray; + + // Serialize indices (using v0, v1, v2) + nlohmann::json indicesArray = nlohmann::json::array(); + for (const auto& tri : triangles_) { + indicesArray.push_back(tri.v0); + indicesArray.push_back(tri.v1); + indicesArray.push_back(tri.v2); } - data["blocks"] = blocksArray; + data["indices"] = indicesArray; return data; } @@ -252,3 +259,28 @@ void WorldChunk::Deserialize(const nlohmann::json& data) { // Regenerate geometry GenerateGeometry(); } + +nlohmann::json WorldChunk::SerializeHeightmap() const { + nlohmann::json data; + + data["chunkX"] = chunkX_; + data["chunkZ"] = chunkZ_; + data["lod"] = static_cast(lod_); + data["biome"] = static_cast(biome_); + + // Serialize heightmap + nlohmann::json heightmapArray = nlohmann::json::array(); + for (float height : heightmap_) { + heightmapArray.push_back(height); + } + data["heightmap"] = heightmapArray; + + // Serialize blocks (simplified) + nlohmann::json blocksArray = nlohmann::json::array(); + for (BlockType block : blocks_) { + blocksArray.push_back(static_cast(block)); + } + data["blocks"] = blocksArray; + + return data; +} From d02a192988901dfa445e7d7eee138ab8b090575a Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Sat, 18 Apr 2026 05:50:30 +0300 Subject: [PATCH 05/14] added key --clear remove previous compilations --- build.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index c68b285..70c4465 100755 --- a/build.sh +++ b/build.sh @@ -41,6 +41,7 @@ sudo apt-get install -y \ USE_CITUS=OFF USE_SQLITE=OFF ENABLE_ASAN=OFF +CLEAR_PREVIOUS=OFF for arg in "$@"; do case $arg in @@ -58,6 +59,10 @@ for arg in "$@"; do echo "Enabling AddressSanitizer and UndefinedBehaviorSanitizer" ENABLE_ASAN=ON ;; + --clear) + echo "Enabling clear previous compilations" + CLEAR_PREVIOUS=ON + ;; *) ;; esac @@ -73,7 +78,10 @@ rm -rf CMakeFiles # Create build directory and copy related folders mkdir -p build cd build -find . -mindepth 1 -maxdepth 1 ! -name "certs" -exec rm -rf {} + +if $CLEAR_PREVIOUS; then + echo "Clearing previous compilations..." + find . -mindepth 1 -maxdepth 1 ! -name "certs" -exec rm -rf {} + +fi # Run CMake cmake .. -B . \ From a538d58f6520de72f400928632822ee7e1bf0e79 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Sat, 18 Apr 2026 20:59:13 +0300 Subject: [PATCH 06/14] fixed global shutdown --- include/game/GameLogic.hpp | 9 +- include/network/BinaryProtocol.hpp | 5 +- include/network/GameServer.hpp | 8 +- src/database/SQLiteClient.cpp | 26 ++--- src/game/GameLogic.cpp | 161 +++++++++++++++++++++++++++-- src/main.cpp | 41 +++++++- src/network/GameServer.cpp | 21 +--- src/process/ProcessPool.cpp | 47 +++++++-- 8 files changed, 259 insertions(+), 59 deletions(-) diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 298d8ce..4831b35 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -100,6 +100,7 @@ class GameLogic : public LogicCore void OnPlayerDisconnected(uint64_t sessionId); // Broadcasting + void BroadcastPlayerUpdates(); void BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, const std::vector& data, float radius = 50.0f); void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, @@ -107,15 +108,21 @@ class GameLogic : public LogicCore void SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& position); // Helper broadcast methods + void BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius); void BroadcastToAllPlayers(const nlohmann::json& message); void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data); void BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message); + void BroadcastPlayerState(uint64_t playerId, const ServerState& state); + void BroadcastPlayerSpawn(uint64_t playerId); + void BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition); + void BroadcastPlayerSpawnJson(uint64_t playerId); + void BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition); + void BroadcastPlayerUpdatesJson(); // Broadcast entity spawn to nearby players void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, float yaw, const std::string& name); void SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity); - void BroadcastPlayerState(uint64_t playerId, const ServerState& state); void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); void HandleAuthentication(uint64_t sessionId, const std::vector& data); diff --git a/include/network/BinaryProtocol.hpp b/include/network/BinaryProtocol.hpp index 3cbd2dc..c516983 100644 --- a/include/network/BinaryProtocol.hpp +++ b/include/network/BinaryProtocol.hpp @@ -42,7 +42,10 @@ namespace BinaryProtocol { MESSAGE_TYPE_PLAYER_ROTATION = 302, MESSAGE_TYPE_PLAYER_STATE = 303, MESSAGE_TYPE_PLAYER_POSITION_CORRECTION = 304, - + MESSAGE_TYPE_PLAYER_UPDATE = 305, + MESSAGE_TYPE_PLAYER_SPAWN = 306, + MESSAGE_TYPE_PLAYER_DESPAWN = 307, + // NPC messages MESSAGE_TYPE_NPC_SPAWN = 400, MESSAGE_TYPE_NPC_UPDATE = 401, diff --git a/include/network/GameServer.hpp b/include/network/GameServer.hpp index 9186ab1..cc00d90 100644 --- a/include/network/GameServer.hpp +++ b/include/network/GameServer.hpp @@ -28,22 +28,20 @@ class GameServer { void Run(); void Shutdown(); - // For binary protocol + asio::io_context& GetIoContext() { return ioContext_; } + using SessionFactory = std::function(asio::ip::tcp::socket, std::shared_ptr)>; void SetSessionFactory(SessionFactory factory); - // For WebSocket protocol using WebSocketFactory = std::function)>; void SetWebSocketConnectionFactory(WebSocketFactory factory); private: void DoAccept(); void StartWorkerThreads(); - void SetupSignalHandlers(); asio::io_context ioContext_; asio::ip::tcp::acceptor acceptor_; - asio::signal_set signals_; WorkerGroupConfig groupConfig_; const ConfigManager& globalConfig_; @@ -56,7 +54,7 @@ class GameServer { std::vector workerThreads_; std::atomic running_{false}; - std::shared_ptr sslContext_; // optional, set if SSL is configured + std::shared_ptr sslContext_; SessionFactory sessionFactory_; WebSocketFactory webSocketFactory_; diff --git a/src/database/SQLiteClient.cpp b/src/database/SQLiteClient.cpp index 045e4f5..f458bea 100644 --- a/src/database/SQLiteClient.cpp +++ b/src/database/SQLiteClient.cpp @@ -2,7 +2,6 @@ #include "database/SQLiteClient.hpp" -// =============== Constructor and Destructor =============== SQLiteClient::SQLiteClient(const nlohmann::json& config, const SQLProvider& sqlProvider) : db_(nullptr), sqlProvider_(sqlProvider), @@ -10,7 +9,6 @@ SQLiteClient::SQLiteClient(const nlohmann::json& config, const SQLProvider& sqlP lastInsertId_(0), affectedRows_(0) { - // Determine database file path if (config.contains("file") && config["file"].is_string()) { dbPath_ = config["file"].get(); } else if (config.contains("name") && config["name"].is_string()) { @@ -29,7 +27,6 @@ SQLiteClient::~SQLiteClient() { Logger::Debug("SQLiteClient destroyed"); } -// =============== Connection Management =============== bool SQLiteClient::Connect() { std::lock_guard lock(dbMutex_); if (db_) return true; @@ -48,10 +45,16 @@ bool SQLiteClient::Connect() { return false; } - // --- New: Set busy timeout to 5 seconds --- sqlite3_busy_timeout(db_, 5000); char* errMsg = nullptr; + + rc = sqlite3_exec(db_, "PRAGMA synchronous = NORMAL;", nullptr, nullptr, &errMsg); + if (rc != SQLITE_OK) { + Logger::Warn("Failed to enable synchronous NORMAL: {}", errMsg ? errMsg : "unknown error"); + sqlite3_free(errMsg); + } + rc = sqlite3_exec(db_, "PRAGMA journal_mode=WAL;", nullptr, nullptr, &errMsg); if (rc != SQLITE_OK) { Logger::Warn("Failed to enable WAL mode: {}", errMsg ? errMsg : "unknown error"); @@ -115,7 +118,6 @@ void SQLiteClient::ReconnectAll() { Reconnect(); } -// =============== Connection Pool Management (dummy) =============== bool SQLiteClient::InitializeConnectionPool(size_t /*minConnections*/, size_t /*maxConnections*/) { Logger::Debug("SQLiteClient: connection pool not implemented"); return true; @@ -131,7 +133,6 @@ size_t SQLiteClient::GetIdleConnections() const { return 0; } -// =============== Helper Methods =============== bool SQLiteClient::ExecuteSql(const std::string& sql, std::vector>* results) { std::lock_guard lock(dbMutex_); if (!db_) { @@ -226,7 +227,6 @@ bool SQLiteClient::TableExists(const std::string& tableName) { return ExecuteSql(sql, &results) && !results.empty(); } -// =============== Query Operations =============== nlohmann::json SQLiteClient::Query(const std::string& sql) { std::lock_guard lock(dbMutex_); if (!db_) { @@ -312,7 +312,6 @@ bool SQLiteClient::ExecuteWithParams(const std::string& sql, const std::vector(entityId % totalShards_); } @@ -345,7 +343,6 @@ int SQLiteClient::GetAffectedRows() { return affectedRows_; } -// =============== Statistics =============== nlohmann::json SQLiteClient::GetDatabaseStats() { nlohmann::json stats; auto now = std::chrono::steady_clock::now(); @@ -377,7 +374,6 @@ void SQLiteClient::ResetStats() { Logger::Info("SQLite statistics reset"); } -// =============== Transaction Operations =============== bool SQLiteClient::BeginTransaction() { std::string sql = sqlProvider_.GetQuery("begin_transaction"); if (sql.empty()) sql = "BEGIN TRANSACTION;"; @@ -419,7 +415,6 @@ bool SQLiteClient::ExecuteTransaction(const std::function& operation) { return success; } -// =============== Player Data Operations =============== bool SQLiteClient::SavePlayerData(uint64_t playerId, const nlohmann::json& data) { std::string sql = sqlProvider_.GetQuery("save_player_data"); if (sql.empty()) { @@ -506,7 +501,6 @@ nlohmann::json SQLiteClient::GetPlayer(uint64_t playerId) { return nlohmann::json(); } -// =============== Game State Operations =============== bool SQLiteClient::SaveGameState(const std::string& key, const nlohmann::json& state) { std::string sql = sqlProvider_.GetQuery("save_game_state"); if (sql.empty()) { @@ -548,7 +542,6 @@ std::vector SQLiteClient::ListGameStates() { return keys; } -// =============== World Data Operations =============== bool SQLiteClient::SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& chunkData) { std::lock_guard lock(dbMutex_); if (!db_) { @@ -561,7 +554,6 @@ bool SQLiteClient::SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& c sql = "INSERT OR REPLACE INTO world_chunks (chunk_x, chunk_z, biome, data, last_updated) VALUES (?, ?, ?, ?, datetime('now'));"; } - // Begin immediate transaction char* errMsg = nullptr; int rc = sqlite3_exec(db_, "BEGIN IMMEDIATE;", nullptr, nullptr, &errMsg); if (rc != SQLITE_OK) { @@ -570,7 +562,6 @@ bool SQLiteClient::SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& c return false; } - // Prepare and execute the INSERT statement sqlite3_stmt* stmt = nullptr; rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr); if (rc != SQLITE_OK) { @@ -596,7 +587,6 @@ bool SQLiteClient::SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& c sqlite3_finalize(stmt); - // Commit or rollback if (success) { rc = sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, &errMsg); if (rc != SQLITE_OK) { @@ -667,7 +657,6 @@ std::vector> SQLiteClient::ListChunksInRange(int centerX, in return chunks; } -// =============== Inventory Operations =============== bool SQLiteClient::SaveInventory(uint64_t playerId, const nlohmann::json& inventory) { std::string sql = sqlProvider_.GetQuery("save_inventory"); if (sql.empty()) { @@ -688,7 +677,6 @@ nlohmann::json SQLiteClient::LoadInventory(uint64_t playerId) { return nlohmann::json(); } -// =============== Quest Operations =============== bool SQLiteClient::SaveQuestProgress(uint64_t playerId, const std::string& questId, const nlohmann::json& progress) { std::string sql = sqlProvider_.GetQuery("save_quest_progress"); if (sql.empty()) { diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index f7fdd4e..ef08370 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -373,6 +373,10 @@ void GameLogic::SendPositionCorrection(uint64_t sessionId, const glm::vec3& posi } void GameLogic::RegisterWorldHandlers() { + RegisterHandler("protocol_negotiation", [this](uint64_t sessionId, const nlohmann::json& data) { + Logger::Debug("Protocol negotiation received from session {}; data {}", sessionId, data.dump()); + }); + RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, [this](uint64_t sessionId, uint16_t /*type*/, const std::vector& data) { HandleAuthentication(sessionId, data); @@ -466,7 +470,10 @@ void GameLogic::HandleMessage(uint64_t sessionId, const nlohmann::json& message) Logger::Debug("Message content: {}", message.dump()); std::string type = message.value("type", ""); Logger::Debug("Message type: '{}'", type); - if (type == "world_chunk_request") { + if (type == "protocol_negotiation") { + // Already handled by the registered handler, nothing else needed + } + else if (type == "world_chunk_request") { Logger::Debug("Dispatching to HandleWorldChunkRequestJson"); HandleWorldChunkRequestJson(sessionId, message); } @@ -1111,12 +1118,12 @@ void GameLogic::SaveChunkData() { void GameLogic::CleanupOldData() { LogicCore::CleanupOldData(); - Logger::Debug("Cleaning up game data"); + //Logger::Debug("Cleaning up game data"); } // =============== World maintenance =============== void GameLogic::PerformMaintenance() { - Logger::Info("Performing game world maintenance"); + //Logger::Debug("Performing game world maintenance"); // Clean up old data CleanupOldData(); @@ -1133,7 +1140,7 @@ void GameLogic::PerformMaintenance() { } } - Logger::Info("Game world maintenance complete"); + //Logger::Debug("Game world maintenance complete"); } // =============== IPC message handling =============== @@ -1278,8 +1285,41 @@ void GameLogic::BroadcastToPlayers(const std::vector& sessionIds, cons Logger::Debug("Broadcasted to {} specific player(s)", sentCount); } - } catch (const std::exception& e) { - Logger::Error("Error broadcasting to specific players: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("Error broadcasting to specific players: {}", err.what()); + } +} + +void GameLogic::BroadcastPlayerUpdates() { + auto sessions = connectionManager_->GetAllSessions(); + for (auto& session : sessions) { + if (!session->IsAuthenticated()) continue; + + uint64_t localPlayerId = session->GetPlayerId(); + auto localPlayer = GetPlayer(localPlayerId); + if (!localPlayer) continue; + + glm::vec3 localPos = localPlayer->GetPosition(); + + // Collect nearby players + std::vector> nearby = + PlayerManager::GetInstance().GetPlayersInRadius(localPos, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); + + // Build payload + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt32(static_cast(nearby.size())); + for (auto& player : nearby) { + writer.WriteUInt32(player->GetId()); + writer.WriteFloat(player->GetPosition().x); + writer.WriteFloat(player->GetPosition().y); + writer.WriteFloat(player->GetPosition().z); + writer.WriteFloat(player->GetRotation().y); + writer.WriteFloat(player->GetHealth()); + writer.WriteFloat(player->GetMaxHealth()); + writer.WriteString(player->GetName()); + } + + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE, writer.GetBuffer()); } } @@ -1290,7 +1330,105 @@ void GameLogic::BroadcastPlayerState(uint64_t playerId, const ServerState& state writer.WriteVector3(state.rotation); writer.WriteVector3(state.velocity); writer.WriteUInt64(state.timestamp); - BroadcastToNearbyPlayers(state.position, BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer(), 100.0f); + BroadcastToNearbyPlayers(state.position, BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer(), ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} + +void GameLogic::BroadcastPlayerSpawn(uint64_t playerId) { + auto player = GetPlayer(playerId); + if (!player) return; + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(playerId); + writer.WriteString(player->GetName()); + writer.WriteVector3(player->GetPosition()); + writer.WriteFloat(player->GetRotation().y); // yaw + writer.WriteFloat(player->GetHealth()); + writer.WriteFloat(player->GetMaxHealth()); + BroadcastToNearbyPlayers(player->GetPosition(), + BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, + writer.GetBuffer(), + ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} + +void GameLogic::BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(playerId); + BroadcastToNearbyPlayers(lastPosition, + BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, + writer.GetBuffer(), + ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} + +void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { + if (!connectionManager_) return; + + auto& pm = PlayerManager::GetInstance(); + auto nearby = pm.GetPlayersInRadius(position, radius); + + for (auto& player : nearby) { + uint64_t sessionId = pm.GetSessionIdByPlayerId(player->GetId()); + if (sessionId != 0) { + auto session = connectionManager_->GetSession(sessionId); + if (session && session->IsConnected()) { + session->Send(message); + } + } + } +} + +void GameLogic::BroadcastPlayerSpawnJson(uint64_t playerId) { + auto player = GetPlayer(playerId); + if (!player) return; + nlohmann::json msg = { + {"type", "player_spawn"}, + {"player_id", playerId}, + {"name", player->GetName()}, + {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, + {"yaw", player->GetRotation().y}, + {"health", player->GetHealth()}, + {"max_health", player->GetMaxHealth()}, + {"timestamp", GetCurrentTimestamp()} + }; + BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} + +void GameLogic::BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition) { + nlohmann::json msg = { + {"type", "player_despawn"}, + {"player_id", playerId}, + {"timestamp", GetCurrentTimestamp()} + }; + BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} + +void GameLogic::BroadcastPlayerUpdatesJson() { + auto sessions = connectionManager_->GetAllSessions(); + for (auto& session : sessions) { + if (!session->IsAuthenticated()) continue; + uint64_t localPlayerId = session->GetPlayerId(); + auto localPlayer = GetPlayer(localPlayerId); + if (!localPlayer) continue; + glm::vec3 localPos = localPlayer->GetPosition(); + auto nearby = PlayerManager::GetInstance().GetPlayersInRadius(localPos, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); + nlohmann::json playersArray = nlohmann::json::array(); + for (auto& player : nearby) { + playersArray.push_back({ + {"id", player->GetId()}, + {"name", player->GetName()}, + {"x", player->GetPosition().x}, + {"y", player->GetPosition().y}, + {"z", player->GetPosition().z}, + {"yaw", player->GetRotation().y}, + {"health", player->GetHealth()}, + {"max_health", player->GetMaxHealth()} + }); + } + nlohmann::json msg = { + {"type", "player_update"}, + {"players", playersArray}, + {"timestamp", GetCurrentTimestamp()} + }; + session->Send(msg); + } } void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { @@ -1338,16 +1476,19 @@ void GameLogic::HandleAuthentication(uint64_t sessionId, const std::vector(player->GetId())); writer.WriteString(message); SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); } else { authenticated = false; message = "Internal error"; + Logger::Warn("GetPlayerByUsername('{}') return null", username); } } if (!authenticated) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt8(0); + writer.WriteUInt64(0); writer.WriteString(message); SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); } @@ -1396,5 +1537,11 @@ void GameLogic::HandleAuthentication(uint64_t sessionId, const std::string& user {"success", authenticated}, {"message", message} }; + if (authenticated) { + auto player = pm.GetPlayerByUsername(username); + if (player) { + response["player_id"] = player->GetId(); + } + } SendToSession(sessionId, response); } diff --git a/src/main.cpp b/src/main.cpp index 3903da2..f43579d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -164,6 +164,21 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* gameLogic.PreloadWorldData(config.GetWorldPreloadRadius()); } + std::thread signalThread([workerId, &server]() { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGTERM); + int sig; + int ret = sigwait(&set, &sig); + if (ret == 0) { + Logger::Info("Worker {} received SIGTERM via sigwait, initiating shutdown", workerId); + g_shutdown.store(true); + server.Shutdown(); + } else { + Logger::Error("Worker {} sigwait failed: {}", workerId, strerror(errno)); + } + }); + if (server.Initialize()) { Logger::Info("Worker {} game server initialized on {}:{} (protocol: {})", workerId, groupConfig.host, groupConfig.port, groupConfig.protocol); @@ -174,6 +189,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* auto lastCleanupTime = std::chrono::steady_clock::now(); auto lastIPCCheckTime = std::chrono::steady_clock::now(); + auto lastPlayerUpdate = std::chrono::steady_clock::now(); while (worldMaintenanceRunning && !g_shutdown.load()) { auto currentTime = std::chrono::steady_clock::now(); @@ -202,13 +218,30 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* lastIPCCheckTime = currentTime; } + // Player updates every 100 ms + if (std::chrono::duration_cast(currentTime - lastPlayerUpdate).count() >= 100) { + gameLogic.BroadcastPlayerUpdates(); + gameLogic.BroadcastPlayerUpdatesJson(); + lastPlayerUpdate = currentTime; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } Logger::Info("Worker {} world maintenance thread stopped", workerId); }); - Logger::Info("Worker {} starting server loop", workerId); + std::thread watchdog([workerId]() { + while (!g_shutdown.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + std::this_thread::sleep_for(std::chrono::seconds(10)); + Logger::Error("Worker {} watchdog triggered – forcing exit", workerId); + _exit(1); + }); + watchdog.detach(); + + Logger::Info("Worker {} entering server.Run()", workerId); server.Run(); worldMaintenanceRunning = false; @@ -220,6 +253,8 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Critical("Worker {} failed to initialize server", workerId); } + if (signalThread.joinable()) { signalThread.join(); } + Logger::Info("Worker {} beginning cleanup...", workerId); gameLogic.Shutdown(); @@ -390,7 +425,7 @@ int main(int argc, char* argv[]) { processPool.SendToWorker(i, broadcastSerialized); } - Logger::Info("Master broadcasted server status to all workers"); + //Logger::Debug("Master broadcasted server status to all workers"); } } @@ -398,7 +433,7 @@ int main(int argc, char* argv[]) { masterMessagingThread.join(); } - Logger::Info("Initiating graceful shutdown..."); + Logger::Info("Initiating shutdown..."); processPool.Shutdown(); Logger::Info("Game Server shutdown complete"); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index 9983dc3..a628080 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -3,7 +3,6 @@ GameServer::GameServer(const WorkerGroupConfig& groupConfig, const ConfigManager& globalConfig) : ioContext_(groupConfig.threads), acceptor_(ioContext_), - signals_(ioContext_), groupConfig_(groupConfig), globalConfig_(globalConfig), host_(groupConfig.host), @@ -46,7 +45,6 @@ bool GameServer::Initialize() { acceptor_.bind(endpoint); acceptor_.listen(groupConfig_.max_connections); - SetupSignalHandlers(); Logger::Info("GameServer initialized for protocol '{}' on {}:{}", groupConfig_.protocol, host_, port_); return true; @@ -61,15 +59,17 @@ void GameServer::Run() { running_ = true; DoAccept(); StartWorkerThreads(); + + auto work = asio::make_work_guard(ioContext_); + Logger::Info("GameServer started with {} IO threads for protocol '{}'", ioThreads_, groupConfig_.protocol); try { ioContext_.run(); } catch (const std::exception& err) { Logger::Critical("Unhandled exception in IO context: {}", err.what()); - } catch (...) { - Logger::Critical("Unknown exception in IO context"); } + for (auto& thread : workerThreads_) { if (thread.joinable()) thread.join(); } @@ -160,23 +160,10 @@ void GameServer::StartWorkerThreads() { } } -void GameServer::SetupSignalHandlers() { - signals_.add(SIGINT); - signals_.add(SIGTERM); - signals_.add(SIGQUIT); - signals_.async_wait([this](std::error_code ec, int signal) { - if (!ec) { - Logger::Info("Received signal {}, shutting down...", signal); - Shutdown(); - } - }); -} - void GameServer::Shutdown() { if (!running_) return; running_ = false; ConnectionManager::GetInstance().StopAll(); - signals_.cancel(); acceptor_.close(); ioContext_.stop(); Logger::Info("GameServer shutdown initiated for protocol '{}'", groupConfig_.protocol); diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index 4629919..bec4260 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -90,13 +90,14 @@ void ProcessPool::MasterProcess() { if (pid == 0) { signal(SIGINT, SIG_IGN); - signal(SIGTERM, [](int) { g_shutdown.store(true, std::memory_order_relaxed); }); + signal(SIGTERM, SIG_DFL); // Restore default, ASIO will override it + //signal(SIGTERM, [](int) { g_shutdown.store(true, std::memory_order_relaxed); }); UnblockSignals(&oldset); sigset_t block_int; sigemptyset(&block_int); - sigaddset(&block_int, SIGINT); + sigaddset(&block_int, SIGTERM); //sigaddset(&block_int, SIGINT); pthread_sigmask(SIG_BLOCK, &block_int, nullptr); for (int i = 0; i < totalWorkers_ * 2; ++i) { @@ -149,6 +150,10 @@ void ProcessPool::MasterProcess() { } } + // ---------- shutdown with timeout ---------- + constexpr int SHUTDOWN_TIMEOUT_SEC = 5; + + // Send SIGTERM to all workers for (int i = 0; i < totalWorkers_; ++i) { if (workers_[i].pid > 0) { Logger::Info("Terminating worker {} (PID: {})", i, workers_[i].pid); @@ -156,11 +161,41 @@ void ProcessPool::MasterProcess() { } } - int status; + std::vector exited(totalWorkers_, false); + int remaining = totalWorkers_; + auto start = std::chrono::steady_clock::now(); + + while (remaining > 0) { + auto elapsed = std::chrono::steady_clock::now() - start; + if (elapsed >= std::chrono::seconds(SHUTDOWN_TIMEOUT_SEC)) { + Logger::Warn("Shutdown timeout reached, {} workers still alive", remaining); + break; + } + + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid > 0) { + for (int i = 0; i < totalWorkers_; ++i) { + if (!exited[i] && workers_[i].pid == pid) { + exited[i] = true; + remaining--; + Logger::Info("Worker {} exited with status: {}", i, WEXITSTATUS(status)); + break; + } + } + } else if (pid == 0) { // No child exited, sleep a bit + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } else { // Error (e.g., no children left) + break; + } + } + + // Force kill any remaining workers for (int i = 0; i < totalWorkers_; ++i) { - if (workers_[i].pid > 0) { - waitpid(workers_[i].pid, &status, 0); - Logger::Info("Worker {} exited with status: {}", i, status); + if (!exited[i] && workers_[i].pid > 0) { + Logger::Warn("Worker {} (PID: {}) still alive – sending SIGKILL", i, workers_[i].pid); + kill(workers_[i].pid, SIGKILL); + waitpid(workers_[i].pid, nullptr, 0); } } From d01b3bc3cbbd9a64f826e6e8598e2a0e26ebbe9b Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:30:06 +0300 Subject: [PATCH 07/14] refactor network layer --- ARCHITECTURE_DIAGRAM.md | 312 --- CMakeLists.txt | 4 +- MOBS_SYSTEM.md | 248 --- QUICK_REFERENCE.md | 231 --- STRUCTURE.md | 637 ------ config/core.json | 45 +- config/loot_tables.json | 212 +- include/config/ConfigManager.hpp | 2 +- include/database/Backend.hpp | 6 +- include/database/CitusClient.hpp | 14 +- include/database/DbManager.hpp | 65 +- include/database/DbService.hpp | 44 + include/database/PostgreSqlClient.hpp | 29 +- include/database/SQLProvider.hpp | 47 +- include/database/SQLiteClient.hpp | 26 - include/game/GameData.hpp | 51 + include/game/GameLogic.hpp | 131 +- include/game/LogicCore.hpp | 86 +- include/game/LogicEntity.hpp | 4 +- include/game/LogicWorld.hpp | 2 +- include/game/LootTableManager.hpp | 6 +- include/game/NPCSystem.hpp | 9 +- include/game/Player.hpp | 100 +- include/game/PlayerManager.hpp | 2 +- include/logging/Logger.hpp | 2 + .../{GameSession.hpp => BinarySession.hpp} | 14 +- include/network/GameServer.hpp | 27 +- include/network/IConnection.hpp | 13 + include/network/PredictionSystem.hpp | 66 +- include/network/WebSocketProtocol.hpp | 122 +- include/network/WebSocketSession.hpp | 8 + include/scripting/PythonScripting.hpp | 4 - src/config/ConfigManager.cpp | 82 +- src/database/CitusClient.cpp | 12 + src/database/DbManager.cpp | 42 +- src/database/DbService.cpp | 95 + src/database/PostgreSqlClient.cpp | 4 + src/database/SQLProvider.cpp | 46 + src/database/SQLiteClient.cpp | 6 +- src/game/GameEntity.cpp | 152 +- src/game/GameLogic.cpp | 1837 +++++++++-------- src/game/LogicCore.cpp | 373 +--- src/game/LogicEntity.cpp | 63 +- src/game/LogicWorld.cpp | 6 +- src/game/LootTableManager.cpp | 161 +- src/game/MobSystem.cpp | 123 +- src/game/NPCEntity.cpp | 255 +-- src/game/Player.cpp | 192 ++ src/logging/Logger.cpp | 14 +- src/main.cpp | 133 +- .../{GameSession.cpp => BinarySession.cpp} | 305 +-- src/network/ConnectionManager.cpp | 9 +- src/network/GameServer.cpp | 347 +++- src/network/PredictionSystem.cpp | 16 +- src/network/WebSocketProtocol.cpp | 158 +- src/network/WebSocketSession.cpp | 59 +- src/process/ProcessPool.cpp | 4 +- vcpkg.json | 112 - 58 files changed, 2876 insertions(+), 4299 deletions(-) delete mode 100644 ARCHITECTURE_DIAGRAM.md delete mode 100644 MOBS_SYSTEM.md delete mode 100644 QUICK_REFERENCE.md delete mode 100644 STRUCTURE.md create mode 100644 include/database/DbService.hpp create mode 100644 include/game/GameData.hpp rename include/network/{GameSession.hpp => BinarySession.hpp} (97%) create mode 100644 src/database/DbService.cpp create mode 100644 src/database/SQLProvider.cpp rename src/network/{GameSession.cpp => BinarySession.cpp} (83%) delete mode 100644 vcpkg.json diff --git a/ARCHITECTURE_DIAGRAM.md b/ARCHITECTURE_DIAGRAM.md deleted file mode 100644 index 98ffe8b..0000000 --- a/ARCHITECTURE_DIAGRAM.md +++ /dev/null @@ -1,312 +0,0 @@ -# Architecture Diagrams - -## System Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Master Process │ -│ (ProcessPool Manager) │ -│ - Spawns worker processes │ -│ - Monitors worker health │ -│ - Handles graceful shutdown │ -└────────────────────────────┬────────────────────────────────────┘ - │ - ┌────────────┼────────────┐ - │ │ │ - ┌───────▼────┐ ┌─────▼─────┐ ┌───▼──────┐ - │ Worker 1 │ │ Worker 2 │ │ Worker N │ - │ Process │ │ Process │ │ Process │ - └───────┬────┘ └─────┬─────┘ └───┬──────┘ - │ │ │ - └────────────┼────────────┘ - │ - ┌────────────▼────────────┐ - │ GameServer │ - │ (ASIO TCP Server) │ - │ - Port: 8080 │ - │ - Max Connections: 1000│ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ ConnectionManager │ - │ - Active Sessions │ - │ - Connection Stats │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ GameSession │ - │ (Per Client) │ - │ - Message Handler │ - │ - Rate Limiting │ - │ - Compression │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ GameLogic │ - │ (Message Router) │ - └────────────┬────────────┘ - │ - ┌────────────────────┼────────────────────┐ - │ │ │ -┌───────▼──────┐ ┌────────▼────────┐ ┌────────▼────────┐ -│ World System │ │ Entity Manager │ │ NPC System │ -│ │ │ │ │ │ -│ - Chunks │ │ - Players │ │ - AI Behaviors │ -│ - Generator │ │ - NPCs │ │ - Spawning │ -│ - Collision │ │ - Objects │ │ - Interactions │ -└───────┬──────┘ └────────────────┘ └────────────────┘ - │ - └────────────────────┬────────────────────┐ - │ │ - ┌────────────▼────────────┐ ┌────▼─────────┐ - │ Python Scripting │ │ CitusClient │ - │ │ │ │ - │ - Event Handlers │ │ - Sharding │ - │ - Game Logic │ │ - Queries │ - │ - Hot Reload │ │ - Pool │ - └────────────────────────┘ └──────────────┘ -``` - -## Component Interaction Flow - -### Message Processing Flow - -``` -Client Message - │ - ▼ -GameSession::HandleMessage() - │ - ▼ -GameLogic::HandleMessage() - │ - ├─→ Route to Handler - │ │ - │ ├─→ HandleLogin() - │ ├─→ HandleMovement() - │ ├─→ HandleWorldChunkRequest() - │ └─→ HandleNPCInteraction() - │ - ├─→ Fire Python Event (if registered) - │ │ - │ └─→ PythonScripting::FireEvent() - │ │ - │ └─→ Call Python Handler - │ │ - │ └─→ Python may call C++ API - │ - └─→ Send Response - │ - └─→ GameSession::Send() -``` - -### World Generation Flow - -``` -Player Moves - │ - ▼ -HandlePlayerPositionUpdate() - │ - ▼ -GenerateWorldAroundPlayer() - │ - ├─→ Calculate Required Chunks - │ - ├─→ For Each Chunk: - │ │ - │ ├─→ Check if Loaded - │ │ - │ ├─→ If Not: Generate - │ │ │ - │ │ ├─→ WorldGenerator::GenerateChunk() - │ │ │ - │ │ ├─→ Create WorldChunk - │ │ │ - │ │ ├─→ Generate Geometry - │ │ │ - │ │ └─→ Generate Collision Mesh - │ │ - │ └─→ Send to Player - │ │ - │ └─→ GameSession::SendWorldChunk() - │ - └─→ Unload Distant Chunks -``` - -### Entity System Flow - -``` -Entity Creation - │ - ▼ -EntityManager::CreateEntity() - │ - ├─→ Allocate Entity ID - │ - ├─→ Create Entity Object - │ │ - │ ├─→ Player - │ ├─→ NPCEntity - │ └─→ GameEntity - │ - ├─→ Add to Spatial Grid - │ - └─→ Register with Chunk - │ - └─→ WorldChunk::AddEntity() -``` - -## Data Layer Architecture - -``` -Application Layer - │ - ▼ -CitusClient (Shard Router) - │ - ├─→ Coordinator Node - │ │ - │ └─→ Metadata & Query Planning - │ - └─→ Worker Nodes (Shards) - │ - ├─→ Shard 1 (player_id % 32 == 0) - ├─→ Shard 2 (player_id % 32 == 1) - ├─→ ... - └─→ Shard 32 (player_id % 32 == 31) -``` - -## Python Integration Architecture - -``` -C++ Game Logic - │ - ├─→ Fire Event - │ │ - │ └─→ PythonScripting::FireEvent() - │ │ - │ └─→ Python Handler - │ │ - │ └─→ May Call PythonAPI - │ │ - │ └─→ C++ Function Execution - │ - └─→ Call Python Function - │ - └─→ PythonScripting::CallFunction() - │ - └─→ Python Module Function - │ - └─→ Returns Result to C++ -``` - -## Network Protocol Stack - -``` -┌─────────────────────────────────────┐ -│ Application Layer │ -│ (Game Messages: login, move, etc.) │ -└──────────────┬──────────────────────┘ - │ -┌──────────────▼──────────────────────┐ -│ JSON Serialization │ -└──────────────┬──────────────────────┘ - │ -┌──────────────▼──────────────────────┐ -│ Compression (Optional) │ -└──────────────┬──────────────────────┘ - │ -┌──────────────▼──────────────────────┐ -│ TCP/IP (ASIO) │ -└──────────────┬──────────────────────┘ - │ -┌──────────────▼──────────────────────┐ -│ Network Interface │ -└──────────────────────────────────────┘ -``` - -## Memory Management - -``` -GameSession (Per Connection) - │ - ├─→ Read Buffer (asio::streambuf) - │ - └─→ Write Queue (std::queue) - │ - └─→ Rate Limited - -WorldChunk (Per Chunk) - │ - ├─→ Block Data (std::vector) - │ - ├─→ Heightmap (std::vector) - │ - ├─→ Vertices (std::vector) - │ - └─→ Triangles (std::vector) - -EntityManager - │ - ├─→ Entities (std::unordered_map) - │ - └─→ Spatial Grid (for fast queries) -``` - -## Threading Model - -``` -Main Thread - │ - ├─→ ProcessPool (Master) - │ │ - │ └─→ Spawns Worker Processes - │ - └─→ Worker Process - │ - ├─→ Main Thread - │ │ - │ └─→ GameServer::Run() - │ - ├─→ I/O Threads (8 threads) - │ │ - │ └─→ ASIO Worker Threads - │ - ├─→ Game Loop Thread - │ │ - │ └─→ GameLogic::GameLoop() - │ - └─→ World Maintenance Thread - │ - └─→ Periodic Cleanup -``` - -## Configuration Hierarchy - -``` -config/config.json - │ - ├─→ server - │ ├─→ port - │ ├─→ maxConnections - │ ├─→ processCount - │ └─→ workerThreads - │ - ├─→ world - │ ├─→ seed - │ ├─→ chunkSize - │ ├─→ viewDistance - │ └─→ terrainScale - │ - ├─→ database - │ ├─→ host - │ ├─→ port - │ └─→ workerNodes - │ - └─→ pythonScripting - ├─→ enabled - ├─→ scriptDirectory - └─→ hotReload -``` - diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f9d456..6a1df96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ set(NETWORK_SOURCES src/network/BinaryProtocol.cpp src/network/ConnectionManager.cpp src/network/GameServer.cpp - src/network/GameSession.cpp + src/network/BinarySession.cpp src/network/NetworkQualityMonitor.cpp src/network/PredictionSystem.cpp src/network/WebSocketProtocol.cpp @@ -139,6 +139,8 @@ set(LOGIC_CORE_SOURCES # Database system set(DATABASE_SOURCES + src/database/SQLProvider.cpp + src/database/DbService.cpp src/database/DbManager.cpp src/database/PostgreSqlClient.cpp ) diff --git a/MOBS_SYSTEM.md b/MOBS_SYSTEM.md deleted file mode 100644 index 1e9f79b..0000000 --- a/MOBS_SYSTEM.md +++ /dev/null @@ -1,248 +0,0 @@ -# Mob System Implementation - -## Overview - -A comprehensive mob (hostile NPC) system has been added to the game server with the following features: - -- **Mob Spawning**: Zone-based spawning with configurable parameters -- **Loot System**: Drop tables with chance-based item generation -- **Experience Rewards**: Level-based experience on mob death -- **Respawn System**: Automatic respawning with configurable timers -- **Mob Variants**: Leveled versions of mobs with scaled stats -- **Python Integration**: Event handlers for mob death, loot drops, and experience - -## Files Added - -### Headers -- `include/game/GameEntity.hpp` - Base entity class -- `include/game/MobSystem.hpp` - Mob system interface - -### Sources -- `src/game/GameEntity.cpp` - Entity base implementation -- `src/game/MobSystem.cpp` - Mob system implementation -- `src/game/NPCSystem.cpp` - NPC system implementation (enhanced) - -### Scripts -- `scripts/mobs.py` - Python event handlers for mob system - -### Configuration -- Updated `config/config.json` with mob spawn zones - -## Features - -### 1. Mob Spawn Zones - -Mobs spawn in predefined zones with the following properties: -- **Center Position**: 3D coordinates -- **Radius**: Spawn area size -- **Mob Type**: Which mob to spawn (Goblin, Orc, Dragon, Slime) -- **Level Range**: Min/max level for spawned mobs -- **Max Mobs**: Maximum mobs in zone at once -- **Respawn Time**: Time before respawn after death - -Example configuration: -```json -{ - "name": "goblin_forest", - "center": [100.0, 10.0, 100.0], - "radius": 50.0, - "mobType": 0, - "minLevel": 1, - "maxLevel": 5, - "maxMobs": 15, - "respawnTime": 30.0 -} -``` - -### 2. Loot System - -Each mob type has a default loot table with: -- **Item ID**: Item identifier -- **Quantity Range**: Min/max quantity -- **Drop Chance**: Probability (0.0 to 1.0) -- **Level Range**: Valid mob levels for this loot - -Default loot tables are defined for: -- **Goblin**: Gold coins, goblin ears, rusty sword -- **Orc**: Gold coins, orc tusks, iron sword, leather armor -- **Dragon**: Gold coins, dragon scales, dragon heart, legendary sword -- **Slime**: Gold coins, slime core, health potion - -### 3. Experience System - -Mobs award experience based on: -- **Base Experience**: 10 × level -- **Mob Type**: Different multipliers per type -- **Level Scaling**: Higher level mobs = more experience - -Experience is automatically awarded to the killer when a mob dies. - -### 4. Mob Variants - -Mobs can have different levels with scaled stats: -- **Health Multiplier**: 1.0 + (level - 1) × 0.2 -- **Damage Multiplier**: 1.0 + (level - 1) × 0.15 -- **Experience**: 10 × level - -Variants are automatically generated for levels 1-50. - -### 5. Respawn System - -When a mob dies: -1. Death is recorded with position and time -2. Loot is generated and dropped -3. Experience is awarded to killer -4. Respawn is scheduled based on zone settings -5. Mob is despawned - -Respawns are processed periodically, maintaining zone population. - -## Integration - -### GameLogic Integration - -The MobSystem is integrated into GameLogic: -- Initialized during server startup -- Updated each game tick -- Handles mob death in combat interactions -- Processes spawn zones and respawns - -### Combat Integration - -When a player attacks an NPC: -1. Damage is applied -2. If mob dies, `MobSystem::OnMobDeath()` is called -3. Loot is generated and dropped -4. Experience is awarded -5. Respawn is scheduled - -### Python Events - -The following events are fired for Python scripts: -- `mob_death` - When a mob dies -- `mob_loot_drop` - When loot is dropped -- `player_experience_gain` - When player gains experience -- `mob_spawn` - When a mob spawns - -## Usage - -### Spawning Mobs - -```cpp -// Spawn a mob at a specific position -uint64_t mobId = mobSystem.SpawnMob(NPCType::GOBLIN, position, level); - -// Spawn a mob in a zone -uint64_t mobId = mobSystem.SpawnMobInZone("goblin_forest"); -``` - -### Registering Spawn Zones - -```cpp -MobSpawnZone zone; -zone.name = "goblin_forest"; -zone.center = glm::vec3(100.0f, 10.0f, 100.0f); -zone.radius = 50.0f; -zone.mobType = NPCType::GOBLIN; -zone.minLevel = 1; -zone.maxLevel = 5; -zone.maxMobs = 15; -zone.respawnTime = 30.0f; - -mobSystem.RegisterSpawnZone(zone); -``` - -### Custom Loot Tables - -```cpp -std::vector lootTable = { - {"gold_coin", 1, 5, 0.8f, 1, 100}, - {"rare_item", 1, 1, 0.1f, 10, 50} -}; - -mobSystem.SetDefaultLootTable(NPCType::GOBLIN, lootTable); -``` - -## Configuration - -Mob system configuration is in `config/config.json`: - -```json -{ - "mobs": { - "enabled": true, - "spawnZones": [ - { - "name": "goblin_forest", - "center": [100.0, 10.0, 100.0], - "radius": 50.0, - "mobType": 0, - "minLevel": 1, - "maxLevel": 5, - "maxMobs": 15, - "respawnTime": 30.0 - } - ], - "experienceMultiplier": 1.0, - "lootDropChance": 1.0 - } -} -``` - -## Python Event Handlers - -Example Python handler: - -```python -def on_mob_death(event_data): - mob_id = event_data['data']['mobId'] - killer_id = event_data['data']['killerId'] - mob_type = event_data['data']['mobType'] - - # Custom logic here - if mob_type == 2: # Dragon - server.log_info(f"Player {killer_id} defeated a dragon!") - - return True -``` - -## Mob Types - -Currently supported hostile mobs: -- **GOBLIN** (0) - Weak, fast, common -- **ORC** (1) - Medium strength, slower -- **DRAGON** (2) - Very strong, rare, high rewards -- **SLIME** (3) - Weak, very common, low rewards - -## Future Enhancements - -Potential additions: -- Mob AI behaviors (patrol, chase, flee) -- Mob groups/squads -- Elite mob variants -- Boss mobs with special mechanics -- Dynamic spawn zones based on player activity -- Mob quests and objectives -- Mob reputation system - -## API Reference - -### MobSystem - -- `SpawnMob(type, position, level)` - Spawn a mob -- `SpawnMobInZone(zoneName)` - Spawn in zone -- `OnMobDeath(mobId, killerId)` - Handle mob death -- `GenerateLoot(type, level)` - Generate loot -- `GetExperienceReward(type, level)` - Get experience value -- `RegisterSpawnZone(zone)` - Register spawn zone -- `UpdateSpawnZones(deltaTime)` - Update spawn logic -- `ProcessRespawns(deltaTime)` - Process respawn queue - -### NPCEntity - -- `TakeDamage(damage, attackerId)` - Apply damage -- `IsAlive()` / `IsDead()` - Health status -- `GetStats()` - Get mob statistics -- `SetTarget(targetId)` - Set AI target -- `Update(deltaTime)` - Update AI and movement - diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index 7fc115c..0000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,231 +0,0 @@ -# Quick Reference Guide - -## Build & Run - -```bash -# Build -mkdir build && cd build -cmake .. -make - -# Run -./gameserver3d -``` - -## Configuration - -Edit `config/config.json`: - -```json -{ - "server": { - "port": 8080, - "processCount": 4, - "workerThreads": 8 - }, - "world": { - "seed": 12345, - "chunkSize": 32.0, - "viewDistance": 4 - } -} -``` - -## Key Classes - -### Network -- **GameServer** - Main TCP server -- **GameSession** - Per-client connection handler -- **ConnectionManager** - Connection tracking - -### Game Logic -- **GameLogic** - Central message router -- **EntityManager** - Entity lifecycle -- **PlayerManager** - Player state -- **WorldChunk** - Chunk data structure -- **WorldGenerator** - Procedural generation - -### Database -- **CitusClient** - Sharded database client -- **DatabasePool** - Connection pooling - -### Scripting -- **PythonScripting** - Python engine -- **PythonAPI** - C++ functions for Python - -## Message Types - -| Type | Description | -|------|-------------| -| `login` | Player authentication | -| `movement` | Position update | -| `chat` | Chat message | -| `world_chunk_request` | Request chunk data | -| `player_position_update` | Update position | -| `npc_interaction` | Interact with NPC | -| `collision_check` | Check collision | -| `familiar_command` | Command pet | - -## Python API Functions - -### Logging -```python -server.log_info("message") -server.log_error("message") -server.log_debug("message") -``` - -### Player Operations -```python -player = server.get_player(player_id) -server.set_player_position(player_id, x, y, z) -server.give_player_item(player_id, "item_id", count) -server.send_message_to_player(player_id, "message") -``` - -### Events -```python -server.fire_event("event_name", {"data": "value"}) -server.schedule_event(delay_ms, "event_name", data) -``` - -### Database -```python -result = server.query_database("SELECT * FROM players") -server.execute_database("UPDATE players SET ...") -``` - -## Event Handlers - -Register in Python scripts: - -```python -def on_player_login(event_data): - player_id = event_data['data']['player_id'] - # Handle login - return True - -# Register in register_event_handlers() -``` - -## File Locations - -| Component | Headers | Sources | -|-----------|---------|---------| -| Network | `include/network/` | `src/network/` | -| Game Logic | `include/game/` | `src/game/` | -| Database | `include/database/` | `src/database/` | -| Scripting | `include/scripting/` | `src/scripting/` | -| Config | `include/config/` | `src/config/` | -| Logging | `include/logging/` | `src/logging/` | -| Process | `include/process/` | `src/process/` | - -## Dependencies - -- **ASIO** - Networking -- **nlohmann/json** - JSON -- **GLM** - 3D Math -- **spdlog** - Logging -- **PostgreSQL** - Database -- **Python 3.x** - Scripting - -## Architecture Highlights - -- **Multi-Process** - Worker pool for scaling -- **Async I/O** - Non-blocking network operations -- **Chunk-Based World** - Infinite world system -- **Python Scripting** - Hot-reloadable game logic -- **Sharded Database** - Horizontal scaling -- **Entity System** - Unified entity management - -## Common Tasks - -### Add New Message Type - -1. Add handler in `GameLogic.hpp`: -```cpp -void HandleNewMessage(uint64_t sessionId, const nlohmann::json& data); -``` - -2. Implement in `GameLogic.cpp` - -3. Register in `GameLogic::Initialize()`: -```cpp -messageHandlers_["new_message"] = [this](uint64_t id, const json& data) { - HandleNewMessage(id, data); -}; -``` - -### Add Python Event - -1. Fire event in C++: -```cpp -FirePythonEvent("new_event", {{"data", "value"}}); -``` - -2. Handle in Python: -```python -def on_new_event(event_data): - # Handle event - return True -``` - -### Add Database Table - -1. Create table via CitusClient: -```cpp -citusClient.CreateDistributedTable("table_name", "player_id"); -``` - -2. Query in Python: -```python -result = server.query_database("SELECT * FROM table_name") -``` - -## Debugging - -### Enable Debug Logging - -Edit `config/config.json`: -```json -{ - "logging": { - "level": "debug" - } -} -``` - -### Check Worker Health - -Workers are monitored by master process. Check logs for worker status. - -### Python Script Errors - -Check Python script output in logs. Errors are logged with full stack traces. - -## Performance Tips - -1. **Adjust Process Count** - Match CPU cores -2. **Tune View Distance** - Balance performance vs. visibility -3. **Optimize Chunk Size** - Larger chunks = fewer chunks to manage -4. **Use Connection Pooling** - Already configured -5. **Enable Compression** - For large world data - -## Security Checklist - -- ✅ Rate limiting enabled -- ✅ Authentication required -- ✅ Input validation -- ✅ SQL injection prevention (parameterized queries) -- ✅ Connection monitoring - -## Troubleshooting - -| Issue | Solution | -|-------|----------| -| Port already in use | Change port in config | -| Database connection failed | Check credentials and host | -| Python scripts not loading | Check script directory path | -| Workers not starting | Check process count vs. CPU cores | -| Memory issues | Reduce maxActiveChunks | - diff --git a/STRUCTURE.md b/STRUCTURE.md deleted file mode 100644 index 72adb9c..0000000 --- a/STRUCTURE.md +++ /dev/null @@ -1,637 +0,0 @@ -# Game Server Repository Structure - -## Project Overview - -This is a **3D multiplayer game server** built in C++17 with Python scripting support. The server implements a distributed architecture with multi-process worker pools, chunk-based world generation, and real-time networking capabilities. - -**Key Features:** -- Multi-process architecture with worker pools -- 3D chunk-based infinite world system -- Real-time networking with ASIO -- Python scripting engine for game logic -- Distributed database with Citus (PostgreSQL extension) -- Comprehensive logging and monitoring -- Entity management system with collision detection -- NPC system with AI behaviors - ---- - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Master Process │ -│ (ProcessPool Manager) │ -└──────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────────┼──────────────┐ - │ │ │ - ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ - │ Worker │ │ Worker │ │ Worker │ - │ 1 │ │ 2 │ │ N │ - └───┬────┘ └───┬────┘ └───┬────┘ - │ │ │ - └──────────────┼──────────────┘ - │ - ┌────────────▼────────────┐ - │ GameServer (ASIO) │ - │ - TCP Acceptor │ - │ - Connection Manager │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ GameSession │ - │ - Message Handling │ - │ - Rate Limiting │ - │ - Compression │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ GameLogic │ - │ - Message Routing │ - │ - World Management │ - │ - Entity System │ - └────────────┬────────────┘ - │ - ┌─────────────────┼─────────────────┐ - │ │ │ -┌───▼────┐ ┌───────▼──────┐ ┌─────▼─────┐ -│ World │ │ Entity │ │ NPC │ -│ Chunks │ │ Manager │ │ System │ -└────────┘ └──────────────┘ └───────────┘ - │ │ │ - └─────────────────┼─────────────────┘ - │ - ┌────────────▼────────────┐ - │ Python Scripting │ - │ - Event Handlers │ - │ - Game Logic Scripts │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ CitusClient │ - │ - Sharded Database │ - │ - Player Data │ - └─────────────────────────┘ -``` - ---- - -## Directory Structure - -``` -gameserver/ -│ -├── ARCHITECTURE_DIAGRAM.md -├── build.sh -├── clients -│   ├── agdk-imgui -│   │   ├── AndroidManifest.xml -│   │   ├── assets -│   │   │   ├── config -│   │   │   │   └── config.json -│   │   │   └── shaders -│   │   │   ├── basic.frag -│   │   │   └── basic.vert -│   │   ├── build.gradle -│   │   ├── CMakeLists.txt -│   │   ├── include -│   │   │   ├── EntityState.hpp -│   │   │   ├── GameClient.hpp -│   │   │   ├── GameState.hpp -│   │   │   ├── InputHandler.hpp -│   │   │   ├── NetworkClient.hpp -│   │   │   ├── Renderer.hpp -│   │   │   ├── ShaderProgram.hpp -│   │   │   ├── TextureManager.hpp -│   │   │   └── UIManager.hpp -│   │   └── src -│   │   ├── AndroidMain.cpp -│   │   ├── EntityState.cpp -│   │   ├── GameClient.cpp -│   │   ├── GameState.cpp -│   │   ├── InputHandler.cpp -│   │   ├── NetworkClient.cpp -│   │   ├── Renderer.cpp -│   │   ├── ShaderProgram.cpp -│   │   ├── TextureManager.cpp -│   │   └── UIManager.cpp -│   ├── ogre3d-py -│   │   ├── game -│   │   │   └── __init__.py -│   │   ├── gui -│   │   │   ├── __init__.py -│   │   │   └── input.py -│   │   ├── main.py -│   │   ├── network -│   │   │   ├── __init__.py -│   │   │   └── protocol.py -│   │   ├── README.MD -│   │   ├── renderer -│   │   │   └── __init__.py -│   │   ├── requirements.txt -│   │   ├── run_client.bat -│   │   ├── run_client.sh -│   │   └── setup.py -│   └── wx-cpp -│   ├── build.sh -│   ├── CMakeLists.txt -│   ├── config -│   │   ├── input_config.json -│   │   └── network_config.json -│   ├── include -│   │   ├── client -│   │   │   ├── Camera.hpp -│   │   │   ├── ClientFrame.hpp -│   │   │   ├── ConnectionState.hpp -│   │   │   ├── EventDispatcher.hpp -│   │   │   ├── GameClient.hpp -│   │   │   ├── GLCanvas.hpp -│   │   │   ├── InputEvents.hpp -│   │   │   ├── InputManager.hpp -│   │   │   ├── NetworkClient.hpp -│   │   │   ├── NetworkMonitor.hpp -│   │   │   ├── RenderSystem.hpp -│   │   │   └── UIComponents.hpp -│   │   └── python -│   │   ├── PythonScriptManager.hpp -│   │   └── ScriptBindings.hpp -│   ├── resources -│   │   ├── config -│   │   ├── shaders -│   │   └── textures -│   ├── scripts -│   │   └── game_scripts.py -│   ├── src -│   │   ├── ClientApp.cpp -│   │   ├── ClientFrame.cpp -│   │   ├── ConnectionState.cpp -│   │   ├── EventDispatcher.cpp -│   │   ├── GameClient.cpp -│   │   ├── GameWorld.cpp -│   │   ├── GLCanvas.cpp -│   │   ├── InputManager.cpp -│   │   ├── main.cpp -│   │   ├── NetworkClient.cpp -│   │   └── python -│   │   └── PythonScriptManager.cpp -│   └── STRUCTURE.md -├── CMakeLists.txt -├── config -│   ├── config.json -│   ├── items.json -│   └── loot_tables.json -├── CONTRIBUTING.md -├── examples -│   ├── logging.json -│   ├── server.cpp -│   └── test.py -├── .gitignore -├── include -│   ├── config -│   │   └── ConfigManager.hpp -│   ├── database -│   │   ├── CitusClient.hpp -│   │   └── DatabasePool.hpp -│   ├── game -│   │   ├── ChunkCache.hpp -│   │   ├── ChunkLOD.hpp -│   │   ├── ChunkPool.hpp -│   │   ├── ChunkStreamer.hpp -│   │   ├── CollisionSystem.hpp -│   │   ├── EntityManager.hpp -│   │   ├── GameEntity.hpp -│   │   ├── GameLogic.hpp -│   │   ├── InventorySystem.hpp -│   │   ├── LootItem.hpp -│   │   ├── LootTable.hpp -│   │   ├── MobSystem.hpp -│   │   ├── NPCSystem.hpp -│   │   ├── PlayerManager.hpp -│   │   ├── WorldChunk.hpp -│   │   └── WorldGenerator.hpp -│   ├── logging -│   │   └── Logger.hpp -│   ├── network -│   │   ├── ConnectionManager.hpp -│   │   ├── GameServer.hpp -│   │   └── GameSession.hpp -│   ├── process -│   │   └── ProcessPool.hpp -│   └── scripting -│   ├── PythonEvent.hpp -│   ├── PythonModule.hpp -│   └── PythonScripting.hpp -├── install_dependencies_linux.sh -├── install_dependencies_mac.sh -├── LICENSE -├── MOBS_SYSTEM.md -├── QUICK_REFERENCE.md -├── README.md -├── scripts -│   ├── client -│   │   └── ui_handlers.py -│   ├── db.sql -│   ├── game_events.py -│   ├── loot_handlers.py -│   ├── mobs.py -│   ├── quests.py -│   └── server -│   └── game_logic.py -├── setup_dependencies.sh -├── src -│   ├── config -│   │   └── ConfigManager.cpp -│   ├── database -│   │   ├── CitusClient.cpp -│   │   └── DatabasePool.cpp -│   ├── game -│   │   ├── ChunkCache.cpp -│   │   ├── ChunkLOD.cpp -│   │   ├── ChunkPool.cpp -│   │   ├── ChunkStreamer.cpp -│   │   ├── GameEntity.cpp -│   │   ├── GameLogic.cpp -│   │   ├── InventorySystem.cpp -│   │   ├── LootItem.cpp -│   │   ├── LootTable.cpp -│   │   ├── MobSystem.cpp -│   │   ├── NPCSystem.cpp -│   │   ├── PlayerManager.cpp -│   │   └── WorldChunk.cpp -│   ├── logging -│   │   └── Logger.cpp -│   ├── main.cpp -│   ├── network -│   │   ├── ConnectionManager.cpp -│   │   ├── GameServer.cpp -│   │   └── GameSession.cpp -│   ├── process -│   │   └── ProcessPool.cpp -│   └── scripting -│   ├── PythonAPI.cpp -│   └── PythonScripting.cpp -├── STRUCTURE.md -└── vcpkg.json -``` - ---- - -## Core Components - -### 1. Process Management (`process/`) - -**ProcessPool** -- Manages multiple worker processes for horizontal scaling -- Master process spawns and monitors worker processes -- Each worker runs an independent game server instance -- Supports graceful shutdown and worker health monitoring - -**Key Features:** -- Process-based parallelism (not just threads) -- Inter-process communication (IPC) -- Worker health monitoring -- Automatic worker restart on failure - ---- - -### 2. Network Layer (`network/`) - -**GameServer** -- ASIO-based TCP server -- Handles incoming connections -- Manages worker threads for I/O operations -- Supports SO_REUSEPORT for load balancing - -**GameSession** -- Per-client connection handler -- Message parsing and routing -- Rate limiting and throttling -- Compression support -- Heartbeat/ping-pong mechanism -- Session data storage -- Group management (for broadcasting) - -**ConnectionManager** -- Tracks active connections -- Manages connection lifecycle -- Provides connection statistics - -**Key Features:** -- Async I/O with ASIO -- JSON message protocol -- Rate limiting per session -- Message compression -- Connection quality monitoring (latency tracking) -- Graceful connection shutdown - ---- - -### 3. Game Logic (`game/`) - -**GameLogic** (Central Orchestrator) -- Routes messages to appropriate handlers -- Manages game state -- Coordinates world, entity, and NPC systems -- Integrates with Python scripting -- Handles player lifecycle (connect/disconnect) - -**Message Types Handled:** -- `login` - Player authentication -- `movement` - Player position updates -- `chat` - Chat messages -- `combat` - Combat actions -- `inventory` - Inventory operations -- `quest` - Quest interactions -- `world_chunk_request` - Chunk loading -- `player_position_update` - Position sync -- `npc_interaction` - NPC interactions -- `collision_check` - Collision queries -- `familiar_command` - Pet/familiar commands - -**EntityManager** -- Manages all game entities (players, NPCs, objects) -- Provides spatial queries (entities in radius) -- Handles entity ownership (e.g., player's pets) -- Entity serialization for network transmission - -**PlayerManager** -- Player state management -- Player data persistence -- Player statistics tracking - -**WorldChunk** -- Represents a 16x16 block chunk of the world -- Stores block data and heightmap -- Generates rendering geometry (vertices, triangles) -- Generates collision mesh -- Tracks entities within chunk -- Supports serialization for network transmission - -**WorldGenerator** -- Procedural world generation -- Biome placement -- Terrain height generation -- Chunk generation on-demand - -**NPCSystem** -- NPC spawning and despawning -- NPC AI behaviors -- NPC interaction handling -- NPC state management - -**CollisionSystem** -- 3D collision detection -- Spatial partitioning (grid-based) -- Raycasting support -- Collision response - ---- - -### 4. Database Layer (`database/`) - -**CitusClient** -- Interface to Citus (distributed PostgreSQL) -- Shard management -- Query routing to appropriate shards -- Player data sharded by `player_id` -- Supports distributed tables and reference tables - -**DatabasePool** -- Connection pooling for PostgreSQL -- Manages database connections efficiently -- Thread-safe connection access - -**Key Features:** -- Horizontal scaling via sharding -- Automatic query routing -- Connection pooling -- Support for distributed analytics queries - ---- - -### 5. Scripting System (`scripting/`) - -**PythonScripting** -- Embeds Python interpreter -- Loads and manages Python modules -- Event-driven scripting architecture -- Hot-reload support for development -- Thread-safe Python execution (GIL management) - -**PythonAPI** (C++ functions exposed to Python) -- Logging functions -- Player manipulation (position, items, stats) -- Database queries -- Event firing -- Utility functions (time, UUID, JSON, math) - -**Event System:** -- Python scripts register event handlers -- C++ fires events that Python can handle -- Bidirectional communication (C++ ↔ Python) - -**Key Features:** -- Hot-reload scripts without server restart -- Event-driven architecture -- Rich C++ API exposed to Python -- Thread-safe execution - ---- - -### 6. Configuration (`config/`) - -**ConfigManager** -- Singleton configuration manager -- Loads JSON configuration files -- Provides typed accessors for config values -- Supports runtime configuration reloading - -**Configuration Sections:** -- `server` - Port, connections, processes, threads -- `world` - Seed, chunk size, view distance, terrain -- `npcs` - NPC spawn settings -- `collision` - Collision system settings -- `database` - Database connection and sharding -- `logging` - Log levels and file settings -- `pythonScripting` - Script directory and hot-reload - ---- - -### 7. Logging (`logging/`) - -**Logger** -- Wrapper around spdlog -- Multiple log levels (TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL) -- Console and file sinks -- Log rotation support -- Thread-safe logging - ---- - -## Data Flow - -### Client Connection Flow - -``` -1. Client connects → GameServer::DoAccept() -2. Create GameSession → sessionFactory_() -3. GameSession::Start() → Begin async read -4. Message received → GameSession::HandleMessage() -5. Route to GameLogic::HandleMessage() -6. GameLogic routes to appropriate handler -7. Handler processes message (may call Python scripts) -8. Response sent via GameSession::Send() -``` - -### World Chunk Loading Flow - -``` -1. Player moves → HandlePlayerPositionUpdate() -2. GameLogic::GenerateWorldAroundPlayer() -3. Calculate required chunks based on view distance -4. For each chunk: - a. Check if already loaded - b. If not, call WorldGenerator to generate - c. Create WorldChunk object - d. Generate geometry and collision mesh -5. Send chunk data to player via GameSession -6. Track loaded chunks for cleanup -``` - -### Python Event Flow - -``` -1. Game event occurs (e.g., player login) -2. GameLogic::FirePythonEvent() -3. PythonScripting::FireEvent() -4. Lookup registered Python handlers -5. Call Python function with event data -6. Python handler processes event -7. Python may call back to C++ via PythonAPI -8. C++ executes requested action -``` - ---- - -## Technology Stack - -### Dependencies - -**C++ Libraries:** -- **ASIO** - Asynchronous networking -- **nlohmann/json** - JSON parsing and generation -- **GLM** - 3D math library (vectors, matrices) -- **spdlog** - Logging library -- **PostgreSQL/libpq** - Database client -- **Python C API** - Python embedding - -**Build System:** -- CMake 3.15+ -- C++17 standard - -**Database:** -- PostgreSQL with Citus extension (for sharding) - -**Scripting:** -- Python 3.x - ---- - -## Key Design Patterns - -1. **Singleton Pattern** - ConfigManager, GameLogic, Logger, etc. -2. **Factory Pattern** - Session factory for creating GameSession instances -3. **Observer Pattern** - Event system for Python scripting -4. **Pool Pattern** - ProcessPool, DatabasePool -5. **Spatial Partitioning** - Collision system uses grid-based partitioning -6. **Component System** - Entity system with different entity types - ---- - -## Message Protocol - -Messages are JSON-encoded with the following structure: - -```json -{ - "type": "message_type", - "data": { - // Message-specific data - } -} -``` - -**Common Message Types:** -- `login` - Authentication -- `movement` - Position updates -- `chat` - Chat messages -- `world_chunk_request` - Request chunk data -- `player_position_update` - Update player position -- `npc_interaction` - Interact with NPC -- `collision_check` - Check collision -- `familiar_command` - Command pet/familiar - ---- - -## Performance Considerations - -1. **Multi-Process Architecture** - Scales horizontally across CPU cores -2. **Async I/O** - Non-blocking network operations -3. **Connection Pooling** - Efficient database access -4. **Spatial Partitioning** - Optimized collision and entity queries -5. **Chunk-Based World** - Only load visible chunks -6. **Rate Limiting** - Prevent client abuse -7. **Message Compression** - Reduce network bandwidth -8. **Hot-Reload Scripts** - No server restart for script changes - ---- - -## Security Features - -1. **Rate Limiting** - Per-session message throttling -2. **Authentication** - Token-based player authentication -3. **Input Validation** - Message validation before processing -4. **Connection Monitoring** - Track and disconnect abusive clients -5. **SQL Injection Prevention** - Parameterized queries (via database pool) - ---- - -## Development Workflow - -1. **Configuration** - Edit `config/config.json` -2. **C++ Code** - Modify headers in `include/`, implementations in `src/` -3. **Python Scripts** - Edit scripts in `scripts/` (hot-reload enabled) -4. **Build** - Use CMake to build -5. **Run** - Execute `gameserver3d` binary - ---- - -## Future Enhancements (Based on README) - -- Comprehensive logging system with multiple sinks -- Advanced debugging system with profiling -- Performance monitoring and metrics -- Error detection and crash reporting -- Integration with monitoring tools - ---- - -## File Count Summary - -- **Header Files**: 18 -- **Source Files**: 12 -- **Python Scripts**: 4+ -- **Configuration Files**: 1 -- **Build Files**: 1 (CMakeLists.txt) - ---- - -## Notes - -- The server uses a chunk-based world system similar to Minecraft -- Each worker process runs independently with its own world state -- Python scripting allows for rapid game logic iteration -- Database sharding enables horizontal scaling of player data -- The architecture supports thousands of concurrent players across multiple processes - diff --git a/config/core.json b/config/core.json index 699abc3..e8bd33a 100644 --- a/config/core.json +++ b/config/core.json @@ -1,5 +1,7 @@ { "process": { + "max_message_size": 1048576, + "receive_timeout_ms": 1000, "workers": [ { "protocol": "binary", @@ -38,14 +40,16 @@ "world": { "seed": 12345, - "chunkSize": 32.0, - "viewDistance": 4, - "maxActiveChunks": 100, - "terrainScale": 100.0, - "maxTerrainHeight": 50.0, - "waterLevel": 10.0, - "preloadRadius": 100.0, - "preloadWorld": true + "chunk_size": 32.0, + "view_distance": 4, + "unload_distance": 200.0, + "max_active_chunks": 100, + "terrain_scale": 100.0, + "max_terrain_height": 50.0, + "water_level": 10.0, + "interest_radius": 100.0, + "preload_radius": 100.0, + "preload_world": false }, "npcs": { @@ -116,17 +120,28 @@ "name": "data/game.db", "user": "gameuser", "password": "password", - "workerNodes": [], - "useCitusEmulation": true, - "poolSize": 10, - "reconnectAttempts": 3 + "connection_pool":{ + "enabled": false, + "pool_size": 10, + "pool_threads": 2, + "reconnect_attempts": 3, + "min_connections": 5, + "max_connections": 20, + "connection_timeout_ms": 5000 + }, + "citus": { + "shard_count": 32, + "replication_factor": 2, + "worker_nodes": [] + } }, "logging": { - "level": "debug", + "level": "trace", "file": "logs/server.log", - "maxSize": 10485760, - "backupCount": 5 + "max_file_size": 1048576, + "max_files": 10, + "console_output": true }, "pythonScripting": { diff --git a/config/loot_tables.json b/config/loot_tables.json index b000c99..887c07b 100644 --- a/config/loot_tables.json +++ b/config/loot_tables.json @@ -1,108 +1,114 @@ +[ { - "tables": [ + "tableId": "goblin", + "name": "Goblin Loot Table", + "guaranteedDrops": 1, + "maxDrops": 3, + "uniqueDrops": false, + "goldMultiplier": 1.0, + "minGold": 5, + "maxGold": 20, + "entries": [ { - "tableId": "goblin", - "name": "Goblin Loot Table", - "guaranteedDrops": 1, - "maxDrops": 3, - "uniqueDrops": false, - "goldMultiplier": 1.0, - "minGold": 5, - "maxGold": 20, - "entries": [ - { - "itemId": "gold_coin", - "dropChance": 0.8, - "minQuantity": 1, - "maxQuantity": 5, - "minLevel": 1, - "maxLevel": 100, - "minRarity": 0, - "maxRarity": 0 - }, - { - "itemId": "goblin_ear", - "dropChance": 0.6, - "minQuantity": 1, - "maxQuantity": 2, - "minLevel": 1, - "maxLevel": 100, - "minRarity": 0, - "maxRarity": 0 - }, - { - "itemId": "rusty_sword", - "dropChance": 0.2, - "minQuantity": 1, - "maxQuantity": 1, - "minLevel": 1, - "maxLevel": 10, - "minRarity": 0, - "maxRarity": 1 - }, - { - "itemId": "health_potion_minor", - "dropChance": 0.3, - "minQuantity": 1, - "maxQuantity": 1, - "minLevel": 1, - "maxLevel": 100, - "minRarity": 0, - "maxRarity": 0 - } - ] + "itemId": 0, + "name": "gold coin", + "dropChance": 0.8, + "minQuantity": 1, + "maxQuantity": 5, + "minLevel": 1, + "maxLevel": 100, + "minRarity": 0, + "maxRarity": 0 }, { - "tableId": "dragon", - "name": "Dragon Loot Table", - "guaranteedDrops": 3, - "maxDrops": 8, - "uniqueDrops": true, - "goldMultiplier": 5.0, - "minGold": 1000, - "maxGold": 5000, - "entries": [ - { - "itemId": "gold_coin", - "dropChance": 1.0, - "minQuantity": 100, - "maxQuantity": 500, - "minLevel": 30, - "maxLevel": 100, - "minRarity": 0, - "maxRarity": 0 - }, - { - "itemId": "dragon_scale", - "dropChance": 1.0, - "minQuantity": 5, - "maxQuantity": 20, - "minLevel": 30, - "maxLevel": 100, - "minRarity": 2, - "maxRarity": 4 - }, - { - "itemId": "dragon_heart", - "dropChance": 0.8, - "minQuantity": 1, - "maxQuantity": 1, - "minLevel": 30, - "maxLevel": 100, - "minRarity": 3, - "maxRarity": 5 - }, - { - "itemId": "legendary_sword", - "dropChance": 0.1, - "minQuantity": 1, - "maxQuantity": 1, - "minLevel": 40, - "maxLevel": 100, - "minRarity": 4, - "maxRarity": 5 - } - ] + "itemId": 1, + "name": "goblin ear", + "dropChance": 0.6, + "minQuantity": 1, + "maxQuantity": 2, + "minLevel": 1, + "maxLevel": 100, + "minRarity": 0, + "maxRarity": 0 + }, + { + "itemId": 2, + "name": "rusty sword", + "dropChance": 0.2, + "minQuantity": 1, + "maxQuantity": 1, + "minLevel": 1, + "maxLevel": 10, + "minRarity": 0, + "maxRarity": 1 + }, + { + "itemId": 3, + "name": "health potion minor", + "dropChance": 0.3, + "minQuantity": 1, + "maxQuantity": 1, + "minLevel": 1, + "maxLevel": 100, + "minRarity": 0, + "maxRarity": 0 + } + ] +}, +{ + "tableId": "dragon", + "name": "Dragon Loot Table", + "guaranteedDrops": 3, + "maxDrops": 8, + "uniqueDrops": true, + "goldMultiplier": 5.0, + "minGold": 1000, + "maxGold": 5000, + "entries": [ + { + "itemId": 0, + "name": "gold coin", + "dropChance": 1.0, + "minQuantity": 100, + "maxQuantity": 500, + "minLevel": 30, + "maxLevel": 100, + "minRarity": 0, + "maxRarity": 0 + }, + { + "itemId": 1, + "name": "dragon scale", + "dropChance": 1.0, + "minQuantity": 5, + "maxQuantity": 20, + "minLevel": 30, + "maxLevel": 100, + "minRarity": 2, + "maxRarity": 4 + }, + { + "itemId": 2, + "name": "dragon heart", + "dropChance": 0.8, + "minQuantity": 1, + "maxQuantity": 1, + "minLevel": 30, + "maxLevel": 100, + "minRarity": 3, + "maxRarity": 5 + }, + { + "itemId": 3, + "name": "legendary sword", + "dropChance": 0.1, + "minQuantity": 1, + "maxQuantity": 1, + "minLevel": 40, + "maxLevel": 100, + "minRarity": 4, + "maxRarity": 5 } - ] -} \ No newline at end of file + ] +} +] diff --git a/include/config/ConfigManager.hpp b/include/config/ConfigManager.hpp index 5633abc..4626a26 100644 --- a/include/config/ConfigManager.hpp +++ b/include/config/ConfigManager.hpp @@ -81,7 +81,7 @@ class ConfigManager { std::string GetDatabaseBackend() const; int GetDatabasePoolSize() const; std::vector GetCitusWorkerNodes() const; - int GetShardCount() const; + int GetShardCount(int default_value=32) const; // Game configuration int GetMaxPlayersPerSession() const; diff --git a/include/database/Backend.hpp b/include/database/Backend.hpp index fa0f4e5..1251cc1 100644 --- a/include/database/Backend.hpp +++ b/include/database/Backend.hpp @@ -12,11 +12,13 @@ class Logger { template static void Warn(Args&&...) {} template - static void Debug(Args&&...) {} - template static void Error(Args&&...) {} template static void Critical(Args&&...) {} + template + static void Debug(Args&&...) {} + template + static void Trace(Args&&...) {} }; #endif diff --git a/include/database/CitusClient.hpp b/include/database/CitusClient.hpp index 15dd62b..a1868e9 100644 --- a/include/database/CitusClient.hpp +++ b/include/database/CitusClient.hpp @@ -80,16 +80,10 @@ class CitusClient : public PostgreSqlClient { bool ReplicateReferenceTables(); // Backward compatibility methods - bool ExecuteDatabase(const std::string& sql) { return Execute(sql); } - nlohmann::json QueryDatabase(const std::string& sql) { return Query(sql); } - - // Legacy singleton access (for backward compatibility) - static CitusClient* GetInstancePtr() { - std::lock_guard lock(instanceMutex_); - return instance_; - } - - bool IsCitusEnabled() const { return citusEnabled_; } + bool ExecuteDatabase(const std::string& sql); + nlohmann::json QueryDatabase(const std::string& sql); + static CitusClient* GetInstancePtr(); + bool IsCitusEnabled() const; bool ConnectToDatabase(const std::string& dbname) override; private: diff --git a/include/database/DbManager.hpp b/include/database/DbManager.hpp index 6651e6b..acf89c5 100644 --- a/include/database/DbManager.hpp +++ b/include/database/DbManager.hpp @@ -27,76 +27,55 @@ #include "database/SQLiteClient.hpp" #endif -/** - * @brief Database Manager Singleton - * - * Manages database connections and provides access to the appropriate - * database backend based on configuration. - */ +enum BackendType { + SQLITE, + POSTGRESQL, + CITUS, + INVALID +}; + class DbManager { public: - // Database types - enum BackendType { - SQLITE, - POSTGRESQL, - CITUS, - INVALID - }; - static DbManager& GetInstance(); - // Lifecycle Management bool Initialize(const std::string& configPath = ""); void Shutdown(); - bool IsInitialized() const { return initialized_; } + bool IsInitialized() const; - const SQLProvider& GetSQLProvider() const { return sqlProvider_; } + const SQLProvider& GetSQLProvider() const; bool LoadSQLForBackend(); bool EnsureDatabaseExists(const std::string& configPath = ""); std::string EscapeString(const std::string& input); - // Backend Management bool SaveGameState(const std::string& key, const nlohmann::json& state); bool SetBackend(BackendType type, const nlohmann::json& config); - DatabaseBackend* GetBackend() const { return backend_.get(); } - BackendType GetCurrentType() const { return currentType_; } - nlohmann::json GetPlayer(uint64_t playerId){ return backend_->GetPlayer(playerId); }; - - nlohmann::json Query(const std::string& sql) { return backend_->Query(sql); }; - nlohmann::json QueryWithParams(const std::string& sql, const std::vector& params) - { return backend_->QueryWithParams(sql, params); }; - bool Execute(const std::string& sql) { return backend_->Execute(sql); }; - bool ExecuteWithParams(const std::string& sql, const std::vector& params) - { return backend_->ExecuteWithParams(sql, params); }; - - bool UpdatePlayerPosition(uint64_t playerId, float x, float y, float z) { - if (backend_) { - return backend_->UpdatePlayerPosition(playerId, x, y, z); - } - return false; - } - - // Configuration + DatabaseBackend* GetBackend() const; + BackendType GetCurrentType() const; + nlohmann::json GetPlayer(uint64_t playerId); + + nlohmann::json Query(const std::string& sql); + nlohmann::json QueryWithParams(const std::string& sql, const std::vector& params); + bool Execute(const std::string& sql); + bool ExecuteWithParams(const std::string& sql, const std::vector& params); + + bool UpdatePlayerPosition(uint64_t playerId, float x, float y, float z); + bool LoadConfiguration(const std::string& configPath = ""); - nlohmann::json GetConfiguration() const { return config_; } + nlohmann::json GetConfiguration() const; - // Connection Management bool Connect(); bool Reconnect(); void Disconnect(); bool IsConnected() const; - // Shard Mapping int GetShardId(uint64_t entityId) const; int GetTotalShards() const; - // Statistics nlohmann::json GetStatistics() const; void PrintStatistics() const; - // Migration Management bool RunMigrations(); bool CheckMigrationStatus(); bool RollbackMigration(int version); @@ -120,7 +99,6 @@ class DbManager { std::atomic initialized_; std::atomic connected_; - // Statistics struct Statistics { std::atomic queriesExecuted{0}; std::atomic queriesFailed{0}; @@ -131,7 +109,6 @@ class DbManager { }; mutable Statistics stats_; - // Helper methods bool ValidateConfiguration(const nlohmann::json& config) const; BackendType ParseBackendType(const std::string& typeStr) const; std::string BackendTypeToString(BackendType type) const; diff --git a/include/database/DbService.hpp b/include/database/DbService.hpp new file mode 100644 index 0000000..dd7dfa9 --- /dev/null +++ b/include/database/DbService.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +//#include "logging/Logger.hpp" +//#include "database/DbManager.hpp" + +#include "game/Player.hpp" +#include "game/PlayerManager.hpp" + +class DatabaseService { +public: + explicit DatabaseService(asio::io_context& main_io, size_t num_threads = 4); + ~DatabaseService(); + + void asyncGetPlayer(uint64_t playerId, + std::function callback); + void asyncSavePlayerData(uint64_t playerId, const nlohmann::json& data, + std::function callback); + void asyncPlayerExists(uint64_t playerId, + std::function callback); + void asyncPlayerExists(const std::string& username, + std::function callback); + void asyncCreatePlayer(const std::string& username, + std::function)> callback); + void asyncAuthenticatePlayer(const std::string& username, const std::string& password, + std::function callback); + void asyncGetPlayerByName(const std::string& username, + std::function callback); + void asyncSaveChunkData(int chunkX, int chunkZ, const nlohmann::json& data, + std::function callback); + void asyncLoadChunkData(int chunkX, int chunkZ, + std::function callback); + + void shutdown(); + +private: + asio::io_context& main_io_; + asio::thread_pool db_pool_; + std::unique_ptr> work_guard_; +}; diff --git a/include/database/PostgreSqlClient.hpp b/include/database/PostgreSqlClient.hpp index 021ff70..549a8f5 100644 --- a/include/database/PostgreSqlClient.hpp +++ b/include/database/PostgreSqlClient.hpp @@ -15,24 +15,15 @@ #include "utils/Converters.hpp" #include "database/Backend.hpp" -//#include "database/DbManager.hpp" - -/** - * @brief PostgreSQL Client Implementation - * - * Provides a concrete implementation of DatabaseBackend for PostgreSQL. - * Includes connection pooling, prepared statements, and transaction support. - */ + class PostgreSqlClient : public DatabaseBackend { public: - // Connection pool entry struct Connection { PGconn* conn; bool inUse; std::chrono::steady_clock::time_point lastUsed; }; - // Prepared statement struct PreparedStatement { std::string name; std::string sql; @@ -42,7 +33,6 @@ class PostgreSqlClient : public DatabaseBackend { PostgreSqlClient(const nlohmann::json& config, const SQLProvider& sqlProvider); virtual ~PostgreSqlClient(); - // Connection Management bool Connect() override; bool ConnectToDatabase(const std::string& dbname) override; bool Reconnect() override; @@ -51,7 +41,6 @@ class PostgreSqlClient : public DatabaseBackend { bool CheckHealth() override; void ReconnectAll() override; - // Player Data Operations bool SavePlayerData(uint64_t playerId, const nlohmann::json& data) override; nlohmann::json LoadPlayerData(uint64_t playerId) override; bool UpdatePlayer(uint64_t playerId, const nlohmann::json& updates) override; @@ -62,46 +51,38 @@ class PostgreSqlClient : public DatabaseBackend { bool UpdatePlayerStats(uint64_t playerId, const nlohmann::json& stats) override; nlohmann::json GetPlayer(uint64_t playerId) override; - // Game State Operations bool SaveGameState(const std::string& key, const nlohmann::json& state) override; nlohmann::json LoadGameState(const std::string& key) override; bool DeleteGameState(const std::string& key) override; std::vector ListGameStates() override; - // World Data Operations bool SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& chunkData) override; nlohmann::json LoadChunkData(int chunkX, int chunkZ) override; bool DeleteChunkData(int chunkX, int chunkZ) override; std::vector> ListChunksInRange(int centerX, int centerZ, int radius) override; - // Inventory Operations bool SaveInventory(uint64_t playerId, const nlohmann::json& inventory) override; nlohmann::json LoadInventory(uint64_t playerId) override; - // Quest Operations bool SaveQuestProgress(uint64_t playerId, const std::string& questId, const nlohmann::json& progress) override; nlohmann::json LoadQuestProgress(uint64_t playerId, const std::string& questId) override; std::vector ListActiveQuests(uint64_t playerId) override; - // Transaction Operations bool BeginTransaction() override; bool CommitTransaction() override; bool RollbackTransaction() override; bool ExecuteTransaction(const std::function& operation) override; - // Query Operations nlohmann::json Query(const std::string& sql) override; nlohmann::json QueryWithParams(const std::string& sql, const std::vector& params) override; bool Execute(const std::string& sql) override; bool ExecuteWithParams(const std::string& sql, const std::vector& params) override; - // Shard Operations (simulated for compatibility) nlohmann::json QueryShard(int shardId, const std::string& sql) override; nlohmann::json QueryShardWithParams(int shardId, const std::string& sql, const std::vector& params) override; bool ExecuteShard(int shardId, const std::string& sql) override; bool ExecuteShardWithParams(int shardId, const std::string& sql, const std::vector& params) override; - // Utility Methods std::string EscapeString(const std::string& str) override; int GetShardId(uint64_t entityId) const override; int GetTotalShards() const override; @@ -109,24 +90,20 @@ class PostgreSqlClient : public DatabaseBackend { int64_t GetLastInsertId() override; int GetAffectedRows() override; - // Statistics nlohmann::json GetDatabaseStats() override; void ResetStats() override; - // Connection Pool Management bool InitializeConnectionPool(size_t minConnections, size_t maxConnections) override; void ReleaseConnectionPool() override; size_t GetActiveConnections() const override; size_t GetIdleConnections() const override; - // Prepared Statements bool PrepareStatement(const std::string& name, const std::string& sql, int paramCount); bool ExecutePrepared(const std::string& name, const std::vector& params); nlohmann::json QueryPrepared(const std::string& name, const std::vector& params); - // Backward compatibility methods - bool ExecuteDatabase(const std::string& sql) { return Execute(sql); } - nlohmann::json QueryDatabase(const std::string& sql) { return Query(sql); } + bool ExecuteDatabase(const std::string& sql); + nlohmann::json QueryDatabase(const std::string& sql); private: const SQLProvider& sqlProvider_; diff --git a/include/database/SQLProvider.hpp b/include/database/SQLProvider.hpp index 6169449..9039a57 100644 --- a/include/database/SQLProvider.hpp +++ b/include/database/SQLProvider.hpp @@ -8,51 +8,10 @@ class SQLProvider { public: - bool LoadFromFile(const std::string& filePath) { - std::ifstream file(filePath); - if (!file.is_open()) { - std::cerr << "Failed to open SQL file: " << filePath << std::endl; - return false; - } + bool LoadFromFile(const std::string& filePath); - std::string line, currentKey, currentQuery; - while (std::getline(file, line)) { - // Check for section marker: -- [key] - if (line.size() > 5 && line[0] == '-' && line[1] == '-' && line[2] == ' ' && line[3] == '[') { - // Save previous query if any - if (!currentKey.empty() && !currentQuery.empty()) { - queries_[currentKey] = currentQuery; - } - // Extract key - size_t endBracket = line.find(']', 4); - if (endBracket != std::string::npos) { - currentKey = line.substr(4, endBracket - 4); - currentQuery.clear(); - } else { - currentKey.clear(); - } - } else if (!currentKey.empty()) { - // Append line to current query (preserve newlines) - currentQuery += line + "\n"; - } - } - // Save last query - if (!currentKey.empty() && !currentQuery.empty()) { - queries_[currentKey] = currentQuery; - } - - file.close(); - return true; - } - - std::string GetQuery(const std::string& key) const { - auto it = queries_.find(key); - if (it == queries_.end()) { - return ""; - } - return it->second; - } + std::string GetQuery(const std::string& key) const; private: std::unordered_map queries_; -}; \ No newline at end of file +}; diff --git a/include/database/SQLiteClient.hpp b/include/database/SQLiteClient.hpp index 0b4290b..88a4dc0 100644 --- a/include/database/SQLiteClient.hpp +++ b/include/database/SQLiteClient.hpp @@ -16,19 +16,11 @@ #include "database/Backend.hpp" -/** - * @brief SQLite Client Implementation - * - * Provides a concrete implementation of DatabaseBackend for SQLite. - * Uses a single connection with mutex locking for simplicity. - * Supports JSON storage via SQLite's JSON1 extension (if available). - */ class SQLiteClient : public DatabaseBackend { public: explicit SQLiteClient(const nlohmann::json& config, const SQLProvider& sqlProvider); virtual ~SQLiteClient(); - // Connection Management bool Connect() override; bool ConnectToDatabase(const std::string& dbname) override; bool Reconnect() override; @@ -37,7 +29,6 @@ class SQLiteClient : public DatabaseBackend { bool CheckHealth() override; void ReconnectAll() override; - // Player Data Operations bool SavePlayerData(uint64_t playerId, const nlohmann::json& data) override; nlohmann::json LoadPlayerData(uint64_t playerId) override; bool UpdatePlayer(uint64_t playerId, const nlohmann::json& updates) override; @@ -48,40 +39,33 @@ class SQLiteClient : public DatabaseBackend { bool UpdatePlayerStats(uint64_t playerId, const nlohmann::json& stats) override; nlohmann::json GetPlayer(uint64_t playerId) override; - // Game State Operations bool SaveGameState(const std::string& key, const nlohmann::json& state) override; nlohmann::json LoadGameState(const std::string& key) override; bool DeleteGameState(const std::string& key) override; std::vector ListGameStates() override; - // World Data Operations bool SaveChunkData(int chunkX, int chunkZ, const nlohmann::json& chunkData) override; nlohmann::json LoadChunkData(int chunkX, int chunkZ) override; bool DeleteChunkData(int chunkX, int chunkZ) override; std::vector> ListChunksInRange(int centerX, int centerZ, int radius) override; - // Inventory Operations bool SaveInventory(uint64_t playerId, const nlohmann::json& inventory) override; nlohmann::json LoadInventory(uint64_t playerId) override; - // Quest Operations bool SaveQuestProgress(uint64_t playerId, const std::string& questId, const nlohmann::json& progress) override; nlohmann::json LoadQuestProgress(uint64_t playerId, const std::string& questId) override; std::vector ListActiveQuests(uint64_t playerId) override; - // Transaction Operations bool BeginTransaction() override; bool CommitTransaction() override; bool RollbackTransaction() override; bool ExecuteTransaction(const std::function& operation) override; - // Query Operations nlohmann::json Query(const std::string& sql) override; nlohmann::json QueryWithParams(const std::string& sql, const std::vector& params) override; bool Execute(const std::string& sql) override; bool ExecuteWithParams(const std::string& sql, const std::vector& params) override; - // Shard Operations (ignored, forward to main) nlohmann::json QueryShard(int shardId, const std::string& sql) override; nlohmann::json QueryShardWithParams(int shardId, const std::string& sql, const std::vector& params) override; @@ -89,7 +73,6 @@ class SQLiteClient : public DatabaseBackend { bool ExecuteShardWithParams(int shardId, const std::string& sql, const std::vector& params) override; - // Utility Methods std::string EscapeString(const std::string& str) override; int GetShardId(uint64_t entityId) const override; int GetTotalShards() const override; @@ -97,29 +80,23 @@ class SQLiteClient : public DatabaseBackend { int64_t GetLastInsertId() override; int GetAffectedRows() override; - // Statistics nlohmann::json GetDatabaseStats() override; void ResetStats() override; - // Connection Pool Management (SQLite uses single connection) bool InitializeConnectionPool(size_t minConnections, size_t maxConnections) override; void ReleaseConnectionPool() override; size_t GetActiveConnections() const override; size_t GetIdleConnections() const override; private: - // Core database handle sqlite3* db_; const SQLProvider& sqlProvider_; - // Configuration nlohmann::json config_; std::string dbPath_; - // Synchronization mutable std::mutex dbMutex_; - // Statistics struct SQLiteStats { std::atomic totalQueries{0}; std::atomic failedQueries{0}; @@ -129,14 +106,11 @@ class SQLiteClient : public DatabaseBackend { }; SQLiteStats stats_; - // Last operation results int64_t lastInsertId_; int affectedRows_; - // Shard configuration (dummy, for compatibility) int totalShards_; - // Helper methods bool OpenDatabase(const std::string& path); void CloseDatabase(); bool ExecuteSql(const std::string& sql, std::vector>* results = nullptr); diff --git a/include/game/GameData.hpp b/include/game/GameData.hpp new file mode 100644 index 0000000..ac59ce4 --- /dev/null +++ b/include/game/GameData.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include + +struct AuthenticationData { + std::string username; + std::string password; + uint64_t session_id; +}; + +struct PlayerStateData { + uint64_t player_id; + uint32_t input_id; + glm::vec3 position; + glm::vec3 velocity; + glm::vec3 rotation; + glm::vec3 movement; + bool on_ground; + bool jumping; + bool crouching; + bool sprinting; + uint64_t timestamp; + uint64_t session_id; +}; + +struct PlayerPositionData { + uint64_t player_id; + glm::vec3 position; + glm::vec3 velocity; + uint64_t timestamp; + uint64_t session_id; +}; + +struct ChunkRequestData { + int chunk_x; + int chunk_z; + uint8_t lod; + uint64_t session_id; // which session requested it +}; + +struct ChunkData { + int chunk_x; + int chunk_z; + uint8_t lod; + nlohmann::json chunk_json; // already serialized + uint64_t timestamp; + uint64_t session_id; +}; diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 4831b35..67156a2 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -1,14 +1,18 @@ #pragma once -#include #include +#include #include #include #include +#include "database/DbService.hpp" + #include "network/PredictionSystem.hpp" -#include "network/WebSocketProtocol.hpp" +#include "network/IConnection.hpp" + +#include "scripting/PythonScripting.hpp" #include "game/LogicCore.hpp" #include "game/PlayerManager.hpp" @@ -17,31 +21,18 @@ #include "game/SkillSystem.hpp" #include "game/QuestManager.hpp" #include "game/EntityManager.hpp" +#include "game/GameData.hpp" + +class DatabaseService; -class GameLogic : public LogicCore +class GameLogic : public LogicCore, public std::enable_shared_from_this { public: static GameLogic& GetInstance(); // Core lifecycle - void Initialize(); - void Shutdown(); - - // Set connection manager for broadcasting - void SetConnectionManager(std::shared_ptr connMgr) { - connectionManager_ = connMgr; - Logger::Info("ConnectionManager set for GameLogic"); - } - - // Database backend management - void SetDatabaseBackend(std::unique_ptr backend) { - databaseBackend_ = std::move(backend); - Logger::Info("Database backend set for GameLogic"); - } - - DatabaseBackend* GetDatabaseBackend() const { - return databaseBackend_.get(); - } + void Initialize() override; + void Shutdown() override; // World configuration struct WorldConfig : public LogicWorld::WorldConfig {}; @@ -81,85 +72,111 @@ class GameLogic : public LogicCore void HandleTradeRequest(uint64_t sessionId, const nlohmann::json& data); void HandleGoldTransaction(uint64_t sessionId, const nlohmann::json& data); - // World message handlers - void HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json& data); - void HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data); - void HandleWorldChunkHMapRequest(uint64_t sessionId, const nlohmann::json& data); - void HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::json& data); + // Message handlers (remaining non-virtual ones) void HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& data); void HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& data); void HandleEntitySpawnRequest(uint64_t sessionId, const nlohmann::json& data); void HandleFamiliarCommand(uint64_t sessionId, const nlohmann::json& data); - void HandlePlayerState(uint64_t sessionId, const std::vector& data); - // Message handling + // Message handling entry points void HandleMessage(uint64_t sessionId, const nlohmann::json& message); // Player connection/disconnection - void OnPlayerConnected(uint64_t sessionId, uint64_t playerId); - void OnPlayerDisconnected(uint64_t sessionId); + void OnPlayerConnected(uint64_t sessionId, uint64_t playerId) override; + void OnPlayerDisconnected(uint64_t sessionId) override; - // Broadcasting - void BroadcastPlayerUpdates(); + // Broadcasting methods (still used) void BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, - const std::vector& data, float radius = 50.0f); + const std::vector& data, float radius = 50.0f); void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, const std::vector& data, float radius = 50.0f); void SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& position); - - // Helper broadcast methods void BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius); void BroadcastToAllPlayers(const nlohmann::json& message); void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data); void BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message); - void BroadcastPlayerState(uint64_t playerId, const ServerState& state); void BroadcastPlayerSpawn(uint64_t playerId); void BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition); void BroadcastPlayerSpawnJson(uint64_t playerId); void BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition); - void BroadcastPlayerUpdatesJson(); - - // Broadcast entity spawn to nearby players void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, float yaw, const std::string& name); void SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity); void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); - - void HandleAuthentication(uint64_t sessionId, const std::vector& data); - void HandleAuthentication(uint64_t sessionId, const std::string& username, const std::string& password); + void SendAuthenticationSuccess(uint64_t sessionId, uint64_t playerId, const std::string& message); + void SendAuthenticationFailure(uint64_t sessionId, const std::string& message); + + // Callback setters for network layer + void SetSendAuthenticationResponseCallback(std::function cb); + void SetSendChunkCallback(std::function cb); + void SetPlayerStateCallback(std::function cb); + void SetBroadcastPlayerPositionCallback(std::function cb); + + // Incoming data entry points (called by sessions) + void OnAuthentication(const AuthenticationData& data); + void OnChunkRequest(const ChunkRequestData& data); + void OnPlayerPosition(const PlayerPositionData& data); + void OnPlayerState(const PlayerStateData& data); + + // Overrides of virtual methods from LogicCore + void FirePythonEvent(const std::string& eventName, const nlohmann::json& data) override; + nlohmann::json CallPythonFunction(const std::string& moduleName, const std::string& functionName, + const nlohmann::json& args) override; + void RegisterPythonEventHandlers() override; + void SaveGameState() override; + void CleanupOldData() override; + void ProcessGameTick(float deltaTime) override; + void SpawnEnemies() override; + void RespawnNPCs() override; + void SpawnResources() override; + void SaveLoop() override; + void HandleLogin(uint64_t sessionId, const nlohmann::json& data) override; + void HandleChat(uint64_t sessionId, const nlohmann::json& data) override; + void HandleCombat(uint64_t sessionId, const nlohmann::json& data) override; + void HandleQuest(uint64_t sessionId, const nlohmann::json& data) override; + + // Additional helpers + void SetDatabaseService(DatabaseService* dbService); + void SetConnectionManager(std::shared_ptr connMgr); + void SetDatabaseBackend(std::unique_ptr backend); + DatabaseBackend* GetDatabaseBackend() const; private: GameLogic(); ~GameLogic(); + bool initialized_ = false; static std::mutex instanceMutex_; static GameLogic* instance_; - // Component systems - //PlayerManager& playerManager_; - - // Database backend std::unique_ptr databaseBackend_; - - // Connection manager for broadcasting std::shared_ptr connectionManager_; - std::unordered_map playerPrediction_; std::mutex predictionMutex_; + DatabaseService* dbService_ = nullptr; - // Thread functions - void GameLoop(); - void SpawnerLoop(); - void SaveLoop(); + std::function sendAuthResponseCb_; + std::function sendChunkCb_; + std::function broadcastPlayerPositionCb_; + std::function playerStateCb_; - // Game tick processing - void ProcessGameTick(float deltaTime); + bool pythonEnabled_ = false; + + void GameLoop() override; + void SpawnerLoop() override; void UpdateWorld(float deltaTime); // Helper methods void RegisterWorldHandlers(); bool LoadGameData(); - void SaveGameState(); void SaveChunkData(); - void CleanupOldData(); + + nlohmann::json PlayerUpdateToJson(uint64_t playerId, const glm::vec3& pos, float yaw, + float health, float maxHealth, const std::string& name); + nlohmann::json PlayerPositionToJson(const std::vector& data); + nlohmann::json PlayerUpdateToJson(const std::vector& data); + nlohmann::json EntitySpawnToJson(const std::vector& data); + nlohmann::json EntityUpdateToJson(const std::vector& data); + nlohmann::json EntityDespawnToJson(const std::vector& data); + nlohmann::json ChunkDataToJson(const std::vector& data); }; diff --git a/include/game/LogicCore.hpp b/include/game/LogicCore.hpp index fae0048..9bb8b18 100644 --- a/include/game/LogicCore.hpp +++ b/include/game/LogicCore.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -15,34 +14,24 @@ #include #include +#include -#include "network/ConnectionManager.hpp" -#include "network/BinaryProtocol.hpp" -//#include "config/ConfigManager.hpp" -//#include "logging/Logger.hpp" #include "database/DbManager.hpp" - #include "game/RAIIThread.hpp" #include "game/LogicWorld.hpp" #include "game/LogicEntity.hpp" -//class PlayerManager; -#include "game/PlayerManager.hpp" - -//class PythonScripting; -//class ScriptHotReloader; -#include "scripting/PythonScripting.hpp" +// No includes for PlayerManager or PythonScripting class LogicCore { public: LogicCore(); - ~LogicCore(); + virtual ~LogicCore(); using MessageHandler = std::function; using BinaryMessageHandler = std::function&)>; using EventCallback = std::function; - // Rate limiting structure struct RateLimitInfo { std::chrono::steady_clock::time_point lastMessageTime; int messageCount = 0; @@ -51,49 +40,39 @@ class LogicCore { static LogicCore& GetInstance(); - // Core lifecycle - void Initialize(); - void Shutdown(); - bool IsRunning() const { return running_; } + virtual void Initialize(); + virtual void Shutdown(); + bool IsRunning() const; - // Message handling void HandleMessage(uint64_t sessionId, const nlohmann::json& msg); void HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, const std::vector& data); void RegisterHandler(const std::string& messageType, MessageHandler handler); - void RegisterBinaryHandler(uint16_t messageType, BinaryMessageHandler handler); void RegisterDefaultHandlers(); - // Session management - void OnPlayerConnected(uint64_t sessionId, uint64_t playerId); - void OnPlayerDisconnected(uint64_t sessionId); + virtual void OnPlayerConnected(uint64_t sessionId, uint64_t playerId); + virtual void OnPlayerDisconnected(uint64_t sessionId); uint64_t GetPlayerIdBySession(uint64_t sessionId) const; uint64_t GetSessionIdByPlayer(uint64_t playerId) const; - // Response methods void SendError(uint64_t sessionId, const std::string& message, int code = 0); void SendSuccess(uint64_t sessionId, const std::string& message, const nlohmann::json& data = {}); void SendToSession(uint64_t sessionId, const nlohmann::json& message); void SendBinaryToSession(uint64_t sessionId, uint16_t messageType, const std::vector& data); - // Python scripting - void FirePythonEvent(const std::string& eventName, const nlohmann::json& data); - nlohmann::json CallPythonFunction(const std::string& moduleName, const std::string& functionName, - const nlohmann::json& args); - void RegisterPythonEventHandlers(); + // Python scripting – virtual, to be overridden by GameLogic + virtual void FirePythonEvent(const std::string& eventName, const nlohmann::json& data); + virtual nlohmann::json CallPythonFunction(const std::string& moduleName, const std::string& functionName, + const nlohmann::json& args); + virtual void RegisterPythonEventHandlers(); - // Event queue void QueueEvent(EventCallback event); void ProcessEvents(); - // Utility int64_t GetCurrentTimestamp(); bool CheckRateLimit(uint64_t sessionId); - float CalculateDistance(const glm::vec3& a, const glm::vec3& b) { - return glm::distance(a, b); - } + float CalculateDistance(const glm::vec3& a, const glm::vec3& b); protected: - // Threading RAIIThread gameLoopThread_; RAIIThread spawnerThread_; @@ -111,54 +90,43 @@ class LogicCore { std::mutex spawnerMutex_; std::mutex saveMutex_; - // Message handling std::unordered_map messageHandlers_; std::unordered_map binaryHandlers_; std::mutex handlersMutex_; - // Rate limiting std::unordered_map rateLimits_; std::mutex rateLimitMutex_; const int MAX_MESSAGES_PER_SECOND = 100; - // Session management std::unordered_map sessionToPlayerMap_; std::unordered_map playerToSessionMap_; mutable std::mutex sessionMutex_; - // Event queue std::queue eventQueue_; std::mutex eventQueueMutex_; - // References to other systems - PlayerManager& playerManager_; + // Dependencies removed: PlayerManager&, PythonScripting& DbManager& dbManager_; + bool pythonEnabled_ = false; // keep flag, but no direct reference to PythonScripting - PythonScripting& pythonScripting_; - std::unique_ptr scriptHotReloader_; - bool pythonEnabled_; - - // Random generator std::mt19937 rng_; - // Thread functions + // Virtual methods – to be overridden by GameLogic virtual void GameLoop(); virtual void SpawnerLoop(); virtual void SaveLoop(); - // method declarations - void ProcessGameTick(float deltaTime); - void SpawnEnemies(); - void RespawnNPCs(); - void SpawnResources(); - void SaveGameState(); - void CleanupOldData(); + virtual void ProcessGameTick(float deltaTime); + virtual void SpawnEnemies(); + virtual void RespawnNPCs(); + virtual void SpawnResources(); + virtual void SaveGameState(); + virtual void CleanupOldData(); - // handler declarations - void HandleLogin(uint64_t sessionId, const nlohmann::json& data); - void HandleChat(uint64_t sessionId, const nlohmann::json& data); - void HandleCombat(uint64_t sessionId, const nlohmann::json& data); - void HandleQuest(uint64_t sessionId, const nlohmann::json& data); + virtual void HandleLogin(uint64_t sessionId, const nlohmann::json& data); + virtual void HandleChat(uint64_t sessionId, const nlohmann::json& data); + virtual void HandleCombat(uint64_t sessionId, const nlohmann::json& data); + virtual void HandleQuest(uint64_t sessionId, const nlohmann::json& data); private: static std::mutex instanceMutex_; diff --git a/include/game/LogicEntity.hpp b/include/game/LogicEntity.hpp index 9fa1b79..86444d3 100644 --- a/include/game/LogicEntity.hpp +++ b/include/game/LogicEntity.hpp @@ -42,7 +42,7 @@ class LogicEntity { void CreateLootEntity(const glm::vec3& position, std::shared_ptr item, int quantity); // Statistics - int GetActiveNPCCount() const { return activeNPCCount_; } + int GetActiveNPCCount() const { return npcManager_->GetNPCs().size(); } private: LogicEntity(); @@ -52,9 +52,7 @@ class LogicEntity { static LogicEntity* instance_; std::unique_ptr npcManager_; - std::unordered_map> npcEntities_; std::mutex npcMutex_; - std::atomic activeNPCCount_{0}; MobSystem& mobSystem_; EntityManager& entityManager_; diff --git a/include/game/LogicWorld.hpp b/include/game/LogicWorld.hpp index 575e6d6..6ac71f5 100644 --- a/include/game/LogicWorld.hpp +++ b/include/game/LogicWorld.hpp @@ -23,7 +23,7 @@ class LogicWorld { float terrainScale = 100.0f; float maxTerrainHeight = 50.0f; float waterLevel = 10.0f; - float chunkUnloadDistance = 200.0f; + float unloadDistance = 200.0f; }; static LogicWorld& GetInstance(); diff --git a/include/game/LootTableManager.hpp b/include/game/LootTableManager.hpp index 76aecf5..225ec7b 100644 --- a/include/game/LootTableManager.hpp +++ b/include/game/LootTableManager.hpp @@ -64,8 +64,8 @@ class LootTableManager { int GenerateGold(const LootTable& table, float luckMultiplier = 1.0f) const; // Serialization - bool LoadLootTables(const std::string& filePath); - bool SaveLootTables(const std::string& filePath) const; + bool LoadLootTables(const std::string& filePath=""); + bool SaveLootTables(const std::string& filePath="") const; bool LoadLootTablesFromJson(const nlohmann::json& jsonData); nlohmann::json SerializeAllTables() const; @@ -107,6 +107,8 @@ class LootTableManager { void GenerateRandomStats(std::shared_ptr item, int itemLevel) const; void ApplyRandomEnchantment(std::shared_ptr item, LootRarity rarity) const; + std::string file_path_ ="config/loot_tables.json"; + // Random number generation float GetRandomFloat(float min = 0.0f, float max = 1.0f) const; int GetRandomInt(int min, int max) const; diff --git a/include/game/NPCSystem.hpp b/include/game/NPCSystem.hpp index 1e00657..75758a9 100644 --- a/include/game/NPCSystem.hpp +++ b/include/game/NPCSystem.hpp @@ -16,6 +16,11 @@ class NPCManager { public: NPCManager(); + // Accessors + const std::unordered_map>& GetNPCs() const { return npcs_; } + const std::unordered_map>& GetAllNPCs() const { return npcs_; } + size_t GetNPCCount() const { return npcs_.size(); } + uint64_t SpawnNPC(NPCType type, const glm::vec3& position, uint64_t ownerId = 0); void DespawnNPC(uint64_t npcId); NPCEntity* GetNPC(uint64_t npcId); @@ -32,7 +37,7 @@ class NPCManager { private: std::unordered_map> npcs_; - std::unordered_map> squads_; // squadId -> npcIds + std::unordered_map> squads_; uint64_t nextNPCId_ = 1000; uint64_t nextSquadId_ = 1; @@ -41,4 +46,4 @@ class NPCManager { void ProcessNPCAI(NPCEntity* npc, float deltaTime); void HandleCombat(NPCEntity* npc, float deltaTime); void HandleMovement(NPCEntity* npc, float deltaTime); -}; +}; \ No newline at end of file diff --git a/include/game/Player.hpp b/include/game/Player.hpp index 3864172..98dac84 100644 --- a/include/game/Player.hpp +++ b/include/game/Player.hpp @@ -157,8 +157,10 @@ class Player : public GameEntity { Player(const glm::vec3& position, PlayerClass player_class, PlayerRace race); virtual ~Player(); - uint64_t GetId() const { return id_; } - const std::string& GetUsername() const { return username_; } + uint64_t GetId() const; + const std::string& GetUsername() const; + void SetPlayerClass(PlayerClass player_class); + PlayerClass GetPlayerClass() const; void UpdatePosition(float x, float y, float z); void UpdateHeartbeat(); @@ -166,39 +168,35 @@ class Player : public GameEntity { void ApplyDamage(int damage, uint64_t attackerId); void ApplyHealing(int amount, uint64_t healerId); - // Player-specific properties - void SetPlayerClass(PlayerClass player_class) { player_class_ = player_class; } - PlayerClass GetPlayerClass() const { return player_class_; } + void SetPlayerRace(PlayerRace race); + PlayerRace GetPlayerRace() const; - void SetPlayerRace(PlayerRace race) { race_ = race; } - PlayerRace GetPlayerRace() const { return race_; } - - void SetStatus(PlayerStatus status) { status_ = status; } - PlayerStatus GetStatus() const { return status_; } + void SetStatus(PlayerStatus status); + PlayerStatus GetStatus() const; // Player stats management void SetHealth(int health); void SetMaxHealth(int max_health); - int GetHealth() const { return stats_.health; } - int GetMaxHealth() const { return stats_.max_health; } + int GetHealth() const; + int GetMaxHealth() const; void SetMana(int mana); void SetMaxMana(int max_mana); - int GetMana() const { return stats_.mana; } - int GetMaxMana() const { return stats_.max_mana; } - float GetAttackDamage() const { return stats_.attack_damage; } - float GetAttackRange() const { return stats_.attack_range; } + int GetMana() const; + int GetMaxMana() const; + float GetAttackDamage() const; + float GetAttackRange() const; void SetLevel(int level); - int GetLevel() const { return stats_.level; } + int GetLevel() const; void AddExperience(int64_t amount); void LoseExperience(int64_t amount); - int64_t GetExperience() const { return stats_.experience; } - int64_t GetExperienceToNextLevel() const { return stats_.experience_to_next_level; } + int64_t GetExperience() const; + int64_t GetExperienceToNextLevel() const; // Attributes management - const PlayerAttributes& GetAttributes() const { return attributes_; } + const PlayerAttributes& GetAttributes() const; void SetAttribute(const std::string& attribute_name, float value); float GetAttribute(const std::string& attribute_name) const; void UpdateDerivedStats(); @@ -207,7 +205,7 @@ class Player : public GameEntity { bool EquipItem(const std::string& item_id, const std::string& slot); bool UnequipItem(const std::string& slot); std::string GetEquippedItem(const std::string& slot) const; - const PlayerEquipment& GetEquipment() const { return equipment_; } + const PlayerEquipment& GetEquipment() const; // Inventory management void AddItem(const std::string& item_id, int count = 1); @@ -218,11 +216,11 @@ class Player : public GameEntity { // Currency management void AddGold(int64_t amount); void RemoveGold(int64_t amount); - int64_t GetGold() const { return stats_.currency_gold; } + int64_t GetGold() const; void AddGems(int64_t amount); void RemoveGems(int64_t amount); - int64_t GetGems() const { return stats_.currency_gems; } + int64_t GetGems() const; // Skills and abilities void LearnSkill(const std::string& skill_id, int level = 1); @@ -275,32 +273,32 @@ class Player : public GameEntity { void UnblockPlayer(uint64_t player_id); // Player systems access - InventorySystem& GetInventorySystem() const { return inventory_system_; } - SkillSystem& GetSkillSystem() const { return skill_system_; } - QuestManager& GetQuestManager() const { return quest_manager_; } + InventorySystem& GetInventorySystem() const; + SkillSystem& GetSkillSystem() const; + QuestManager& GetQuestManager() const; // Utility methods - bool IsAlive() const { return stats_.health > 0; } - bool IsDead() const { return stats_.health <= 0; } - bool IsInCombat() const { return status_ == PlayerStatus::COMBAT; } - bool IsCasting() const { return status_ == PlayerStatus::CASTING; } - bool IsMoving() const { return status_ == PlayerStatus::MOVING; } + bool IsAlive() const; + bool IsDead() const; + bool IsInCombat() const; + bool IsCasting() const; + bool IsMoving() const; std::string GetClassString(PlayerClass player_class) const; std::string GetRaceString(PlayerRace race) const; - float GetAttackPower() const { return attributes_.attack_power; } - float GetDefense() const { return attributes_.defense; } - float GetCriticalChance() const { return attributes_.critical_chance; } - float GetMoveSpeed() const { return attributes_.move_speed; } + float GetAttackPower() const; + float GetDefense() const; + float GetCriticalChance() const; + float GetMoveSpeed() const; // Player achievements void AddAchievement(const std::string& achievement_id); bool HasAchievement(const std::string& achievement_id) const; - int GetAchievementCount() const { return achievements_.size(); } + int GetAchievementCount() const; // Player titles and cosmetics void SetTitle(const std::string& title); - std::string GetTitle() const { return title_; } + const std::string& GetTitle() const; void SetCosmetic(const std::string& slot, const std::string& cosmetic_id); std::string GetCosmetic(const std::string& slot) const; @@ -310,18 +308,18 @@ class Player : public GameEntity { void SetOnline(bool online); // Player session - void SetSessionId(uint64_t session_id) { session_id_ = session_id; } - uint64_t GetSessionId() const { return session_id_; } - void SetConnectionQuality(float quality) { connection_quality_ = quality; } - float GetConnectionQuality() const { return connection_quality_; } - bool IsOnline() const { return online_; } - - void SetBanned(bool banned) { banned_ = banned; } - bool IsBanned() const { return banned_; } - void SetBanReason(const std::string& reason) { ban_reason_ = reason; } - const std::string& GetBanReason() const { return ban_reason_; } - void SetBanExpires(std::chrono::system_clock::time_point expires) { ban_expires_ = expires; }; - std::chrono::system_clock::time_point GetBanExpires() const { return ban_expires_; } + void SetSessionId(uint64_t session_id); + uint64_t GetSessionId() const; + void SetConnectionQuality(float quality); + float GetConnectionQuality() const; + bool IsOnline() const; + + void SetBanned(bool banned); + bool IsBanned() const; + void SetBanReason(const std::string& reason); + const std::string& GetBanReason() const; + void SetBanExpires(std::chrono::system_clock::time_point expires); + std::chrono::system_clock::time_point GetBanExpires() const; // Serialization virtual nlohmann::json Serialize() const override; @@ -330,8 +328,8 @@ class Player : public GameEntity { nlohmann::json ToJson() const; // =============== Movement state getters/setters =============== - bool IsOnGround() const { return onGround_; } - void SetOnGround(bool g) { onGround_ = g; } + bool IsOnGround() const; + void SetOnGround(bool g); private: uint64_t id_; diff --git a/include/game/PlayerManager.hpp b/include/game/PlayerManager.hpp index cd397ee..09ee620 100644 --- a/include/game/PlayerManager.hpp +++ b/include/game/PlayerManager.hpp @@ -18,7 +18,7 @@ #include #include "logging/Logger.hpp" #include "network/ConnectionManager.hpp" -#include "network/GameSession.hpp" +#include "network/BinarySession.hpp" #include "utils/Passwords.hpp" #include "database/DbManager.hpp" diff --git a/include/logging/Logger.hpp b/include/logging/Logger.hpp index 45d4e75..04e38e0 100644 --- a/include/logging/Logger.hpp +++ b/include/logging/Logger.hpp @@ -50,6 +50,8 @@ class Logger { GetLogger()->critical(fmt, args...); } + static void Flush(); + private: static std::shared_ptr logger_; diff --git a/include/network/GameSession.hpp b/include/network/BinarySession.hpp similarity index 97% rename from include/network/GameSession.hpp rename to include/network/BinarySession.hpp index 5a6e600..9d8336a 100644 --- a/include/network/GameSession.hpp +++ b/include/network/BinarySession.hpp @@ -21,11 +21,15 @@ #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" + #include "network/BinaryProtocol.hpp" #include "network/NetworkQualityMonitor.hpp" #include "network/PredictionSystem.hpp" #include "network/IConnection.hpp" +#include "game/GameData.hpp" +#include "game/GameLogic.hpp" + struct SessionStats { // Message statistics uint64_t messages_received{0}; @@ -153,14 +157,16 @@ struct RateLimitConfig { bool exempt_authenticated_users{false}; // Whether authenticated users are exempt }; -class GameSession : public IConnection, public std::enable_shared_from_this { +class BinarySession : public IConnection, public std::enable_shared_from_this { public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; // Constructor with SSL context option - explicit GameSession(asio::ip::tcp::socket socket, + explicit BinarySession(asio::ip::tcp::socket socket, std::shared_ptr ssl_context = nullptr); - ~GameSession(); + ~BinarySession(); + + ProtocolMode GetProtocolMode() const override { return ProtocolMode::Binary; } // Core session management void Start() override; diff --git a/include/network/GameServer.hpp b/include/network/GameServer.hpp index cc00d90..7ffa440 100644 --- a/include/network/GameServer.hpp +++ b/include/network/GameServer.hpp @@ -12,11 +12,15 @@ #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" +#include "process/ProcessPool.hpp" + #include "network/ConnectionManager.hpp" -#include "network/GameSession.hpp" +#include "network/BinarySession.hpp" #include "network/WebSocketProtocol.hpp" #include "network/WebSocketSession.hpp" +#include "game/GameData.hpp" + #include "game/GameLogic.hpp" class GameServer { @@ -30,16 +34,10 @@ class GameServer { asio::io_context& GetIoContext() { return ioContext_; } - using SessionFactory = std::function(asio::ip::tcp::socket, std::shared_ptr)>; - void SetSessionFactory(SessionFactory factory); - - using WebSocketFactory = std::function)>; - void SetWebSocketConnectionFactory(WebSocketFactory factory); + void InitSessionFactory(int workerId, ProcessPool* processPool, GameLogic& game_logic); + void RegisterCallbacks(const std::string& protocol, GameLogic& game_logic); private: - void DoAccept(); - void StartWorkerThreads(); - asio::io_context ioContext_; asio::ip::tcp::acceptor acceptor_; @@ -56,6 +54,13 @@ class GameServer { std::shared_ptr sslContext_; - SessionFactory sessionFactory_; - WebSocketFactory webSocketFactory_; + std::function(asio::ip::tcp::socket, std::shared_ptr)> sessionFactory_; + std::function(asio::ip::tcp::socket, std::shared_ptr)> webSocketFactory_; + + std::optional> work_guard_; + + void DoAccept(); + void StartWorkerThreads(); + std::vector> GetSessionsInRadius(const glm::vec3& position, float radius); + }; diff --git a/include/network/IConnection.hpp b/include/network/IConnection.hpp index e948623..88277af 100644 --- a/include/network/IConnection.hpp +++ b/include/network/IConnection.hpp @@ -6,13 +6,26 @@ #include #include #include +#include #include +enum class ProtocolMode { Binary, Json, Unknown }; + +static const std::unordered_map IPCMessageTypes = { + {"welcome", 1}, + {"heartbeat", 2}, + {"broadcast", 3}, + {"shutdown", 4}, + {"reload_config", 5} +}; + class IConnection { public: virtual ~IConnection() = default; + virtual ProtocolMode GetProtocolMode() const = 0; + // Core methods virtual void Start() = 0; virtual void Stop() = 0; diff --git a/include/network/PredictionSystem.hpp b/include/network/PredictionSystem.hpp index d41b862..c9dc878 100644 --- a/include/network/PredictionSystem.hpp +++ b/include/network/PredictionSystem.hpp @@ -9,8 +9,11 @@ struct ClientInput { uint32_t input_id; uint64_t timestamp; - glm::vec3 movement; + glm::vec3 position; + glm::vec3 velocity; glm::vec3 rotation; + glm::vec3 movement; + bool on_ground{true}; bool jumping{false}; bool crouching{false}; bool sprinting{false}; @@ -39,52 +42,44 @@ struct ServerState { static ServerState Interpolate(const ServerState& a, const ServerState& b, float t); }; +struct PredictionStats { + uint32_t total_predictions{0}; + uint32_t corrections_sent{0}; + uint32_t corrections_received{0}; + float average_correction_distance{0.0f}; + uint64_t last_correction_time{0}; + void Reset(); + std::string ToString() const; +}; + class PredictionSystem { public: + struct ReconciliationResult { + bool needs_correction{false}; + ServerState corrected_state; + std::vector processed_inputs; + }; + PredictionSystem(); - // Client-side methods void StoreClientInput(const ClientInput& input); ServerState PredictPosition(uint64_t current_time) const; - // Server-side methods void StoreServerState(const ServerState& state); std::vector GetUnprocessedInputs(uint32_t last_processed) const; - // Reconciliation - struct ReconciliationResult { - bool needs_correction{false}; - ServerState corrected_state; - std::vector processed_inputs; - }; - ReconciliationResult ReconcileWithServer(const ServerState& server_state); - // State management - ServerState GetLastConfirmedState() const { return last_confirmed_state_; } - ServerState GetLatestPredictedState() const { return latest_predicted_state_; } - - // Input history - const std::deque& GetInputHistory() const { return input_history_; } + ServerState GetLastConfirmedState() const; - // Clear history - void Clear(); + ServerState GetLatestPredictedState() const; - // Statistics - struct PredictionStats { - uint32_t total_predictions{0}; - uint32_t corrections_sent{0}; - uint32_t corrections_received{0}; - float average_correction_distance{0.0f}; - uint64_t last_correction_time{0}; + const std::deque& GetInputHistory() const; - void Reset(); - std::string ToString() const; - }; + const PredictionStats& GetStats() const; - const PredictionStats& GetStats() const { return stats_; } + void Clear(); - // Helper methods ServerState SimulateMovement(const ServerState& start_state, const std::vector& inputs, float delta_time) const; @@ -94,22 +89,17 @@ class PredictionSystem { private: mutable std::mutex mutex_; - // Input history (client-side) std::deque input_history_; static constexpr size_t MAX_INPUT_HISTORY = 1000; - // Server state history std::deque server_state_history_; static constexpr size_t MAX_STATE_HISTORY = 100; - // Current states ServerState last_confirmed_state_; mutable ServerState latest_predicted_state_; - // Statistics PredictionStats stats_; - // Physics constants static constexpr float GRAVITY = -9.81f; static constexpr float MAX_SPEED = 10.0f; static constexpr float ACCELERATION = 20.0f; @@ -117,17 +107,15 @@ class PredictionSystem { static constexpr float JUMP_FORCE = 5.0f; }; -// Input buffer for handling out-of-order inputs class InputBuffer { public: InputBuffer(size_t max_size = 1000); - void AddInput(const ClientInput& input); std::vector GetOrderedInputs() const; ClientInput GetNextInput() const; - bool HasInputs() const { return !inputs_.empty(); } void Clear(); - size_t Size() const { return inputs_.size(); } + bool HasInputs() const; + size_t Size() const; private: mutable std::mutex mutex_; diff --git a/include/network/WebSocketProtocol.hpp b/include/network/WebSocketProtocol.hpp index 25ab1ab..ea28387 100644 --- a/include/network/WebSocketProtocol.hpp +++ b/include/network/WebSocketProtocol.hpp @@ -54,21 +54,21 @@ namespace WebSocketProtocol { uint8_t masking_key[4]{0, 0, 0, 0}; uint64_t payload_length{0}; std::vector payload_data; - + // Serialization std::vector Serialize() const; static WebSocketFrame Deserialize(const uint8_t* data, size_t length); static WebSocketFrame Deserialize(const std::vector& data); - + // Frame type helpers bool IsControlFrame() const { return opcode == OP_CLOSE || opcode == OP_PING || opcode == OP_PONG; } - + bool IsDataFrame() const { return opcode == OP_TEXT || opcode == OP_BINARY || opcode == OP_CONTINUATION; } - + // Create common frame types static WebSocketFrame CreateTextFrame(const std::string& text); static WebSocketFrame CreateBinaryFrame(const std::vector& data); @@ -82,18 +82,18 @@ namespace WebSocketProtocol { Opcode opcode{OP_BINARY}; std::vector data; bool complete{false}; - + // For text messages std::string GetText() const { return std::string(data.begin(), data.end()); } - + // Set text data void SetText(const std::string& text) { data.assign(text.begin(), text.end()); opcode = OP_TEXT; } - + // Convert to JSON nlohmann::json ToJson() const { if (opcode == OP_TEXT) { @@ -101,7 +101,7 @@ namespace WebSocketProtocol { } throw std::runtime_error("Cannot convert binary data to JSON"); } - + // Create from JSON static WebSocketMessage FromJson(const nlohmann::json& json) { WebSocketMessage msg; @@ -116,14 +116,14 @@ namespace WebSocketProtocol { std::string path{"/"}; std::string http_version{"HTTP/1.1"}; std::unordered_map headers; - + std::string Serialize() const; static HandshakeRequest Parse(const std::string& request); - + // Common headers std::string GetHeader(const std::string& name) const; void SetHeader(const std::string& name, const std::string& value); - + // WebSocket specific std::string GetKey() const { return GetHeader("Sec-WebSocket-Key"); } std::string GetVersion() const { return GetHeader("Sec-WebSocket-Version"); } @@ -155,41 +155,41 @@ namespace WebSocketProtocol { class WebSocketConnection : public std::enable_shared_from_this { public: using Pointer = std::shared_ptr; - + // Event callbacks using MessageHandler = std::function; using TextHandler = std::function; using BinaryHandler = std::function&)>; using CloseHandler = std::function; using ErrorHandler = std::function; - + WebSocketConnection(asio::ip::tcp::socket socket); virtual ~WebSocketConnection(); - + // Connection management virtual void Start(); virtual void Close(uint16_t code = 1000, const std::string& reason = ""); bool IsOpen() const { return state_ == State::OPEN; } bool IsClosing() const { return state_ == State::CLOSING; } - + // Message sending void SendText(const std::string& text); void SendBinary(const std::vector& data); void SendJson(const nlohmann::json& json); void SendPing(const std::vector& data = {}); void SendPong(const std::vector& data = {}); - + // Event handlers void SetMessageHandler(MessageHandler handler) { message_handler_ = std::move(handler); } void SetTextHandler(TextHandler handler) { text_handler_ = std::move(handler); } void SetBinaryHandler(BinaryHandler handler) { binary_handler_ = std::move(handler); } void SetCloseHandler(CloseHandler handler) { close_handler_ = std::move(handler); } void SetErrorHandler(ErrorHandler handler) { error_handler_ = std::move(handler); } - + // Connection info asio::ip::tcp::endpoint GetRemoteEndpoint() const; uint64_t GetConnectionId() const { return connection_id_; } - + // Statistics struct Statistics { uint64_t messages_sent{0}; @@ -199,7 +199,7 @@ namespace WebSocketProtocol { uint64_t ping_count{0}; uint64_t pong_count{0}; }; - + Statistics GetStatistics() const; void ReadFramePayload(bool fin, uint8_t opcode, bool masked, uint64_t payload_length, size_t header_size); @@ -212,50 +212,50 @@ namespace WebSocketProtocol { CLOSING, CLOSED }; - + asio::ip::tcp::socket socket_; State state_{State::HANDSHAKE}; uint64_t connection_id_; static std::atomic next_connection_id_; - + // Buffers asio::streambuf read_buffer_; std::vector write_buffer_; - std::mutex write_mutex_; - + std::recursive_mutex write_mutex_; + // Message assembly WebSocketMessage current_message_; - + // Event handlers MessageHandler message_handler_; TextHandler text_handler_; BinaryHandler binary_handler_; CloseHandler close_handler_; ErrorHandler error_handler_; - + // Statistics mutable std::mutex stats_mutex_; Statistics stats_; - + // Handshake virtual void HandleHandshake(); void ReadHandshake(); void WriteHandshakeResponse(const HandshakeResponse& response); - + // Frame handling void ReadFrame(); void HandleFrame(const WebSocketFrame& frame); void SendFrame(const WebSocketFrame& frame); void SendFrameAsync(const WebSocketFrame& frame); - + // Message assembly void ProcessMessageData(const WebSocketFrame& frame); void CompleteCurrentMessage(); - + // Error handling void HandleError(const std::error_code& ec); void HandleClose(uint16_t code, const std::string& reason); - + private: void DoWrite(); }; @@ -264,35 +264,35 @@ namespace WebSocketProtocol { class WebSocketServer { public: using ConnectionFactory = std::function; - + WebSocketServer(asio::io_context& io_context, uint16_t port); ~WebSocketServer(); - + void Start(); void Stop(); - + void SetConnectionFactory(ConnectionFactory factory) { connection_factory_ = std::move(factory); } - + // Broadcast void BroadcastText(const std::string& text); void BroadcastBinary(const std::vector& data); void BroadcastJson(const nlohmann::json& json); - + // Connection management std::vector GetConnections() const; size_t GetConnectionCount() const; - + private: asio::io_context& io_context_; asio::ip::tcp::acceptor acceptor_; uint16_t port_; - + ConnectionFactory connection_factory_; std::vector connections_; mutable std::mutex connections_mutex_; - + std::atomic running_{false}; - + void DoAccept(); void AddConnection(WebSocketConnection::Pointer connection); void RemoveConnection(WebSocketConnection::Pointer connection); @@ -302,27 +302,27 @@ namespace WebSocketProtocol { class WebSocketClient : public WebSocketConnection { public: WebSocketClient(asio::io_context& io_context); - + void Connect(const std::string& host, uint16_t port, const std::string& path = "/"); void Connect(const std::string& url); // Supports ws:// and wss:// - + // SSL/TLS support void UseSSL(bool enable = true); bool IsSecure() const { return ssl_context_ != nullptr; } - + private: asio::io_context& io_context_; std::shared_ptr ssl_context_; std::unique_ptr> ssl_stream_; - + std::string host_; uint16_t port_; std::string path_; - + void ResolveAndConnect(); void HandleResolve(const std::error_code& ec, asio::ip::tcp::resolver::results_type endpoints); void HandleConnect(const std::error_code& ec, const asio::ip::tcp::endpoint& endpoint); - + // Override handshake for client void HandleHandshake() override; void SendHandshakeRequest(); @@ -332,22 +332,22 @@ namespace WebSocketProtocol { std::string GenerateWebSocketKey(); std::string GenerateAcceptKey(const std::string& key); uint16_t GenerateMaskingKey(uint8_t key[4]); - + bool IsValidOpcode(uint8_t opcode); bool IsControlOpcode(uint8_t opcode); - + // Frame parsing utilities size_t GetFrameHeaderSize(const WebSocketFrame& frame); size_t GetFrameSize(const WebSocketFrame& frame); - + // Masking utilities void ApplyMask(uint8_t* data, size_t length, const uint8_t masking_key[4]); void ApplyMask(std::vector& data, const uint8_t masking_key[4]); - + // Close code utilities bool IsValidCloseCode(uint16_t code); std::string GetCloseReason(uint16_t code); - + // URL parsing struct WebSocketURL { std::string protocol; // "ws" or "wss" @@ -355,7 +355,7 @@ namespace WebSocketProtocol { uint16_t port{80}; std::string path{"/"}; std::string query; - + static WebSocketURL Parse(const std::string& url); std::string ToString() const; }; @@ -365,19 +365,19 @@ namespace WebSocketProtocol { public: CompressionContext(); ~CompressionContext(); - + bool Initialize(bool server, int compression_level = 6); std::vector Compress(const std::vector& data); std::vector Decompress(const std::vector& compressed_data); - + bool IsInitialized() const { return initialized_; } - + private: void* deflate_context_{nullptr}; void* inflate_context_{nullptr}; bool initialized_{false}; bool server_{false}; - + void Cleanup(); }; @@ -385,14 +385,14 @@ namespace WebSocketProtocol { class MessageFragmenter { public: MessageFragmenter(size_t max_frame_size = 16384); // 16KB default - + std::vector FragmentMessage(const WebSocketMessage& message); std::vector FragmentText(const std::string& text); std::vector FragmentBinary(const std::vector& data); - + size_t GetMaxFrameSize() const { return max_frame_size_; } void SetMaxFrameSize(size_t size) { max_frame_size_ = size; } - + private: size_t max_frame_size_; }; @@ -401,14 +401,14 @@ namespace WebSocketProtocol { class WebSocketRateLimiter { public: WebSocketRateLimiter(size_t messages_per_second = 100, size_t burst_size = 1000); - + bool CheckLimit(); void Update(); - + void SetLimit(size_t messages_per_second, size_t burst_size); size_t GetMessagesPerSecond() const { return messages_per_second_; } size_t GetBurstSize() const { return burst_size_; } - + private: size_t messages_per_second_; size_t burst_size_; diff --git a/include/network/WebSocketSession.hpp b/include/network/WebSocketSession.hpp index 9beb56b..039b3ed 100644 --- a/include/network/WebSocketSession.hpp +++ b/include/network/WebSocketSession.hpp @@ -10,11 +10,17 @@ #include "network/BinaryProtocol.hpp" #include "network/WebSocketProtocol.hpp" +#include "game/GameData.hpp" +#include "game/GameLogic.hpp" + class WebSocketSession : public IConnection, public std::enable_shared_from_this { public: WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn); ~WebSocketSession(); + void SetProtocolMode(ProtocolMode mode) { protocolMode_ = mode; } + ProtocolMode GetProtocolMode() const override { return protocolMode_; } + // IConnection implementation void Start() override; void Stop() override; @@ -61,6 +67,8 @@ class WebSocketSession : public IConnection, public std::enable_shared_from_this void SetDefaultBinaryMessageHandler(BinaryMessageHandler handler); private: + ProtocolMode protocolMode_; + WebSocketProtocol::WebSocketConnection::Pointer wsConn_; uint64_t sessionId_; static std::atomic nextSessionId_; diff --git a/include/scripting/PythonScripting.hpp b/include/scripting/PythonScripting.hpp index f10494b..105a3fc 100644 --- a/include/scripting/PythonScripting.hpp +++ b/include/scripting/PythonScripting.hpp @@ -17,10 +17,6 @@ #include "scripting/PythonAPI.hpp" #include "scripting/PythonModule.hpp" -// Forward declarations -//class Player; -//class GameSession; - // Python object wrapper for RAII class PyObjectRef { diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 4c307cb..b69d52d 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -245,7 +245,7 @@ std::string ConfigManager::GetDatabaseHost() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("host").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "127.0.0.1"; } } @@ -254,7 +254,7 @@ uint16_t ConfigManager::GetDatabasePort() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("port").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 5432; } } @@ -263,7 +263,7 @@ std::string ConfigManager::GetDatabaseName() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("name").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "game_db"; } } @@ -272,7 +272,7 @@ std::string ConfigManager::GetDatabaseUser() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("user").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "game_user"; } } @@ -281,7 +281,7 @@ std::string ConfigManager::GetDatabasePassword() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("password").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return ""; } } @@ -290,7 +290,7 @@ std::string ConfigManager::GetDatabaseBackend() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("backend").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "postgresql"; } } @@ -299,7 +299,7 @@ int ConfigManager::GetDatabasePoolSize() const { std::lock_guard lock(configMutex_); try { return config_.at("database").at("pool_size").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 10; } } @@ -308,23 +308,23 @@ std::vector ConfigManager::GetCitusWorkerNodes() const { std::lock_guard lock(configMutex_); std::vector nodes; try { - auto& db = config_.at("database"); - if (db.contains("citus_worker_nodes") && db["citus_worker_nodes"].is_array()) { - for (const auto& node : db["citus_worker_nodes"]) { + auto& citus = config_.at("database").at("citus"); + if (citus.contains("worker_nodes") && citus["worker_nodes"].is_array()) { + for (const auto& node : citus["worker_nodes"]) { if (node.is_string()) nodes.push_back(node.get()); } } - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what());} + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what());} return nodes; } -int ConfigManager::GetShardCount() const { +int ConfigManager::GetShardCount(int default_value) const { std::lock_guard lock(configMutex_); try { - return config_.at("database").at("shard_count").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); - return 32; + return config_.at("database").at("citus").at("shard_count").get(); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); + return default_value; } } @@ -335,7 +335,7 @@ int ConfigManager::GetMaxPlayersPerSession() const { std::lock_guard lock(configMutex_); try { return config_.at("game").at("max_players_per_session").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 100; } } @@ -344,7 +344,7 @@ int ConfigManager::GetHeartbeatInterval() const { std::lock_guard lock(configMutex_); try { return config_.at("game").at("heartbeat_interval_seconds").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 30; } } @@ -353,7 +353,7 @@ int ConfigManager::GetSessionTimeout() const { std::lock_guard lock(configMutex_); try { return config_.at("game").at("session_timeout_seconds").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 300; } } @@ -365,7 +365,7 @@ int ConfigManager::GetWorldSeed() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("seed").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 12345; } } @@ -374,7 +374,7 @@ int ConfigManager::GetViewDistance() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("view_distance").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 1000; } } @@ -383,7 +383,7 @@ int ConfigManager::GetChunkSize() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("chunk_size").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 32; } } @@ -392,7 +392,7 @@ int ConfigManager::GetMaxActiveChunks() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("max_active_chunks").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 1000; } } @@ -401,7 +401,7 @@ float ConfigManager::GetTerrainScale() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("terrain_scale").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 1.0f; } } @@ -410,7 +410,7 @@ float ConfigManager::GetMaxTerrainHeight() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("max_terrain_height").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 100.0f; } } @@ -419,7 +419,7 @@ float ConfigManager::GetWaterLevel() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("water_level").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 10.0f; } } @@ -428,7 +428,7 @@ bool ConfigManager::ShouldPreloadWorld() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("preload_world").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return false; } } @@ -437,7 +437,7 @@ int ConfigManager::GetWorldPreloadRadius() const { std::lock_guard lock(configMutex_); try { return config_.at("world").at("preload_radius").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 500; } } @@ -451,7 +451,7 @@ std::string ConfigManager::GetLogLevel() const { std::string level = config_.at("logging").at("level").get(); std::transform(level.begin(), level.end(), level.begin(), ::tolower); return level; - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "info"; } } @@ -460,7 +460,7 @@ std::string ConfigManager::GetLogFilePath() const { std::lock_guard lock(configMutex_); try { return config_.at("logging").at("file").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return "gameserver.log"; } } @@ -468,9 +468,9 @@ std::string ConfigManager::GetLogFilePath() const { int ConfigManager::GetMaxLogFileSize() const { std::lock_guard lock(configMutex_); try { - return config_.at("logging").at("max_file_size_mb").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); - return 100; + return config_.at("logging").at("max_file_size").get(); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); + return 1048576;//1Mb default } } @@ -478,7 +478,7 @@ int ConfigManager::GetMaxLogFiles() const { std::lock_guard lock(configMutex_); try { return config_.at("logging").at("max_files").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return 10; } } @@ -487,7 +487,7 @@ bool ConfigManager::GetConsoleOutput() const { std::lock_guard lock(configMutex_); try { return config_.at("logging").at("console_output").get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return true; } } @@ -501,7 +501,7 @@ int ConfigManager::GetInt(const std::string& key, int defaultValue) const { std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.at(nlohmann::json::json_pointer("/" + keyPath)).get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return defaultValue; } } @@ -512,7 +512,7 @@ float ConfigManager::GetFloat(const std::string& key, float defaultValue) const std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.at(nlohmann::json::json_pointer("/" + keyPath)).get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return defaultValue; } } @@ -523,7 +523,7 @@ bool ConfigManager::GetBool(const std::string& key, bool defaultValue) const { std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.at(nlohmann::json::json_pointer("/" + keyPath)).get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return defaultValue; } } @@ -534,7 +534,7 @@ std::string ConfigManager::GetString(const std::string& key, const std::string& std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.at(nlohmann::json::json_pointer("/" + keyPath)).get(); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return defaultValue; } } @@ -554,7 +554,7 @@ std::vector ConfigManager::GetStringArray(const std::string& key) c result.push_back(item.dump()); } } - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what());} + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what());} return result; } @@ -564,7 +564,7 @@ nlohmann::json ConfigManager::GetJson(const std::string& key) const { std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.at(nlohmann::json::json_pointer("/" + keyPath)); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return nlohmann::json(); } } @@ -575,7 +575,7 @@ bool ConfigManager::HasKey(const std::string& key) const { std::string keyPath = key; std::replace(keyPath.begin(), keyPath.end(), '.', '/'); return config_.contains(nlohmann::json::json_pointer("/" + keyPath)); - } catch (const std::exception& err) {Logger::Warn("failed: {}", err.what()); + } catch (const std::exception& err) {Logger::Warn("missed: {}", err.what()); return false; } } diff --git a/src/database/CitusClient.cpp b/src/database/CitusClient.cpp index 500115a..a05d6a5 100644 --- a/src/database/CitusClient.cpp +++ b/src/database/CitusClient.cpp @@ -788,4 +788,16 @@ bool CitusClient::ConnectToDatabase(const std::string& dbname) { return true; } +bool CitusClient::ExecuteDatabase(const std::string& sql) { return Execute(sql); } + +nlohmann::json CitusClient::QueryDatabase(const std::string& sql) { return Query(sql); } + +static CitusClient* CitusClient::GetInstancePtr() { + std::lock_guard lock(instanceMutex_); + return instance_; +} + +bool CitusClient::IsCitusEnabled() const { return citusEnabled_; } + + #endif // USE_CITUS diff --git a/src/database/DbManager.cpp b/src/database/DbManager.cpp index d1ec90e..34d8884 100644 --- a/src/database/DbManager.cpp +++ b/src/database/DbManager.cpp @@ -26,6 +26,10 @@ DbManager::~DbManager() { Logger::Debug("DbManager destroyed"); } +bool DbManager::IsInitialized() const { return initialized_; } + +const SQLProvider& DbManager::GetSQLProvider() const { return sqlProvider_; } + bool DbManager::LoadSQLForBackend() { std::string sqlPath = "dbschema/"; // configurable base path switch (currentType_) { @@ -265,8 +269,11 @@ bool DbManager::LoadConfiguration(const std::string& configPath) { {"min_connections", poolConfig.value("min", 5)}, {"max_connections", poolConfig.value("max", 20)} }}, - {"shards", config_.value("shards", 32)}, - {"replication", config_.value("replication", false)}, + {"citus", { + {"shard_count", config_.value("shard_count", 32)}, + {"replication_factor", config_.value("replication_factor", 2)}, + {"worker_nodes", config_.value("worker_nodes", "[]")}, + }}, {"ssl", config_.value("ssl", false)}, {"timeout", config_.value("timeout", 30)} }; @@ -274,8 +281,8 @@ bool DbManager::LoadConfiguration(const std::string& configPath) { Logger::Debug("Database configuration finalized"); return true; - } catch (const std::exception& e) { - Logger::Error("Failed to load database configuration: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("Failed to load database configuration: {}", err.what()); return false; } } @@ -733,7 +740,7 @@ bool DbManager::RollbackMigration(int version) { } } -DbManager::BackendType DbManager::ParseBackendType(const std::string& backendStr) const { +BackendType DbManager::ParseBackendType(const std::string& backendStr) const { std::string lowerType = backendStr; std::transform(lowerType.begin(), lowerType.end(), lowerType.begin(), ::tolower); @@ -872,3 +879,28 @@ bool DbManager::CheckDefaultTablesExist() { } return true; } + +DatabaseBackend* DbManager::GetBackend() const { return backend_.get(); } + +BackendType DbManager::GetCurrentType() const { return currentType_; } + +nlohmann::json DbManager::GetPlayer(uint64_t playerId){ return backend_->GetPlayer(playerId); } + +nlohmann::json DbManager::Query(const std::string& sql) { return backend_->Query(sql); } + +nlohmann::json DbManager::QueryWithParams(const std::string& sql, const std::vector& params) +{ return backend_->QueryWithParams(sql, params); } + +bool DbManager::Execute(const std::string& sql) { return backend_->Execute(sql); } + +bool DbManager::ExecuteWithParams(const std::string& sql, const std::vector& params) +{ return backend_->ExecuteWithParams(sql, params); } + +bool DbManager::UpdatePlayerPosition(uint64_t playerId, float x, float y, float z) { + if (backend_) { + return backend_->UpdatePlayerPosition(playerId, x, y, z); + } + return false; +} + +nlohmann::json DbManager::GetConfiguration() const { return config_; } diff --git a/src/database/DbService.cpp b/src/database/DbService.cpp new file mode 100644 index 0000000..2eb5d3c --- /dev/null +++ b/src/database/DbService.cpp @@ -0,0 +1,95 @@ +#include "database/DbService.hpp" + +DatabaseService::DatabaseService(asio::io_context& main_io, size_t num_threads) + : main_io_(main_io), db_pool_(num_threads) +{ + work_guard_ = std::make_unique>( + asio::make_work_guard(db_pool_)); + Logger::Info("DatabaseService started with {} threads", num_threads); +} + +DatabaseService::~DatabaseService() { + shutdown(); +} + +void DatabaseService::shutdown() { + work_guard_.reset(); // Allow pool to run out of work + db_pool_.join(); // Wait for all tasks to complete + Logger::Info("DatabaseService shut down"); +} + +void DatabaseService::asyncGetPlayer(uint64_t playerId, + std::function callback) { + asio::post(db_pool_, [this, playerId, cb = std::move(callback)]() { + auto result = DbManager::GetInstance().GetPlayer(playerId); + asio::post(main_io_, [cb = std::move(cb), res = std::move(result)]() { + cb(res); + }); + }); +} + +void DatabaseService::asyncSavePlayerData(uint64_t playerId, const nlohmann::json& data, + std::function callback) { + asio::post(db_pool_, [this, playerId, data, cb = std::move(callback)]() { + bool success = DbManager::GetInstance().GetBackend()->SavePlayerData(playerId, data); + asio::post(main_io_, [cb = std::move(cb), success]() { cb(success); }); + }); +} + +void DatabaseService::asyncPlayerExists(uint64_t playerId, + std::function callback) { + asio::post(db_pool_, [this, playerId, cb = std::move(callback)]() { + bool exists = DbManager::GetInstance().GetBackend()->PlayerExists(playerId); + asio::post(main_io_, [cb = std::move(cb), exists]() { cb(exists); }); + }); +} + +void DatabaseService::asyncCreatePlayer(const std::string& username, + std::function)> callback) { + asio::post(db_pool_, [this, username, cb = std::move(callback)]() { + auto& pm = PlayerManager::GetInstance(); + auto player = pm.CreatePlayer(username); + asio::post(main_io_, [cb = std::move(cb), player]() { cb(player); }); + }); +} + +void DatabaseService::asyncAuthenticatePlayer(const std::string& username, const std::string& password, + std::function callback) { + asio::post(db_pool_, [this, username, password, cb = std::move(callback)]() { + bool success = PlayerManager::GetInstance().AuthenticatePlayer(username, password); + asio::post(main_io_, [cb = std::move(cb), success]() { cb(success); }); + }); +} + +void DatabaseService::asyncGetPlayerByName(const std::string& username, + std::function callback) { + asio::post(db_pool_, [this, username, cb = std::move(callback)]() { + auto player = PlayerManager::GetInstance().GetPlayerByUsername(username); + nlohmann::json result = player ? player->Serialize() : nlohmann::json(); + asio::post(main_io_, [cb = std::move(cb), res = std::move(result)]() { cb(res); }); + }); +} + +void DatabaseService::asyncPlayerExists(const std::string& username, + std::function callback) { + asio::post(db_pool_, [this, username, cb = std::move(callback)]() { + bool exists = PlayerManager::GetInstance().PlayerExists(username); + asio::post(main_io_, [cb = std::move(cb), exists]() { cb(exists); }); + }); +} + +void DatabaseService::asyncSaveChunkData(int chunkX, int chunkZ, const nlohmann::json& data, + std::function callback) { + asio::post(db_pool_, [this, chunkX, chunkZ, data, cb = std::move(callback)]() { + bool success = DbManager::GetInstance().GetBackend()->SaveChunkData(chunkX, chunkZ, data); + asio::post(main_io_, [cb = std::move(cb), success]() { cb(success); }); + }); +} + +void DatabaseService::asyncLoadChunkData(int chunkX, int chunkZ, + std::function callback) { + asio::post(db_pool_, [this, chunkX, chunkZ, cb = std::move(callback)]() { + auto data = DbManager::GetInstance().GetBackend()->LoadChunkData(chunkX, chunkZ); + asio::post(main_io_, [cb = std::move(cb), data = std::move(data)]() { cb(data); }); + }); +} diff --git a/src/database/PostgreSqlClient.cpp b/src/database/PostgreSqlClient.cpp index 5aced80..d224b5f 100644 --- a/src/database/PostgreSqlClient.cpp +++ b/src/database/PostgreSqlClient.cpp @@ -1055,3 +1055,7 @@ bool PostgreSqlClient::ConnectToDatabase(const std::string& dbname) { Logger::Debug("Switched database connection to '{}'", dbname); return true; } + +bool PostgreSqlClient::ExecuteDatabase(const std::string& sql) { return Execute(sql); } + +nlohmann::json PostgreSqlClient::QueryDatabase(const std::string& sql) { return Query(sql); } diff --git a/src/database/SQLProvider.cpp b/src/database/SQLProvider.cpp new file mode 100644 index 0000000..b8d06e8 --- /dev/null +++ b/src/database/SQLProvider.cpp @@ -0,0 +1,46 @@ +#include "database/SQLProvider.hpp" + +bool SQLProvider::LoadFromFile(const std::string& filePath) { + std::ifstream file(filePath); + if (!file.is_open()) { + std::cerr << "Failed to open SQL file: " << filePath << std::endl; + return false; + } + + std::string line, currentKey, currentQuery; + while (std::getline(file, line)) { + // Check for section marker: -- [key] + if (line.size() > 5 && line[0] == '-' && line[1] == '-' && line[2] == ' ' && line[3] == '[') { + // Save previous query if any + if (!currentKey.empty() && !currentQuery.empty()) { + queries_[currentKey] = currentQuery; + } + // Extract key + size_t endBracket = line.find(']', 4); + if (endBracket != std::string::npos) { + currentKey = line.substr(4, endBracket - 4); + currentQuery.clear(); + } else { + currentKey.clear(); + } + } else if (!currentKey.empty()) { + // Append line to current query (preserve newlines) + currentQuery += line + "\n"; + } + } + // Save last query + if (!currentKey.empty() && !currentQuery.empty()) { + queries_[currentKey] = currentQuery; + } + + file.close(); + return true; +} + +std::string SQLProvider::GetQuery(const std::string& key) const { + auto it = queries_.find(key); + if (it == queries_.end()) { + return ""; + } + return it->second; +} diff --git a/src/database/SQLiteClient.cpp b/src/database/SQLiteClient.cpp index f458bea..6d3b70b 100644 --- a/src/database/SQLiteClient.cpp +++ b/src/database/SQLiteClient.cpp @@ -16,7 +16,7 @@ SQLiteClient::SQLiteClient(const nlohmann::json& config, const SQLProvider& sqlP } else { dbPath_ = "game.db"; } - int shards = config.value("shards", 1); + int shards = config.value("citus.shard_count", 1); totalShards_ = (shards > 0) ? shards : 1; stats_.startTime = std::chrono::steady_clock::now(); Logger::Debug("SQLiteClient created with database file: {}", dbPath_); @@ -135,6 +135,7 @@ size_t SQLiteClient::GetIdleConnections() const { bool SQLiteClient::ExecuteSql(const std::string& sql, std::vector>* results) { std::lock_guard lock(dbMutex_); + Logger::Trace("SQLite ExecuteSql start: {}", sql.substr(0, 100)); if (!db_) { Logger::Error("ExecuteSql: database not connected"); stats_.failedQueries++; @@ -182,6 +183,7 @@ bool SQLiteClient::ExecuteSql(const std::string& sql, std::vector lock(dbMutex_); + Logger::Trace("SQLite Query start: {}", sql.substr(0, 100)); if (!db_) { Logger::Error("Query: database not connected"); stats_.failedQueries++; @@ -281,6 +284,7 @@ nlohmann::json SQLiteClient::Query(const std::string& sql) { sqlite3_finalize(stmt); stats_.totalQueries++; + Logger::Trace("SQLite Query end"); return result; } diff --git a/src/game/GameEntity.cpp b/src/game/GameEntity.cpp index 04c359b..a8a1e1a 100644 --- a/src/game/GameEntity.cpp +++ b/src/game/GameEntity.cpp @@ -30,12 +30,12 @@ bool GameEntity::BoundingBox::Intersects(const BoundingBox& other) const { float GameEntity::BoundingBox::GetDistanceSquared(const glm::vec3& point) const { glm::vec3 closest_point; - + // Find closest point on box closest_point.x = std::max(min.x, std::min(point.x, max.x)); closest_point.y = std::max(min.y, std::min(point.y, max.y)); closest_point.z = std::max(min.z, std::min(point.z, max.z)); - + glm::vec3 diff = point - closest_point; return glm::dot(diff, diff); } @@ -69,11 +69,7 @@ GameEntity::GameEntity(EntityType type, const glm::vec3& position) scale_(1.0f, 1.0f, 1.0f), velocity_(0.0f, 0.0f, 0.0f), acceleration_(0.0f, 0.0f, 0.0f) { - - // Set default name name_ = std::string(EntityTypeToString(type)) + "_" + std::to_string(id_); - - // Default category based on type switch (type_) { case EntityType::PLAYER: category_ = "player"; break; case EntityType::NPC: category_ = "npc"; break; @@ -81,26 +77,18 @@ GameEntity::GameEntity(EntityType type, const glm::vec3& position) case EntityType::PROJECTILE: category_ = "projectile"; break; default: category_ = "misc"; break; } - - // Update bounding volumes UpdateBoundingVolumes(); - - Logger::Debug("GameEntity created: {} (ID: {}) at [{:.1f}, {:.1f}, {:.1f}]", - name_, id_, position.x, position.y, position.z); + // Logger::Trace("GameEntity created: {} (ID: {}) at [{:.1f}, {:.1f}, {:.1f}]", + // name_, id_, position.x, position.y, position.z); } GameEntity::~GameEntity() { OnDestroy(); - - // Remove from parent if (auto parent = parent_.lock()) { parent->RemoveChild(id_); } - - // Clear children children_.clear(); - - Logger::Debug("GameEntity destroyed: {} (ID: {})", name_, id_); + //Logger::Trace("GameEntity destroyed: {} (ID: {})", name_, id_); } // =============== Bounding Volume Methods =============== @@ -122,16 +110,16 @@ void GameEntity::UpdateBoundingVolumes() { // Default bounding box size (1x1x1 unit cube centered at position) const float DEFAULT_SIZE = 1.0f; const float HALF_SIZE = DEFAULT_SIZE / 2.0f; - + bounding_box_.min = position_ - glm::vec3(HALF_SIZE); bounding_box_.max = position_ + glm::vec3(HALF_SIZE); bounding_box_.center = position_; bounding_box_.size = glm::vec3(DEFAULT_SIZE); - + // Bounding sphere that contains the box bounding_sphere_.center = position_; bounding_sphere_.radius = glm::length(bounding_box_.size) / 2.0f; - + bounding_volumes_dirty_ = false; } @@ -146,20 +134,15 @@ void GameEntity::SetMaxHealth(float max_health) { } void GameEntity::TakeDamage(float damage, uint64_t source_id) { + (void)source_id; if (!IsAlive() || damage <= 0.0f) return; - float old_health = health_; SetHealth(health_ - damage); - - Logger::Debug("Entity {} took {} damage (health: {}/{}) from source {}", - id_, damage, health_, max_health_, source_id); - - // Fire damage event + // Logger::Trace("Entity {} took {} damage (health: {}/{}) from source {}", + // id_, damage, health_, max_health_, source_id); if (health_ < old_health) { FireEvent("on_damage_taken"); } - - // Check for death if (IsDead()) { FireEvent("on_death"); active_ = false; @@ -167,14 +150,12 @@ void GameEntity::TakeDamage(float damage, uint64_t source_id) { } void GameEntity::Heal(float amount, uint64_t source_id) { + (void)source_id; if (!IsAlive() || amount <= 0.0f) return; - float old_health = health_; SetHealth(health_ + amount); - - Logger::Debug("Entity {} healed for {} (health: {}/{}) from source {}", - id_, amount, health_, max_health_, source_id); - + // Logger::Trace("Entity {} healed for {} (health: {}/{}) from source {}", + // id_, amount, health_, max_health_, source_id); if (health_ > old_health) { FireEvent("on_healed"); } @@ -219,7 +200,7 @@ void GameEntity::SetParent(std::shared_ptr parent) { if (auto old_parent = parent_.lock()) { old_parent->RemoveChild(id_); } - + // Set new parent if (parent) { parent_ = parent; @@ -227,7 +208,7 @@ void GameEntity::SetParent(std::shared_ptr parent) { } else { parent_.reset(); } - + OnParentChanged(); } @@ -235,13 +216,13 @@ void GameEntity::AddChild(std::shared_ptr child) { if (!child || child.get() == this) { return; } - + // Check if already a child auto it = std::find_if(children_.begin(), children_.end(), [child](const std::shared_ptr& c) { return c->GetId() == child->GetId(); }); - + if (it == children_.end()) { children_.push_back(child); OnChildAdded(child); @@ -253,7 +234,7 @@ void GameEntity::RemoveChild(uint64_t child_id) { [child_id](const std::shared_ptr& child) { return child->GetId() == child_id; }); - + if (it != children_.end()) { auto child = *it; children_.erase(it); @@ -290,12 +271,12 @@ glm::vec3 GameEntity::GetWorldScale() const { void GameEntity::Update(float delta_time) { // Apply movement Move(delta_time); - + // Update bounding volumes if needed if (bounding_volumes_dirty_) { UpdateBoundingVolumes(); } - + // Update children for (auto& child : children_) { if (child->IsActive()) { @@ -326,22 +307,22 @@ void GameEntity::LateUpdate(float delta_time) { void GameEntity::Move(float delta_time) { if (!IsMoving()) return; - + // Apply acceleration velocity_ += acceleration_ * delta_time; - + // Apply velocity to position position_ += velocity_ * delta_time; - + // Apply damping (simple friction) velocity_ *= 0.95f; - + // If velocity is very small, stop moving if (glm::length(velocity_) < 0.001f) { velocity_ = glm::vec3(0.0f); acceleration_ = glm::vec3(0.0f); } - + // Mark bounding volumes as dirty bounding_volumes_dirty_ = true; } @@ -353,27 +334,30 @@ void GameEntity::Stop() { // =============== Event Handlers =============== void GameEntity::OnCreate() { - Logger::Debug("Entity {} onCreate", id_); + //Logger::Trace("Entity {} onCreate", id_); FireEvent("on_create"); } void GameEntity::OnDestroy() { - Logger::Debug("Entity {} onDestroy", id_); + //Logger::Trace("Entity {} onDestroy", id_); FireEvent("on_destroy"); } void GameEntity::OnCollision(std::shared_ptr other) { - Logger::Debug("Entity {} collided with entity {}", id_, other ? other->GetId() : 0); + (void)other; + //Logger::Trace("Entity {} collided with entity {}", id_, other ? other->GetId() : 0); FireEvent("on_collision"); } void GameEntity::OnTriggerEnter(std::shared_ptr other) { - Logger::Debug("Entity {} trigger enter with entity {}", id_, other ? other->GetId() : 0); + (void)other; + //Logger::Trace("Entity {} trigger enter with entity {}", id_, other ? other->GetId() : 0); FireEvent("on_trigger_enter"); } void GameEntity::OnTriggerExit(std::shared_ptr other) { - Logger::Debug("Entity {} trigger exit with entity {}", id_, other ? other->GetId() : 0); + (void)other; + //Logger::Trace("Entity {} trigger exit with entity {}", id_, other ? other->GetId() : 0); FireEvent("on_trigger_exit"); } @@ -382,67 +366,65 @@ void GameEntity::OnParentChanged() { } void GameEntity::OnChildAdded(std::shared_ptr child) { - Logger::Debug("Entity {} added child {}", id_, child->GetId()); + (void)child; + //Logger::Trace("Entity {} added child {}", id_, child->GetId()); FireEvent("on_child_added"); } void GameEntity::OnChildRemoved(std::shared_ptr child) { - Logger::Debug("Entity {} removed child {}", id_, child->GetId()); + (void)child; + //Logger::Trace("Entity {} removed child {}", id_, child->GetId()); FireEvent("on_child_removed"); } // =============== Transform Updates =============== -void GameEntity::UpdateWorldTransform() { - // Update this entity's world transform based on parent - // (Currently handled in GetWorld* methods) -} +void GameEntity::UpdateWorldTransform() {} void GameEntity::UpdateLocalTransform() { - // Update local transform (could be used for animation) bounding_volumes_dirty_ = true; } // =============== Serialization =============== nlohmann::json GameEntity::Serialize() const { nlohmann::json json; - + // Basic properties json["id"] = id_; json["type"] = static_cast(type_); json["name"] = name_; json["category"] = category_; - + // Transform json["position"] = {position_.x, position_.y, position_.z}; json["rotation"] = {rotation_.x, rotation_.y, rotation_.z}; json["scale"] = {scale_.x, scale_.y, scale_.z}; json["velocity"] = {velocity_.x, velocity_.y, velocity_.z}; - + // State json["active"] = active_; json["visible"] = visible_; json["collidable"] = collidable_; json["persistent"] = persistent_; - + // Health json["health"] = health_; json["max_health"] = max_health_; - + // Tags if (!tags_.empty()) { json["tags"] = tags_; } - + // Properties if (!properties_.empty()) { json["properties"] = properties_; } - + // Hierarchy (only store parent ID, children will be reconstructed) if (auto parent = parent_.lock()) { json["parent_id"] = parent->GetId(); } - + return json; } @@ -451,56 +433,56 @@ void GameEntity::Deserialize(const nlohmann::json& data) { if (data.contains("id")) { id_ = data["id"]; } - + // Name and category name_ = data.value("name", name_); category_ = data.value("category", category_); - + // Transform if (data.contains("position") && data["position"].is_array() && data["position"].size() >= 3) { position_.x = data["position"][0]; position_.y = data["position"][1]; position_.z = data["position"][2]; } - + if (data.contains("rotation") && data["rotation"].is_array() && data["rotation"].size() >= 3) { rotation_.x = data["rotation"][0]; rotation_.y = data["rotation"][1]; rotation_.z = data["rotation"][2]; } - + if (data.contains("scale") && data["scale"].is_array() && data["scale"].size() >= 3) { scale_.x = data["scale"][0]; scale_.y = data["scale"][1]; scale_.z = data["scale"][2]; } - + if (data.contains("velocity") && data["velocity"].is_array() && data["velocity"].size() >= 3) { velocity_.x = data["velocity"][0]; velocity_.y = data["velocity"][1]; velocity_.z = data["velocity"][2]; } - + // State active_ = data.value("active", active_); visible_ = data.value("visible", visible_); collidable_ = data.value("collidable", collidable_); persistent_ = data.value("persistent", persistent_); - + // Health health_ = data.value("health", health_); max_health_ = data.value("max_health", max_health_); - + // Tags if (data.contains("tags") && data["tags"].is_array()) { tags_ = data["tags"].get>(); } - + // Properties if (data.contains("properties")) { properties_ = data["properties"]; } - + // Update bounding volumes UpdateBoundingVolumes(); } @@ -510,19 +492,19 @@ std::shared_ptr GameEntity::CreateFromJson(const nlohmann::json& dat Logger::Error("Invalid entity JSON: missing or invalid type field"); return nullptr; } - + EntityType type = static_cast(data["type"].get()); glm::vec3 position(0.0f); - + if (data.contains("position") && data["position"].is_array() && data["position"].size() >= 3) { position.x = data["position"][0]; position.y = data["position"][1]; position.z = data["position"][2]; } - + auto entity = std::make_shared(type, position); entity->Deserialize(data); - + return entity; } @@ -558,16 +540,16 @@ bool GameEntity::IsInRange(std::shared_ptr other, float range) const // =============== LookAt Methods =============== void GameEntity::LookAt(const glm::vec3& target) { glm::vec3 direction = target - position_; - + if (glm::length(direction) > 0.001f) { direction = glm::normalize(direction); - + // Calculate yaw (rotation around Y axis) float yaw = std::atan2(direction.x, direction.z); - + // Calculate pitch (rotation around X axis) float pitch = std::asin(-direction.y); - + rotation_.x = pitch; rotation_.y = yaw; rotation_.z = 0.0f; @@ -649,7 +631,7 @@ void GameEntity::DumpInfo() const { std::string GameEntity::ToString() const { std::stringstream ss; - ss << name_ << " (ID: " << id_ + ss << name_ << " (ID: " << id_ << ", Type: " << EntityTypeToString(type_) << ", Pos: [" << std::fixed << std::setprecision(1) << position_.x << ", " << position_.y << ", " << position_.z << "])"; @@ -704,7 +686,7 @@ EntityType GameEntity::StringToEntityType(const std::string& type_str) { {"WAYPOINT", EntityType::WAYPOINT}, {"ANY", EntityType::ANY} }; - + auto it = type_map.find(type_str); return it != type_map.end() ? it->second : EntityType::ANY; } diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index ef08370..1949043 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -1,6 +1,6 @@ #include "game/GameLogic.hpp" - +static std::unique_ptr g_hotReloader; std::mutex GameLogic::instanceMutex_; GameLogic* GameLogic::instance_ = nullptr; @@ -9,11 +9,10 @@ GameLogic& GameLogic::GetInstance() { if (!instance_) { instance_ = new GameLogic(); } - return *instance_; + return *instance_; } -GameLogic::GameLogic() -{ +GameLogic::GameLogic() { Logger::Debug("GameLogic created"); } @@ -24,10 +23,12 @@ GameLogic::~GameLogic() { } void GameLogic::Initialize() { - if (instance_) { + if (initialized_) { Logger::Warn("GameLogic already initialized"); return; } + initialized_ = true; + LogicCore::Initialize(); Logger::Info("Initializing GameLogic with world system..."); auto& config = ConfigManager::GetInstance(); WorldConfig worldConfig; @@ -38,7 +39,7 @@ void GameLogic::Initialize() { worldConfig.terrainScale = config.GetFloat("world.terrain_scale", 100.0f); worldConfig.maxTerrainHeight = config.GetFloat("world.max_terrain_height", 50.0f); worldConfig.waterLevel = config.GetFloat("world.water_level", 10.0f); - worldConfig.chunkUnloadDistance = config.GetFloat("world.chunk_unload_distance", 200.0f); + worldConfig.unloadDistance = config.GetFloat("world.unload_distance", 200.0f); SetWorldConfig(worldConfig); LogicWorld::GetInstance().Initialize(worldConfig); int preloadRadius = 1; @@ -51,18 +52,44 @@ void GameLogic::Initialize() { if (!LoadGameData()) { Logger::Error("Failed to load game data"); } + pythonEnabled_ = config.GetBool("python.enabled", false); + if (pythonEnabled_) { + auto& pythonScripting = PythonScripting::GetInstance(); + if (pythonScripting.Initialize()) { + Logger::Info("Python scripting initialized"); + RegisterPythonEventHandlers(); + bool hotReloadEnabled = config.GetBool("python.hot_reload", true); + if (hotReloadEnabled) { + std::string scriptDir = config.GetString("python.script_dir", "./scripts"); + g_hotReloader = std::make_unique(scriptDir, 2000); + g_hotReloader->Start(); + } + } else { + Logger::Warn("Failed to initialize Python scripting"); + pythonEnabled_ = false; + } + } RegisterWorldHandlers(); Logger::Info("GameLogic world system initialized successfully"); } void GameLogic::Shutdown() { - if (!instance_) { + if (!initialized_) { + Logger::Warn("GameLogic already shutdown"); return; } Logger::Info("Shutting down GameLogic world system..."); + if (g_hotReloader) { + g_hotReloader->Stop(); + g_hotReloader.reset(); + } + if (pythonEnabled_) { + PythonScripting::GetInstance().Shutdown(); + } LogicEntity::GetInstance().Shutdown(); LogicWorld::GetInstance().Shutdown(); - Logger::Info("GameLogic world system shutdown complete"); + LogicCore::Shutdown(); + initialized_ = false; } void GameLogic::SetWorldConfig(const WorldConfig& config) { @@ -73,13 +100,13 @@ const GameLogic::WorldConfig& GameLogic::GetWorldConfig() const { return static_cast(LogicWorld::GetInstance().GetConfig()); } -std::shared_ptr GameLogic::GetOrCreateChunk(int chunkX, int chunkZ) { - return LogicWorld::GetInstance().GetOrCreateChunk(chunkX, chunkZ); +std::shared_ptr GameLogic::GetOrCreateChunk(int chunk_x, int chunk_z) { + return LogicWorld::GetInstance().GetOrCreateChunk(chunk_x, chunk_z); } -void GameLogic::GenerateWorldAroundPlayer(uint64_t playerId, const glm::vec3& position) { +void GameLogic::GenerateWorldAroundPlayer(uint64_t player_id, const glm::vec3& position) { LogicWorld::GetInstance().GenerateWorldAroundPlayer(position, GetWorldConfig().viewDistance); - SyncNearbyEntitiesToPlayer(GetSessionIdByPlayer(playerId), position); + SyncNearbyEntitiesToPlayer(GetSessionIdByPlayer(player_id), position); } void GameLogic::PreloadWorldData(float radius) { @@ -110,8 +137,8 @@ GameEntity* GameLogic::GetEntity(uint64_t entityId) { return LogicEntity::GetInstance().GetEntity(entityId); } -std::shared_ptr GameLogic::GetPlayer(uint64_t playerId) { - return PlayerManager::GetInstance().GetPlayer(playerId); +std::shared_ptr GameLogic::GetPlayer(uint64_t player_id) { + return PlayerManager::GetInstance().GetPlayer(player_id); } CollisionResult GameLogic::CheckCollision(const glm::vec3& position, float radius, uint64_t excludeEntityId) { @@ -125,7 +152,7 @@ bool GameLogic::Raycast(const glm::vec3& origin, const glm::vec3& direction, flo void GameLogic::CreateLootEntity(const glm::vec3& position, std::shared_ptr item, int quantity) { if (!item) { auto& lootTables = LootTableManager::GetInstance(); - auto loot = lootTables.GenerateLoot("default_loot_table"); // configurable + auto loot = lootTables.GenerateLoot("default_loot_table"); if (!loot.empty()) { item = loot[0].first; quantity = loot[0].second; @@ -137,60 +164,59 @@ void GameLogic::CreateLootEntity(const glm::vec3& position, std::shared_ptrGetType() != EntityType::ITEM) { - SendError(sessionId, "Invalid loot entity"); + SendError(session_id, "Invalid loot entity"); return; } - std::shared_ptr player = PlayerManager::GetInstance().GetPlayer(playerId); + std::shared_ptr player = PlayerManager::GetInstance().GetPlayer(player_id); if (!player) { - SendError(sessionId, "Player not found"); + SendError(session_id, "Player not found"); return; } float distance = glm::distance(player->GetPosition(), lootEntity->GetPosition()); if (distance > 5.0f) { - SendError(sessionId, "Too far to loot"); + SendError(session_id, "Too far to loot"); return; } auto& inv = InventorySystem::GetInstance(); - if (inv.AddItem(playerId, LootItem(lootId, lootEntity->GetName()), quantity)) { + if (inv.AddItem(player_id, LootItem(lootId, lootEntity->GetName()), quantity)) { EntityManager::GetInstance().DestroyEntity(lootId); - SendSuccess(sessionId, "Loot collected"); + SendSuccess(session_id, "Loot collected"); FirePythonEvent("loot_pickup", { - {"playerId", playerId}, + {"player_id", player_id}, {"itemId", lootId}, {"quantity", quantity} }); } else { - SendError(sessionId, "Inventory full"); + SendError(session_id, "Inventory full"); } - } catch (const std::exception& e) { Logger::Error("Error in HandleLootPickup: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::HandleInventoryMove(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleInventoryMove(uint64_t session_id, const nlohmann::json& data) { try { - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); int fromSlot = data.value("fromSlot", -1); int toSlot = data.value("toSlot", -1); if (fromSlot < 0 || toSlot < 0) { - SendError(sessionId, "Invalid slot indices"); + SendError(session_id, "Invalid slot indices"); return; } auto& inv = InventorySystem::GetInstance(); - if (inv.MoveItem(playerId, fromSlot, toSlot)) { + if (inv.MoveItem(player_id, fromSlot, toSlot)) { nlohmann::json response = { {"type", "inventory_move_response"}, {"success", true}, @@ -198,28 +224,27 @@ void GameLogic::HandleInventoryMove(uint64_t sessionId, const nlohmann::json& da {"toSlot", toSlot}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, response); + SendToSession(session_id, response); } else { - SendError(sessionId, "Failed to move item"); + SendError(session_id, "Failed to move item"); } } catch (const std::exception& e) { Logger::Error("Error in HandleInventoryMove: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::HandleItemUse(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleItemUse(uint64_t session_id, const nlohmann::json& data) { try { - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); int slot = data.value("slot", -1); uint64_t itemId = data.value("itemId", 0); auto& inv = InventorySystem::GetInstance(); std::shared_ptr item; if (slot >= 0) { - item = inv.GetItem(playerId, slot); + item = inv.GetItem(player_id, slot); } else if (itemId) { - // Find item by ID (could be in any slot) - auto invData = inv.GetInventory(playerId); + auto invData = inv.GetInventory(player_id); for (const auto& s : invData) { if (s.item && s.item->GetId() == itemId) { item = s.item; @@ -229,593 +254,328 @@ void GameLogic::HandleItemUse(uint64_t sessionId, const nlohmann::json& data) { } } if (!item) { - SendError(sessionId, "Item not found"); + SendError(session_id, "Item not found"); return; } if (item->IsConsumable()) { - if (inv.RemoveItem(playerId, itemId, 1)) { - SendSuccess(sessionId, "Item used"); + if (inv.RemoveItem(player_id, itemId, 1)) { + SendSuccess(session_id, "Item used"); FirePythonEvent("item_used", { - {"playerId", playerId}, + {"player_id", player_id}, {"itemId", itemId}, {"slot", slot} }); } else { - SendError(sessionId, "Failed to use item"); + SendError(session_id, "Failed to use item"); } } else { - SendError(sessionId, "Item cannot be used this way"); + SendError(session_id, "Item cannot be used this way"); } } catch (const std::exception& e) { Logger::Error("Error in HandleItemUse: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::HandleItemDrop(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleItemDrop(uint64_t session_id, const nlohmann::json& data) { try { - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); int slot = data.value("slot", -1); int quantity = data.value("quantity", 1); if (slot < 0) { - SendError(sessionId, "Invalid slot"); + SendError(session_id, "Invalid slot"); return; } auto& inv = InventorySystem::GetInstance(); - auto item = inv.GetItem(playerId, slot); + auto item = inv.GetItem(player_id, slot); if (!item) { - SendError(sessionId, "No item in that slot"); + SendError(session_id, "No item in that slot"); return; } - if (inv.RemoveItem(playerId, item->GetId(), quantity)) { - auto player = GetPlayer(playerId); + if (inv.RemoveItem(player_id, item->GetId(), quantity)) { + auto player = GetPlayer(player_id); if (player) { CreateLootEntity(player->GetPosition(), item, quantity); } - SendSuccess(sessionId, "Item dropped"); + SendSuccess(session_id, "Item dropped"); } else { - SendError(sessionId, "Failed to drop item"); + SendError(session_id, "Failed to drop item"); } } catch (const std::exception& e) { Logger::Error("Error in HandleItemDrop: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::HandleTradeRequest(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleTradeRequest(uint64_t session_id, const nlohmann::json& data) { try { - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); - std::string action = data.value("action", "request"); // request, accept, decline, cancel + std::string action = data.value("action", "request"); if (targetPlayerId == 0) { - SendError(sessionId, "Invalid target player"); + SendError(session_id, "Invalid target player"); return; } if (action == "request") { uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); if (targetSession == 0) { - SendError(sessionId, "Target player not online"); + SendError(session_id, "Target player not online"); return; } nlohmann::json request = { {"type", "trade_request"}, - {"fromPlayerId", playerId}, - {"fromPlayerName", GetPlayer(playerId)->GetName()}, + {"fromPlayerId", player_id}, + {"fromPlayerName", GetPlayer(player_id)->GetName()}, {"timestamp", GetCurrentTimestamp()} }; SendToSession(targetSession, request); - SendSuccess(sessionId, "Trade request sent"); + SendSuccess(session_id, "Trade request sent"); } else if (action == "accept") { uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); nlohmann::json acceptMsg = { {"type", "trade_start"}, - {"player1", playerId}, + {"player1", player_id}, {"player2", targetPlayerId}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, acceptMsg); + SendToSession(session_id, acceptMsg); SendToSession(targetSession, acceptMsg); } else { - SendError(sessionId, "Unsupported trade action"); + SendError(session_id, "Unsupported trade action"); } } catch (const std::exception& e) { Logger::Error("Error in HandleTradeRequest: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::HandleGoldTransaction(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleGoldTransaction(uint64_t session_id, const nlohmann::json& data) { try { - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); int64_t amount = data.value("amount", 0); - std::string type = data.value("type", "transfer"); // transfer, gift, etc. + std::string type = data.value("type", "transfer"); if (targetPlayerId == 0 || amount <= 0) { - SendError(sessionId, "Invalid target or amount"); + SendError(session_id, "Invalid target or amount"); return; } auto& inv = InventorySystem::GetInstance(); if (type == "transfer") { - if (inv.GetGold(playerId) < amount) { - SendError(sessionId, "Insufficient gold"); + if (inv.GetGold(player_id) < amount) { + SendError(session_id, "Insufficient gold"); return; } - if (inv.RemoveGold(playerId, amount) && inv.AddGold(targetPlayerId, amount)) { - SendSuccess(sessionId, "Gold transferred"); + if (inv.RemoveGold(player_id, amount) && inv.AddGold(targetPlayerId, amount)) { + SendSuccess(session_id, "Gold transferred"); uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); if (targetSession) { nlohmann::json notify = { {"type", "gold_received"}, - {"fromPlayerId", playerId}, + {"fromPlayerId", player_id}, {"amount", amount}, {"timestamp", GetCurrentTimestamp()} }; SendToSession(targetSession, notify); } } else { - SendError(sessionId, "Transaction failed"); + SendError(session_id, "Transaction failed"); } } else { - SendError(sessionId, "Unsupported transaction type"); + SendError(session_id, "Unsupported transaction type"); } } catch (const std::exception& e) { Logger::Error("Error in HandleGoldTransaction: {}", e.what()); - SendError(sessionId, "Internal server error"); + SendError(session_id, "Internal server error"); } } -void GameLogic::SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity) { +void GameLogic::SendPositionCorrection(uint64_t session_id, const glm::vec3& position, const glm::vec3& velocity) { BinaryProtocol::BinaryWriter writer; writer.WriteVector3(position); writer.WriteVector3(velocity); writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer()); + auto data = writer.GetBuffer(); + auto session = connectionManager_->GetSession(session_id); + if (!session || !session->IsConnected()) return; + if (session->GetProtocolMode() == ProtocolMode::Binary) { + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, data); + } else { + nlohmann::json jsonMsg = { + {"type", "position_correction"}, + {"x", position.x}, {"y", position.y}, {"z", position.z}, + {"vx", velocity.x}, {"vy", velocity.y}, {"vz", velocity.z}, + {"timestamp", GetCurrentTimestamp()} + }; + session->Send(jsonMsg); + } } void GameLogic::RegisterWorldHandlers() { - RegisterHandler("protocol_negotiation", [this](uint64_t sessionId, const nlohmann::json& data) { - Logger::Debug("Protocol negotiation received from session {}; data {}", sessionId, data.dump()); - }); - - RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, - [this](uint64_t sessionId, uint16_t /*type*/, const std::vector& data) { - HandleAuthentication(sessionId, data); + RegisterHandler("npc_interaction", [this](uint64_t session_id, const nlohmann::json& data) { + HandleNPCInteraction(session_id, data); }); - - RegisterHandler("authentication", [this](uint64_t sessionId, const nlohmann::json& data) { - std::string username = data.value("login", ""); - std::string password = data.value("password", ""); - HandleAuthentication(sessionId, username, password); - }); - - RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, - [this](uint64_t sessionId, uint16_t /*messageType*/, const std::vector& data) { - HandlePlayerState(sessionId, data); - }); - - RegisterHandler("world_chunk_request", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleWorldChunkRequestJson(sessionId, data); - }); - - RegisterHandler("player_position_update", [this](uint64_t sessionId, const nlohmann::json& data) { - HandlePlayerPositionUpdate(sessionId, data); - }); - - RegisterHandler("npc_interaction", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleNPCInteraction(sessionId, data); - }); - - RegisterHandler("collision_check", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleCollisionCheck(sessionId, data); + RegisterHandler("collision_check", [this](uint64_t session_id, const nlohmann::json& data) { + HandleCollisionCheck(session_id, data); }); - - RegisterHandler("familiar_command", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleFamiliarCommand(sessionId, data); + RegisterHandler("familiar_command", [this](uint64_t session_id, const nlohmann::json& data) { + HandleFamiliarCommand(session_id, data); }); - - RegisterHandler("entity_spawn_request", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleEntitySpawnRequest(sessionId, data); + RegisterHandler("entity_spawn_request", [this](uint64_t session_id, const nlohmann::json& data) { + HandleEntitySpawnRequest(session_id, data); }); - - RegisterHandler("loot_pickup", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleLootPickup(sessionId, data); + RegisterHandler("loot_pickup", [this](uint64_t session_id, const nlohmann::json& data) { + HandleLootPickup(session_id, data); }); - RegisterHandler("inventory_move", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleInventoryMove(sessionId, data); + RegisterHandler("inventory_move", [this](uint64_t session_id, const nlohmann::json& data) { + HandleInventoryMove(session_id, data); }); - RegisterHandler("item_use", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleItemUse(sessionId, data); + RegisterHandler("item_use", [this](uint64_t session_id, const nlohmann::json& data) { + HandleItemUse(session_id, data); }); - RegisterHandler("item_drop", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleItemDrop(sessionId, data); + RegisterHandler("item_drop", [this](uint64_t session_id, const nlohmann::json& data) { + HandleItemDrop(session_id, data); }); - RegisterHandler("trade_request", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleTradeRequest(sessionId, data); + RegisterHandler("trade_request", [this](uint64_t session_id, const nlohmann::json& data) { + HandleTradeRequest(session_id, data); }); - RegisterHandler("gold_transaction", [this](uint64_t sessionId, const nlohmann::json& data) { - HandleGoldTransaction(sessionId, data); + RegisterHandler("gold_transaction", [this](uint64_t session_id, const nlohmann::json& data) { + HandleGoldTransaction(session_id, data); }); - - RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, - [this](uint64_t sessionId, uint16_t /*messageType*/, const std::vector& data) { - BinaryProtocol::BinaryReader reader(data.data(), data.size()); - glm::vec3 position = reader.ReadVector3(); - nlohmann::json jsonData = { - {"x", position.x}, - {"y", position.y}, - {"z", position.z} - }; - HandlePlayerPositionUpdate(sessionId, jsonData); - }); - - RegisterBinaryHandler(BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST, - [this](uint64_t sessionId, uint16_t /*messageType*/, const std::vector& data) { - BinaryProtocol::BinaryReader reader(data.data(), data.size()); - int chunkX = reader.ReadInt32(); - int chunkZ = reader.ReadInt32(); - int lod = reader.ReadUInt8(); - nlohmann::json jsonData = { - {"chunkX", chunkX}, - {"chunkZ", chunkZ}, - {"lod", lod} - }; - HandleWorldChunkRequest(sessionId, jsonData); - }); - Logger::Info("Registered world message handlers"); } -void GameLogic::HandleMessage(uint64_t sessionId, const nlohmann::json& message) { - Logger::Debug("GameLogic::HandleMessage called for session {}", sessionId); +void GameLogic::HandleMessage(uint64_t session_id, const nlohmann::json& message) { + Logger::Debug("GameLogic::HandleMessage called for session {}", session_id); Logger::Debug("Message content: {}", message.dump()); std::string type = message.value("type", ""); Logger::Debug("Message type: '{}'", type); - if (type == "protocol_negotiation") { - // Already handled by the registered handler, nothing else needed - } - else if (type == "world_chunk_request") { - Logger::Debug("Dispatching to HandleWorldChunkRequestJson"); - HandleWorldChunkRequestJson(sessionId, message); - } - else if (type == "authentication") { - std::string username = message.value("login", ""); - std::string password = message.value("password", ""); - Logger::Debug("Dispatching authentication for user '{}'", username); - HandleAuthentication(sessionId, username, password); - } - else if (type == "player_position_update") { - HandlePlayerPositionUpdate(sessionId, message); - } - else if (type == "npc_interaction") { - HandleNPCInteraction(sessionId, message); - } - else if (type == "collision_check") { - HandleCollisionCheck(sessionId, message); - } - else if (type == "familiar_command") { - HandleFamiliarCommand(sessionId, message); - } - else if (type == "entity_spawn_request") { - HandleEntitySpawnRequest(sessionId, message); - } - else if (type == "loot_pickup") { - HandleLootPickup(sessionId, message); - } - else if (type == "inventory_move") { - HandleInventoryMove(sessionId, message); - } - else if (type == "item_use") { - HandleItemUse(sessionId, message); - } - else if (type == "item_drop") { - HandleItemDrop(sessionId, message); - } - else if (type == "trade_request") { - HandleTradeRequest(sessionId, message); - } - else if (type == "gold_transaction") { - HandleGoldTransaction(sessionId, message); - } - else { - Logger::Warn("Unknown message type '{}' from session {}", type, sessionId); - LogicCore::HandleMessage(sessionId, message); + if (type == "collision_check") { + HandleCollisionCheck(session_id, message); + } else if (type == "npc_interaction") { + HandleNPCInteraction(session_id, message); + } else if (type == "familiar_command") { + HandleFamiliarCommand(session_id, message); + } else if (type == "entity_spawn_request") { + HandleEntitySpawnRequest(session_id, message); + } else if (type == "loot_pickup") { + HandleLootPickup(session_id, message); + } else if (type == "inventory_move") { + HandleInventoryMove(session_id, message); + } else if (type == "item_use") { + HandleItemUse(session_id, message); + } else if (type == "item_drop") { + HandleItemDrop(session_id, message); + } else if (type == "trade_request") { + HandleTradeRequest(session_id, message); + } else if (type == "gold_transaction") { + HandleGoldTransaction(session_id, message); + } else { + Logger::Warn("Unknown message type '{}' from session {}", type, session_id); + LogicCore::HandleMessage(session_id, message); } } -void GameLogic::OnPlayerConnected(uint64_t sessionId, uint64_t playerId) { - Logger::Info("GameLogic: Player {} connected with session {}", playerId, sessionId); +void GameLogic::OnPlayerConnected(uint64_t session_id, uint64_t player_id) { + Logger::Info("GameLogic: Player {} connected with session {}", player_id, session_id); FirePythonEvent("player_connected", { - {"sessionId", sessionId}, - {"playerId", playerId} + {"session_id", session_id}, + {"player_id", player_id} }); - LogicCore::OnPlayerConnected(sessionId, playerId); + LogicCore::OnPlayerConnected(session_id, player_id); } -void GameLogic::OnPlayerDisconnected(uint64_t sessionId) { - uint64_t playerId = GetPlayerIdBySession(sessionId); +void GameLogic::OnPlayerDisconnected(uint64_t session_id) { + uint64_t player_id = GetPlayerIdBySession(session_id); { std::lock_guard lock(predictionMutex_); - playerPrediction_.erase(playerId); + playerPrediction_.erase(player_id); } - Logger::Info("GameLogic: Player {} disconnected from session {}", playerId, sessionId); + Logger::Info("GameLogic: Player {} disconnected from session {}", player_id, session_id); FirePythonEvent("player_disconnected", { - {"sessionId", sessionId}, - {"playerId", playerId} + {"session_id", session_id}, + {"player_id", player_id} }); - LogicCore::OnPlayerDisconnected(sessionId); -} - -void GameLogic::HandleWorldChunkRequest(uint64_t sessionId, const nlohmann::json& data) { - try { - int chunkX = data.value("chunkX", 0); - int chunkZ = data.value("chunkZ", 0); - int lod = data.value("lod", 0); - Logger::Debug("World chunk request: [{}, {}] LOD: {}", chunkX, chunkZ, lod); - auto chunk = GetOrCreateChunk(chunkX, chunkZ); - if (!chunk) { - SendError(sessionId, "Failed to generate chunk", 404); - return; - } - BinaryProtocol::BinaryWriter writer; - writer.WriteInt32(chunkX); - writer.WriteInt32(chunkZ); - writer.WriteUInt8(static_cast(lod)); - writer.WriteJson(chunk->Serialize()); - writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); - } catch (const std::exception& e) { - Logger::Error("Error handling world chunk request: {}", e.what()); - SendError(sessionId, "Failed to process chunk request", 500); - } -} - -void GameLogic::HandleWorldChunkRequestJson(uint64_t sessionId, const nlohmann::json& data) { - try { - int chunkX = data.value("chunkX", 0); - int chunkZ = data.value("chunkZ", 0); - int lod = data.value("lod", 0); - auto start = std::chrono::steady_clock::now(); - auto chunk = GetOrCreateChunk(chunkX, chunkZ); - auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); - if (!chunk) { - Logger::Error("Failed to get or create chunk ({},{})", chunkX, chunkZ); - SendError(sessionId, "Failed to generate chunk", 404); - return; - } - chunk->GenerateLowPolyGeometry(); - nlohmann::json response = { - {"type", "world_chunk"}, - {"chunkX", chunkX}, - {"chunkZ", chunkZ}, - {"lod", lod}, - {"data", chunk->Serialize()}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(sessionId, response); - } catch (const std::exception& err) { - Logger::Error("Exception in HandleWorldChunkRequestJson: {}", err.what()); - try { - SendError(sessionId, "Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", sessionId); - } - } catch (...) { - Logger::Error("Unknown exception in HandleWorldChunkRequestJson"); - try { - SendError(sessionId, "Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", sessionId); - } - } -} - -void GameLogic::HandleWorldChunkHMapRequest(uint64_t sessionId, const nlohmann::json& data) { - try { - int chunkX = data.value("chunkX", 0); - int chunkZ = data.value("chunkZ", 0); - int lod = data.value("lod", 0); - auto start = std::chrono::steady_clock::now(); - auto chunk = GetOrCreateChunk(chunkX, chunkZ); - auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); - if (!chunk) { - Logger::Error("Failed to get or create chunk ({},{})", chunkX, chunkZ); - SendError(sessionId, "Failed to generate chunk", 404); - return; - } - nlohmann::json response = { - {"type", "world_chunk"}, - {"chunkX", chunkX}, - {"chunkZ", chunkZ}, - {"lod", lod}, - {"data", chunk->Serialize()}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(sessionId, response); - } catch (const std::exception& err) { - Logger::Error("Exception in HandleWorldChunkRequestJson: {}", err.what()); - try { - SendError(sessionId, "Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", sessionId); - } - } catch (...) { - Logger::Error("Unknown exception in HandleWorldChunkRequestJson"); - try { - SendError(sessionId, "Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", sessionId); - } - } -} - -void GameLogic::HandlePlayerPositionUpdate(uint64_t sessionId, const nlohmann::json& data) { - try { - float x = data.value("x", 0.0f); - float y = data.value("y", 0.0f); - float z = data.value("z", 0.0f); - glm::vec3 position(x, y, z); - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (playerId == 0) { - return; - } - std::shared_ptr player = GetPlayer(playerId); - if (player) { - float collisionRadius = 0.5f; - CollisionResult collision = CheckCollision(position, collisionRadius, playerId); - if (collision.collided) { - position += collision.resolution; - } - player->SetPosition(position); - GenerateWorldAroundPlayer(playerId, position); - BinaryProtocol::BinaryWriter positionWriter; - positionWriter.WriteUInt64(playerId); - positionWriter.WriteVector3(position); - positionWriter.WriteVector3(glm::vec3(0, 0, 0)); - positionWriter.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, - positionWriter.GetBuffer(), 100.0f); - FirePythonEvent("player_move_3d", { - {"player_id", playerId}, - {"x", x}, - {"y", y}, - {"z", z}, - {"session_id", sessionId} - }); - } - } catch (const std::exception& e) { - Logger::Error("Error handling player position update: {}", e.what()); - } -} - -void GameLogic::HandlePlayerState(uint64_t sessionId, const std::vector& data) { - try { - ClientInput input = ClientInput::Deserialize(data.data(), data.size()); - if (!input.IsValid()) { - Logger::Warn("Invalid client input from session {}", sessionId); - return; - } - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (playerId == 0) { - Logger::Warn("No player for session {}", sessionId); - return; - } - { - std::lock_guard lock(predictionMutex_); - playerPrediction_[playerId].StoreClientInput(input); - } - auto player = GetPlayer(playerId); - if (!player) return; - PredictionSystem* pred = &playerPrediction_[playerId]; - auto currentTime = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - ServerState authState; - authState.last_processed_input = 0; // TODO: track last processed input - authState.timestamp = currentTime; - authState.position = player->GetPosition(); - authState.velocity = player->GetVelocity(); // assuming Player has velocity - authState.rotation = player->GetRotation(); // assuming Player has rotation - authState.on_ground = player->IsOnGround(); // assuming Player has onGround - auto unprocessed = pred->GetUnprocessedInputs(authState.last_processed_input); - if (!unprocessed.empty()) { - float deltaTime = 1.0f / 30.0f; // or compute based on time difference - authState = pred->SimulateMovement(authState, unprocessed, deltaTime); - } - player->SetPosition(authState.position); - player->SetVelocity(authState.velocity); - player->SetRotation(authState.rotation); - player->SetOnGround(authState.on_ground); - BroadcastPlayerState(playerId, authState); - static float correctionThreshold = 0.5f; // 0.5 meters - if (glm::distance(authState.position, player->GetPosition()) > correctionThreshold) { - SendPositionCorrection(sessionId, authState.position, authState.velocity); - } - } catch (const std::exception& e) { - Logger::Error("HandlePlayerState error: {}", e.what()); - } + LogicCore::OnPlayerDisconnected(session_id); } -void GameLogic::HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleNPCInteraction(uint64_t session_id, const nlohmann::json& data) { try { uint64_t npcId = data.value("npcId", 0ULL); std::string interactionType = data.value("interaction", ""); if (npcId == 0 || interactionType.empty()) { - SendError(sessionId, "Invalid NPC interaction", 400); + SendError(session_id, "Invalid NPC interaction", 400); return; } NPCEntity* npc = GetNPCEntity(npcId); if (!npc) { - SendError(sessionId, "NPC not found", 404); + SendError(session_id, "NPC not found", 404); return; } - uint64_t playerId = GetPlayerIdBySession(sessionId); - std::shared_ptr player = GetPlayer(playerId); + uint64_t player_id = GetPlayerIdBySession(session_id); + std::shared_ptr player = GetPlayer(player_id); if (!player) { - SendError(sessionId, "Player not found", 404); + SendError(session_id, "Player not found", 404); return; } float distance = glm::distance(player->GetPosition(), npc->GetPosition()); if (distance > 15.0f) { - SendError(sessionId, "Too far from NPC", 400); + SendError(session_id, "Too far from NPC", 400); return; } if (interactionType == "attack") { float damage = 10.0f; - npc->TakeDamage(damage, playerId); + npc->TakeDamage(damage, player_id); bool isDead = npc->IsDead(); if (isDead) { - MobSystem::GetInstance().OnMobDeath(npcId, playerId); + MobSystem::GetInstance().OnMobDeath(npcId, player_id); } BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(playerId); + writer.WriteUInt64(player_id); writer.WriteUInt64(npcId); writer.WriteFloat(damage); writer.WriteFloat(npc->GetStats().health); writer.WriteUInt8(isDead ? 1 : 0); writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_COMBAT_EVENT, writer.GetBuffer()); + SendBinaryToSession(session_id, BinaryProtocol::MESSAGE_TYPE_COMBAT_EVENT, writer.GetBuffer()); } else if (interactionType == "talk") { auto& questMgr = QuestManager::GetInstance(); - auto quests = questMgr.GetQuestsFromNPC(playerId, npc->GetId()); + auto quests = questMgr.GetQuestsFromNPC(player_id, npc->GetId()); nlohmann::json response = { {"type", "npc_dialogue"}, {"npcId", npcId}, {"quests", quests}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, response); + SendToSession(session_id, response); } } catch (const std::exception& e) { Logger::Error("Error handling NPC interaction: {}", e.what()); - SendError(sessionId, "Failed to process NPC interaction", 500); + SendError(session_id, "Failed to process NPC interaction", 500); } } -void GameLogic::HandleFamiliarCommand(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleFamiliarCommand(uint64_t session_id, const nlohmann::json& data) { try { uint64_t familiarId = data.value("familiarId", 0ULL); std::string command = data.value("command", ""); uint64_t targetId = data.value("targetId", 0ULL); - if (familiarId == 0 || command.empty()) { - SendError(sessionId, "Invalid familiar command", 400); + SendError(session_id, "Invalid familiar command", 400); return; } - NPCEntity* familiar = GetNPCEntity(familiarId); if (!familiar) { - SendError(sessionId, "Familiar not found", 404); + SendError(session_id, "Familiar not found", 404); return; } - - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (familiar->GetOwnerId() != playerId) { - SendError(sessionId, "Not your familiar", 403); + uint64_t player_id = GetPlayerIdBySession(session_id); + if (familiar->GetOwnerId() != player_id) { + SendError(session_id, "Not your familiar", 403); return; } - if (command == "follow") { familiar->SetBehaviorState(NPCAIState::FOLLOW); - familiar->SetTarget(playerId); + familiar->SetTarget(player_id); } else if (command == "attack") { familiar->SetBehaviorState(NPCAIState::CHASE); familiar->SetTarget(targetId); @@ -823,7 +583,6 @@ void GameLogic::HandleFamiliarCommand(uint64_t sessionId, const nlohmann::json& familiar->SetBehaviorState(NPCAIState::IDLE); familiar->SetTarget(0); } - nlohmann::json response = { {"type", "familiar_command_response"}, {"familiarId", familiarId}, @@ -831,24 +590,21 @@ void GameLogic::HandleFamiliarCommand(uint64_t sessionId, const nlohmann::json& {"success", true}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, response); - + SendToSession(session_id, response); } catch (const std::exception& e) { Logger::Error("Error handling familiar command: {}", e.what()); - SendError(sessionId, "Failed to process familiar command", 500); + SendError(session_id, "Failed to process familiar command", 500); } } -void GameLogic::HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleCollisionCheck(uint64_t session_id, const nlohmann::json& data) { try { float x = data.value("x", 0.0f); float y = data.value("y", 0.0f); float z = data.value("z", 0.0f); float radius = data.value("radius", 0.5f); - glm::vec3 position(x, y, z); CollisionResult result = CheckCollision(position, radius); - nlohmann::json response = { {"type", "collision_check_response"}, {"position", {x, y, z}}, @@ -857,30 +613,25 @@ void GameLogic::HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& d {"penetration", result.penetration}, {"timestamp", GetCurrentTimestamp()} }; - - SendToSession(sessionId, response); - + SendToSession(session_id, response); } catch (const std::exception& e) { Logger::Error("Error handling collision check: {}", e.what()); - SendError(sessionId, "Failed to check collision", 500); + SendError(session_id, "Failed to check collision", 500); } } -void GameLogic::HandleEntitySpawnRequest(uint64_t sessionId, const nlohmann::json& data) { +void GameLogic::HandleEntitySpawnRequest(uint64_t session_id, const nlohmann::json& data) { try { int entityType = data.value("entityType", 0); float x = data.value("x", 0.0f); float y = data.value("y", 0.0f); float z = data.value("z", 0.0f); glm::vec3 position(x, y, z); - if (entityType >= static_cast(NPCType::WOLF_FAMILIAR) && entityType <= static_cast(NPCType::CAT_FAMILIAR)) { - - uint64_t playerId = GetPlayerIdBySession(sessionId); + uint64_t player_id = GetPlayerIdBySession(session_id); NPCType type = static_cast(entityType); - uint64_t npcId = SpawnNPC(type, position, playerId); - + uint64_t npcId = SpawnNPC(type, position, player_id); if (npcId > 0) { nlohmann::json response = { {"type", "entity_spawn_response"}, @@ -890,34 +641,64 @@ void GameLogic::HandleEntitySpawnRequest(uint64_t sessionId, const nlohmann::jso {"success", true}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, response); + SendToSession(session_id, response); } else { - SendError(sessionId, "Failed to spawn entity", 500); + SendError(session_id, "Failed to spawn entity", 500); } } else { - SendError(sessionId, "Invalid entity type", 400); + SendError(session_id, "Invalid entity type", 400); } - } catch (const std::exception& e) { Logger::Error("Error handling entity spawn request: {}", e.what()); - SendError(sessionId, "Failed to spawn entity", 500); + SendError(session_id, "Failed to spawn entity", 500); } } -// =============== Broadcasting =============== void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, const std::vector& data, float radius) { if (!connectionManager_) return; auto& pm = PlayerManager::GetInstance(); auto nearby = pm.GetPlayersInRadius(position, radius); for (auto& player : nearby) { - uint64_t sessionId = pm.GetSessionIdByPlayerId(player->GetId()); - if (sessionId != 0) { - auto session = connectionManager_->GetSession(sessionId); - if (session && session->IsConnected()) { - session->SendBinary(messageType, data); + uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); + if (session_id == 0) continue; + auto session = connectionManager_->GetSession(session_id); + if (!session || !session->IsConnected()) continue; + if (session->GetProtocolMode() == ProtocolMode::Binary) { + session->SendBinary(messageType, data); + continue; + } + nlohmann::json jsonMsg; + try { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + switch (messageType) { + case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: + jsonMsg = PlayerPositionToJson(data); + break; + case BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE: + jsonMsg = PlayerUpdateToJson(data); + break; + case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: + jsonMsg = EntitySpawnToJson(data); + break; + case BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE: + jsonMsg = EntityUpdateToJson(data); + break; + case BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN: + jsonMsg = EntityDespawnToJson(data); + break; + case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: + jsonMsg = ChunkDataToJson(data); + break; + default: + Logger::Warn("No JSON conversion for message type {}", messageType); + continue; } + } catch (const std::exception& e) { + Logger::Error("BroadcastToNearbyPlayers: Failed to convert binary to JSON: {}", e.what()); + continue; } + session->Send(jsonMsg); } } @@ -928,9 +709,9 @@ void GameLogic::BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16 auto onlinePlayers = pm.GetOnlinePlayers(); for (const auto& player : onlinePlayers) { if (glm::distance(player->GetPosition(), position) <= radius) { - uint64_t sessionId = GetSessionIdByPlayer(player->GetId()); - if (sessionId != 0) { - auto session = connectionManager_->GetSession(sessionId); + uint64_t session_id = GetSessionIdByPlayer(player->GetId()); + if (session_id != 0) { + auto session = connectionManager_->GetSession(session_id); if (session && session->IsConnected()) { session->SendBinary(messageType, data); } @@ -951,14 +732,13 @@ void GameLogic::BroadcastEntitySpawn(uint64_t entityId, EntityType type, const g BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer(), 100.0f); } -void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& position) { - // Send a batch of entity data (positions, types, etc.) to the player +void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& position) { auto nearbyEntities = EntityManager::GetInstance().GetEntitiesInRadius(position, 100.0f); nlohmann::json entityList = nlohmann::json::array(); for (uint64_t entityId : nearbyEntities) { auto entity = GetEntity(entityId); if (entity) { - entityList.push_back(entity->Serialize()); // assume Serialize() + entityList.push_back(entity->Serialize()); } } nlohmann::json message = { @@ -966,245 +746,112 @@ void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& {"entities", entityList}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, message); + SendToSession(session_id, message); } -// =============== Thread Functions =============== -void GameLogic::GameLoop() { - Logger::Info("Game loop started"); - - auto lastUpdate = std::chrono::steady_clock::now(); - - while (!instanceMutex_.try_lock()) { - try { - auto startTime = std::chrono::steady_clock::now(); - - auto now = std::chrono::steady_clock::now(); - auto deltaTimeMillis = std::chrono::duration_cast(now - lastUpdate); - float deltaTime = deltaTimeMillis.count() / 1000.0f; - lastUpdate = now; - - // Update systems - UpdateWorld(deltaTime); - LogicEntity::GetInstance().UpdateNPCs(deltaTime); - LogicEntity::GetInstance().UpdateCollisions(deltaTime); - - // Process game logic - ProcessGameTick(deltaTime); - ProcessEvents(); +void GameLogic::BroadcastPlayerSpawn(uint64_t player_id) { + auto player = GetPlayer(player_id); + if (!player) return; + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(player_id); + writer.WriteString(player->GetName()); + writer.WriteVector3(player->GetPosition()); + writer.WriteFloat(player->GetRotation().y); + writer.WriteFloat(player->GetHealth()); + writer.WriteFloat(player->GetMaxHealth()); + BroadcastToNearbyPlayers(player->GetPosition(), + BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, + writer.GetBuffer(), + ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} - auto endTime = std::chrono::steady_clock::now(); - auto processingTime = std::chrono::duration_cast(endTime - startTime).count(); +void GameLogic::BroadcastPlayerDespawn(uint64_t player_id, const glm::vec3& lastPosition) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(player_id); + BroadcastToNearbyPlayers(lastPosition, + BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, + writer.GetBuffer(), + ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +} - if (processingTime < gameLoopInterval_.count()) { - std::unique_lock lock(gameLoopMutex_); - gameLoopCV_.wait_for(lock, - gameLoopInterval_ - std::chrono::milliseconds(processingTime), - [this] { return !instance_; }); - } else { - Logger::Warn("Game loop lagging: {}ms", processingTime); +void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { + if (!connectionManager_) return; + auto& pm = PlayerManager::GetInstance(); + auto nearby = pm.GetPlayersInRadius(position, radius); + for (auto& player : nearby) { + uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); + if (session_id != 0) { + auto session = connectionManager_->GetSession(session_id); + if (session && session->IsConnected()) { + session->Send(message); } - } catch (const std::exception& e) { - Logger::Error("Error in game loop: {}", e.what()); } } - - instanceMutex_.unlock(); - - Logger::Info("Game loop stopped"); } -void GameLogic::SpawnerLoop() { - Logger::Info("Spawner loop started"); - LogicCore::SpawnerLoop(); - Logger::Info("Spawner loop stopped"); +void GameLogic::BroadcastPlayerSpawnJson(uint64_t player_id) { + auto player = GetPlayer(player_id); + if (!player) return; + nlohmann::json msg = { + {"type", "player_spawn"}, + {"player_id", player_id}, + {"name", player->GetName()}, + {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, + {"yaw", player->GetRotation().y}, + {"health", player->GetHealth()}, + {"max_health", player->GetMaxHealth()}, + {"timestamp", GetCurrentTimestamp()} + }; + BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); } -void GameLogic::SaveLoop() { - Logger::Info("Save loop started"); - LogicCore::SaveLoop(); - SaveChunkData(); - Logger::Info("Save loop stopped"); +void GameLogic::BroadcastPlayerDespawnJson(uint64_t player_id, const glm::vec3& lastPosition) { + nlohmann::json msg = { + {"type", "player_despawn"}, + {"player_id", player_id}, + {"timestamp", GetCurrentTimestamp()} + }; + BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); } -// =============== Game Tick Processing =============== -void GameLogic::ProcessGameTick(float deltaTime) { - LogicCore::ProcessGameTick(deltaTime); - // Additional game tick processing +void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(entityId); + writer.WriteUInt64(GetCurrentTimestamp()); + BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); } -void GameLogic::UpdateWorld(float deltaTime) { - // 1. Update world time (e.g., day/night cycle) - static float worldTime = 0.0f; - worldTime += deltaTime; - // Example: 24-hour cycle every 30 minutes real time (1800 seconds) - const float dayLength = 1800.0f; // in seconds - float timeOfDay = fmod(worldTime, dayLength) / dayLength; // 0.0 to 1.0 - // Notify world systems or update lighting via some manager - LogicWorld::GetInstance().SetTimeOfDay(timeOfDay); - - // 2. Throttled chunk unloading – only run every N seconds - static float timeSinceLastUnload = 0.0f; - timeSinceLastUnload += deltaTime; - const float unloadInterval = 1.0f; // unload check every second - if (timeSinceLastUnload >= unloadInterval) { - timeSinceLastUnload = 0.0f; +void GameLogic::SendAuthenticationSuccess(uint64_t session_id, uint64_t player_id, const std::string& message) { + nlohmann::json response = { + {"type", "authentication_response"}, + {"success", true}, + {"player_id", player_id}, + {"message", message}, + {"timestamp", GetCurrentTimestamp()} + }; + SendToSession(session_id, response); +} - // Collect player positions safely - std::vector playerPositions; - { - std::lock_guard lock(sessionMutex_); - for (const auto& [playerId, sessionId] : playerToSessionMap_) { - if (auto player = GetPlayer(playerId)) { - playerPositions.push_back(player->GetPosition()); - } - } - } +void GameLogic::SendAuthenticationFailure(uint64_t session_id, const std::string& message) { + nlohmann::json response = { + {"type", "authentication_response"}, + {"success", false}, + {"message", message}, + {"timestamp", GetCurrentTimestamp()} + }; + SendToSession(session_id, response); +} - // Unload distant chunks for each player - float unloadDistance = GetWorldConfig().chunkUnloadDistance; - for (const auto& pos : playerPositions) { - LogicWorld::GetInstance().UnloadDistantChunks(pos, unloadDistance); - } - } - - // 3. Update dynamic world objects (if any) - // e.g., moving platforms, floating items, environmental animations - // LogicWorld::GetInstance().UpdateDynamicObjects(deltaTime); - - // 4. Update weather system - // WeatherSystem::GetInstance().Update(deltaTime); - - // (Optional) Log or debug info using deltaTime - Logger::Trace("UpdateWorld processed with deltaTime = {} ms", deltaTime * 1000.0f); -} - -// =============== Data Management =============== -bool GameLogic::LoadGameData() { - Logger::Debug("Loading game data"); - return true; -} - -void GameLogic::SaveGameState() { - LogicCore::SaveGameState(); - - try { - nlohmann::json gameState = { - {"server_time", GetCurrentTimestamp()}, - {"world_seed", GetWorldConfig().seed}, - {"active_chunks", LogicWorld::GetInstance().GetActiveChunkCount()}, - {"active_npcs", 0}, // TODO: expose from LogicEntity if needed - {"world_config", { - {"view_distance", GetWorldConfig().viewDistance}, - {"chunk_size", GetWorldConfig().chunkSize}, - {"terrain_scale", GetWorldConfig().terrainScale} - }} - }; - - // Use DbManager instead of direct CitusClient - if (!DbManager::GetInstance().SaveGameState("current_game", gameState)) { - Logger::Error("Failed to save game state: DbManager returned false"); - } else { - Logger::Debug("Game state saved"); - } - } catch (const std::exception& e) { - Logger::Error("Failed to save game state: {}", e.what()); - } -} - -void GameLogic::SaveChunkData() { - LogicWorld::GetInstance().SaveChunkData(); -} - -void GameLogic::CleanupOldData() { - LogicCore::CleanupOldData(); - //Logger::Debug("Cleaning up game data"); -} - -// =============== World maintenance =============== -void GameLogic::PerformMaintenance() { - //Logger::Debug("Performing game world maintenance"); - - // Clean up old data - CleanupOldData(); - - // Save chunk data - SaveChunkData(); - - // Check database health - if (databaseBackend_) { - bool healthy = databaseBackend_->CheckHealth(); - if (!healthy) { - Logger::Warn("Database health check failed, attempting to reconnect"); - databaseBackend_->ReconnectAll(); - } - } - - //Logger::Debug("Game world maintenance complete"); -} - -// =============== IPC message handling =============== -void GameLogic::HandleIPCMessage(const nlohmann::json& message) { - try { - std::string msgType = message.value("type", ""); - auto it = WebSocketProtocol::IPCMessageTypes.find(msgType); - if (it == WebSocketProtocol::IPCMessageTypes.end()) { - Logger::Warn("Unknown IPC message type: {}", msgType); - return; - } - int typeCode = it->second; - switch (typeCode) { - case 1: // welcome - //Logger::Info("Received welcome message from master: {}", message.value("message", "")); - break; - case 2: // heartbeat - //int count = message.value("count", 0); - //Logger::Debug("Received heartbeat #{} from master", count); - break; - case 3: // broadcast - if (message.contains("data")) { - BroadcastToAllPlayers(message["data"]); - } - break; - case 4: // shutdown - Logger::Info("Received shutdown command from master"); - Shutdown(); - break; - case 5: // reload_config - Logger::Info("Received config reload command from master"); - // TODO: Reload configuration if needed - break; - default: - Logger::Warn("Unhandled IPC message code: {}", typeCode); - break; - } - } catch (const std::exception& e) { - Logger::Error("Error handling IPC message: {}", e.what()); - } -} - -// =============== Helper broadcast methods =============== void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { if (!connectionManager_) { Logger::Warn("Cannot broadcast: ConnectionManager not available"); return; } - try { - // Serialize the message to string std::string serialized = message.dump(); - - // Get all active sessions auto sessions = connectionManager_->GetAllSessions(); - - if (sessions.empty()) { - //Logger::Debug("No active sessions to broadcast to"); - return; - } - + if (sessions.empty()) return; Logger::Debug("Broadcasting to {} player(s): {}", sessions.size(), message.dump()); - - // Send message to each session for (auto& session : sessions) { if (session && session->IsConnected()) { try { @@ -1215,9 +862,7 @@ void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { } } } - Logger::Info("Successfully broadcasted to {} player(s)", sessions.size()); - } catch (const std::exception& e) { Logger::Error("Error broadcasting to all players: {}", e.what()); } @@ -1228,20 +873,11 @@ void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vec Logger::Warn("Cannot broadcast binary: ConnectionManager not available"); return; } - try { - // Get all active sessions auto sessions = connectionManager_->GetAllSessions(); - - if (sessions.empty()) { - //Logger::Debug("No active sessions to broadcast binary to"); - return; - } - + if (sessions.empty()) return; Logger::Debug("Broadcasting binary message type {} to {} player(s)", messageType, sessions.size()); - - // Send binary message to each session for (auto& session : sessions) { if (session && session->IsConnected()) { try { @@ -1252,212 +888,233 @@ void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vec } } } - } catch (const std::exception& e) { Logger::Error("Error broadcasting binary to all players: {}", e.what()); } } -void GameLogic::BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message) { +void GameLogic::BroadcastToPlayers(const std::vector& session_ids, const nlohmann::json& message) { if (!connectionManager_) { Logger::Warn("Cannot broadcast: ConnectionManager not available"); return; } - try { std::string serialized = message.dump(); int sentCount = 0; - - for (uint64_t sessionId : sessionIds) { - auto session = connectionManager_->GetSession(sessionId); + for (uint64_t session_id : session_ids) { + auto session = connectionManager_->GetSession(session_id); if (session && session->IsConnected()) { try { session->SendRaw(serialized); sentCount++; } catch (const std::exception& e) { Logger::Error("Failed to send message to session {}: {}", - sessionId, e.what()); + session_id, e.what()); } } } - if (sentCount > 0) { Logger::Debug("Broadcasted to {} specific player(s)", sentCount); } - } catch (const std::exception& err) { Logger::Error("Error broadcasting to specific players: {}", err.what()); } } -void GameLogic::BroadcastPlayerUpdates() { - auto sessions = connectionManager_->GetAllSessions(); - for (auto& session : sessions) { - if (!session->IsAuthenticated()) continue; - - uint64_t localPlayerId = session->GetPlayerId(); - auto localPlayer = GetPlayer(localPlayerId); - if (!localPlayer) continue; - - glm::vec3 localPos = localPlayer->GetPosition(); - - // Collect nearby players - std::vector> nearby = - PlayerManager::GetInstance().GetPlayersInRadius(localPos, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); - - // Build payload - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt32(static_cast(nearby.size())); - for (auto& player : nearby) { - writer.WriteUInt32(player->GetId()); - writer.WriteFloat(player->GetPosition().x); - writer.WriteFloat(player->GetPosition().y); - writer.WriteFloat(player->GetPosition().z); - writer.WriteFloat(player->GetRotation().y); - writer.WriteFloat(player->GetHealth()); - writer.WriteFloat(player->GetMaxHealth()); - writer.WriteString(player->GetName()); +void GameLogic::HandleIPCMessage(const nlohmann::json& message) { + try { + std::string msgType = message.value("type", ""); + auto it = IPCMessageTypes.find(msgType); + if (it == IPCMessageTypes.end()) { + Logger::Warn("Unknown IPC message type: {}", msgType); + return; } - - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE, writer.GetBuffer()); + int typeCode = it->second; + switch (typeCode) { + case 1: + break; + case 2: + break; + case 3: + if (message.contains("data")) { + BroadcastToAllPlayers(message["data"]); + } + break; + case 4: + Logger::Info("Received shutdown command from master"); + Shutdown(); + break; + case 5: + Logger::Info("Received config reload command from master"); + break; + default: + Logger::Warn("Unhandled IPC message code: {}", typeCode); + break; + } + } catch (const std::exception& e) { + Logger::Error("Error handling IPC message: {}", e.what()); } } -void GameLogic::BroadcastPlayerState(uint64_t playerId, const ServerState& state) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(playerId); - writer.WriteVector3(state.position); - writer.WriteVector3(state.rotation); - writer.WriteVector3(state.velocity); - writer.WriteUInt64(state.timestamp); - BroadcastToNearbyPlayers(state.position, BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer(), ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +void GameLogic::PerformMaintenance() { + CleanupOldData(); + SaveChunkData(); + if (databaseBackend_) { + bool healthy = databaseBackend_->CheckHealth(); + if (!healthy) { + Logger::Warn("Database health check failed, attempting to reconnect"); + databaseBackend_->ReconnectAll(); + } + } } -void GameLogic::BroadcastPlayerSpawn(uint64_t playerId) { - auto player = GetPlayer(playerId); - if (!player) return; - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(playerId); - writer.WriteString(player->GetName()); - writer.WriteVector3(player->GetPosition()); - writer.WriteFloat(player->GetRotation().y); // yaw - writer.WriteFloat(player->GetHealth()); - writer.WriteFloat(player->GetMaxHealth()); - BroadcastToNearbyPlayers(player->GetPosition(), - BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, - writer.GetBuffer(), - ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +bool GameLogic::LoadGameData() { + Logger::Debug("Loading game data"); + return true; } -void GameLogic::BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(playerId); - BroadcastToNearbyPlayers(lastPosition, - BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, - writer.GetBuffer(), - ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +void GameLogic::SaveChunkData() { + LogicWorld::GetInstance().SaveChunkData(); } -void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { - if (!connectionManager_) return; - - auto& pm = PlayerManager::GetInstance(); - auto nearby = pm.GetPlayersInRadius(position, radius); +nlohmann::json GameLogic::PlayerUpdateToJson(uint64_t player_id, const glm::vec3& pos, float yaw, float health, float maxHealth, const std::string& name) { + return { + {"type", "player_update"}, + {"player_id", player_id}, + {"position", {pos.x, pos.y, pos.z}}, + {"yaw", yaw}, + {"health", health}, + {"max_health", maxHealth}, + {"name", name}, + {"timestamp", GetCurrentTimestamp()} + }; +} - for (auto& player : nearby) { - uint64_t sessionId = pm.GetSessionIdByPlayerId(player->GetId()); - if (sessionId != 0) { - auto session = connectionManager_->GetSession(sessionId); - if (session && session->IsConnected()) { - session->Send(message); - } - } - } +nlohmann::json GameLogic::PlayerPositionToJson(const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + uint64_t player_id = reader.ReadUInt64(); + glm::vec3 pos = reader.ReadVector3(); + glm::vec3 vel = reader.ReadVector3(); + uint64_t timestamp = reader.ReadUInt64(); + return { + {"type", "player_position"}, + {"player_id", player_id}, + {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, + {"vx", vel.x}, {"vy", vel.y}, {"vz", vel.z}, + {"timestamp", timestamp} + }; } -void GameLogic::BroadcastPlayerSpawnJson(uint64_t playerId) { - auto player = GetPlayer(playerId); - if (!player) return; - nlohmann::json msg = { - {"type", "player_spawn"}, - {"player_id", playerId}, - {"name", player->GetName()}, - {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, - {"yaw", player->GetRotation().y}, - {"health", player->GetHealth()}, - {"max_health", player->GetMaxHealth()}, +nlohmann::json GameLogic::PlayerUpdateToJson(const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + uint32_t count = reader.ReadUInt32(); + nlohmann::json playersArray = nlohmann::json::array(); + for (uint32_t i = 0; i < count; ++i) { + uint32_t pid = reader.ReadUInt32(); + float x = reader.ReadFloat(), y = reader.ReadFloat(), z = reader.ReadFloat(); + float yaw = reader.ReadFloat(); + float health = reader.ReadFloat(), maxHealth = reader.ReadFloat(); + std::string name = reader.ReadString(); + playersArray.push_back({ + {"id", pid}, + {"x", x}, {"y", y}, {"z", z}, + {"yaw", yaw}, + {"health", health}, + {"max_health", maxHealth}, + {"name", name} + }); + } + return { + {"type", "player_update"}, + {"players", playersArray}, {"timestamp", GetCurrentTimestamp()} }; - BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); } -void GameLogic::BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition) { - nlohmann::json msg = { - {"type", "player_despawn"}, - {"player_id", playerId}, - {"timestamp", GetCurrentTimestamp()} +nlohmann::json GameLogic::EntitySpawnToJson(const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + uint64_t entityId = reader.ReadUInt64(); + uint8_t type = reader.ReadUInt8(); + std::string name = reader.ReadString(); + glm::vec3 pos = reader.ReadVector3(); + float yaw = reader.ReadFloat(); + uint64_t timestamp = reader.ReadUInt64(); + return { + {"type", "entity_spawn"}, + {"entity_id", entityId}, + {"entity_type", type}, + {"name", name}, + {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, + {"yaw", yaw}, + {"timestamp", timestamp} }; - BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); } -void GameLogic::BroadcastPlayerUpdatesJson() { - auto sessions = connectionManager_->GetAllSessions(); - for (auto& session : sessions) { - if (!session->IsAuthenticated()) continue; - uint64_t localPlayerId = session->GetPlayerId(); - auto localPlayer = GetPlayer(localPlayerId); - if (!localPlayer) continue; - glm::vec3 localPos = localPlayer->GetPosition(); - auto nearby = PlayerManager::GetInstance().GetPlayersInRadius(localPos, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); - nlohmann::json playersArray = nlohmann::json::array(); - for (auto& player : nearby) { - playersArray.push_back({ - {"id", player->GetId()}, - {"name", player->GetName()}, - {"x", player->GetPosition().x}, - {"y", player->GetPosition().y}, - {"z", player->GetPosition().z}, - {"yaw", player->GetRotation().y}, - {"health", player->GetHealth()}, - {"max_health", player->GetMaxHealth()} - }); - } - nlohmann::json msg = { - {"type", "player_update"}, - {"players", playersArray}, - {"timestamp", GetCurrentTimestamp()} - }; - session->Send(msg); - } +nlohmann::json GameLogic::EntityUpdateToJson(const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + uint64_t entityId = reader.ReadUInt64(); + glm::vec3 pos = reader.ReadVector3(); + glm::vec3 rot = reader.ReadVector3(); + glm::vec3 vel = reader.ReadVector3(); + uint64_t timestamp = reader.ReadUInt64(); + return { + {"type", "entity_update"}, + {"entity_id", entityId}, + {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, + {"rx", rot.x}, {"ry", rot.y}, {"rz", rot.z}, + {"vx", vel.x}, {"vy", vel.y}, {"vz", vel.z}, + {"timestamp", timestamp} + }; } -void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(entityId); - writer.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); +nlohmann::json GameLogic::EntityDespawnToJson(const std::vector& data) { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + uint64_t entityId = reader.ReadUInt64(); + uint64_t timestamp = reader.ReadUInt64(); + return { + {"type", "entity_despawn"}, + {"entity_id", entityId}, + {"timestamp", timestamp} + }; } -void GameLogic::HandleAuthentication(uint64_t sessionId, const std::vector& data) { +nlohmann::json GameLogic::ChunkDataToJson(const std::vector& data) { BinaryProtocol::BinaryReader reader(data.data(), data.size()); - std::string username = reader.ReadString(); - std::string password = reader.ReadString(); + int chunk_x = reader.ReadInt32(); + int chunk_z = reader.ReadInt32(); + uint8_t lod = reader.ReadUInt8(); + nlohmann::json chunk_json = reader.ReadJson(); + uint64_t timestamp = reader.ReadUInt64(); + return { + {"type", "world_chunk"}, + {"chunk_x", chunk_x}, + {"chunk_z", chunk_z}, + {"lod", lod}, + {"data", chunk_json}, + {"timestamp", timestamp} + }; +} + +void GameLogic::OnAuthentication(const AuthenticationData& data) { auto& pm = PlayerManager::GetInstance(); bool authenticated = false; std::string message; - if (pm.PlayerExists(username)) { - if (password.empty() || pm.AuthenticatePlayer(username, password)) { + uint64_t player_id = 0; + if (pm.PlayerExists(data.username)) { + if (data.password.empty() || pm.AuthenticatePlayer(data.username, data.password)) { authenticated = true; - message = "Welcome back, " + username; + message = "Welcome back, " + data.username; + player_id = pm.GetPlayerByUsername(data.username)->GetId(); } else { message = "Invalid password"; } } else { - if (password.empty()) { - auto player = pm.CreatePlayer(username); + if (data.password.empty()) { + auto player = pm.CreatePlayer(data.username); if (player) { authenticated = true; - message = "Welcome, " + username; + message = "Welcome, " + data.username; + player_id = player->GetId(); } else { message = "Failed to create player"; } @@ -1466,82 +1123,462 @@ void GameLogic::HandleAuthentication(uint64_t sessionId, const std::vectorGetId()); - auto session = connectionManager_->GetSession(sessionId); - if (session) { - session->SetPlayerId(player->GetId()); - session->Authenticate(password); - } - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt8(1); - writer.WriteUInt64(static_cast(player->GetId())); - writer.WriteString(message); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); + pm.PlayerConnected(data.session_id, player_id); + auto session = connectionManager_->GetSession(data.session_id); + if (session) { + session->SetPlayerId(player_id); + session->Authenticate(data.password); + } + } + if (sendAuthResponseCb_) { + sendAuthResponseCb_(data.session_id, authenticated, message, player_id); + } else { + Logger::Error("No sendAuthResponseCb_ set in GameLogic"); + } +} + +void GameLogic::OnChunkRequest(const ChunkRequestData& req) { + auto chunk = GetOrCreateChunk(req.chunk_x, req.chunk_z); + if (!chunk) { + Logger::Error("Failed to get chunk ({},{}) for session {}", req.chunk_x, req.chunk_z, req.session_id); + return; + } + ChunkData resp; + resp.chunk_x = req.chunk_x; + resp.chunk_z = req.chunk_z; + resp.lod = req.lod; + resp.chunk_json = chunk->Serialize(); + resp.timestamp = GetCurrentTimestamp(); + if (sendChunkCb_) { + sendChunkCb_(req.session_id, resp); + } else { + Logger::Error("No sendChunkCb_ set in GameLogic"); + } +} + +void GameLogic::OnPlayerPosition(const PlayerPositionData& data) { + auto player = GetPlayer(data.player_id); + if (!player) return; + float collisionRadius = 0.5f; + CollisionResult collision = CheckCollision(data.position, collisionRadius, data.player_id); + glm::vec3 finalPos = data.position; + if (collision.collided) { + finalPos += collision.resolution; + } + player->SetPosition(finalPos); + GenerateWorldAroundPlayer(data.player_id, finalPos); + if (broadcastPlayerPositionCb_) { + PlayerPositionData broadcastData = data; + broadcastData.position = finalPos; + broadcastPlayerPositionCb_(broadcastData, 100.0f); + } + FirePythonEvent("player_move_3d", { + {"player_id", data.player_id}, + {"x", finalPos.x}, {"y", finalPos.y}, {"z", finalPos.z}, + {"session_id", data.session_id} + }); +} + +void GameLogic::OnPlayerState(const PlayerStateData& data) { + uint64_t player_id = data.player_id; + auto player = GetPlayer(player_id); + if (!player) return; + ClientInput input; + input.input_id = data.input_id; + input.position = data.position; + input.velocity = data.velocity; + input.rotation = data.rotation; + input.on_ground = data.on_ground; + input.jumping = data.jumping; + input.crouching = data.crouching; + input.sprinting = data.sprinting; + input.timestamp = data.timestamp; + { + std::lock_guard lock(predictionMutex_); + playerPrediction_[player_id].StoreClientInput(input); + } + PredictionSystem* pred = &playerPrediction_[player_id]; + auto currentTime = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + ServerState authState; + authState.last_processed_input = 0; + authState.timestamp = currentTime; + authState.position = player->GetPosition(); + authState.velocity = player->GetVelocity(); + authState.rotation = player->GetRotation(); + authState.on_ground = player->IsOnGround(); + auto unprocessed = pred->GetUnprocessedInputs(authState.last_processed_input); + if (!unprocessed.empty()) { + float deltaTime = 1.0f / 30.0f; + authState = pred->SimulateMovement(authState, unprocessed, deltaTime); + } + player->SetPosition(authState.position); + player->SetVelocity(authState.velocity); + player->SetRotation(authState.rotation); + player->SetOnGround(authState.on_ground); + if (playerStateCb_) { + PlayerStateData correctedState = data; + correctedState.position = authState.position; + correctedState.velocity = authState.velocity; + correctedState.rotation = authState.rotation; + correctedState.on_ground = authState.on_ground; + correctedState.timestamp = currentTime; + playerStateCb_(correctedState); + } else { + Logger::Error("No playerStateCb_ set in GameLogic"); + } + static float correctionThreshold = 0.5f; + if (glm::distance(authState.position, player->GetPosition()) > correctionThreshold) { + SendPositionCorrection(data.session_id, authState.position, authState.velocity); + } +} + +void GameLogic::SetSendAuthenticationResponseCallback(std::function cb) { + sendAuthResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendChunkCallback(std::function cb) { + sendChunkCb_ = std::move(cb); +} + +void GameLogic::SetPlayerStateCallback(std::function cb) { + playerStateCb_ = std::move(cb); +} + +void GameLogic::SetBroadcastPlayerPositionCallback(std::function cb) { + broadcastPlayerPositionCb_ = std::move(cb); +} + +void GameLogic::SetDatabaseService(DatabaseService* dbService) { + dbService_ = dbService; +} + +void GameLogic::SetConnectionManager(std::shared_ptr connMgr) { + connectionManager_ = std::move(connMgr); + Logger::Trace("ConnectionManager set for GameLogic"); +} + +void GameLogic::SetDatabaseBackend(std::unique_ptr backend) { + databaseBackend_ = std::move(backend); + Logger::Trace("Database backend set for GameLogic"); +} + +DatabaseBackend* GameLogic::GetDatabaseBackend() const { + return databaseBackend_.get(); +} + +void GameLogic::FirePythonEvent(const std::string& eventName, const nlohmann::json& data) { + if (pythonEnabled_) { + PythonScripting::GetInstance().FireEvent(eventName, data); + } +} + +nlohmann::json GameLogic::CallPythonFunction(const std::string& moduleName, + const std::string& functionName, + const nlohmann::json& args) { + if (!pythonEnabled_) return nlohmann::json(); + return PythonScripting::GetInstance().CallFunctionWithResult(moduleName, functionName, args); +} + +void GameLogic::RegisterPythonEventHandlers() { + if (!pythonEnabled_) return; + auto& scripting = PythonScripting::GetInstance(); + scripting.RegisterEventHandler("player_login", "game_events", "on_player_login"); + scripting.RegisterEventHandler("player_move", "game_events", "on_player_move"); + scripting.RegisterEventHandler("player_attack", "game_events", "on_player_attack"); + scripting.RegisterEventHandler("player_level_up", "game_events", "on_player_level_up"); + scripting.RegisterEventHandler("player_death", "game_events", "on_player_death"); + scripting.RegisterEventHandler("player_respawn", "game_events", "on_player_respawn"); + scripting.RegisterEventHandler("custom_event", "game_events", "on_custom_event"); + Logger::Info("Python event handlers registered"); +} + +void GameLogic::SaveGameState() { + try { + nlohmann::json gameState = { + {"server_time", GetCurrentTimestamp()}, + {"world_seed", GetWorldConfig().seed}, + {"active_chunks", LogicWorld::GetInstance().GetActiveChunkCount()}, + {"active_npcs", 0}, + {"world_config", { + {"view_distance", GetWorldConfig().viewDistance}, + {"chunk_size", GetWorldConfig().chunkSize}, + {"terrain_scale", GetWorldConfig().terrainScale} + }} + }; + if (!DbManager::GetInstance().SaveGameState("current_game", gameState)) { + Logger::Error("Failed to save game state: DbManager returned false"); } else { - authenticated = false; - message = "Internal error"; - Logger::Warn("GetPlayerByUsername('{}') return null", username); + Logger::Debug("Game state saved"); } + } catch (const std::exception& e) { + Logger::Error("Failed to save game state: {}", e.what()); } - if (!authenticated) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt8(0); - writer.WriteUInt64(0); - writer.WriteString(message); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); +} + +void GameLogic::CleanupOldData() { +} + +void GameLogic::ProcessGameTick(float deltaTime) { + auto& world = LogicWorld::GetInstance(); + world.UpdateEntities(deltaTime); + ProcessEvents(); + FirePythonEvent("game_tick", {{"delta_time", deltaTime}}); +} + +void GameLogic::SpawnEnemies() { +} + +void GameLogic::RespawnNPCs() { +} + +void GameLogic::SpawnResources() { +} + +void GameLogic::SaveLoop() { + Logger::Info("Save loop started"); + while (running_) { + std::unique_lock lock(saveMutex_); + saveCV_.wait_for(lock, std::chrono::minutes(5), [this] { return !running_; }); + if (!running_) break; + PlayerManager::GetInstance().SaveAllPlayers(); + SaveGameState(); + CleanupOldData(); } + Logger::Info("Save loop stopped"); } -void GameLogic::HandleAuthentication(uint64_t sessionId, const std::string& username, const std::string& password) { - bool authenticated = false; - std::string message; +void GameLogic::HandleLogin(uint64_t session_id, const nlohmann::json& data) { + if (!data.contains("username") || !data["username"].is_string() || + !data.contains("password") || !data["password"].is_string()) { + SendError(session_id, "Missing or invalid username/password", 400); + return; + } + std::string username = data["username"]; + std::string password = data["password"]; auto& pm = PlayerManager::GetInstance(); - if (pm.PlayerExists(username)) { - if (password.empty() || pm.AuthenticatePlayer(username, password)) { - authenticated = true; - message = "Welcome back, " + username; + bool authenticated = pm.AuthenticatePlayer(username, password); + if (!authenticated) { + SendError(session_id, "Invalid credentials", 401); + return; + } + auto player = pm.GetPlayerByUsername(username); + if (!player) { + SendError(session_id, "Player data unavailable", 500); + return; + } + uint64_t player_id = player->GetId(); + OnPlayerConnected(session_id, player_id); + auto& world = LogicWorld::GetInstance(); + world.AddEntity(player); + FirePythonEvent("player_login", {{"player_id", player_id}, {"username", username}}); + SendSuccess(session_id, "Login successful", nlohmann::json{ + {"player_id", player_id}, + {"position", player->JsonGetPosition()}, + {"health", player->GetHealth()}, + {"max_health", player->GetMaxHealth()}, + {"level", player->GetLevel()}, + {"experience", player->GetExperience()}, + {"inventory", player->JsonGetInventory()} + }); +} + +void GameLogic::HandleChat(uint64_t session_id, const nlohmann::json& data) { + uint64_t player_id = GetPlayerIdBySession(session_id); + if (player_id == 0) { + SendError(session_id, "Player not authenticated", 401); + return; + } + if (!data.contains("message") || !data["message"].is_string()) { + SendError(session_id, "Missing or invalid message", 400); + return; + } + std::string chatMessage = data["message"]; + if (chatMessage.empty()) { + SendError(session_id, "Message cannot be empty", 400); + return; + } + auto player = PlayerManager::GetInstance().GetPlayer(player_id); + if (!player) { + SendError(session_id, "Player not found", 500); + return; + } + Logger::Info("Chat from player {} ({}): {}", player_id, player->GetName(), chatMessage); + nlohmann::json chatJson = { + {"type", "chat"}, + {"player_id", player_id}, + {"name", player->GetName()}, + {"message", chatMessage}, + {"timestamp", GetCurrentTimestamp()} + }; + std::string channel = data.value("channel", "local"); + if (channel == "global") { + auto& connMgr = ConnectionManager::GetInstance(); + auto sessions = connMgr.GetAllSessions(); + for (auto& session : sessions) { + if (session && session->IsConnected()) + session->Send(chatJson); + } + } else { + PlayerManager::GetInstance().BroadcastToNearbyPlayers(player_id, chatJson); + } + FirePythonEvent("player_chat", {{"player_id", player_id}, {"message", chatMessage}, {"channel", channel}}); +} + +void GameLogic::HandleCombat(uint64_t session_id, const nlohmann::json& data) { + uint64_t player_id = GetPlayerIdBySession(session_id); + if (player_id == 0) { + SendError(session_id, "Player not authenticated", 401); + return; + } + if (!data.contains("target_id") || !data["target_id"].is_number_integer()) { + SendError(session_id, "Missing or invalid target_id", 400); + return; + } + uint64_t target_id = data["target_id"]; + std::string attack_type = data.value("attack_type", "melee"); + auto player = PlayerManager::GetInstance().GetPlayer(player_id); + if (!player) { + SendError(session_id, "Player not found", 500); + return; + } + auto& world = LogicWorld::GetInstance(); + auto target = world.GetEntity(target_id); + if (!target) { + SendError(session_id, "Target not found", 404); + return; + } + float distance = glm::distance(player->GetPosition(), target->GetPosition()); + if (distance > player->GetAttackRange()) { + SendError(session_id, "Target out of range", 400); + return; + } + int damage = player->CalculateDamage(attack_type); + target->TakeDamage(damage, player_id); + FirePythonEvent("player_attack", {{"player_id", player_id}, {"target_id", target_id}, {"damage", damage}, {"attack_type", attack_type}}); + SendSuccess(session_id, "Combat resolved", nlohmann::json{ + {"damage", damage}, + {"target_health", target->GetHealth()}, + {"target_defeated", target->IsDead()} + }); +} + +void GameLogic::HandleQuest(uint64_t session_id, const nlohmann::json& data) { + uint64_t player_id = GetPlayerIdBySession(session_id); + if (player_id == 0) { + SendError(session_id, "Player not authenticated", 401); + return; + } + if (!data.contains("quest_id") || !data["quest_id"].is_number_integer()) { + SendError(session_id, "Missing or invalid quest_id", 400); + return; + } + uint64_t quest_id = data["quest_id"]; + if (!data.contains("action") || !data["action"].is_string()) { + SendError(session_id, "Missing or invalid action", 400); + return; + } + std::string action = data["action"]; + auto player = PlayerManager::GetInstance().GetPlayer(player_id); + if (!player) { + SendError(session_id, "Player not found", 500); + return; + } + auto& questManager = QuestManager::GetInstance(); + nlohmann::json result; + if (action == "start") { + if (questManager.CanStartQuest(player_id, quest_id)) { + questManager.StartQuest(player_id, quest_id); + result["status"] = "started"; } else { - message = "Invalid password"; + SendError(session_id, "Cannot start quest", 400); + return; + } + } else if (action == "update") { + std::string objective = data.value("objective", ""); + int progress = data.value("progress", 1); + questManager.UpdateObjective(player_id, quest_id, objective, progress); + result["status"] = "updated"; + } else if (action == "complete") { + if (questManager.CanCompleteQuest(player_id, quest_id)) { + auto rewards = questManager.CompleteQuest(player_id, quest_id); + result["status"] = "completed"; + result["rewards"] = rewards; + } else { + SendError(session_id, "Quest cannot be completed yet", 400); + return; } } else { - if (password.empty()) { - auto player = pm.CreatePlayer(username); - if (player) { - authenticated = true; - message = "Welcome, " + username; + SendError(session_id, "Unknown action: " + action, 400); + return; + } + FirePythonEvent("player_quest", {{"player_id", player_id}, {"quest_id", quest_id}, {"action", action}}); + SendSuccess(session_id, "Quest action processed", result); +} + +void GameLogic::GameLoop() { + Logger::Info("Game loop started"); + auto lastUpdate = std::chrono::steady_clock::now(); + while (!instanceMutex_.try_lock()) { + try { + auto startTime = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); + auto deltaTimeMillis = std::chrono::duration_cast(now - lastUpdate); + float deltaTime = deltaTimeMillis.count() / 1000.0f; + lastUpdate = now; + UpdateWorld(deltaTime); + LogicEntity::GetInstance().UpdateNPCs(deltaTime); + LogicEntity::GetInstance().UpdateCollisions(deltaTime); + ProcessGameTick(deltaTime); + ProcessEvents(); + auto endTime = std::chrono::steady_clock::now(); + auto processingTime = std::chrono::duration_cast(endTime - startTime).count(); + if (processingTime < gameLoopInterval_.count()) { + std::unique_lock lock(gameLoopMutex_); + gameLoopCV_.wait_for(lock, + gameLoopInterval_ - std::chrono::milliseconds(processingTime), + [this] { return !instance_; }); } else { - message = "Failed to create player"; + Logger::Warn("Game loop lagging: {}ms", processingTime); } - } else { - message = "Player does not exist"; + } catch (const std::exception& e) { + Logger::Error("Error in game loop: {}", e.what()); } } - if (authenticated) { - auto player = pm.GetPlayerByUsername(username); - if (player) { - pm.PlayerConnected(sessionId, player->GetId()); - auto session = connectionManager_->GetSession(sessionId); - if (session) { - session->SetPlayerId(player->GetId()); - session->Authenticate(password); + instanceMutex_.unlock(); + Logger::Info("Game loop stopped"); +} + +void GameLogic::SpawnerLoop() { + Logger::Info("Spawner loop started"); + LogicCore::SpawnerLoop(); + Logger::Info("Spawner loop stopped"); +} + +void GameLogic::UpdateWorld(float deltaTime) { + static float worldTime = 0.0f; + worldTime += deltaTime; + const float dayLength = 1800.0f; + float timeOfDay = fmod(worldTime, dayLength) / dayLength; + LogicWorld::GetInstance().SetTimeOfDay(timeOfDay); + static float timeSinceLastUnload = 0.0f; + timeSinceLastUnload += deltaTime; + const float unloadInterval = 1.0f; + if (timeSinceLastUnload >= unloadInterval) { + timeSinceLastUnload = 0.0f; + std::vector playerPositions; + { + std::lock_guard lock(sessionMutex_); + for (const auto& [player_id, session_id] : playerToSessionMap_) { + if (auto player = GetPlayer(player_id)) { + playerPositions.push_back(player->GetPosition()); + } } - } else { - authenticated = false; - message = "Internal error"; } - } - nlohmann::json response = { - {"type", "authentication_response"}, - {"success", authenticated}, - {"message", message} - }; - if (authenticated) { - auto player = pm.GetPlayerByUsername(username); - if (player) { - response["player_id"] = player->GetId(); + float unloadDistance = GetWorldConfig().unloadDistance; + for (const auto& pos : playerPositions) { + LogicWorld::GetInstance().UnloadDistantChunks(pos, unloadDistance); } } - SendToSession(sessionId, response); + Logger::Trace("UpdateWorld processed with deltaTime = {} ms", deltaTime * 1000.0f); } diff --git a/src/game/LogicCore.cpp b/src/game/LogicCore.cpp index 921119e..1f39470 100644 --- a/src/game/LogicCore.cpp +++ b/src/game/LogicCore.cpp @@ -1,19 +1,12 @@ #include "game/LogicCore.hpp" -// =============== Static Members =============== std::mutex LogicCore::instanceMutex_; LogicCore* LogicCore::instance_ = nullptr; -// =============== Constructor and Destructor =============== LogicCore::LogicCore() : running_(false), - playerManager_(PlayerManager::GetInstance()), - dbManager_(DbManager::GetInstance()), - pythonScripting_(PythonScripting::GetInstance()), - scriptHotReloader_(nullptr), - pythonEnabled_(false) -{ - + dbManager_(DbManager::GetInstance()), + pythonEnabled_(false) { rng_.seed(std::random_device()()); Logger::Debug("LogicCore initialized"); } @@ -24,7 +17,6 @@ LogicCore::~LogicCore() { } } -// =============== Singleton Access =============== LogicCore& LogicCore::GetInstance() { std::lock_guard lock(instanceMutex_); if (!instance_) { @@ -33,113 +25,69 @@ LogicCore& LogicCore::GetInstance() { return *instance_; } -// =============== Initialization and Shutdown =============== void LogicCore::Initialize() { if (running_) { Logger::Warn("LogicCore already initialized"); return; } - Logger::Info("Initializing LogicCore..."); - auto& config = ConfigManager::GetInstance(); - - // Register default handlers RegisterDefaultHandlers(); - - // Initialize Python scripting if enabled pythonEnabled_ = config.GetBool("python.enabled", false); - if (pythonEnabled_) { - if (pythonScripting_.Initialize()) { - Logger::Info("Python scripting initialized"); - RegisterPythonEventHandlers(); - - bool hotReloadEnabled = config.GetBool("python.hot_reload", true); - if (hotReloadEnabled) { - std::string scriptDir = config.GetString("python.script_dir", "./scripts"); - scriptHotReloader_ = std::make_unique(scriptDir, 2000); - scriptHotReloader_->Start(); - } - } else { - Logger::Warn("Failed to initialize Python scripting"); - pythonEnabled_ = false; - } - } - - // Start threads running_ = true; gameLoopThread_ = RAIIThread([this]() { GameLoop(); }); spawnerThread_ = RAIIThread([this]() { SpawnerLoop(); }); saveThread_ = RAIIThread([this]() { SaveLoop(); }); - Logger::Info("LogicCore initialized successfully"); } void LogicCore::Shutdown() { - if (!running_) { - return; - } - + if (!running_) return; Logger::Info("Shutting down LogicCore..."); - - if (scriptHotReloader_) { - scriptHotReloader_->Stop(); - scriptHotReloader_.reset(); - } - - if (pythonEnabled_) { - pythonScripting_.Shutdown(); - } - running_ = false; - - // Notify all condition variables gameLoopCV_.notify_all(); spawnerCV_.notify_all(); saveCV_.notify_all(); - - // Stop threads gameLoopThread_.Stop(); spawnerThread_.Stop(); saveThread_.Stop(); - - // Cleanup { std::lock_guard lock(handlersMutex_); messageHandlers_.clear(); binaryHandlers_.clear(); } - { std::lock_guard lock(rateLimitMutex_); rateLimits_.clear(); } - { std::lock_guard lock(sessionMutex_); sessionToPlayerMap_.clear(); playerToSessionMap_.clear(); } - Logger::Info("LogicCore shutdown complete"); } -// =============== Message Handling =============== +bool LogicCore::IsRunning() const { + return running_; +} + +float LogicCore::CalculateDistance(const glm::vec3& a, const glm::vec3& b) { + return glm::distance(a, b); +} + void LogicCore::HandleMessage(uint64_t sessionId, const nlohmann::json& message) { if (!message.contains("type") || !message["type"].is_string()) { SendError(sessionId, "Invalid message format"); return; } - std::string messageType = message["type"]; Logger::Debug("Handling message type '{}' from session {}", messageType, sessionId); - try { if (!CheckRateLimit(sessionId)) { SendError(sessionId, "Rate limit exceeded", 429); return; } - std::lock_guard lock(handlersMutex_); auto it = messageHandlers_.find(messageType); if (it != messageHandlers_.end()) { @@ -155,7 +103,6 @@ void LogicCore::HandleMessage(uint64_t sessionId, const nlohmann::json& message) void LogicCore::HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, const std::vector& data) { Logger::Debug("Handling binary message type {} from session {}", messageType, sessionId); - try { if (!CheckRateLimit(sessionId)) { BinaryProtocol::BinaryWriter writer; @@ -165,7 +112,6 @@ void LogicCore::HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, co SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_ERROR, writer.GetBuffer()); return; } - std::lock_guard lock(handlersMutex_); auto it = binaryHandlers_.find(messageType); if (it != binaryHandlers_.end()) { @@ -192,13 +138,6 @@ void LogicCore::HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, co void LogicCore::RegisterHandler(const std::string& messageType, MessageHandler handler) { std::lock_guard lock(handlersMutex_); messageHandlers_[messageType] = handler; - Logger::Debug("Registered handler for: {}", messageType); -} - -void LogicCore::RegisterBinaryHandler(uint16_t messageType, BinaryMessageHandler handler) { - std::lock_guard lock(handlersMutex_); - binaryHandlers_[messageType] = handler; - Logger::Debug("Registered binary handler for: {}", messageType); } void LogicCore::RegisterDefaultHandlers() { @@ -214,11 +153,9 @@ void LogicCore::RegisterDefaultHandlers() { RegisterHandler("quest", [this](uint64_t sessionId, const nlohmann::json& data) { HandleQuest(sessionId, data); }); - Logger::Info("Registered default message handlers"); } -// =============== Session Management =============== void LogicCore::OnPlayerConnected(uint64_t sessionId, uint64_t playerId) { std::lock_guard lock(sessionMutex_); sessionToPlayerMap_[sessionId] = playerId; @@ -250,7 +187,6 @@ uint64_t LogicCore::GetSessionIdByPlayer(uint64_t playerId) const { return it != playerToSessionMap_.end() ? it->second : 0; } -// =============== Response Methods =============== void LogicCore::SendError(uint64_t sessionId, const std::string& message, int code) { nlohmann::json errorMsg = { {"type", "error"}, @@ -287,38 +223,23 @@ void LogicCore::SendBinaryToSession(uint64_t sessionId, uint16_t messageType, co } } -// =============== Python Scripting =============== void LogicCore::FirePythonEvent(const std::string& eventName, const nlohmann::json& data) { - if (pythonEnabled_) { - Logger::Debug("LogicCore::FirePythonEvent: {}", eventName); - pythonScripting_.FireEvent(eventName, data); - } + (void)eventName; + (void)data; } nlohmann::json LogicCore::CallPythonFunction(const std::string& moduleName, const std::string& functionName, const nlohmann::json& args) { - if (!pythonEnabled_) { - return nlohmann::json(); - } - return pythonScripting_.CallFunctionWithResult(moduleName, functionName, args); + (void)moduleName; + (void)functionName; + (void)args; + return nlohmann::json(); } void LogicCore::RegisterPythonEventHandlers() { - if (!pythonEnabled_) return; - - pythonScripting_.RegisterEventHandler("player_login", "game_events", "on_player_login"); - pythonScripting_.RegisterEventHandler("player_move", "game_events", "on_player_move"); - pythonScripting_.RegisterEventHandler("player_attack", "game_events", "on_player_attack"); - pythonScripting_.RegisterEventHandler("player_level_up", "game_events", "on_player_level_up"); - pythonScripting_.RegisterEventHandler("player_death", "game_events", "on_player_death"); - pythonScripting_.RegisterEventHandler("player_respawn", "game_events", "on_player_respawn"); - pythonScripting_.RegisterEventHandler("custom_event", "game_events", "on_custom_event"); - - Logger::Info("Python event handlers registered"); } -// =============== Event Queue =============== void LogicCore::QueueEvent(EventCallback event) { std::lock_guard lock(eventQueueMutex_); eventQueue_.push(event); @@ -338,31 +259,23 @@ void LogicCore::ProcessEvents() { } } -// =============== Thread Functions =============== void LogicCore::GameLoop() { Logger::Info("Game loop started"); - auto lastUpdate = std::chrono::steady_clock::now(); - while (running_) { try { auto startTime = std::chrono::steady_clock::now(); - auto now = std::chrono::steady_clock::now(); auto deltaTimeMillis = std::chrono::duration_cast(now - lastUpdate); float deltaTime = deltaTimeMillis.count() / 1000.0f; lastUpdate = now; - - // Process game logic ProcessGameTick(deltaTime); ProcessEvents(); - auto endTime = std::chrono::steady_clock::now(); auto processingTime = std::chrono::duration_cast(endTime - startTime).count(); - if (processingTime < gameLoopInterval_.count()) { std::unique_lock lock(gameLoopMutex_); - gameLoopCV_.wait_for(lock, + gameLoopCV_.wait_for(lock, gameLoopInterval_ - std::chrono::milliseconds(processingTime), [this] { return !running_; }); } else { @@ -372,19 +285,16 @@ void LogicCore::GameLoop() { Logger::Error("Error in game loop: {}", e.what()); } } - Logger::Info("Game loop stopped"); } void LogicCore::SpawnerLoop() { Logger::Info("Spawner loop started"); - while (running_) { try { SpawnEnemies(); RespawnNPCs(); SpawnResources(); - std::unique_lock lock(spawnerMutex_); spawnerCV_.wait_for(lock, std::chrono::seconds(30), [this] { return !running_; }); @@ -392,19 +302,15 @@ void LogicCore::SpawnerLoop() { Logger::Error("Error in spawner loop: {}", e.what()); } } - Logger::Info("Spawner loop stopped"); } void LogicCore::SaveLoop() { Logger::Info("Save loop started"); - while (running_) { try { - playerManager_.SaveAllPlayers(); SaveGameState(); CleanupOldData(); - std::unique_lock lock(saveMutex_); saveCV_.wait_for(lock, std::chrono::minutes(5), [this] { return !running_; }); @@ -412,31 +318,24 @@ void LogicCore::SaveLoop() { Logger::Error("Error in save loop: {}", e.what()); } } - Logger::Info("Save loop stopped"); } -// =============== Utility Methods =============== bool LogicCore::CheckRateLimit(uint64_t sessionId) { std::lock_guard lock(rateLimitMutex_); auto now = std::chrono::steady_clock::now(); - auto& limitInfo = rateLimits_[sessionId]; auto cutoff = now - std::chrono::seconds(1); - while (!limitInfo.messageTimes.empty() && limitInfo.messageTimes.front() < cutoff) { limitInfo.messageTimes.pop_front(); limitInfo.messageCount--; } - if (limitInfo.messageCount >= MAX_MESSAGES_PER_SECOND) { return false; } - limitInfo.messageTimes.push_back(now); limitInfo.messageCount++; limitInfo.lastMessageTime = now; - return true; } @@ -446,259 +345,41 @@ int64_t LogicCore::GetCurrentTimestamp() { now.time_since_epoch()).count(); } -// =============== Stub Methods =============== void LogicCore::HandleLogin(uint64_t sessionId, const nlohmann::json& data) { - if (!data.contains("username") || !data["username"].is_string() || - !data.contains("password") || !data["password"].is_string()) { - SendError(sessionId, "Missing or invalid username/password", 400); - return; - } - - std::string username = data["username"]; - std::string password = data["password"]; - - uint64_t playerId = playerManager_.AuthenticatePlayer(username, password); - if (playerId == 0) { - SendError(sessionId, "Invalid credentials", 401); - return; - } - - auto player = playerManager_.GetPlayer(playerId); - if (!player) { - SendError(sessionId, "Player data unavailable", 500); - return; - } - - OnPlayerConnected(sessionId, playerId); - - auto& world = LogicWorld::GetInstance(); - world.AddEntity(player); - - FirePythonEvent("player_login", { - {"playerId", playerId}, - {"username", username} - }); - - SendSuccess(sessionId, "Login successful", nlohmann::json{ - {"playerId", playerId}, - {"position", player->JsonGetPosition()}, - {"health", player->GetHealth()}, - {"maxHealth", player->GetMaxHealth()}, - {"level", player->GetLevel()}, - {"experience", player->GetExperience()}, - {"inventory", player->JsonGetInventory()} - }); + (void)data; + SendError(sessionId, "Login not implemented", 501); } void LogicCore::HandleChat(uint64_t sessionId, const nlohmann::json& data) { - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (playerId == 0) { - SendError(sessionId, "Player not authenticated", 401); - return; - } - - if (!data.contains("message") || !data["message"].is_string()) { - SendError(sessionId, "Missing or invalid message", 400); - return; - } - - std::string chatMessage = data["message"]; - if (chatMessage.empty()) { - SendError(sessionId, "Message cannot be empty", 400); - return; - } - - // Optional: handle commands (starting with '/') - if (chatMessage[0] == '/') { - // Delegate to a command handler if desired - // For now, just treat as normal chat - } - - auto player = playerManager_.GetPlayer(playerId); - if (!player) { - SendError(sessionId, "Player not found", 500); - return; - } - - Logger::Info("Chat from player {} ({}): {}", playerId, player->GetName(), chatMessage); - - nlohmann::json chatJson = { - {"type", "chat"}, - {"playerId", playerId}, - {"playerName", player->GetName()}, - {"message", chatMessage}, - {"timestamp", GetCurrentTimestamp()} - }; - - std::string channel = data.value("channel", "local"); - if (channel == "global") { - // Broadcast to all online players - auto& connMgr = ConnectionManager::GetInstance(); - auto sessions = connMgr.GetAllSessions(); - for (auto& session : sessions) { - if (session && session->IsConnected()) { - session->Send(chatJson); - } - } - } else { - // Default to local broadcast (nearby players) - playerManager_.BroadcastToNearbyPlayers(playerId, chatJson); - } - - FirePythonEvent("player_chat", { - {"playerId", playerId}, - {"message", chatMessage}, - {"channel", channel} - }); + (void)data; + SendError(sessionId, "Chat not implemented", 501); } void LogicCore::HandleCombat(uint64_t sessionId, const nlohmann::json& data) { - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (playerId == 0) { - SendError(sessionId, "Player not authenticated", 401); - return; - } - - if (!data.contains("targetId") || !data["targetId"].is_number_integer()) { - SendError(sessionId, "Missing or invalid targetId", 400); - return; - } - uint64_t targetId = data["targetId"]; - - std::string attackType = data.value("attackType", "melee"); - - auto player = playerManager_.GetPlayer(playerId); - if (!player) { - SendError(sessionId, "Player not found", 500); - return; - } - - auto& world = LogicWorld::GetInstance(); - auto target = world.GetEntity(targetId); - if (!target) { - SendError(sessionId, "Target not found", 404); - return; - } - - float distance = CalculateDistance(player->GetPosition(), target->GetPosition()); - if (distance > player->GetAttackRange()) { - SendError(sessionId, "Target out of range", 400); - return; - } - - int damage = player->CalculateDamage(attackType); - target->TakeDamage(damage, playerId); - - FirePythonEvent("player_attack", { - {"playerId", playerId}, - {"targetId", targetId}, - {"damage", damage}, - {"attackType", attackType} - }); - - SendSuccess(sessionId, "Combat resolved", nlohmann::json{ - {"damageDealt", damage}, - {"targetHealth", target->GetHealth()}, - {"targetDefeated", target->IsDead()} - }); + (void)data; + SendError(sessionId, "Combat not implemented", 501); } void LogicCore::HandleQuest(uint64_t sessionId, const nlohmann::json& data) { - uint64_t playerId = GetPlayerIdBySession(sessionId); - if (playerId == 0) { - SendError(sessionId, "Player not authenticated", 401); - return; - } - - if (!data.contains("questId") || !data["questId"].is_number_integer()) { - SendError(sessionId, "Missing or invalid questId", 400); - return; - } - uint64_t questId = data["questId"]; - - if (!data.contains("action") || !data["action"].is_string()) { - SendError(sessionId, "Missing or invalid action", 400); - return; - } - std::string action = data["action"]; - - auto player = playerManager_.GetPlayer(playerId); - if (!player) { - SendError(sessionId, "Player not found", 500); - return; - } - - auto& questManager = QuestManager::GetInstance(); - nlohmann::json result; - - if (action == "start") { - if (questManager.CanStartQuest(playerId, questId)) { - questManager.StartQuest(playerId, questId); - result["status"] = "started"; - } else { - SendError(sessionId, "Cannot start quest", 400); - return; - } - } else if (action == "update") { - std::string objective = data.value("objective", ""); - int progress = data.value("progress", 1); - questManager.UpdateObjective(playerId, questId, objective, progress); - result["status"] = "updated"; - } else if (action == "complete") { - if (questManager.CanCompleteQuest(playerId, questId)) { - auto rewards = questManager.CompleteQuest(playerId, questId); - result["status"] = "completed"; - result["rewards"] = rewards; - } else { - SendError(sessionId, "Quest cannot be completed yet", 400); - return; - } - } else { - SendError(sessionId, "Unknown action: " + action, 400); - return; - } - - FirePythonEvent("player_quest", { - {"playerId", playerId}, - {"questId", questId}, - {"action", action} - }); - - SendSuccess(sessionId, "Quest action processed", result); + (void)data; + SendError(sessionId, "Quest not implemented", 501); } void LogicCore::SpawnEnemies() { - Logger::Debug("Spawning enemies"); } void LogicCore::RespawnNPCs() { - Logger::Debug("Respawning NPCs"); } void LogicCore::SpawnResources() { - Logger::Debug("Spawning resources"); } void LogicCore::ProcessGameTick(float deltaTime) { - auto& world = LogicWorld::GetInstance(); - world.UpdateEntities(deltaTime); - ProcessEvents(); - FirePythonEvent("game_tick", {{"deltaTime", deltaTime}}); + (void)deltaTime; } void LogicCore::SaveGameState() { - try { - nlohmann::json gameState = { - {"server_time", GetCurrentTimestamp()}, - {"online_players", playerManager_.GetOnlinePlayerCount()} - }; - dbManager_.SaveGameState("current_game", gameState); - Logger::Debug("Game state saved"); - } catch (const std::exception& e) { - Logger::Error("Failed to save game state: {}", e.what()); - } } void LogicCore::CleanupOldData() { - //Logger::Debug("Cleaning up old data"); } diff --git a/src/game/LogicEntity.cpp b/src/game/LogicEntity.cpp index 723a12d..25786a5 100644 --- a/src/game/LogicEntity.cpp +++ b/src/game/LogicEntity.cpp @@ -16,7 +16,7 @@ LogicEntity& LogicEntity::GetInstance() { LogicEntity::LogicEntity() : mobSystem_(MobSystem::GetInstance()), entityManager_(EntityManager::GetInstance()) { - Logger::Debug("LogicEntity created"); + Logger::Trace("LogicEntity created"); } LogicEntity::~LogicEntity() { @@ -24,24 +24,17 @@ LogicEntity::~LogicEntity() { } void LogicEntity::Initialize() { + InitializeCollisionSystem(); InitializeNPCSystem(); InitializeMobSystem(); - InitializeCollisionSystem(); - //auto& config = ConfigManager::GetInstance(); - // Initialize loot systems //inventorySystem_ = std::make_unique(); //lootTableManager_ = std::make_unique(); - //lootTableManager_->LoadLootTables("config/loot_tables.json"); - LootTableManager::GetInstance().LoadLootTables("config/loot_tables.json"); + //lootTableManager_->LoadLootTables(); + //LootTableManager::GetInstance().LoadLootTables(); Logger::Info("LogicEntity initialized"); } void LogicEntity::Shutdown() { - { - std::lock_guard lock(npcMutex_); - npcEntities_.clear(); - activeNPCCount_ = 0; - } npcManager_.reset(); collisionSystem_.reset(); //inventorySystem_.reset(); @@ -73,7 +66,7 @@ void LogicEntity::InitializeMobSystem() { mobSystem_.Initialize(); auto& config = ConfigManager::GetInstance(); if (config.HasKey("mobs")) { - nlohmann::json mobConfig = config.GetJson("mobs"); + static const nlohmann::json mobConfig = config.GetJson("mobs"); mobSystem_.LoadMobConfig(mobConfig); } } @@ -95,35 +88,34 @@ uint64_t LogicEntity::SpawnNPC(NPCType type, const glm::vec3& position, uint64_t Logger::Error("Failed to get spawned NPC"); return 0; } - npcEntities_[npcId] = std::unique_ptr(npc); - activeNPCCount_++; - // Register in collision system - BoundingSphere bounds{position, 1.0f}; - collisionSystem_->RegisterEntity(npcId, bounds, CollisionType::ENTITY); - Logger::Debug("Spawned NPC {} at [{:.1f}, {:.1f}, {:.1f}]", - npcId, position.x, position.y, position.z); + if (!collisionSystem_) { + Logger::Error("Failed to register collision system for NPC {}", npcId); + } else { + BoundingSphere bounds{position, 1.0f}; + collisionSystem_->RegisterEntity(npcId, bounds, CollisionType::ENTITY); + } + //Logger::Trace("Spawned NPC {} at [{:.1f}, {:.1f}, {:.1f}]", + // npcId, position.x, position.y, position.z); return npcId; } void LogicEntity::DespawnNPC(uint64_t npcId) { std::lock_guard lock(npcMutex_); - auto it = npcEntities_.find(npcId); - if (it == npcEntities_.end()) { - return; + if (!npcManager_->GetNPC(npcId)) return; + if (collisionSystem_) { + collisionSystem_->UnregisterEntity(npcId); } - collisionSystem_->UnregisterEntity(npcId); npcManager_->DespawnNPC(npcId); - npcEntities_.erase(it); - activeNPCCount_--; - Logger::Debug("Despawned NPC {}", npcId); + //Logger::Trace("Despawned NPC {}", npcId); } void LogicEntity::UpdateNPCs(float deltaTime) { std::lock_guard lock(npcMutex_); - for (auto& [npcId, npc] : npcEntities_) { - if (!npc) continue; - npc->Update(deltaTime); - collisionSystem_->UpdateEntity(npcId, npc->GetPosition()); + npcManager_->Update(deltaTime); + for (auto& [npcId, npc] : npcManager_->GetAllNPCs()) { + if (npc && collisionSystem_) { + collisionSystem_->UpdateEntity(npcId, npc->GetPosition()); + } } mobSystem_.UpdateSpawnZones(deltaTime); mobSystem_.ProcessRespawns(deltaTime); @@ -131,8 +123,7 @@ void LogicEntity::UpdateNPCs(float deltaTime) { NPCEntity* LogicEntity::GetNPCEntity(uint64_t npcId) { std::lock_guard lock(npcMutex_); - auto it = npcEntities_.find(npcId); - return it != npcEntities_.end() ? it->second.get() : nullptr; + return npcManager_->GetNPC(npcId); } GameEntity* LogicEntity::GetEntity(uint64_t entityId) { @@ -162,7 +153,9 @@ void LogicEntity::UpdateCollisions(float deltaTime) { void LogicEntity::CreateLootEntity(const glm::vec3& position, std::shared_ptr item, int quantity) { uint64_t entityId = entityManager_.CreateEntity(EntityType::ITEM, position); - BoundingSphere bounds{position, 0.5f}; - collisionSystem_->RegisterEntity(entityId, bounds, CollisionType::TRIGGER); - Logger::Debug("Created loot entity {}: {} x{}", entityId, item->GetName(), quantity); + if (collisionSystem_) { + BoundingSphere bounds{position, 0.5f}; + collisionSystem_->RegisterEntity(entityId, bounds, CollisionType::TRIGGER); + } + Logger::Trace("Created loot entity {}: {} x{}", entityId, item->GetName(), quantity); } diff --git a/src/game/LogicWorld.cpp b/src/game/LogicWorld.cpp index 8130eb5..a9bb315 100644 --- a/src/game/LogicWorld.cpp +++ b/src/game/LogicWorld.cpp @@ -12,7 +12,7 @@ LogicWorld& LogicWorld::GetInstance() { } LogicWorld::LogicWorld() { - Logger::Debug("LogicWorld created"); + Logger::Info("LogicWorld created"); } LogicWorld::~LogicWorld() { @@ -46,7 +46,7 @@ std::string LogicWorld::GetChunkKey(int chunkX, int chunkZ) const { std::shared_ptr LogicWorld::GetOrCreateChunk(int chunkX, int chunkZ) { std::lock_guard lock(chunksMutex_); - Logger::Debug("worldGenerator_ raw pointer: {}", static_cast(worldGenerator_.get())); + //Logger::Trace("worldGenerator_ raw pointer: {}", static_cast(worldGenerator_.get())); if (canary_ != 0xDEADBEEF) { Logger::Critical("LogicWorld memory corrupted! canary=0x{:x}", canary_); std::abort(); @@ -87,7 +87,7 @@ std::shared_ptr LogicWorld::GetOrCreateChunk(int chunkX, int chunkZ) std::shared_ptr chunk = std::move(uniqueChunk); loadedChunks_[chunkKey] = chunk; activeChunkCount_++; - Logger::Debug("Generated chunk [{}, {}], total: {}", chunkX, chunkZ, activeChunkCount_.load()); + //Logger::Trace("Generated chunk [{}, {}], total: {}", chunkX, chunkZ, activeChunkCount_.load()); return chunk; } diff --git a/src/game/LootTableManager.cpp b/src/game/LootTableManager.cpp index 76dabfb..cac2d8b 100644 --- a/src/game/LootTableManager.cpp +++ b/src/game/LootTableManager.cpp @@ -5,7 +5,6 @@ std::unique_ptr LootTableManager::instance_ = nullptr; std::once_flag LootTableManager::initFlag_; LootTableManager::LootTableManager() { - // Seed the random number generator std::random_device rd; rng_.seed(rd()); } @@ -22,19 +21,16 @@ LootTableManager& LootTableManager::GetInstance() { void LootTableManager::RegisterTable(const LootTable& table) { std::lock_guard lock(tablesMutex_); - if (lootTables_.find(table.tableId) != lootTables_.end()) { Logger::Warn("LootTableManager: Overwriting existing table '{}'", table.tableId); } - lootTables_[table.tableId] = table; - Logger::Debug("LootTableManager: Registered table '{}' with {} entries", + Logger::Debug("LootTableManager: Registered table '{}' with {} entries", table.tableId, table.entries.size()); } void LootTableManager::UnregisterTable(const std::string& tableId) { std::lock_guard lock(tablesMutex_); - auto it = lootTables_.find(tableId); if (it != lootTables_.end()) { lootTables_.erase(it); @@ -46,12 +42,10 @@ void LootTableManager::UnregisterTable(const std::string& tableId) { const LootTable* LootTableManager::GetTable(const std::string& tableId) const { std::lock_guard lock(tablesMutex_); - auto it = lootTables_.find(tableId); if (it != lootTables_.end()) { return &it->second; } - Logger::Warn("LootTableManager: Table '{}' not found", tableId); return nullptr; } @@ -68,68 +62,50 @@ std::vector, int>> LootTableManager::Generat const std::unordered_map& factionRep ) { std::vector, int>> result; - const LootTable* table = GetTable(tableId); if (!table) { Logger::Error("LootTableManager: Cannot generate loot from non-existent table '{}'", tableId); return result; } - std::lock_guard lock(tablesMutex_); - - // Track which items have been dropped if unique drops is enabled std::vector droppedIndices; if (table->uniqueDrops) { droppedIndices.reserve(table->entries.size()); } - - // Process guaranteed drops first int guaranteedCount = 0; for (size_t i = 0; i < table->entries.size() && guaranteedCount < table->guaranteedDrops; ++i) { const LootEntry& entry = table->entries[i]; - - if (entry.dropChance >= 1.0f && + if (entry.dropChance >= 1.0f && PlayerMeetsRequirements(entry, playerLevel, factionRep) && (!table->uniqueDrops || std::find(droppedIndices.begin(), droppedIndices.end(), i) == droppedIndices.end())) { - int quantity = GetRandomInt(entry.minQuantity, entry.maxQuantity); auto item = CreateItemFromEntry(entry, playerLevel, luckMultiplier); - if (item) { result.emplace_back(item, quantity); guaranteedCount++; - if (table->uniqueDrops) { droppedIndices.push_back(i); } } } } - - // Process random drops int maxRandomDrops = table->maxDrops - guaranteedCount; if (maxRandomDrops > 0) { - // Calculate total weight for weighted random selection float totalWeight = 0.0f; std::vector weights; std::vector eligibleIndices; - for (size_t i = 0; i < table->entries.size(); ++i) { const LootEntry& entry = table->entries[i]; - - // Skip if already dropped and unique drops is enabled if (table->uniqueDrops && std::find(droppedIndices.begin(), droppedIndices.end(), i) != droppedIndices.end()) { continue; } - if (PlayerMeetsRequirements(entry, playerLevel, factionRep)) { float adjustedChance = CalculateAdjustedDropChance( - entry.dropChance, - luckMultiplier, - playerLevel, + entry.dropChance, + luckMultiplier, + playerLevel, GetRandomInt(entry.minLevel, entry.maxLevel) ); - if (adjustedChance > 0.0f) { weights.push_back(adjustedChance); eligibleIndices.push_back(i); @@ -137,21 +113,15 @@ std::vector, int>> LootTableManager::Generat } } } - - // Normalize weights if (totalWeight > 0.0f) { for (float& weight : weights) { weight /= totalWeight; } - - // Generate random drops int randomDropCount = 0; while (randomDropCount < maxRandomDrops && !eligibleIndices.empty()) { - // Weighted random selection float randomValue = GetRandomFloat(); float cumulativeWeight = 0.0f; size_t selectedIndex = 0; - for (size_t i = 0; i < weights.size(); ++i) { cumulativeWeight += weights[i]; if (randomValue <= cumulativeWeight) { @@ -159,24 +129,16 @@ std::vector, int>> LootTableManager::Generat break; } } - size_t entryIndex = eligibleIndices[selectedIndex]; const LootEntry& entry = table->entries[entryIndex]; - - // Generate item int quantity = GetRandomInt(entry.minQuantity, entry.maxQuantity); auto item = CreateItemFromEntry(entry, playerLevel, luckMultiplier); - if (item) { result.emplace_back(item, quantity); randomDropCount++; - - // Remove from eligible list if unique drops if (table->uniqueDrops) { eligibleIndices.erase(eligibleIndices.begin() + selectedIndex); weights.erase(weights.begin() + selectedIndex); - - // Re-normalize weights float newTotal = 0.0f; for (float w : weights) newTotal += w; if (newTotal > 0.0f) { @@ -184,15 +146,12 @@ std::vector, int>> LootTableManager::Generat } } } - - // Break if no more eligible items if (eligibleIndices.empty()) { break; } } } } - Logger::Debug("LootTableManager: Generated {} items from table '{}'", result.size(), tableId); return result; } @@ -203,12 +162,10 @@ std::vector, int>> LootTableManager::Generat float luckMultiplier ) { std::vector, int>> result; - for (const auto& tableId : tableIds) { auto loot = GenerateLoot(tableId, playerLevel, luckMultiplier); result.insert(result.end(), loot.begin(), loot.end()); } - return result; } @@ -218,28 +175,21 @@ std::vector, int>> LootTableManager::Generat int playerLevel ) { std::vector, int>> result; - if (tables.empty() || tables.size() != weights.size()) { Logger::Error("LootTableManager: Tables and weights size mismatch"); return result; } - - // Normalize weights float totalWeight = 0.0f; for (float weight : weights) { totalWeight += weight; } - if (totalWeight <= 0.0f) { Logger::Error("LootTableManager: Total weight must be positive"); return result; } - - // Select a table based on weights float randomValue = GetRandomFloat(0.0f, totalWeight); float cumulativeWeight = 0.0f; size_t selectedIndex = 0; - for (size_t i = 0; i < weights.size(); ++i) { cumulativeWeight += weights[i]; if (randomValue <= cumulativeWeight) { @@ -247,23 +197,16 @@ std::vector, int>> LootTableManager::Generat break; } } - - // Generate loot from selected table const LootTable& selectedTable = tables[selectedIndex]; std::string tableId = selectedTable.tableId; - - // Check if table exists in manager, if not register it temporarily bool temporaryRegister = !HasTable(tableId); if (temporaryRegister) { RegisterTable(selectedTable); } - result = GenerateLoot(tableId, playerLevel, 1.0f); - if (temporaryRegister) { UnregisterTable(tableId); } - return result; } @@ -279,48 +222,44 @@ int LootTableManager::GenerateGold(const LootTable& table, float luckMultiplier) if (table.minGold <= 0 || table.maxGold <= 0) { return 0; } - int baseGold = GetRandomInt(table.minGold, table.maxGold); int adjustedGold = static_cast(baseGold * table.goldMultiplier * luckMultiplier); - return std::max(0, adjustedGold); } bool LootTableManager::LoadLootTables(const std::string& filePath) { - std::ifstream file(filePath); + if (!filePath.empty()) file_path_ = filePath; + std::ifstream file(file_path_); if (!file.is_open()) { - Logger::Error("LootTableManager: Failed to open file '{}'", filePath); + Logger::Error("LootTableManager: Failed to open file '{}'", file_path_); return false; } - try { nlohmann::json jsonData; file >> jsonData; file.close(); - return LoadLootTablesFromJson(jsonData); } catch (const std::exception& e) { - Logger::Error("LootTableManager: Failed to parse JSON from '{}': {}", filePath, e.what()); + Logger::Error("LootTableManager: Failed to parse JSON from '{}': {}", file_path_, e.what()); return false; } } bool LootTableManager::SaveLootTables(const std::string& filePath) const { - std::ofstream file(filePath); + std::string path = filePath.empty() ? file_path_ : filePath; + std::ofstream file(path); if (!file.is_open()) { - Logger::Error("LootTableManager: Failed to create file '{}'", filePath); + Logger::Error("LootTableManager: Failed to create file '{}'", path); return false; } - try { nlohmann::json jsonData = SerializeAllTables(); - file << jsonData.dump(4); // Pretty print with 4 spaces + file << jsonData.dump(4); file.close(); - - Logger::Info("LootTableManager: Saved {} tables to '{}'", GetTableCount(), filePath); + Logger::Info("LootTableManager: Saved {} tables to '{}'", GetTableCount(), path); return true; } catch (const std::exception& e) { - Logger::Error("LootTableManager: Failed to save tables to '{}': {}", filePath, e.what()); + Logger::Error("LootTableManager: Failed to save tables to '{}': {}", path, e.what()); return false; } } @@ -330,38 +269,30 @@ bool LootTableManager::LoadLootTablesFromJson(const nlohmann::json& jsonData) { Logger::Error("LootTableManager: Expected JSON array of loot tables"); return false; } - std::lock_guard lock(tablesMutex_); - size_t loadedCount = 0; for (const auto& tableJson : jsonData) { try { LootTable table; table.Deserialize(tableJson); - lootTables_[table.tableId] = table; loadedCount++; - - Logger::Debug("LootTableManager: Loaded table '{}' with {} entries", + Logger::Debug("LootTableManager: Loaded table '{}' with {} entries", table.tableId, table.entries.size()); } catch (const std::exception& e) { Logger::Error("LootTableManager: Failed to load table: {}", e.what()); } } - Logger::Info("LootTableManager: Loaded {} loot tables from JSON", loadedCount); return loadedCount > 0; } nlohmann::json LootTableManager::SerializeAllTables() const { nlohmann::json jsonArray = nlohmann::json::array(); - std::lock_guard lock(tablesMutex_); - for (const auto& pair : lootTables_) { jsonArray.push_back(pair.second.Serialize()); } - return jsonArray; } @@ -381,26 +312,18 @@ bool LootTableManager::PlayerMeetsRequirements( int playerLevel, const std::unordered_map& factionRep ) { - // Check level requirement if (playerLevel < entry.minLevel || playerLevel > entry.maxLevel) { return false; } - - // Check quest requirement if (!entry.requiredQuest.empty()) { - // This would need integration with quest system - // For now, assume no quest requirement checking return false; } - - // Check faction requirement if (!entry.requiredFaction.empty()) { auto it = factionRep.find(entry.requiredFaction); if (it == factionRep.end() || it->second < entry.factionRepRequired) { return false; } } - return true; } @@ -410,33 +333,20 @@ std::shared_ptr LootTableManager::CreateItemFromEntry( float luckMultiplier ) const { (void)playerLevel; // suppress unused parameter warning - - // Determine item level int itemLevel = GetRandomInt(entry.minLevel, entry.maxLevel); - - // Generate rarity LootRarity rarity = GenerateRarity(entry.minRarity, entry.maxRarity, luckMultiplier); - - // Create base item auto item = std::make_shared(); item->SetId(entry.itemId); item->SetName(entry.name); item->SetRarity(rarity); item->SetLevelRequirement(itemLevel); - - // Apply rarity-based modifications ApplyRarityStats(item, rarity); - - // Generate random stats if applicable if (item->GetType() == ItemType::WEAPON || item->GetType() == ItemType::ARMOR) { GenerateRandomStats(item, itemLevel); } - - // Apply enchantments for rare+ items if (static_cast(rarity) >= static_cast(LootRarity::RARE)) { ApplyRandomEnchantment(item, rarity); } - return item; } @@ -447,12 +357,9 @@ LootRarity LootTableManager::GenerateRarity( ) const { int minValue = static_cast(minRarity); int maxValue = static_cast(maxRarity); - if (minValue > maxValue) { std::swap(minValue, maxValue); } - - // Calculate probabilities for each rarity tier std::vector probabilities = { 0.60f, // COMMON 0.25f, // UNCOMMON @@ -461,29 +368,21 @@ LootRarity LootTableManager::GenerateRarity( 0.009f, // LEGENDARY 0.001f // MYTHIC }; - - // Apply luck multiplier to improve chances for (int i = minValue + 1; i <= maxValue; ++i) { probabilities[i] *= luckMultiplier; } - - // Normalize probabilities float total = 0.0f; for (int i = minValue; i <= maxValue; ++i) { total += probabilities[i]; } - - // Select rarity based on probabilities float randomValue = GetRandomFloat(0.0f, total); float cumulative = 0.0f; - for (int i = minValue; i <= maxValue; ++i) { cumulative += probabilities[i]; if (randomValue <= cumulative) { return static_cast(i); } } - return maxRarity; // Fallback } @@ -496,26 +395,18 @@ float LootTableManager::CalculateAdjustedDropChance( if (baseChance <= 0.0f) { return 0.0f; } - - // Adjust based on luck multiplier float adjustedChance = baseChance * luckMultiplier; - - // Adjust based on level difference (higher level items are rarer for lower level players) int levelDiff = itemLevel - playerLevel; if (levelDiff > 0) { adjustedChance /= (1.0f + (levelDiff * 0.1f)); // 10% reduction per level above player } else if (levelDiff < 0) { adjustedChance *= (1.0f - (abs(levelDiff) * 0.05f)); // 5% reduction per level below player } - - // Clamp between 0 and 1 return std::clamp(adjustedChance, 0.0f, 1.0f); } void LootTableManager::ApplyRarityStats(std::shared_ptr item, LootRarity rarity) const { - // Apply stat multipliers based on rarity float multiplier = 1.0f; - switch (rarity) { case LootRarity::COMMON: multiplier = 1.0f; @@ -536,14 +427,10 @@ void LootTableManager::ApplyRarityStats(std::shared_ptr item, LootRari multiplier = 5.0f; break; } - - // Apply multiplier to all stats auto stats = item->GetStats(); for (const auto& stat : stats) { item->AddStat(stat.statName, stat.baseValue * multiplier, stat.maxValue * multiplier); } - - // Set icon color based on rarity glm::vec3 color; switch (rarity) { case LootRarity::COMMON: color = glm::vec3(1.0f, 1.0f, 1.0f); break; // White @@ -553,15 +440,11 @@ void LootTableManager::ApplyRarityStats(std::shared_ptr item, LootRari case LootRarity::LEGENDARY: color = glm::vec3(1.0f, 0.5f, 0.0f); break; // Orange case LootRarity::MYTHIC: color = glm::vec3(1.0f, 0.0f, 0.0f); break; // Red } - item->SetIconColor(color); } void LootTableManager::GenerateRandomStats(std::shared_ptr item, int itemLevel) const { - // Generate 1-3 random stats based on item level int numStats = GetRandomInt(1, std::min(3, itemLevel / 10 + 1)); - - // List of possible stats std::vector> possibleStats = { {"strength", 1.0f}, {"dexterity", 1.0f}, @@ -576,27 +459,20 @@ void LootTableManager::GenerateRandomStats(std::shared_ptr item, int i {"health", 10.0f}, {"mana", 10.0f} }; - - // Shuffle and select random stats std::vector indices(possibleStats.size()); std::iota(indices.begin(), indices.end(), 0); std::shuffle(indices.begin(), indices.end(), rng_); - for (int i = 0; i < numStats && i < static_cast(indices.size()); ++i) { size_t idx = indices[i]; const auto& stat = possibleStats[idx]; - - // Calculate value based on item level float baseValue = stat.second * (1.0f + (itemLevel / 10.0f)); float variance = GetRandomFloat(0.8f, 1.2f); float finalValue = baseValue * variance; - item->AddStat(stat.first, finalValue, finalValue * 1.5f); } } void LootTableManager::ApplyRandomEnchantment(std::shared_ptr item, LootRarity rarity) const { - // List of possible enchantments std::vector possibleEnchantments = { {"multiply", "attack_damage", 1.25f, 0, "enchantment"}, {"multiply", "attack_speed", 1.15f, 0, "enchantment"}, @@ -607,16 +483,11 @@ void LootTableManager::ApplyRandomEnchantment(std::shared_ptr item, Lo {"add", "health", 50.0f, 0, "enchantment"}, {"add", "mana", 30.0f, 0, "enchantment"} }; - - // Number of enchantments based on rarity int numEnchantments = static_cast(rarity) - static_cast(LootRarity::RARE) + 1; numEnchantments = std::min(numEnchantments, 3); - - // Shuffle and select random enchantments std::vector indices(possibleEnchantments.size()); std::iota(indices.begin(), indices.end(), 0); std::shuffle(indices.begin(), indices.end(), rng_); - for (int i = 0; i < numEnchantments; ++i) { if (i < static_cast(indices.size())) { item->AddModifier(possibleEnchantments[indices[i]]); diff --git a/src/game/MobSystem.cpp b/src/game/MobSystem.cpp index b366d7c..259c4e6 100644 --- a/src/game/MobSystem.cpp +++ b/src/game/MobSystem.cpp @@ -209,44 +209,89 @@ void MobSystem::SetZoneLootTable(const std::string& zoneName, const std::string& } void MobSystem::LoadMobConfig(const nlohmann::json& config) { + //Logger::Trace("MobSystem::LoadMobConfig: started"); + if (!config.is_object()) { + Logger::Error("MobSystem::LoadMobConfig: config is not a JSON object"); + return; + } + //Logger::Trace("Checking for 'spawnZones' key..."); if (config.contains("spawnZones")) { - for (const auto& zoneData : config["spawnZones"]) { - MobSpawnZone zone; - zone.name = zoneData.value("name", ""); - zone.center.x = zoneData["center"][0]; - zone.center.y = zoneData["center"][1]; - zone.center.z = zoneData["center"][2]; - zone.radius = zoneData.value("radius", 50.0f); - zone.mobType = static_cast(zoneData.value("mobType", 0)); - zone.minLevel = zoneData.value("minLevel", 1); - zone.maxLevel = zoneData.value("maxLevel", 10); - zone.maxMobs = zoneData.value("maxMobs", 10); - zone.respawnTime = zoneData.value("respawnTime", 30.0f); - zone.lootTableId = zoneData.value("lootTableId", ""); - - RegisterSpawnZone(zone); - - // Set zone loot table if specified - if (!zone.lootTableId.empty()) { - SetZoneLootTable(zone.name, zone.lootTableId); + //Logger::Trace("'spawnZones' key exists"); + if (config["spawnZones"].is_array()) { + //Logger::Trace("'spawnZones' is an array with {} elements", config["spawnZones"].size()); + int zoneIndex = 0; + for (const auto& zoneData : config["spawnZones"]) { + //Logger::Trace("Processing spawn zone index {}", zoneIndex); + if (!zoneData.is_object()) { + Logger::Warn("Spawn zone entry is not an object, skipping"); + zoneIndex++; + continue; + } + MobSpawnZone zone; + zone.name = zoneData.value("name", ""); + if (zone.name.empty()) { + Logger::Warn("Spawn zone missing name, skipping"); + zoneIndex++; + continue; + } + //Logger::Trace("Zone name: {}", zone.name); + if (zoneData.contains("center") && zoneData["center"].is_array() && zoneData["center"].size() >= 3) { + zone.center.x = zoneData["center"][0].get(); + zone.center.y = zoneData["center"][1].get(); + zone.center.z = zoneData["center"][2].get(); + //Logger::Trace("Center: ({}, {}, {})", zone.center.x, zone.center.y, zone.center.z); + } else { + Logger::Warn("Spawn zone '{}' missing or invalid 'center', skipping", zone.name); + zoneIndex++; + continue; + } + zone.radius = zoneData.value("radius", 50.0f); + zone.mobType = static_cast(zoneData.value("mobType", 0)); + zone.minLevel = zoneData.value("minLevel", 1); + zone.maxLevel = zoneData.value("maxLevel", 10); + zone.maxMobs = zoneData.value("maxMobs", 10); + zone.respawnTime = zoneData.value("respawnTime", 30.0f); + zone.lootTableId = zoneData.value("lootTableId", ""); + //Logger::Trace("Registering spawn zone '{}'", zone.name); + RegisterSpawnZone(zone); + if (!zone.lootTableId.empty()) { + SetZoneLootTable(zone.name, zone.lootTableId); + } + zoneIndex++; } + } else { + Logger::Warn("'spawnZones' is not an array, skipping"); } + //} else { + //Logger::Trace("No 'spawnZones' key found"); } - + //Logger::Trace("Checking for 'mobLootTables' key..."); if (config.contains("mobLootTables")) { - for (const auto& [typeStr, tableId] : config["mobLootTables"].items()) { - NPCType type = static_cast(std::stoi(typeStr)); - SetMobLootTable(type, tableId.get()); + Logger::Trace("'mobLootTables' key exists"); + if (config["mobLootTables"].is_object()) { + //Logger::Trace("Processing mobLootTables object"); + for (const auto& [typeStr, tableId] : config["mobLootTables"].items()) { + try { + //Logger::Trace("Processing mob loot table for type '{}'", typeStr); + NPCType type = static_cast(std::stoi(typeStr)); + SetMobLootTable(type, tableId.get()); + } catch (const std::exception& e) { + Logger::Error("Invalid mob loot table entry: {}", e.what()); + } + } + } else { + Logger::Warn("'mobLootTables' is not an object, skipping"); } + //} else { + //Logger::Trace("No 'mobLootTables' key found"); } + //Logger::Trace("MobSystem::LoadMobConfig: finished successfully"); } // ==================== Missing Function Implementations ==================== void MobSystem::InitializeDefaultVariants() { - // Register default mob variants based on NPC type and level - // For now, we register a few common variants - Logger::Debug("MobSystem::InitializeDefaultVariants() called"); + Logger::Trace("MobSystem::InitializeDefaultVariants() called"); // Example: Goblin variants for levels 1-5 for (int level = 1; level <= 5; ++level) { @@ -285,17 +330,12 @@ void MobSystem::InitializeDefaultVariants() { void MobSystem::UpdateSpawnZones(float deltaTime) { (void)deltaTime; - // This function should check each spawn zone and spawn mobs if needed - // For now, we'll implement a simple timer-based spawning for (auto& [zoneName, zone] : spawnZones_) { - // Check if we need to spawn more mobs auto& mobIds = zoneMobs_[zoneName]; if (static_cast(mobIds.size()) < zone.maxMobs) { - // Check last spawn time auto now = std::chrono::steady_clock::now(); auto& lastSpawn = zoneLastSpawn_[zoneName]; if (std::chrono::duration_cast(now - lastSpawn).count() >= zone.respawnTime) { - // Spawn a new mob uint64_t mobId = SpawnMobInZone(zoneName); if (mobId != 0) { mobIds.push_back(mobId); @@ -309,11 +349,9 @@ void MobSystem::UpdateSpawnZones(float deltaTime) { void MobSystem::ProcessRespawns(float deltaTime) { (void)deltaTime; - // Check pending respawns and spawn mobs when time is reached auto now = std::chrono::steady_clock::now(); for (auto it = pendingRespawns_.begin(); it != pendingRespawns_.end(); ) { if (now >= it->respawnTime) { - // Spawn the mob in its zone uint64_t mobId = SpawnMob(it->mobType, GetRandomSpawnPosition(spawnZones_[it->zoneName]), it->level); if (mobId != 0) { zoneMobs_[it->zoneName].push_back(mobId); @@ -328,12 +366,14 @@ void MobSystem::ProcessRespawns(float deltaTime) { uint64_t MobSystem::SpawnMob(NPCType type, const glm::vec3& position, int level) { // TODO: Create and register a new NPCEntity using EntityManager - Logger::Warn("MobSystem::SpawnMob not fully implemented – returning mock ID"); - // Simulate a new mob ID (in real implementation, use EntityManager to create an entity) + (void)type; + (void)position; + (void)level; + Logger::Warn("MobSystem::SpawnMob not implemented – returning mock ID"); static uint64_t nextId = 1000; uint64_t newId = ++nextId; - Logger::Debug("Spawned mob {} of type {} at ({:.1f},{:.1f},{:.1f}) level {}", - newId, static_cast(type), position.x, position.y, position.z, level); + // Logger::Trace("Spawned mob {} of type {} at ({:.1f},{:.1f},{:.1f}) level {}", + // newId, static_cast(type), position.x, position.y, position.z, level); return newId; } @@ -350,7 +390,6 @@ uint64_t MobSystem::SpawnMobInZone(const std::string& zoneName) { } void MobSystem::DespawnMob(uint64_t mobId) { - // Remove from zone tracking and entity manager auto zoneIt = mobToZone_.find(mobId); if (zoneIt != mobToZone_.end()) { auto& mobIds = zoneMobs_[zoneIt->second]; @@ -358,14 +397,14 @@ void MobSystem::DespawnMob(uint64_t mobId) { mobToZone_.erase(zoneIt); } // TODO: Also remove from EntityManager - Logger::Debug("Despawned mob {}", mobId); + //Logger::Trace("Despawned mob {}", mobId); } void MobSystem::RegisterSpawnZone(const MobSpawnZone& zone) { spawnZones_[zone.name] = zone; zoneMobs_[zone.name] = {}; // initialize empty mob list zoneLastSpawn_[zone.name] = std::chrono::steady_clock::now(); // set last spawn to now - Logger::Debug("Registered spawn zone '{}'", zone.name); + //Logger::Trace("Registered spawn zone '{}'", zone.name); } void MobSystem::UnregisterSpawnZone(const std::string& zoneName) { @@ -378,14 +417,14 @@ void MobSystem::UnregisterSpawnZone(const std::string& zoneName) { spawnZones_.erase(it); zoneMobs_.erase(zoneName); zoneLastSpawn_.erase(zoneName); - Logger::Debug("Unregistered spawn zone '{}'", zoneName); + //Logger::Trace("Unregistered spawn zone '{}'", zoneName); } } void MobSystem::RegisterMobVariant(const MobVariant& variant) { std::string key = GetVariantKey(variant.baseType, variant.level); mobVariants_[key] = variant; - Logger::Debug("Registered mob variant: {} level {}", static_cast(variant.baseType), variant.level); + //Logger::Trace("Registered mob variant: {} level {}", static_cast(variant.baseType), variant.level); } MobVariant MobSystem::GetMobVariant(NPCType type, int level) const { diff --git a/src/game/NPCEntity.cpp b/src/game/NPCEntity.cpp index 900a8d1..6cfc355 100644 --- a/src/game/NPCEntity.cpp +++ b/src/game/NPCEntity.cpp @@ -272,12 +272,12 @@ NPCEntity::NPCEntity(NPCType type, const glm::vec3& position, int level) // Set spawn position spawn_position_ = position; - Logger::Debug("NPCEntity created: {} (ID: {}) at [{:.1f}, {:.1f}, {:.1f}]", - name_, GetId(), position.x, position.y, position.z); + // Logger::Trace("NPCEntity created: {} (ID: {}) at [{:.1f}, {:.1f}, {:.1f}]", + // name_, GetId(), position.x, position.y, position.z); } NPCEntity::~NPCEntity() { - Logger::Debug("NPCEntity destroyed: {} (ID: {})", name_, GetId()); + //Logger::Trace("NPCEntity destroyed: {} (ID: {})", name_, GetId()); } // =============== NPC Type Management =============== @@ -615,13 +615,11 @@ void NPCEntity::SetDefaultAIProfile() { void NPCEntity::SetAIState(NPCAIState state) { if (ai_state_ == state) return; - NPCAIState old_state = ai_state_; + //NPCAIState old_state = ai_state_; ai_state_ = state; - // Reset state timer state_timer_ = 0.0f; - // State entry logic switch (ai_state_) { case NPCAIState::IDLE: ChangeToIdle(); @@ -649,15 +647,13 @@ void NPCEntity::SetAIState(NPCAIState state) { break; } - Logger::Debug("NPC {} changed AI state from {} to {}", - GetId(), AIStateToString(old_state), GetAIStateString()); + // Logger::Trace("NPC {} changed AI state from {} to {}", + // GetId(), AIStateToString(old_state), GetAIStateString()); } void NPCEntity::ChangeToIdle() { Stop(); idle_timer_ = 0.0f; - - // Set random idle time std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis( @@ -668,19 +664,15 @@ void NPCEntity::ChangeToIdle() { } void NPCEntity::ChangeToPatrol() { - // If no patrol points, generate random patrol area if (ai_profile_.patrol_points.empty() && ai_profile_.patrol_radius > 0) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(-ai_profile_.patrol_radius, ai_profile_.patrol_radius); - glm::vec3 patrol_point = spawn_position_; patrol_point.x += dis(gen); patrol_point.z += dis(gen); - MoveTo(patrol_point, ai_profile_.patrol_speed); } else if (!ai_profile_.patrol_points.empty()) { - // Move to first patrol point glm::vec3 target = GetNextPatrolPoint(); MoveTo(target, ai_profile_.patrol_speed); } @@ -688,7 +680,6 @@ void NPCEntity::ChangeToPatrol() { void NPCEntity::ChangeToChase(uint64_t target_id) { target_id_ = target_id; - // Chase logic handled in UpdateChase } void NPCEntity::ChangeToAttack(uint64_t target_id) { @@ -698,20 +689,14 @@ void NPCEntity::ChangeToAttack(uint64_t target_id) { void NPCEntity::ChangeToFlee() { flee_timer_ = 0.0f; - - // Calculate flee direction away from target if (target_id_ != 0) { - // This would require getting target position from EntityManager - // For now, just move in a random direction std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(-1.0f, 1.0f); - glm::vec3 flee_dir = glm::vec3(dis(gen), 0.0f, dis(gen)); if (glm::length(flee_dir) > 0.01f) { flee_dir = glm::normalize(flee_dir); } - glm::vec3 flee_target = GetPosition() + flee_dir * 20.0f; MoveTo(flee_target, npc_stats_.flee_speed); } @@ -722,8 +707,6 @@ void NPCEntity::ChangeToDead() { SetActive(false); SetVisible(false); SetCollidable(false); - - // Schedule despawn state_timer_ = DESPAWN_DELAY; ai_state_ = NPCAIState::DESPAWNING; } @@ -731,8 +714,6 @@ void NPCEntity::ChangeToDead() { // =============== AI Update Methods =============== void NPCEntity::Update(float delta_time) { GameEntity::Update(delta_time); - - // Auto-stop when close to move target if (has_move_target_) { float distance = glm::distance(GetPosition(), move_target_); if (distance < 0.5f) { // Threshold – can be a constant or configurable @@ -740,45 +721,29 @@ void NPCEntity::Update(float delta_time) { has_move_target_ = false; } } - - // Update AI if active and not dead if (IsActive() && ai_state_ != NPCAIState::DEAD && ai_state_ != NPCAIState::DESPAWNING) { UpdateAI(delta_time); } - - // Update timers state_timer_ += delta_time; - - // Update attack cooldown if (attack_cooldown_ > 0.0f) { attack_cooldown_ -= delta_time; } - - // Update stun timer if (stun_timer_ > 0.0f) { stun_timer_ -= delta_time; if (stun_timer_ <= 0.0f && ai_state_ == NPCAIState::IDLE) { - // Return to previous state after stun SetAIState(NPCAIState::IDLE); } } - - // Update summon cooldown if (summon_cooldown_ > 0.0f) { summon_cooldown_ -= delta_time; } } void NPCEntity::UpdateAI(float delta_time) { - // Don't update AI if stunned if (stun_timer_ > 0.0f) { return; } - - // Update target selection UpdateTargetSelection(); - - // Update current AI state switch (ai_state_) { case NPCAIState::IDLE: UpdateIdle(delta_time); @@ -802,56 +767,41 @@ void NPCEntity::UpdateAI(float delta_time) { UpdateDespawning(delta_time); break; default: - // Other states don't need AI updates break; } } void NPCEntity::UpdateIdle(float delta_time) { idle_timer_ += delta_time; - - // Check for targets if aggressive if (ai_profile_.is_aggressive && HasTarget()) { SetAIState(NPCAIState::CHASE); return; } - - // Check if idle time is up if (idle_timer_ >= state_timer_) { - // Transition to patrol or wander if (!ai_profile_.patrol_points.empty() || ai_profile_.patrol_radius > 0) { SetAIState(NPCAIState::PATROL); } else { - // Reset idle timer ChangeToIdle(); } } } void NPCEntity::UpdatePatrol(float delta_time) { - // Check for targets if aggressive if (ai_profile_.is_aggressive && HasTarget()) { SetAIState(NPCAIState::CHASE); return; } - if (waiting_at_patrol_point_) { - // Decrease wait timer patrol_wait_timer_ -= delta_time; if (patrol_wait_timer_ <= 0.0f) { - // Finished waiting, move to next point waiting_at_patrol_point_ = false; glm::vec3 next_point = GetNextPatrolPoint(); MoveTo(next_point, ai_profile_.patrol_speed); } return; } - - // Check if reached patrol point if (!IsMoving()) { - // Arrived at point – start waiting waiting_at_patrol_point_ = true; - // Random wait duration (e.g., 2–5 seconds) std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution wait_dist(2.0f, 5.0f); @@ -864,31 +814,20 @@ void NPCEntity::UpdateChase(float delta_time) { SetAIState(NPCAIState::IDLE); return; } - - // Check if target is in attack range - // This would require getting target position from EntityManager - // For now, simulate with random chance std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(0.0f, 1.0f); - - if (dis(gen) < 0.1f) { // 10% chance per frame to enter attack range + if (dis(gen) < 0.1f) { SetAIState(NPCAIState::ATTACK); return; } - - // Check if target is out of chase range - if (dis(gen) < 0.05f) { // 5% chance per frame to lose target + if (dis(gen) < 0.05f) { target_id_ = 0; SetAIState(NPCAIState::IDLE); return; } - - // Move toward target (simulated) - // In real implementation, you would get target position and move toward it - glm::vec3 chase_dir = glm::vec3(1.0f, 0.0f, 1.0f); // Simplified + glm::vec3 chase_dir = glm::vec3(1.0f, 0.0f, 1.0f); chase_dir = glm::normalize(chase_dir); - glm::vec3 velocity = chase_dir * npc_stats_.chase_speed * delta_time; Translate(velocity); } @@ -899,21 +838,15 @@ void NPCEntity::UpdateAttack(float delta_time) { SetAIState(NPCAIState::IDLE); return; } - - // Check if should flee if (ai_profile_.can_flee && GetHealth() / GetMaxHealth() <= ai_profile_.flee_health_threshold) { SetAIState(NPCAIState::FLEE); return; } - - // Check attack cooldown if (attack_cooldown_ <= 0.0f) { PerformAttack(); attack_cooldown_ = ATTACK_COOLDOWN_BASE / npc_stats_.attack_speed; } - - // Check if should summon allies if (ai_profile_.can_summon_allies && summon_cooldown_ <= 0.0f) { SummonAllies(); summon_cooldown_ = ai_profile_.summon_cooldown; @@ -922,14 +855,10 @@ void NPCEntity::UpdateAttack(float delta_time) { void NPCEntity::UpdateFlee(float delta_time) { flee_timer_ += delta_time; - - // Check if flee time is up if (flee_timer_ >= FLEE_DURATION) { SetAIState(NPCAIState::IDLE); return; } - - // Check if still need to flee if (GetHealth() / GetMaxHealth() > ai_profile_.flee_health_threshold * 1.5f) { SetAIState(NPCAIState::IDLE); return; @@ -938,7 +867,6 @@ void NPCEntity::UpdateFlee(float delta_time) { void NPCEntity::UpdateSpawning(float delta_time) { (void)delta_time; - // For now, just transition to idle after a short time if (state_timer_ >= 2.0f) { SetAIState(NPCAIState::IDLE); } @@ -947,67 +875,42 @@ void NPCEntity::UpdateSpawning(float delta_time) { void NPCEntity::UpdateDespawning(float delta_time) { (void)delta_time; if (state_timer_ >= DESPAWN_DELAY) { - // Mark for removal (should be handled by EntityManager) SetActive(false); - - // If respawns, schedule respawn - if (ai_profile_.respawns) { - // This would be handled by a respawn system - Logger::Debug("NPC {} scheduled for respawn", GetId()); - } + // if (ai_profile_.respawns) { + // Logger::Trace("NPC {} scheduled for respawn", GetId()); + // } } } // =============== Targeting =============== void NPCEntity::SetTarget(uint64_t target_id) { - if (target_id == GetId()) return; // Can't target self - + if (target_id == GetId()) return; target_id_ = target_id; - - // If we have a target and are aggressive, start chasing if (target_id_ != 0 && ai_profile_.is_aggressive) { SetAIState(NPCAIState::CHASE); } } void NPCEntity::UpdateTargetSelection() { - // Only update target if we don't have one or current target is invalid if (HasTarget() || !ai_profile_.is_aggressive) { return; } - - // In real implementation, you would: - // 1. Query EntityManager for entities in sight range - // 2. Filter by faction (hostile to player, etc.) - // 3. Select closest or most threatening target - - // For now, this is a placeholder } void NPCEntity::UpdateHateList(uint64_t attacker_id, float damage) { if (attacker_id == 0 || attacker_id == GetId()) return; - - // Add damage to the attacker's total damage_taken_[attacker_id] += damage; - - // Update hate list order hate_list_.clear(); for (const auto& [id, dmg] : damage_taken_) { hate_list_.push_back(id); } - - // Sort by damage dealt (descending) std::sort(hate_list_.begin(), hate_list_.end(), - [this](uint64_t a, uint64_t b) { - return damage_taken_[a] > damage_taken_[b]; - }); - - // Limit hate list size + [this](uint64_t a, uint64_t b) { + return damage_taken_[a] > damage_taken_[b]; + }); if (hate_list_.size() > 10) { hate_list_.resize(10); } - - // Update target to top hated if (!hate_list_.empty()) { SetTarget(hate_list_[0]); } @@ -1028,28 +931,19 @@ void NPCEntity::PerformAttack() { if (!HasTarget() || attack_cooldown_ > 0.0f) { return; } - - // Calculate damage with critical chance std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(0.0f, 1.0f); - float damage = npc_stats_.attack_damage; - - // Critical hit check if (dis(gen) < npc_stats_.critical_chance) { damage *= npc_stats_.critical_damage; - Logger::Debug("NPC {} critical hit on target {}: {:.1f} damage", - GetId(), target_id_, damage); - } else { - Logger::Debug("NPC {} attacks target {}: {:.1f} damage", - GetId(), target_id_, damage); + // Logger::Trace("NPC {} critical hit on target {}: {:.1f} damage", + // GetId(), target_id_, damage); + // } else { + // Logger::Trace("NPC {} attacks target {}: {:.1f} damage", + // GetId(), target_id_, damage); } - - // Apply damage to target (would be handled by combat system) FireEvent("on_attack"); - - // Reset attack cooldown attack_cooldown_ = ATTACK_COOLDOWN_BASE / npc_stats_.attack_speed; } @@ -1057,58 +951,38 @@ void NPCEntity::SummonAllies() { if (!ai_profile_.can_summon_allies || summon_cooldown_ > 0.0f) { return; } - - int allies_to_summon = std::min(ai_profile_.max_allies, 5); // Limit + int allies_to_summon = std::min(ai_profile_.max_allies, 5); Logger::Debug("NPC {} summoning {} allies", GetId(), allies_to_summon); - - // This would create new NPC entities around this NPC - // For now, just log it - summon_cooldown_ = ai_profile_.summon_cooldown; } void NPCEntity::TakeDamage(float damage, uint64_t attacker_id) { if (IsDead() || damage <= 0.0f) return; - - // Update hate list UpdateHateList(attacker_id, damage); - - // Call base class to apply damage GameEntity::TakeDamage(damage, attacker_id); - - // Update AI state based on damage if (IsAlive()) { - // If we have a target and are not already chasing/attacking, start chasing if (HasTarget() && ai_state_ != NPCAIState::CHASE && ai_state_ != NPCAIState::ATTACK) { SetAIState(NPCAIState::CHASE); } - - // Check for stun (simplified) - if (damage > GetMaxHealth() * 0.3f) { // Large hit stuns + if (damage > GetMaxHealth() * 0.3f) { stun_timer_ = STUN_DURATION; Stop(); } - } else { - // Died + } else { // Died SetAIState(NPCAIState::DEAD); - - // Generate loot and experience if (ai_profile_.drops_loot) { auto loot = GenerateLoot(); int gold = GenerateGold(); - - Logger::Debug("NPC {} died. Loot: {} items, {} gold", - GetId(), loot.size(), gold); + (void)gold; + // Logger::Trace("NPC {} died. Loot: {} items, {} gold", + // GetId(), loot.size(), gold); } } } void NPCEntity::Heal(float amount, uint64_t healer_id) { GameEntity::Heal(amount, healer_id); - - // Update AI if healed significantly if (amount > GetMaxHealth() * 0.2f && ai_state_ == NPCAIState::FLEE) { - // Stop fleeing if healed enough SetAIState(NPCAIState::IDLE); } } @@ -1128,25 +1002,18 @@ void NPCEntity::ClearPatrolPoints() { glm::vec3 NPCEntity::GetNextPatrolPoint() { if (patrol_queue_.empty()) { - // Regenerate patrol queue from points for (const auto& point : ai_profile_.patrol_points) { patrol_queue_.push(point); } - - // If still empty, return current position if (patrol_queue_.empty()) { return GetPosition(); } } - glm::vec3 next_point = patrol_queue_.front(); patrol_queue_.pop(); - - // If looping, add back to end if (ai_profile_.patrol_loop) { patrol_queue_.push(next_point); } - return next_point; } @@ -1171,41 +1038,30 @@ void NPCEntity::RemoveDropItem(const std::string& item_id) { std::vector> NPCEntity::GenerateLoot() const { std::vector> loot; - if (!ai_profile_.drops_loot) { return loot; } - std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(0.0f, 1.0f); - - // Check if loot should drop if (dis(gen) > loot_table_.drop_chance) { return loot; } - - // Determine number of items std::uniform_int_distribution count_dis( loot_table_.min_items, loot_table_.max_items ); int item_count = count_dis(gen); - - // Generate items for (int i = 0; i < item_count && !loot_table_.item_drop_rates.empty(); ++i) { for (const auto& [item_id, drop_rate] : loot_table_.item_drop_rates) { if (dis(gen) <= drop_rate) { - // Determine quantity (usually 1, but could be more for stackable items) std::uniform_int_distribution qty_dis(1, 3); int quantity = qty_dis(gen); - loot.emplace_back(item_id, quantity); - break; // One item per iteration + break; } } } - return loot; } @@ -1216,7 +1072,6 @@ int NPCEntity::GenerateGold() const { npc_stats_.min_gold, npc_stats_.max_gold ); - return dis(gen); } @@ -1227,8 +1082,6 @@ void NPCEntity::SetDialogue(const NPCDialogue& dialogue) { void NPCEntity::AddDialogueTopic(const std::string& topic, const std::string& response) { dialogue_.responses[topic] = response; - - // Add to topics list if not already there if (std::find(dialogue_.topics.begin(), dialogue_.topics.end(), topic) == dialogue_.topics.end()) { dialogue_.topics.push_back(topic); } @@ -1287,81 +1140,54 @@ int NPCEntity::GetItemPrice(const std::string& item_id) const { // =============== Spawn/Respawn =============== void NPCEntity::Respawn() { if (!IsDead()) return; - - // Reset position to spawn point SetPosition(spawn_position_); - - // Reset stats CalculateStats(); SetHealth(GetMaxHealth()); - - // Reset AI state ClearHateList(); SetAIState(NPCAIState::SPAWNING); - - // Reset visual state SetActive(true); SetVisible(true); SetCollidable(true); - - Logger::Debug("NPC {} respawned at [{:.1f}, {:.1f}, {:.1f}]", - GetId(), spawn_position_.x, spawn_position_.y, spawn_position_.z); + // Logger::Trace("NPC {} respawned at [{:.1f}, {:.1f}, {:.1f}]", + // GetId(), spawn_position_.x, spawn_position_.y, spawn_position_.z); } // =============== Serialization =============== nlohmann::json NPCEntity::Serialize() const { nlohmann::json json = GameEntity::Serialize(); - - // NPC-specific data json["npc_type"] = static_cast(npc_type_); json["rarity"] = static_cast(rarity_); json["faction"] = static_cast(faction_); json["ai_state"] = static_cast(ai_state_); - - // Spawn position json["spawn_position"] = {spawn_position_.x, spawn_position_.y, spawn_position_.z}; - - // Targeting json["target_id"] = target_id_; - - // Serialize NPC systems SaveStatsToJson(json); SaveAIProfileToJson(json); SaveLootTableToJson(json); SaveDialogueToJson(json); SaveQuestsToJson(json); SaveTradeItemsToJson(json); - - // Serialize hate list and damage taken json["hate_list"] = hate_list_; - nlohmann::json damage_json; for (const auto& [attacker_id, damage] : damage_taken_) { damage_json[std::to_string(attacker_id)] = damage; } json["damage_taken"] = damage_json; - - // Serialize timers json["state_timer"] = state_timer_; json["idle_timer"] = idle_timer_; json["attack_cooldown"] = attack_cooldown_; json["stun_timer"] = stun_timer_; json["flee_timer"] = flee_timer_; json["summon_cooldown"] = summon_cooldown_; - return json; } void NPCEntity::Deserialize(const nlohmann::json& data) { GameEntity::Deserialize(data); - - // NPC-specific data npc_type_ = static_cast(data.value("npc_type", 0)); rarity_ = static_cast(data.value("rarity", 0)); faction_ = static_cast(data.value("faction", 0)); ai_state_ = static_cast(data.value("ai_state", 0)); - - // Spawn position if (data.contains("spawn_position") && data["spawn_position"].is_array() && data["spawn_position"].size() >= 3) { spawn_position_.x = data["spawn_position"][0]; @@ -1370,24 +1196,17 @@ void NPCEntity::Deserialize(const nlohmann::json& data) { } else { spawn_position_ = GetPosition(); } - - // Targeting target_id_ = data.value("target_id", 0); - - // Deserialize NPC systems LoadStatsFromJson(data); LoadAIProfileFromJson(data); LoadLootTableFromJson(data); LoadDialogueFromJson(data); LoadQuestsFromJson(data); LoadTradeItemsFromJson(data); - - // Deserialize hate list and damage taken hate_list_.clear(); if (data.contains("hate_list") && data["hate_list"].is_array()) { hate_list_ = data["hate_list"].get>(); } - damage_taken_.clear(); if (data.contains("damage_taken")) { for (const auto& [attacker_str, damage] : data["damage_taken"].items()) { @@ -1395,16 +1214,12 @@ void NPCEntity::Deserialize(const nlohmann::json& data) { damage_taken_[attacker_id] = damage.get(); } } - - // Deserialize timers state_timer_ = data.value("state_timer", 0.0f); idle_timer_ = data.value("idle_timer", 0.0f); attack_cooldown_ = data.value("attack_cooldown", 0.0f); stun_timer_ = data.value("stun_timer", 0.0f); flee_timer_ = data.value("flee_timer", 0.0f); summon_cooldown_ = data.value("summon_cooldown", 0.0f); - - // Update name based on type name_ = GetNPCTypeString() + "_" + std::to_string(GetId()); } @@ -1492,10 +1307,7 @@ void NPCEntity::OnDestroy() { void NPCEntity::OnCollision(std::shared_ptr other) { GameEntity::OnCollision(other); - - // NPC-specific collision logic if (other && other->GetType() == EntityType::PLAYER) { - // If aggressive and sees player, attack if (ai_profile_.is_aggressive && ai_state_ != NPCAIState::ATTACK) { SetTarget(other->GetId()); SetAIState(NPCAIState::ATTACK); @@ -1506,26 +1318,19 @@ void NPCEntity::OnCollision(std::shared_ptr other) { // =============== Fixed Update =============== void NPCEntity::FixedUpdate(float delta_time) { GameEntity::FixedUpdate(delta_time); - - // NPC-specific physics updates - // (Could include pathfinding, collision avoidance, etc.) } void NPCEntity::MoveTo(const glm::vec3& destination, float speed_multiplier) { move_target_ = destination; move_speed_multiplier_ = speed_multiplier; has_move_target_ = true; - - // Calculate direction and set velocity glm::vec3 direction = destination - GetPosition(); float distance = glm::length(direction); if (distance > 0.01f) { direction /= distance; - // Use npc_stats_.move_speed as base speed multiplied by multiplier float base_speed = npc_stats_.move_speed; SetVelocity(direction * base_speed * speed_multiplier); } else { - // Already at target Stop(); has_move_target_ = false; } diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 91cb58e..0c3a477 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -1641,3 +1641,195 @@ std::string Player::GetRaceString(PlayerRace race) const { default: return "Unknown"; } } + +uint64_t Player::GetId() const { + return id_; +} + +const std::string& Player::GetUsername() const { + return username_; +} + +void Player::SetPlayerClass(PlayerClass player_class) { + player_class_ = player_class; +} + +PlayerClass Player::GetPlayerClass() const { + return player_class_; +} + +void Player::SetPlayerRace(PlayerRace race) { + race_ = race; +} + +PlayerRace Player::GetPlayerRace() const { + return race_; +} + +void Player::SetStatus(PlayerStatus status) { + status_ = status; +} + +PlayerStatus Player::GetStatus() const { + return status_; +} + +int Player::GetHealth() const { + return stats_.health; +} + +int Player::GetMaxHealth() const { + return stats_.max_health; +} + +int Player::GetMana() const { + return stats_.mana; +} + +int Player::GetMaxMana() const { + return stats_.max_mana; +} + +float Player::GetAttackDamage() const { + return stats_.attack_damage; +} + +float Player::GetAttackRange() const { + return stats_.attack_range; +} + +int Player::GetLevel() const { + return stats_.level; +} + +int64_t Player::GetExperience() const { + return stats_.experience; +} + +int64_t Player::GetExperienceToNextLevel() const { + return stats_.experience_to_next_level; +} + +const PlayerAttributes& Player::GetAttributes() const { + return attributes_; +} + +const PlayerEquipment& Player::GetEquipment() const { + return equipment_; +} + +int64_t Player::GetGold() const { + return stats_.currency_gold; +} + +int64_t Player::GetGems() const { + return stats_.currency_gems; +} + +InventorySystem& Player::GetInventorySystem() const { + return inventory_system_; +} + +SkillSystem& Player::GetSkillSystem() const { + return skill_system_; +} + +QuestManager& Player::GetQuestManager() const { + return quest_manager_; +} + +bool Player::IsAlive() const { + return stats_.health > 0; +} + +bool Player::IsDead() const { + return stats_.health <= 0; +} + +bool Player::IsInCombat() const { + return status_ == PlayerStatus::COMBAT; +} + +bool Player::IsCasting() const { + return status_ == PlayerStatus::CASTING; +} + +bool Player::IsMoving() const { + return status_ == PlayerStatus::MOVING; +} + +float Player::GetAttackPower() const { + return attributes_.attack_power; +} + +float Player::GetDefense() const { + return attributes_.defense; +} + +float Player::GetCriticalChance() const { + return attributes_.critical_chance; +} + +float Player::GetMoveSpeed() const { + return attributes_.move_speed; +} + +int Player::GetAchievementCount() const { + return achievements_.size(); +} + +const std::string& Player::GetTitle() const { + return title_; +} + +void Player::SetSessionId(uint64_t session_id) { + session_id_ = session_id; +} + +uint64_t Player::GetSessionId() const { + return session_id_; +} + +void Player::SetConnectionQuality(float quality) { + connection_quality_ = quality; +} + +float Player::GetConnectionQuality() const { + return connection_quality_; +} + +bool Player::IsOnline() const { + return online_; +} + +void Player::SetBanned(bool banned) { + banned_ = banned; +} + +bool Player::IsBanned() const { + return banned_; +} + +void Player::SetBanReason(const std::string& reason) { + ban_reason_ = reason; +} + +const std::string& Player::GetBanReason() const { + return ban_reason_; +} + +void Player::SetBanExpires(std::chrono::system_clock::time_point expires) { + ban_expires_ = expires; +} + +std::chrono::system_clock::time_point Player::GetBanExpires() const { + return ban_expires_; +} + +bool Player::IsOnGround() const { + return onGround_; +} + +void Player::SetOnGround(bool g) { + onGround_ = g; +} diff --git a/src/logging/Logger.cpp b/src/logging/Logger.cpp index 9735932..b79fc1f 100644 --- a/src/logging/Logger.cpp +++ b/src/logging/Logger.cpp @@ -3,6 +3,12 @@ // Static member initialization (no longer inline in header) std::shared_ptr Logger::logger_; +void Logger::Flush() { + if (logger_) { + logger_->flush(); + } +} + void Logger::InitializeDefaults() { auto console_sink = std::make_shared(); console_sink->set_level(spdlog::level::info); @@ -44,15 +50,13 @@ void Logger::SetupLogger(const std::string& loggerName) { } auto file_sink = std::make_shared( - logFilePath, - config.GetMaxLogFileSize() * 1024 * 1024, // Convert MB to bytes - config.GetMaxLogFiles() + logFilePath, config.GetMaxLogFileSize(), config.GetMaxLogFiles() ); file_sink->set_level(logLevel); file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%P] %v"); sinks.push_back(file_sink); - } catch (const std::exception& e) { - // If file logging fails, fall back to console only + } catch (const std::exception& err) { + std::cerr << "[LOGGER ERROR] " << err.what() << std::endl; if (sinks.empty()) { auto console_sink = std::make_shared(); console_sink->set_level(logLevel); diff --git a/src/main.cpp b/src/main.cpp index f43579d..ab3e565 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,20 +4,17 @@ #include #include -#include -//#include - #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" -#include "network/GameServer.hpp" -#include "process/ProcessPool.hpp" - #include "database/DbManager.hpp" +#include "database/DbService.hpp" -#include "game/GameLogic.hpp" +#include "process/ProcessPool.hpp" +#include "network/GameServer.hpp" +#include "game/GameLogic.hpp" std::atomic g_shutdown(false); @@ -26,16 +23,18 @@ void SignalHandler(int signal) { g_shutdown.store(true); } -void crash_handler(int sig) { - void* array[20]; - size_t size = backtrace(array, 20); - fprintf(stderr, "Error: signal %d:\n", sig); - backtrace_symbols_fd(array, size, STDERR_FILENO); - _exit(1); -} +//#include +//#include +// void crash_handler(int sig) { +// void* array[20]; +// size_t size = backtrace(array, 20); +// fprintf(stderr, "Error: signal %d:\n", sig); +// backtrace_symbols_fd(array, size, STDERR_FILENO); +// _exit(1); +// } -// Worker main signature: receives group config -void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* processPool = nullptr, const std::string& path_config = "config.json") { + +void worker(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* processPool = nullptr, const std::string& path_config = "config.json") { try { auto& config = ConfigManager::GetInstance(); @@ -70,9 +69,9 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* dbConfig["user"] = config.GetDatabaseUser(); dbConfig["password"] = config.GetDatabasePassword(); - dbConfig["max_connections"] = config.GetInt("database.max_connections", 20); - dbConfig["min_connections"] = config.GetInt("database.min_connections", 5); - dbConfig["connection_timeout_ms"] = config.GetInt("database.connection_timeout_ms", 5000); + dbConfig["max_connections"] = config.GetInt("database.connection_pool.max_connections", 20); + dbConfig["min_connections"] = config.GetInt("database.connection_pool.min_connections", 5); + dbConfig["connection_timeout_ms"] = config.GetInt("database.connection_pool.connection_timeout_ms", 5000); if (!dbManager.Initialize(path_config)) { Logger::Error("Worker {} failed to initialize database", workerId); @@ -102,80 +101,26 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* GameServer server(groupConfig, config); - if (groupConfig.protocol == "binary") { - server.SetSessionFactory([workerId, processPool, &groupConfig] - (asio::ip::tcp::socket socket, - std::shared_ptr sslCtx) - { - auto session = std::make_shared(std::move(socket), sslCtx); - Logger::Debug("Worker {} created new game session {}", - workerId, session->GetSessionId()); - - session->SetMessageHandler([session, workerId, processPool](const nlohmann::json& msg) { - try { - std::string msgType = msg.value("type", ""); - Logger::Debug("Worker {} processing message type: {}", workerId, msgType); - if (msgType == "ipc_message" && processPool) { - if (msg.contains("target_worker") && msg.contains("payload")) { - int targetWorker = msg["target_worker"]; - std::string payload = msg["payload"].dump(); - if (processPool->SendToWorker(targetWorker, payload)) { - Logger::Debug("Worker {} sent IPC message to worker {}", - workerId, targetWorker); - } else { - Logger::Error("Worker {} failed to send IPC message to worker {}", - workerId, targetWorker); - } - } - } else { - GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); - } - } catch (const std::exception& e) { - Logger::Error("Worker {} error processing message: {}", workerId, e.what()); - session->SendError("Internal server error", 500); - } - }); - - session->SetDefaultBinaryMessageHandler([session, workerId](uint16_t type, - const std::vector& data) { - GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data); - }); - - session->SetCloseHandler([session, workerId]() { - Logger::Info("Worker {} session {} closing", workerId, session->GetSessionId()); - GameLogic::GetInstance().OnPlayerDisconnected(session->GetSessionId()); - Logger::Debug("Worker {} session {} cleanup complete", workerId, session->GetSessionId()); - }); - return session; - }); - } else if (groupConfig.protocol == "websocket") { - server.SetWebSocketConnectionFactory([workerId, processPool, &groupConfig](asio::ip::tcp::socket socket, std::shared_ptr /*sslCtx*/) { - auto wsConn = std::make_shared(std::move(socket)); - // SSL handling would be added later if needed - return wsConn; - }); - } + server.InitSessionFactory(workerId, processPool, gameLogic); + server.RegisterCallbacks(groupConfig.protocol, gameLogic); gameLogic.SetConnectionManager(ConnectionManager::GetInstancePtr()); gameLogic.Initialize(); + DatabaseService dbService(server.GetIoContext(), config.GetInt("database.pool_threads", 2)); + gameLogic.SetDatabaseService(&dbService); + if (config.ShouldPreloadWorld()) { Logger::Info("Worker {} preloading world data...", workerId); gameLogic.PreloadWorldData(config.GetWorldPreloadRadius()); } - std::thread signalThread([workerId, &server]() { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGTERM); - int sig; - int ret = sigwait(&set, &sig); - if (ret == 0) { - Logger::Info("Worker {} received SIGTERM via sigwait, initiating shutdown", workerId); + asio::signal_set signals(server.GetIoContext(), SIGINT, SIGTERM); + signals.async_wait([&](std::error_code ec, int signum) { + if (!ec) { + Logger::Info("Worker {} received signal {}, shutting down", workerId, signum); g_shutdown.store(true); server.Shutdown(); - } else { - Logger::Error("Worker {} sigwait failed: {}", workerId, strerror(errno)); } }); @@ -189,7 +134,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* auto lastCleanupTime = std::chrono::steady_clock::now(); auto lastIPCCheckTime = std::chrono::steady_clock::now(); - auto lastPlayerUpdate = std::chrono::steady_clock::now(); + //auto lastPlayerUpdate = std::chrono::steady_clock::now(); while (worldMaintenanceRunning && !g_shutdown.load()) { auto currentTime = std::chrono::steady_clock::now(); @@ -201,7 +146,6 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* lastCleanupTime = currentTime; } - // Check for IPC messages every 10 ms and drain all pending auto elapsedIPC = std::chrono::duration_cast( currentTime - lastIPCCheckTime); if (elapsedIPC.count() >= 10 && processPool) { @@ -219,11 +163,11 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* } // Player updates every 100 ms - if (std::chrono::duration_cast(currentTime - lastPlayerUpdate).count() >= 100) { - gameLogic.BroadcastPlayerUpdates(); - gameLogic.BroadcastPlayerUpdatesJson(); - lastPlayerUpdate = currentTime; - } + // if (std::chrono::duration_cast(currentTime - lastPlayerUpdate).count() >= 100) { + // gameLogic.BroadcastPlayerUpdates(); + // gameLogic.BroadcastPlayerUpdatesJson(); + // lastPlayerUpdate = currentTime; + // } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -235,7 +179,7 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* while (!g_shutdown.load()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(60)); Logger::Error("Worker {} watchdog triggered – forcing exit", workerId); _exit(1); }); @@ -253,11 +197,10 @@ void WorkerMain(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* Logger::Critical("Worker {} failed to initialize server", workerId); } - if (signalThread.joinable()) { signalThread.join(); } - Logger::Info("Worker {} beginning cleanup...", workerId); gameLogic.Shutdown(); + dbService.shutdown(); dbManager.Disconnect(); dbManager.Shutdown(); @@ -332,11 +275,11 @@ int main(int argc, char* argv[]) { Logger::Info("Process pool configured: max message size = {} bytes, timeout = {}ms", maxMessageSize, receiveTimeout); - auto workerMainWithPool = [&processPool, &conf_path](int workerId, const WorkerGroupConfig& groupConfig) { - WorkerMain(workerId, groupConfig, &processPool, conf_path); + auto worker_pool = [&processPool, &conf_path](int workerId, const WorkerGroupConfig& groupConfig) { + worker(workerId, groupConfig, &processPool, conf_path); }; - processPool.SetWorkerMain(workerMainWithPool); + processPool.SetWorkerMain(worker_pool); Logger::Info("Starting {} worker processes", processPool.GetTotalWorkerCount()); processPool.Run(); diff --git a/src/network/GameSession.cpp b/src/network/BinarySession.cpp similarity index 83% rename from src/network/GameSession.cpp rename to src/network/BinarySession.cpp index 04c8592..e025948 100644 --- a/src/network/GameSession.cpp +++ b/src/network/BinarySession.cpp @@ -1,11 +1,11 @@ -#include "network/GameSession.hpp" +#include "network/BinarySession.hpp" // Static member initialization -std::atomic GameSession::nextSessionId_{1}; +std::atomic BinarySession::nextSessionId_{1}; // =============== Constructor and Destructor =============== -GameSession::GameSession(asio::ip::tcp::socket socket, +BinarySession::BinarySession(asio::ip::tcp::socket socket, std::shared_ptr ssl_context) : socket_(std::move(socket)) , ssl_context_(ssl_context) @@ -51,24 +51,24 @@ GameSession::GameSession(asio::ip::tcp::socket socket, rate_limit_.tokens = rate_limit_.burst_size; rate_limit_.last_refill = std::chrono::steady_clock::now(); - Logger::Info("GameSession {} created for {}", + Logger::Info("BinarySession {} created for {}", sessionId_, GetRemoteEndpoint().address().to_string()); } -GameSession::~GameSession() { +BinarySession::~BinarySession() { Stop(); - Logger::Debug("GameSession {} destroyed", sessionId_); + Logger::Debug("BinarySession {} destroyed", sessionId_); } // =============== Core Session Management =============== -void GameSession::Start() { +void BinarySession::Start() { if (!connected_) { Logger::Warn("Session {} already closed", sessionId_); return; } - Logger::Debug("Starting GameSession {}", sessionId_); + Logger::Debug("Starting BinarySession {}", sessionId_); if (ssl_stream_) { // Start TLS handshake for encrypted connections @@ -79,7 +79,7 @@ void GameSession::Start() { } } -void GameSession::StartTLSHandshake() { +void BinarySession::StartTLSHandshake() { if (!ssl_stream_) return; auto self = shared_from_this(); @@ -97,7 +97,7 @@ void GameSession::StartTLSHandshake() { }); } -void GameSession::StartProtocolNegotiation() { +void BinarySession::StartProtocolNegotiation() { // Send protocol capabilities to client SendProtocolCapabilities(); @@ -113,80 +113,71 @@ void GameSession::StartProtocolNegotiation() { // Start network adaptation monitoring StartNetworkAdaptation(); - Logger::Info("GameSession {} started", sessionId_); + Logger::Info("BinarySession {} started", sessionId_); } -void GameSession::Stop() { +void BinarySession::Stop() { if (closing_.exchange(true)) { - return; // Already closing + return; } - - Logger::Debug("Stopping GameSession {}", sessionId_); - + Logger::Debug("Stopping BinarySession {}", sessionId_); connected_ = false; - - // Cancel all timers try { heartbeat_timer_.cancel(); - } catch (const std::exception& e) { - Logger::Debug("Error cancelling heartbeat timer: {}", e.what()); + } catch (const std::exception& err) { + Logger::Debug("Error cancelling heartbeat timer: {}", err.what()); } - try { shutdown_timer_.cancel(); - } catch (const std::exception& e) { - Logger::Debug("Error cancelling shutdown timer: {}", e.what()); + } catch (const std::exception& err) { + Logger::Debug("Error cancelling shutdown timer: {}", err.what()); } - try { network_adaptation_timer_.cancel(); - } catch (const std::exception& e) { - Logger::Debug("Error cancelling network adaptation timer: {}", e.what()); + } catch (const std::exception& err) { + Logger::Debug("Error cancelling network adaptation timer: {}", err.what()); } - std::error_code ec; if (ec) { Logger::Debug("Error cancelling timers: {}", ec.message()); } - - // Close socket if (GetSocket().is_open()) { try { + GetSocket().cancel(ec); + if (ec) { + Logger::Debug("Error cancel socket: {}", ec.message()); + } GetSocket().shutdown(asio::ip::tcp::socket::shutdown_both, ec); if (ec && ec != asio::error::not_connected) { Logger::Debug("Error shutting down socket: {}", ec.message()); } - GetSocket().close(ec); if (ec) { Logger::Debug("Error closing socket: {}", ec.message()); } - } catch (const std::exception& e) { - Logger::Error("Exception closing socket: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("Exception closing socket: {}", err.what()); } } - - // Notify close handler if (close_handler_) { try { close_handler_(); - } catch (const std::exception& e) { - Logger::Error("Error in close handler: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("Error in close handler: {}", err.what()); } } - - Logger::Info("GameSession {} stopped", sessionId_); + Logger::Info("BinarySession {} stopped", sessionId_); } -void GameSession::Disconnect() { +void BinarySession::Disconnect() { Stop(); } -bool GameSession::IsConnected() const { +bool BinarySession::IsConnected() const { return connected_ && !closing_ && GetSocket().is_open(); } -asio::ip::tcp::endpoint GameSession::GetRemoteEndpoint() const { +asio::ip::tcp::endpoint BinarySession::GetRemoteEndpoint() const { try { // if (GetSocket().is_open()) { // return GetSocket().remote_endpoint(); @@ -203,8 +194,7 @@ asio::ip::tcp::endpoint GameSession::GetRemoteEndpoint() const { } // =============== Binary Protocol Implementation =============== - -void GameSession::DoBinaryRead() { +void BinarySession::DoBinaryRead() { if (!connected_ || closing_) return; auto self = shared_from_this(); @@ -215,7 +205,7 @@ void GameSession::DoBinaryRead() { asio::async_read(GetSocket(), asio::buffer(&header, sizeof(BinaryProtocol::NetworkHeader)), [self, header](std::error_code ec, std::size_t length) mutable { - Logger::Debug("GameSession::DoBinaryRead asio::async_read length = {}", length); + Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); if (ec) { self->HandleNetworkError(ec); return; @@ -247,18 +237,30 @@ void GameSession::DoBinaryRead() { return; } + // Create deadline timer for payload read + auto deadline = std::make_shared(self->GetSocket().get_executor()); + deadline->expires_after(std::chrono::seconds(10)); + deadline->async_wait([self, deadline](std::error_code ec) { + if (!ec && self->connected_) { + Logger::Warn("Session {}: payload read timeout", self->sessionId_); + self->Stop(); + } + }); + // Read message body std::vector body(header.length); asio::async_read(self->GetSocket(), asio::buffer(body), - [self, header, body](std::error_code ec, std::size_t length) mutable { - Logger::Debug("GameSession::DoBinaryRead asio::async_read length = {}", length); + [self, header, body, deadline](std::error_code ec, std::size_t length) mutable { + deadline->cancel(); // Cancel timeout on successful read or error if (ec) { self->HandleNetworkError(ec); return; } + Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); + // Verify checksum uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); if (calculated != header.checksum) { @@ -307,7 +309,7 @@ void GameSession::DoBinaryRead() { }); } -void GameSession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& message) { +void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& message) { // Update heartbeat on any valid message last_heartbeat_ = std::chrono::steady_clock::now(); @@ -331,6 +333,17 @@ void GameSession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& messa HandleProtocolNegotiation(message.data); return; + case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { + BinaryProtocol::BinaryReader reader(message.data.data(), message.data.size()); + ChunkRequestData req; + req.chunk_x = reader.ReadInt32(); + req.chunk_z = reader.ReadInt32(); + req.lod = reader.ReadUInt8(); + req.session_id = sessionId_; + GameLogic::GetInstance().OnChunkRequest(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_ERROR: Logger::Warn("Session {} received error from client", sessionId_); return; @@ -368,7 +381,7 @@ void GameSession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& messa } } -void GameSession::SendBinary(uint16_t message_type, const std::vector& data) { +void BinarySession::SendBinary(uint16_t message_type, const std::vector& data) { if (!connected_ || closing_) { Logger::Warn("Session {} not connected, cannot send binary", sessionId_); return; @@ -420,14 +433,14 @@ void GameSession::SendBinary(uint16_t message_type, const std::vector& } } -void GameSession::SendBinary(uint16_t message_type, const void* data, size_t length) { +void BinarySession::SendBinary(uint16_t message_type, const void* data, size_t length) { SendBinary(message_type, std::vector( static_cast(data), static_cast(data) + length )); } -void GameSession::SendBinaryWithAck(uint16_t message_type, const std::vector& data) { +void BinarySession::SendBinaryWithAck(uint16_t message_type, const std::vector& data) { // Store in pending acks for reliability uint32_t sequence = next_sequence_.load(); @@ -464,7 +477,7 @@ void GameSession::SendBinaryWithAck(uint16_t message_type, const std::vector lock(write_mutex_); @@ -500,7 +513,7 @@ void GameSession::DoBinaryWrite() { }); } -void GameSession::SendAcknowledgment(uint32_t sequence) { +void BinarySession::SendAcknowledgment(uint32_t sequence) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt32(sequence); @@ -523,7 +536,7 @@ void GameSession::SendAcknowledgment(uint32_t sequence) { } } -void GameSession::ProcessAcknowledgment(uint32_t sequence) { +void BinarySession::ProcessAcknowledgment(uint32_t sequence) { std::lock_guard lock(ack_mutex_); auto it = pending_acks_.find(sequence); @@ -540,7 +553,7 @@ void GameSession::ProcessAcknowledgment(uint32_t sequence) { } } -void GameSession::SendBinaryError(uint16_t message_type, const std::string& error_message, int code) { +void BinarySession::SendBinaryError(uint16_t message_type, const std::string& error_message, int code) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt32(static_cast(code)); writer.WriteString(error_message); @@ -552,7 +565,7 @@ void GameSession::SendBinaryError(uint16_t message_type, const std::string& erro // =============== Protocol Negotiation =============== -void GameSession::SendProtocolCapabilities() { +void BinarySession::SendProtocolCapabilities() { BinaryProtocol::ProtocolCapabilities caps; caps.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; caps.supports_compression = true; @@ -568,7 +581,7 @@ void GameSession::SendProtocolCapabilities() { SendBinary(BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION, caps_data); } -void GameSession::HandleProtocolNegotiation(const std::vector& data) { +void BinarySession::HandleProtocolNegotiation(const std::vector& data) { try { auto client_caps = BinaryProtocol::ProtocolCapabilities::Deserialize( data.data(), data.size()); @@ -603,7 +616,7 @@ void GameSession::HandleProtocolNegotiation(const std::vector& data) { // =============== JSON Compatibility Methods =============== -void GameSession::Send(const nlohmann::json& message) { +void BinarySession::Send(const nlohmann::json& message) { try { std::string data = message.dump() + "\n"; SendRaw(data); @@ -613,7 +626,7 @@ void GameSession::Send(const nlohmann::json& message) { } } -void GameSession::SendRaw(const std::string& data) { +void BinarySession::SendRaw(const std::string& data) { if (!connected_ || closing_) { Logger::Warn("Session {} not connected, cannot send", sessionId_); return; @@ -633,7 +646,7 @@ void GameSession::SendRaw(const std::string& data) { } } -void GameSession::SendError(const std::string& message, int code) { +void BinarySession::SendError(const std::string& message, int code) { nlohmann::json error = { {"type", "error"}, {"code", code}, @@ -644,7 +657,7 @@ void GameSession::SendError(const std::string& message, int code) { Send(error); } -void GameSession::SendSuccess(const std::string& message, const nlohmann::json& data) { +void BinarySession::SendSuccess(const std::string& message, const nlohmann::json& data) { nlohmann::json success = { {"type", "success"}, {"message", message}, @@ -659,7 +672,7 @@ void GameSession::SendSuccess(const std::string& message, const nlohmann::json& Send(success); } -void GameSession::SendPing() { +void BinarySession::SendPing() { nlohmann::json ping = { {"type", "ping"}, {"timestamp", std::chrono::duration_cast( @@ -668,7 +681,7 @@ void GameSession::SendPing() { Send(ping); } -void GameSession::SendPong() { +void BinarySession::SendPong() { nlohmann::json pong = { {"type", "pong"}, {"timestamp", std::chrono::duration_cast( @@ -679,7 +692,7 @@ void GameSession::SendPong() { // =============== Heartbeat Management =============== -void GameSession::StartHeartbeat() { +void BinarySession::StartHeartbeat() { if (!connected_ || closing_) return; // Set initial heartbeat time @@ -689,7 +702,7 @@ void GameSession::StartHeartbeat() { CheckHeartbeat(); } -void GameSession::CheckHeartbeat() { +void BinarySession::CheckHeartbeat() { if (!connected_ || closing_) return; heartbeat_timer_.expires_after(std::chrono::seconds(30)); @@ -725,13 +738,13 @@ void GameSession::CheckHeartbeat() { }); } -void GameSession::UpdateHeartbeat() { +void BinarySession::UpdateHeartbeat() { last_heartbeat_ = std::chrono::steady_clock::now(); } // =============== Network Quality Monitoring =============== -void GameSession::StartNetworkAdaptation() { +void BinarySession::StartNetworkAdaptation() { auto self = shared_from_this(); network_adaptation_timer_.expires_after(std::chrono::seconds(10)); @@ -743,7 +756,7 @@ void GameSession::StartNetworkAdaptation() { }); } -void GameSession::CheckNetworkConditions() { +void BinarySession::CheckNetworkConditions() { // Update network monitor statistics network_monitor_.Update(); @@ -793,43 +806,43 @@ void GameSession::CheckNetworkConditions() { } } -void GameSession::AdaptToNetworkConditions() { +void BinarySession::AdaptToNetworkConditions() { CheckNetworkConditions(); } // =============== Binary Message Handlers =============== -void GameSession::SetBinaryMessageHandler(uint16_t message_type, BinaryMessageHandler handler) { +void BinarySession::SetBinaryMessageHandler(uint16_t message_type, BinaryMessageHandler handler) { std::lock_guard lock(binary_handlers_mutex_); binary_handlers_[message_type] = std::move(handler); } -void GameSession::SetDefaultBinaryMessageHandler(BinaryMessageHandler handler) { +void BinarySession::SetDefaultBinaryMessageHandler(BinaryMessageHandler handler) { std::lock_guard lock(binary_handlers_mutex_); default_binary_handler_ = std::move(handler); } -void GameSession::SetMessageHandler(std::function handler) { +void BinarySession::SetMessageHandler(std::function handler) { message_handler_ = std::move(handler); } -void GameSession::SetCloseHandler(std::function handler) { +void BinarySession::SetCloseHandler(std::function handler) { close_handler_ = std::move(handler); } // =============== Session Statistics =============== -SessionStats GameSession::GetStats() const { +SessionStats BinarySession::GetStats() const { std::lock_guard lock(stats_mutex_); return stats_; } -void GameSession::ResetStats() { +void BinarySession::ResetStats() { std::lock_guard lock(stats_mutex_); stats_ = SessionStats{}; } -void GameSession::RecordMessageReceived(size_t size) { +void BinarySession::RecordMessageReceived(size_t size) { std::lock_guard lock(stats_mutex_); stats_.messages_received++; stats_.bytes_received += size; @@ -839,7 +852,7 @@ void GameSession::RecordMessageReceived(size_t size) { network_monitor_.RecordBytesReceived(size); } -void GameSession::RecordMessageSent(size_t size) { +void BinarySession::RecordMessageSent(size_t size) { std::lock_guard lock(stats_mutex_); stats_.messages_sent++; stats_.bytes_sent += size; @@ -851,17 +864,17 @@ void GameSession::RecordMessageSent(size_t size) { // =============== Compression =============== -void GameSession::SetCompressionEnabled(bool enabled) { +void BinarySession::SetCompressionEnabled(bool enabled) { compression_enabled_ = enabled; } -bool GameSession::IsCompressionEnabled() const { +bool BinarySession::IsCompressionEnabled() const { return compression_enabled_; } // =============== Rate Limiting =============== -void GameSession::SetRateLimit(int messages_per_second, int burst_size) { +void BinarySession::SetRateLimit(int messages_per_second, int burst_size) { std::lock_guard lock(rate_limit_mutex_); rate_limit_.messages_per_second = messages_per_second; rate_limit_.burst_size = burst_size; @@ -869,7 +882,7 @@ void GameSession::SetRateLimit(int messages_per_second, int burst_size) { rate_limit_.last_refill = std::chrono::steady_clock::now(); } -bool GameSession::CheckRateLimit() { +bool BinarySession::CheckRateLimit() { if (!rate_limit_enabled_) { return true; } @@ -903,40 +916,40 @@ bool GameSession::CheckRateLimit() { return false; } -void GameSession::SetRateLimitEnabled(bool enabled) { +void BinarySession::SetRateLimitEnabled(bool enabled) { rate_limit_enabled_ = enabled; } // =============== Session Groups =============== -void GameSession::JoinGroup(const std::string& groupId) { +void BinarySession::JoinGroup(const std::string& groupId) { std::lock_guard lock(groups_mutex_); joined_groups_.insert(groupId); } -void GameSession::LeaveGroup(const std::string& groupId) { +void BinarySession::LeaveGroup(const std::string& groupId) { std::lock_guard lock(groups_mutex_); joined_groups_.erase(groupId); } -void GameSession::LeaveAllGroups() { +void BinarySession::LeaveAllGroups() { std::lock_guard lock(groups_mutex_); joined_groups_.clear(); } -std::set GameSession::GetJoinedGroups() const { +std::set BinarySession::GetJoinedGroups() const { std::lock_guard lock(groups_mutex_); return joined_groups_; } -bool GameSession::IsInGroup(const std::string& groupId) const { +bool BinarySession::IsInGroup(const std::string& groupId) const { std::lock_guard lock(groups_mutex_); return joined_groups_.find(groupId) != joined_groups_.end(); } // =============== Authentication and Security =============== -void GameSession::Authenticate(const std::string& authToken) { +void BinarySession::Authenticate(const std::string& authToken) { std::lock_guard lock(auth_mutex_); auth_token_ = authToken; authenticated_ = true; @@ -945,7 +958,7 @@ void GameSession::Authenticate(const std::string& authToken) { Logger::Info("Session {} authenticated", sessionId_); } -void GameSession::Deauthenticate() { +void BinarySession::Deauthenticate() { std::lock_guard lock(auth_mutex_); auth_token_.clear(); authenticated_ = false; @@ -954,36 +967,36 @@ void GameSession::Deauthenticate() { Logger::Info("Session {} deauthenticated", sessionId_); } -bool GameSession::IsAuthenticated() const { +bool BinarySession::IsAuthenticated() const { std::lock_guard lock(auth_mutex_); return authenticated_; } -std::string GameSession::GetAuthToken() const { +std::string BinarySession::GetAuthToken() const { std::lock_guard lock(auth_mutex_); return auth_token_; } -void GameSession::SetPlayerId(int64_t playerId) { +void BinarySession::SetPlayerId(int64_t playerId) { std::lock_guard lock(auth_mutex_); player_id_ = playerId; Logger::Debug("Session {} assigned to player {}", sessionId_, playerId); } -int64_t GameSession::GetPlayerId() const { +int64_t BinarySession::GetPlayerId() const { std::lock_guard lock(auth_mutex_); return player_id_; } // =============== Session Data Storage =============== -void GameSession::SetData(const std::string& key, const nlohmann::json& value) { +void BinarySession::SetData(const std::string& key, const nlohmann::json& value) { std::lock_guard lock(data_mutex_); session_data_[key] = value; } -nlohmann::json GameSession::GetData(const std::string& key, const nlohmann::json& defaultValue) const { +nlohmann::json BinarySession::GetData(const std::string& key, const nlohmann::json& defaultValue) const { std::lock_guard lock(data_mutex_); auto it = session_data_.find(key); if (it != session_data_.end()) { @@ -992,22 +1005,22 @@ nlohmann::json GameSession::GetData(const std::string& key, const nlohmann::json return defaultValue; } -bool GameSession::HasData(const std::string& key) const { +bool BinarySession::HasData(const std::string& key) const { std::lock_guard lock(data_mutex_); return session_data_.find(key) != session_data_.end(); } -void GameSession::RemoveData(const std::string& key) { +void BinarySession::RemoveData(const std::string& key) { std::lock_guard lock(data_mutex_); session_data_.erase(key); } -void GameSession::ClearData() { +void BinarySession::ClearData() { std::lock_guard lock(data_mutex_); session_data_.clear(); } -nlohmann::json GameSession::GetAllData() const { +nlohmann::json BinarySession::GetAllData() const { std::lock_guard lock(data_mutex_); nlohmann::json result; for (const auto& [key, value] : session_data_) { @@ -1018,12 +1031,12 @@ nlohmann::json GameSession::GetAllData() const { // =============== Session Properties =============== -void GameSession::SetProperty(const std::string& key, const std::string& value) { +void BinarySession::SetProperty(const std::string& key, const std::string& value) { std::lock_guard lock(properties_mutex_); properties_[key] = value; } -std::string GameSession::GetProperty(const std::string& key, const std::string& defaultValue) const { +std::string BinarySession::GetProperty(const std::string& key, const std::string& defaultValue) const { std::lock_guard lock(properties_mutex_); auto it = properties_.find(key); if (it != properties_.end()) { @@ -1032,14 +1045,14 @@ std::string GameSession::GetProperty(const std::string& key, const std::string& return defaultValue; } -std::map GameSession::GetAllProperties() const { +std::map BinarySession::GetAllProperties() const { std::lock_guard lock(properties_mutex_); return properties_; } // =============== Metrics and Monitoring =============== -SessionMetrics GameSession::GetMetrics() const { +SessionMetrics BinarySession::GetMetrics() const { auto now = std::chrono::steady_clock::now(); auto connected_time = std::chrono::duration_cast( now - connected_time_); @@ -1087,7 +1100,7 @@ SessionMetrics GameSession::GetMetrics() const { return metrics; } -void GameSession::PrintMetrics() const { +void BinarySession::PrintMetrics() const { auto metrics = GetMetrics(); Logger::Info("Session {} Metrics:", metrics.session_id); @@ -1111,9 +1124,9 @@ void GameSession::PrintMetrics() const { // =============== Utility Methods =============== -std::string GameSession::ToString() const { +std::string BinarySession::ToString() const { std::stringstream ss; - ss << "GameSession[" << sessionId_ << "] "; + ss << "BinarySession[" << sessionId_ << "] "; try { auto endpoint = GetRemoteEndpoint(); @@ -1133,7 +1146,7 @@ std::string GameSession::ToString() const { return ss.str(); } -uint64_t GameSession::GetUptimeSeconds() const { +uint64_t BinarySession::GetUptimeSeconds() const { if (!connected_) { return 0; } @@ -1147,26 +1160,26 @@ uint64_t GameSession::GetUptimeSeconds() const { // =============== Connection Quality Monitoring =============== -void GameSession::RecordLatency(uint64_t latencyMs) { +void BinarySession::RecordLatency(uint64_t latencyMs) { network_monitor_.RecordLatencySample(latencyMs); } -uint64_t GameSession::GetAverageLatency() const { +uint64_t BinarySession::GetAverageLatency() const { auto metrics = network_monitor_.GetMetrics(); return metrics.average_latency_ms; } -uint64_t GameSession::GetMinLatency() const { +uint64_t BinarySession::GetMinLatency() const { auto metrics = network_monitor_.GetMetrics(); return metrics.min_latency_ms; } -uint64_t GameSession::GetMaxLatency() const { +uint64_t BinarySession::GetMaxLatency() const { auto metrics = network_monitor_.GetMetrics(); return metrics.max_latency_ms; } -std::vector GameSession::GetLatencySamples() const { +std::vector BinarySession::GetLatencySamples() const { // Note: NetworkQualityMonitor doesn't expose samples directly // In a full implementation, we would add this method return {}; @@ -1174,18 +1187,18 @@ std::vector GameSession::GetLatencySamples() const { // =============== Custom Event Handlers =============== -void GameSession::SetCustomEventHandler(const std::string& eventName, +void BinarySession::SetCustomEventHandler(const std::string& eventName, std::function handler) { std::lock_guard lock(event_handlers_mutex_); custom_event_handlers_[eventName] = handler; } -void GameSession::RemoveCustomEventHandler(const std::string& eventName) { +void BinarySession::RemoveCustomEventHandler(const std::string& eventName) { std::lock_guard lock(event_handlers_mutex_); custom_event_handlers_.erase(eventName); } -void GameSession::HandleCustomEvent(const std::string& eventName, const nlohmann::json& data) { +void BinarySession::HandleCustomEvent(const std::string& eventName, const nlohmann::json& data) { std::lock_guard lock(event_handlers_mutex_); auto it = custom_event_handlers_.find(eventName); if (it != custom_event_handlers_.end()) { @@ -1199,29 +1212,29 @@ void GameSession::HandleCustomEvent(const std::string& eventName, const nlohmann // =============== Message Queue Management =============== -size_t GameSession::GetPendingMessageCount() const { +size_t BinarySession::GetPendingMessageCount() const { std::lock_guard lock(write_mutex_); return write_queue_.size(); } -void GameSession::ClearPendingMessages() { +void BinarySession::ClearPendingMessages() { std::lock_guard lock(write_mutex_); std::queue> empty; std::swap(write_queue_, empty); } -bool GameSession::IsWriteQueueFull() const { +bool BinarySession::IsWriteQueueFull() const { std::lock_guard lock(write_mutex_); return write_queue_.size() >= max_write_queue_size_; } -void GameSession::SetMaxWriteQueueSize(size_t maxSize) { +void BinarySession::SetMaxWriteQueueSize(size_t maxSize) { max_write_queue_size_ = maxSize; } // =============== Graceful Shutdown =============== -void GameSession::BeginGracefulShutdown() { +void BinarySession::BeginGracefulShutdown() { if (graceful_shutdown_) { return; } @@ -1249,7 +1262,7 @@ void GameSession::BeginGracefulShutdown() { }); } -void GameSession::CancelGracefulShutdown() { +void BinarySession::CancelGracefulShutdown() { if (!graceful_shutdown_) { return; } @@ -1266,7 +1279,7 @@ void GameSession::CancelGracefulShutdown() { // =============== World and Entity Methods =============== -void GameSession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vector& chunkData) { +void BinarySession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vector& chunkData) { BinaryProtocol::BinaryWriter writer; writer.WriteInt32(chunkX); writer.WriteInt32(chunkZ); @@ -1281,7 +1294,7 @@ void GameSession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vector SendBinary(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, combined_data); } -void GameSession::SendEntityUpdateBinary(uint64_t entityId, const std::vector& entityData) { +void BinarySession::SendEntityUpdateBinary(uint64_t entityId, const std::vector& entityData) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); @@ -1295,7 +1308,7 @@ void GameSession::SendEntityUpdateBinary(uint64_t entityId, const std::vector& spawnData) { +void BinarySession::SendEntitySpawnBinary(uint64_t entityId, const std::vector& spawnData) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); @@ -1308,7 +1321,7 @@ void GameSession::SendEntitySpawnBinary(uint64_t entityId, const std::vectornext_layer(); } return socket_; } -const asio::ip::tcp::socket& GameSession::GetSocket() const { +const asio::ip::tcp::socket& BinarySession::GetSocket() const { if (ssl_stream_) { return ssl_stream_->next_layer(); } return socket_; } -void GameSession::HandleNetworkError(std::error_code ec) { +void BinarySession::HandleNetworkError(std::error_code ec) { if (ec == asio::error::eof || ec == asio::error::connection_reset) { Logger::Debug("Session {} disconnected: {}", sessionId_, ec.message()); @@ -1369,7 +1382,7 @@ void GameSession::HandleNetworkError(std::error_code ec) { Stop(); } -void GameSession::HandleMessage(const std::string& message) { +void BinarySession::HandleMessage(const std::string& message) { if (message.empty()) { return; } @@ -1403,14 +1416,14 @@ void GameSession::HandleMessage(const std::string& message) { // =============== Prediction System Integration =============== -PredictionSystem& GameSession::GetPredictionSystem() { +PredictionSystem& BinarySession::GetPredictionSystem() { return prediction_system_; } // =============== Legacy JSON Handlers (for backward compatibility) =============== // These methods handle the old JSON-based protocol for backward compatibility -void GameSession::SendWorldChunk(int chunkX, int chunkZ, const nlohmann::json& chunkData) { +void BinarySession::SendWorldChunk(int chunkX, int chunkZ, const nlohmann::json& chunkData) { nlohmann::json message = { {"type", "world_chunk"}, {"chunkX", chunkX}, @@ -1422,7 +1435,7 @@ void GameSession::SendWorldChunk(int chunkX, int chunkZ, const nlohmann::json& c Send(message); } -void GameSession::SendEntityUpdate(uint64_t entityId, const nlohmann::json& entityData) { +void BinarySession::SendEntityUpdate(uint64_t entityId, const nlohmann::json& entityData) { nlohmann::json message = { {"type", "entity_update"}, {"entityId", entityId}, @@ -1433,7 +1446,7 @@ void GameSession::SendEntityUpdate(uint64_t entityId, const nlohmann::json& enti Send(message); } -void GameSession::SendEntitySpawn(uint64_t entityId, const nlohmann::json& spawnData) { +void BinarySession::SendEntitySpawn(uint64_t entityId, const nlohmann::json& spawnData) { nlohmann::json message = { {"type", "entity_spawn"}, {"entityId", entityId}, @@ -1444,7 +1457,7 @@ void GameSession::SendEntitySpawn(uint64_t entityId, const nlohmann::json& spawn Send(message); } -void GameSession::SendEntityDespawn(uint64_t entityId) { +void BinarySession::SendEntityDespawn(uint64_t entityId) { nlohmann::json message = { {"type", "entity_despawn"}, {"entityId", entityId}, @@ -1454,7 +1467,7 @@ void GameSession::SendEntityDespawn(uint64_t entityId) { Send(message); } -void GameSession::SendCollisionEvent(uint64_t entityId1, uint64_t entityId2, const glm::vec3& point) { +void BinarySession::SendCollisionEvent(uint64_t entityId1, uint64_t entityId2, const glm::vec3& point) { nlohmann::json message = { {"type", "collision_event"}, {"entityId1", entityId1}, @@ -1466,7 +1479,7 @@ void GameSession::SendCollisionEvent(uint64_t entityId1, uint64_t entityId2, con Send(message); } -void GameSession::SyncPlayerState(const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& velocity) { +void BinarySession::SyncPlayerState(const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& velocity) { nlohmann::json message = { {"type", "player_state_sync"}, {"position", {position.x, position.y, position.z}}, @@ -1478,7 +1491,7 @@ void GameSession::SyncPlayerState(const glm::vec3& position, const glm::vec3& ro Send(message); } -void GameSession::SendNearbyEntities(const std::vector& entities) { +void BinarySession::SendNearbyEntities(const std::vector& entities) { nlohmann::json message = { {"type", "nearby_entities"}, {"entities", entities}, @@ -1488,7 +1501,7 @@ void GameSession::SendNearbyEntities(const std::vector& entities Send(message); } -void GameSession::SendNPCInteraction(uint64_t npcId, const std::string& interactionType, const nlohmann::json& data) { +void BinarySession::SendNPCInteraction(uint64_t npcId, const std::string& interactionType, const nlohmann::json& data) { nlohmann::json message = { {"type", "npc_interaction"}, {"npcId", npcId}, @@ -1500,7 +1513,7 @@ void GameSession::SendNPCInteraction(uint64_t npcId, const std::string& interact Send(message); } -void GameSession::SendCompressedWorldData(const std::vector& compressedData) { +void BinarySession::SendCompressedWorldData(const std::vector& compressedData) { // Convert binary data to base64 for JSON transmission std::string base64_data; const char base64_chars[] = @@ -1541,7 +1554,7 @@ void GameSession::SendCompressedWorldData(const std::vector& compressed // =============== Legacy Handlers (Kept for backward compatibility) =============== -void GameSession::HandleWorldRequest(const nlohmann::json& data) { +void BinarySession::HandleWorldRequest(const nlohmann::json& data) { // Legacy handler - convert to binary protocol Logger::Debug("Session {}: converting legacy world request to binary", sessionId_); @@ -1557,7 +1570,7 @@ void GameSession::HandleWorldRequest(const nlohmann::json& data) { } } -void GameSession::HandleEntityInteraction(const nlohmann::json& data) { +void BinarySession::HandleEntityInteraction(const nlohmann::json& data) { // Legacy handler - convert to binary protocol BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(data.value("entityId", 0ULL)); @@ -1571,7 +1584,7 @@ void GameSession::HandleEntityInteraction(const nlohmann::json& data) { } } -void GameSession::HandleMovementUpdate(const nlohmann::json& data) { +void BinarySession::HandleMovementUpdate(const nlohmann::json& data) { // Legacy handler - convert to binary protocol BinaryProtocol::BinaryWriter writer; @@ -1598,7 +1611,7 @@ void GameSession::HandleMovementUpdate(const nlohmann::json& data) { } } -void GameSession::HandleFamiliarCommand(const nlohmann::json& data) { +void BinarySession::HandleFamiliarCommand(const nlohmann::json& data) { // Legacy handler - convert to binary protocol BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(data.value("familiarId", 0ULL)); @@ -1612,11 +1625,11 @@ void GameSession::HandleFamiliarCommand(const nlohmann::json& data) { } } -void GameSession::SetPlayerStateHandler(std::function handler) { +void BinarySession::SetPlayerStateHandler(std::function handler) { player_state_handler_ = std::move(handler); } -void GameSession::SetupDefaultHandlers() { +void BinarySession::SetupDefaultHandlers() { SetBinaryMessageHandler(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, [this](uint16_t type, const std::vector& data) { (void)type; diff --git a/src/network/ConnectionManager.cpp b/src/network/ConnectionManager.cpp index 333e2a6..8370c52 100644 --- a/src/network/ConnectionManager.cpp +++ b/src/network/ConnectionManager.cpp @@ -641,13 +641,10 @@ std::vector> ConnectionManager::GetSessionsByWorker } void ConnectionManager::RedistributeSessions(const std::vector& workerIds) { - // This is a simplified example. In production, you'd implement - // a proper session migration strategy between workers. - Logger::Info("Redistributing sessions across {} workers", workerIds.size()); - - // Implementation would depend on your load balancing strategy - // For example, you could move sessions between groups or workers + // TODO + // Implementation would depend on load balancing strategy + // move sessions between groups or workers // based on load metrics. } diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index a628080..6f6d230 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -10,7 +10,6 @@ GameServer::GameServer(const WorkerGroupConfig& groupConfig, const ConfigManager reuse_(groupConfig.reuse), ioThreads_(groupConfig.threads) { - // Set up SSL context if requested if (groupConfig.ssl.has_value()) { sslContext_ = std::make_shared(asio::ssl::context::tls_server); sslContext_->use_certificate_chain_file(groupConfig.ssl->certificate); @@ -18,7 +17,6 @@ GameServer::GameServer(const WorkerGroupConfig& groupConfig, const ConfigManager if (!groupConfig.ssl->dh_params.empty()) { sslContext_->use_tmp_dh_file(groupConfig.ssl->dh_params); } - // Additional SSL options can be set here } } @@ -44,7 +42,6 @@ bool GameServer::Initialize() { } acceptor_.bind(endpoint); acceptor_.listen(groupConfig_.max_connections); - Logger::Info("GameServer initialized for protocol '{}' on {}:{}", groupConfig_.protocol, host_, port_); return true; @@ -59,9 +56,7 @@ void GameServer::Run() { running_ = true; DoAccept(); StartWorkerThreads(); - - auto work = asio::make_work_guard(ioContext_); - + work_guard_.emplace(asio::make_work_guard(ioContext_)); Logger::Info("GameServer started with {} IO threads for protocol '{}'", ioThreads_, groupConfig_.protocol); try { @@ -69,13 +64,186 @@ void GameServer::Run() { } catch (const std::exception& err) { Logger::Critical("Unhandled exception in IO context: {}", err.what()); } - for (auto& thread : workerThreads_) { if (thread.joinable()) thread.join(); } Logger::Info("GameServer run finished for protocol '{}'", groupConfig_.protocol); } +void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, GameLogic& game_logic) { + if (groupConfig_.protocol == "binary") { + sessionFactory_ = [workerId, processPool, &game_logic, this] + (asio::ip::tcp::socket socket, std::shared_ptr sslCtx) mutable{ + auto session = std::make_shared(std::move(socket), sslCtx); + Logger::Trace("Worker {} created new game session {}; protocol: binary", workerId, session->GetSessionId()); + session->SetMessageHandler([session, workerId, processPool, &game_logic] + (const nlohmann::json& msg) mutable{ + try { + std::string msgType = msg.value("type", ""); + Logger::Trace("Worker {} processing message type: {}", workerId, msgType); + if (msgType == "ipc_message" && processPool) { + if (msg.contains("target_worker") && msg.contains("payload")) { + int targetWorker = msg["target_worker"]; + std::string payload = msg["payload"].dump(); + if (processPool->SendToWorker(targetWorker, payload)) { + Logger::Trace("Worker {} sent IPC message to worker {}", workerId, targetWorker); + } else { + Logger::Error("Worker {} failed to send IPC message to worker {}", workerId, targetWorker); + } + } + } else { + game_logic.HandleMessage(session->GetSessionId(), msg); + } + } catch (const std::exception& e) { + Logger::Error("Worker {} error processing message: {}", workerId, e.what()); + session->SendError("Internal server error", 500); + } + }); + session->SetDefaultBinaryMessageHandler([session, workerId, &game_logic] + (uint16_t type, const std::vector& data) mutable{ + switch (type) { + case BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + AuthenticationData authData; + authData.username = reader.ReadString(); + authData.password = reader.ReadString(); + authData.session_id = session->GetSessionId(); + game_logic.OnAuthentication(authData); + break; + } + case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + ChunkRequestData req; + req.chunk_x = reader.ReadInt32(); + req.chunk_z = reader.ReadInt32(); + req.lod = reader.ReadUInt8(); + req.session_id = session->GetSessionId(); + game_logic.OnChunkRequest(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + PlayerStateData stateData; + stateData.player_id = game_logic.GetPlayerIdBySession(session->GetSessionId()); + stateData.input_id = reader.ReadUInt32(); + stateData.position = reader.ReadVector3(); + stateData.velocity = reader.ReadVector3(); + stateData.rotation = reader.ReadVector3(); + stateData.on_ground = reader.ReadUInt8() != 0; + stateData.timestamp = reader.ReadUInt64(); + stateData.session_id = session->GetSessionId(); + game_logic.OnPlayerState(stateData); + break; + } + case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + PlayerPositionData posData; + posData.player_id = reader.ReadUInt64(); + posData.position = reader.ReadVector3(); + posData.velocity = reader.ReadVector3(); + posData.timestamp = reader.ReadUInt64(); + posData.session_id = session->GetSessionId(); + game_logic.OnPlayerPosition(posData); + break; + } + default: + game_logic.HandleBinaryMessage(session->GetSessionId(), type, data); + break; + } + }); + session->SetCloseHandler([session, workerId, &game_logic]() mutable{ + Logger::Trace("Worker {} session {} closing", workerId, session->GetSessionId()); + game_logic.OnPlayerDisconnected(session->GetSessionId()); + Logger::Trace("Worker {} session {} cleanup complete", workerId, session->GetSessionId()); + }); + return session; + }; + } + else if (groupConfig_.protocol == "websocket") { + webSocketFactory_ = [workerId, processPool, &game_logic, this] + (asio::ip::tcp::socket socket, std::shared_ptr /*sslCtx*/) mutable{ + auto wsConn = std::make_shared(std::move(socket)); + auto session = std::make_shared(wsConn); + Logger::Trace("Worker {} created new game session {}; protocol: websocket", workerId, session->GetSessionId()); + session->SetMessageHandler([session, workerId, processPool, &game_logic] + (const nlohmann::json& msg) mutable{ + std::string msgType = msg.value("type", ""); + if (msgType == "authentication") { + AuthenticationData authData; + authData.username = msg.value("login", ""); + authData.password = msg.value("password", ""); + authData.session_id = session->GetSessionId(); + game_logic.OnAuthentication(authData); + } + else if (msgType == "get_chunk") { + ChunkRequestData req; + req.chunk_x = msg.value("x", 0); + req.chunk_z = msg.value("z", 0); + req.lod = static_cast(msg.value("lod", 0)); + req.session_id = session->GetSessionId(); + game_logic.OnChunkRequest(req); + } + else if (msgType == "player_state") { + // TODO: fill full state data format + PlayerStateData posData; + posData.player_id = game_logic.GetPlayerIdBySession(session->GetSessionId()); + posData.position.x = msg.value("pos_x", 0.0f); + posData.position.y = msg.value("pos_y", 0.0f); + posData.position.z = msg.value("pos_z", 0.0f); + posData.timestamp = game_logic.GetCurrentTimestamp(); + posData.session_id = session->GetSessionId(); + game_logic.OnPlayerState(posData); + } + else if (msgType == "player_position_update") { + PlayerPositionData posData; + posData.player_id = game_logic.GetPlayerIdBySession(session->GetSessionId()); + posData.position.x = msg.value("x", 0.0f); + posData.position.y = msg.value("y", 0.0f); + posData.position.z = msg.value("z", 0.0f); + posData.timestamp = game_logic.GetCurrentTimestamp(); + posData.session_id = session->GetSessionId(); + game_logic.OnPlayerPosition(posData); + } + else { + game_logic.HandleMessage(session->GetSessionId(), msg); + } + }); + // session->SetMessageHandler([session, workerId, processPool](const nlohmann::json& msg) { + // try { + // std::string msgType = msg.value("type", ""); + // Logger::Trace("Worker {} processing message type: {}", workerId, msgType); + // if (msgType == "ipc_message" && processPool) { + // if (msg.contains("target_worker") && msg.contains("payload")) { + // int targetWorker = msg["target_worker"]; + // std::string payload = msg["payload"].dump(); + // if (processPool->SendToWorker(targetWorker, payload)) { + // Logger::Trace("Worker {} sent IPC message to worker {}", workerId, targetWorker); + // } else { + // Logger::Error("Worker {} failed to send IPC message to worker {}", workerId, targetWorker); + // } + // } + // } else { + // game_logic.HandleMessage(session->GetSessionId(), msg); + // } + // } catch (const std::exception& e) { + // Logger::Error("Worker {} error processing message: {}", workerId, e.what()); + // session->SendError("Internal server error", 500); + // } + // }); + session->SetBinaryMessageHandler([session, workerId, &game_logic] + (uint16_t type, const std::vector& data) mutable{ + game_logic.HandleBinaryMessage(session->GetSessionId(), type, data); + }); + session->SetCloseHandler([session, workerId, &game_logic]() mutable{ + Logger::Trace("Worker {} session {} closing", workerId, session->GetSessionId()); + game_logic.OnPlayerDisconnected(session->GetSessionId()); + Logger::Trace("Worker {} session {} cleanup complete", workerId, session->GetSessionId()); + }); + return session; + }; + } +} + void GameServer::DoAccept() { acceptor_.async_accept( [this](std::error_code ec, asio::ip::tcp::socket socket) { @@ -102,37 +270,10 @@ void GameServer::DoAccept() { } } else if (groupConfig_.protocol == "websocket") { if (webSocketFactory_) { - auto wsConn = webSocketFactory_(std::move(socket), sslContext_); - auto session = std::make_shared(wsConn); - session->SetBinaryMessageHandler([session](uint16_t type, const std::vector& data) { - GameLogic::GetInstance().HandleBinaryMessage(session->GetSessionId(), type, data); - }); - session->SetMessageHandler([session](const nlohmann::json& msg) { - try { - GameLogic::GetInstance().HandleMessage(session->GetSessionId(), msg); - } catch (const std::exception& err) { - Logger::Error("Exception in message handler for session {}: {}", - session->GetSessionId(), err.what()); - try { - session->SendError("Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", - session->GetSessionId()); - } - } catch (...) { - Logger::Error("Unknown exception in message handler for session {}", - session->GetSessionId()); - try { - session->SendError("Internal server error", 500); - } catch (...) { - Logger::Error("Failed to send error response to session {}", - session->GetSessionId()); - } - } - }); - Logger::Debug("WebSocket message handlers set for session {}", session->GetSessionId()); + auto session = webSocketFactory_(std::move(socket), sslContext_); ConnectionManager::GetInstance().Start(session); session->Start(); + Logger::Trace("WebSocket session {} started", session->GetSessionId()); } else { Logger::Error("No WebSocket factory set"); } @@ -161,18 +302,136 @@ void GameServer::StartWorkerThreads() { } void GameServer::Shutdown() { - if (!running_) return; + if (!running_) { + Logger::Trace("GameServer::Shutdown - already stopped, do nothing, return"); + return; + } + Logger::Trace("GameServer::Shutdown running ..."); running_ = false; - ConnectionManager::GetInstance().StopAll(); acceptor_.close(); - ioContext_.stop(); - Logger::Info("GameServer shutdown initiated for protocol '{}'", groupConfig_.protocol); + ConnectionManager::GetInstance().StopAll(); + work_guard_.reset(); + Logger::Info("GameServer shutdown completed"); } -void GameServer::SetSessionFactory(SessionFactory factory) { - sessionFactory_ = std::move(factory); +std::vector> GameServer::GetSessionsInRadius(const glm::vec3& position, float radius) { + std::vector> result; + auto& pm = PlayerManager::GetInstance(); + auto nearbyPlayers = pm.GetPlayersInRadius(position, radius); + for (auto& player : nearbyPlayers) { + uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); + if (session_id != 0) { + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session && session->IsConnected()) { + result.push_back(session); + } + } + } + return result; } -void GameServer::SetWebSocketConnectionFactory(WebSocketFactory factory) { - webSocketFactory_ = std::move(factory); +void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_logic) { + if (protocol == "binary") { + game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, bool success, const std::string& message, uint64_t player_id) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt8(success ? 1 : 0); + writer.WriteUInt64(player_id); + writer.WriteString(message); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); + } + }); + game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { + BinaryProtocol::BinaryWriter writer; + writer.WriteInt32(data.chunk_x); + writer.WriteInt32(data.chunk_z); + writer.WriteUInt8(data.lod); + writer.WriteJson(data.chunk_json); + writer.WriteUInt64(data.timestamp); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); + } + }); + game_logic.SetPlayerStateCallback([&](const PlayerStateData& state) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(state.player_id); + writer.WriteVector3(state.position); + writer.WriteVector3(state.velocity); + writer.WriteVector3(state.rotation); + writer.WriteUInt8(state.on_ground ? 1 : 0); + writer.WriteUInt64(state.timestamp); + auto nearbySessions = GetSessionsInRadius(state.position, 100.0f); + for (auto& session : nearbySessions) { + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer()); + } + }); + game_logic.SetBroadcastPlayerPositionCallback([&](const PlayerPositionData& data, float radius) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(data.player_id); + writer.WriteVector3(data.position); + writer.WriteVector3(data.velocity); + writer.WriteUInt64(data.timestamp); + auto nearbySessions = GetSessionsInRadius(data.position, radius); + for (auto& session : nearbySessions) { + session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, writer.GetBuffer()); + } + }); + } else { // websocket + game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, bool success, const std::string& message, uint64_t player_id) { + nlohmann::json response = { + {"type", "authentication_response"}, + {"success", success}, + {"message", message}, + {"player_id", player_id}, + {"timestamp", game_logic.GetCurrentTimestamp()} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(response); + } + }); + game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { + nlohmann::json msg = { + {"type", "world_chunk"}, + {"chunk_x", data.chunk_x}, + {"chunk_z", data.chunk_z}, + {"lod", data.lod}, + {"data", data.chunk_json}, + {"timestamp", data.timestamp} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(msg); + } + }); + game_logic.SetPlayerStateCallback([&](const PlayerStateData& state) { + nlohmann::json jsonMsg = { + {"type", "entity_update"}, + {"entity_id", state.player_id}, + {"x", state.position.x}, {"y", state.position.y}, {"z", state.position.z}, + {"rx", state.rotation.x}, {"ry", state.rotation.y}, {"rz", state.rotation.z}, + {"vx", state.velocity.x}, {"vy", state.velocity.y}, {"vz", state.velocity.z}, + {"timestamp", state.timestamp} + }; + auto nearbySessions = GetSessionsInRadius(state.position, 100.0f); + for (auto& session : nearbySessions) { + session->Send(jsonMsg); + } + }); + game_logic.SetBroadcastPlayerPositionCallback([&](const PlayerPositionData& data, float radius) { + nlohmann::json jsonMsg = { + {"type", "player_position"}, + {"player_id", data.player_id}, + {"x", data.position.x}, {"y", data.position.y}, {"z", data.position.z}, + {"vx", data.velocity.x}, {"vy", data.velocity.y}, {"vz", data.velocity.z}, + {"timestamp", data.timestamp} + }; + auto nearbySessions = GetSessionsInRadius(data.position, radius); + for (auto& session : nearbySessions) { + session->Send(jsonMsg); + } + }); + } } diff --git a/src/network/PredictionSystem.cpp b/src/network/PredictionSystem.cpp index 45f6368..4667fa4 100644 --- a/src/network/PredictionSystem.cpp +++ b/src/network/PredictionSystem.cpp @@ -304,7 +304,7 @@ ServerState ServerState::Interpolate(const ServerState& a, const ServerState& b, return result; } -void PredictionSystem::PredictionStats::Reset() { +void PredictionStats::Reset() { total_predictions = 0; corrections_sent = 0; corrections_received = 0; @@ -312,7 +312,7 @@ void PredictionSystem::PredictionStats::Reset() { last_correction_time = 0; } -std::string PredictionSystem::PredictionStats::ToString() const { +std::string PredictionStats::ToString() const { std::stringstream ss; ss << "Prediction Stats:\n"; ss << " Total Predictions: " << total_predictions << "\n"; @@ -324,6 +324,14 @@ std::string PredictionSystem::PredictionStats::ToString() const { return ss.str(); } +ServerState PredictionSystem::GetLastConfirmedState() const { return last_confirmed_state_; } + +ServerState PredictionSystem::GetLatestPredictedState() const { return latest_predicted_state_; } + +const std::deque& PredictionSystem::GetInputHistory() const { return input_history_; } + +const PredictionStats& PredictionSystem::GetStats() const { return stats_; } + // InputBuffer implementation InputBuffer::InputBuffer(size_t max_size) : max_size_(max_size) {} @@ -385,3 +393,7 @@ void InputBuffer::Clear() { std::lock_guard lock(mutex_); inputs_.clear(); } + +bool InputBuffer::HasInputs() const { return !inputs_.empty(); } + +size_t InputBuffer::Size() const { return inputs_.size(); } diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index 084ebb9..ccb11b1 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -451,9 +451,10 @@ void WebSocketConnection::ReadFrame() { return; } auto self = shared_from_this(); + Logger::Trace("WebSocketConnection {} ReadFrame: starting async_read (2 bytes)", connection_id_); asio::async_read(socket_, read_buffer_, asio::transfer_exactly(2), - [self](std::error_code ec, size_t /*bytes_transferred*/) { - //Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); + [self](std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); if (ec) { self->HandleError(ec); return; @@ -484,8 +485,8 @@ void WebSocketConnection::ReadFrame() { asio::async_read(self->socket_, self->read_buffer_, asio::transfer_exactly(header_size - 2), [self, fin, opcode, masked, payload_length, header_size] - (std::error_code ec, size_t /*bytes_transferred*/) { - //Logger::Debug("WebSocketConnection::ReadFrame asio::async_read {}", bytes_transferred); + (std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); if (ec) { self->HandleError(ec); return; @@ -501,11 +502,12 @@ void WebSocketConnection::ReadFrame() { void WebSocketConnection::ReadFramePayload(bool fin, uint8_t opcode, bool masked, uint64_t payload_length, size_t header_size) { auto self = shared_from_this(); + Logger::Trace("WebSocketConnection {} ReadFramePayload: starting async_read", connection_id_); if (payload_length > 0) { asio::async_read(socket_, read_buffer_, asio::transfer_exactly(payload_length), [self, fin, opcode, masked, payload_length, header_size] - (std::error_code ec, size_t /*bytes_transferred*/) { - //Logger::Debug("WebSocketConnection::ReadFramePayload asio::async_read {}", bytes_transferred); + (std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection {} ReadFramePayload completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); if (ec) { self->HandleError(ec); return; @@ -529,6 +531,7 @@ void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked } std::vector frame_data(total_frame_size); auto buffers = read_buffer_.data(); + Logger::Trace("WebSocketConnection {} ProcessFrameData: starting buffer_copy", connection_id_); size_t bytes_copied = asio::buffer_copy( asio::buffer(frame_data), // destination buffers, // source buffer sequence @@ -549,6 +552,7 @@ void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked if (state_ == State::OPEN || state_ == State::CLOSING) { ReadFrame(); } + Logger::Trace("WebSocketConnection {} ProcessFrameData: complete", connection_id_); } void WebSocketConnection::HandleFrame(const WebSocketFrame& frame) { @@ -629,7 +633,7 @@ void WebSocketConnection::CompleteCurrentMessage() { void WebSocketConnection::SendFrame(const WebSocketFrame& frame) { std::vector frame_data = frame.Serialize(); { - std::lock_guard lock(write_mutex_); + std::lock_guard lock(write_mutex_); write_buffer_.insert(write_buffer_.end(), frame_data.begin(), frame_data.end()); stats_.messages_sent++; stats_.bytes_sent += frame.payload_data.size(); @@ -653,32 +657,60 @@ void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { }); } +// ATTENTION: THIS VERSION NOT WORKED +// void WebSocketConnection::DoWrite() { +// std::lock_guard lock(write_mutex_); +// if (write_buffer_.empty() || state_ != State::OPEN) +// return; +// auto self = shared_from_this(); +// Logger::Trace("WebSocketConnection {} DoWrite: starting async_write", connection_id_); +// asio::async_write(socket_, asio::buffer(write_buffer_), +// [self](std::error_code ec, size_t bytes_transferred) { +// Logger::Trace("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); +// try { +// if (ec) { +// self->HandleError(ec); +// return; +// } +// std::lock_guard lock(self->write_mutex_); +// self->write_buffer_.erase(self->write_buffer_.begin(), +// self->write_buffer_.begin() + bytes_transferred); +// if (!self->write_buffer_.empty()) { +// self->DoWrite(); +// } +// } catch (const std::exception& err) { +// Logger::Critical("Exception in WebSocket write handler: {}", err.what()); +// self->Close(1011, "Internal error"); +// } catch (...) { +// Logger::Critical("Unknown exception in WebSocket write handler"); +// self->Close(1011, "Internal error"); +// } +// }); +// Logger::Trace("WebSocketConnection {} DoWrite: complete", connection_id_); +// } + void WebSocketConnection::DoWrite() { - auto self = shared_from_this(); - std::lock_guard lock(write_mutex_); - if (write_buffer_.empty()) { + std::lock_guard lock(write_mutex_); + if (write_buffer_.empty() || state_ != State::OPEN) return; - } + auto self = shared_from_this(); + Logger::Trace("WebSocketConnection {} DoWrite: starting async_write", connection_id_); asio::async_write(socket_, asio::buffer(write_buffer_), - [self](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); - try { - if (ec) { - self->HandleError(ec); - return; - } - std::lock_guard lock(self->write_mutex_); - self->write_buffer_.erase(self->write_buffer_.begin(), - self->write_buffer_.begin() + bytes_transferred); - if (!self->write_buffer_.empty()) { + [self](std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection::DoWrite asio::async_write {}", bytes); + if (ec) { + self->HandleError(ec); + return; + } + { + std::lock_guard lock(self->write_mutex_); + self->write_buffer_.erase(self->write_buffer_.begin(), self->write_buffer_.begin() + bytes); + self->stats_.bytes_sent += bytes; + } + if (!self->write_buffer_.empty()) { + asio::post(self->socket_.get_executor(), [self]() { self->DoWrite(); - } - } catch (const std::exception& err) { - Logger::Critical("Exception in WebSocket write handler: {}", err.what()); - self->Close(1011, "Internal error"); - } catch (...) { - Logger::Critical("Unknown exception in WebSocket write handler"); - self->Close(1011, "Internal error"); + }); } }); } @@ -707,42 +739,68 @@ void WebSocketConnection::SendPong(const std::vector& data) { SendFrameAsync(frame); } +// ATTENTION: THIS VERSION NOT WORKED +// void WebSocketConnection::Close(uint16_t code, const std::string& reason) { +// Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); +// if (state_ == State::CLOSED || state_ == State::CLOSING) { +// if (state_ == State::CLOSED) +// Logger::Warn("WebSocketConnection now is closed"); +// else +// Logger::Warn("WebSocketConnection now is in process closing"); +// return; +// } +// state_ = State::CLOSING; +// WebSocketFrame close_frame = WebSocketFrame::CreateCloseFrame(code, reason); +// SendFrameAsync(close_frame); +// auto self = shared_from_this(); +// asio::post(socket_.get_executor(), [self]() { +// std::error_code ec; +// self->socket_.cancel(ec); +// Logger::Trace("WebSocketConnection {} cancel result: {}", self->connection_id_, ec.message()); +// self->socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); +// Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); +// self->socket_.close(ec); +// self->state_ = State::CLOSED; +// if (self->close_handler_) { +// self->close_handler_(1000, "Normal closure"); +// } +// Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); +// }); +// Logger::Trace("WebSocketConnection {} Close: complete", connection_id_); +// } + void WebSocketConnection::Close(uint16_t code, const std::string& reason) { + Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); if (state_ == State::CLOSED || state_ == State::CLOSING) { + if (state_ == State::CLOSED) + Logger::Warn("WebSocketConnection now is closed"); + else + Logger::Warn("WebSocketConnection now is in process closing"); return; } - state_ = State::CLOSING; - - // Send close frame - WebSocketFrame close_frame = WebSocketFrame::CreateCloseFrame(code, reason); - SendFrameAsync(close_frame); - - // Close socket after sending - auto self = shared_from_this(); - asio::post(socket_.get_executor(), [self]() { - std::error_code ec; - self->socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); - self->socket_.close(ec); - self->state_ = State::CLOSED; - - if (self->close_handler_) { - self->close_handler_(1000, "Normal closure"); - } - }); + std::error_code ec; + socket_.cancel(ec); + Logger::Trace("WebSocketConnection {} cancel result: {}", connection_id_, ec.message()); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); + socket_.close(ec); + Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); + if (close_handler_) { + close_handler_(code, reason.empty() ? "Connection closed" : reason); + } + Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); } void WebSocketConnection::HandleError(const std::error_code& ec) { if (ec == asio::error::eof || ec == asio::error::connection_reset) { - Logger::Debug("WebSocketConnection {} disconnected", connection_id_); + Logger::Trace("WebSocketConnection {} disconnected", connection_id_); } else { Logger::Error("WebSocketConnection {} error: {}", connection_id_, ec.message()); } - if (error_handler_) { error_handler_(ec); } - Close(1006, "Connection error"); } diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index 181a5df..da81d01 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -3,7 +3,7 @@ std::atomic WebSocketSession::nextSessionId_{1}; WebSocketSession::WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn) - : wsConn_(std::move(wsConn)), sessionId_(nextSessionId_++) { + : protocolMode_(ProtocolMode::Json), wsConn_(std::move(wsConn)), sessionId_(nextSessionId_++) { wsConn_->SetMessageHandler([this](const WebSocketProtocol::WebSocketMessage& msg) { OnMessage(msg); }); @@ -45,8 +45,18 @@ void WebSocketSession::SendRaw(const std::string& data) { wsConn_->SendText(data); } -void WebSocketSession::SendBinary(uint16_t /*message_type*/, const std::vector& data) { - wsConn_->SendBinary(data); +void WebSocketSession::SendBinary(uint16_t message_type, const std::vector& data) { + BinaryProtocol::BinaryMessage msg; + msg.header.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; + msg.header.message_type = message_type; + msg.header.sequence = 0; + msg.header.timestamp = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + msg.data = data; + msg.header.length = static_cast(data.size()); + msg.header.checksum = BinaryProtocol::CalculateCRC32(data.data(), data.size()); + auto serialized = msg.Serialize(); + wsConn_->SendBinary(serialized); } void WebSocketSession::SetMessageHandler(MessageHandler handler) { @@ -151,31 +161,54 @@ std::string WebSocketSession::GetRemoteAddress() const { } } -void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { - Logger::Debug("WebSocketSession {} received {} bytes, opcode: {}", - sessionId_, msg.data.size(), (int)msg.opcode); +void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { + Logger::Trace("WebSocketSession {} received {} bytes, opcode: {}", sessionId_, msg.data.size(), (int)msg.opcode); if (msg.opcode == WebSocketProtocol::OP_TEXT) { std::string text = msg.GetText(); - Logger::Debug("WebSocketSession {} received TEXT: {}", sessionId_, text); - + Logger::Trace("WebSocketSession {} received TEXT: {}", sessionId_, text); + if (protocolMode_ == ProtocolMode::Binary) { + Logger::Warn("Session {} sent TEXT frame but negotiated BINARY – ignoring", sessionId_); + return; + } + protocolMode_ = ProtocolMode::Json; if (messageHandler_) { try { auto json = nlohmann::json::parse(text); - Logger::Debug("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); + Logger::Trace("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); messageHandler_(json); - } catch (const std::exception& e) { - Logger::Error("WebSocketSession {} invalid JSON: {}", sessionId_, e.what()); + if (json.value("type", "") == "protocol_negotiation" && + json.value("protocol", "") == "websocket") { + protocolMode_ = ProtocolMode::Json; + Logger::Trace("WebSocketSession {} switched to JSON protocol mode", sessionId_); + return; + } + else if (json.value("type", "") == "get_chunk") { + ChunkRequestData req; + nlohmann::json data = msg.ToJson(); + req.chunk_x = data.value("x", 0); + req.chunk_z = data.value("z", 0); + req.lod = data.value("lod", 0); + req.session_id = sessionId_; + GameLogic::GetInstance().OnChunkRequest(req); + } + } catch (const std::exception& err) { + Logger::Error("WebSocketSession {} invalid: {}", sessionId_, err.what()); } } else { Logger::Error("WebSocketSession {} has no messageHandler_ set!", sessionId_); } } else if (msg.opcode == WebSocketProtocol::OP_BINARY) { - Logger::Debug("WebSocketSession {} received BINARY ({} bytes)", sessionId_, msg.data.size()); + Logger::Trace("WebSocketSession {} received BINARY ({} bytes)", sessionId_, msg.data.size()); + if (protocolMode_ == ProtocolMode::Json) { + Logger::Warn("Session {} sent BINARY frame but negotiated JSON – ignoring", sessionId_); + return; + } + protocolMode_ = ProtocolMode::Binary; try { auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize(msg.data.data(), msg.data.size()); - Logger::Debug("WebSocketSession {} binary message type: {}", sessionId_, binaryMsg.header.message_type); + Logger::Trace("WebSocketSession {} binary message type: {}", sessionId_, binaryMsg.header.message_type); if (binary_handler_) { binary_handler_(binaryMsg.header.message_type, binaryMsg.data); } else if (default_binary_handler_) { diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index bec4260..f3dd694 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -150,10 +150,8 @@ void ProcessPool::MasterProcess() { } } - // ---------- shutdown with timeout ---------- - constexpr int SHUTDOWN_TIMEOUT_SEC = 5; + constexpr int SHUTDOWN_TIMEOUT_SEC = 30; - // Send SIGTERM to all workers for (int i = 0; i < totalWorkers_; ++i) { if (workers_[i].pid > 0) { Logger::Info("Terminating worker {} (PID: {})", i, workers_[i].pid); diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index a2c6cb0..0000000 --- a/vcpkg.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "name": "GameServer", - "description": "C++ game server with multi-process architecture, PostgreSQL sharding via Citus, and Python scripting support", - "version": "1.0.0", - "language": "C++17", - "compiler": { - "minimum": "gcc 9.0+ or clang 10.0+", - "cxx_standard": 17 - }, - "dependencies": { - "asio": { - "version": "1.28.0", - "description": "Standalone ASIO library (non-Boost version)", - "usage": "Networking and asynchronous I/O", - "fetch_method": "git clone https://github.com/chriskohlhoff/asio.git", - "build_requirements": "Header-only library, no build required" - }, - "libpq": { - "version": "15.0+", - "description": "PostgreSQL C client library", - "usage": "Database connectivity", - "install_command_ubuntu": "sudo apt-get install libpq-dev", - "install_command_macos": "brew install libpq" - }, - "nlohmann-json": { - "version": "3.11.2", - "description": "JSON library for modern C++", - "usage": "JSON serialization/deserialization", - "fetch_method": "git clone https://github.com/nlohmann/json.git", - "build_requirements": "Header-only library, no build required" - }, - "spdlog": { - "version": "1.12.0", - "description": "Fast C++ logging library", - "usage": "Logging throughout the application", - "fetch_method": "git clone https://github.com/gabime/spdlog.git", - "build_requirements": "CMake build required" - }, - "python3": { - "version": "3.8+", - "description": "Python interpreter and development headers", - "usage": "Python scripting engine integration", - "install_command_ubuntu": "sudo apt-get install python3-dev", - "install_command_macos": "brew install python@3.9" - }, - "glm": { - "version": "0.9.9.8", - "description": "Mathematics library for graphics software", - "usage": "Vector and matrix operations for game world", - "fetch_method": "git clone https://github.com/g-truc/glm.git", - "build_requirements": "Header-only library, no build required" - } - }, - "system_requirements": { - "libraries": [ - "pthread", - "dl", - "m" - ], - "postgresql": { - "version": "15.0+", - "extensions": ["citus"], - "setup_notes": "Citus extension required for distributed tables" - }, - "operating_systems": ["Linux", "macOS"] - }, - "build_system": "CMake", - "cmake_minimum_version": "3.16", - "project_structure": { - "include_directories": [ - "include/", - "thirdparty/asio/include/", - "thirdparty/spdlog/include/", - "thirdparty/json/include/", - "thirdparty/glm/", - "/usr/include/postgresql/" - ], - "link_directories": [ - "/usr/lib/x86_64-linux-gnu/", - "/usr/local/lib/" - ], - "libraries_to_link": [ - "pq", - "pthread", - "dl", - "m", - "python3.9" - ] - }, - "optional_features": { - "compression": "Requires zlib development headers", - "ssl_support": "Requires OpenSSL development headers", - "profiling": "gperftools or similar for performance analysis" - }, - "platform_notes": { - "linux": { - "additional_packages": [ - "libssl-dev", - "zlib1g-dev", - "build-essential", - "pkg-config" - ] - }, - "macos": { - "additional_packages": [ - "openssl", - "pkg-config" - ], - "notes": "Python3 may need explicit linking with -lpython3.9" - } - } -} \ No newline at end of file From cd99931900dc834c013b838502d705b3c1f027fc Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:08:22 +0300 Subject: [PATCH 08/14] change readme info --- README.md | 95 +---------------------------------- install_dependencies_linux.sh | 11 ---- install_dependencies_mac.sh | 7 --- setup_dependencies.sh | 18 ------- 4 files changed, 2 insertions(+), 129 deletions(-) delete mode 100755 install_dependencies_linux.sh delete mode 100755 install_dependencies_mac.sh delete mode 100755 setup_dependencies.sh diff --git a/README.md b/README.md index 152bfae..8e0ce7e 100644 --- a/README.md +++ b/README.md @@ -38,44 +38,6 @@ GameServer is a sophisticated, game server designed for massively multiplayer on - **Process Monitoring**: Health checks and automatic worker restart - **Python Scripting**: Extensible game logic through Python integration -## Architecture - -### Core Components -┌─────────────────────────────────────────────────┐ -│ Master Process │ -│┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ -││ Worker #1 │ │ Worker #2 │ │ Worker #N ││ -││ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ ││ -││ │GameLogic│ │ │ │GameLogic│ │ │ │GameLogic│ ││ -││ │ World │ │ │ │ World │ │ │ │ World │ ││ -││ │ Entities│ │ │ │ Entities│ │ │ │ Entities│ ││ -││ │ Network │ │ │ │ Network │ │ │ │ Network │ ││ -││ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ ││ -│└─────────────┘ └─────────────┘ └─────────────┘│ -└─────────────────────────────────────────────────┘ - │ │ │ - ▼ ▼ ▼ -┌─────────────────────────────────────────────────┐ -│ Distributed Database (Citus) │ -└─────────────────────────────────────────────────┘ - - -### Network Protocol Stack -┌─────────────────────────────────────────────────┐ -│ Application Layer │ -│ •Game Logic Messages •Chat •Inventory •Combat │ -├─────────────────────────────────────────────────┤ -│ Binary Protocol Layer │ -│ •Message Serialization •Compression •Encryption │ -├─────────────────────────────────────────────────┤ -│ Transport Layer (TCP/SSL) │ -│ • Reliable Delivery • Flow Control • Congestion │ -├─────────────────────────────────────────────────┤ -│ Session Management Layer │ -│ •Connection Pooling •Authentication •Rate Limit │ -└─────────────────────────────────────────────────┘ - - ## Technology Stack ### Core Technologies @@ -87,14 +49,13 @@ GameServer is a sophisticated, game server designed for massively multiplayer on - **nlohmann/json**: JSON serialization/deserialization ### Database +- **SQLite**: mainly for fast-testing - **PostgreSQL with Citus**: Distributed database backend - **Connection Pooling**: Efficient database resource management - **Sharding Support**: Horizontal scaling of game data ### Build System - **CMake 3.16+**: Cross-platform build configuration -- **vcpkg**: C++ dependency management -- **Cross-platform**: Linux and macOS support ## Quick Start @@ -110,65 +71,13 @@ GameServer is a sophisticated, game server designed for massively multiplayer on git clone https://github.com/usermicrodevices/gameserver.git cd gameserver -# Install dependencies (Linux) -chmod +x install_dependencies_linux.sh -./install_dependencies_linux.sh - # Build the server -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -make -j$(nproc) - -# Configure the server -cp ../config/server_config.example.json config/server_config.json -# Edit server_config.json with your database and world settings +./build.sh --with-sqlite --with-asan # Run the server ./gameserver ``` -### Configuration - -Create ```config/server_config.json```: -``` -{ - "server": { - "host": "0.0.0.0", - "port": 8080, - "process_count": 4, - "io_threads": 2, - "reuse_port": true - }, - "database": { - "backend": "postgresql", - "host": "localhost", - "port": 5432, - "name": "gameserver", - "user": "gameserver", - "password": "secure_password", - "worker_nodes": ["node1:5432", "node2:5432"] - }, - "world": { - "seed": 12345, - "view_distance": 8, - "chunk_size": 32, - "max_active_chunks": 1000, - "terrain_scale": 1.0, - "max_terrain_height": 256.0, - "water_level": 64.0, - "preload_world": true, - "world_preload_radius": 500.0 - }, - "logging": { - "level": "info", - "file": "logs/gameserver.log", - "max_size": 104857600, - "max_files": 10, - "compress": true - } -} -``` - ## Network Protocol ### Dual Protocol Architecture diff --git a/install_dependencies_linux.sh b/install_dependencies_linux.sh deleted file mode 100755 index 1f174ca..0000000 --- a/install_dependencies_linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - cmake \ - libpq-dev \ - python3-dev \ - libssl-dev \ - zlib1g-dev \ - pkg-config \ - postgresql-15 \ - postgresql-15-citus-12 \ No newline at end of file diff --git a/install_dependencies_mac.sh b/install_dependencies_mac.sh deleted file mode 100755 index 8a5a1d0..0000000 --- a/install_dependencies_mac.sh +++ /dev/null @@ -1,7 +0,0 @@ -brew update -brew install \ - cmake \ - libpq \ - python@3.9 \ - openssl \ - pkg-config \ No newline at end of file diff --git a/setup_dependencies.sh b/setup_dependencies.sh deleted file mode 100755 index 85e4c1b..0000000 --- a/setup_dependencies.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Clone the repository -git clone https://github.com/usermicrodevices/gameserver.git -cd gameserver - -# Create thirdparty directory -mkdir -p thirdparty - -# Clone ASIO (standalone) -git clone https://github.com/chriskohlhoff/asio.git thirdparty/asio - -# Clone spdlog -git clone https://github.com/gabime/spdlog.git thirdparty/spdlog - -# Clone nlohmann/json -git clone https://github.com/nlohmann/json.git thirdparty/json - -# Clone glm -git clone https://github.com/g-truc/glm.git thirdparty/glm \ No newline at end of file From 1b0fd4986bad10ed5c2f9ccfb5e9ff90c9408100 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:14:11 +0300 Subject: [PATCH 09/14] readme --- README.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8e0ce7e..c25d029 100644 --- a/README.md +++ b/README.md @@ -81,20 +81,21 @@ cd gameserver ## Network Protocol ### Dual Protocol Architecture -The GameServer supports two communication protocols running in parallel: -Client ↔ Server Communication -├── JSON Protocol (Development) -│ ├── Human-readable format -│ ├── Easy debugging and testing -│ ├── Slower, higher bandwidth -│ └── Used for configuration, -│ chat, admin commands -│ -├── Binary Protocol (Production) -├── High-performance binary format -├── Minimal bandwidth usage -├── Fast serialization/deserialization -└── Used for real-time gameplay, +The GameServer supports two communication +protocols running in parallel:__ +Client ↔ Server Communication__ +├── JSON Protocol (Development)__ +│ ├── Human-readable format__ +│ ├── Easy debugging and testing__ +│ ├── Slower, higher bandwidth__ +│ └── Used for configuration,__ +│ chat, admin commands__ +│__ +├── Binary Protocol (Production)__ +├── High-performance binary format__ +├── Minimal bandwidth usage__ +├── Fast serialization/deserialization__ +└── Used for real-time gameplay,__ entity updates, world data From bf7d0720d0bc473d58b9e25588a83256986d2e6d Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:20:23 +0300 Subject: [PATCH 10/14] readme --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c25d029..dc544aa 100644 --- a/README.md +++ b/README.md @@ -82,20 +82,20 @@ cd gameserver ### Dual Protocol Architecture The GameServer supports two communication -protocols running in parallel:__ -Client ↔ Server Communication__ -├── JSON Protocol (Development)__ -│ ├── Human-readable format__ -│ ├── Easy debugging and testing__ -│ ├── Slower, higher bandwidth__ -│ └── Used for configuration,__ -│ chat, admin commands__ -│__ -├── Binary Protocol (Production)__ -├── High-performance binary format__ -├── Minimal bandwidth usage__ -├── Fast serialization/deserialization__ -└── Used for real-time gameplay,__ +protocols running in parallel:\ +Client ↔ Server Communication\ +├── JSON Protocol (Development)\ +│ ├── Human-readable format\ +│ ├── Easy debugging and testing\ +│ ├── Slower, higher bandwidth\ +│ └── Used for configuration,\ +│ chat, admin commands\ +│\ +├── Binary Protocol (Production)\ +├── High-performance binary format\ +├── Minimal bandwidth usage\ +├── Fast serialization/deserialization\ +└── Used for real-time gameplay,\ entity updates, world data From b812b678f5ebaebc7663ba2b0e0669c0501f88b7 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:46:13 +0300 Subject: [PATCH 11/14] continue refactor network layer --- include/game/GameData.hpp | 155 ++++- include/game/GameLogic.hpp | 59 +- include/game/InventorySystem.hpp | 9 +- include/game/LogicCore.hpp | 8 +- include/game/LootItem.hpp | 42 +- include/network/BinaryProtocol.hpp | 87 +-- include/network/BinarySession.hpp | 32 +- include/network/GameServer.hpp | 4 +- include/network/IConnection.hpp | 5 +- include/network/WebSocketProtocol.hpp | 8 - include/network/WebSocketSession.hpp | 7 +- src/game/GameLogic.cpp | 781 +++++++++----------------- src/game/InventorySystem.cpp | 516 ++++++++++------- src/game/LogicCore.cpp | 36 +- src/game/LootItem.cpp | 32 +- src/game/LootTableManager.cpp | 4 +- src/game/PlayerManager.cpp | 10 +- src/main.cpp | 14 +- src/network/BinarySession.cpp | 470 +++------------- src/network/GameServer.cpp | 407 ++++++++++++-- src/network/WebSocketSession.cpp | 51 +- 21 files changed, 1325 insertions(+), 1412 deletions(-) diff --git a/include/game/GameData.hpp b/include/game/GameData.hpp index ac59ce4..c466bd6 100644 --- a/include/game/GameData.hpp +++ b/include/game/GameData.hpp @@ -1,17 +1,98 @@ #pragma once #include +#include #include #include +enum class LootRarity { + COMMON, + UNCOMMON, + RARE, + EPIC, + LEGENDARY, + MYTHIC +}; + +enum class LootType { + MISC, + WEAPON, + AMMO, + ARMOR, + CONSUMABLE, + MATERIAL, + QUEST, + KEY, + CURRENCY, + JEWELRY +}; + +enum class InventoryMoveType { + REMOVE, + USE, // use for self + TRADE +}; + +struct WeaponStats { + uint16_t damage; // use as default if weapon like knife (without ammo, ammo_capacity always zero) + uint8_t ammo_capacity; + uint8_t delay_between_shots; // milliseconds, delay reloading between shots + uint8_t element; // 0=physical, 1=fire, etc. + uint8_t required_level; +}; + +struct AmmoStats { + float speed; + uint16_t damage; +}; + +struct ArmorStats { + uint16_t defense; + uint16_t durability; + uint16_t max_durability; + uint8_t armor_class; // light/medium/heavy + uint8_t required_level; +}; + +struct ConsumableStats { + uint16_t effect_id; // e.g., healing potion, mana elixir + uint16_t effect_power; + uint32_t cooldown_ms; +}; + +struct JewelryStats { + uint16_t stat_bonus_type; // strength, intelligence, etc. + uint16_t stat_bonus_value; + uint8_t socket_count; +}; + +struct QuestItem { + uint32_t quest_id; + uint32_t objective_index; +}; + +using LootPayload = std::variant< +std::monostate, // MISC, MATERIAL, KEY, CURRENCY +WeaponStats, +AmmoStats, +ArmorStats, +ConsumableStats, +JewelryStats, +QuestItem +>; + + struct AuthenticationData { + uint64_t timestamp; + uint64_t session_id; std::string username; std::string password; - uint64_t session_id; }; struct PlayerStateData { + uint64_t timestamp; + uint64_t session_id; uint64_t player_id; uint32_t input_id; glm::vec3 position; @@ -22,30 +103,80 @@ struct PlayerStateData { bool jumping; bool crouching; bool sprinting; - uint64_t timestamp; - uint64_t session_id; }; struct PlayerPositionData { + uint64_t timestamp; + uint64_t session_id; uint64_t player_id; glm::vec3 position; glm::vec3 velocity; - uint64_t timestamp; - uint64_t session_id; }; -struct ChunkRequestData { +struct ChunkData { + uint64_t timestamp; + uint64_t session_id; int chunk_x; int chunk_z; uint8_t lod; - uint64_t session_id; // which session requested it + nlohmann::json chunk_json; }; -struct ChunkData { - int chunk_x; - int chunk_z; - uint8_t lod; - nlohmann::json chunk_json; // already serialized +struct CollisionData { + uint64_t timestamp; + uint64_t session_id; + glm::vec3 position; + float radius; +}; + +struct NpcData { + uint64_t timestamp; + uint64_t session_id; + uint64_t player_id; + uint64_t npc_id; + float damage; + float health; + bool is_dead; + std::string type; // "combat" or "dialogue" + nlohmann::json quests; // for dialogue +}; + +struct FamiliarData { uint64_t timestamp; uint64_t session_id; + uint64_t familiar_id; + uint64_t target_id; + bool success; + std::string command; +}; + +struct EntitySpawnData { + uint64_t timestamp; + uint64_t session_id; + uint64_t entity_id; + int type; + glm::vec3 position; +}; + +struct LootPickupData { + uint64_t timestamp; + uint64_t session_id; + uint64_t player_id; + uint64_t loot_id; // world instance ID + LootType type; + LootRarity rarity; + uint16_t quantity; // stack size (ignored for non‑stackables) + LootPayload payload; // type‑safe extra data +}; + +struct InventoryData { + uint64_t timestamp; + uint64_t session_id; + uint64_t player_id; + uint64_t loot_id; // world instance ID + int use_slot_id; // equipmentSlots + int inv_slot_id; // inventorySlots + uint64_t target_id; // optional, if move_type TRADE + uint16_t quantity; + InventoryMoveType move_type; }; diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 67156a2..45485a0 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -30,62 +30,35 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this GetOrCreateChunk(int chunkX, int chunkZ); void GenerateWorldAroundPlayer(uint64_t playerId, const glm::vec3& position); void PreloadWorldData(float radius); float GetTerrainHeight(float x, float z) const; BiomeType GetBiomeAt(float x, float z) const; - // Entity methods uint64_t SpawnNPC(NPCType type, const glm::vec3& position, uint64_t ownerId = 0); void DespawnNPC(uint64_t npcId); NPCEntity* GetNPCEntity(uint64_t npcId); GameEntity* GetEntity(uint64_t entityId); std::shared_ptr GetPlayer(uint64_t playerId); - // Collision methods CollisionResult CheckCollision(const glm::vec3& position, float radius, uint64_t excludeEntityId = 0); bool Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, RaycastHit& hit); - // Loot methods void CreateLootEntity(const glm::vec3& position, std::shared_ptr item, int quantity); - void HandleLootPickup(uint64_t sessionId, const nlohmann::json& data); - void HandleInventoryMove(uint64_t sessionId, const nlohmann::json& data); - void HandleItemUse(uint64_t sessionId, const nlohmann::json& data); - void HandleItemDrop(uint64_t sessionId, const nlohmann::json& data); - void HandleTradeRequest(uint64_t sessionId, const nlohmann::json& data); - void HandleGoldTransaction(uint64_t sessionId, const nlohmann::json& data); - - // Message handlers (remaining non-virtual ones) - void HandleNPCInteraction(uint64_t sessionId, const nlohmann::json& data); - void HandleCollisionCheck(uint64_t sessionId, const nlohmann::json& data); - void HandleEntitySpawnRequest(uint64_t sessionId, const nlohmann::json& data); - void HandleFamiliarCommand(uint64_t sessionId, const nlohmann::json& data); - - // Message handling entry points - void HandleMessage(uint64_t sessionId, const nlohmann::json& message); - - // Player connection/disconnection + void OnPlayerConnected(uint64_t sessionId, uint64_t playerId) override; void OnPlayerDisconnected(uint64_t sessionId) override; - // Broadcasting methods (still used) void BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, const std::vector& data, float radius = 50.0f); void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, @@ -106,23 +79,34 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this cb); void SetSendChunkCallback(std::function cb); + void SetSendCollisionResponseCallback(std::function cb); void SetPlayerStateCallback(std::function cb); void SetBroadcastPlayerPositionCallback(std::function cb); + void SetSendNPCInteractionResponseCallback(std::function cb); + void SetSendFamiliarCommandResponseCallback(std::function cb); + void SetSendEntitySpawnResponseCallback(std::function cb); + void SetSendLootPickupResponseCallback(std::function cb); + void SetSendInventoryResponseCallback(std::function cb); - // Incoming data entry points (called by sessions) void OnAuthentication(const AuthenticationData& data); - void OnChunkRequest(const ChunkRequestData& data); + void OnChunkRequest(const ChunkData& data); + void OnCollisionCheck(const CollisionData& data); void OnPlayerPosition(const PlayerPositionData& data); void OnPlayerState(const PlayerStateData& data); + void OnNPCInteraction(const NpcData& data); + void OnFamiliarCommand(const FamiliarData& data); + void OnEntitySpawnRequest(const EntitySpawnData& data); + void OnLootPickup(const LootPickupData& data); + void OnInventory(const InventoryData& data); - // Overrides of virtual methods from LogicCore + // scripting + void RegisterPythonEventHandlers() override; void FirePythonEvent(const std::string& eventName, const nlohmann::json& data) override; nlohmann::json CallPythonFunction(const std::string& moduleName, const std::string& functionName, const nlohmann::json& args) override; - void RegisterPythonEventHandlers() override; + void SaveGameState() override; void CleanupOldData() override; void ProcessGameTick(float deltaTime) override; @@ -135,7 +119,6 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this connMgr); void SetDatabaseBackend(std::unique_ptr backend); @@ -157,8 +140,14 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this sendAuthResponseCb_; std::function sendChunkCb_; + std::function sendCollisionResponseCb_; std::function broadcastPlayerPositionCb_; std::function playerStateCb_; + std::function sendNPCInteractionResponseCb_; + std::function sendFamiliarCommandResponseCb_; + std::function sendEntitySpawnResponseCb_; + std::function sendLootPickupResponseCb_; + std::function sendInventoryResponseCb_; bool pythonEnabled_ = false; @@ -166,8 +155,6 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this GetItem(uint64_t playerId, int slot); diff --git a/include/game/LogicCore.hpp b/include/game/LogicCore.hpp index 9bb8b18..1b75c50 100644 --- a/include/game/LogicCore.hpp +++ b/include/game/LogicCore.hpp @@ -54,10 +54,10 @@ class LogicCore { uint64_t GetPlayerIdBySession(uint64_t sessionId) const; uint64_t GetSessionIdByPlayer(uint64_t playerId) const; - void SendError(uint64_t sessionId, const std::string& message, int code = 0); - void SendSuccess(uint64_t sessionId, const std::string& message, const nlohmann::json& data = {}); - void SendToSession(uint64_t sessionId, const nlohmann::json& message); - void SendBinaryToSession(uint64_t sessionId, uint16_t messageType, const std::vector& data); + void SendError(uint64_t sessionId, const std::string& description, int code = 0); + void SendSuccess(uint64_t sessionId, const std::string& description, const nlohmann::json& data = {}); + void SendToSession(uint64_t sessionId, uint16_t messageType, const std::vector& data); + void SendToSessionJson(uint64_t sessionId, const nlohmann::json& message); // Python scripting – virtual, to be overridden by GameLogic virtual void FirePythonEvent(const std::string& eventName, const nlohmann::json& data); diff --git a/include/game/LootItem.hpp b/include/game/LootItem.hpp index f140992..d76e07b 100644 --- a/include/game/LootItem.hpp +++ b/include/game/LootItem.hpp @@ -7,27 +7,9 @@ #include #include -enum class LootRarity { - COMMON = 0, - UNCOMMON = 1, - RARE = 2, - EPIC = 3, - LEGENDARY = 4, - MYTHIC = 5 -}; - -enum class ItemType { - WEAPON = 0, - ARMOR = 1, - CONSUMABLE = 2, - MATERIAL = 3, - QUEST = 4, - KEY = 5, - CURRENCY = 6, - JEWELRY = 7 -}; +#include "game/GameData.hpp" -struct ItemStat { +struct LootStat { std::string statName; float baseValue; float currentValue; @@ -37,7 +19,7 @@ struct ItemStat { void Deserialize(const nlohmann::json& data); }; -struct ItemModifier { +struct LootModifier { std::string modifierType; // "add", "multiply", "set" std::string targetStat; float value; @@ -48,12 +30,12 @@ struct ItemModifier { class LootItem { public: LootItem(); - LootItem(uint64_t id, const std::string& name = "", ItemType type = ItemType::MATERIAL, LootRarity rarity = LootRarity::COMMON); + LootItem(uint64_t id, const std::string& name = "", LootType type = LootType::MATERIAL, LootRarity rarity = LootRarity::COMMON); // Getters uint64_t GetId() const { return id_; } const std::string& GetName() const { return name_; } - ItemType GetType() const { return type_; } + LootType GetType() const { return type_; } LootRarity GetRarity() const { return rarity_; } int GetStackSize() const { return stackSize_; } int GetMaxStackSize() const { return maxStackSize_; } @@ -70,12 +52,12 @@ class LootItem { // Stats management void AddStat(const std::string& name, float baseValue, float maxValue = 0.0f); - ItemStat* GetStat(const std::string& name); - const std::vector& GetStats() const { return stats_; } + LootStat* GetStat(const std::string& name); + const std::vector& GetStats() const { return stats_; } // Modifiers management - void AddModifier(const ItemModifier& modifier); - std::vector GetModifiersForStat(const std::string& statName) const; + void AddModifier(const LootModifier& modifier); + std::vector GetModifiersForStat(const std::string& statName) const; // Serialization nlohmann::json Serialize() const; @@ -91,7 +73,7 @@ class LootItem { uint64_t id_; std::string name_; std::string description_; - ItemType type_; + LootType type_; LootRarity rarity_; int stackSize_ = 1; @@ -101,8 +83,8 @@ class LootItem { glm::vec3 iconColor_ = glm::vec3(1.0f); std::string iconTexture_; - std::vector stats_; - std::vector modifiers_; + std::vector stats_; + std::vector modifiers_; // Trading properties bool tradable_ = true; diff --git a/include/network/BinaryProtocol.hpp b/include/network/BinaryProtocol.hpp index c516983..ea52c0c 100644 --- a/include/network/BinaryProtocol.hpp +++ b/include/network/BinaryProtocol.hpp @@ -16,56 +16,61 @@ namespace BinaryProtocol { // Message types enum MessageType : uint16_t { MESSAGE_TYPE_INVALID = 0, - + // System messages MESSAGE_TYPE_HEARTBEAT = 1, MESSAGE_TYPE_PROTOCOL_NEGOTIATION = 2, MESSAGE_TYPE_AUTHENTICATION = 3, MESSAGE_TYPE_ERROR = 4, MESSAGE_TYPE_SUCCESS = 5, - + MESSAGE_TYPE_COLLISION_CHECK = 50, + // World messages MESSAGE_TYPE_CHUNK_DATA = 100, MESSAGE_TYPE_CHUNK_REQUEST = 101, MESSAGE_TYPE_TERRAIN_HEIGHT = 102, MESSAGE_TYPE_BIOME_DATA = 103, - - // Entity messages - MESSAGE_TYPE_ENTITY_SPAWN = 200, - MESSAGE_TYPE_ENTITY_UPDATE = 201, - MESSAGE_TYPE_ENTITY_DESPAWN = 202, - MESSAGE_TYPE_ENTITY_BATCH_UPDATE = 203, - + // Player messages - MESSAGE_TYPE_PLAYER_POSITION = 300, - MESSAGE_TYPE_PLAYER_VELOCITY = 301, - MESSAGE_TYPE_PLAYER_ROTATION = 302, - MESSAGE_TYPE_PLAYER_STATE = 303, - MESSAGE_TYPE_PLAYER_POSITION_CORRECTION = 304, - MESSAGE_TYPE_PLAYER_UPDATE = 305, - MESSAGE_TYPE_PLAYER_SPAWN = 306, - MESSAGE_TYPE_PLAYER_DESPAWN = 307, + MESSAGE_TYPE_PLAYER_POSITION = 200, + MESSAGE_TYPE_PLAYER_VELOCITY = 201, + MESSAGE_TYPE_PLAYER_ROTATION = 202, + MESSAGE_TYPE_PLAYER_STATE = 203, + MESSAGE_TYPE_PLAYER_POSITION_CORRECTION = 204, + MESSAGE_TYPE_PLAYER_UPDATE = 205, + MESSAGE_TYPE_PLAYER_SPAWN = 206, + MESSAGE_TYPE_PLAYER_DESPAWN = 207, + + // Entity messages + MESSAGE_TYPE_ENTITY_SPAWN = 300, + MESSAGE_TYPE_ENTITY_UPDATE = 301, + MESSAGE_TYPE_ENTITY_DESPAWN = 302, + MESSAGE_TYPE_ENTITY_BATCH_UPDATE = 303, // NPC messages MESSAGE_TYPE_NPC_SPAWN = 400, MESSAGE_TYPE_NPC_UPDATE = 401, MESSAGE_TYPE_NPC_DESPAWN = 402, MESSAGE_TYPE_NPC_INTERACTION = 403, - + // Combat messages MESSAGE_TYPE_COMBAT_EVENT = 500, MESSAGE_TYPE_DAMAGE_EVENT = 501, MESSAGE_TYPE_HEALTH_UPDATE = 502, - + // Inventory messages - MESSAGE_TYPE_INVENTORY_UPDATE = 600, - MESSAGE_TYPE_LOOT_SPAWN = 601, - MESSAGE_TYPE_LOOT_PICKUP = 602, - + MESSAGE_TYPE_LOOT_SPAWN = 600, + MESSAGE_TYPE_LOOT_PICKUP = 601, + MESSAGE_TYPE_INVENTORY_UPDATE = 602, + MESSAGE_TYPE_INVENTORY_MOVE = 603, + // Chat messages MESSAGE_TYPE_CHAT_MESSAGE = 700, MESSAGE_TYPE_SYSTEM_MESSAGE = 701, - + + // Familiar + MESSAGE_TYPE_FAMILIAR_COMMAND = 800, + // Custom messages MESSAGE_TYPE_CUSTOM_EVENT = 1000 }; @@ -89,10 +94,10 @@ namespace BinaryProtocol { uint32_t timestamp; // Timestamp in ms uint32_t length; // Payload length uint32_t checksum; // CRC32 checksum - + // Constructor for easy initialization - NetworkHeader(uint16_t type = 0, uint32_t seq = 0, uint8_t ver = 1, uint8_t flgs = 0) - : version(ver), flags(flgs), message_type(type), + NetworkHeader(uint16_t type = 0, uint32_t seq = 0, uint8_t ver = 1, uint8_t flgs = 0) + : version(ver), flags(flgs), message_type(type), sequence(seq), timestamp(0), length(0), checksum(0) {} }; @@ -100,11 +105,11 @@ namespace BinaryProtocol { struct BinaryMessage { NetworkHeader header; std::vector data; - + // Serialization std::vector Serialize() const; static BinaryMessage Deserialize(const uint8_t* buffer, size_t length); - + // Helper methods bool IsCompressed() const { return (header.flags & FLAG_COMPRESSED) != 0; } bool IsEncrypted() const { return (header.flags & FLAG_ENCRYPTED) != 0; } @@ -115,7 +120,7 @@ namespace BinaryProtocol { class BinaryWriter { public: BinaryWriter(); - + // Write methods void WriteUInt8(uint8_t value); void WriteUInt16(uint16_t value); @@ -130,14 +135,14 @@ namespace BinaryProtocol { void WriteVector3(const glm::vec3& vec); void WriteQuaternion(const glm::quat& quaternion); void WriteJson(const nlohmann::json& json); - + // Get the buffer const std::vector& GetBuffer() const { return buffer_; } size_t GetSize() const { return buffer_.size(); } - + // Clear the buffer void Clear(); - + private: std::vector buffer_; }; @@ -145,7 +150,7 @@ namespace BinaryProtocol { class BinaryReader { public: BinaryReader(const uint8_t* data, size_t length); - + // Read methods uint8_t ReadUInt8(); uint16_t ReadUInt16(); @@ -160,19 +165,19 @@ namespace BinaryProtocol { glm::vec3 ReadVector3(); glm::quat ReadQuaternion(); nlohmann::json ReadJson(); - + // Check remaining data size_t Remaining() const { return length_ - position_; } bool CanRead(size_t size) const { return position_ + size <= length_; } - + // Get current position size_t GetPosition() const { return position_; } - + private: const uint8_t* data_; size_t length_; size_t position_{0}; - + void CheckBounds(size_t size) const; }; @@ -180,11 +185,11 @@ namespace BinaryProtocol { uint32_t CalculateCRC32(const void* data, size_t length); std::vector CompressData(const std::vector& data, int level = 6); std::vector DecompressData(const std::vector& compressed); - + // Protocol version management constexpr uint8_t CURRENT_PROTOCOL_VERSION = 1; constexpr uint32_t MAX_MESSAGE_SIZE = 10 * 1024 * 1024; // 10 MB - + // Protocol negotiation struct ProtocolCapabilities { uint8_t version; @@ -192,7 +197,7 @@ namespace BinaryProtocol { bool supports_encryption; uint32_t max_message_size; std::vector supported_message_types; - + std::vector Serialize() const; static ProtocolCapabilities Deserialize(const uint8_t* data, size_t length); }; diff --git a/include/network/BinarySession.hpp b/include/network/BinarySession.hpp index 9d8336a..45f3a97 100644 --- a/include/network/BinarySession.hpp +++ b/include/network/BinarySession.hpp @@ -189,29 +189,13 @@ class BinarySession : public IConnection, public std::enable_shared_from_this& data) override; - void SendBinary(uint16_t message_type, const void* data, size_t length); + void Send(uint16_t message_type, const std::vector& data) override; + void Send(uint16_t message_type, const void* data, size_t length); + void SendRaw(const std::string& data) override; + void SendJson(const nlohmann::json& message) override; + void SendWithAck(uint16_t message_type, const std::vector& data); void SendBinaryWithAck(uint16_t message_type, const std::vector& data); - void SendBinaryError(uint16_t message_type, const std::string& error_message, int code); - - // JSON compatibility (for backward compatibility) - void Send(const nlohmann::json& message) override; - void SendRaw(const std::string& data); - void SendError(const std::string& message, int code); - void SendSuccess(const std::string& message, const nlohmann::json& data = {}); - void SendWorldChunk(int chunkX, int chunkZ, const nlohmann::json& chunkData); - void SendEntityUpdate(uint64_t entityId, const nlohmann::json& entityData); - void SendEntitySpawn(uint64_t entityId, const nlohmann::json& spawnData); - void SendEntityDespawn(uint64_t entityId); - void SendCollisionEvent(uint64_t entityId1, uint64_t entityId2, const glm::vec3& point); - void SyncPlayerState(const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& velocity); - void SendNearbyEntities(const std::vector& entities); - void SendNPCInteraction(uint64_t npcId, const std::string& interactionType, const nlohmann::json& data); - void SendCompressedWorldData(const std::vector& compressedData); - void HandleWorldRequest(const nlohmann::json& data); - void HandleEntityInteraction(const nlohmann::json& data); - void HandleMovementUpdate(const nlohmann::json& data); - void HandleFamiliarCommand(const nlohmann::json& data); + void SendError(uint16_t message_type, const std::string& error_message, int code) override; // Protocol negotiation void StartProtocolNegotiation(); @@ -306,10 +290,6 @@ class BinarySession : public IConnection, public std::enable_shared_from_this& chunkData); void SendEntityUpdateBinary(uint64_t entityId, const std::vector& entityData); diff --git a/include/network/GameServer.hpp b/include/network/GameServer.hpp index 7ffa440..d9afb5c 100644 --- a/include/network/GameServer.hpp +++ b/include/network/GameServer.hpp @@ -14,6 +14,7 @@ #include "config/ConfigManager.hpp" #include "process/ProcessPool.hpp" +#include "network/IConnection.hpp" #include "network/ConnectionManager.hpp" #include "network/BinarySession.hpp" #include "network/WebSocketProtocol.hpp" @@ -32,7 +33,8 @@ class GameServer { void Run(); void Shutdown(); - asio::io_context& GetIoContext() { return ioContext_; } + asio::io_context& GetIoContext(); + void HandleIPCMessage(const nlohmann::json& data, GameLogic& game_logic); void InitSessionFactory(int workerId, ProcessPool* processPool, GameLogic& game_logic); void RegisterCallbacks(const std::string& protocol, GameLogic& game_logic); diff --git a/include/network/IConnection.hpp b/include/network/IConnection.hpp index 88277af..cb32cc9 100644 --- a/include/network/IConnection.hpp +++ b/include/network/IConnection.hpp @@ -34,9 +34,10 @@ class IConnection { virtual uint64_t GetSessionId() const = 0; // Send methods - virtual void Send(const nlohmann::json& message) = 0; + virtual void Send(uint16_t message_type, const std::vector& data) = 0; virtual void SendRaw(const std::string& data) = 0; - virtual void SendBinary(uint16_t message_type, const std::vector& data) = 0; + virtual void SendJson(const nlohmann::json& message) = 0; + virtual void SendError(uint16_t message_type, const std::string& error_message, int code) = 0; // Callback setters using MessageHandler = std::function; diff --git a/include/network/WebSocketProtocol.hpp b/include/network/WebSocketProtocol.hpp index ea28387..1399dbf 100644 --- a/include/network/WebSocketProtocol.hpp +++ b/include/network/WebSocketProtocol.hpp @@ -35,14 +35,6 @@ namespace WebSocketProtocol { OP_PONG = 0xA }; - static const std::unordered_map IPCMessageTypes = { - {"welcome", 1}, - {"heartbeat", 2}, - {"broadcast", 3}, - {"shutdown", 4}, - {"reload_config", 5} - }; - // WebSocket frame structure struct WebSocketFrame { bool fin{true}; diff --git a/include/network/WebSocketSession.hpp b/include/network/WebSocketSession.hpp index 039b3ed..4e446b8 100644 --- a/include/network/WebSocketSession.hpp +++ b/include/network/WebSocketSession.hpp @@ -28,11 +28,10 @@ class WebSocketSession : public IConnection, public std::enable_shared_from_this bool IsConnected() const override; uint64_t GetSessionId() const override; - void SendError(const std::string& message, int code = 500); - - void Send(const nlohmann::json& message) override; + void Send(uint16_t message_type, const std::vector& data) override; void SendRaw(const std::string& data) override; - void SendBinary(uint16_t message_type, const std::vector& data) override; + void SendJson(const nlohmann::json& message) override; + void SendError(uint16_t message_type, const std::string& error_message, int code) override; void SetMessageHandler(MessageHandler handler) override; void SetCloseHandler(CloseHandler handler) override; diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 1949043..562db5e 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -69,7 +69,6 @@ void GameLogic::Initialize() { pythonEnabled_ = false; } } - RegisterWorldHandlers(); Logger::Info("GameLogic world system initialized successfully"); } @@ -125,12 +124,12 @@ uint64_t GameLogic::SpawnNPC(NPCType type, const glm::vec3& position, uint64_t o return LogicEntity::GetInstance().SpawnNPC(type, position, ownerId); } -void GameLogic::DespawnNPC(uint64_t npcId) { - LogicEntity::GetInstance().DespawnNPC(npcId); +void GameLogic::DespawnNPC(uint64_t npc_id) { + LogicEntity::GetInstance().DespawnNPC(npc_id); } -NPCEntity* GameLogic::GetNPCEntity(uint64_t npcId) { - return LogicEntity::GetInstance().GetNPCEntity(npcId); +NPCEntity* GameLogic::GetNPCEntity(uint64_t npc_id) { + return LogicEntity::GetInstance().GetNPCEntity(npc_id); } GameEntity* GameLogic::GetEntity(uint64_t entityId) { @@ -164,231 +163,6 @@ void GameLogic::CreateLootEntity(const glm::vec3& position, std::shared_ptrGetType() != EntityType::ITEM) { - SendError(session_id, "Invalid loot entity"); - return; - } - std::shared_ptr player = PlayerManager::GetInstance().GetPlayer(player_id); - if (!player) { - SendError(session_id, "Player not found"); - return; - } - float distance = glm::distance(player->GetPosition(), lootEntity->GetPosition()); - if (distance > 5.0f) { - SendError(session_id, "Too far to loot"); - return; - } - auto& inv = InventorySystem::GetInstance(); - if (inv.AddItem(player_id, LootItem(lootId, lootEntity->GetName()), quantity)) { - EntityManager::GetInstance().DestroyEntity(lootId); - SendSuccess(session_id, "Loot collected"); - FirePythonEvent("loot_pickup", { - {"player_id", player_id}, - {"itemId", lootId}, - {"quantity", quantity} - }); - } else { - SendError(session_id, "Inventory full"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleLootPickup: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - -void GameLogic::HandleInventoryMove(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t player_id = GetPlayerIdBySession(session_id); - int fromSlot = data.value("fromSlot", -1); - int toSlot = data.value("toSlot", -1); - if (fromSlot < 0 || toSlot < 0) { - SendError(session_id, "Invalid slot indices"); - return; - } - auto& inv = InventorySystem::GetInstance(); - if (inv.MoveItem(player_id, fromSlot, toSlot)) { - nlohmann::json response = { - {"type", "inventory_move_response"}, - {"success", true}, - {"fromSlot", fromSlot}, - {"toSlot", toSlot}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, response); - } else { - SendError(session_id, "Failed to move item"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleInventoryMove: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - -void GameLogic::HandleItemUse(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t player_id = GetPlayerIdBySession(session_id); - int slot = data.value("slot", -1); - uint64_t itemId = data.value("itemId", 0); - auto& inv = InventorySystem::GetInstance(); - std::shared_ptr item; - if (slot >= 0) { - item = inv.GetItem(player_id, slot); - } else if (itemId) { - auto invData = inv.GetInventory(player_id); - for (const auto& s : invData) { - if (s.item && s.item->GetId() == itemId) { - item = s.item; - slot = s.position; - break; - } - } - } - if (!item) { - SendError(session_id, "Item not found"); - return; - } - if (item->IsConsumable()) { - if (inv.RemoveItem(player_id, itemId, 1)) { - SendSuccess(session_id, "Item used"); - FirePythonEvent("item_used", { - {"player_id", player_id}, - {"itemId", itemId}, - {"slot", slot} - }); - } else { - SendError(session_id, "Failed to use item"); - } - } else { - SendError(session_id, "Item cannot be used this way"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleItemUse: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - -void GameLogic::HandleItemDrop(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t player_id = GetPlayerIdBySession(session_id); - int slot = data.value("slot", -1); - int quantity = data.value("quantity", 1); - if (slot < 0) { - SendError(session_id, "Invalid slot"); - return; - } - auto& inv = InventorySystem::GetInstance(); - auto item = inv.GetItem(player_id, slot); - if (!item) { - SendError(session_id, "No item in that slot"); - return; - } - if (inv.RemoveItem(player_id, item->GetId(), quantity)) { - auto player = GetPlayer(player_id); - if (player) { - CreateLootEntity(player->GetPosition(), item, quantity); - } - SendSuccess(session_id, "Item dropped"); - } else { - SendError(session_id, "Failed to drop item"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleItemDrop: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - -void GameLogic::HandleTradeRequest(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t player_id = GetPlayerIdBySession(session_id); - uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); - std::string action = data.value("action", "request"); - if (targetPlayerId == 0) { - SendError(session_id, "Invalid target player"); - return; - } - if (action == "request") { - uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); - if (targetSession == 0) { - SendError(session_id, "Target player not online"); - return; - } - nlohmann::json request = { - {"type", "trade_request"}, - {"fromPlayerId", player_id}, - {"fromPlayerName", GetPlayer(player_id)->GetName()}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(targetSession, request); - SendSuccess(session_id, "Trade request sent"); - } else if (action == "accept") { - uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); - nlohmann::json acceptMsg = { - {"type", "trade_start"}, - {"player1", player_id}, - {"player2", targetPlayerId}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, acceptMsg); - SendToSession(targetSession, acceptMsg); - } else { - SendError(session_id, "Unsupported trade action"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleTradeRequest: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - -void GameLogic::HandleGoldTransaction(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t player_id = GetPlayerIdBySession(session_id); - uint64_t targetPlayerId = data.value("targetPlayerId", 0ULL); - int64_t amount = data.value("amount", 0); - std::string type = data.value("type", "transfer"); - if (targetPlayerId == 0 || amount <= 0) { - SendError(session_id, "Invalid target or amount"); - return; - } - auto& inv = InventorySystem::GetInstance(); - if (type == "transfer") { - if (inv.GetGold(player_id) < amount) { - SendError(session_id, "Insufficient gold"); - return; - } - if (inv.RemoveGold(player_id, amount) && inv.AddGold(targetPlayerId, amount)) { - SendSuccess(session_id, "Gold transferred"); - uint64_t targetSession = GetSessionIdByPlayer(targetPlayerId); - if (targetSession) { - nlohmann::json notify = { - {"type", "gold_received"}, - {"fromPlayerId", player_id}, - {"amount", amount}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(targetSession, notify); - } - } else { - SendError(session_id, "Transaction failed"); - } - } else { - SendError(session_id, "Unsupported transaction type"); - } - } catch (const std::exception& e) { - Logger::Error("Error in HandleGoldTransaction: {}", e.what()); - SendError(session_id, "Internal server error"); - } -} - void GameLogic::SendPositionCorrection(uint64_t session_id, const glm::vec3& position, const glm::vec3& velocity) { BinaryProtocol::BinaryWriter writer; writer.WriteVector3(position); @@ -398,7 +172,7 @@ void GameLogic::SendPositionCorrection(uint64_t session_id, const glm::vec3& pos auto session = connectionManager_->GetSession(session_id); if (!session || !session->IsConnected()) return; if (session->GetProtocolMode() == ProtocolMode::Binary) { - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, data); + session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, data); } else { nlohmann::json jsonMsg = { {"type", "position_correction"}, @@ -406,72 +180,7 @@ void GameLogic::SendPositionCorrection(uint64_t session_id, const glm::vec3& pos {"vx", velocity.x}, {"vy", velocity.y}, {"vz", velocity.z}, {"timestamp", GetCurrentTimestamp()} }; - session->Send(jsonMsg); - } -} - -void GameLogic::RegisterWorldHandlers() { - RegisterHandler("npc_interaction", [this](uint64_t session_id, const nlohmann::json& data) { - HandleNPCInteraction(session_id, data); - }); - RegisterHandler("collision_check", [this](uint64_t session_id, const nlohmann::json& data) { - HandleCollisionCheck(session_id, data); - }); - RegisterHandler("familiar_command", [this](uint64_t session_id, const nlohmann::json& data) { - HandleFamiliarCommand(session_id, data); - }); - RegisterHandler("entity_spawn_request", [this](uint64_t session_id, const nlohmann::json& data) { - HandleEntitySpawnRequest(session_id, data); - }); - RegisterHandler("loot_pickup", [this](uint64_t session_id, const nlohmann::json& data) { - HandleLootPickup(session_id, data); - }); - RegisterHandler("inventory_move", [this](uint64_t session_id, const nlohmann::json& data) { - HandleInventoryMove(session_id, data); - }); - RegisterHandler("item_use", [this](uint64_t session_id, const nlohmann::json& data) { - HandleItemUse(session_id, data); - }); - RegisterHandler("item_drop", [this](uint64_t session_id, const nlohmann::json& data) { - HandleItemDrop(session_id, data); - }); - RegisterHandler("trade_request", [this](uint64_t session_id, const nlohmann::json& data) { - HandleTradeRequest(session_id, data); - }); - RegisterHandler("gold_transaction", [this](uint64_t session_id, const nlohmann::json& data) { - HandleGoldTransaction(session_id, data); - }); - Logger::Info("Registered world message handlers"); -} - -void GameLogic::HandleMessage(uint64_t session_id, const nlohmann::json& message) { - Logger::Debug("GameLogic::HandleMessage called for session {}", session_id); - Logger::Debug("Message content: {}", message.dump()); - std::string type = message.value("type", ""); - Logger::Debug("Message type: '{}'", type); - if (type == "collision_check") { - HandleCollisionCheck(session_id, message); - } else if (type == "npc_interaction") { - HandleNPCInteraction(session_id, message); - } else if (type == "familiar_command") { - HandleFamiliarCommand(session_id, message); - } else if (type == "entity_spawn_request") { - HandleEntitySpawnRequest(session_id, message); - } else if (type == "loot_pickup") { - HandleLootPickup(session_id, message); - } else if (type == "inventory_move") { - HandleInventoryMove(session_id, message); - } else if (type == "item_use") { - HandleItemUse(session_id, message); - } else if (type == "item_drop") { - HandleItemDrop(session_id, message); - } else if (type == "trade_request") { - HandleTradeRequest(session_id, message); - } else if (type == "gold_transaction") { - HandleGoldTransaction(session_id, message); - } else { - Logger::Warn("Unknown message type '{}' from session {}", type, session_id); - LogicCore::HandleMessage(session_id, message); + session->SendJson(jsonMsg); } } @@ -498,159 +207,16 @@ void GameLogic::OnPlayerDisconnected(uint64_t session_id) { LogicCore::OnPlayerDisconnected(session_id); } -void GameLogic::HandleNPCInteraction(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t npcId = data.value("npcId", 0ULL); - std::string interactionType = data.value("interaction", ""); - if (npcId == 0 || interactionType.empty()) { - SendError(session_id, "Invalid NPC interaction", 400); - return; - } - NPCEntity* npc = GetNPCEntity(npcId); - if (!npc) { - SendError(session_id, "NPC not found", 404); - return; - } - uint64_t player_id = GetPlayerIdBySession(session_id); - std::shared_ptr player = GetPlayer(player_id); - if (!player) { - SendError(session_id, "Player not found", 404); - return; - } - float distance = glm::distance(player->GetPosition(), npc->GetPosition()); - if (distance > 15.0f) { - SendError(session_id, "Too far from NPC", 400); - return; - } - if (interactionType == "attack") { - float damage = 10.0f; - npc->TakeDamage(damage, player_id); - bool isDead = npc->IsDead(); - if (isDead) { - MobSystem::GetInstance().OnMobDeath(npcId, player_id); - } - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(player_id); - writer.WriteUInt64(npcId); - writer.WriteFloat(damage); - writer.WriteFloat(npc->GetStats().health); - writer.WriteUInt8(isDead ? 1 : 0); - writer.WriteUInt64(GetCurrentTimestamp()); - SendBinaryToSession(session_id, BinaryProtocol::MESSAGE_TYPE_COMBAT_EVENT, writer.GetBuffer()); - } else if (interactionType == "talk") { - auto& questMgr = QuestManager::GetInstance(); - auto quests = questMgr.GetQuestsFromNPC(player_id, npc->GetId()); - nlohmann::json response = { - {"type", "npc_dialogue"}, - {"npcId", npcId}, - {"quests", quests}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, response); - } - } catch (const std::exception& e) { - Logger::Error("Error handling NPC interaction: {}", e.what()); - SendError(session_id, "Failed to process NPC interaction", 500); - } -} - -void GameLogic::HandleFamiliarCommand(uint64_t session_id, const nlohmann::json& data) { - try { - uint64_t familiarId = data.value("familiarId", 0ULL); - std::string command = data.value("command", ""); - uint64_t targetId = data.value("targetId", 0ULL); - if (familiarId == 0 || command.empty()) { - SendError(session_id, "Invalid familiar command", 400); - return; - } - NPCEntity* familiar = GetNPCEntity(familiarId); - if (!familiar) { - SendError(session_id, "Familiar not found", 404); - return; - } - uint64_t player_id = GetPlayerIdBySession(session_id); - if (familiar->GetOwnerId() != player_id) { - SendError(session_id, "Not your familiar", 403); - return; - } - if (command == "follow") { - familiar->SetBehaviorState(NPCAIState::FOLLOW); - familiar->SetTarget(player_id); - } else if (command == "attack") { - familiar->SetBehaviorState(NPCAIState::CHASE); - familiar->SetTarget(targetId); - } else if (command == "stay") { - familiar->SetBehaviorState(NPCAIState::IDLE); - familiar->SetTarget(0); - } - nlohmann::json response = { - {"type", "familiar_command_response"}, - {"familiarId", familiarId}, - {"command", command}, - {"success", true}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, response); - } catch (const std::exception& e) { - Logger::Error("Error handling familiar command: {}", e.what()); - SendError(session_id, "Failed to process familiar command", 500); - } -} - -void GameLogic::HandleCollisionCheck(uint64_t session_id, const nlohmann::json& data) { - try { - float x = data.value("x", 0.0f); - float y = data.value("y", 0.0f); - float z = data.value("z", 0.0f); - float radius = data.value("radius", 0.5f); - glm::vec3 position(x, y, z); - CollisionResult result = CheckCollision(position, radius); - nlohmann::json response = { - {"type", "collision_check_response"}, - {"position", {x, y, z}}, - {"collided", result.collided}, - {"collidedWith", result.collidedWith}, - {"penetration", result.penetration}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, response); - } catch (const std::exception& e) { - Logger::Error("Error handling collision check: {}", e.what()); - SendError(session_id, "Failed to check collision", 500); - } +void GameLogic::SetSendCollisionResponseCallback(std::function cb) { + sendCollisionResponseCb_ = std::move(cb); } -void GameLogic::HandleEntitySpawnRequest(uint64_t session_id, const nlohmann::json& data) { - try { - int entityType = data.value("entityType", 0); - float x = data.value("x", 0.0f); - float y = data.value("y", 0.0f); - float z = data.value("z", 0.0f); - glm::vec3 position(x, y, z); - if (entityType >= static_cast(NPCType::WOLF_FAMILIAR) && - entityType <= static_cast(NPCType::CAT_FAMILIAR)) { - uint64_t player_id = GetPlayerIdBySession(session_id); - NPCType type = static_cast(entityType); - uint64_t npcId = SpawnNPC(type, position, player_id); - if (npcId > 0) { - nlohmann::json response = { - {"type", "entity_spawn_response"}, - {"entityId", npcId}, - {"entityType", entityType}, - {"position", {x, y, z}}, - {"success", true}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSession(session_id, response); - } else { - SendError(session_id, "Failed to spawn entity", 500); - } - } else { - SendError(session_id, "Invalid entity type", 400); - } - } catch (const std::exception& e) { - Logger::Error("Error handling entity spawn request: {}", e.what()); - SendError(session_id, "Failed to spawn entity", 500); +void GameLogic::OnCollisionCheck(const CollisionData& data) { + CollisionResult result = CheckCollision(data.position, data.radius); + if (sendCollisionResponseCb_) { + sendCollisionResponseCb_(data.session_id, result); + } else { + Logger::Error("No sendCollisionResponseCb_ set in GameLogic"); } } @@ -665,7 +231,7 @@ void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t mes auto session = connectionManager_->GetSession(session_id); if (!session || !session->IsConnected()) continue; if (session->GetProtocolMode() == ProtocolMode::Binary) { - session->SendBinary(messageType, data); + session->Send(messageType, data); continue; } nlohmann::json jsonMsg; @@ -698,7 +264,7 @@ void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t mes Logger::Error("BroadcastToNearbyPlayers: Failed to convert binary to JSON: {}", e.what()); continue; } - session->Send(jsonMsg); + session->SendJson(jsonMsg); } } @@ -713,7 +279,7 @@ void GameLogic::BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16 if (session_id != 0) { auto session = connectionManager_->GetSession(session_id); if (session && session->IsConnected()) { - session->SendBinary(messageType, data); + session->Send(messageType, data); } } } @@ -742,11 +308,11 @@ void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& } } nlohmann::json message = { - {"type", "entity_sync"}, + {"msg", "entity_sync"}, {"entities", entityList}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(session_id, message); + SendToSessionJson(session_id, message); } void GameLogic::BroadcastPlayerSpawn(uint64_t player_id) { @@ -783,7 +349,7 @@ void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nl if (session_id != 0) { auto session = connectionManager_->GetSession(session_id); if (session && session->IsConnected()) { - session->Send(message); + session->SendJson(message); } } } @@ -823,23 +389,23 @@ void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& posit void GameLogic::SendAuthenticationSuccess(uint64_t session_id, uint64_t player_id, const std::string& message) { nlohmann::json response = { - {"type", "authentication_response"}, + {"msg", "authentication"}, {"success", true}, {"player_id", player_id}, - {"message", message}, + {"desc", message}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(session_id, response); + SendToSessionJson(session_id, response); } void GameLogic::SendAuthenticationFailure(uint64_t session_id, const std::string& message) { nlohmann::json response = { - {"type", "authentication_response"}, + {"msg", "authentication"}, {"success", false}, - {"message", message}, + {"desc", message}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(session_id, response); + SendToSessionJson(session_id, response); } void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { @@ -881,7 +447,7 @@ void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vec for (auto& session : sessions) { if (session && session->IsConnected()) { try { - session->SendBinary(messageType, data); + session->Send(messageType, data); } catch (const std::exception& e) { Logger::Error("Failed to send binary broadcast to session {}: {}", session->GetSessionId(), e.what()); @@ -921,41 +487,6 @@ void GameLogic::BroadcastToPlayers(const std::vector& session_ids, con } } -void GameLogic::HandleIPCMessage(const nlohmann::json& message) { - try { - std::string msgType = message.value("type", ""); - auto it = IPCMessageTypes.find(msgType); - if (it == IPCMessageTypes.end()) { - Logger::Warn("Unknown IPC message type: {}", msgType); - return; - } - int typeCode = it->second; - switch (typeCode) { - case 1: - break; - case 2: - break; - case 3: - if (message.contains("data")) { - BroadcastToAllPlayers(message["data"]); - } - break; - case 4: - Logger::Info("Received shutdown command from master"); - Shutdown(); - break; - case 5: - Logger::Info("Received config reload command from master"); - break; - default: - Logger::Warn("Unhandled IPC message code: {}", typeCode); - break; - } - } catch (const std::exception& e) { - Logger::Error("Error handling IPC message: {}", e.what()); - } -} - void GameLogic::PerformMaintenance() { CleanupOldData(); SaveChunkData(); @@ -979,7 +510,7 @@ void GameLogic::SaveChunkData() { nlohmann::json GameLogic::PlayerUpdateToJson(uint64_t player_id, const glm::vec3& pos, float yaw, float health, float maxHealth, const std::string& name) { return { - {"type", "player_update"}, + {"msg", "player_update"}, {"player_id", player_id}, {"position", {pos.x, pos.y, pos.z}}, {"yaw", yaw}, @@ -997,7 +528,7 @@ nlohmann::json GameLogic::PlayerPositionToJson(const std::vector& data) glm::vec3 vel = reader.ReadVector3(); uint64_t timestamp = reader.ReadUInt64(); return { - {"type", "player_position"}, + {"msg", "player_position"}, {"player_id", player_id}, {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, {"vx", vel.x}, {"vy", vel.y}, {"vz", vel.z}, @@ -1025,7 +556,7 @@ nlohmann::json GameLogic::PlayerUpdateToJson(const std::vector& data) { }); } return { - {"type", "player_update"}, + {"msg", "player_update"}, {"players", playersArray}, {"timestamp", GetCurrentTimestamp()} }; @@ -1040,9 +571,9 @@ nlohmann::json GameLogic::EntitySpawnToJson(const std::vector& data) { float yaw = reader.ReadFloat(); uint64_t timestamp = reader.ReadUInt64(); return { - {"type", "entity_spawn"}, + {"msg", "entity_spawn"}, {"entity_id", entityId}, - {"entity_type", type}, + {"type", type}, {"name", name}, {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, {"yaw", yaw}, @@ -1058,7 +589,7 @@ nlohmann::json GameLogic::EntityUpdateToJson(const std::vector& data) { glm::vec3 vel = reader.ReadVector3(); uint64_t timestamp = reader.ReadUInt64(); return { - {"type", "entity_update"}, + {"msg", "entity_update"}, {"entity_id", entityId}, {"x", pos.x}, {"y", pos.y}, {"z", pos.z}, {"rx", rot.x}, {"ry", rot.y}, {"rz", rot.z}, @@ -1072,7 +603,7 @@ nlohmann::json GameLogic::EntityDespawnToJson(const std::vector& data) uint64_t entityId = reader.ReadUInt64(); uint64_t timestamp = reader.ReadUInt64(); return { - {"type", "entity_despawn"}, + {"msg", "entity_despawn"}, {"entity_id", entityId}, {"timestamp", timestamp} }; @@ -1086,22 +617,58 @@ nlohmann::json GameLogic::ChunkDataToJson(const std::vector& data) { nlohmann::json chunk_json = reader.ReadJson(); uint64_t timestamp = reader.ReadUInt64(); return { - {"type", "world_chunk"}, - {"chunk_x", chunk_x}, - {"chunk_z", chunk_z}, + {"msg", "get_chunk"}, + {"x", chunk_x}, + {"z", chunk_z}, {"lod", lod}, {"data", chunk_json}, {"timestamp", timestamp} }; } +void GameLogic::SetSendAuthenticationResponseCallback(std::function cb) { + sendAuthResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendChunkCallback(std::function cb) { + sendChunkCb_ = std::move(cb); +} + +void GameLogic::SetPlayerStateCallback(std::function cb) { + playerStateCb_ = std::move(cb); +} + +void GameLogic::SetBroadcastPlayerPositionCallback(std::function cb) { + broadcastPlayerPositionCb_ = std::move(cb); +} + +void GameLogic::SetSendNPCInteractionResponseCallback(std::function cb) { + sendNPCInteractionResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendFamiliarCommandResponseCallback(std::function cb) { + sendFamiliarCommandResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendEntitySpawnResponseCallback(std::function cb) { + sendEntitySpawnResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendLootPickupResponseCallback(std::function cb) { + sendLootPickupResponseCb_ = std::move(cb); +} + +void GameLogic::SetSendInventoryResponseCallback(std::function cb) { + sendInventoryResponseCb_ = std::move(cb); +} + void GameLogic::OnAuthentication(const AuthenticationData& data) { auto& pm = PlayerManager::GetInstance(); bool authenticated = false; std::string message; uint64_t player_id = 0; if (pm.PlayerExists(data.username)) { - if (data.password.empty() || pm.AuthenticatePlayer(data.username, data.password)) { + if (pm.AuthenticatePlayer(data.username, data.password)) { authenticated = true; message = "Welcome back, " + data.username; player_id = pm.GetPlayerByUsername(data.username)->GetId(); @@ -1109,17 +676,16 @@ void GameLogic::OnAuthentication(const AuthenticationData& data) { message = "Invalid password"; } } else { - if (data.password.empty()) { - auto player = pm.CreatePlayer(data.username); - if (player) { - authenticated = true; - message = "Welcome, " + data.username; - player_id = player->GetId(); - } else { - message = "Failed to create player"; + auto player = pm.CreatePlayer(data.username, data.password); + if (player) { + authenticated = true; + player_id = player->GetId(); + message = "Account created successfully. Welcome, " + data.username; + if (data.password.empty()) { + message += " (Warning: no password set)"; } } else { - message = "Player does not exist"; + message = "Failed to create player account"; } } if (authenticated) { @@ -1137,7 +703,7 @@ void GameLogic::OnAuthentication(const AuthenticationData& data) { } } -void GameLogic::OnChunkRequest(const ChunkRequestData& req) { +void GameLogic::OnChunkRequest(const ChunkData& req) { auto chunk = GetOrCreateChunk(req.chunk_x, req.chunk_z); if (!chunk) { Logger::Error("Failed to get chunk ({},{}) for session {}", req.chunk_x, req.chunk_z, req.session_id); @@ -1233,20 +799,183 @@ void GameLogic::OnPlayerState(const PlayerStateData& data) { } } -void GameLogic::SetSendAuthenticationResponseCallback(std::function cb) { - sendAuthResponseCb_ = std::move(cb); +void GameLogic::OnNPCInteraction(const NpcData& data) { + NPCEntity* npc = GetNPCEntity(data.npc_id); + if (!npc) { + NpcData error; + error.type = "error"; + error.session_id = data.session_id; + if (sendNPCInteractionResponseCb_) sendNPCInteractionResponseCb_(data.session_id, error); + return; + } + uint64_t player_id = GetPlayerIdBySession(data.session_id); + std::shared_ptr player = GetPlayer(player_id); + if (!player) { + NpcData error; + error.type = "error"; + error.session_id = data.session_id; + if (sendNPCInteractionResponseCb_) sendNPCInteractionResponseCb_(data.session_id, error); + return; + } + float distance = glm::distance(player->GetPosition(), npc->GetPosition()); + if (distance > 15.0f) { + NpcData error; + error.type = "error"; + error.session_id = data.session_id; + if (sendNPCInteractionResponseCb_) sendNPCInteractionResponseCb_(data.session_id, error); + return; + } + NpcData response; + response.session_id = data.session_id; + response.npc_id = data.npc_id; + response.player_id = player_id; + response.timestamp = GetCurrentTimestamp(); + if (data.type == "attack") { + float damage = 10.0f; + npc->TakeDamage(damage, player_id); + response.type = "combat"; + response.damage = damage; + response.health = npc->GetStats().health; + response.is_dead = npc->IsDead(); + if (response.is_dead) MobSystem::GetInstance().OnMobDeath(data.npc_id, player_id); + } else if (data.type == "talk") { + auto& questMgr = QuestManager::GetInstance(); + response.type = "dialogue"; + response.quests = questMgr.GetQuestsFromNPC(player_id, npc->GetId()); + } else { + response.type = "error"; + } + if (sendNPCInteractionResponseCb_) sendNPCInteractionResponseCb_(data.session_id, response); } -void GameLogic::SetSendChunkCallback(std::function cb) { - sendChunkCb_ = std::move(cb); +void GameLogic::OnFamiliarCommand(const FamiliarData& data) { + NPCEntity* familiar = GetNPCEntity(data.familiar_id); + if (!familiar) { + FamiliarData error; + error.success = false; + error.session_id = data.session_id; + if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); + return; + } + uint64_t player_id = GetPlayerIdBySession(data.session_id); + if (familiar->GetOwnerId() != player_id) { + FamiliarData error; + error.success = false; + error.session_id = data.session_id; + if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); + return; + } + if (data.command == "follow") { + familiar->SetBehaviorState(NPCAIState::FOLLOW); + familiar->SetTarget(player_id); + } else if (data.command == "attack") { + familiar->SetBehaviorState(NPCAIState::CHASE); + familiar->SetTarget(data.target_id); + } else if (data.command == "stay") { + familiar->SetBehaviorState(NPCAIState::IDLE); + familiar->SetTarget(0); + } else { + FamiliarData error; + error.success = false; + error.session_id = data.session_id; + if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); + return; + } + FamiliarData response; + response.success = true; + response.session_id = data.session_id; + response.familiar_id = data.familiar_id; + response.target_id = data.target_id; + response.command = data.command; + response.timestamp = GetCurrentTimestamp(); + if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, response); } -void GameLogic::SetPlayerStateCallback(std::function cb) { - playerStateCb_ = std::move(cb); +void GameLogic::OnEntitySpawnRequest(const EntitySpawnData& data) { + EntitySpawnData response = data; + response.timestamp = GetCurrentTimestamp(); + if (sendEntitySpawnResponseCb_) { + sendEntitySpawnResponseCb_(data.session_id, response); + } } -void GameLogic::SetBroadcastPlayerPositionCallback(std::function cb) { - broadcastPlayerPositionCb_ = std::move(cb); +void GameLogic::OnLootPickup(const LootPickupData& data) { + LootPickupData response = data; + response.timestamp = GetCurrentTimestamp(); + uint64_t player_id = GetPlayerIdBySession(data.session_id); + GameEntity* lootEntity = GetEntity(data.loot_id); + if (!lootEntity || lootEntity->GetType() != EntityType::ITEM) { + if (sendLootPickupResponseCb_) sendLootPickupResponseCb_(data.session_id, response); + return; + } + std::shared_ptr player = GetPlayer(player_id); + if (!player) { + if (sendLootPickupResponseCb_) sendLootPickupResponseCb_(data.session_id, response); + return; + } + float distance = glm::distance(player->GetPosition(), lootEntity->GetPosition()); + if (distance > 5.0f) { + if (sendLootPickupResponseCb_) sendLootPickupResponseCb_(data.session_id, response); + return; + } + auto& inv = InventorySystem::GetInstance(); + if (inv.AddItem(player_id, LootItem(data.loot_id, lootEntity->GetName()), data.quantity)) { + EntityManager::GetInstance().DestroyEntity(data.loot_id); + FirePythonEvent("loot_pickup", { + {"player_id", player_id}, + {"itemId", data.loot_id}, + {"quantity", data.quantity} + }); + } + if (sendLootPickupResponseCb_) sendLootPickupResponseCb_(data.session_id, response); +} + +void GameLogic::OnInventory(const InventoryData& data) { + InventoryData response = data; + response.timestamp = GetCurrentTimestamp(); + uint64_t player_id = GetPlayerIdBySession(data.session_id); + auto& inv = InventorySystem::GetInstance(); + switch (data.move_type) { + case InventoryMoveType::REMOVE: { + if (data.inv_slot_id >= 0) { + auto item = inv.GetItem(player_id, data.inv_slot_id); + if (item && inv.RemoveItem(player_id, item->GetId(), data.quantity)) { + Logger::Trace("Player {} Inventory RemoveItem {}({}) count {}", + player_id, data.inv_slot_id, item->GetId(), data.quantity); + } + } + break; + } + case InventoryMoveType::USE: { + if (data.inv_slot_id >= 0) { + auto item = inv.GetItem(player_id, data.inv_slot_id); + if (item) { + if (item->IsEquippable()) { + inv.EquipItem(player_id, data.inv_slot_id, data.quantity); + } else if (item->IsConsumable()) { + inv.UseItem(player_id, item->GetId(), data.quantity); + } + } + } + break; + } + case InventoryMoveType::TRADE: { + if (data.inv_slot_id >= 0 && data.target_id != 0) { + auto item = inv.GetItem(player_id, data.inv_slot_id); + if (item && inv.CanTradeItem(player_id, item->GetId())) { + inv.TransferItem(player_id, data.target_id, item->GetId(), data.quantity); + } + } + break; + } + default: + break; + } + if (sendInventoryResponseCb_) { + sendInventoryResponseCb_(data.session_id, response); + } else { + SendError(data.session_id, "No inventory callback set", 500); + } } void GameLogic::SetDatabaseService(DatabaseService* dbService) { @@ -1417,7 +1146,7 @@ void GameLogic::HandleChat(uint64_t session_id, const nlohmann::json& data) { auto sessions = connMgr.GetAllSessions(); for (auto& session : sessions) { if (session && session->IsConnected()) - session->Send(chatJson); + session->SendJson(chatJson); } } else { PlayerManager::GetInstance().BroadcastToNearbyPlayers(player_id, chatJson); diff --git a/src/game/InventorySystem.cpp b/src/game/InventorySystem.cpp index 2e65d6c..daa3169 100644 --- a/src/game/InventorySystem.cpp +++ b/src/game/InventorySystem.cpp @@ -6,11 +6,9 @@ nlohmann::json InventorySlot::Serialize() const { {"equipped", equipped}, {"position", position} }; - if (item) { result["item"] = item->Serialize(); } - return result; } @@ -18,7 +16,6 @@ void InventorySlot::Deserialize(const nlohmann::json& data) { quantity = data["quantity"]; equipped = data["equipped"]; position = data["position"]; - if (data.contains("item") && !data["item"].is_null()) { item = std::make_shared(); item->Deserialize(data["item"]); @@ -32,23 +29,16 @@ InventorySystem& InventorySystem::GetInstance() { bool InventorySystem::AddItem(uint64_t playerId, const LootItem& item, int quantity) { std::lock_guard lock(mutex_); - if (quantity <= 0) return false; - auto& inventory = playerInventories_[playerId]; - - // Check if we can stack with existing items if (item.GetMaxStackSize() > 1) { for (auto& slot : inventory.inventorySlots) { - if (slot.item && slot.item->CanStackWith(item) && + if (slot.item && slot.item->CanStackWith(item) && slot.quantity < slot.item->GetMaxStackSize()) { - int canAdd = slot.item->GetMaxStackSize() - slot.quantity; int toAdd = std::min(quantity, canAdd); - slot.quantity += toAdd; quantity -= toAdd; - if (quantity == 0) { SaveInventory(playerId); return true; @@ -56,86 +46,41 @@ bool InventorySystem::AddItem(uint64_t playerId, const LootItem& item, int quant } } } - - // Need new slots while (quantity > 0) { if (inventory.inventorySlots.size() >= (uint64_t)inventory.maxInventorySize) { Logger::Error("Inventory full for player {}", playerId); SaveInventory(playerId); return false; } - int stackSize = std::min(quantity, item.GetMaxStackSize()); InventorySlot newSlot; newSlot.item = std::make_shared(item); newSlot.item->SetStackSize(stackSize); newSlot.quantity = stackSize; newSlot.position = inventory.inventorySlots.size(); - inventory.inventorySlots.push_back(newSlot); quantity -= stackSize; } - - SaveInventory(playerId); - return true; -} - -bool InventorySystem::MoveItem(uint64_t playerId, int fromSlot, int toSlot) { - std::lock_guard lock(mutex_); - - auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) - return false; - - auto& inventory = it->second; - // Validate slot indices - if (fromSlot < 0 || fromSlot >= static_cast(inventory.inventorySlots.size()) || - toSlot < 0 || toSlot >= static_cast(inventory.inventorySlots.size())) - return false; - - if (fromSlot == toSlot) - return true; // Nothing to do - - auto& slotFrom = inventory.inventorySlots[fromSlot]; - auto& slotTo = inventory.inventorySlots[toSlot]; - - // Swap the contents - std::swap(slotFrom.item, slotTo.item); - std::swap(slotFrom.quantity, slotTo.quantity); - std::swap(slotFrom.equipped, slotTo.equipped); - - // Update position fields to match their new indices - slotFrom.position = fromSlot; - slotTo.position = toSlot; - SaveInventory(playerId); return true; } bool InventorySystem::RemoveItem(uint64_t playerId, uint64_t itemId, int quantity) { std::lock_guard lock(mutex_); - if (quantity <= 0) return false; - auto it = playerInventories_.find(playerId); if (it == playerInventories_.end()) return false; - auto& inventory = it->second; int remaining = quantity; - - // Remove from inventory for (auto& slot : inventory.inventorySlots) { if (slot.item && slot.item->GetId() == itemId) { int toRemove = std::min(remaining, slot.quantity); slot.quantity -= toRemove; remaining -= toRemove; - if (slot.quantity == 0) { slot.item.reset(); } - if (remaining == 0) { - // Clean up empty slots inventory.inventorySlots.erase( std::remove_if(inventory.inventorySlots.begin(), inventory.inventorySlots.end(), @@ -144,74 +89,308 @@ bool InventorySystem::RemoveItem(uint64_t playerId, uint64_t itemId, int quantit }), inventory.inventorySlots.end() ); - - // Update positions for (size_t i = 0; i < inventory.inventorySlots.size(); ++i) { inventory.inventorySlots[i].position = i; } - SaveInventory(playerId); return true; } } } - SaveInventory(playerId); return false; } -bool InventorySystem::EquipItem(uint64_t playerId, int inventorySlot) { +bool InventorySystem::UseItem(uint64_t playerId, uint64_t itemId, int quantity) { std::lock_guard lock(mutex_); - auto it = playerInventories_.find(playerId); if (it == playerInventories_.end()) return false; - auto& inventory = it->second; - - if (!ValidateSlot(playerId, inventorySlot) || - (uint64_t)inventorySlot >= inventory.inventorySlots.size()) { - return false; + for (auto& slot : inventory.inventorySlots) { + if (slot.item && slot.item->GetId() == itemId && slot.quantity >= quantity) { + if (!slot.item->IsConsumable()) return false; + slot.quantity -= quantity; + if (slot.quantity == 0) { + slot.item.reset(); + } + SaveInventory(playerId); + return true; + } } + return false; +} - auto& slot = inventory.inventorySlots[inventorySlot]; - if (!slot.item || !slot.item->IsEquippable()) { +bool InventorySystem::MoveItem(uint64_t playerId, int fromSlot, int toSlot) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; - } - - if (!MeetsRequirements(playerId, *slot.item)) { + auto& inventory = it->second; + if (fromSlot < 0 || fromSlot >= static_cast(inventory.inventorySlots.size()) || + toSlot < 0 || toSlot >= static_cast(inventory.inventorySlots.size())) return false; - } + if (fromSlot == toSlot) + return true; + auto& slotFrom = inventory.inventorySlots[fromSlot]; + auto& slotTo = inventory.inventorySlots[toSlot]; + std::swap(slotFrom.item, slotTo.item); + std::swap(slotFrom.quantity, slotTo.quantity); + std::swap(slotFrom.equipped, slotTo.equipped); + slotFrom.position = fromSlot; + slotTo.position = toSlot; + SaveInventory(playerId); + return true; +} - int equipSlot = GetEquipmentSlotForItem(*slot.item); - if (equipSlot == -1 || (uint64_t)equipSlot >= inventory.equipmentSlots.size()) { +bool InventorySystem::SwapItems(uint64_t playerId, int slot1, int slot2) { + return MoveItem(playerId, slot1, slot2); +} + +bool InventorySystem::SplitStack(uint64_t playerId, int slot, int splitQuantity) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + auto& inventory = it->second; + if (slot < 0 || slot >= static_cast(inventory.inventorySlots.size())) return false; + auto& sourceSlot = inventory.inventorySlots[slot]; + if (!sourceSlot.item || sourceSlot.quantity <= splitQuantity) return false; + if (inventory.inventorySlots.size() >= (uint64_t)inventory.maxInventorySize) return false; + InventorySlot newSlot; + newSlot.item = std::make_shared(*sourceSlot.item); + newSlot.item->SetStackSize(splitQuantity); + newSlot.quantity = splitQuantity; + newSlot.position = inventory.inventorySlots.size(); + sourceSlot.quantity -= splitQuantity; + sourceSlot.item->SetStackSize(sourceSlot.quantity); + inventory.inventorySlots.push_back(newSlot); + SaveInventory(playerId); + return true; +} + +bool InventorySystem::MergeStacks(uint64_t playerId, int sourceSlot, int targetSlot) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + auto& inventory = it->second; + if (sourceSlot < 0 || sourceSlot >= static_cast(inventory.inventorySlots.size()) || + targetSlot < 0 || targetSlot >= static_cast(inventory.inventorySlots.size())) return false; + if (sourceSlot == targetSlot) return true; + auto& source = inventory.inventorySlots[sourceSlot]; + auto& target = inventory.inventorySlots[targetSlot]; + if (!source.item || !target.item) return false; + if (!source.item->CanStackWith(*target.item)) return false; + int maxStack = source.item->GetMaxStackSize(); + int space = maxStack - target.quantity; + if (space <= 0) return false; + int move = std::min(source.quantity, space); + target.quantity += move; + source.quantity -= move; + if (source.quantity == 0) { + source.item.reset(); + inventory.inventorySlots.erase(inventory.inventorySlots.begin() + sourceSlot); + for (size_t i = 0; i < inventory.inventorySlots.size(); ++i) { + inventory.inventorySlots[i].position = i; + } } + SaveInventory(playerId); + return true; +} - // Check if equipment slot is occupied - if (inventory.equipmentSlots[equipSlot].quantity > 0) { - // Swap with inventory - auto temp = inventory.equipmentSlots[equipSlot]; - inventory.equipmentSlots[equipSlot] = slot; - slot = temp; +bool InventorySystem::EquipItem(uint64_t playerId, int inventorySlot, int quantity) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + auto& inventory = it->second; + if (inventorySlot < 0 || inventorySlot >= static_cast(inventory.inventorySlots.size())) + return false; + auto& slot = inventory.inventorySlots[inventorySlot]; + if (!slot.item || !slot.item->IsEquippable() || slot.quantity < quantity) return false; + if (!MeetsRequirements(playerId, *slot.item)) return false; + int equipSlot = GetEquipmentSlotForItem(*slot.item); + if (equipSlot == -1 || equipSlot >= inventory.maxEquipmentSlots) return false; + if ((uint64_t)equipSlot >= inventory.equipmentSlots.size()) + inventory.equipmentSlots.resize(inventory.maxEquipmentSlots); + auto& equipped = inventory.equipmentSlots[equipSlot]; + if (equipped.quantity > 0) { + std::swap(equipped, slot); slot.position = inventorySlot; + equipped.equipped = true; } else { - // Move to equipment slot - inventory.equipmentSlots[equipSlot] = slot; + equipped = slot; slot.item.reset(); slot.quantity = 0; - - // Clean up empty slot inventory.inventorySlots.erase(inventory.inventorySlots.begin() + inventorySlot); - - // Update positions - for (size_t i = 0; i < inventory.inventorySlots.size(); ++i) { + for (size_t i = 0; i < inventory.inventorySlots.size(); ++i) inventory.inventorySlots[i].position = i; + } + equipped.equipped = true; + SaveInventory(playerId); + return true; +} + +bool InventorySystem::UnequipItem(uint64_t playerId, int equipmentSlot, int quantity) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + auto& inventory = it->second; + if (equipmentSlot < 0 || equipmentSlot >= static_cast(inventory.equipmentSlots.size())) + return false; + auto& slot = inventory.equipmentSlots[equipmentSlot]; + if (!slot.item || slot.quantity < quantity) return false; + if (inventory.inventorySlots.size() >= (uint64_t)inventory.maxInventorySize) return false; + InventorySlot newSlot; + newSlot.item = std::make_shared(*slot.item); + newSlot.quantity = quantity; + newSlot.position = inventory.inventorySlots.size(); + slot.quantity -= quantity; + if (slot.quantity == 0) { + slot.item.reset(); + slot.equipped = false; + } + inventory.inventorySlots.push_back(newSlot); + SaveInventory(playerId); + return true; +} + +bool InventorySystem::AutoEquip(uint64_t playerId, uint64_t itemId, int quantity) { + int slot = FindItemSlot(playerId, itemId); + if (slot == -1) return false; + return EquipItem(playerId, slot, quantity); +} + +std::shared_ptr InventorySystem::GetItem(uint64_t playerId, int slot) { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return nullptr; + if (slot < 0 || slot >= static_cast(it->second.inventorySlots.size())) return nullptr; + return it->second.inventorySlots[slot].item; +} + +int InventorySystem::GetItemCount(uint64_t playerId, uint64_t itemId) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return 0; + int count = 0; + for (const auto& slot : it->second.inventorySlots) { + if (slot.item && slot.item->GetId() == itemId) count += slot.quantity; + } + return count; +} + +bool InventorySystem::HasItem(uint64_t playerId, uint64_t itemId, int quantity) const { + return GetItemCount(playerId, itemId) >= quantity; +} + +std::vector InventorySystem::GetInventory(uint64_t playerId) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return {}; + return it->second.inventorySlots; +} + +std::vector InventorySystem::GetEquipment(uint64_t playerId) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return {}; + return it->second.equipmentSlots; +} + +int InventorySystem::GetFreeSlots(uint64_t playerId) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return 0; + return it->second.maxInventorySize - it->second.inventorySlots.size(); +} + +int InventorySystem::GetTotalSlots(uint64_t playerId) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return 0; + return it->second.maxInventorySize; +} + +bool InventorySystem::HasSpaceFor(uint64_t playerId, const LootItem& item, int quantity) const { + std::lock_guard lock(mutex_); + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + const auto& inventory = it->second; + int remaining = quantity; + if (item.GetMaxStackSize() > 1) { + for (const auto& slot : inventory.inventorySlots) { + if (slot.item && slot.item->CanStackWith(item)) { + int space = slot.item->GetMaxStackSize() - slot.quantity; + if (space > 0) { + remaining -= space; + if (remaining <= 0) return true; + } + } } } + int neededSlots = (remaining + item.GetMaxStackSize() - 1) / item.GetMaxStackSize(); + return (inventory.inventorySlots.size() + neededSlots) <= (uint64_t)inventory.maxInventorySize; +} - inventory.equipmentSlots[equipSlot].equipped = true; +bool InventorySystem::CanTradeItem(uint64_t playerId, uint64_t itemId) const { + auto it = playerInventories_.find(playerId); + if (it == playerInventories_.end()) return false; + for (const auto& slot : it->second.inventorySlots) { + if (slot.item && slot.item->GetId() == itemId) { + return true; // Trading allowed by default, could add tradable flag + } + } + return false; +} - SaveInventory(playerId); +bool InventorySystem::TransferItem(uint64_t fromPlayerId, uint64_t toPlayerId, uint64_t itemId, int quantity) { + std::lock_guard lock(mutex_); + auto fromIt = playerInventories_.find(fromPlayerId); + auto toIt = playerInventories_.find(toPlayerId); + if (fromIt == playerInventories_.end() || toIt == playerInventories_.end()) return false; + if (!HasItem(fromPlayerId, itemId, quantity)) return false; + auto& fromInv = fromIt->second; + auto& toInv = toIt->second; + LootItem* sourceItem = nullptr; + int sourceSlot = -1; + for (size_t i = 0; i < fromInv.inventorySlots.size(); ++i) { + if (fromInv.inventorySlots[i].item && fromInv.inventorySlots[i].item->GetId() == itemId) { + sourceItem = fromInv.inventorySlots[i].item.get(); + sourceSlot = i; + break; + } + } + if (!sourceItem) return false; + int remaining = quantity; + if (sourceItem->GetMaxStackSize() > 1) { + for (auto& slot : toInv.inventorySlots) { + if (slot.item && slot.item->CanStackWith(*sourceItem)) { + int space = slot.item->GetMaxStackSize() - slot.quantity; + int add = std::min(remaining, space); + slot.quantity += add; + remaining -= add; + if (remaining == 0) break; + } + } + } + while (remaining > 0) { + if (toInv.inventorySlots.size() >= (uint64_t)toInv.maxInventorySize) return false; + int stackSize = std::min(remaining, sourceItem->GetMaxStackSize()); + InventorySlot newSlot; + newSlot.item = std::make_shared(*sourceItem); + newSlot.item->SetStackSize(stackSize); + newSlot.quantity = stackSize; + newSlot.position = toInv.inventorySlots.size(); + toInv.inventorySlots.push_back(newSlot); + remaining -= stackSize; + } + fromInv.inventorySlots[sourceSlot].quantity -= quantity; + if (fromInv.inventorySlots[sourceSlot].quantity == 0) { + fromInv.inventorySlots.erase(fromInv.inventorySlots.begin() + sourceSlot); + for (size_t i = 0; i < fromInv.inventorySlots.size(); ++i) + fromInv.inventorySlots[i].position = i; + } + SaveInventory(fromPlayerId); + SaveInventory(toPlayerId); return true; } @@ -227,43 +406,30 @@ bool InventorySystem::SaveInventory(uint64_t playerId) { nlohmann::json InventorySystem::SerializeInventory(uint64_t playerId) const { std::lock_guard lock(mutex_); - auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) { - return nlohmann::json::object(); - } - + if (it == playerInventories_.end()) return {}; const auto& inventory = it->second; nlohmann::json result; - - // Serialize inventory slots nlohmann::json invArray = nlohmann::json::array(); for (const auto& slot : inventory.inventorySlots) { invArray.push_back(slot.Serialize()); } result["inventory"] = invArray; - - // Serialize equipment slots nlohmann::json equipArray = nlohmann::json::array(); for (const auto& slot : inventory.equipmentSlots) { equipArray.push_back(slot.Serialize()); } result["equipment"] = equipArray; - result["gold"] = inventory.gold; result["maxInventorySize"] = inventory.maxInventorySize; result["maxEquipmentSlots"] = inventory.maxEquipmentSlots; - return result; } bool InventorySystem::DeserializeInventory(uint64_t playerId, const nlohmann::json& data) { std::lock_guard lock(mutex_); - PlayerInventory inventory; - try { - // Deserialize inventory slots if (data.contains("inventory")) { for (const auto& slotData : data["inventory"]) { InventorySlot slot; @@ -271,8 +437,6 @@ bool InventorySystem::DeserializeInventory(uint64_t playerId, const nlohmann::js inventory.inventorySlots.push_back(slot); } } - - // Deserialize equipment slots if (data.contains("equipment")) { for (const auto& slotData : data["equipment"]) { InventorySlot slot; @@ -280,14 +444,10 @@ bool InventorySystem::DeserializeInventory(uint64_t playerId, const nlohmann::js inventory.equipmentSlots.push_back(slot); } } - inventory.gold = data.value("gold", 0); inventory.maxInventorySize = data.value("maxInventorySize", 40); inventory.maxEquipmentSlots = data.value("maxEquipmentSlots", 12); - - // Ensure equipment slots vector size inventory.equipmentSlots.resize(inventory.maxEquipmentSlots); - playerInventories_[playerId] = inventory; return true; } catch (const std::exception& e) { @@ -298,120 +458,89 @@ bool InventorySystem::DeserializeInventory(uint64_t playerId, const nlohmann::js int64_t InventorySystem::GetGold(uint64_t playerId) const { std::lock_guard lock(mutex_); - auto it = playerInventories_.find(playerId); if (it == playerInventories_.end()) return 0; - return it->second.gold; } bool InventorySystem::AddGold(uint64_t playerId, int64_t amount) { std::lock_guard lock(mutex_); - auto it = playerInventories_.find(playerId); if (it == playerInventories_.end()) { - // Create new inventory playerInventories_[playerId] = PlayerInventory(); it = playerInventories_.find(playerId); } - if (amount < 0) return false; - it->second.gold += amount; - - // Cap at max value - if (it->second.gold < 0) { // Handle overflow - it->second.gold = INT64_MAX; - } - + if (it->second.gold < 0) it->second.gold = INT64_MAX; SaveInventory(playerId); return true; } bool InventorySystem::RemoveGold(uint64_t playerId, int64_t amount) { std::lock_guard lock(mutex_); - auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) { - return false; // No inventory → cannot remove gold - } - - if (amount < 0) return false; // Cannot remove negative amount - if (it->second.gold < amount) return false; // Insufficient funds - + if (it == playerInventories_.end()) return false; + if (amount < 0) return false; + if (it->second.gold < amount) return false; it->second.gold -= amount; SaveInventory(playerId); return true; } bool InventorySystem::TransferGold(uint64_t fromPlayerId, uint64_t toPlayerId, int64_t amount) { - if (fromPlayerId == toPlayerId || amount <= 0) - return false; - - std::lock_guard lock(mutex_); // Lock once for atomicity - + if (fromPlayerId == toPlayerId || amount <= 0) return false; + std::lock_guard lock(mutex_); auto fromIt = playerInventories_.find(fromPlayerId); - auto toIt = playerInventories_.find(toPlayerId); - - if (fromIt == playerInventories_.end() || toIt == playerInventories_.end()) - return false; // Both players must have inventories - - if (fromIt->second.gold < amount) - return false; // Insufficient funds - - // Perform transfer + auto toIt = playerInventories_.find(toPlayerId); + if (fromIt == playerInventories_.end() || toIt == playerInventories_.end()) return false; + if (fromIt->second.gold < amount) return false; fromIt->second.gold -= amount; toIt->second.gold += amount; - - // Cap at max (though gold is int64_t, addition could overflow, but unlikely) if (toIt->second.gold < 0) toIt->second.gold = INT64_MAX; - - // Save both inventories (optional – SaveInventory already writes to DB) SaveInventory(fromPlayerId); SaveInventory(toPlayerId); return true; } -std::shared_ptr InventorySystem::GetItem(uint64_t playerId, int slot) { - std::lock_guard lock(mutex_); - +bool InventorySystem::ValidateSlot(uint64_t playerId, int slot) const { auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) - return nullptr; - - if (slot < 0 || slot >= static_cast(it->second.inventorySlots.size())) - return nullptr; - - const auto& slotData = it->second.inventorySlots[slot]; - return slotData.item; // may be nullptr if slot empty + if (it == playerInventories_.end()) return false; + return slot >= 0 && slot < it->second.maxInventorySize; } -std::vector InventorySystem::GetInventory(uint64_t playerId) const { - std::lock_guard lock(mutex_); +int InventorySystem::FindItemSlot(uint64_t playerId, uint64_t itemId) const { auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) { - return {}; + if (it == playerInventories_.end()) return -1; + for (size_t i = 0; i < it->second.inventorySlots.size(); ++i) { + if (it->second.inventorySlots[i].item && it->second.inventorySlots[i].item->GetId() == itemId) + return i; } - return it->second.inventorySlots; + return -1; } -// Helper method implementations -bool InventorySystem::ValidateSlot(uint64_t playerId, int slot) const { +int InventorySystem::FindFreeSlot(uint64_t playerId) const { auto it = playerInventories_.find(playerId); - if (it == playerInventories_.end()) return false; + if (it == playerInventories_.end()) return -1; + for (size_t i = 0; i < it->second.inventorySlots.size(); ++i) { + if (!it->second.inventorySlots[i].item) return i; + } + return it->second.inventorySlots.size(); +} - return slot >= 0 && slot < it->second.maxInventorySize; +bool InventorySystem::CanStackWithSlot(const InventorySlot& slot, const LootItem& item) const { + return slot.item && slot.item->CanStackWith(item); +} + +bool InventorySystem::IsEquipmentSlot(int slot) const { + return slot >= PlayerInventory::HEAD && slot <= PlayerInventory::TRINKET2; } int InventorySystem::GetEquipmentSlotForItem(const LootItem& item) const { switch (item.GetType()) { - case ItemType::WEAPON: return PlayerInventory::MAIN_HAND; - case ItemType::ARMOR: - // This would need more logic based on armor subtype - return PlayerInventory::CHEST; - case ItemType::JEWELRY: - // This would need more logic based on jewelry type - return PlayerInventory::RING1; + case LootType::WEAPON: return PlayerInventory::MAIN_HAND; + case LootType::ARMOR: return PlayerInventory::CHEST; + case LootType::JEWELRY: return PlayerInventory::RING1; default: return -1; } } @@ -419,17 +548,11 @@ int InventorySystem::GetEquipmentSlotForItem(const LootItem& item) const { bool InventorySystem::MeetsRequirements(uint64_t playerId, const LootItem& item) const { (void)playerId; (void)item; - // TODO: Implement player level and other requirement checks - // For now, just check level requirement - // This would need access to player stats/level return true; } -// ========================== Database methods ========================== - bool InventorySystem::LoadFromDatabase(uint64_t playerId) { std::lock_guard lock(mutex_); - try { auto& dbManager = DbManager::GetInstance(); auto* backend = dbManager.GetBackend(); @@ -437,60 +560,43 @@ bool InventorySystem::LoadFromDatabase(uint64_t playerId) { Logger::Error("No database backend available for player {}", playerId); return false; } - int shardId = dbManager.GetShardId(playerId); - std::string query = - "SELECT inventory_data FROM player_inventory WHERE player_id = " + - std::to_string(playerId); - + std::string query = "SELECT inventory_data FROM player_inventory WHERE player_id = " + std::to_string(playerId); nlohmann::json result = backend->QueryShard(shardId, query); - if (!result.empty() && result[0].contains("inventory_data")) { std::string inventoryJson = result[0]["inventory_data"]; nlohmann::json data = nlohmann::json::parse(inventoryJson); return DeserializeInventory(playerId, data); } - - // No inventory exists, create default playerInventories_[playerId] = PlayerInventory(); return true; } catch (const std::exception& e) { - Logger::Error("Failed to load inventory from database for player {}: {}", - playerId, e.what()); + Logger::Error("Failed to load inventory for player {}: {}", playerId, e.what()); return false; } } bool InventorySystem::SaveToDatabase(uint64_t playerId) { std::lock_guard lock(mutex_); - try { auto it = playerInventories_.find(playerId); if (it == playerInventories_.end()) return false; - auto& dbManager = DbManager::GetInstance(); auto* backend = dbManager.GetBackend(); if (!backend) { Logger::Error("No database backend available for player {}", playerId); return false; } - nlohmann::json data = SerializeInventory(playerId); std::string inventoryJson = data.dump(); std::string escaped = backend->EscapeString(inventoryJson); - int shardId = dbManager.GetShardId(playerId); - std::string query = - "INSERT INTO player_inventory (player_id, inventory_data) " - "VALUES (" + std::to_string(playerId) + ", '" + escaped + "') " - "ON CONFLICT (player_id) DO UPDATE SET " - "inventory_data = EXCLUDED.inventory_data, " - "last_updated = NOW()"; - + std::string query = "INSERT INTO player_inventory (player_id, inventory_data) VALUES (" + + std::to_string(playerId) + ", '" + escaped + "') ON CONFLICT (player_id) DO UPDATE SET " + + "inventory_data = EXCLUDED.inventory_data, last_updated = NOW()"; return backend->ExecuteShard(shardId, query); } catch (const std::exception& e) { - Logger::Error("Failed to save inventory to database for player {}: {}", - playerId, e.what()); + Logger::Error("Failed to save inventory for player {}: {}", playerId, e.what()); return false; } -} +} \ No newline at end of file diff --git a/src/game/LogicCore.cpp b/src/game/LogicCore.cpp index 1f39470..22a1698 100644 --- a/src/game/LogicCore.cpp +++ b/src/game/LogicCore.cpp @@ -77,11 +77,11 @@ float LogicCore::CalculateDistance(const glm::vec3& a, const glm::vec3& b) { } void LogicCore::HandleMessage(uint64_t sessionId, const nlohmann::json& message) { - if (!message.contains("type") || !message["type"].is_string()) { + if (!message.contains("msg") || !message["msg"].is_string()) { SendError(sessionId, "Invalid message format"); return; } - std::string messageType = message["type"]; + std::string messageType = message["msg"]; Logger::Debug("Handling message type '{}' from session {}", messageType, sessionId); try { if (!CheckRateLimit(sessionId)) { @@ -109,7 +109,7 @@ void LogicCore::HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, co writer.WriteUInt8(1); writer.WriteString("Rate limit exceeded"); writer.WriteUInt32(429); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_ERROR, writer.GetBuffer()); + SendToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_ERROR, writer.GetBuffer()); return; } std::lock_guard lock(handlersMutex_); @@ -126,11 +126,11 @@ void LogicCore::HandleBinaryMessage(uint64_t sessionId, uint16_t messageType, co writer.WriteUInt8(1); writer.WriteString("Unknown binary message type"); writer.WriteUInt32(400); - SendBinaryToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_ERROR, writer.GetBuffer()); + SendToSession(sessionId, BinaryProtocol::MESSAGE_TYPE_ERROR, writer.GetBuffer()); } } - } catch (const std::exception& e) { - Logger::Error("Error handling binary message: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("Error handling binary message: {}", err.what()); SendError(sessionId, "Internal server error", 500); } } @@ -187,39 +187,39 @@ uint64_t LogicCore::GetSessionIdByPlayer(uint64_t playerId) const { return it != playerToSessionMap_.end() ? it->second : 0; } -void LogicCore::SendError(uint64_t sessionId, const std::string& message, int code) { +void LogicCore::SendError(uint64_t sessionId, const std::string& description, int code) { nlohmann::json errorMsg = { - {"type", "error"}, - {"message", message}, + {"msg", "error"}, + {"desc", description}, {"code", code}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, errorMsg); + SendToSessionJson(sessionId, errorMsg); } -void LogicCore::SendSuccess(uint64_t sessionId, const std::string& message, const nlohmann::json& data) { +void LogicCore::SendSuccess(uint64_t sessionId, const std::string& description, const nlohmann::json& data) { nlohmann::json successMsg = { - {"type", "success"}, - {"message", message}, + {"msg", "success"}, + {"desc", description}, {"data", data}, {"timestamp", GetCurrentTimestamp()} }; - SendToSession(sessionId, successMsg); + SendToSessionJson(sessionId, successMsg); } -void LogicCore::SendToSession(uint64_t sessionId, const nlohmann::json& message) { +void LogicCore::SendToSessionJson(uint64_t sessionId, const nlohmann::json& message) { auto& connMgr = ConnectionManager::GetInstance(); auto session = connMgr.GetSession(sessionId); if (session) { - session->Send(message); + session->SendJson(message); } } -void LogicCore::SendBinaryToSession(uint64_t sessionId, uint16_t messageType, const std::vector& data) { +void LogicCore::SendToSession(uint64_t sessionId, uint16_t messageType, const std::vector& data) { auto& connMgr = ConnectionManager::GetInstance(); auto session = connMgr.GetSession(sessionId); if (session) { - session->SendBinary(messageType, data); + session->Send(messageType, data); } } diff --git a/src/game/LootItem.cpp b/src/game/LootItem.cpp index 484844e..466846b 100644 --- a/src/game/LootItem.cpp +++ b/src/game/LootItem.cpp @@ -1,6 +1,6 @@ #include "game/LootItem.hpp" -nlohmann::json ItemStat::Serialize() const { +nlohmann::json LootStat::Serialize() const { return { {"statName", statName}, {"baseValue", baseValue}, @@ -9,15 +9,15 @@ nlohmann::json ItemStat::Serialize() const { }; } -void ItemStat::Deserialize(const nlohmann::json& data) { +void LootStat::Deserialize(const nlohmann::json& data) { statName = data["statName"]; baseValue = data["baseValue"]; currentValue = data["currentValue"]; maxValue = data["maxValue"]; } -LootItem::LootItem() : id_(0), name_(""), type_(ItemType::MATERIAL), rarity_(LootRarity::COMMON) {} -LootItem::LootItem(uint64_t id, const std::string& name, ItemType type, LootRarity rarity) +LootItem::LootItem() : id_(0), name_(""), type_(LootType::MATERIAL), rarity_(LootRarity::COMMON) {} +LootItem::LootItem(uint64_t id, const std::string& name, LootType type, LootRarity rarity) : id_(id), name_(name), type_(type), rarity_(rarity) { switch (rarity) { case LootRarity::COMMON: iconColor_ = glm::vec3(0.8f, 0.8f, 0.8f); break; @@ -42,7 +42,7 @@ void LootItem::SetIconColor(const glm::vec3& color) { } void LootItem::AddStat(const std::string& name, float baseValue, float maxValue) { - ItemStat stat; + LootStat stat; stat.statName = name; stat.baseValue = baseValue; stat.currentValue = baseValue; @@ -50,7 +50,7 @@ void LootItem::AddStat(const std::string& name, float baseValue, float maxValue) stats_.push_back(stat); } -ItemStat* LootItem::GetStat(const std::string& name) { +LootStat* LootItem::GetStat(const std::string& name) { for (auto& stat : stats_) { if (stat.statName == name) { return &stat; @@ -59,12 +59,12 @@ ItemStat* LootItem::GetStat(const std::string& name) { return nullptr; } -void LootItem::AddModifier(const ItemModifier& modifier) { +void LootItem::AddModifier(const LootModifier& modifier) { modifiers_.push_back(modifier); } -std::vector LootItem::GetModifiersForStat(const std::string& statName) const { - std::vector result; +std::vector LootItem::GetModifiersForStat(const std::string& statName) const { + std::vector result; for (const auto& modifier : modifiers_) { if (modifier.targetStat == statName) { result.push_back(modifier); @@ -118,7 +118,7 @@ void LootItem::Deserialize(const nlohmann::json& data) { id_ = data["id"]; name_ = data["name"]; description_ = data["description"]; - type_ = static_cast(data["type"]); + type_ = static_cast(data["type"]); rarity_ = static_cast(data["rarity"]); stackSize_ = data["stackSize"]; maxStackSize_ = data["maxStackSize"]; @@ -131,14 +131,14 @@ void LootItem::Deserialize(const nlohmann::json& data) { stats_.clear(); for (const auto& statData : data["stats"]) { - ItemStat stat; + LootStat stat; stat.Deserialize(statData); stats_.push_back(stat); } modifiers_.clear(); for (const auto& modData : data["modifiers"]) { - ItemModifier modifier; + LootModifier modifier; modifier.modifierType = modData["modifierType"]; modifier.targetStat = modData["targetStat"]; modifier.value = modData["value"]; @@ -192,11 +192,11 @@ bool LootItem::CanStackWith(const LootItem& other) const { } bool LootItem::IsEquippable() const { - return type_ == ItemType::WEAPON || - type_ == ItemType::ARMOR || - type_ == ItemType::JEWELRY; + return type_ == LootType::WEAPON || + type_ == LootType::ARMOR || + type_ == LootType::JEWELRY; } bool LootItem::IsConsumable() const { - return type_ == ItemType::CONSUMABLE; + return type_ == LootType::CONSUMABLE; } diff --git a/src/game/LootTableManager.cpp b/src/game/LootTableManager.cpp index cac2d8b..b6fe4f7 100644 --- a/src/game/LootTableManager.cpp +++ b/src/game/LootTableManager.cpp @@ -341,7 +341,7 @@ std::shared_ptr LootTableManager::CreateItemFromEntry( item->SetRarity(rarity); item->SetLevelRequirement(itemLevel); ApplyRarityStats(item, rarity); - if (item->GetType() == ItemType::WEAPON || item->GetType() == ItemType::ARMOR) { + if (item->GetType() == LootType::WEAPON || item->GetType() == LootType::AMMO || item->GetType() == LootType::ARMOR) { GenerateRandomStats(item, itemLevel); } if (static_cast(rarity) >= static_cast(LootRarity::RARE)) { @@ -473,7 +473,7 @@ void LootTableManager::GenerateRandomStats(std::shared_ptr item, int i } void LootTableManager::ApplyRandomEnchantment(std::shared_ptr item, LootRarity rarity) const { - std::vector possibleEnchantments = { + std::vector possibleEnchantments = { {"multiply", "attack_damage", 1.25f, 0, "enchantment"}, {"multiply", "attack_speed", 1.15f, 0, "enchantment"}, {"add", "critical_chance", 0.05f, 0, "enchantment"}, diff --git a/src/game/PlayerManager.cpp b/src/game/PlayerManager.cpp index 7f36275..834e4c3 100644 --- a/src/game/PlayerManager.cpp +++ b/src/game/PlayerManager.cpp @@ -207,7 +207,7 @@ void PlayerManager::BroadcastToNearbyPlayers(int64_t playerId, const nlohmann::j for (int64_t id : nearby) { if (auto sessionId = GetSessionIdByPlayerId(id)) { if (auto session = connMgr.GetSession(sessionId)) - session->Send(message); + session->SendJson(message); } } } @@ -524,7 +524,7 @@ void PlayerManager::SendToPlayer(int64_t playerId, const nlohmann::json& message if (auto sessionId = GetSessionIdByPlayerId(playerId)) { auto& connMgr = ConnectionManager::GetInstance(); if (auto session = connMgr.GetSession(sessionId)) - session->Send(message); + session->SendJson(message); } else { Logger::Warn("Player {} is not online", playerId); } @@ -535,7 +535,7 @@ void PlayerManager::SendToPlayers(const std::vector& playerIds, const n for (int64_t id : playerIds) { if (auto sessionId = GetSessionIdByPlayerId(id)) { if (auto session = connMgr.GetSession(sessionId)) - session->Send(message); + session->SendJson(message); } } } @@ -556,8 +556,8 @@ void PlayerManager::BanPlayer(int64_t playerId, const std::string& reason, int64 if (auto sessionId = GetSessionIdByPlayerId(playerId)) { auto& connMgr = ConnectionManager::GetInstance(); if (auto session = connMgr.GetSession(sessionId)) { - nlohmann::json msg = {{"type", "banned"}, {"reason", reason}, {"duration", durationSeconds}}; - session->Send(msg); + nlohmann::json msg = {{"msg", "banned"}, {"reason", reason}, {"duration", durationSeconds}}; + session->SendJson(msg); session->Stop(); } } diff --git a/src/main.cpp b/src/main.cpp index ab3e565..40c22ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -129,7 +129,7 @@ void worker(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* pro workerId, groupConfig.host, groupConfig.port, groupConfig.protocol); std::atomic worldMaintenanceRunning{true}; - std::thread worldMaintenanceThread([&gameLogic, &worldMaintenanceRunning, workerId, processPool]() { + std::thread worldMaintenanceThread([&server, &gameLogic, &worldMaintenanceRunning, workerId, processPool]() { Logger::Info("Worker {} starting world maintenance thread", workerId); auto lastCleanupTime = std::chrono::steady_clock::now(); @@ -153,7 +153,7 @@ void worker(int workerId, const WorkerGroupConfig& groupConfig, ProcessPool* pro while (!message.empty()) { try { auto jsonMsg = nlohmann::json::parse(message); - gameLogic.HandleIPCMessage(jsonMsg); + server.HandleIPCMessage(jsonMsg, gameLogic); } catch (const std::exception& e) { Logger::Error("Worker {} failed to parse IPC message: {}", workerId, e.what()); } @@ -298,11 +298,11 @@ int main(int argc, char* argv[]) { } nlohmann::json testMsg; - testMsg["type"] = "welcome"; + testMsg["msg"] = "welcome"; testMsg["from"] = "master"; testMsg["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); testMsg["worker_id"] = i; - testMsg["message"] = "Welcome to the game server!"; + testMsg["desc"] = "Welcome to the game server!"; std::string serialized = testMsg.dump(); @@ -340,7 +340,7 @@ int main(int argc, char* argv[]) { if (!processPool.IsWorkerAlive(i)) continue; nlohmann::json heartbeat; - heartbeat["type"] = "heartbeat"; + heartbeat["msg"] = "heartbeat"; heartbeat["count"] = heartbeatCount; heartbeat["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); @@ -353,12 +353,12 @@ int main(int argc, char* argv[]) { statusUpdateCount++; if (statusUpdateCount % 15 == 0) { // every 30 seconds nlohmann::json serverStatus; - serverStatus["type"] = "server_status"; + serverStatus["msg"] = "server_status"; serverStatus["online_workers"] = totalWorkers; serverStatus["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); nlohmann::json broadcastMsg; - broadcastMsg["type"] = "broadcast"; + broadcastMsg["msg"] = "broadcast"; broadcastMsg["data"] = serverStatus; std::string broadcastSerialized = broadcastMsg.dump(); diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index e025948..a583582 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -215,7 +215,7 @@ void BinarySession::DoBinaryRead() { if (header.version > BinaryProtocol::CURRENT_PROTOCOL_VERSION) { Logger::Warn("Session {}: incompatible protocol version {}", self->sessionId_, header.version); - self->SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Incompatible protocol version", 400); self->DoBinaryRead(); return; @@ -265,7 +265,7 @@ void BinarySession::DoBinaryRead() { uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); if (calculated != header.checksum) { Logger::Error("Session {}: checksum mismatch", self->sessionId_); - self->SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Checksum error", 400); self->DoBinaryRead(); return; @@ -279,7 +279,7 @@ void BinarySession::DoBinaryRead() { } catch (const std::exception& e) { Logger::Error("Session {}: decompression failed: {}", self->sessionId_, e.what()); - self->SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Decompression failed", 400); self->DoBinaryRead(); return; @@ -318,7 +318,7 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes // Check rate limiting if (!CheckRateLimit()) { - SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Rate limit exceeded", 429); + SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Rate limit exceeded", 429); return; } @@ -326,7 +326,7 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes switch (message.header.message_type) { case BinaryProtocol::MESSAGE_TYPE_HEARTBEAT: // This is a ping, send pong - SendBinary(BinaryProtocol::MESSAGE_TYPE_HEARTBEAT, message.data); + Send(BinaryProtocol::MESSAGE_TYPE_HEARTBEAT, message.data); return; case BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION: @@ -335,7 +335,7 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { BinaryProtocol::BinaryReader reader(message.data.data(), message.data.size()); - ChunkRequestData req; + ChunkData req; req.chunk_x = reader.ReadInt32(); req.chunk_z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); @@ -363,7 +363,7 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes } catch (const std::exception& e) { Logger::Error("Session {} error in binary handler {}: {}", sessionId_, message.header.message_type, e.what()); - SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Handler error", 500); } } else if (default_binary_handler_) { @@ -376,17 +376,16 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes } else { Logger::Warn("Session {}: no handler for binary message type {}", sessionId_, message.header.message_type); - SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Unknown message type", 400); } } -void BinarySession::SendBinary(uint16_t message_type, const std::vector& data) { +void BinarySession::Send(uint16_t message_type, const std::vector& data) { if (!connected_ || closing_) { Logger::Warn("Session {} not connected, cannot send binary", sessionId_); return; } - BinaryProtocol::BinaryMessage message; message.header.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; message.header.message_type = message_type; @@ -395,12 +394,10 @@ void BinarySession::SendBinary(uint16_t message_type, const std::vector std::chrono::system_clock::now().time_since_epoch()).count(); message.data = data; message.header.length = static_cast(data.size()); - - // Apply compression if enabled and beneficial if (compression_enabled_ && data.size() > 1024) { try { auto compressed = BinaryProtocol::CompressData(data); - if (compressed.size() < data.size() * 0.9) { // Only compress if we save at least 10% + if (compressed.size() < data.size() * 0.9) { message.data = compressed; message.header.length = static_cast(compressed.size()); message.header.flags |= BinaryProtocol::FLAG_COMPRESSED; @@ -409,47 +406,49 @@ void BinarySession::SendBinary(uint16_t message_type, const std::vector Logger::Warn("Session {} compression failed: {}", sessionId_, e.what()); } } - - // Add encryption flag if using TLS if (ssl_stream_) { message.header.flags |= BinaryProtocol::FLAG_ENCRYPTED; } - - // Calculate checksum message.header.checksum = BinaryProtocol::CalculateCRC32( message.data.data(), message.data.size()); - auto serialized = message.Serialize(); - - // Record for network monitoring network_monitor_.RecordPacketSent(message.header.sequence, serialized.size()); - - // Add to write queue std::lock_guard lock(write_mutex_); write_queue_.push(serialized); - if (write_queue_.size() == 1) { DoBinaryWrite(); } } -void BinarySession::SendBinary(uint16_t message_type, const void* data, size_t length) { - SendBinary(message_type, std::vector( +void BinarySession::Send(uint16_t message_type, const void* data, size_t length) { + Send(message_type, std::vector( static_cast(data), static_cast(data) + length )); } -void BinarySession::SendBinaryWithAck(uint16_t message_type, const std::vector& data) { - // Store in pending acks for reliability - uint32_t sequence = next_sequence_.load(); +void BinarySession::SendRaw(const std::string& data) { + if (!connected_ || closing_) { + Logger::Warn("Session {} not connected, cannot send", sessionId_); + } + else + { + std::lock_guard lock(write_mutex_); + bool write_in_progress = !write_queue_.empty(); + std::vector binary_data(data.begin(), data.end()); + write_queue_.push(binary_data); + if (!write_in_progress) { + DoBinaryWrite(); + } + } +} +void BinarySession::SendWithAck(uint16_t message_type, const std::vector& data) { + uint32_t sequence = next_sequence_.load(); { std::lock_guard lock(ack_mutex_); pending_acks_[sequence] = std::chrono::steady_clock::now(); } - - // Send with reliable flag BinaryProtocol::BinaryMessage message; message.header.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; message.header.message_type = message_type; @@ -457,24 +456,45 @@ void BinarySession::SendBinaryWithAck(uint16_t message_type, const std::vector( std::chrono::system_clock::now().time_since_epoch()).count(); - message.data = data; - message.header.length = static_cast(data.size()); - message.header.checksum = BinaryProtocol::CalculateCRC32(data.data(), data.size()); + message.data = data; + message.header.length = static_cast(data.size()); + message.header.checksum = BinaryProtocol::CalculateCRC32(data.data(), data.size()); + auto serialized = message.Serialize(); + network_monitor_.RecordPacketSent(sequence, serialized.size()); + std::lock_guard lock(write_mutex_); + write_queue_.push(serialized); + if (write_queue_.size() == 1) { + DoBinaryWrite(); + } + next_sequence_++; +} - auto serialized = message.Serialize(); +void BinarySession::SendError(uint16_t message_type, const std::string& error_message, int code) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt32(static_cast(code)); + writer.WriteString(error_message); + writer.WriteUInt64(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count()); - // Record for network monitoring - network_monitor_.RecordPacketSent(sequence, serialized.size()); + Send(message_type, writer.GetBuffer()); +} - std::lock_guard lock(write_mutex_); - write_queue_.push(serialized); +void BinarySession::SendJson(const nlohmann::json& message) +{ + SendRaw(message.dump()); +} - if (write_queue_.size() == 1) { - DoBinaryWrite(); - } - // Increment sequence number - next_sequence_++; +void BinarySession::SendBinaryWithAck(uint16_t message_type, const std::vector& data) { + SendWithAck(message_type, data); +} + +void BinarySession::SendPing() { + Send(BinaryProtocol::MESSAGE_TYPE_HEARTBEAT, {}); +} + +void BinarySession::SendPong() { + Send(BinaryProtocol::MESSAGE_TYPE_HEARTBEAT, {}); } void BinarySession::DoBinaryWrite() { @@ -553,16 +573,6 @@ void BinarySession::ProcessAcknowledgment(uint32_t sequence) { } } -void BinarySession::SendBinaryError(uint16_t message_type, const std::string& error_message, int code) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt32(static_cast(code)); - writer.WriteString(error_message); - writer.WriteUInt64(std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()); - - SendBinary(message_type, writer.GetBuffer()); -} - // =============== Protocol Negotiation =============== void BinarySession::SendProtocolCapabilities() { @@ -578,7 +588,7 @@ void BinarySession::SendProtocolCapabilities() { } auto caps_data = caps.Serialize(); - SendBinary(BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION, caps_data); + Send(BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION, caps_data); } void BinarySession::HandleProtocolNegotiation(const std::vector& data) { @@ -604,92 +614,16 @@ void BinarySession::HandleProtocolNegotiation(const std::vector& data) writer.WriteUInt8(1); // success writer.WriteString("Protocol negotiation successful"); - SendBinary(BinaryProtocol::MESSAGE_TYPE_SUCCESS, writer.GetBuffer()); + Send(BinaryProtocol::MESSAGE_TYPE_SUCCESS, writer.GetBuffer()); } catch (const std::exception& e) { Logger::Error("Session {}: protocol negotiation failed: {}", sessionId_, e.what()); - SendBinaryError(BinaryProtocol::MESSAGE_TYPE_ERROR, + SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Protocol negotiation failed", 400); } } -// =============== JSON Compatibility Methods =============== - -void BinarySession::Send(const nlohmann::json& message) { - try { - std::string data = message.dump() + "\n"; - SendRaw(data); - } catch (const std::exception& e) { - Logger::Error("Session {} failed to serialize JSON: {}", - sessionId_, e.what()); - } -} - -void BinarySession::SendRaw(const std::string& data) { - if (!connected_ || closing_) { - Logger::Warn("Session {} not connected, cannot send", sessionId_); - return; - } - - { - std::lock_guard lock(write_mutex_); - bool write_in_progress = !write_queue_.empty(); - - // Convert string to vector - std::vector binary_data(data.begin(), data.end()); - write_queue_.push(binary_data); - - if (!write_in_progress) { - DoBinaryWrite(); - } - } -} - -void BinarySession::SendError(const std::string& message, int code) { - nlohmann::json error = { - {"type", "error"}, - {"code", code}, - {"message", message}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(error); -} - -void BinarySession::SendSuccess(const std::string& message, const nlohmann::json& data) { - nlohmann::json success = { - {"type", "success"}, - {"message", message}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - - if (!data.empty()) { - success["data"] = data; - } - - Send(success); -} - -void BinarySession::SendPing() { - nlohmann::json ping = { - {"type", "ping"}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(ping); -} - -void BinarySession::SendPong() { - nlohmann::json pong = { - {"type", "pong"}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(pong); -} - // =============== Heartbeat Management =============== void BinarySession::StartHeartbeat() { @@ -792,8 +726,8 @@ void BinarySession::CheckNetworkConditions() { // Send warning to client nlohmann::json warning = { - {"type", "network_warning"}, - {"message", "Unstable network connection detected"}, + {"msg", "network_warning"}, + {"desc", "Unstable network connection detected"}, {"metrics", { {"latency", metrics.average_latency_ms}, {"packet_loss", metrics.packet_loss_percent}, @@ -802,7 +736,7 @@ void BinarySession::CheckNetworkConditions() { {"timestamp", std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count()} }; - Send(warning); + SendJson(warning); } } @@ -1232,51 +1166,6 @@ void BinarySession::SetMaxWriteQueueSize(size_t maxSize) { max_write_queue_size_ = maxSize; } -// =============== Graceful Shutdown =============== - -void BinarySession::BeginGracefulShutdown() { - if (graceful_shutdown_) { - return; - } - - Logger::Info("Beginning graceful shutdown for session {}", sessionId_); - graceful_shutdown_ = true; - - // Send shutdown notification to client - nlohmann::json shutdown_msg = { - {"type", "shutdown_notice"}, - {"message", "Server shutting down"}, - {"timeout_seconds", 30}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(shutdown_msg); - - // Start shutdown timer - shutdown_timer_.expires_after(std::chrono::seconds(30)); - shutdown_timer_.async_wait([self = shared_from_this()](std::error_code ec) { - if (!ec) { - Logger::Info("Graceful shutdown timeout for session {}", self->sessionId_); - self->Stop(); - } - }); -} - -void BinarySession::CancelGracefulShutdown() { - if (!graceful_shutdown_) { - return; - } - - Logger::Info("Cancelling graceful shutdown for session {}", sessionId_); - graceful_shutdown_ = false; - - try { - shutdown_timer_.cancel(); - } catch (const std::exception& e) { - Logger::Debug("Error cancelling shutdown timer: {}", e.what()); - } -} - // =============== World and Entity Methods =============== void BinarySession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vector& chunkData) { @@ -1291,7 +1180,7 @@ void BinarySession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vect std::copy(metadata.begin(), metadata.end(), combined_data.begin()); std::copy(chunkData.begin(), chunkData.end(), combined_data.begin() + metadata.size()); - SendBinary(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, combined_data); + Send(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, combined_data); } void BinarySession::SendEntityUpdateBinary(uint64_t entityId, const std::vector& entityData) { @@ -1305,7 +1194,7 @@ void BinarySession::SendEntityUpdateBinary(uint64_t entityId, const std::vector< std::copy(id_data.begin(), id_data.end(), combined_data.begin()); std::copy(entityData.begin(), entityData.end(), combined_data.begin() + id_data.size()); - SendBinary(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, combined_data); + Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, combined_data); } void BinarySession::SendEntitySpawnBinary(uint64_t entityId, const std::vector& spawnData) { @@ -1318,14 +1207,14 @@ void BinarySession::SendEntitySpawnBinary(uint64_t entityId, const std::vector( std::chrono::system_clock::now().time_since_epoch()).count()); - SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, writer.GetBuffer()); + Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, writer.GetBuffer()); } void BinarySession::SendPositionCorrection(const glm::vec3& position, const glm::vec3& velocity) { @@ -1352,7 +1241,7 @@ void BinarySession::SendPositionCorrection(const glm::vec3& position, const glm: writer.WriteUInt64(std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count()); - SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer()); + Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer()); } // =============== Private Helper Methods =============== @@ -1406,7 +1295,7 @@ void BinarySession::HandleMessage(const std::string& message) { sessionId_, e.what(), message); // Send error response for malformed JSON - SendError("Invalid JSON format", 400); + SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Invalid JSON format", 400); } catch (const std::exception& e) { Logger::Error("Session {} message handling error: {}", @@ -1414,217 +1303,10 @@ void BinarySession::HandleMessage(const std::string& message) { } } -// =============== Prediction System Integration =============== - PredictionSystem& BinarySession::GetPredictionSystem() { return prediction_system_; } -// =============== Legacy JSON Handlers (for backward compatibility) =============== - -// These methods handle the old JSON-based protocol for backward compatibility -void BinarySession::SendWorldChunk(int chunkX, int chunkZ, const nlohmann::json& chunkData) { - nlohmann::json message = { - {"type", "world_chunk"}, - {"chunkX", chunkX}, - {"chunkZ", chunkZ}, - {"data", chunkData}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendEntityUpdate(uint64_t entityId, const nlohmann::json& entityData) { - nlohmann::json message = { - {"type", "entity_update"}, - {"entityId", entityId}, - {"data", entityData}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendEntitySpawn(uint64_t entityId, const nlohmann::json& spawnData) { - nlohmann::json message = { - {"type", "entity_spawn"}, - {"entityId", entityId}, - {"data", spawnData}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendEntityDespawn(uint64_t entityId) { - nlohmann::json message = { - {"type", "entity_despawn"}, - {"entityId", entityId}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendCollisionEvent(uint64_t entityId1, uint64_t entityId2, const glm::vec3& point) { - nlohmann::json message = { - {"type", "collision_event"}, - {"entityId1", entityId1}, - {"entityId2", entityId2}, - {"point", {point.x, point.y, point.z}}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SyncPlayerState(const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& velocity) { - nlohmann::json message = { - {"type", "player_state_sync"}, - {"position", {position.x, position.y, position.z}}, - {"rotation", {rotation.x, rotation.y, rotation.z}}, - {"velocity", {velocity.x, velocity.y, velocity.z}}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendNearbyEntities(const std::vector& entities) { - nlohmann::json message = { - {"type", "nearby_entities"}, - {"entities", entities}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendNPCInteraction(uint64_t npcId, const std::string& interactionType, const nlohmann::json& data) { - nlohmann::json message = { - {"type", "npc_interaction"}, - {"npcId", npcId}, - {"interaction", interactionType}, - {"data", data}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -void BinarySession::SendCompressedWorldData(const std::vector& compressedData) { - // Convert binary data to base64 for JSON transmission - std::string base64_data; - const char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - size_t i = 0; - while (i < compressedData.size()) { - uint32_t octet_a = i < compressedData.size() ? compressedData[i++] : 0; - uint32_t octet_b = i < compressedData.size() ? compressedData[i++] : 0; - uint32_t octet_c = i < compressedData.size() ? compressedData[i++] : 0; - - uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; - - base64_data += base64_chars[(triple >> 18) & 0x3F]; - base64_data += base64_chars[(triple >> 12) & 0x3F]; - base64_data += base64_chars[(triple >> 6) & 0x3F]; - base64_data += base64_chars[triple & 0x3F]; - } - - // Add padding - size_t padding = compressedData.size() % 3; - if (padding > 0) { - for (size_t j = 0; j < 3 - padding; ++j) { - base64_data[base64_data.size() - 1 - j] = '='; - } - } - - nlohmann::json message = { - {"type", "compressed_world_data"}, - {"data", base64_data}, - {"compression", "base64"}, - {"original_size", compressedData.size()}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(message); -} - -// =============== Legacy Handlers (Kept for backward compatibility) =============== - -void BinarySession::HandleWorldRequest(const nlohmann::json& data) { - // Legacy handler - convert to binary protocol - Logger::Debug("Session {}: converting legacy world request to binary", sessionId_); - - BinaryProtocol::BinaryWriter writer; - writer.WriteInt32(data.value("chunkX", 0)); - writer.WriteInt32(data.value("chunkZ", 0)); - writer.WriteUInt8(data.value("lod", 0)); - - // Forward to binary handler - auto handler_it = binary_handlers_.find(BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST); - if (handler_it != binary_handlers_.end()) { - handler_it->second(BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST, writer.GetBuffer()); - } -} - -void BinarySession::HandleEntityInteraction(const nlohmann::json& data) { - // Legacy handler - convert to binary protocol - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(data.value("entityId", 0ULL)); - writer.WriteString(data.value("interaction", "")); - writer.WriteJson(data.value("data", nlohmann::json())); - - // Forward to appropriate binary handler - auto handler_it = binary_handlers_.find(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION); - if (handler_it != binary_handlers_.end()) { - handler_it->second(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, writer.GetBuffer()); - } -} - -void BinarySession::HandleMovementUpdate(const nlohmann::json& data) { - // Legacy handler - convert to binary protocol - BinaryProtocol::BinaryWriter writer; - - glm::vec3 position( - data.value("x", 0.0f), - data.value("y", 0.0f), - data.value("z", 0.0f) - ); - - glm::vec3 velocity( - data.value("vx", 0.0f), - data.value("vy", 0.0f), - data.value("vz", 0.0f) - ); - - writer.WriteVector3(position); - writer.WriteVector3(velocity); - writer.WriteUInt32(data.value("input_id", 0U)); - - // Forward to binary handler - auto handler_it = binary_handlers_.find(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION); - if (handler_it != binary_handlers_.end()) { - handler_it->second(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, writer.GetBuffer()); - } -} - -void BinarySession::HandleFamiliarCommand(const nlohmann::json& data) { - // Legacy handler - convert to binary protocol - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(data.value("familiarId", 0ULL)); - writer.WriteString(data.value("command", "")); - writer.WriteUInt64(data.value("targetId", 0ULL)); - - // Forward to binary handler (using entity interaction as fallback) - auto handler_it = binary_handlers_.find(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION); - if (handler_it != binary_handlers_.end()) { - handler_it->second(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, writer.GetBuffer()); - } -} - void BinarySession::SetPlayerStateHandler(std::function handler) { player_state_handler_ = std::move(handler); } diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index 6f6d230..b40eb64 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -22,6 +22,8 @@ GameServer::GameServer(const WorkerGroupConfig& groupConfig, const ConfigManager GameServer::~GameServer() = default; +asio::io_context& GameServer::GetIoContext() { return ioContext_; } + bool GameServer::Initialize() { try { asio::ip::tcp::endpoint endpoint( @@ -70,6 +72,63 @@ void GameServer::Run() { Logger::Info("GameServer run finished for protocol '{}'", groupConfig_.protocol); } +// void GameServer::HandleIPCMessage(const nlohmann::json& data, GameLogic& game_logic) { +// try { +// std::string msgType = data.value("msg", ""); +// if (msgType == "shutdown") { +// Logger::Info("Received shutdown command via IPC"); +// game_logic.Shutdown(); +// Shutdown(); +// } else if (msgType == "reload_config") { +// Logger::Info("Received config reload command via IPC"); +// } else if (msgType == "broadcast") { +// if (data.contains("data")) { +// game_logic.BroadcastToAllPlayers(data["data"]); +// } +// } else { +// Logger::Warn("Unknown IPC message type: {}", msgType); +// } +// } catch (const std::exception& err) { +// Logger::Error("Error handling IPC message: {}", err.what()); +// } +// } + +void GameServer::HandleIPCMessage(const nlohmann::json& data, GameLogic& game_logic) { + try { + std::string msgType = data.value("msg", ""); + auto it = IPCMessageTypes.find(msgType); + if (it == IPCMessageTypes.end()) { + Logger::Warn("Unknown IPC message: {}", msgType); + return; + } + int typeCode = it->second; + switch (typeCode) { + case 1://welcome + break; + case 2://heartbeat + break; + case 3://broadcast + if (data.contains("data")) { + game_logic.BroadcastToAllPlayers(data["data"]); + } + break; + case 4://shutdown + Logger::Info("Received shutdown command from master"); + game_logic.Shutdown(); + Shutdown(); + break; + case 5://reload_config + Logger::Info("Received config reload command from master"); + break; + default: + Logger::Warn("Unhandled IPC message: {}({})", typeCode, msgType); + break; + } + } catch (const std::exception& err) { + Logger::Error("Error handling IPC message: {}", err.what()); + } +} + void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, GameLogic& game_logic) { if (groupConfig_.protocol == "binary") { sessionFactory_ = [workerId, processPool, &game_logic, this] @@ -79,16 +138,16 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game session->SetMessageHandler([session, workerId, processPool, &game_logic] (const nlohmann::json& msg) mutable{ try { - std::string msgType = msg.value("type", ""); - Logger::Trace("Worker {} processing message type: {}", workerId, msgType); + std::string msgType = msg.value("msg", ""); + Logger::Trace("Worker {} processing message: {}", workerId, msgType); if (msgType == "ipc_message" && processPool) { if (msg.contains("target_worker") && msg.contains("payload")) { int targetWorker = msg["target_worker"]; std::string payload = msg["payload"].dump(); if (processPool->SendToWorker(targetWorker, payload)) { - Logger::Trace("Worker {} sent IPC message to worker {}", workerId, targetWorker); + Logger::Trace("Worker {} sent IPC message {} to worker {}", workerId, msgType, targetWorker); } else { - Logger::Error("Worker {} failed to send IPC message to worker {}", workerId, targetWorker); + Logger::Error("Worker {} failed to send IPC message {} to worker {}", workerId, msgType, targetWorker); } } } else { @@ -96,7 +155,7 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game } } catch (const std::exception& e) { Logger::Error("Worker {} error processing message: {}", workerId, e.what()); - session->SendError("Internal server error", 500); + session->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Internal server error", 500); } }); session->SetDefaultBinaryMessageHandler([session, workerId, &game_logic] @@ -113,7 +172,7 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game } case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { BinaryProtocol::BinaryReader reader(data.data(), data.size()); - ChunkRequestData req; + ChunkData req; req.chunk_x = reader.ReadInt32(); req.chunk_z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); @@ -121,6 +180,15 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game game_logic.OnChunkRequest(req); break; } + case BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + CollisionData req; + req.position = reader.ReadVector3(); + req.radius = reader.ReadFloat(); + req.session_id = session->GetSessionId(); + game_logic.OnCollisionCheck(req); + break; + } case BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE: { BinaryProtocol::BinaryReader reader(data.data(), data.size()); PlayerStateData stateData; @@ -146,6 +214,58 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game game_logic.OnPlayerPosition(posData); break; } + case BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + NpcData req; + req.npc_id = reader.ReadUInt64(); + req.type = reader.ReadString(); + req.session_id = session->GetSessionId(); + game_logic.OnNPCInteraction(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + FamiliarData req; + req.session_id = session->GetSessionId(); + req.familiar_id = reader.ReadUInt64(); + req.target_id = reader.ReadUInt64(); + req.command = reader.ReadString(); + game_logic.OnFamiliarCommand(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + EntitySpawnData req; + req.session_id = session->GetSessionId(); + req.entity_id = reader.ReadUInt64(); + req.type = reader.ReadInt32(); + req.position = reader.ReadVector3(); + game_logic.OnEntitySpawnRequest(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_LOOT_PICKUP: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + LootPickupData req; + req.loot_id = reader.ReadUInt64(); + req.quantity = reader.ReadUInt16(); + req.session_id = session->GetSessionId(); + game_logic.OnLootPickup(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_INVENTORY_MOVE: { + BinaryProtocol::BinaryReader reader(data.data(), data.size()); + InventoryData req; + req.loot_id = reader.ReadUInt64(); + req.target_id = reader.ReadUInt64(); + req.move_type = static_cast(reader.ReadUInt8()); + req.inv_slot_id = reader.ReadInt32(); + req.use_slot_id = reader.ReadInt32(); + req.quantity = reader.ReadUInt16(); + req.session_id = session->GetSessionId(); + game_logic.OnInventory(req); + break; + } + //TODO: add all game_logic MESSAGE_TYPE_XXX default: game_logic.HandleBinaryMessage(session->GetSessionId(), type, data); break; @@ -167,7 +287,7 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game Logger::Trace("Worker {} created new game session {}; protocol: websocket", workerId, session->GetSessionId()); session->SetMessageHandler([session, workerId, processPool, &game_logic] (const nlohmann::json& msg) mutable{ - std::string msgType = msg.value("type", ""); + std::string msgType = msg.value("msg", ""); if (msgType == "authentication") { AuthenticationData authData; authData.username = msg.value("login", ""); @@ -176,13 +296,22 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game game_logic.OnAuthentication(authData); } else if (msgType == "get_chunk") { - ChunkRequestData req; + ChunkData req; req.chunk_x = msg.value("x", 0); req.chunk_z = msg.value("z", 0); req.lod = static_cast(msg.value("lod", 0)); req.session_id = session->GetSessionId(); game_logic.OnChunkRequest(req); } + else if (msgType == "collision") { + CollisionData req; + req.position.x = msg.value("x", 0.0f); + req.position.y = msg.value("y", 0.0f); + req.position.z = msg.value("z", 0.0f); + req.radius = msg.value("radius", 0.5f); + req.session_id = session->GetSessionId(); + game_logic.OnCollisionCheck(req); + } else if (msgType == "player_state") { // TODO: fill full state data format PlayerStateData posData; @@ -204,32 +333,54 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game posData.session_id = session->GetSessionId(); game_logic.OnPlayerPosition(posData); } + else if (msgType == "npc_interaction") { + NpcData req; + req.npc_id = msg.value("npc_id", 0); + req.type = msg.value("npc_type", ""); + req.session_id = session->GetSessionId(); + game_logic.OnNPCInteraction(req); + } + else if (msgType == "familiar") { + FamiliarData req; + req.session_id = session->GetSessionId(); + req.familiar_id = msg.value("familiarId", 0ULL); + req.target_id = msg.value("targetId", 0ULL); + req.command = msg.value("command", ""); + game_logic.OnFamiliarCommand(req); + } + else if (msgType == "entity_spawn") { + EntitySpawnData req; + req.entity_id = msg.value("id", 0); + req.type = msg.value("entity_type", 0); + req.position.x = msg.value("x", 0.0f); + req.position.y = msg.value("y", 0.0f); + req.position.z = msg.value("z", 0.0f); + req.session_id = session->GetSessionId(); + game_logic.OnEntitySpawnRequest(req); + } + else if (msgType == "loot_pickup") { + LootPickupData req; + req.loot_id = msg.value("loot_id", 0ULL); + req.quantity = msg.value("quantity", 1); + req.session_id = session->GetSessionId(); + game_logic.OnLootPickup(req); + } + else if (msgType == "inventory") { + InventoryData req; + req.loot_id = msg.value("loot_id", 0ULL); + req.target_id = msg.value("target_id", 0ULL); + req.move_type = static_cast(msg.value("move_type", 0)); + req.inv_slot_id = msg.value("inv_slot_id", -1); + req.use_slot_id = msg.value("use_slot_id", -1); + req.quantity = msg.value("quantity", 1); + req.session_id = session->GetSessionId(); + game_logic.OnInventory(req); + } + //TODO: add all game_logic msgType else { game_logic.HandleMessage(session->GetSessionId(), msg); } }); - // session->SetMessageHandler([session, workerId, processPool](const nlohmann::json& msg) { - // try { - // std::string msgType = msg.value("type", ""); - // Logger::Trace("Worker {} processing message type: {}", workerId, msgType); - // if (msgType == "ipc_message" && processPool) { - // if (msg.contains("target_worker") && msg.contains("payload")) { - // int targetWorker = msg["target_worker"]; - // std::string payload = msg["payload"].dump(); - // if (processPool->SendToWorker(targetWorker, payload)) { - // Logger::Trace("Worker {} sent IPC message to worker {}", workerId, targetWorker); - // } else { - // Logger::Error("Worker {} failed to send IPC message to worker {}", workerId, targetWorker); - // } - // } - // } else { - // game_logic.HandleMessage(session->GetSessionId(), msg); - // } - // } catch (const std::exception& e) { - // Logger::Error("Worker {} error processing message: {}", workerId, e.what()); - // session->SendError("Internal server error", 500); - // } - // }); session->SetBinaryMessageHandler([session, workerId, &game_logic] (uint16_t type, const std::vector& data) mutable{ game_logic.HandleBinaryMessage(session->GetSessionId(), type, data); @@ -339,7 +490,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteString(message); auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) { - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); + session->Send(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, writer.GetBuffer()); } }); game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { @@ -351,7 +502,19 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteUInt64(data.timestamp); auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) { - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); + session->Send(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); + } + }); + game_logic.SetSendCollisionResponseCallback([&](uint64_t session_id, const CollisionResult& result) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt8(result.collided ? 1 : 0); + writer.WriteUInt64(result.collidedWith); + writer.WriteFloat(result.penetration); + writer.WriteVector3(result.resolution); + writer.WriteUInt64(game_logic.GetCurrentTimestamp()); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK, writer.GetBuffer()); } }); game_logic.SetPlayerStateCallback([&](const PlayerStateData& state) { @@ -364,7 +527,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteUInt64(state.timestamp); auto nearbySessions = GetSessionsInRadius(state.position, 100.0f); for (auto& session : nearbySessions) { - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer()); + session->Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer()); } }); game_logic.SetBroadcastPlayerPositionCallback([&](const PlayerPositionData& data, float radius) { @@ -375,13 +538,82 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteUInt64(data.timestamp); auto nearbySessions = GetSessionsInRadius(data.position, radius); for (auto& session : nearbySessions) { - session->SendBinary(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, writer.GetBuffer()); + session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, writer.GetBuffer()); + } + }); + game_logic.SetSendNPCInteractionResponseCallback([&](uint64_t session_id, const NpcData& response) { + BinaryProtocol::BinaryWriter writer; + if (response.type == "combat") { + writer.WriteUInt64(response.timestamp); + writer.WriteUInt64(response.player_id); + writer.WriteUInt64(response.npc_id); + writer.WriteFloat(response.damage); + writer.WriteFloat(response.health); + writer.WriteUInt8(response.is_dead ? 1 : 0); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->Send(BinaryProtocol::MESSAGE_TYPE_COMBAT_EVENT, writer.GetBuffer()); + } else if (response.type == "dialogue") { + writer.WriteUInt64(response.npc_id); + writer.WriteJson(response.quests); + writer.WriteUInt64(response.timestamp); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->Send(BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, writer.GetBuffer()); + } else { + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) + session->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "NPC interaction failed", 400); + } + }); + game_logic.SetSendFamiliarCommandResponseCallback([&](uint64_t session_id, const FamiliarData& response) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(response.timestamp); + writer.WriteUInt64(response.familiar_id); + writer.WriteUInt64(response.target_id); + writer.WriteString(response.command); + writer.WriteUInt8(response.success ? 1 : 0); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->Send(BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND, writer.GetBuffer()); + }); + game_logic.SetSendEntitySpawnResponseCallback([&](uint64_t session_id, const EntitySpawnData& response) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(response.timestamp); + writer.WriteUInt64(response.entity_id); + writer.WriteInt32(response.type); + writer.WriteVector3(response.position); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer()); } }); + game_logic.SetSendLootPickupResponseCallback([&](uint64_t session_id, const LootPickupData& response) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(response.loot_id); + writer.WriteUInt16(response.quantity); + writer.WriteUInt64(response.timestamp); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(BinaryProtocol::MESSAGE_TYPE_LOOT_PICKUP, writer.GetBuffer()); + } + }); + game_logic.SetSendInventoryResponseCallback([&](uint64_t session_id, const InventoryData& response) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(response.loot_id); + writer.WriteUInt64(response.target_id); + writer.WriteUInt8(static_cast(response.move_type)); + writer.WriteInt32(response.inv_slot_id); + writer.WriteInt32(response.use_slot_id); + writer.WriteUInt16(response.quantity); + writer.WriteUInt64(response.timestamp); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(BinaryProtocol::MESSAGE_TYPE_INVENTORY_MOVE, writer.GetBuffer()); + } + }); + //TODO: add all game_logic } else { // websocket game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, bool success, const std::string& message, uint64_t player_id) { nlohmann::json response = { - {"type", "authentication_response"}, + {"msg", "authentication_response"}, {"success", success}, {"message", message}, {"player_id", player_id}, @@ -389,26 +621,40 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }; auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) { - session->Send(response); + session->SendJson(response); } }); game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { nlohmann::json msg = { - {"type", "world_chunk"}, - {"chunk_x", data.chunk_x}, - {"chunk_z", data.chunk_z}, + {"msg", "get_chunk"}, + {"x", data.chunk_x}, + {"z", data.chunk_z}, {"lod", data.lod}, {"data", data.chunk_json}, {"timestamp", data.timestamp} }; auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) { - session->Send(msg); + session->SendJson(msg); + } + }); + game_logic.SetSendCollisionResponseCallback([&](uint64_t session_id, const CollisionResult& result) { + nlohmann::json response = { + {"msg", "collision"}, + {"collided", result.collided}, + {"collidedWith", result.collidedWith}, + {"penetration", result.penetration}, + {"resolution", {result.resolution.x, result.resolution.y, result.resolution.z}}, + {"timestamp", game_logic.GetCurrentTimestamp()} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->SendJson(response); } }); game_logic.SetPlayerStateCallback([&](const PlayerStateData& state) { nlohmann::json jsonMsg = { - {"type", "entity_update"}, + {"msg", "entity_update"}, {"entity_id", state.player_id}, {"x", state.position.x}, {"y", state.position.y}, {"z", state.position.z}, {"rx", state.rotation.x}, {"ry", state.rotation.y}, {"rz", state.rotation.z}, @@ -417,12 +663,12 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }; auto nearbySessions = GetSessionsInRadius(state.position, 100.0f); for (auto& session : nearbySessions) { - session->Send(jsonMsg); + session->SendJson(jsonMsg); } }); game_logic.SetBroadcastPlayerPositionCallback([&](const PlayerPositionData& data, float radius) { nlohmann::json jsonMsg = { - {"type", "player_position"}, + {"msg", "player_position"}, {"player_id", data.player_id}, {"x", data.position.x}, {"y", data.position.y}, {"z", data.position.z}, {"vx", data.velocity.x}, {"vy", data.velocity.y}, {"vz", data.velocity.z}, @@ -430,8 +676,81 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }; auto nearbySessions = GetSessionsInRadius(data.position, radius); for (auto& session : nearbySessions) { - session->Send(jsonMsg); + session->SendJson(jsonMsg); } }); + game_logic.SetSendNPCInteractionResponseCallback([&](uint64_t session_id, const NpcData& response) { + nlohmann::json jsonMsg; + if (response.type == "combat") { + jsonMsg = { + {"msg", "combat"}, + {"player_id", response.player_id}, + {"npc_id", response.npc_id}, + {"damage", response.damage}, + {"health", response.health}, + {"is_dead", response.is_dead}, + {"timestamp", response.timestamp} + }; + } else if (response.type == "dialogue") { + jsonMsg = { + {"msg", "dialogue"}, + {"npc_id", response.npc_id}, + {"quests", response.quests}, + {"timestamp", response.timestamp} + }; + } else { + jsonMsg = {{"msg", "error"}, {"desc", "NPC interaction failed"}}; + } + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->SendJson(jsonMsg); + }); + game_logic.SetSendFamiliarCommandResponseCallback([&](uint64_t session_id, const FamiliarData& response) { + nlohmann::json jsonMsg = { + {"msg", "familiar"}, + {"familiar_id", response.familiar_id}, + {"target_id", response.target_id}, + {"command", response.command}, + {"success", response.success}, + {"timestamp", response.timestamp} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->SendJson(jsonMsg); + }); + game_logic.SetSendEntitySpawnResponseCallback([&](uint64_t session_id, const EntitySpawnData& response) { + nlohmann::json jsonMsg = { + {"msg", "entity_spawn"}, + {"id", response.entity_id}, + {"type", response.type}, + {"position", {response.position.x, response.position.y, response.position.z}}, + {"timestamp", response.timestamp} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->SendJson(jsonMsg); + }); + game_logic.SetSendLootPickupResponseCallback([&](uint64_t session_id, const LootPickupData& response) { + nlohmann::json jsonMsg = { + {"msg", "loot_pickup"}, + {"loot_id", response.loot_id}, + {"quantity", response.quantity}, + {"timestamp", response.timestamp} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->SendJson(jsonMsg); + }); + game_logic.SetSendInventoryResponseCallback([&](uint64_t session_id, const InventoryData& response) { + nlohmann::json jsonMsg = { + {"msg", "inventory"}, + {"loot_id", response.loot_id}, + {"target_id", response.target_id}, + {"move_type", static_cast(response.move_type)}, + {"inv_slot_id", response.inv_slot_id}, + {"use_slot_id", response.use_slot_id}, + {"quantity", response.quantity}, + {"timestamp", response.timestamp} + }; + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) session->SendJson(jsonMsg); + }); + //TODO: add all game_logic } } diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index da81d01..166e689 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -20,36 +20,11 @@ void WebSocketSession::Disconnect() { wsConn_->Close(1000, "Disconnect"); } bool WebSocketSession::IsConnected() const { return wsConn_->IsOpen(); } uint64_t WebSocketSession::GetSessionId() const { return sessionId_; } -void WebSocketSession::SendError(const std::string& message, int code) { - try { - nlohmann::json error = { - {"type", "error"}, - {"code", code}, - {"message", message}, - {"timestamp", std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()} - }; - Send(error); - } catch (const std::exception& err) { - Logger::Error("WebSocketSession::SendError failed: {}", err.what()); - } catch (...) { - Logger::Error("WebSocketSession::SendError failed with unknown exception"); - } -} - -void WebSocketSession::Send(const nlohmann::json& message) { - wsConn_->SendJson(message); -} - -void WebSocketSession::SendRaw(const std::string& data) { - wsConn_->SendText(data); -} - -void WebSocketSession::SendBinary(uint16_t message_type, const std::vector& data) { +void WebSocketSession::Send(uint16_t message_type, const std::vector& data) { BinaryProtocol::BinaryMessage msg; msg.header.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; msg.header.message_type = message_type; - msg.header.sequence = 0; + msg.header.sequence = 0; // sequence not used for websocket msg.header.timestamp = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); msg.data = data; @@ -59,6 +34,26 @@ void WebSocketSession::SendBinary(uint16_t message_type, const std::vectorSendBinary(serialized); } +void WebSocketSession::SendRaw(const std::string& data) { + wsConn_->SendText(data); +} + +void WebSocketSession::SendJson(const nlohmann::json& message) { + wsConn_->SendJson(message); +} + +void WebSocketSession::SendError(uint16_t message_type, const std::string& error_message, int code) { + nlohmann::json error = { + {"msg", "error"}, + {"type", message_type}, + {"code", code}, + {"message", error_message}, + {"timestamp", std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count()} + }; + SendJson(error); +} + void WebSocketSession::SetMessageHandler(MessageHandler handler) { messageHandler_ = std::move(handler); } @@ -184,7 +179,7 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) return; } else if (json.value("type", "") == "get_chunk") { - ChunkRequestData req; + ChunkData req; nlohmann::json data = msg.ToJson(); req.chunk_x = data.value("x", 0); req.chunk_z = data.value("z", 0); From 5c67cc74d69c478a1722099d1654eb5107964046 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:03:43 +0300 Subject: [PATCH 12/14] refactor network layer continue --- include/game/GameData.hpp | 11 + include/game/GameLogic.hpp | 40 ++- include/game/PlayerManager.hpp | 18 +- include/network/BinaryProtocol.hpp | 2 + src/game/GameLogic.cpp | 534 +++++++++++++++++------------ src/game/PlayerManager.cpp | 20 ++ src/network/GameServer.cpp | 54 ++- src/network/WebSocketProtocol.cpp | 304 +++++++++++----- 8 files changed, 647 insertions(+), 336 deletions(-) diff --git a/include/game/GameData.hpp b/include/game/GameData.hpp index c466bd6..be19045 100644 --- a/include/game/GameData.hpp +++ b/include/game/GameData.hpp @@ -113,6 +113,17 @@ struct PlayerPositionData { glm::vec3 velocity; }; +struct PlayerUpdateData { + uint64_t timestamp; + uint64_t session_id; + uint64_t player_id; + glm::vec3 position; + float yaw; + float health; + float max_health; + std::string name; +}; + struct ChunkData { uint64_t timestamp; uint64_t session_id; diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 45485a0..385a291 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -59,31 +59,35 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this& data, float radius = 50.0f); - void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, - const std::vector& data, float radius = 50.0f); + // void BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, + // const std::vector& data, float radius = 50.0f); + // void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, + // const std::vector& data, float radius = 50.0f); void SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& position); - void BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius); + // void BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius); void BroadcastToAllPlayers(const nlohmann::json& message); - void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data); - void BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message); - void BroadcastPlayerSpawn(uint64_t playerId); - void BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition); - void BroadcastPlayerSpawnJson(uint64_t playerId); - void BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition); - void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, - float yaw, const std::string& name); + // void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data); + // void BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message); + // void BroadcastPlayerSpawn(uint64_t playerId); + // void BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition); + // void BroadcastPlayerSpawnJson(uint64_t playerId); + // void BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition); + // void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, + // float yaw, const std::string& name); void SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity); - void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); + // void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); void SendAuthenticationSuccess(uint64_t sessionId, uint64_t playerId, const std::string& message); void SendAuthenticationFailure(uint64_t sessionId, const std::string& message); void SetSendAuthenticationResponseCallback(std::function cb); void SetSendChunkCallback(std::function cb); void SetSendCollisionResponseCallback(std::function cb); - void SetPlayerStateCallback(std::function cb); + + void SetPlayerStateCallback(std::function cb); // now it also used for update void SetBroadcastPlayerPositionCallback(std::function cb); + void SetSendPlayerUpdateCallback(std::function cb); + void SetSendPlayersUpdateCallback(std::function cb); + void SetSendNPCInteractionResponseCallback(std::function cb); void SetSendFamiliarCommandResponseCallback(std::function cb); void SetSendEntitySpawnResponseCallback(std::function cb); @@ -95,6 +99,8 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this sendAuthResponseCb_; std::function sendChunkCb_; std::function sendCollisionResponseCb_; + std::function broadcastPlayerPositionCb_; std::function playerStateCb_; + std::function sendPlayerUpdateCb_; + std::function sendPlayersUpdateCb_; + std::function sendNPCInteractionResponseCb_; std::function sendFamiliarCommandResponseCb_; std::function sendEntitySpawnResponseCb_; diff --git a/include/game/PlayerManager.hpp b/include/game/PlayerManager.hpp index 09ee620..665bad0 100644 --- a/include/game/PlayerManager.hpp +++ b/include/game/PlayerManager.hpp @@ -25,9 +25,6 @@ #include "game/RAIIThread.hpp" #include "game/Player.hpp" -// Forward declarations -//class Player; - struct GlobalPlayerStats { int total_players = 0; int online_players = 0; @@ -64,6 +61,9 @@ class PlayerManager { void PlayerConnected(uint64_t sessionId, int64_t playerId); void PlayerDisconnected(uint64_t sessionId); + void UpdatePosition(uint64_t playerId, float x, float y, float z); + std::vector GetDirtyPlayersAndClear(); + void BroadcastToNearbyPlayers(int64_t playerId, const nlohmann::json& message); std::vector GetNearbyPlayers(int64_t playerId, float radius); std::vector> GetPlayersInRadius(const glm::vec3& center, float radius); @@ -100,7 +100,7 @@ class PlayerManager { std::vector GetPartyMembers(int64_t partyId) const; int64_t GeneratePartyId(); - void Shutdown(); // Added missing declaration + void Shutdown(); private: PlayerManager(); @@ -143,6 +143,12 @@ class PlayerManager { std::mutex saveMutex_; std::mutex cleanupMutex_; - static constexpr float DEFAULT_BROADCAST_RANGE = 100.0f; // Added - static constexpr size_t MAX_PARTY_SIZE = 5; // Added + static constexpr float DEFAULT_BROADCAST_RANGE = 100.0f; + static constexpr size_t MAX_PARTY_SIZE = 5; + + mutable std::shared_mutex dirtyMutex_; + std::unordered_set dirtyPlayers_; + + void MarkDirty(uint64_t playerId); + }; diff --git a/include/network/BinaryProtocol.hpp b/include/network/BinaryProtocol.hpp index ea52c0c..ef1d5e2 100644 --- a/include/network/BinaryProtocol.hpp +++ b/include/network/BinaryProtocol.hpp @@ -40,6 +40,8 @@ namespace BinaryProtocol { MESSAGE_TYPE_PLAYER_UPDATE = 205, MESSAGE_TYPE_PLAYER_SPAWN = 206, MESSAGE_TYPE_PLAYER_DESPAWN = 207, + // Players messages + MESSAGE_TYPE_PLAYERS_UPDATE = 250, // Entity messages MESSAGE_TYPE_ENTITY_SPAWN = 300, diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 562db5e..b6156a1 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -220,83 +220,83 @@ void GameLogic::OnCollisionCheck(const CollisionData& data) { } } -void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, - const std::vector& data, float radius) { - if (!connectionManager_) return; - auto& pm = PlayerManager::GetInstance(); - auto nearby = pm.GetPlayersInRadius(position, radius); - for (auto& player : nearby) { - uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); - if (session_id == 0) continue; - auto session = connectionManager_->GetSession(session_id); - if (!session || !session->IsConnected()) continue; - if (session->GetProtocolMode() == ProtocolMode::Binary) { - session->Send(messageType, data); - continue; - } - nlohmann::json jsonMsg; - try { - BinaryProtocol::BinaryReader reader(data.data(), data.size()); - switch (messageType) { - case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: - jsonMsg = PlayerPositionToJson(data); - break; - case BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE: - jsonMsg = PlayerUpdateToJson(data); - break; - case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: - jsonMsg = EntitySpawnToJson(data); - break; - case BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE: - jsonMsg = EntityUpdateToJson(data); - break; - case BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN: - jsonMsg = EntityDespawnToJson(data); - break; - case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: - jsonMsg = ChunkDataToJson(data); - break; - default: - Logger::Warn("No JSON conversion for message type {}", messageType); - continue; - } - } catch (const std::exception& e) { - Logger::Error("BroadcastToNearbyPlayers: Failed to convert binary to JSON: {}", e.what()); - continue; - } - session->SendJson(jsonMsg); - } -} - -void GameLogic::BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, - const std::vector& data, float radius) { - if (!connectionManager_) return; - auto& pm = PlayerManager::GetInstance(); - auto onlinePlayers = pm.GetOnlinePlayers(); - for (const auto& player : onlinePlayers) { - if (glm::distance(player->GetPosition(), position) <= radius) { - uint64_t session_id = GetSessionIdByPlayer(player->GetId()); - if (session_id != 0) { - auto session = connectionManager_->GetSession(session_id); - if (session && session->IsConnected()) { - session->Send(messageType, data); - } - } - } - } -} - -void GameLogic::BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, - float yaw, const std::string& name) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(entityId); - writer.WriteUInt8(static_cast(type)); - writer.WriteString(name); - writer.WriteVector3(position); - writer.WriteFloat(yaw); - writer.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer(), 100.0f); -} +// void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, +// const std::vector& data, float radius) { +// if (!connectionManager_) return; +// auto& pm = PlayerManager::GetInstance(); +// auto nearby = pm.GetPlayersInRadius(position, radius); +// for (auto& player : nearby) { +// uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); +// if (session_id == 0) continue; +// auto session = connectionManager_->GetSession(session_id); +// if (!session || !session->IsConnected()) continue; +// if (session->GetProtocolMode() == ProtocolMode::Binary) { +// session->Send(messageType, data); +// continue; +// } +// nlohmann::json jsonMsg; +// try { +// BinaryProtocol::BinaryReader reader(data.data(), data.size()); +// switch (messageType) { +// case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: +// jsonMsg = PlayerPositionToJson(data); +// break; +// case BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE: +// jsonMsg = PlayerUpdateToJson(data); +// break; +// case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: +// jsonMsg = EntitySpawnToJson(data); +// break; +// case BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE: +// jsonMsg = EntityUpdateToJson(data); +// break; +// case BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN: +// jsonMsg = EntityDespawnToJson(data); +// break; +// case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: +// jsonMsg = ChunkDataToJson(data); +// break; +// default: +// Logger::Warn("No JSON conversion for message type {}", messageType); +// continue; +// } +// } catch (const std::exception& e) { +// Logger::Error("BroadcastToNearbyPlayers: Failed to convert binary to JSON: {}", e.what()); +// continue; +// } +// session->SendJson(jsonMsg); +// } +// } + +// void GameLogic::BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, +// const std::vector& data, float radius) { +// if (!connectionManager_) return; +// auto& pm = PlayerManager::GetInstance(); +// auto onlinePlayers = pm.GetOnlinePlayers(); +// for (const auto& player : onlinePlayers) { +// if (glm::distance(player->GetPosition(), position) <= radius) { +// uint64_t session_id = GetSessionIdByPlayer(player->GetId()); +// if (session_id != 0) { +// auto session = connectionManager_->GetSession(session_id); +// if (session && session->IsConnected()) { +// session->Send(messageType, data); +// } +// } +// } +// } +// } + +// void GameLogic::BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, +// float yaw, const std::string& name) { +// BinaryProtocol::BinaryWriter writer; +// writer.WriteUInt64(entityId); +// writer.WriteUInt8(static_cast(type)); +// writer.WriteString(name); +// writer.WriteVector3(position); +// writer.WriteFloat(yaw); +// writer.WriteUInt64(GetCurrentTimestamp()); +// BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer(), 100.0f); +// } void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& position) { auto nearbyEntities = EntityManager::GetInstance().GetEntitiesInRadius(position, 100.0f); @@ -315,77 +315,77 @@ void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& SendToSessionJson(session_id, message); } -void GameLogic::BroadcastPlayerSpawn(uint64_t player_id) { - auto player = GetPlayer(player_id); - if (!player) return; - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(player_id); - writer.WriteString(player->GetName()); - writer.WriteVector3(player->GetPosition()); - writer.WriteFloat(player->GetRotation().y); - writer.WriteFloat(player->GetHealth()); - writer.WriteFloat(player->GetMaxHealth()); - BroadcastToNearbyPlayers(player->GetPosition(), - BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, - writer.GetBuffer(), - ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -} - -void GameLogic::BroadcastPlayerDespawn(uint64_t player_id, const glm::vec3& lastPosition) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(player_id); - BroadcastToNearbyPlayers(lastPosition, - BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, - writer.GetBuffer(), - ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -} - -void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { - if (!connectionManager_) return; - auto& pm = PlayerManager::GetInstance(); - auto nearby = pm.GetPlayersInRadius(position, radius); - for (auto& player : nearby) { - uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); - if (session_id != 0) { - auto session = connectionManager_->GetSession(session_id); - if (session && session->IsConnected()) { - session->SendJson(message); - } - } - } -} - -void GameLogic::BroadcastPlayerSpawnJson(uint64_t player_id) { - auto player = GetPlayer(player_id); - if (!player) return; - nlohmann::json msg = { - {"type", "player_spawn"}, - {"player_id", player_id}, - {"name", player->GetName()}, - {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, - {"yaw", player->GetRotation().y}, - {"health", player->GetHealth()}, - {"max_health", player->GetMaxHealth()}, - {"timestamp", GetCurrentTimestamp()} - }; - BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -} - -void GameLogic::BroadcastPlayerDespawnJson(uint64_t player_id, const glm::vec3& lastPosition) { - nlohmann::json msg = { - {"type", "player_despawn"}, - {"player_id", player_id}, - {"timestamp", GetCurrentTimestamp()} - }; - BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -} - -void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { - BinaryProtocol::BinaryWriter writer; - writer.WriteUInt64(entityId); - writer.WriteUInt64(GetCurrentTimestamp()); - BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); -} +// void GameLogic::BroadcastPlayerSpawn(uint64_t player_id) { +// auto player = GetPlayer(player_id); +// if (!player) return; +// BinaryProtocol::BinaryWriter writer; +// writer.WriteUInt64(player_id); +// writer.WriteString(player->GetName()); +// writer.WriteVector3(player->GetPosition()); +// writer.WriteFloat(player->GetRotation().y); +// writer.WriteFloat(player->GetHealth()); +// writer.WriteFloat(player->GetMaxHealth()); +// BroadcastToNearbyPlayers(player->GetPosition(), +// BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, +// writer.GetBuffer(), +// ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +// } + +// void GameLogic::BroadcastPlayerDespawn(uint64_t player_id, const glm::vec3& lastPosition) { +// BinaryProtocol::BinaryWriter writer; +// writer.WriteUInt64(player_id); +// BroadcastToNearbyPlayers(lastPosition, +// BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, +// writer.GetBuffer(), +// ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +// } + +// void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { +// if (!connectionManager_) return; +// auto& pm = PlayerManager::GetInstance(); +// auto nearby = pm.GetPlayersInRadius(position, radius); +// for (auto& player : nearby) { +// uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); +// if (session_id != 0) { +// auto session = connectionManager_->GetSession(session_id); +// if (session && session->IsConnected()) { +// session->SendJson(message); +// } +// } +// } +// } + +// void GameLogic::BroadcastPlayerSpawnJson(uint64_t player_id) { +// auto player = GetPlayer(player_id); +// if (!player) return; +// nlohmann::json msg = { +// {"type", "player_spawn"}, +// {"player_id", player_id}, +// {"name", player->GetName()}, +// {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, +// {"yaw", player->GetRotation().y}, +// {"health", player->GetHealth()}, +// {"max_health", player->GetMaxHealth()}, +// {"timestamp", GetCurrentTimestamp()} +// }; +// BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +// } + +// void GameLogic::BroadcastPlayerDespawnJson(uint64_t player_id, const glm::vec3& lastPosition) { +// nlohmann::json msg = { +// {"type", "player_despawn"}, +// {"player_id", player_id}, +// {"timestamp", GetCurrentTimestamp()} +// }; +// BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); +// } + +// void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { +// BinaryProtocol::BinaryWriter writer; +// writer.WriteUInt64(entityId); +// writer.WriteUInt64(GetCurrentTimestamp()); +// BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); +// } void GameLogic::SendAuthenticationSuccess(uint64_t session_id, uint64_t player_id, const std::string& message) { nlohmann::json response = { @@ -434,58 +434,58 @@ void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { } } -void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data) { - if (!connectionManager_) { - Logger::Warn("Cannot broadcast binary: ConnectionManager not available"); - return; - } - try { - auto sessions = connectionManager_->GetAllSessions(); - if (sessions.empty()) return; - Logger::Debug("Broadcasting binary message type {} to {} player(s)", - messageType, sessions.size()); - for (auto& session : sessions) { - if (session && session->IsConnected()) { - try { - session->Send(messageType, data); - } catch (const std::exception& e) { - Logger::Error("Failed to send binary broadcast to session {}: {}", - session->GetSessionId(), e.what()); - } - } - } - } catch (const std::exception& e) { - Logger::Error("Error broadcasting binary to all players: {}", e.what()); - } -} - -void GameLogic::BroadcastToPlayers(const std::vector& session_ids, const nlohmann::json& message) { - if (!connectionManager_) { - Logger::Warn("Cannot broadcast: ConnectionManager not available"); - return; - } - try { - std::string serialized = message.dump(); - int sentCount = 0; - for (uint64_t session_id : session_ids) { - auto session = connectionManager_->GetSession(session_id); - if (session && session->IsConnected()) { - try { - session->SendRaw(serialized); - sentCount++; - } catch (const std::exception& e) { - Logger::Error("Failed to send message to session {}: {}", - session_id, e.what()); - } - } - } - if (sentCount > 0) { - Logger::Debug("Broadcasted to {} specific player(s)", sentCount); - } - } catch (const std::exception& err) { - Logger::Error("Error broadcasting to specific players: {}", err.what()); - } -} +// void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data) { +// if (!connectionManager_) { +// Logger::Warn("Cannot broadcast binary: ConnectionManager not available"); +// return; +// } +// try { +// auto sessions = connectionManager_->GetAllSessions(); +// if (sessions.empty()) return; +// Logger::Debug("Broadcasting binary message type {} to {} player(s)", +// messageType, sessions.size()); +// for (auto& session : sessions) { +// if (session && session->IsConnected()) { +// try { +// session->Send(messageType, data); +// } catch (const std::exception& e) { +// Logger::Error("Failed to send binary broadcast to session {}: {}", +// session->GetSessionId(), e.what()); +// } +// } +// } +// } catch (const std::exception& e) { +// Logger::Error("Error broadcasting binary to all players: {}", e.what()); +// } +// } + +// void GameLogic::BroadcastToPlayers(const std::vector& session_ids, const nlohmann::json& message) { +// if (!connectionManager_) { +// Logger::Warn("Cannot broadcast: ConnectionManager not available"); +// return; +// } +// try { +// std::string serialized = message.dump(); +// int sentCount = 0; +// for (uint64_t session_id : session_ids) { +// auto session = connectionManager_->GetSession(session_id); +// if (session && session->IsConnected()) { +// try { +// session->SendRaw(serialized); +// sentCount++; +// } catch (const std::exception& e) { +// Logger::Error("Failed to send message to session {}: {}", +// session_id, e.what()); +// } +// } +// } +// if (sentCount > 0) { +// Logger::Debug("Broadcasted to {} specific player(s)", sentCount); +// } +// } catch (const std::exception& err) { +// Logger::Error("Error broadcasting to specific players: {}", err.what()); +// } +// } void GameLogic::PerformMaintenance() { CleanupOldData(); @@ -642,6 +642,14 @@ void GameLogic::SetBroadcastPlayerPositionCallback(std::function cb) { + sendPlayerUpdateCb_ = std::move(cb); +} + +void GameLogic::SetSendPlayersUpdateCallback(std::function cb) { + sendPlayersUpdateCb_ = std::move(cb); +} + void GameLogic::SetSendNPCInteractionResponseCallback(std::function cb) { sendNPCInteractionResponseCb_ = std::move(cb); } @@ -722,29 +730,6 @@ void GameLogic::OnChunkRequest(const ChunkData& req) { } } -void GameLogic::OnPlayerPosition(const PlayerPositionData& data) { - auto player = GetPlayer(data.player_id); - if (!player) return; - float collisionRadius = 0.5f; - CollisionResult collision = CheckCollision(data.position, collisionRadius, data.player_id); - glm::vec3 finalPos = data.position; - if (collision.collided) { - finalPos += collision.resolution; - } - player->SetPosition(finalPos); - GenerateWorldAroundPlayer(data.player_id, finalPos); - if (broadcastPlayerPositionCb_) { - PlayerPositionData broadcastData = data; - broadcastData.position = finalPos; - broadcastPlayerPositionCb_(broadcastData, 100.0f); - } - FirePythonEvent("player_move_3d", { - {"player_id", data.player_id}, - {"x", finalPos.x}, {"y", finalPos.y}, {"z", finalPos.z}, - {"session_id", data.session_id} - }); -} - void GameLogic::OnPlayerState(const PlayerStateData& data) { uint64_t player_id = data.player_id; auto player = GetPlayer(player_id); @@ -793,12 +778,95 @@ void GameLogic::OnPlayerState(const PlayerStateData& data) { } else { Logger::Error("No playerStateCb_ set in GameLogic"); } + + // ATTENTION: useless and duplicate piece of code needs to be removed + // Send self player update for rendering + // PlayerUpdateData update; + // update.session_id = data.session_id; + // update.player_id = data.player_id; + // update.position = authState.position; + // update.yaw = authState.rotation.y; + // update.health = player->GetHealth(); + // update.max_health = player->GetMaxHealth(); + // update.name = player->GetName(); + // OnPlayerUpdate(update); + + // correction static float correctionThreshold = 0.5f; if (glm::distance(authState.position, player->GetPosition()) > correctionThreshold) { SendPositionCorrection(data.session_id, authState.position, authState.velocity); } } +void GameLogic::OnPlayerPosition(const PlayerPositionData& data) { + auto player = GetPlayer(data.player_id); + if (!player) return; + float collisionRadius = 0.5f; + CollisionResult collision = CheckCollision(data.position, collisionRadius, data.player_id); + glm::vec3 finalPos = data.position; + if (collision.collided) { + finalPos += collision.resolution; + } + player->SetPosition(finalPos); + GenerateWorldAroundPlayer(data.player_id, finalPos); + if (broadcastPlayerPositionCb_) { + PlayerPositionData broadcastData = data; + broadcastData.position = finalPos; + broadcastPlayerPositionCb_(broadcastData, 100.0f); + } + + // ATTENTION: useless and duplicate piece of code needs to be removed + // PlayerUpdateData update; + // update.session_id = data.session_id; + // update.player_id = data.player_id; + // update.position = finalPos; + // update.yaw = player->GetRotation().y; + // update.health = player->GetHealth(); + // update.max_health = player->GetMaxHealth(); + // update.name = player->GetName(); + // OnPlayerUpdate(update); + + FirePythonEvent("player_move_3d", { + {"player_id", data.player_id}, + {"x", finalPos.x}, {"y", finalPos.y}, {"z", finalPos.z}, + {"session_id", data.session_id} + }); +} + +void GameLogic::OnPlayerUpdate(const PlayerUpdateData& data) { + auto player = GetPlayer(data.player_id); + if (!player) return; + if (sendPlayerUpdateCb_) { + PlayerUpdateData update; + update.timestamp = GetCurrentTimestamp(); + update.session_id = data.session_id; + update.player_id = data.player_id; + update.position = player->GetPosition(); + update.yaw = player->GetRotation().y; + update.health = player->GetHealth(); + update.max_health = player->GetMaxHealth(); + update.name = player->GetName(); + sendPlayerUpdateCb_(data.session_id, update); + } +} + +void GameLogic::OnPlayersUpdate(const PlayerUpdateData& data) { + auto player = GetPlayer(data.player_id); + if (!player) return; + if (sendPlayersUpdateCb_) { + PlayerUpdateData update; + update.timestamp = GetCurrentTimestamp(); + update.session_id = data.session_id; + update.player_id = data.player_id; + update.position = player->GetPosition(); + update.yaw = player->GetRotation().y; + update.health = player->GetHealth(); + update.max_health = player->GetMaxHealth(); + update.name = player->GetName(); + sendPlayersUpdateCb_(data.session_id, update); + } +} + void GameLogic::OnNPCInteraction(const NpcData& data) { NPCEntity* npc = GetNPCEntity(data.npc_id); if (!npc) { @@ -1256,6 +1324,26 @@ void GameLogic::GameLoop() { float deltaTime = deltaTimeMillis.count() / 1000.0f; lastUpdate = now; UpdateWorld(deltaTime); + auto dirtyPlayers = PlayerManager::GetInstance().GetDirtyPlayersAndClear(); + for (uint64_t playerId : dirtyPlayers) { + auto player = GetPlayer(playerId); + if (!player || !player->IsOnline()) continue; + PlayerUpdateData update; + update.timestamp = GetCurrentTimestamp(); + update.session_id = player->GetSessionId(); + update.player_id = playerId; + update.position = player->GetPosition(); + update.yaw = player->GetRotation().y; + update.health = player->GetHealth(); + update.max_health = player->GetMaxHealth(); + update.name = player->GetUsername(); + if (sendPlayerUpdateCb_) {// Send to self for correction (PLAYER_UPDATE) + sendPlayerUpdateCb_(player->GetSessionId(), update); + } + if (sendPlayersUpdateCb_) {// Send to others (PLAYERS_UPDATE) + sendPlayersUpdateCb_(player->GetSessionId(), update); + } + } LogicEntity::GetInstance().UpdateNPCs(deltaTime); LogicEntity::GetInstance().UpdateCollisions(deltaTime); ProcessGameTick(deltaTime); diff --git a/src/game/PlayerManager.cpp b/src/game/PlayerManager.cpp index 834e4c3..59cfedb 100644 --- a/src/game/PlayerManager.cpp +++ b/src/game/PlayerManager.cpp @@ -201,6 +201,26 @@ void PlayerManager::UpdateConnectionStats(int64_t playerId, bool connected) { } } +void PlayerManager::MarkDirty(uint64_t playerId) { + std::unique_lock lock(dirtyMutex_); + dirtyPlayers_.insert(playerId); +} + +std::vector PlayerManager::GetDirtyPlayersAndClear() { + std::unique_lock lock(dirtyMutex_); + std::vector result(dirtyPlayers_.begin(), dirtyPlayers_.end()); + dirtyPlayers_.clear(); + return result; +} + +void PlayerManager::UpdatePosition(uint64_t playerId, float x, float y, float z) { + auto player = GetPlayer(playerId); + if (player) { + player->UpdatePosition(x, y, z); + MarkDirty(playerId); + } +} + void PlayerManager::BroadcastToNearbyPlayers(int64_t playerId, const nlohmann::json& message) { auto nearby = GetNearbyPlayers(playerId, DEFAULT_BROADCAST_RANGE); auto& connMgr = ConnectionManager::GetInstance(); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index b40eb64..ff1029a 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -323,7 +323,7 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game posData.session_id = session->GetSessionId(); game_logic.OnPlayerState(posData); } - else if (msgType == "player_position_update") { + else if (msgType == "player_position") { PlayerPositionData posData; posData.player_id = game_logic.GetPlayerIdBySession(session->GetSessionId()); posData.position.x = msg.value("x", 0.0f); @@ -527,7 +527,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteUInt64(state.timestamp); auto nearbySessions = GetSessionsInRadius(state.position, 100.0f); for (auto& session : nearbySessions) { - session->Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, writer.GetBuffer()); + session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, writer.GetBuffer()); } }); game_logic.SetBroadcastPlayerPositionCallback([&](const PlayerPositionData& data, float radius) { @@ -541,6 +541,36 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, writer.GetBuffer()); } }); + game_logic.SetSendPlayerUpdateCallback([&](uint64_t session_id, const PlayerUpdateData& data) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(data.player_id); + writer.WriteVector3(data.position); + writer.WriteFloat(data.yaw); + writer.WriteFloat(data.health); + writer.WriteFloat(data.max_health); + writer.WriteString(data.name); + writer.WriteUInt64(data.timestamp); + auto session = ConnectionManager::GetInstance().GetSession(session_id); + if (session) { + session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE, writer.GetBuffer()); + } + }); + game_logic.SetSendPlayersUpdateCallback([&](uint64_t session_id, const PlayerUpdateData& data) { + BinaryProtocol::BinaryWriter writer; + writer.WriteUInt64(data.player_id); + writer.WriteVector3(data.position); + writer.WriteFloat(data.yaw); + writer.WriteFloat(data.health); + writer.WriteFloat(data.max_health); + writer.WriteString(data.name); + writer.WriteUInt64(data.timestamp); + auto nearbySessions = GetSessionsInRadius(data.position, 100.0f); + for (auto& session : nearbySessions) {// Don't send to the player who owns this update + if (session->GetSessionId() != session_id) { + session->Send(BinaryProtocol::MESSAGE_TYPE_PLAYERS_UPDATE, writer.GetBuffer()); + } + } + }); game_logic.SetSendNPCInteractionResponseCallback([&](uint64_t session_id, const NpcData& response) { BinaryProtocol::BinaryWriter writer; if (response.type == "combat") { @@ -654,7 +684,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }); game_logic.SetPlayerStateCallback([&](const PlayerStateData& state) { nlohmann::json jsonMsg = { - {"msg", "entity_update"}, + {"msg", "player_state"}, {"entity_id", state.player_id}, {"x", state.position.x}, {"y", state.position.y}, {"z", state.position.z}, {"rx", state.rotation.x}, {"ry", state.rotation.y}, {"rz", state.rotation.z}, @@ -679,6 +709,24 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ session->SendJson(jsonMsg); } }); + game_logic.SetSendPlayersUpdateCallback([&](uint64_t /*session_id*/, const PlayerUpdateData& data) { + nlohmann::json jsonMsg = { + {"msg", "players_update"}, + {"players", {{ + {"id", data.player_id}, + {"x", data.position.x}, {"y", data.position.y}, {"z", data.position.z}, + {"yaw", data.yaw}, + {"health", data.health}, + {"max_health", data.max_health}, + {"name", data.name} + }}}, + {"timestamp", data.timestamp} + }; + auto nearbySessions = GetSessionsInRadius(data.position, 100.0f); + for (auto& session : nearbySessions) { + session->SendJson(jsonMsg); + } + }); game_logic.SetSendNPCInteractionResponseCallback([&](uint64_t session_id, const NpcData& response) { nlohmann::json jsonMsg; if (response.type == "combat") { diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index ccb11b1..cff9b71 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -446,6 +446,59 @@ void WebSocketConnection::WriteHandshakeResponse(const HandshakeResponse& respon }); } +// void WebSocketConnection::ReadFrame() { +// if (state_ != State::OPEN && state_ != State::CLOSING) { +// return; +// } +// auto self = shared_from_this(); +// Logger::Trace("WebSocketConnection {} ReadFrame: starting async_read (2 bytes)", connection_id_); +// asio::async_read(socket_, read_buffer_, asio::transfer_exactly(2), +// [self](std::error_code ec, size_t bytes) { +// Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); +// if (ec) { +// self->HandleError(ec); +// return; +// } +// auto buffers = self->read_buffer_.data(); +// auto it = asio::buffers_begin(buffers); +// if (std::distance(it, asio::buffers_end(buffers)) < 2) { +// Logger::Error("Insufficient data for frame header"); +// throw std::runtime_error("Insufficient data for frame header"); +// } +// uint8_t first_byte = *it; +// uint8_t second_byte = *(++it); +// bool fin = (first_byte & 0x80) != 0; +// uint8_t opcode = first_byte & 0x0F; +// bool masked = (second_byte & 0x80) != 0; +// uint64_t payload_length = second_byte & 0x7F; +// size_t header_size = 2; +// if (payload_length == 126) { +// header_size += 2; +// } else if (payload_length == 127) { +// header_size += 8; +// } +// if (masked) { +// header_size += 4; +// } +// if (header_size > 2) { +// self->read_buffer_.consume(2); // Remove the 2 bytes we already read +// asio::async_read(self->socket_, self->read_buffer_, +// asio::transfer_exactly(header_size - 2), +// [self, fin, opcode, masked, payload_length, header_size] +// (std::error_code ec, size_t bytes) { +// Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); +// if (ec) { +// self->HandleError(ec); +// return; +// } +// self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); +// }); +// } else { +// self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); +// } +// }); +// } + void WebSocketConnection::ReadFrame() { if (state_ != State::OPEN && state_ != State::CLOSING) { return; @@ -453,50 +506,67 @@ void WebSocketConnection::ReadFrame() { auto self = shared_from_this(); Logger::Trace("WebSocketConnection {} ReadFrame: starting async_read (2 bytes)", connection_id_); asio::async_read(socket_, read_buffer_, asio::transfer_exactly(2), - [self](std::error_code ec, size_t bytes) { - Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); - if (ec) { - self->HandleError(ec); + [self](std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); + if (ec) { + if (ec == asio::error::operation_aborted) { + Logger::Trace("WebSocketConnection {} read aborted", self->connection_id_); return; } - auto buffers = self->read_buffer_.data(); - auto it = asio::buffers_begin(buffers); - if (std::distance(it, asio::buffers_end(buffers)) < 2) { - Logger::Error("Insufficient data for frame header"); - throw std::runtime_error("Insufficient data for frame header"); - } - uint8_t first_byte = *it; - uint8_t second_byte = *(++it); - bool fin = (first_byte & 0x80) != 0; - uint8_t opcode = first_byte & 0x0F; - bool masked = (second_byte & 0x80) != 0; - uint64_t payload_length = second_byte & 0x7F; - size_t header_size = 2; - if (payload_length == 126) { - header_size += 2; - } else if (payload_length == 127) { - header_size += 8; - } - if (masked) { - header_size += 4; - } - if (header_size > 2) { - self->read_buffer_.consume(2); // Remove the 2 bytes we already read - asio::async_read(self->socket_, self->read_buffer_, - asio::transfer_exactly(header_size - 2), - [self, fin, opcode, masked, payload_length, header_size] - (std::error_code ec, size_t bytes) { - Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); - if (ec) { - self->HandleError(ec); + self->HandleError(ec); + return; + } + if (self->state_ == State::CLOSED || self->state_ == State::CLOSING) { + return; + } + auto buffers = self->read_buffer_.data(); + auto it = asio::buffers_begin(buffers); + if (std::distance(it, asio::buffers_end(buffers)) < 2) { + Logger::Error("Insufficient data for frame header"); + self->Close(1002, "Protocol error"); + return; + } + uint8_t first_byte = *it; + uint8_t second_byte = *(++it); + bool fin = (first_byte & 0x80) != 0; + uint8_t opcode = first_byte & 0x0F; + bool masked = (second_byte & 0x80) != 0; + uint64_t payload_length = second_byte & 0x7F; + size_t header_size = 2; + if (payload_length == 126) { + header_size += 2; + } else if (payload_length == 127) { + header_size += 8; + } + if (masked) { + header_size += 4; + } + if (header_size > 2) { + self->read_buffer_.consume(2); + asio::async_read(self->socket_, self->read_buffer_, + asio::transfer_exactly(header_size - 2), + [self, fin, opcode, masked, payload_length, header_size] + (std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); + if (ec) { + if (ec == asio::error::operation_aborted) { return; } - self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); - }); - } else { - self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); + self->HandleError(ec); + return; + } + if (self->state_ == State::CLOSED || self->state_ == State::CLOSING) { + return; + } + self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); + }); + } else { + if (self->state_ == State::CLOSED || self->state_ == State::CLOSING) { + return; } - }); + self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); + } + }); } void WebSocketConnection::ReadFramePayload(bool fin, uint8_t opcode, bool masked, @@ -509,36 +579,83 @@ void WebSocketConnection::ReadFramePayload(bool fin, uint8_t opcode, bool masked (std::error_code ec, size_t bytes) { Logger::Trace("WebSocketConnection {} ReadFramePayload completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); if (ec) { + if (ec == asio::error::operation_aborted) { + return; + } self->HandleError(ec); return; } + if (self->state_ == State::CLOSED || self->state_ == State::CLOSING) { + return; + } self->ProcessFrameData(fin, opcode, masked, payload_length, header_size); }); } else { + if (state_ == State::CLOSED || state_ == State::CLOSING) { + return; + } ProcessFrameData(fin, opcode, masked, payload_length, header_size); } } +// void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked, +// uint64_t payload_length, size_t header_size) { +// (void)fin; +// (void)opcode; +// (void)masked; +// size_t total_frame_size = header_size + payload_length; +// constexpr size_t MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16MB limit +// if (total_frame_size > MAX_FRAME_SIZE) { +// throw std::runtime_error("Frame too large"); +// } +// std::vector frame_data(total_frame_size); +// auto buffers = read_buffer_.data(); +// Logger::Trace("WebSocketConnection {} ProcessFrameData: starting buffer_copy", connection_id_); +// size_t bytes_copied = asio::buffer_copy( +// asio::buffer(frame_data), // destination +// buffers, // source buffer sequence +// total_frame_size // maximum bytes to copy +// ); +// if (bytes_copied != total_frame_size) { +// throw std::runtime_error("Incomplete frame data in buffer"); +// } +// read_buffer_.consume(bytes_copied); +// try { +// WebSocketFrame frame = WebSocketFrame::Deserialize(frame_data); +// HandleFrame(frame); +// } catch (const std::exception& e) { +// Logger::Error("WebSocket frame parsing error: {}", e.what()); +// Close(1002, "Protocol error"); +// return; +// } +// if (state_ == State::OPEN || state_ == State::CLOSING) { +// ReadFrame(); +// } +// Logger::Trace("WebSocketConnection {} ProcessFrameData: complete", connection_id_); +// } + void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked, uint64_t payload_length, size_t header_size) { (void)fin; (void)opcode; (void)masked; size_t total_frame_size = header_size + payload_length; - constexpr size_t MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16MB limit + constexpr size_t MAX_FRAME_SIZE = 16 * 1024 * 1024; if (total_frame_size > MAX_FRAME_SIZE) { - throw std::runtime_error("Frame too large"); + Close(1009, "Frame too large"); + return; } std::vector frame_data(total_frame_size); auto buffers = read_buffer_.data(); Logger::Trace("WebSocketConnection {} ProcessFrameData: starting buffer_copy", connection_id_); size_t bytes_copied = asio::buffer_copy( - asio::buffer(frame_data), // destination - buffers, // source buffer sequence - total_frame_size // maximum bytes to copy + asio::buffer(frame_data), + buffers, + total_frame_size ); if (bytes_copied != total_frame_size) { - throw std::runtime_error("Incomplete frame data in buffer"); + Close(1002, "Protocol error"); + return; } read_buffer_.consume(bytes_copied); try { @@ -739,7 +856,39 @@ void WebSocketConnection::SendPong(const std::vector& data) { SendFrameAsync(frame); } -// ATTENTION: THIS VERSION NOT WORKED +void WebSocketConnection::Close(uint16_t code, const std::string& reason) { + if (state_ == State::CLOSED) { + Logger::Trace("WebSocketConnection {} already closed", connection_id_); + return; + } + if (state_ == State::CLOSING) { + Logger::Trace("WebSocketConnection {} already closing", connection_id_); + return; + } + Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); + state_ = State::CLOSING; + std::error_code ec; + if (socket_.is_open()) { + socket_.cancel(ec); + if (ec) { + Logger::Debug("WebSocketConnection {} cancel error: {}", connection_id_, ec.message()); + } + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + if (ec && ec != asio::error::not_connected) { + Logger::Debug("WebSocketConnection {} shutdown error: {}", connection_id_, ec.message()); + } + socket_.close(ec); + if (ec && ec != asio::error::not_connected) { + Logger::Debug("WebSocketConnection {} close error: {}", connection_id_, ec.message()); + } + } + state_ = State::CLOSED; + if (close_handler_) { + close_handler_(code, reason.empty() ? "Connection closed" : reason); + } + Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); +} + // void WebSocketConnection::Close(uint16_t code, const std::string& reason) { // Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); // if (state_ == State::CLOSED || state_ == State::CLOSING) { @@ -750,58 +899,35 @@ void WebSocketConnection::SendPong(const std::vector& data) { // return; // } // state_ = State::CLOSING; -// WebSocketFrame close_frame = WebSocketFrame::CreateCloseFrame(code, reason); -// SendFrameAsync(close_frame); -// auto self = shared_from_this(); -// asio::post(socket_.get_executor(), [self]() { -// std::error_code ec; -// self->socket_.cancel(ec); -// Logger::Trace("WebSocketConnection {} cancel result: {}", self->connection_id_, ec.message()); -// self->socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); -// Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); -// self->socket_.close(ec); -// self->state_ = State::CLOSED; -// if (self->close_handler_) { -// self->close_handler_(1000, "Normal closure"); -// } -// Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); -// }); -// Logger::Trace("WebSocketConnection {} Close: complete", connection_id_); +// std::error_code ec; +// socket_.cancel(ec); +// Logger::Trace("WebSocketConnection {} cancel result: {}", connection_id_, ec.message()); +// socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); +// Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); +// socket_.close(ec); +// Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); +// if (close_handler_) { +// close_handler_(code, reason.empty() ? "Connection closed" : reason); +// } +// Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); // } -void WebSocketConnection::Close(uint16_t code, const std::string& reason) { - Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); +void WebSocketConnection::HandleError(const std::error_code& ec) { if (state_ == State::CLOSED || state_ == State::CLOSING) { - if (state_ == State::CLOSED) - Logger::Warn("WebSocketConnection now is closed"); - else - Logger::Warn("WebSocketConnection now is in process closing"); return; } - state_ = State::CLOSING; - std::error_code ec; - socket_.cancel(ec); - Logger::Trace("WebSocketConnection {} cancel result: {}", connection_id_, ec.message()); - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); - Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); - socket_.close(ec); - Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); - if (close_handler_) { - close_handler_(code, reason.empty() ? "Connection closed" : reason); - } - Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); -} - -void WebSocketConnection::HandleError(const std::error_code& ec) { - if (ec == asio::error::eof || ec == asio::error::connection_reset) { - Logger::Trace("WebSocketConnection {} disconnected", connection_id_); + if (ec == asio::error::eof || ec == asio::error::connection_reset || ec == asio::error::broken_pipe) { + Logger::Trace("WebSocketConnection {} disconnected by client", connection_id_); + } else if (ec == asio::error::operation_aborted) { + Logger::Debug("WebSocketConnection {} operation aborted", connection_id_); + } else if (ec == asio::error::bad_descriptor) { + Logger::Debug("WebSocketConnection {} bad descriptor - already closed", connection_id_); } else { Logger::Error("WebSocketConnection {} error: {}", connection_id_, ec.message()); } - if (error_handler_) { - error_handler_(ec); + if (state_ == State::OPEN) { + Close(1006, "Connection error"); } - Close(1006, "Connection error"); } void WebSocketConnection::HandleClose(uint16_t code, const std::string& reason) { From 82e5aa22549daf7974632aa8662e39dda53d49b2 Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 24 Apr 2026 07:10:41 +0300 Subject: [PATCH 13/14] refactor chunk data format --- include/game/CollisionSystem.hpp | 10 +- include/game/GameData.hpp | 10 +- include/game/GameLogic.hpp | 20 +-- include/game/WorldChunk.hpp | 9 +- src/game/ChunkCache.cpp | 4 +- src/game/CollisionSystem.cpp | 32 ++-- src/game/GameLogic.cpp | 250 ++++-------------------------- src/game/LogicWorld.cpp | 2 +- src/game/WorldChunk.cpp | 56 +++++-- src/network/BinarySession.cpp | 4 +- src/network/GameServer.cpp | 59 ++++--- src/network/WebSocketProtocol.cpp | 145 ----------------- src/network/WebSocketSession.cpp | 44 ++++-- 13 files changed, 177 insertions(+), 468 deletions(-) diff --git a/include/game/CollisionSystem.hpp b/include/game/CollisionSystem.hpp index 8cbad33..63519b8 100644 --- a/include/game/CollisionSystem.hpp +++ b/include/game/CollisionSystem.hpp @@ -25,8 +25,8 @@ struct RaycastHit { glm::vec3 point; glm::vec3 normal; float distance = 0.0f; - uint64_t entityId = 0; - uint64_t chunkId = 0; + uint64_t entity_id = 0; + uint64_t chunk_id = 0; }; // Define CollisionType enum here since it's specific to CollisionSystem @@ -41,8 +41,8 @@ struct CollisionResult { bool collided = false; glm::vec3 resolution = glm::vec3(0.0f, 0.0f, 0.0f); float penetration = 0.0f; - uint64_t collidedWith = 0; // entityId or 0 for world - uint64_t chunkId = 0; // chunkId for world collisions + uint64_t collided_id = 0; // collided with entityId or 0 for world + uint64_t chunk_id = 0; // chunkId for world collisions CollisionType type = CollisionType::NONE; }; @@ -123,7 +123,7 @@ class CollisionSystem { struct CollisionChunk { int chunkX = 0; int chunkZ = 0; - uint64_t chunkId = 0; + uint64_t chunk_id = 0; BoundingBox bounds; std::vector vertices; std::vector> triangles; // Triangle indices diff --git a/include/game/GameData.hpp b/include/game/GameData.hpp index be19045..4bd3883 100644 --- a/include/game/GameData.hpp +++ b/include/game/GameData.hpp @@ -127,10 +127,13 @@ struct PlayerUpdateData { struct ChunkData { uint64_t timestamp; uint64_t session_id; - int chunk_x; - int chunk_z; + int x; + int z; uint8_t lod; - nlohmann::json chunk_json; + int size; + float spacing; + std::vector vertices; + std::vector indices; }; struct CollisionData { @@ -157,7 +160,6 @@ struct FamiliarData { uint64_t session_id; uint64_t familiar_id; uint64_t target_id; - bool success; std::string command; }; diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index 385a291..2a91b79 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -59,27 +59,13 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this& data, float radius = 50.0f); - // void BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, - // const std::vector& data, float radius = 50.0f); void SyncNearbyEntitiesToPlayer(uint64_t sessionId, const glm::vec3& position); - // void BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius); void BroadcastToAllPlayers(const nlohmann::json& message); - // void BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data); - // void BroadcastToPlayers(const std::vector& sessionIds, const nlohmann::json& message); - // void BroadcastPlayerSpawn(uint64_t playerId); - // void BroadcastPlayerDespawn(uint64_t playerId, const glm::vec3& lastPosition); - // void BroadcastPlayerSpawnJson(uint64_t playerId); - // void BroadcastPlayerDespawnJson(uint64_t playerId, const glm::vec3& lastPosition); - // void BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, - // float yaw, const std::string& name); void SendPositionCorrection(uint64_t sessionId, const glm::vec3& position, const glm::vec3& velocity); - // void BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position); - void SendAuthenticationSuccess(uint64_t sessionId, uint64_t playerId, const std::string& message); + void SendAuthentication(uint64_t sessionId, const std::string& message, uint64_t playerId); void SendAuthenticationFailure(uint64_t sessionId, const std::string& message); - void SetSendAuthenticationResponseCallback(std::function cb); + void SetSendAuthenticationResponseCallback(std::function cb); void SetSendChunkCallback(std::function cb); void SetSendCollisionResponseCallback(std::function cb); @@ -144,7 +130,7 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this sendAuthResponseCb_; + std::function sendAuthResponseCb_; std::function sendChunkCb_; std::function sendCollisionResponseCb_; diff --git a/include/game/WorldChunk.hpp b/include/game/WorldChunk.hpp index 15049a9..0f56680 100644 --- a/include/game/WorldChunk.hpp +++ b/include/game/WorldChunk.hpp @@ -9,6 +9,8 @@ #include #include +#include "network/BinaryProtocol.hpp" + struct LODConfig; class LODChunk; @@ -61,9 +63,10 @@ struct Triangle { class WorldChunk { public: - static const int CHUNK_SIZE = 16; // 16x16 blocks + static const int CHUNK_SIZE = 32; // 32x32 blocks static const float BLOCK_SIZE; static const float CHUNK_WIDTH; + static constexpr float DEFAULT_SPACING = 1.0f; WorldChunk(int x, int z, ChunkLOD lod = ChunkLOD::HIGH); virtual ~WorldChunk() = default; @@ -97,7 +100,9 @@ class WorldChunk { bool HasEntities() const { return !entities_.empty(); } // Serialization - virtual nlohmann::json Serialize() const; + void SerializeToWriter(BinaryProtocol::BinaryWriter& writer) const; + std::vector SerializeBinary() const; + nlohmann::json SerializeJson() const; virtual void Deserialize(const nlohmann::json& data); virtual nlohmann::json SerializeHeightmap() const; diff --git a/src/game/ChunkCache.cpp b/src/game/ChunkCache.cpp index caddd3e..edade7e 100644 --- a/src/game/ChunkCache.cpp +++ b/src/game/ChunkCache.cpp @@ -959,10 +959,8 @@ std::vector ChunkCache::DecompressData(const std::vector& comp } std::vector ChunkCache::SerializeChunk(const WorldChunk& chunk) const { - // Simple serialization to JSON then to binary - nlohmann::json json_data = chunk.Serialize(); + nlohmann::json json_data = chunk.SerializeJson(); std::string json_str = json_data.dump(); - std::vector data(json_str.begin(), json_str.end()); return data; } diff --git a/src/game/CollisionSystem.cpp b/src/game/CollisionSystem.cpp index b43bbd6..21ce362 100644 --- a/src/game/CollisionSystem.cpp +++ b/src/game/CollisionSystem.cpp @@ -214,10 +214,10 @@ bool CollisionSystem::IsEntityRegistered(uint64_t entityId) const { void CollisionSystem::RegisterChunk(const WorldChunk& chunk) { std::lock_guard lock(mutex_); - uint64_t chunkId = CalculateChunkId(chunk.GetChunkX(), chunk.GetChunkZ()); + uint64_t chunk_id = CalculateChunkId(chunk.GetChunkX(), chunk.GetChunkZ()); // Check if chunk already exists - if (chunks_.find(chunkId) != chunks_.end()) { + if (chunks_.find(chunk_id) != chunks_.end()) { return; } @@ -225,7 +225,7 @@ void CollisionSystem::RegisterChunk(const WorldChunk& chunk) { CollisionChunk collisionChunk; collisionChunk.chunkX = chunk.GetChunkX(); collisionChunk.chunkZ = chunk.GetChunkZ(); - collisionChunk.chunkId = chunkId; + collisionChunk.chunk_id = chunk_id; // Calculate bounding box for the chunk glm::vec3 worldPos = chunk.GetWorldPosition(); @@ -240,14 +240,14 @@ void CollisionSystem::RegisterChunk(const WorldChunk& chunk) { BuildChunkCollisionData(collisionChunk, chunk); // Store the chunk - chunks_[chunkId] = std::move(collisionChunk); + chunks_[chunk_id] = std::move(collisionChunk); } void CollisionSystem::UnregisterChunk(int chunkX, int chunkZ) { std::lock_guard lock(mutex_); - uint64_t chunkId = CalculateChunkId(chunkX, chunkZ); - chunks_.erase(chunkId); + uint64_t chunk_id = CalculateChunkId(chunkX, chunkZ); + chunks_.erase(chunk_id); } void CollisionSystem::ClearAllChunks() { @@ -275,14 +275,14 @@ CollisionResult CollisionSystem::CheckCollision(const glm::vec3& position, float if (TestSphereSphere(testSphere, entity.bounds, result)) { result.collided = true; - result.collidedWith = entityId; + result.collided_id = entityId; result.type = entity.type; return result; } } // Check against world chunks - for (const auto& [chunkId, chunk] : chunks_) { + for (const auto& [chunk_id, chunk] : chunks_) { // Early out with bounding box test if (!chunk.bounds.IntersectsSphere(position, radius)) { continue; @@ -291,9 +291,9 @@ CollisionResult CollisionSystem::CheckCollision(const glm::vec3& position, float // Test against chunk bounding box first if (TestSphereBox(testSphere, chunk.bounds, result)) { result.collided = true; - result.collidedWith = 0; // World collision + result.collided_id = 0; // World collision result.type = CollisionType::WORLD; - result.chunkId = chunkId; + result.chunk_id = chunk_id; // If chunk has detailed collision data, test against triangles if (chunk.hasCollisionData) { @@ -314,9 +314,9 @@ CollisionResult CollisionSystem::CheckCollision(const glm::vec3& position, float if (!detailedCollision || detailedResult.penetration < result.penetration) { detailedCollision = true; result = detailedResult; - result.collidedWith = 0; + result.collided_id = 0; result.type = CollisionType::WORLD; - result.chunkId = chunkId; + result.chunk_id = chunk_id; } } } @@ -354,14 +354,14 @@ bool CollisionSystem::Raycast(const glm::vec3& origin, const glm::vec3& directio hit.point = origin + normalizedDir * distance; hit.normal = glm::normalize(hit.point - entity.bounds.center); hit.distance = distance; - hit.entityId = entityId; + hit.entity_id = entityId; closestDistance = distance; foundHit = true; } } // Check against world chunks - for (const auto& [chunkId, chunk] : chunks_) { + for (const auto& [chunk_id, chunk] : chunks_) { float tMin, tMax; if (!TestRayAABB(origin, normalizedDir, chunk.bounds, tMin, tMax)) { continue; @@ -405,7 +405,7 @@ bool CollisionSystem::Raycast(const glm::vec3& origin, const glm::vec3& directio hit.point = origin + normalizedDir * chunkDistance; hit.normal = chunkNormal; hit.distance = chunkDistance; - hit.chunkId = chunkId; + hit.chunk_id = chunk_id; closestDistance = chunkDistance; foundHit = true; } @@ -414,7 +414,7 @@ bool CollisionSystem::Raycast(const glm::vec3& origin, const glm::vec3& directio hit.hit = true; hit.point = origin + normalizedDir * tMin; hit.distance = tMin; - hit.chunkId = chunkId; + hit.chunk_id = chunk_id; // Calculate normal from bounding box glm::vec3 center = chunk.bounds.GetCenter(); diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index b6156a1..1393011 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -220,84 +220,6 @@ void GameLogic::OnCollisionCheck(const CollisionData& data) { } } -// void GameLogic::BroadcastToNearbyPlayers(const glm::vec3& position, uint16_t messageType, -// const std::vector& data, float radius) { -// if (!connectionManager_) return; -// auto& pm = PlayerManager::GetInstance(); -// auto nearby = pm.GetPlayersInRadius(position, radius); -// for (auto& player : nearby) { -// uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); -// if (session_id == 0) continue; -// auto session = connectionManager_->GetSession(session_id); -// if (!session || !session->IsConnected()) continue; -// if (session->GetProtocolMode() == ProtocolMode::Binary) { -// session->Send(messageType, data); -// continue; -// } -// nlohmann::json jsonMsg; -// try { -// BinaryProtocol::BinaryReader reader(data.data(), data.size()); -// switch (messageType) { -// case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: -// jsonMsg = PlayerPositionToJson(data); -// break; -// case BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE: -// jsonMsg = PlayerUpdateToJson(data); -// break; -// case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: -// jsonMsg = EntitySpawnToJson(data); -// break; -// case BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE: -// jsonMsg = EntityUpdateToJson(data); -// break; -// case BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN: -// jsonMsg = EntityDespawnToJson(data); -// break; -// case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: -// jsonMsg = ChunkDataToJson(data); -// break; -// default: -// Logger::Warn("No JSON conversion for message type {}", messageType); -// continue; -// } -// } catch (const std::exception& e) { -// Logger::Error("BroadcastToNearbyPlayers: Failed to convert binary to JSON: {}", e.what()); -// continue; -// } -// session->SendJson(jsonMsg); -// } -// } - -// void GameLogic::BroadcastToNearbyOnlinePlayers(const glm::vec3& position, uint16_t messageType, -// const std::vector& data, float radius) { -// if (!connectionManager_) return; -// auto& pm = PlayerManager::GetInstance(); -// auto onlinePlayers = pm.GetOnlinePlayers(); -// for (const auto& player : onlinePlayers) { -// if (glm::distance(player->GetPosition(), position) <= radius) { -// uint64_t session_id = GetSessionIdByPlayer(player->GetId()); -// if (session_id != 0) { -// auto session = connectionManager_->GetSession(session_id); -// if (session && session->IsConnected()) { -// session->Send(messageType, data); -// } -// } -// } -// } -// } - -// void GameLogic::BroadcastEntitySpawn(uint64_t entityId, EntityType type, const glm::vec3& position, -// float yaw, const std::string& name) { -// BinaryProtocol::BinaryWriter writer; -// writer.WriteUInt64(entityId); -// writer.WriteUInt8(static_cast(type)); -// writer.WriteString(name); -// writer.WriteVector3(position); -// writer.WriteFloat(yaw); -// writer.WriteUInt64(GetCurrentTimestamp()); -// BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, writer.GetBuffer(), 100.0f); -// } - void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& position) { auto nearbyEntities = EntityManager::GetInstance().GetEntitiesInRadius(position, 100.0f); nlohmann::json entityList = nlohmann::json::array(); @@ -315,82 +237,9 @@ void GameLogic::SyncNearbyEntitiesToPlayer(uint64_t session_id, const glm::vec3& SendToSessionJson(session_id, message); } -// void GameLogic::BroadcastPlayerSpawn(uint64_t player_id) { -// auto player = GetPlayer(player_id); -// if (!player) return; -// BinaryProtocol::BinaryWriter writer; -// writer.WriteUInt64(player_id); -// writer.WriteString(player->GetName()); -// writer.WriteVector3(player->GetPosition()); -// writer.WriteFloat(player->GetRotation().y); -// writer.WriteFloat(player->GetHealth()); -// writer.WriteFloat(player->GetMaxHealth()); -// BroadcastToNearbyPlayers(player->GetPosition(), -// BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, -// writer.GetBuffer(), -// ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -// } - -// void GameLogic::BroadcastPlayerDespawn(uint64_t player_id, const glm::vec3& lastPosition) { -// BinaryProtocol::BinaryWriter writer; -// writer.WriteUInt64(player_id); -// BroadcastToNearbyPlayers(lastPosition, -// BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, -// writer.GetBuffer(), -// ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -// } - -// void GameLogic::BroadcastToNearbyPlayersJson(const glm::vec3& position, const nlohmann::json& message, float radius) { -// if (!connectionManager_) return; -// auto& pm = PlayerManager::GetInstance(); -// auto nearby = pm.GetPlayersInRadius(position, radius); -// for (auto& player : nearby) { -// uint64_t session_id = pm.GetSessionIdByPlayerId(player->GetId()); -// if (session_id != 0) { -// auto session = connectionManager_->GetSession(session_id); -// if (session && session->IsConnected()) { -// session->SendJson(message); -// } -// } -// } -// } - -// void GameLogic::BroadcastPlayerSpawnJson(uint64_t player_id) { -// auto player = GetPlayer(player_id); -// if (!player) return; -// nlohmann::json msg = { -// {"type", "player_spawn"}, -// {"player_id", player_id}, -// {"name", player->GetName()}, -// {"position", {player->GetPosition().x, player->GetPosition().y, player->GetPosition().z}}, -// {"yaw", player->GetRotation().y}, -// {"health", player->GetHealth()}, -// {"max_health", player->GetMaxHealth()}, -// {"timestamp", GetCurrentTimestamp()} -// }; -// BroadcastToNearbyPlayersJson(player->GetPosition(), msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -// } - -// void GameLogic::BroadcastPlayerDespawnJson(uint64_t player_id, const glm::vec3& lastPosition) { -// nlohmann::json msg = { -// {"type", "player_despawn"}, -// {"player_id", player_id}, -// {"timestamp", GetCurrentTimestamp()} -// }; -// BroadcastToNearbyPlayersJson(lastPosition, msg, ConfigManager::GetInstance().GetFloat("world.interest_radius", 100.0f)); -// } - -// void GameLogic::BroadcastEntityDespawn(uint64_t entityId, const glm::vec3& position) { -// BinaryProtocol::BinaryWriter writer; -// writer.WriteUInt64(entityId); -// writer.WriteUInt64(GetCurrentTimestamp()); -// BroadcastToNearbyPlayers(position, BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer(), 100.0f); -// } - -void GameLogic::SendAuthenticationSuccess(uint64_t session_id, uint64_t player_id, const std::string& message) { +void GameLogic::SendAuthentication(uint64_t session_id, const std::string& message, uint64_t player_id) { nlohmann::json response = { {"msg", "authentication"}, - {"success", true}, {"player_id", player_id}, {"desc", message}, {"timestamp", GetCurrentTimestamp()} @@ -399,13 +248,7 @@ void GameLogic::SendAuthenticationSuccess(uint64_t session_id, uint64_t player_i } void GameLogic::SendAuthenticationFailure(uint64_t session_id, const std::string& message) { - nlohmann::json response = { - {"msg", "authentication"}, - {"success", false}, - {"desc", message}, - {"timestamp", GetCurrentTimestamp()} - }; - SendToSessionJson(session_id, response); + SendAuthentication(session_id, message, 0); } void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { @@ -434,59 +277,6 @@ void GameLogic::BroadcastToAllPlayers(const nlohmann::json& message) { } } -// void GameLogic::BroadcastToAllPlayersBinary(uint16_t messageType, const std::vector& data) { -// if (!connectionManager_) { -// Logger::Warn("Cannot broadcast binary: ConnectionManager not available"); -// return; -// } -// try { -// auto sessions = connectionManager_->GetAllSessions(); -// if (sessions.empty()) return; -// Logger::Debug("Broadcasting binary message type {} to {} player(s)", -// messageType, sessions.size()); -// for (auto& session : sessions) { -// if (session && session->IsConnected()) { -// try { -// session->Send(messageType, data); -// } catch (const std::exception& e) { -// Logger::Error("Failed to send binary broadcast to session {}: {}", -// session->GetSessionId(), e.what()); -// } -// } -// } -// } catch (const std::exception& e) { -// Logger::Error("Error broadcasting binary to all players: {}", e.what()); -// } -// } - -// void GameLogic::BroadcastToPlayers(const std::vector& session_ids, const nlohmann::json& message) { -// if (!connectionManager_) { -// Logger::Warn("Cannot broadcast: ConnectionManager not available"); -// return; -// } -// try { -// std::string serialized = message.dump(); -// int sentCount = 0; -// for (uint64_t session_id : session_ids) { -// auto session = connectionManager_->GetSession(session_id); -// if (session && session->IsConnected()) { -// try { -// session->SendRaw(serialized); -// sentCount++; -// } catch (const std::exception& e) { -// Logger::Error("Failed to send message to session {}: {}", -// session_id, e.what()); -// } -// } -// } -// if (sentCount > 0) { -// Logger::Debug("Broadcasted to {} specific player(s)", sentCount); -// } -// } catch (const std::exception& err) { -// Logger::Error("Error broadcasting to specific players: {}", err.what()); -// } -// } - void GameLogic::PerformMaintenance() { CleanupOldData(); SaveChunkData(); @@ -626,7 +416,7 @@ nlohmann::json GameLogic::ChunkDataToJson(const std::vector& data) { }; } -void GameLogic::SetSendAuthenticationResponseCallback(std::function cb) { +void GameLogic::SetSendAuthenticationResponseCallback(std::function cb) { sendAuthResponseCb_ = std::move(cb); } @@ -705,24 +495,42 @@ void GameLogic::OnAuthentication(const AuthenticationData& data) { } } if (sendAuthResponseCb_) { - sendAuthResponseCb_(data.session_id, authenticated, message, player_id); + sendAuthResponseCb_(data.session_id, message, player_id); } else { Logger::Error("No sendAuthResponseCb_ set in GameLogic"); } } void GameLogic::OnChunkRequest(const ChunkData& req) { - auto chunk = GetOrCreateChunk(req.chunk_x, req.chunk_z); + auto chunk = GetOrCreateChunk(req.x, req.z); if (!chunk) { - Logger::Error("Failed to get chunk ({},{}) for session {}", req.chunk_x, req.chunk_z, req.session_id); + Logger::Error("Failed to get chunk ({},{}) for session {}", req.x, req.z, req.session_id); return; } ChunkData resp; - resp.chunk_x = req.chunk_x; - resp.chunk_z = req.chunk_z; + resp.x = req.x; + resp.z = req.z; resp.lod = req.lod; - resp.chunk_json = chunk->Serialize(); + resp.size = WorldChunk::CHUNK_SIZE; + resp.spacing = WorldChunk::DEFAULT_SPACING; resp.timestamp = GetCurrentTimestamp(); + const auto& verts = chunk->GetVertices(); + resp.vertices.reserve(verts.size() * 6); + for (const auto& v : verts) { + resp.vertices.push_back(v.position.x); + resp.vertices.push_back(v.position.y); + resp.vertices.push_back(v.position.z); + resp.vertices.push_back(v.normal.x); + resp.vertices.push_back(v.normal.y); + resp.vertices.push_back(v.normal.z); + } + const auto& tris = chunk->GetTriangles(); + resp.indices.reserve(tris.size() * 3); + for (const auto& tri : tris) { + resp.indices.push_back(tri.v0); + resp.indices.push_back(tri.v1); + resp.indices.push_back(tri.v2); + } if (sendChunkCb_) { sendChunkCb_(req.session_id, resp); } else { @@ -920,7 +728,6 @@ void GameLogic::OnFamiliarCommand(const FamiliarData& data) { NPCEntity* familiar = GetNPCEntity(data.familiar_id); if (!familiar) { FamiliarData error; - error.success = false; error.session_id = data.session_id; if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); return; @@ -928,7 +735,6 @@ void GameLogic::OnFamiliarCommand(const FamiliarData& data) { uint64_t player_id = GetPlayerIdBySession(data.session_id); if (familiar->GetOwnerId() != player_id) { FamiliarData error; - error.success = false; error.session_id = data.session_id; if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); return; @@ -944,13 +750,11 @@ void GameLogic::OnFamiliarCommand(const FamiliarData& data) { familiar->SetTarget(0); } else { FamiliarData error; - error.success = false; error.session_id = data.session_id; if (sendFamiliarCommandResponseCb_) sendFamiliarCommandResponseCb_(data.session_id, error); return; } FamiliarData response; - response.success = true; response.session_id = data.session_id; response.familiar_id = data.familiar_id; response.target_id = data.target_id; diff --git a/src/game/LogicWorld.cpp b/src/game/LogicWorld.cpp index a9bb315..f4c83a3 100644 --- a/src/game/LogicWorld.cpp +++ b/src/game/LogicWorld.cpp @@ -159,7 +159,7 @@ void LogicWorld::SaveChunkData() { } for (const auto& [key, chunk] : loadedChunks_) { try { - nlohmann::json chunkData = chunk->Serialize(); + nlohmann::json chunkData = chunk->SerializeJson(); backend->SaveChunkData(chunk->GetChunkX(), chunk->GetChunkZ(), chunkData); } catch (const std::exception& e) { Logger::Error("Failed to save chunk [{}, {}]: {}", diff --git a/src/game/WorldChunk.cpp b/src/game/WorldChunk.cpp index 57bbe6d..512871f 100644 --- a/src/game/WorldChunk.cpp +++ b/src/game/WorldChunk.cpp @@ -200,17 +200,55 @@ bool WorldChunk::IsPositionInside(const glm::vec3& position) const { position.z >= chunkPos.z && position.z < chunkPos.z + CHUNK_WIDTH; } -nlohmann::json WorldChunk::Serialize() const { - nlohmann::json data; +void WorldChunk::SerializeToWriter(BinaryProtocol::BinaryWriter& writer) const +{ + writer.WriteInt32(chunkX_); + writer.WriteInt32(chunkZ_); + std::vector vertexFloats; + vertexFloats.reserve(vertices_.size() * 6); + for (const auto& v : vertices_) + { + vertexFloats.push_back(v.position.x); + vertexFloats.push_back(v.position.y); + vertexFloats.push_back(v.position.z); + vertexFloats.push_back(v.normal.x); + vertexFloats.push_back(v.normal.y); + vertexFloats.push_back(v.normal.z); + } + uint32_t vertexDataSize = static_cast(vertexFloats.size() * sizeof(float)); + writer.WriteUInt32(vertexDataSize); + writer.WriteBytes(reinterpret_cast(vertexFloats.data()), vertexDataSize); + std::vector indexInts; + indexInts.reserve(triangles_.size() * 3); + for (const auto& tri : triangles_) + { + indexInts.push_back(tri.v0); + indexInts.push_back(tri.v1); + indexInts.push_back(tri.v2); + } + uint32_t indexDataSize = static_cast(indexInts.size() * sizeof(uint32_t)); + writer.WriteUInt32(indexDataSize); + writer.WriteBytes(reinterpret_cast(indexInts.data()), indexDataSize); + writer.WriteUInt32(0); +} + +std::vector WorldChunk::SerializeBinary() const +{ + BinaryProtocol::BinaryWriter writer; + SerializeToWriter(writer); + return writer.GetBuffer(); +} +nlohmann::json WorldChunk::SerializeJson() const +{ + nlohmann::json data; data["chunkX"] = chunkX_; data["chunkZ"] = chunkZ_; data["lod"] = static_cast(lod_); data["biome"] = static_cast(biome_); - - // Serialize vertices (position + normal as flat float array) nlohmann::json verticesArray = nlohmann::json::array(); - for (const auto& v : vertices_) { + for (const auto& v : vertices_) + { verticesArray.push_back(v.position.x); verticesArray.push_back(v.position.y); verticesArray.push_back(v.position.z); @@ -219,16 +257,16 @@ nlohmann::json WorldChunk::Serialize() const { verticesArray.push_back(v.normal.z); } data["vertices"] = verticesArray; - - // Serialize indices (using v0, v1, v2) nlohmann::json indicesArray = nlohmann::json::array(); - for (const auto& tri : triangles_) { + for (const auto& tri : triangles_) + { indicesArray.push_back(tri.v0); indicesArray.push_back(tri.v1); indicesArray.push_back(tri.v2); } data["indices"] = indicesArray; - + std::vector binaryData = SerializeBinary(); + data["binary_data"] = binaryData; return data; } diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index a583582..4584ca7 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -336,8 +336,8 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { BinaryProtocol::BinaryReader reader(message.data.data(), message.data.size()); ChunkData req; - req.chunk_x = reader.ReadInt32(); - req.chunk_z = reader.ReadInt32(); + req.x = reader.ReadInt32(); + req.z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); req.session_id = sessionId_; GameLogic::GetInstance().OnChunkRequest(req); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index ff1029a..4d199f7 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -173,8 +173,8 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { BinaryProtocol::BinaryReader reader(data.data(), data.size()); ChunkData req; - req.chunk_x = reader.ReadInt32(); - req.chunk_z = reader.ReadInt32(); + req.x = reader.ReadInt32(); + req.z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); req.session_id = session->GetSessionId(); game_logic.OnChunkRequest(req); @@ -288,7 +288,10 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game session->SetMessageHandler([session, workerId, processPool, &game_logic] (const nlohmann::json& msg) mutable{ std::string msgType = msg.value("msg", ""); - if (msgType == "authentication") { + if (msgType == "protocol_negotiation") { + return; + } + else if (msgType == "authentication") { AuthenticationData authData; authData.username = msg.value("login", ""); authData.password = msg.value("password", ""); @@ -297,8 +300,8 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game } else if (msgType == "get_chunk") { ChunkData req; - req.chunk_x = msg.value("x", 0); - req.chunk_z = msg.value("z", 0); + req.x = msg.value("x", 0); + req.z = msg.value("z", 0); req.lod = static_cast(msg.value("lod", 0)); req.session_id = session->GetSessionId(); game_logic.OnChunkRequest(req); @@ -482,10 +485,13 @@ std::vector> GameServer::GetSessionsInRadius(const } void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_logic) { + // ATTENTION + // In any case, the client checks the response values. + // Which means the client himself determines success or failure. + // Most answers don't need an overloaded, useless "success" flag. if (protocol == "binary") { - game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, bool success, const std::string& message, uint64_t player_id) { + game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, const std::string& message, uint64_t player_id) { BinaryProtocol::BinaryWriter writer; - writer.WriteUInt8(success ? 1 : 0); writer.WriteUInt64(player_id); writer.WriteString(message); auto session = ConnectionManager::GetInstance().GetSession(session_id); @@ -495,11 +501,16 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }); game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { BinaryProtocol::BinaryWriter writer; - writer.WriteInt32(data.chunk_x); - writer.WriteInt32(data.chunk_z); - writer.WriteUInt8(data.lod); - writer.WriteJson(data.chunk_json); - writer.WriteUInt64(data.timestamp); + writer.WriteInt32(data.x); + writer.WriteInt32(data.z); + writer.WriteInt32(data.size); + uint32_t vertexDataSize = static_cast(data.vertices.size() * sizeof(float)); + writer.WriteUInt32(vertexDataSize); + writer.WriteBytes(reinterpret_cast(data.vertices.data()), vertexDataSize); + uint32_t indexDataSize = static_cast(data.indices.size() * sizeof(uint32_t)); + writer.WriteUInt32(indexDataSize); + writer.WriteBytes(reinterpret_cast(data.indices.data()), indexDataSize); + writer.WriteUInt32(0); auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) { session->Send(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, writer.GetBuffer()); @@ -508,7 +519,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ game_logic.SetSendCollisionResponseCallback([&](uint64_t session_id, const CollisionResult& result) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt8(result.collided ? 1 : 0); - writer.WriteUInt64(result.collidedWith); + writer.WriteUInt64(result.collided_id); writer.WriteFloat(result.penetration); writer.WriteVector3(result.resolution); writer.WriteUInt64(game_logic.GetCurrentTimestamp()); @@ -600,7 +611,6 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ writer.WriteUInt64(response.familiar_id); writer.WriteUInt64(response.target_id); writer.WriteString(response.command); - writer.WriteUInt8(response.success ? 1 : 0); auto session = ConnectionManager::GetInstance().GetSession(session_id); if (session) session->Send(BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND, writer.GetBuffer()); }); @@ -641,12 +651,11 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ }); //TODO: add all game_logic } else { // websocket - game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, bool success, const std::string& message, uint64_t player_id) { + game_logic.SetSendAuthenticationResponseCallback([&](uint64_t session_id, const std::string& message, uint64_t player_id) { nlohmann::json response = { - {"msg", "authentication_response"}, - {"success", success}, - {"message", message}, - {"player_id", player_id}, + {"msg", "authentication"}, + {"desc", message}, + {"player_id", player_id},// if value > 0 then success, has no more duplicate success fields {"timestamp", game_logic.GetCurrentTimestamp()} }; auto session = ConnectionManager::GetInstance().GetSession(session_id); @@ -657,10 +666,13 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ game_logic.SetSendChunkCallback([&](uint64_t session_id, const ChunkData& data) { nlohmann::json msg = { {"msg", "get_chunk"}, - {"x", data.chunk_x}, - {"z", data.chunk_z}, + {"x", data.x}, + {"z", data.z}, {"lod", data.lod}, - {"data", data.chunk_json}, + {"size", data.size}, + {"spacing", data.spacing}, + {"vertices", data.vertices}, + {"indices", data.indices}, {"timestamp", data.timestamp} }; auto session = ConnectionManager::GetInstance().GetSession(session_id); @@ -672,7 +684,7 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ nlohmann::json response = { {"msg", "collision"}, {"collided", result.collided}, - {"collidedWith", result.collidedWith}, + {"collided_id", result.collided_id}, {"penetration", result.penetration}, {"resolution", {result.resolution.x, result.resolution.y, result.resolution.z}}, {"timestamp", game_logic.GetCurrentTimestamp()} @@ -758,7 +770,6 @@ void GameServer::RegisterCallbacks(const std::string& protocol, GameLogic& game_ {"familiar_id", response.familiar_id}, {"target_id", response.target_id}, {"command", response.command}, - {"success", response.success}, {"timestamp", response.timestamp} }; auto session = ConnectionManager::GetInstance().GetSession(session_id); diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index cff9b71..1873994 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -446,59 +446,6 @@ void WebSocketConnection::WriteHandshakeResponse(const HandshakeResponse& respon }); } -// void WebSocketConnection::ReadFrame() { -// if (state_ != State::OPEN && state_ != State::CLOSING) { -// return; -// } -// auto self = shared_from_this(); -// Logger::Trace("WebSocketConnection {} ReadFrame: starting async_read (2 bytes)", connection_id_); -// asio::async_read(socket_, read_buffer_, asio::transfer_exactly(2), -// [self](std::error_code ec, size_t bytes) { -// Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); -// if (ec) { -// self->HandleError(ec); -// return; -// } -// auto buffers = self->read_buffer_.data(); -// auto it = asio::buffers_begin(buffers); -// if (std::distance(it, asio::buffers_end(buffers)) < 2) { -// Logger::Error("Insufficient data for frame header"); -// throw std::runtime_error("Insufficient data for frame header"); -// } -// uint8_t first_byte = *it; -// uint8_t second_byte = *(++it); -// bool fin = (first_byte & 0x80) != 0; -// uint8_t opcode = first_byte & 0x0F; -// bool masked = (second_byte & 0x80) != 0; -// uint64_t payload_length = second_byte & 0x7F; -// size_t header_size = 2; -// if (payload_length == 126) { -// header_size += 2; -// } else if (payload_length == 127) { -// header_size += 8; -// } -// if (masked) { -// header_size += 4; -// } -// if (header_size > 2) { -// self->read_buffer_.consume(2); // Remove the 2 bytes we already read -// asio::async_read(self->socket_, self->read_buffer_, -// asio::transfer_exactly(header_size - 2), -// [self, fin, opcode, masked, payload_length, header_size] -// (std::error_code ec, size_t bytes) { -// Logger::Trace("WebSocketConnection {} ReadFrame completion: ec={}, bytes={}", self->connection_id_, ec.message(), bytes); -// if (ec) { -// self->HandleError(ec); -// return; -// } -// self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); -// }); -// } else { -// self->ReadFramePayload(fin, opcode, masked, payload_length, header_size); -// } -// }); -// } - void WebSocketConnection::ReadFrame() { if (state_ != State::OPEN && state_ != State::CLOSING) { return; @@ -598,42 +545,6 @@ void WebSocketConnection::ReadFramePayload(bool fin, uint8_t opcode, bool masked } } -// void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked, -// uint64_t payload_length, size_t header_size) { -// (void)fin; -// (void)opcode; -// (void)masked; -// size_t total_frame_size = header_size + payload_length; -// constexpr size_t MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16MB limit -// if (total_frame_size > MAX_FRAME_SIZE) { -// throw std::runtime_error("Frame too large"); -// } -// std::vector frame_data(total_frame_size); -// auto buffers = read_buffer_.data(); -// Logger::Trace("WebSocketConnection {} ProcessFrameData: starting buffer_copy", connection_id_); -// size_t bytes_copied = asio::buffer_copy( -// asio::buffer(frame_data), // destination -// buffers, // source buffer sequence -// total_frame_size // maximum bytes to copy -// ); -// if (bytes_copied != total_frame_size) { -// throw std::runtime_error("Incomplete frame data in buffer"); -// } -// read_buffer_.consume(bytes_copied); -// try { -// WebSocketFrame frame = WebSocketFrame::Deserialize(frame_data); -// HandleFrame(frame); -// } catch (const std::exception& e) { -// Logger::Error("WebSocket frame parsing error: {}", e.what()); -// Close(1002, "Protocol error"); -// return; -// } -// if (state_ == State::OPEN || state_ == State::CLOSING) { -// ReadFrame(); -// } -// Logger::Trace("WebSocketConnection {} ProcessFrameData: complete", connection_id_); -// } - void WebSocketConnection::ProcessFrameData(bool fin, uint8_t opcode, bool masked, uint64_t payload_length, size_t header_size) { (void)fin; @@ -774,38 +685,6 @@ void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { }); } -// ATTENTION: THIS VERSION NOT WORKED -// void WebSocketConnection::DoWrite() { -// std::lock_guard lock(write_mutex_); -// if (write_buffer_.empty() || state_ != State::OPEN) -// return; -// auto self = shared_from_this(); -// Logger::Trace("WebSocketConnection {} DoWrite: starting async_write", connection_id_); -// asio::async_write(socket_, asio::buffer(write_buffer_), -// [self](std::error_code ec, size_t bytes_transferred) { -// Logger::Trace("WebSocketConnection::DoWrite asio::async_write {}", bytes_transferred); -// try { -// if (ec) { -// self->HandleError(ec); -// return; -// } -// std::lock_guard lock(self->write_mutex_); -// self->write_buffer_.erase(self->write_buffer_.begin(), -// self->write_buffer_.begin() + bytes_transferred); -// if (!self->write_buffer_.empty()) { -// self->DoWrite(); -// } -// } catch (const std::exception& err) { -// Logger::Critical("Exception in WebSocket write handler: {}", err.what()); -// self->Close(1011, "Internal error"); -// } catch (...) { -// Logger::Critical("Unknown exception in WebSocket write handler"); -// self->Close(1011, "Internal error"); -// } -// }); -// Logger::Trace("WebSocketConnection {} DoWrite: complete", connection_id_); -// } - void WebSocketConnection::DoWrite() { std::lock_guard lock(write_mutex_); if (write_buffer_.empty() || state_ != State::OPEN) @@ -889,29 +768,6 @@ void WebSocketConnection::Close(uint16_t code, const std::string& reason) { Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); } -// void WebSocketConnection::Close(uint16_t code, const std::string& reason) { -// Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); -// if (state_ == State::CLOSED || state_ == State::CLOSING) { -// if (state_ == State::CLOSED) -// Logger::Warn("WebSocketConnection now is closed"); -// else -// Logger::Warn("WebSocketConnection now is in process closing"); -// return; -// } -// state_ = State::CLOSING; -// std::error_code ec; -// socket_.cancel(ec); -// Logger::Trace("WebSocketConnection {} cancel result: {}", connection_id_, ec.message()); -// socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); -// Logger::Trace("WebSocketConnection socket_.shutdown {}", ec.message()); -// socket_.close(ec); -// Logger::Trace("WebSocketConnection socket_.close {}", ec.message()); -// if (close_handler_) { -// close_handler_(code, reason.empty() ? "Connection closed" : reason); -// } -// Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); -// } - void WebSocketConnection::HandleError(const std::error_code& ec) { if (state_ == State::CLOSED || state_ == State::CLOSING) { return; @@ -931,7 +787,6 @@ void WebSocketConnection::HandleError(const std::error_code& ec) { } void WebSocketConnection::HandleClose(uint16_t code, const std::string& reason) { - // Echo close frame back to client if (state_ == State::OPEN) { SendFrameAsync(WebSocketFrame::CreateCloseFrame(code, reason)); } diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index 166e689..3136840 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -156,7 +156,6 @@ std::string WebSocketSession::GetRemoteAddress() const { } } - void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { Logger::Trace("WebSocketSession {} received {} bytes, opcode: {}", sessionId_, msg.data.size(), (int)msg.opcode); if (msg.opcode == WebSocketProtocol::OP_TEXT) { @@ -166,29 +165,28 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) Logger::Warn("Session {} sent TEXT frame but negotiated BINARY – ignoring", sessionId_); return; } - protocolMode_ = ProtocolMode::Json; if (messageHandler_) { try { auto json = nlohmann::json::parse(text); Logger::Trace("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); - messageHandler_(json); - if (json.value("type", "") == "protocol_negotiation" && - json.value("protocol", "") == "websocket") { + std::string msgType = json.value("msg", ""); + if (msgType == "protocol_negotiation") { protocolMode_ = ProtocolMode::Json; - Logger::Trace("WebSocketSession {} switched to JSON protocol mode", sessionId_); + std::string protocol = json.value("protocol", ""); + int version = json.value("version", 0); + Logger::Info("WebSocketSession {} client negotiated protocol: {} v{}", sessionId_, protocol, version); + nlohmann::json response = { + {"msg", "protocol_negotiation"}, + {"protocol", "websocket"}, + {"version", 1}, + {"status", "ok"} + }; + wsConn_->SendJson(response); return; } - else if (json.value("type", "") == "get_chunk") { - ChunkData req; - nlohmann::json data = msg.ToJson(); - req.chunk_x = data.value("x", 0); - req.chunk_z = data.value("z", 0); - req.lod = data.value("lod", 0); - req.session_id = sessionId_; - GameLogic::GetInstance().OnChunkRequest(req); - } + messageHandler_(json); } catch (const std::exception& err) { - Logger::Error("WebSocketSession {} invalid: {}", sessionId_, err.what()); + Logger::Error("WebSocketSession {} invalid JSON: {}", sessionId_, err.what()); } } else { Logger::Error("WebSocketSession {} has no messageHandler_ set!", sessionId_); @@ -200,10 +198,22 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) Logger::Warn("Session {} sent BINARY frame but negotiated JSON – ignoring", sessionId_); return; } - protocolMode_ = ProtocolMode::Binary; try { auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize(msg.data.data(), msg.data.size()); Logger::Trace("WebSocketSession {} binary message type: {}", sessionId_, binaryMsg.header.message_type); + if (binaryMsg.header.message_type == BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION) { + protocolMode_ = ProtocolMode::Binary; + Logger::Info("WebSocketSession {} switched to binary protocol mode", sessionId_); + auto caps = BinaryProtocol::ProtocolCapabilities::Deserialize(binaryMsg.data.data(), binaryMsg.data.size()); + nlohmann::json response = { + {"msg", "protocol_negotiation"}, + {"protocol", "binary"}, + {"version", caps.version}, + {"status", "ok"} + }; + wsConn_->SendJson(response); + return; + } if (binary_handler_) { binary_handler_(binaryMsg.header.message_type, binaryMsg.data); } else if (default_binary_handler_) { From 8ba40872fb6e978252027e3931b30b9eb3e667de Mon Sep 17 00:00:00 2001 From: usermicrodevices <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 24 Apr 2026 21:43:23 +0300 Subject: [PATCH 14/14] refactor chunk generation --- config/core.json | 4 +- include/game/EntityManager.hpp | 1 + include/game/GameData.hpp | 24 ++ include/game/WorldChunk.hpp | 61 +++-- include/game/WorldGenerator.hpp | 11 +- src/game/ChunkCache.cpp | 4 +- src/game/EntityManager.cpp | 12 +- src/game/GameLogic.cpp | 2 +- src/game/WorldChunk.cpp | 150 +++++------ src/game/WorldGenerator.cpp | 435 +++++++------------------------ src/network/BinarySession.cpp | 18 +- src/network/GameServer.cpp | 6 + src/network/WebSocketSession.cpp | 52 ++-- 13 files changed, 273 insertions(+), 507 deletions(-) diff --git a/config/core.json b/config/core.json index e8bd33a..103d290 100644 --- a/config/core.json +++ b/config/core.json @@ -44,8 +44,8 @@ "view_distance": 4, "unload_distance": 200.0, "max_active_chunks": 100, - "terrain_scale": 100.0, - "max_terrain_height": 50.0, + "terrain_scale": 1.0, + "max_terrain_height": 1.0, "water_level": 10.0, "interest_radius": 100.0, "preload_radius": 100.0, diff --git a/include/game/EntityManager.hpp b/include/game/EntityManager.hpp index 1f1db1c..542cb5e 100644 --- a/include/game/EntityManager.hpp +++ b/include/game/EntityManager.hpp @@ -17,6 +17,7 @@ #include "logging/Logger.hpp" #include "game/GameEntity.hpp" +#include "game/WorldChunk.hpp" class EntityManager { public: diff --git a/include/game/GameData.hpp b/include/game/GameData.hpp index 4bd3883..70eddc7 100644 --- a/include/game/GameData.hpp +++ b/include/game/GameData.hpp @@ -124,6 +124,27 @@ struct PlayerUpdateData { std::string name; }; +struct StoneData { + float x, y, z; + float trunkHeight; + float foliageRadius; + float rotationY; +}; + +struct TreeData { + float x, y, z; + float trunkHeight; + float foliageRadius; + float rotationY; +}; + +struct PortalData { + float x, y, z; + float rotationY; + float scale; + bool active = false; +}; + struct ChunkData { uint64_t timestamp; uint64_t session_id; @@ -132,6 +153,9 @@ struct ChunkData { uint8_t lod; int size; float spacing; + float player_x; + float player_y; + float player_z; std::vector vertices; std::vector indices; }; diff --git a/include/game/WorldChunk.hpp b/include/game/WorldChunk.hpp index 0f56680..4893429 100644 --- a/include/game/WorldChunk.hpp +++ b/include/game/WorldChunk.hpp @@ -10,6 +10,7 @@ #include #include "network/BinaryProtocol.hpp" +#include "game/GameData.hpp" struct LODConfig; class LODChunk; @@ -63,30 +64,28 @@ struct Triangle { class WorldChunk { public: - static const int CHUNK_SIZE = 32; // 32x32 blocks static const float BLOCK_SIZE; static const float CHUNK_WIDTH; + static constexpr int DEFAULT_SIZE = 32; // 32x32 blocks static constexpr float DEFAULT_SPACING = 1.0f; WorldChunk(int x, int z, ChunkLOD lod = ChunkLOD::HIGH); virtual ~WorldChunk() = default; // Geometry access - const std::vector& GetVertices() const { return vertices_; } - const std::vector& GetTriangles() const { return triangles_; } - const std::vector& GetCollisionVertices() const { return collisionVertices_; } - const std::vector& GetCollisionTriangles() const { return collisionTriangles_; } + const std::vector& GetVertices() const; + const std::vector& GetTriangles() const; + const std::vector& GetCollisionVertices() const; + const std::vector& GetCollisionTriangles() const; // Metadata - int GetChunkX() const { return chunkX_; } - int GetChunkZ() const { return chunkZ_; } - virtual ChunkLOD GetLOD() const { return lod_; } - BiomeType GetBiome() const { return biome_; } - void SetBiome(BiomeType biome) { biome_ = biome; } + int GetChunkX() const; + int GetChunkZ() const; + virtual ChunkLOD GetLOD() const; + BiomeType GetBiome() const; + void SetBiome(BiomeType biome); - glm::vec3 GetWorldPosition() const { - return glm::vec3(chunkX_ * CHUNK_WIDTH, 0.0f, chunkZ_ * CHUNK_WIDTH); - } + glm::vec3 GetWorldPosition() const; // Block access BlockType GetBlock(int x, int y, int z) const; @@ -94,10 +93,10 @@ class WorldChunk { float GetHeightAt(float x, float z) const; // Entity management - void AddEntity(uint64_t entityId) { entities_.insert(entityId); } - void RemoveEntity(uint64_t entityId) { entities_.erase(entityId); } - const std::unordered_set& GetEntities() const { return entities_; } - bool HasEntities() const { return !entities_.empty(); } + void AddEntity(uint64_t entityId); + void RemoveEntity(uint64_t entityId); + const std::unordered_set& GetEntities() const; + bool HasEntities() const; // Serialization void SerializeToWriter(BinaryProtocol::BinaryWriter& writer) const; @@ -107,25 +106,22 @@ class WorldChunk { virtual nlohmann::json SerializeHeightmap() const; // Geometry generation - virtual void GenerateGeometry() { GenerateLowPolyGeometry(); } + virtual void GenerateGeometry(); void GenerateLowPolyGeometry(); void GenerateCollisionMesh(); // LOD-specific geometry generation - virtual void GenerateHighLODGeometry() { GenerateLowPolyGeometry(); } - virtual void GenerateMediumLODGeometry() { GenerateLowPolyGeometry(); } - virtual void GenerateLowLODGeometry() { GenerateLowPolyGeometry(); } - virtual void GenerateBillboardGeometry() { } + virtual void GenerateHighLODGeometry(); + virtual void GenerateMediumLODGeometry(); + virtual void GenerateLowLODGeometry(); + virtual void GenerateBillboardGeometry(); // Utility bool IsPositionInside(const glm::vec3& position) const; - glm::vec3 GetCenter() const { - return glm::vec3( - chunkX_ * CHUNK_WIDTH + CHUNK_WIDTH / 2.0f, - 0.0f, - chunkZ_ * CHUNK_WIDTH + CHUNK_WIDTH / 2.0f - ); - } + glm::vec3 GetCenter() const; + + glm::vec3 GetBlockColor(BlockType type) const; + glm::vec3 GetBiomeColor(BiomeType biome, float height) const; protected: int chunkX_; @@ -148,6 +144,10 @@ class WorldChunk { // Entities in this chunk std::unordered_set entities_; + std::vector stones_; + std::vector trees_; + PortalData portal_; + // Helper methods void GenerateBlockVertices(int x, int y, int z, BlockType type); void AddQuad(const glm::vec3& p1, const glm::vec3& p2, @@ -155,8 +155,5 @@ class WorldChunk { const glm::vec3& normal, const glm::vec3& color); void AddTriangle(uint32_t v0, uint32_t v1, uint32_t v2); - glm::vec3 GetBlockColor(BlockType type) const; - glm::vec3 GetBiomeColor(BiomeType biome, float height) const; - friend class WorldGenerator; }; diff --git a/include/game/WorldGenerator.hpp b/include/game/WorldGenerator.hpp index 40e347a..0441f0e 100644 --- a/include/game/WorldGenerator.hpp +++ b/include/game/WorldGenerator.hpp @@ -10,18 +10,17 @@ #include #include +#include "logging/Logger.hpp" #include "game/WorldChunk.hpp" struct GenerationConfig { - float terrainScale = 100.0f; - float terrainHeight = 50.0f; + float terrainScale = 1.0f; + float terrainHeight = 1.0f; int octaves = 4; float persistence = 0.5f; float lacunarity = 2.0f; - float waterLevel = 10.0f; + float waterLevel = -0.5f; int seed = 12345; - - // Biome settings float forestThreshold = 0.6f; float mountainThreshold = 0.8f; float desertThreshold = -0.3f; @@ -58,4 +57,4 @@ class WorldGenerator { void GenerateMountainFeatures(WorldChunk& chunk); void GenerateDesertFeatures(WorldChunk& chunk); void GeneratePlainsFeatures(WorldChunk& chunk); -}; \ No newline at end of file +}; diff --git a/src/game/ChunkCache.cpp b/src/game/ChunkCache.cpp index edade7e..6451ee2 100644 --- a/src/game/ChunkCache.cpp +++ b/src/game/ChunkCache.cpp @@ -669,8 +669,8 @@ size_t ChunkCache::EstimateChunkSize(const WorldChunk& chunk) const { // Triangles: 3 uint32_t per triangle size += chunk.GetTriangles().size() * 3 * sizeof(uint32_t); - // Block data: CHUNK_SIZE^3 * sizeof(BlockType) - size += WorldChunk::CHUNK_SIZE * WorldChunk::CHUNK_SIZE * WorldChunk::CHUNK_SIZE * sizeof(int); + // Block data: CHUNK SIZE^3 * sizeof(BlockType) + size += WorldChunk::DEFAULT_SIZE * WorldChunk::DEFAULT_SIZE * WorldChunk::DEFAULT_SIZE * sizeof(int); return size; } diff --git a/src/game/EntityManager.cpp b/src/game/EntityManager.cpp index a197ebb..ac24a26 100644 --- a/src/game/EntityManager.cpp +++ b/src/game/EntityManager.cpp @@ -109,13 +109,13 @@ std::vector EntityManager::GetEntitiesInRadius(const glm::vec3& positi std::vector EntityManager::GetEntitiesInChunk(int chunkX, int chunkZ) const { std::lock_guard lock(mutex_); std::vector result; - const float CHUNK_SIZE = 32.0f; - const float HALF_CHUNK = CHUNK_SIZE / 2.0f; + const float chunk_size = float(WorldChunk::DEFAULT_SIZE); + const float HALF_CHUNK = chunk_size / 2.0f; - float minX = chunkX * CHUNK_SIZE - HALF_CHUNK; - float maxX = (chunkX + 1) * CHUNK_SIZE - HALF_CHUNK; - float minZ = chunkZ * CHUNK_SIZE - HALF_CHUNK; - float maxZ = (chunkZ + 1) * CHUNK_SIZE - HALF_CHUNK; + float minX = chunkX * chunk_size - HALF_CHUNK; + float maxX = (chunkX + 1) * chunk_size - HALF_CHUNK; + float minZ = chunkZ * chunk_size - HALF_CHUNK; + float maxZ = (chunkZ + 1) * chunk_size - HALF_CHUNK; for (const auto& [id, entity] : entities_) { glm::vec3 pos = entity->GetPosition(); diff --git a/src/game/GameLogic.cpp b/src/game/GameLogic.cpp index 1393011..998eca3 100644 --- a/src/game/GameLogic.cpp +++ b/src/game/GameLogic.cpp @@ -511,7 +511,7 @@ void GameLogic::OnChunkRequest(const ChunkData& req) { resp.x = req.x; resp.z = req.z; resp.lod = req.lod; - resp.size = WorldChunk::CHUNK_SIZE; + resp.size = WorldChunk::DEFAULT_SIZE; resp.spacing = WorldChunk::DEFAULT_SPACING; resp.timestamp = GetCurrentTimestamp(); const auto& verts = chunk->GetVertices(); diff --git a/src/game/WorldChunk.cpp b/src/game/WorldChunk.cpp index 512871f..b7c8158 100644 --- a/src/game/WorldChunk.cpp +++ b/src/game/WorldChunk.cpp @@ -1,53 +1,78 @@ #include "game/WorldChunk.hpp" const float WorldChunk::BLOCK_SIZE = 1.0f; -const float WorldChunk::CHUNK_WIDTH = CHUNK_SIZE * BLOCK_SIZE; +const float WorldChunk::CHUNK_WIDTH = (DEFAULT_SIZE - 1) * DEFAULT_SPACING; WorldChunk::WorldChunk(int x, int z, ChunkLOD lod) : chunkX_(x), chunkZ_(z), lod_(lod), biome_(BiomeType::PLAINS) { - blocks_.resize(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE, BlockType::AIR); - heightmap_.resize(CHUNK_SIZE * CHUNK_SIZE, 0.0f); + blocks_.resize(DEFAULT_SIZE * DEFAULT_SIZE * DEFAULT_SIZE, BlockType::AIR); + heightmap_.resize(DEFAULT_SIZE * DEFAULT_SIZE, 0.0f); +} + +const std::vector& WorldChunk::GetVertices() const { return vertices_; } + +const std::vector& WorldChunk::GetTriangles() const { return triangles_; } + +const std::vector& WorldChunk::GetCollisionVertices() const { return collisionVertices_; } + +const std::vector& WorldChunk::GetCollisionTriangles() const { return collisionTriangles_; } + +int WorldChunk::GetChunkX() const { return chunkX_; } + +int WorldChunk::GetChunkZ() const { return chunkZ_; } + +ChunkLOD WorldChunk::GetLOD() const { return lod_; } + +BiomeType WorldChunk::GetBiome() const { return biome_; } + +void WorldChunk::SetBiome(BiomeType biome) { biome_ = biome; } + +glm::vec3 WorldChunk::GetWorldPosition() const { + return glm::vec3(chunkX_ * CHUNK_WIDTH, 0.0f, chunkZ_ * CHUNK_WIDTH); } BlockType WorldChunk::GetBlock(int x, int y, int z) const { - if (x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE) { + if (x < 0 || x >= DEFAULT_SIZE || y < 0 || y >= DEFAULT_SIZE || z < 0 || z >= DEFAULT_SIZE) { return BlockType::AIR; } - int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; + int index = x + y * DEFAULT_SIZE + z * DEFAULT_SIZE * DEFAULT_SIZE; return blocks_[index]; } void WorldChunk::SetBlock(int x, int y, int z, BlockType type) { - if (x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE) { + if (x < 0 || x >= DEFAULT_SIZE || y < 0 || y >= DEFAULT_SIZE || z < 0 || z >= DEFAULT_SIZE) { return; } - int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; + int index = x + y * DEFAULT_SIZE + z * DEFAULT_SIZE * DEFAULT_SIZE; blocks_[index] = type; } float WorldChunk::GetHeightAt(float x, float z) const { - int localX = static_cast(std::floor(x)) - chunkX_ * CHUNK_SIZE; - int localZ = static_cast(std::floor(z)) - chunkZ_ * CHUNK_SIZE; - - if (localX < 0 || localX >= CHUNK_SIZE || localZ < 0 || localZ >= CHUNK_SIZE) { + int localX = static_cast(std::floor(x)) - chunkX_ * DEFAULT_SIZE; + int localZ = static_cast(std::floor(z)) - chunkZ_ * DEFAULT_SIZE; + if (localX < 0 || localX >= DEFAULT_SIZE || localZ < 0 || localZ >= DEFAULT_SIZE) { return 0.0f; } - - return heightmap_[localX + localZ * CHUNK_SIZE]; + return heightmap_[localX + localZ * DEFAULT_SIZE]; } +void WorldChunk::AddEntity(uint64_t entityId) { entities_.insert(entityId); } + +void WorldChunk::RemoveEntity(uint64_t entityId) { entities_.erase(entityId); } + +const std::unordered_set& WorldChunk::GetEntities() const { return entities_; } + +bool WorldChunk::HasEntities() const { return !entities_.empty(); } + void WorldChunk::GenerateLowPolyGeometry() { vertices_.clear(); triangles_.clear(); - - // Generate geometry for each block - for (int x = 0; x < CHUNK_SIZE; ++x) { - for (int z = 0; z < CHUNK_SIZE; ++z) { - float height = heightmap_[x + z * CHUNK_SIZE]; + for (int x = 0; x < DEFAULT_SIZE; ++x) { + for (int z = 0; z < DEFAULT_SIZE; ++z) { + float height = heightmap_[x + z * DEFAULT_SIZE]; int blockHeight = static_cast(std::floor(height)); - - for (int y = 0; y <= blockHeight && y < CHUNK_SIZE; ++y) { + for (int y = 0; y <= blockHeight && y < DEFAULT_SIZE; ++y) { BlockType type = GetBlock(x, y, z); if (type != BlockType::AIR) { GenerateBlockVertices(x, y, z, type); @@ -61,22 +86,14 @@ void WorldChunk::GenerateBlockVertices(int x, int y, int z, BlockType type) { float px = static_cast(x); float py = static_cast(y); float pz = static_cast(z); - glm::vec3 color = GetBlockColor(type); - - // Only generate visible faces (simple culling) - // For a proper implementation, check neighboring blocks - - // Top face - if (y == CHUNK_SIZE - 1 || GetBlock(x, y + 1, z) == BlockType::AIR) { + if (y == DEFAULT_SIZE - 1 || GetBlock(x, y + 1, z) == BlockType::AIR) { glm::vec3 p1(px, py + 1, pz); glm::vec3 p2(px + 1, py + 1, pz); glm::vec3 p3(px + 1, py + 1, pz + 1); glm::vec3 p4(px, py + 1, pz + 1); AddQuad(p1, p2, p3, p4, glm::vec3(0, 1, 0), color * 1.2f); } - - // Bottom face if (y == 0 || GetBlock(x, y - 1, z) == BlockType::AIR) { glm::vec3 p1(px, py, pz); glm::vec3 p2(px, py, pz + 1); @@ -84,8 +101,6 @@ void WorldChunk::GenerateBlockVertices(int x, int y, int z, BlockType type) { glm::vec3 p4(px + 1, py, pz); AddQuad(p1, p2, p3, p4, glm::vec3(0, -1, 0), color * 0.8f); } - - // Front face if (z == 0 || GetBlock(x, y, z - 1) == BlockType::AIR) { glm::vec3 p1(px, py, pz); glm::vec3 p2(px + 1, py, pz); @@ -93,17 +108,13 @@ void WorldChunk::GenerateBlockVertices(int x, int y, int z, BlockType type) { glm::vec3 p4(px, py + 1, pz); AddQuad(p1, p2, p3, p4, glm::vec3(0, 0, -1), color); } - - // Back face - if (z == CHUNK_SIZE - 1 || GetBlock(x, y, z + 1) == BlockType::AIR) { + if (z == DEFAULT_SIZE - 1 || GetBlock(x, y, z + 1) == BlockType::AIR) { glm::vec3 p1(px, py, pz + 1); glm::vec3 p2(px, py + 1, pz + 1); glm::vec3 p3(px + 1, py + 1, pz + 1); glm::vec3 p4(px + 1, py, pz + 1); AddQuad(p1, p2, p3, p4, glm::vec3(0, 0, 1), color); } - - // Left face if (x == 0 || GetBlock(x - 1, y, z) == BlockType::AIR) { glm::vec3 p1(px, py, pz); glm::vec3 p2(px, py + 1, pz); @@ -111,9 +122,7 @@ void WorldChunk::GenerateBlockVertices(int x, int y, int z, BlockType type) { glm::vec3 p4(px, py, pz + 1); AddQuad(p1, p2, p3, p4, glm::vec3(-1, 0, 0), color * 0.9f); } - - // Right face - if (x == CHUNK_SIZE - 1 || GetBlock(x + 1, y, z) == BlockType::AIR) { + if (x == DEFAULT_SIZE - 1 || GetBlock(x + 1, y, z) == BlockType::AIR) { glm::vec3 p1(px + 1, py, pz); glm::vec3 p2(px + 1, py, pz + 1); glm::vec3 p3(px + 1, py + 1, pz + 1); @@ -125,16 +134,11 @@ void WorldChunk::GenerateBlockVertices(int x, int y, int z, BlockType type) { void WorldChunk::AddQuad(const glm::vec3& p1, const glm::vec3& p2, const glm::vec3& p3, const glm::vec3& p4, const glm::vec3& normal, const glm::vec3& color) { - // Create two triangles from the quad uint32_t baseIndex = static_cast(vertices_.size()); - - // Create vertices vertices_.emplace_back(p1, normal, color, glm::vec2(0, 0)); vertices_.emplace_back(p2, normal, color, glm::vec2(1, 0)); vertices_.emplace_back(p3, normal, color, glm::vec2(1, 1)); vertices_.emplace_back(p4, normal, color, glm::vec2(0, 1)); - - // Create triangles triangles_.emplace_back(baseIndex, baseIndex + 1, baseIndex + 2); triangles_.emplace_back(baseIndex, baseIndex + 2, baseIndex + 3); } @@ -180,15 +184,12 @@ glm::vec3 WorldChunk::GetBiomeColor(BiomeType biome, float height) const { void WorldChunk::GenerateCollisionMesh() { collisionVertices_.clear(); collisionTriangles_.clear(); - - // Simplified collision mesh - just use block positions for now - for (int x = 0; x < CHUNK_SIZE; ++x) { - for (int z = 0; z < CHUNK_SIZE; ++z) { - float height = heightmap_[x + z * CHUNK_SIZE]; + for (int x = 0; x < DEFAULT_SIZE; ++x) { + for (int z = 0; z < DEFAULT_SIZE; ++z) { + float height = heightmap_[x + z * DEFAULT_SIZE]; if (height > 0) { glm::vec3 pos(x, height, z); collisionVertices_.push_back(pos); - // Create simple collision geometry here } } } @@ -239,16 +240,17 @@ std::vector WorldChunk::SerializeBinary() const return writer.GetBuffer(); } -nlohmann::json WorldChunk::SerializeJson() const -{ +nlohmann::json WorldChunk::SerializeJson() const { nlohmann::json data; - data["chunkX"] = chunkX_; - data["chunkZ"] = chunkZ_; + data["x"] = chunkX_; + data["z"] = chunkZ_; data["lod"] = static_cast(lod_); data["biome"] = static_cast(biome_); + data["size"] = DEFAULT_SIZE; + data["spacing"] = DEFAULT_SPACING; + data["msg"] = "get_chunk"; nlohmann::json verticesArray = nlohmann::json::array(); - for (const auto& v : vertices_) - { + for (const auto& v : vertices_) { verticesArray.push_back(v.position.x); verticesArray.push_back(v.position.y); verticesArray.push_back(v.position.z); @@ -258,16 +260,15 @@ nlohmann::json WorldChunk::SerializeJson() const } data["vertices"] = verticesArray; nlohmann::json indicesArray = nlohmann::json::array(); - for (const auto& tri : triangles_) - { + for (const auto& tri : triangles_) { indicesArray.push_back(tri.v0); indicesArray.push_back(tri.v1); indicesArray.push_back(tri.v2); } data["indices"] = indicesArray; - std::vector binaryData = SerializeBinary(); - data["binary_data"] = binaryData; - return data; + data["timestamp"] = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + return data; } void WorldChunk::Deserialize(const nlohmann::json& data) { @@ -275,8 +276,6 @@ void WorldChunk::Deserialize(const nlohmann::json& data) { chunkZ_ = data.value("chunkZ", 0); lod_ = static_cast(data.value("lod", 0)); biome_ = static_cast(data.value("biome", 0)); - - // Deserialize heightmap if (data.contains("heightmap") && data["heightmap"].is_array()) { const auto& heightmapData = data["heightmap"]; heightmap_.resize(heightmapData.size()); @@ -284,8 +283,6 @@ void WorldChunk::Deserialize(const nlohmann::json& data) { heightmap_[i] = heightmapData[i].get(); } } - - // Deserialize blocks if (data.contains("blocks") && data["blocks"].is_array()) { const auto& blocksData = data["blocks"]; blocks_.resize(blocksData.size()); @@ -293,32 +290,39 @@ void WorldChunk::Deserialize(const nlohmann::json& data) { blocks_[i] = static_cast(blocksData[i].get()); } } - - // Regenerate geometry GenerateGeometry(); } nlohmann::json WorldChunk::SerializeHeightmap() const { nlohmann::json data; - data["chunkX"] = chunkX_; data["chunkZ"] = chunkZ_; data["lod"] = static_cast(lod_); data["biome"] = static_cast(biome_); - - // Serialize heightmap nlohmann::json heightmapArray = nlohmann::json::array(); for (float height : heightmap_) { heightmapArray.push_back(height); } data["heightmap"] = heightmapArray; - - // Serialize blocks (simplified) nlohmann::json blocksArray = nlohmann::json::array(); for (BlockType block : blocks_) { blocksArray.push_back(static_cast(block)); } data["blocks"] = blocksArray; - return data; } + +void WorldChunk::GenerateGeometry() { GenerateLowPolyGeometry(); } + +void WorldChunk::GenerateHighLODGeometry() { GenerateLowPolyGeometry(); } +void WorldChunk::GenerateMediumLODGeometry() { GenerateLowPolyGeometry(); } +void WorldChunk::GenerateLowLODGeometry() { GenerateLowPolyGeometry(); } +void WorldChunk::GenerateBillboardGeometry() { } + +glm::vec3 WorldChunk::GetCenter() const { + return glm::vec3( + chunkX_ * CHUNK_WIDTH + CHUNK_WIDTH / 2.0f, + 0.0f, + chunkZ_ * CHUNK_WIDTH + CHUNK_WIDTH / 2.0f + ); +} diff --git a/src/game/WorldGenerator.cpp b/src/game/WorldGenerator.cpp index 5570408..d1c9deb 100644 --- a/src/game/WorldGenerator.cpp +++ b/src/game/WorldGenerator.cpp @@ -1,148 +1,113 @@ #include "game/WorldGenerator.hpp" - WorldGenerator::WorldGenerator(const GenerationConfig& config) : config_(config), rng_(config.seed), dist_(-1.0f, 1.0f) { } std::unique_ptr WorldGenerator::GenerateChunk(int chunkX, int chunkZ) { auto chunk = std::make_unique(chunkX, chunkZ); - - // Generate terrain heightmap - GenerateLowPolyTerrain(*chunk, chunkX, chunkZ); - - // Generate blocks based on heightmap - const int chunkSize = WorldChunk::CHUNK_SIZE; - const int worldHeight = chunkSize; // Use chunk height (can be increased later) - - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - // Convert to world coordinates - float worldX = (chunkX * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunkZ * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - // Get terrain height at this position - float height = GetTerrainHeight(worldX, worldZ); - - // Determine biome - BiomeType biome = GetBiomeAt(worldX, worldZ); - chunk->SetBiome(biome); - - // Generate column of blocks (only up to chunk height) - for (int y = 0; y < worldHeight; ++y) { - if (y < height) { - // Below ground - generate appropriate block type - BlockType type = BlockType::STONE; - - // Top layer is grass/dirt - if (y >= height - 1) { - if (biome == BiomeType::DESERT) { - type = BlockType::SAND; - } else if (biome == BiomeType::MOUNTAIN && y > config_.waterLevel + 10) { - type = BlockType::SNOW; - } else if (biome == BiomeType::PLAINS || biome == BiomeType::FOREST) { - type = BlockType::GRASS; - } else { - type = BlockType::DIRT; - } - } - // Just below top layer - else if (y >= height - 4 && y < height - 1) { - type = BlockType::DIRT; - } - - chunk->SetBlock(x, y, z, type); - } - else if (y <= config_.waterLevel && y > height) { - // Water layer - chunk->SetBlock(x, y, z, BlockType::WATER); - } - else { - // Air - chunk->SetBlock(x, y, z, BlockType::AIR); - } + const int chunkSize = WorldChunk::DEFAULT_SIZE; + const float spacing = WorldChunk::DEFAULT_SPACING; + const float physWidth = (chunkSize - 1) * spacing; + const float physHeight = (chunkSize - 1) * spacing; + const float worldOriginX = chunkX * physWidth; + const float worldOriginZ = chunkZ * physHeight; + BiomeType biome = GetBiomeAt(worldOriginX + physWidth / 2.0f, worldOriginZ + physHeight / 2.0f); + chunk->SetBiome(biome); + chunk->vertices_.clear(); + chunk->triangles_.clear(); + chunk->heightmap_.resize(chunkSize * chunkSize); + for (int z = 0; z < chunkSize; ++z) { + for (int x = 0; x < chunkSize; ++x) { + float wx = worldOriginX + x * spacing; + float wz = worldOriginZ + z * spacing; + float wy = GetTerrainHeight(wx, wz); + chunk->heightmap_[z * chunkSize + x] = wy; + glm::vec3 normal(0.0f, 1.0f, 0.0f); + glm::vec3 color = chunk->GetBiomeColor(biome, wy / config_.terrainHeight); + chunk->vertices_.emplace_back(glm::vec3(wx, wy, wz), normal, color, glm::vec2(0.0f, 0.0f)); + } + } + for (size_t i = 0; i < chunk->vertices_.size(); ++i) { + int x = i % chunkSize; + int z = i / chunkSize; + if (x > 0 && x < chunkSize - 1 && z > 0 && z < chunkSize - 1) { + float hx1 = chunk->vertices_[(z) * chunkSize + (x + 1)].position.y; + float hx2 = chunk->vertices_[(z) * chunkSize + (x - 1)].position.y; + float hz1 = chunk->vertices_[(z + 1) * chunkSize + x].position.y; + float hz2 = chunk->vertices_[(z - 1) * chunkSize + x].position.y; + float dx = hx1 - hx2; + float dz = hz1 - hz2; + glm::vec3 n(-dx, 2.0f * spacing, -dz); + float len = glm::length(n); + if (len > 0.0f) { + n /= len; } + chunk->vertices_[i].normal = n; } } - - // Add biome-specific features (locking is handled inside each function) - switch (chunk->GetBiome()) { - case BiomeType::FOREST: - GenerateForestFeatures(*chunk); - break; - case BiomeType::MOUNTAIN: - GenerateMountainFeatures(*chunk); - break; - case BiomeType::DESERT: - GenerateDesertFeatures(*chunk); - break; - case BiomeType::PLAINS: - GeneratePlainsFeatures(*chunk); - break; - case BiomeType::OCEAN: - case BiomeType::RIVER: - AddWaterPlane(*chunk); - break; - default: - break; + for (int z = 0; z < chunkSize - 1; ++z) { + for (int x = 0; x < chunkSize - 1; ++x) { + uint32_t i = z * chunkSize + x; + chunk->triangles_.emplace_back(i, i + 1, i + chunkSize); + chunk->triangles_.emplace_back(i + 1, i + chunkSize + 1, i + chunkSize); + } + } + int seed = (chunkX * 1000003) ^ (chunkZ * 1000033); + std::mt19937 rng(seed); + std::uniform_real_distribution dist(0.0f, 1.0f); + int numItems = 5 + (rng() % 6); + for (int i = 0; i < numItems; ++i) { + float x = worldOriginX + 1.5f + dist(rng) * (physWidth - 3.0f); + float z = worldOriginZ + 1.5f + dist(rng) * (physHeight - 3.0f); + float y = GetTerrainHeight(x, z); + float trunkHeight = 1.8f + dist(rng) * 0.4f; + float foliageRadius = 1.0f + dist(rng) * 0.4f; + float rotationY = dist(rng) * 2.0f * 3.14159f; + chunk->stones_.push_back({x, y, z, trunkHeight, foliageRadius, rotationY}); + chunk->trees_.push_back({x + 0.5f, y, z + 0.5f, trunkHeight, foliageRadius, rotationY}); + } + if (dist(rng) < 0.1f) { + float margin = 2.0f; + float px = worldOriginX + margin + dist(rng) * (physWidth - 2.0f * margin); + float pz = worldOriginZ + margin + dist(rng) * (physHeight - 2.0f * margin); + float py = GetTerrainHeight(px, pz); + float rotationY = dist(rng) * 2.0f * 3.14159f; + float scale = 0.9f + dist(rng) * 0.2f; + chunk->portal_ = {px, py, pz, rotationY, scale, true}; } - - // Generate low-poly geometry - chunk->GenerateLowPolyGeometry(); - chunk->GenerateCollisionMesh(); - return chunk; } +float WorldGenerator::GetTerrainHeight(float x, float z) { + float baseHeight = FractalNoise(x / config_.terrainScale, z / config_.terrainScale); + float detail = Noise(x / (config_.terrainScale * 0.5f), z / (config_.terrainScale * 0.5f)) * 0.2f; + float normalizedHeight = (baseHeight + detail + 1.0f) * 0.5f; + normalizedHeight = std::pow(normalizedHeight, 1.5f); + float result = normalizedHeight * config_.terrainHeight; + if (std::abs(x) < 1.0f && std::abs(z) < 1.0f) { + Logger::Trace("GetTerrainHeight({:.2f}, {:.2f}) = {:.4f} (base={:.4f}, norm={:.4f})", + x, z, result, baseHeight, normalizedHeight); + } + return result; +} + BiomeType WorldGenerator::GetBiomeAt(float x, float z) { - // Use noise to determine biome float noiseValue = FractalNoise(x / 1000.0f, z / 1000.0f); - // Avoid division by near-zero if (std::abs(noiseValue) < 0.001f) noiseValue = 0.001f; float temperature = FractalNoise(x / noiseValue * 8.0f, z / noiseValue * 8.0f); float humidity = FractalNoise(x / noiseValue * 7.0f, z / noiseValue * 7.0f); - - // Height-based biomes float height = GetTerrainHeight(x, z); - if (height < config_.waterLevel) { - if (humidity > 0.7f) - return BiomeType::RIVER; + if (humidity > 0.7f) return BiomeType::RIVER; return BiomeType::OCEAN; } - - // Temperature and humidity based biomes - if (height > config_.mountainThreshold * config_.terrainHeight) { - return BiomeType::MOUNTAIN; - } - - if (temperature < config_.desertThreshold) { - return BiomeType::DESERT; - } - - if (humidity > config_.forestThreshold) { - return BiomeType::FOREST; - } - + if (height > config_.mountainThreshold * config_.terrainHeight) return BiomeType::MOUNTAIN; + if (temperature < config_.desertThreshold) return BiomeType::DESERT; + if (humidity > config_.forestThreshold) return BiomeType::FOREST; return BiomeType::PLAINS; } -float WorldGenerator::GetTerrainHeight(float x, float z) { - // Base terrain using fractal noise - float baseHeight = FractalNoise(x / config_.terrainScale, z / config_.terrainScale); - - // Add details with higher frequency noise - float detail = Noise(x / (config_.terrainScale * 0.5f), z / (config_.terrainScale * 0.5f)) * 0.2f; - - // Normalize to [0, 1] and scale by terrain height - float normalizedHeight = (baseHeight + detail + 1.0f) * 0.5f; - - // Apply some smoothing - normalizedHeight = std::pow(normalizedHeight, 1.5f); - - return normalizedHeight * config_.terrainHeight; -} - void WorldGenerator::SetSeed(int seed) { std::lock_guard lock(rngMutex_); config_.seed = seed; @@ -150,7 +115,6 @@ void WorldGenerator::SetSeed(int seed) { } float WorldGenerator::Noise(float x, float y) { - // Simple value noise using glm's simplex noise return glm::simplex(glm::vec2(x, y)); } @@ -158,260 +122,59 @@ float WorldGenerator::FractalNoise(float x, float y) { float value = 0.0f; float amplitude = 1.0f; float frequency = 1.0f; - for (int i = 0; i < config_.octaves; ++i) { float noiseValue = Noise(x * frequency, y * frequency); value += noiseValue * amplitude; - amplitude *= config_.persistence; frequency *= config_.lacunarity; } - return value; } glm::vec3 WorldGenerator::CalculateNormal(float x, float z, float height) { - const float epsilon = 0.1f + height; - - // Sample heights at neighboring points + (void)height; + const float epsilon = 0.1f; float h1 = GetTerrainHeight(x + epsilon, z); float h2 = GetTerrainHeight(x - epsilon, z); float h3 = GetTerrainHeight(x, z + epsilon); float h4 = GetTerrainHeight(x, z - epsilon); - - // Calculate gradient float dx = (h1 - h2) / (2.0f * epsilon); float dz = (h3 - h4) / (2.0f * epsilon); - - // Normal vector (pointing up, adjusted by gradient) - glm::vec3 normal(-dx, 1.0f, -dz); - return glm::normalize(normal); + return glm::normalize(glm::vec3(-dx, 1.0f, -dz)); } void WorldGenerator::GenerateLowPolyTerrain(WorldChunk& chunk, int chunkX, int chunkZ) { - // This function populates the chunk's heightmap - const int chunkSize = WorldChunk::CHUNK_SIZE; - - // Loop over the grid size that matches heightmap_ dimensions - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - float worldX = (chunkX * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunkZ * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - // Corrected 1D index: heightmap_ is sized chunkSize * chunkSize - int index = z * chunkSize + x; - chunk.heightmap_[index] = height; - } - } + (void)chunk; + (void)chunkX; + (void)chunkZ; } void WorldGenerator::AddTrees(WorldChunk& chunk, BiomeType biome) { - if (biome != BiomeType::FOREST) return; - - std::lock_guard lock(rngMutex_); - - const int chunkSize = WorldChunk::CHUNK_SIZE; - std::uniform_int_distribution treeDist(0, 10); - - for (int x = 2; x < chunkSize - 2; x += 3) { - for (int z = 2; z < chunkSize - 2; z += 3) { - // 30% chance to place a tree - if (treeDist(rng_) < 3) { - // Check if position is valid (on ground, not underwater) - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height > config_.waterLevel + 0.5f) { - // Place tree trunk (3-5 blocks high) - int treeHeight = 4 + (rng_() % 3); - int baseY = static_cast(height); - - for (int y = 0; y < treeHeight; ++y) { - if (baseY + y < chunkSize) { - chunk.SetBlock(x, baseY + y, z, BlockType::WOOD); - } - } - - // Place leaves - if (treeHeight >= 3) { - int leavesStart = baseY + treeHeight - 2; - for (int dx = -2; dx <= 2; ++dx) { - for (int dz = -2; dz <= 2; ++dz) { - for (int dy = 0; dy <= 2; ++dy) { - // Skip corners for more natural shape - if (abs(dx) == 2 && abs(dz) == 2) continue; - - int leafX = x + dx; - int leafZ = z + dz; - int leafY = leavesStart + dy; - - if (leafX >= 0 && leafX < chunkSize && - leafZ >= 0 && leafZ < chunkSize && - leafY < chunkSize) { - chunk.SetBlock(leafX, leafY, leafZ, BlockType::LEAVES); - } - } - } - } - } - } - } - } - } + (void)chunk; + (void)biome; } void WorldGenerator::AddRocks(WorldChunk& chunk, BiomeType biome) { - if (biome != BiomeType::MOUNTAIN && biome != BiomeType::DESERT) return; - - std::lock_guard lock(rngMutex_); - - const int chunkSize = WorldChunk::CHUNK_SIZE; - std::uniform_int_distribution rockDist(0, 20); - - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - // 5% chance to place a rock - if (rockDist(rng_) == 0) { - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height > config_.waterLevel + 0.5f) { - int baseY = static_cast(height); - - // Place a 1x1x1 rock - if (baseY < chunkSize - 1) { - chunk.SetBlock(x, baseY, z, BlockType::STONE); - } - } - } - } - } + (void)chunk; + (void)biome; } void WorldGenerator::AddWaterPlane(WorldChunk& chunk) { - const int chunkSize = WorldChunk::CHUNK_SIZE; - int waterY = static_cast(config_.waterLevel); - - // Only add water plane if water level is within chunk bounds - if (waterY >= 0 && waterY < chunkSize) { - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - // Check if this position should have water - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height <= config_.waterLevel) { - chunk.SetBlock(x, waterY, z, BlockType::WATER); - } - } - } - } + (void)chunk; } void WorldGenerator::GenerateForestFeatures(WorldChunk& chunk) { - // Locking is done inside AddTrees and AddRocks - AddTrees(chunk, BiomeType::FOREST); - AddRocks(chunk, BiomeType::FOREST); + (void)chunk; } void WorldGenerator::GenerateMountainFeatures(WorldChunk& chunk) { - AddRocks(chunk, BiomeType::MOUNTAIN); - - std::lock_guard lock(rngMutex_); - - // Add snow on high mountains - const int chunkSize = WorldChunk::CHUNK_SIZE; - int snowLevel = static_cast(config_.waterLevel + 20); - - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height > snowLevel) { - int snowY = static_cast(height); - if (snowY < chunkSize) { - // Replace top block with snow - chunk.SetBlock(x, snowY, z, BlockType::SNOW); - } - } - } - } + (void)chunk; } void WorldGenerator::GenerateDesertFeatures(WorldChunk& chunk) { - AddRocks(chunk, BiomeType::DESERT); - - std::lock_guard lock(rngMutex_); - - // Add occasional cactus - const int chunkSize = WorldChunk::CHUNK_SIZE; - std::uniform_int_distribution cactusDist(0, 30); - - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - if (cactusDist(rng_) == 0) { - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height > config_.waterLevel + 0.5f) { - int baseY = static_cast(height); - int cactusHeight = 2 + (rng_() % 3); - - for (int y = 0; y < cactusHeight; ++y) { - if (baseY + y < chunkSize) { - // Use wood block as placeholder for cactus - chunk.SetBlock(x, baseY + y, z, BlockType::WOOD); - } - } - } - } - } - } + (void)chunk; } void WorldGenerator::GeneratePlainsFeatures(WorldChunk& chunk) { - std::unique_lock lock(rngMutex_); - - // Plains have few features - just occasional grass/trees - const int chunkSize = WorldChunk::CHUNK_SIZE; - std::uniform_int_distribution featureDist(0, 50); - - for (int x = 0; x < chunkSize; ++x) { - for (int z = 0; z < chunkSize; ++z) { - if (featureDist(rng_) == 0) { - float worldX = (chunk.GetChunkX() * chunkSize + x) * WorldChunk::BLOCK_SIZE; - float worldZ = (chunk.GetChunkZ() * chunkSize + z) * WorldChunk::BLOCK_SIZE; - - float height = GetTerrainHeight(worldX, worldZ); - - if (height > config_.waterLevel + 0.5f) { - int baseY = static_cast(height); - - // Small chance for a tree - if (rng_() % 10 == 0) { - lock.unlock(); - AddTrees(chunk, BiomeType::FOREST); - lock.lock(); - } - // Otherwise just place a tall grass block (using leaves as placeholder) - else if (baseY < chunkSize - 1) { - chunk.SetBlock(x, baseY, z, BlockType::LEAVES); - } - } - } - } - } -} \ No newline at end of file + (void)chunk; +} diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index 4584ca7..590ff73 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -310,53 +310,41 @@ void BinarySession::DoBinaryRead() { } void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& message) { - // Update heartbeat on any valid message last_heartbeat_ = std::chrono::steady_clock::now(); - - // Record message for statistics RecordMessageReceived(message.data.size()); - - // Check rate limiting if (!CheckRateLimit()) { SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Rate limit exceeded", 429); return; } - - // Handle special message types switch (message.header.message_type) { case BinaryProtocol::MESSAGE_TYPE_HEARTBEAT: - // This is a ping, send pong Send(BinaryProtocol::MESSAGE_TYPE_HEARTBEAT, message.data); return; - case BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION: HandleProtocolNegotiation(message.data); return; - case BinaryProtocol::MESSAGE_TYPE_CHUNK_REQUEST: { BinaryProtocol::BinaryReader reader(message.data.data(), message.data.size()); ChunkData req; req.x = reader.ReadInt32(); req.z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); + req.player_x = reader.ReadFloat(); + req.player_y = reader.ReadFloat(); + req.player_z = reader.ReadFloat(); req.session_id = sessionId_; GameLogic::GetInstance().OnChunkRequest(req); break; } - case BinaryProtocol::MESSAGE_TYPE_ERROR: Logger::Warn("Session {} received error from client", sessionId_); return; - case BinaryProtocol::MESSAGE_TYPE_SUCCESS: - // Process success acknowledgment return; } - // Look for registered binary handler std::lock_guard lock(binary_handlers_mutex_); auto it = binary_handlers_.find(message.header.message_type); - if (it != binary_handlers_.end()) { try { it->second(message.header.message_type, message.data); diff --git a/src/network/GameServer.cpp b/src/network/GameServer.cpp index 4d199f7..38b6bb5 100644 --- a/src/network/GameServer.cpp +++ b/src/network/GameServer.cpp @@ -176,6 +176,9 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game req.x = reader.ReadInt32(); req.z = reader.ReadInt32(); req.lod = reader.ReadUInt8(); + req.player_x = reader.ReadFloat(); + req.player_y = reader.ReadFloat(); + req.player_z = reader.ReadFloat(); req.session_id = session->GetSessionId(); game_logic.OnChunkRequest(req); break; @@ -303,6 +306,9 @@ void GameServer::InitSessionFactory(int workerId, ProcessPool* processPool, Game req.x = msg.value("x", 0); req.z = msg.value("z", 0); req.lod = static_cast(msg.value("lod", 0)); + req.player_x = msg.value("player_x", 0.0f); + req.player_y = msg.value("player_y", 0.0f); + req.player_z = msg.value("player_z", 0.0f); req.session_id = session->GetSessionId(); game_logic.OnChunkRequest(req); } diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index 3136840..697e7f0 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -156,28 +156,39 @@ std::string WebSocketSession::GetRemoteAddress() const { } } +void WebSocketSession::OnClose(uint16_t code, const std::string& reason) { + Logger::Info("WebSocketSession closed: code={}, reason={}", code, reason); + if (closeHandler_) { + closeHandler_(); + } +} + +void WebSocketSession::SetBinaryMessageHandler(BinaryMessageHandler handler) { + binary_handler_ = std::move(handler); +} + +void WebSocketSession::SetDefaultBinaryMessageHandler(BinaryMessageHandler handler) { + default_binary_handler_ = std::move(handler); +} + void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) { Logger::Trace("WebSocketSession {} received {} bytes, opcode: {}", sessionId_, msg.data.size(), (int)msg.opcode); if (msg.opcode == WebSocketProtocol::OP_TEXT) { std::string text = msg.GetText(); Logger::Trace("WebSocketSession {} received TEXT: {}", sessionId_, text); - if (protocolMode_ == ProtocolMode::Binary) { - Logger::Warn("Session {} sent TEXT frame but negotiated BINARY – ignoring", sessionId_); - return; - } if (messageHandler_) { try { auto json = nlohmann::json::parse(text); Logger::Trace("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); std::string msgType = json.value("msg", ""); if (msgType == "protocol_negotiation") { - protocolMode_ = ProtocolMode::Json; + protocolMode_ = ProtocolMode::Binary; std::string protocol = json.value("protocol", ""); int version = json.value("version", 0); Logger::Info("WebSocketSession {} client negotiated protocol: {} v{}", sessionId_, protocol, version); nlohmann::json response = { {"msg", "protocol_negotiation"}, - {"protocol", "websocket"}, + {"protocol", "binary"}, {"version", 1}, {"status", "ok"} }; @@ -188,22 +199,14 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) } catch (const std::exception& err) { Logger::Error("WebSocketSession {} invalid JSON: {}", sessionId_, err.what()); } - } else { - Logger::Error("WebSocketSession {} has no messageHandler_ set!", sessionId_); } } else if (msg.opcode == WebSocketProtocol::OP_BINARY) { Logger::Trace("WebSocketSession {} received BINARY ({} bytes)", sessionId_, msg.data.size()); - if (protocolMode_ == ProtocolMode::Json) { - Logger::Warn("Session {} sent BINARY frame but negotiated JSON – ignoring", sessionId_); - return; - } try { auto binaryMsg = BinaryProtocol::BinaryMessage::Deserialize(msg.data.data(), msg.data.size()); - Logger::Trace("WebSocketSession {} binary message type: {}", sessionId_, binaryMsg.header.message_type); if (binaryMsg.header.message_type == BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION) { protocolMode_ = ProtocolMode::Binary; - Logger::Info("WebSocketSession {} switched to binary protocol mode", sessionId_); auto caps = BinaryProtocol::ProtocolCapabilities::Deserialize(binaryMsg.data.data(), binaryMsg.data.size()); nlohmann::json response = { {"msg", "protocol_negotiation"}, @@ -218,28 +221,9 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) binary_handler_(binaryMsg.header.message_type, binaryMsg.data); } else if (default_binary_handler_) { default_binary_handler_(binaryMsg.header.message_type, binaryMsg.data); - } else { - Logger::Warn("WebSocketSession {}: no binary handler for message type {}", - sessionId_, binaryMsg.header.message_type); } } catch (const std::exception& e) { - Logger::Error("WebSocketSession {}: failed to deserialize binary message: {}", - sessionId_, e.what()); + Logger::Error("WebSocketSession {}: failed to deserialize binary message: {}", sessionId_, e.what()); } } } - -void WebSocketSession::OnClose(uint16_t code, const std::string& reason) { - Logger::Info("WebSocketSession closed: code={}, reason={}", code, reason); - if (closeHandler_) { - closeHandler_(); - } -} - -void WebSocketSession::SetBinaryMessageHandler(BinaryMessageHandler handler) { - binary_handler_ = std::move(handler); -} - -void WebSocketSession::SetDefaultBinaryMessageHandler(BinaryMessageHandler handler) { - default_binary_handler_ = std::move(handler); -}