diff --git a/include/rendering/swim_effects.hpp b/include/rendering/swim_effects.hpp index 3ff643d4..15858258 100644 --- a/include/rendering/swim_effects.hpp +++ b/include/rendering/swim_effects.hpp @@ -24,6 +24,7 @@ public: void update(const Camera& camera, const CameraController& cc, const WaterRenderer& water, float deltaTime); void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet); + void spawnFootSplash(const glm::vec3& footPos, float waterH); private: struct Particle { diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 619d0796..ba26f5bb 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -2590,7 +2590,20 @@ void Renderer::update(float deltaTime) { float animDurationMs = 0.0f; if (characterRenderer->getAnimationState(characterInstanceId, animId, animTimeMs, animDurationMs) && shouldTriggerFootstepEvent(animId, animTimeMs, animDurationMs)) { - footstepManager->playFootstep(resolveFootstepSurface(), cameraController->isSprinting()); + auto surface = resolveFootstepSurface(); + footstepManager->playFootstep(surface, cameraController->isSprinting()); + // Play additional splash sound and spawn foot splash particles when wading + if (surface == audio::FootstepSurface::WATER) { + if (movementSoundManager) { + movementSoundManager->playWaterFootstep(audio::MovementSoundManager::CharacterSize::MEDIUM); + } + if (swimEffects && waterRenderer) { + auto wh = waterRenderer->getWaterHeightAt(characterPosition.x, characterPosition.y); + if (wh) { + swimEffects->spawnFootSplash(characterPosition, *wh); + } + } + } } mountFootstepNormInitialized = false; } else { diff --git a/src/rendering/swim_effects.cpp b/src/rendering/swim_effects.cpp index 38603be9..b2837167 100644 --- a/src/rendering/swim_effects.cpp +++ b/src/rendering/swim_effects.cpp @@ -345,6 +345,27 @@ void SwimEffects::spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, fl ripples.push_back(p); } +void SwimEffects::spawnFootSplash(const glm::vec3& footPos, float waterH) { + // Small burst of splash droplets at foot position (for wading) + constexpr int splashCount = 5; + for (int i = 0; i < splashCount; ++i) { + if (static_cast(ripples.size()) >= MAX_RIPPLE_PARTICLES) break; + Particle p; + float ox = randFloat(-0.4f, 0.4f); + float oy = randFloat(-0.4f, 0.4f); + p.position = glm::vec3(footPos.x + ox, footPos.y + oy, waterH + 0.1f); + // Small upward spray in random horizontal direction + float angle = randFloat(0.0f, 6.2832f); + float speed = randFloat(0.8f, 2.0f); + p.velocity = glm::vec3(std::cos(angle) * speed, std::sin(angle) * speed, randFloat(1.0f, 2.5f)); + p.lifetime = 0.0f; + p.maxLifetime = randFloat(0.3f, 0.6f); + p.size = randFloat(2.0f, 4.0f); + p.alpha = randFloat(0.4f, 0.7f); + ripples.push_back(p); + } +} + void SwimEffects::spawnBubble(const glm::vec3& pos, float /*waterH*/) { if (static_cast(bubbles.size()) >= MAX_BUBBLE_PARTICLES) return; diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index e10ebbed..89c1e509 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -782,7 +782,7 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, uint8_t basicType = (surface.liquidType == 0) ? 0 : ((surface.liquidType - 1) % 4); // WMO water gets no wave displacement — prevents visible slosh at // geometry edges (bridges, docks) where water is far below the surface. - float waveAmp = isWmoWater ? 0.0f : (basicType == 1 ? 0.35f : 0.18f); + float waveAmp = isWmoWater ? 0.0f : (basicType == 1 ? 0.35f : 0.08f); float waveFreq = canalProfile ? 0.35f : (basicType == 1 ? 0.20f : 0.30f); float waveSpeed = canalProfile ? 1.00f : (basicType == 1 ? 1.20f : 1.40f);