diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index 06ba2578..34600b47 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -97,6 +97,7 @@ public: void setExternalMoving(bool moving) { externalMoving_ = moving; } void setFacingYaw(float yaw) { facingYaw = yaw; } // For taxi/scripted movement void clearMovementInputs(); + void suppressMovementFor(float seconds) { movementSuppressTimer_ = seconds; } // Trigger mount jump (applies vertical velocity for physics hop) void triggerMountJump(); @@ -211,6 +212,9 @@ private: static constexpr float SWIM_SINK_SPEED = -3.0f; static constexpr float WATER_SURFACE_OFFSET = 0.9f; + // Movement input suppression (after teleport/portal, ignore held keys) + float movementSuppressTimer_ = 0.0f; + // State bool enabled = true; bool sitting = false; diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index d29ada83..ab14021c 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -244,6 +244,7 @@ private: glm::vec3 shadowCenter = glm::vec3(0.0f); bool shadowCenterInitialized = false; bool shadowsEnabled = true; + float shadowDistance_ = 72.0f; // Shadow frustum half-extent (default: 72 units) uint32_t shadowFrameCounter_ = 0; @@ -254,6 +255,8 @@ public: void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; } bool areShadowsEnabled() const { return shadowsEnabled; } + void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 200.0f); } + float getShadowDistance() const { return shadowDistance_; } void setMsaaSamples(VkSampleCountFlagBits samples); void setWaterRefractionEnabled(bool enabled); diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 0a2f72f8..7e428523 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -87,6 +87,7 @@ private: bool pendingVsync = false; int pendingResIndex = 0; bool pendingShadows = true; + float pendingShadowDistance = 72.0f; bool pendingWaterRefraction = false; int pendingMasterVolume = 100; int pendingMusicVolume = 30; diff --git a/src/core/application.cpp b/src/core/application.cpp index d5da23ff..417d6438 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1555,8 +1555,10 @@ void Application::setupUICallbacks() { taxiLandingClampTimer_ = 0.0f; lastTaxiFlight_ = false; // Stop any movement that was active before the teleport - if (renderer->getCameraController()) + if (renderer->getCameraController()) { renderer->getCameraController()->clearMovementInputs(); + renderer->getCameraController()->suppressMovementFor(0.5f); + } return; } @@ -1573,8 +1575,10 @@ void Application::setupUICallbacks() { taxiLandingClampTimer_ = 0.0f; lastTaxiFlight_ = false; // Stop any movement that was active before the teleport - if (renderer && renderer->getCameraController()) + if (renderer && renderer->getCameraController()) { renderer->getCameraController()->clearMovementInputs(); + renderer->getCameraController()->suppressMovementFor(1.0f); + } loadOnlineWorldTerrain(mapId, x, y, z); // loadedMapId_ is set inside loadOnlineWorldTerrain (including // any deferred entries it processes), so we must NOT override it here. diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 392ef71b..4103cc9f 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -200,16 +200,22 @@ void CameraController::update(float deltaTime) { // Don't process keyboard input when UI text input (e.g. chat box) has focus bool uiWantsKeyboard = ImGui::GetIO().WantTextInput; + // Suppress movement input after teleport/portal (keys may still be held) + if (movementSuppressTimer_ > 0.0f) { + movementSuppressTimer_ -= deltaTime; + } + bool movementSuppressed = movementSuppressTimer_ > 0.0f; + // Determine current key states - bool keyW = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_W); - bool keyS = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_S); - bool keyA = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_A); - bool keyD = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_D); - bool keyQ = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_Q); - bool keyE = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_E); + bool keyW = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_W); + bool keyS = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_S); + bool keyA = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_A); + bool keyD = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_D); + bool keyQ = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_Q); + bool keyE = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyPressed(SDL_SCANCODE_E); bool shiftDown = !uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LSHIFT) || input.isKeyPressed(SDL_SCANCODE_RSHIFT)); bool ctrlDown = !uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LCTRL) || input.isKeyPressed(SDL_SCANCODE_RCTRL)); - bool nowJump = !uiWantsKeyboard && !sitting && input.isKeyJustPressed(SDL_SCANCODE_SPACE); + bool nowJump = !uiWantsKeyboard && !sitting && !movementSuppressed && input.isKeyJustPressed(SDL_SCANCODE_SPACE); // Idle camera: any input resets the timer; timeout triggers a slow orbit pan bool anyInput = leftMouseDown || rightMouseDown || keyW || keyS || keyA || keyD || keyQ || keyE || nowJump; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 77b4c74a..5f3e48ae 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3766,10 +3766,10 @@ void Renderer::renderHUD() { // in createPerFrameResources() as part of the Vulkan shadow infrastructure. glm::mat4 Renderer::computeLightSpaceMatrix() { - constexpr float kShadowHalfExtent = 60.0f; - constexpr float kShadowLightDistance = 200.0f; + const float kShadowHalfExtent = shadowDistance_; + const float kShadowLightDistance = shadowDistance_ * 3.0f; constexpr float kShadowNearPlane = 1.0f; - constexpr float kShadowFarPlane = 450.0f; + const float kShadowFarPlane = shadowDistance_ * 6.5f; // Use active lighting direction so shadow projection matches main shading. // Fragment shaders derive lighting with `ldir = normalize(-lightDir.xyz)`, @@ -3973,7 +3973,7 @@ void Renderer::renderShadowPass() { vkCmdSetScissor(currentCmd, 0, 1, &sc); // Phase 7/8: render shadow casters - const float shadowCullRadius = 80.0f; + const float shadowCullRadius = shadowDistance_ * 1.35f; if (wmoRenderer) { wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius); } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 1d02d795..3f1c0eb9 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6180,6 +6180,7 @@ void GameScreen::renderSettingsWindow() { pendingVsync = window->isVsyncEnabled(); if (renderer) { renderer->setShadowsEnabled(pendingShadows); + renderer->setShadowDistance(pendingShadowDistance); // Read non-volume settings from actual state (volumes come from saved settings) if (auto* cameraController = renderer->getCameraController()) { pendingMouseSensitivity = cameraController->getMouseSensitivity(); @@ -6246,6 +6247,14 @@ void GameScreen::renderSettingsWindow() { if (renderer) renderer->setShadowsEnabled(pendingShadows); saveSettings(); } + if (pendingShadows) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f); + if (ImGui::SliderFloat("Distance##shadow", &pendingShadowDistance, 40.0f, 200.0f, "%.0f")) { + if (renderer) renderer->setShadowDistance(pendingShadowDistance); + saveSettings(); + } + } if (ImGui::Checkbox("Water Refraction", &pendingWaterRefraction)) { if (renderer) renderer->setWaterRefractionEnabled(pendingWaterRefraction); saveSettings(); @@ -6339,6 +6348,7 @@ void GameScreen::renderSettingsWindow() { pendingFullscreen = kDefaultFullscreen; pendingVsync = kDefaultVsync; pendingShadows = kDefaultShadows; + pendingShadowDistance = 72.0f; pendingGroundClutterDensity = kDefaultGroundClutterDensity; pendingAntiAliasing = 0; pendingNormalMapping = true; @@ -6350,7 +6360,10 @@ void GameScreen::renderSettingsWindow() { window->setVsync(pendingVsync); window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]); pendingWaterRefraction = false; - if (renderer) renderer->setShadowsEnabled(pendingShadows); + if (renderer) { + renderer->setShadowsEnabled(pendingShadows); + renderer->setShadowDistance(pendingShadowDistance); + } if (renderer) renderer->setWaterRefractionEnabled(pendingWaterRefraction); if (renderer) renderer->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT); if (renderer) { @@ -7364,6 +7377,7 @@ void GameScreen::saveSettings() { out << "auto_loot=" << (pendingAutoLoot ? 1 : 0) << "\n"; out << "ground_clutter_density=" << pendingGroundClutterDensity << "\n"; out << "shadows=" << (pendingShadows ? 1 : 0) << "\n"; + out << "shadow_distance=" << pendingShadowDistance << "\n"; out << "water_refraction=" << (pendingWaterRefraction ? 1 : 0) << "\n"; out << "antialiasing=" << pendingAntiAliasing << "\n"; out << "normal_mapping=" << (pendingNormalMapping ? 1 : 0) << "\n"; @@ -7449,6 +7463,7 @@ void GameScreen::loadSettings() { else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0); else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150); else if (key == "shadows") pendingShadows = (std::stoi(val) != 0); + else if (key == "shadow_distance") pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 200.0f); else if (key == "water_refraction") pendingWaterRefraction = (std::stoi(val) != 0); else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3); else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);