#include #include #include #include #include #include #include #include #include #include #include "entity/player/PlayerController.hpp" #include "entity/player/PlayerRenderer.hpp" #include "net/NetworkManager.hpp" #include "ui/LoginWindow.hpp" #include "sky/Sky.hpp" #include "render/RenderContext.hpp" #include "utils/Coords.hpp" #include "terrain/Heightmap.hpp" #include "config.hpp" //Forwarddeclaration of login window functionbool ShowLoginWindow(std::shared_ptr network, LoginResult& result) class Game { GameConfig config; PlayerController playerController; std::unique_ptr playerRenderer; RenderContext renderContext; Model terrainModel; Heightmap heightmap; std::shared_ptr network; std::unique_ptr sky; Vector3 playerPos{0, 0, 0}; Vector3 playerVelocity{0, 0, 0}; float playerYaw = 0.0f; float playerPitch = 0.0f; Texture2D terrainTexture; // Movement tracking float lastMovementUpdate = 0.0f; const float movementUpdateInterval = 0.05f; // Send updates every 50ms // Debug options bool showDebugAxes = false; bool showWorldBounds = false; // Player info std::string currentUsername = ""; public: Game(const GameConfig& cfg) : config(cfg), network(std::make_shared()) { // Show login window first LoginResult loginResult; if (!ShowLoginWindow(network, loginResult)) { std::cerr << "Login cancelled or failed\n"; return; } currentUsername = "Player" + std::to_string(loginResult.playerID); // Connect to world server with auth token network->connectToWorldServer(loginResult.worldHost, loginResult.worldPort); network->sendAuth(loginResult.token); // Wait for authentication float authTimeout = 5.0f; float authWaitTime = 0.0f; while (!network->isAuthenticated() && authWaitTime < authTimeout) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); authWaitTime += 0.1f; } if (!network->isAuthenticated()) { std::cerr << "Failed to authenticate with world server\n"; return; } // Now initialize game window // Initialize network manager network = std::make_shared(); // Show login window LoginWindow loginWindow; if (!loginWindow.Run(network)) { std::cerr << "Login cancelled or failed\n"; return; } // Parse world server URL std::string worldUrl = network->getWorldServerUrl(); std::string worldHost = "localhost"; uint16_t worldPort = 8082; size_t colonPos = worldUrl.find(':'); if (colonPos != std::string::npos) { worldHost = worldUrl.substr(0, colonPos); worldPort = std::stoi(worldUrl.substr(colonPos + 1)); } // Connect to world server if (!network->connectToWorld(worldHost, worldPort, network->getAuthToken())) { std::cerr << "Failed to connect to world server\n"; return; } // Initialize game window InitWindow(config.windowWidth, config.windowHeight, config.windowTitle.c_str()); SetTargetFPS(config.targetFPS); // Initialize components after window is created playerRenderer = std::make_unique(); sky = std::make_unique(); // Configure and load heightmap Heightmap::Config heightConfig; heightConfig.unitsPerSample = config.unitsPerSample; heightConfig.heightScale = config.heightScale; heightmap = Heightmap(heightConfig); if (!heightmap.load(config.heightmapFile)) { std::cerr << "Failed to load heightmap\n"; } else { // Update world bounds based on loaded heightmap Coords::setWorldBounds(heightmap.getWorldBounds()); std::cout << "Loaded heightmap: " << heightmap.getSamplesPerSide() << "x" << heightmap.getSamplesPerSide() << " samples, " << "world size: " << heightmap.getWorldWidth() << "x" << heightmap.getWorldHeight() << " units\n"; } // Load textures terrainTexture = LoadTexture("../assets/textures/black.png"); // Create terrain model auto terrainMesh = heightmap.generateMesh(); terrainModel = LoadModelFromMesh(terrainMesh); terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = terrainTexture; } ~Game() { // Disconnect from servers if (networknetwork)-> { network->disconnect->(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (IsWindowReady()) { UnloadTexture(terrainTexture); UnloadModel(terrainModel); CloseWindow(); } } void run() { if (!IsWindowReady()) return; while (!WindowShouldClose()) { update(); render(); } } private: void update() { if (!network->isConnected()) { // Lost connection - could show reconnect UI here if (!network->isConnected()) { return; } // Update sky with server time float serverTime = network->->getServerTimeOfDay(); sky->updateFromServerTime(serverTime); // Time of day controls (for testing) if (IsKeyPressed(KEY_T)) { float currentTime = sky->getTimeOfDay(); currentTime += 0.1f; if (currentTime > 1.0f) currentTime -= 1.0f; sky->setTimeOfDay(currentTime); } // Debug toggles if (IsKeyPressed(KEY_F1)) showDebugAxes = !showDebugAxes; if (IsKeyPressed(KEY_F2)) showWorldBounds = !showWorldBounds; // Get server position playerPos = network->->getPosition(); playerController.setPlayerPosition(playerPos); -> float deltaTime = GetFrameTime(); playerController.update(deltaTime); // Get movement input Vector3 moveInput = playerController.getMoveInput(); // Calculate velocity based on input const float moveSpeed = 10.0f; playerVelocity = Vector3Scale(moveInput, moveSpeed); // Get camera orientation for yaw/pitch Camera camera = playerController.getCamera(); Vector3 forward = Vector3Subtract(camera.target, camera.position); playerYaw = atan2f(forward.x, forward.z); playerPitch = atan2f(forward.y, sqrtf(forward.x * forward.x + forward.z * forward.z)); // Send movement updates at regular intervals or when moving -> float currentTime = GetTime(); if (currentTime - lastMovementUpdate >= movementUpdateInterval) { network->sendMovement->(playerPos, playerYaw, playerPitch, playerVelocity); lastMovementUpdate = currentTime; ->std::this_thread::sleep_for(std::chrono::milliseconds(100))break // Exit game loop } } void render() { BeginDrawing(); ClearBackground(SKYBLUE); // Begin 3D rendering with render context renderContext.begin3D(playerController.getCamera()); // Submit skybox first (lowest layer) renderContext.submitCustom([this]() { if (sky) { sky->renderSkybox(playerController.getCamera()); } }, RenderContext::RenderLayer::SKYBOX); // Submit terrain renderContext.submitModel(terrainModel, {0, 0, 0}, 1.0f, WHITE, RenderContext::RenderLayer::TERRAIN); // Submit players ->// Local player playerRenderer->renderLocalPlayer(renderContext, playerPos, "blue")"blue"); // Remote players auto remotePlayers = network->->getRemotePlayers(); playerRenderer->renderRemotePlayers(renderContext, remotePlayers); // Debug rendering if (showDebugAxes) { renderContext.submitCustom([this]() { Coords::drawDebugAxes(playerPos, 2.0f); Coords::drawDebugAxes({0, 0, 0}, 5.0f); }, RenderContext::RenderLayer::TRANSPARENT); } if (showWorldBounds) { renderContext.submitCustom([]() { Coords::drawWorldBounds(); }, RenderContext::RenderLayer::TRANSPARENT); } // Execute all render commands in order renderContext.end3D(); // UI DrawText(TextFormat("PlayerPlayer IDID: %dd", network->getPlayerIDnetwork->getPlayerID()), 10, 10, 20, WHITE); DrawText("WASD: Move | Q/E: Strafe | Right-Click: Rotate Camera", 10, 35, 20, WHITE); DrawText("Mouse Wheel: Zoom | ESC: Exit", 10, 60, 20, WHITE); DrawText("T: Change Time | F1: Debug Axes | F2: World Bounds", 10, 85, 20, WHITE); // Show time of day float timeHour = sky->getTimeOfDay() * 24.0f; int hour = (int)timeHour; int minute = (int)((timeHour - hour) * 60); DrawText(TextFormat("Time: %02d:%02d", hour, minute), 10, 110110, 20, WHITE); // Show position if debug is on if (showDebugAxes) { DrawText(TextFormat("Pos: %.1f, %.1f, %.1f", playerPos.x, playerPos.y, playerPos.z), 10, 135135, 20, YELLOW); } DrawFPS(10, showDebugAxes ? 160160 : 135); EndDrawing(135); EndDrawing(); } }; int main(int argc, char* argv[]) { GameConfig config = GameConfig::fromArgs(argc, argv); // Show configuration if debug is enabled if (config.showDebugInfo) { std::cout << "Configuration:\n"; std::cout << " Heightmap: " << config.heightmapFile << "\n"; std::cout << " Units per sample: " << config.unitsPerSample << "\n"; std::cout << " Height scale: " << config.heightScale << "\n"; } Game game(config); game.run(); return 0; }