mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
Add mount rider bob and hoofbeat sounds, improve world map
- Rider character bobs with mount's run animation (sinusoidal, 0.12u amplitude) - Mount hoofbeat footstep sounds triggered at 4 points per animation cycle - M key opens map directly to player's current zone instead of continent - Mouse wheel scroll zooms map in/out between world, continent, and zone views - Fog of war darkens unexplored zones on continent view, clears on visit
This commit is contained in:
parent
0874f4f239
commit
35fff9307d
4 changed files with 174 additions and 11 deletions
|
|
@ -518,6 +518,7 @@ void Renderer::updateCharacterAnimation() {
|
|||
}
|
||||
|
||||
// Sync mount instance position and rotation
|
||||
float mountBob = 0.0f;
|
||||
if (mountInstanceId_ > 0) {
|
||||
characterRenderer->setInstancePosition(mountInstanceId_, characterPosition);
|
||||
float yawRad = glm::radians(characterYaw);
|
||||
|
|
@ -531,11 +532,18 @@ void Renderer::updateCharacterAnimation() {
|
|||
if (!haveMountState || curMountAnim != mountAnimId) {
|
||||
characterRenderer->playAnimation(mountInstanceId_, mountAnimId, true);
|
||||
}
|
||||
|
||||
// Rider bob: sinusoidal motion synced to mount's run animation
|
||||
if (moving && haveMountState && curMountDur > 1.0f) {
|
||||
float norm = std::fmod(curMountTime, curMountDur) / curMountDur;
|
||||
// Two bounces per stride cycle (horse gait), lowest at footfalls (0.22, 0.72)
|
||||
mountBob = std::sin(norm * 4.0f * 3.14159f) * 0.12f;
|
||||
}
|
||||
}
|
||||
|
||||
// Offset player Z above mount
|
||||
// Offset player Z above mount + bob
|
||||
glm::vec3 playerPos = characterPosition;
|
||||
playerPos.z += mountHeightOffset_;
|
||||
playerPos.z += mountHeightOffset_ + mountBob;
|
||||
characterRenderer->setInstancePosition(characterInstanceId, playerPos);
|
||||
return;
|
||||
}
|
||||
|
|
@ -993,10 +1001,44 @@ void Renderer::update(float deltaTime) {
|
|||
// Footsteps: animation-event driven + surface query at event time.
|
||||
if (footstepManager) {
|
||||
footstepManager->update(deltaTime);
|
||||
if (characterRenderer && characterInstanceId > 0 &&
|
||||
bool canPlayFootsteps = characterRenderer && characterInstanceId > 0 &&
|
||||
cameraController && cameraController->isThirdPerson() &&
|
||||
isFootstepAnimationState() && cameraController->isGrounded() &&
|
||||
!cameraController->isSwimming()) {
|
||||
cameraController->isGrounded() && !cameraController->isSwimming();
|
||||
|
||||
if (canPlayFootsteps && isMounted() && mountInstanceId_ > 0) {
|
||||
// Mount footsteps: use mount's animation for timing
|
||||
uint32_t animId = 0;
|
||||
float animTimeMs = 0.0f, animDurationMs = 0.0f;
|
||||
if (characterRenderer->getAnimationState(mountInstanceId_, animId, animTimeMs, animDurationMs) &&
|
||||
animDurationMs > 1.0f && cameraController->isMoving()) {
|
||||
float norm = std::fmod(animTimeMs, animDurationMs) / animDurationMs;
|
||||
if (norm < 0.0f) norm += 1.0f;
|
||||
|
||||
if (animId != mountFootstepLastAnimId) {
|
||||
mountFootstepLastAnimId = animId;
|
||||
mountFootstepLastNormTime = norm;
|
||||
mountFootstepNormInitialized = true;
|
||||
} else if (!mountFootstepNormInitialized) {
|
||||
mountFootstepNormInitialized = true;
|
||||
mountFootstepLastNormTime = norm;
|
||||
} else {
|
||||
// Horse gait: 4 hoofbeats per cycle
|
||||
auto crossed = [&](float eventNorm) {
|
||||
if (mountFootstepLastNormTime <= norm) {
|
||||
return mountFootstepLastNormTime < eventNorm && eventNorm <= norm;
|
||||
}
|
||||
return mountFootstepLastNormTime < eventNorm || eventNorm <= norm;
|
||||
};
|
||||
if (crossed(0.1f) || crossed(0.35f) || crossed(0.6f) || crossed(0.85f)) {
|
||||
footstepManager->playFootstep(resolveFootstepSurface(), true);
|
||||
}
|
||||
mountFootstepLastNormTime = norm;
|
||||
}
|
||||
} else {
|
||||
mountFootstepNormInitialized = false;
|
||||
}
|
||||
footstepNormInitialized = false; // Reset player footstep tracking
|
||||
} else if (canPlayFootsteps && isFootstepAnimationState()) {
|
||||
uint32_t animId = 0;
|
||||
float animTimeMs = 0.0f;
|
||||
float animDurationMs = 0.0f;
|
||||
|
|
@ -1004,8 +1046,10 @@ void Renderer::update(float deltaTime) {
|
|||
shouldTriggerFootstepEvent(animId, animTimeMs, animDurationMs)) {
|
||||
footstepManager->playFootstep(resolveFootstepSurface(), cameraController->isSprinting());
|
||||
}
|
||||
mountFootstepNormInitialized = false;
|
||||
} else {
|
||||
footstepNormInitialized = false;
|
||||
mountFootstepNormInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -347,6 +347,38 @@ int WorldMap::findBestContinentForPlayer(const glm::vec3& playerRenderPos) const
|
|||
return bestIdx;
|
||||
}
|
||||
|
||||
int WorldMap::findZoneForPlayer(const glm::vec3& playerRenderPos) const {
|
||||
float wowX = playerRenderPos.y; // north/south
|
||||
float wowY = playerRenderPos.x; // west/east
|
||||
|
||||
int bestIdx = -1;
|
||||
float bestArea = std::numeric_limits<float>::max();
|
||||
|
||||
for (int i = 0; i < static_cast<int>(zones.size()); i++) {
|
||||
const auto& z = zones[i];
|
||||
if (z.areaID == 0) continue; // skip continent-level entries
|
||||
|
||||
float minX = std::min(z.locLeft, z.locRight);
|
||||
float maxX = std::max(z.locLeft, z.locRight);
|
||||
float minY = std::min(z.locTop, z.locBottom);
|
||||
float maxY = std::max(z.locTop, z.locBottom);
|
||||
float spanX = maxX - minX;
|
||||
float spanY = maxY - minY;
|
||||
if (spanX < 0.001f || spanY < 0.001f) continue;
|
||||
|
||||
bool contains = (wowX >= minX && wowX <= maxX && wowY >= minY && wowY <= maxY);
|
||||
if (contains) {
|
||||
float area = spanX * spanY;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
bool WorldMap::zoneBelongsToContinent(int zoneIdx, int contIdx) const {
|
||||
if (zoneIdx < 0 || zoneIdx >= static_cast<int>(zones.size())) return false;
|
||||
if (contIdx < 0 || contIdx >= static_cast<int>(zones.size())) return false;
|
||||
|
|
@ -691,6 +723,52 @@ glm::vec2 WorldMap::renderPosToMapUV(const glm::vec3& renderPos, int zoneIdx) co
|
|||
return glm::vec2(u, v);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Exploration tracking
|
||||
// --------------------------------------------------------
|
||||
|
||||
void WorldMap::updateExploration(const glm::vec3& playerRenderPos) {
|
||||
int zoneIdx = findZoneForPlayer(playerRenderPos);
|
||||
if (zoneIdx >= 0) {
|
||||
exploredZones.insert(zoneIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldMap::zoomIn(const glm::vec3& playerRenderPos) {
|
||||
if (viewLevel == ViewLevel::WORLD) {
|
||||
// World → Continent
|
||||
if (continentIdx >= 0) {
|
||||
loadZoneTextures(continentIdx);
|
||||
compositeZone(continentIdx);
|
||||
currentIdx = continentIdx;
|
||||
viewLevel = ViewLevel::CONTINENT;
|
||||
}
|
||||
} else if (viewLevel == ViewLevel::CONTINENT) {
|
||||
// Continent → Zone (use player's current zone)
|
||||
int zoneIdx = findZoneForPlayer(playerRenderPos);
|
||||
if (zoneIdx >= 0 && zoneBelongsToContinent(zoneIdx, continentIdx)) {
|
||||
loadZoneTextures(zoneIdx);
|
||||
compositeZone(zoneIdx);
|
||||
currentIdx = zoneIdx;
|
||||
viewLevel = ViewLevel::ZONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorldMap::zoomOut() {
|
||||
if (viewLevel == ViewLevel::ZONE) {
|
||||
// Zone → Continent
|
||||
if (continentIdx >= 0) {
|
||||
compositeZone(continentIdx);
|
||||
currentIdx = continentIdx;
|
||||
viewLevel = ViewLevel::CONTINENT;
|
||||
}
|
||||
} else if (viewLevel == ViewLevel::CONTINENT) {
|
||||
// Continent → World
|
||||
enterWorldView();
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Main render
|
||||
// --------------------------------------------------------
|
||||
|
|
@ -700,6 +778,11 @@ void WorldMap::render(const glm::vec3& playerRenderPos, int screenWidth, int scr
|
|||
|
||||
auto& input = core::Input::getInstance();
|
||||
|
||||
// Track exploration even when map is closed
|
||||
if (!zones.empty()) {
|
||||
updateExploration(playerRenderPos);
|
||||
}
|
||||
|
||||
// When map is open, always allow M/Escape to close (bypass ImGui keyboard capture)
|
||||
if (open) {
|
||||
if (input.isKeyJustPressed(SDL_SCANCODE_M) ||
|
||||
|
|
@ -707,6 +790,14 @@ void WorldMap::render(const glm::vec3& playerRenderPos, int screenWidth, int scr
|
|||
open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse wheel: scroll up = zoom in, scroll down = zoom out
|
||||
auto& io = ImGui::GetIO();
|
||||
if (io.MouseWheel > 0.0f) {
|
||||
zoomIn(playerRenderPos);
|
||||
} else if (io.MouseWheel < 0.0f) {
|
||||
zoomOut();
|
||||
}
|
||||
} else {
|
||||
auto& io = ImGui::GetIO();
|
||||
if (!io.WantCaptureKeyboard && input.isKeyJustPressed(SDL_SCANCODE_M)) {
|
||||
|
|
@ -721,8 +812,15 @@ void WorldMap::render(const glm::vec3& playerRenderPos, int screenWidth, int scr
|
|||
compositedIdx = -1;
|
||||
}
|
||||
|
||||
// Ensure continent textures are loaded and composited
|
||||
if (continentIdx >= 0) {
|
||||
// Open directly to the player's current zone
|
||||
int playerZone = findZoneForPlayer(playerRenderPos);
|
||||
if (playerZone >= 0 && continentIdx >= 0 &&
|
||||
zoneBelongsToContinent(playerZone, continentIdx)) {
|
||||
loadZoneTextures(playerZone);
|
||||
compositeZone(playerZone);
|
||||
currentIdx = playerZone;
|
||||
viewLevel = ViewLevel::ZONE;
|
||||
} else if (continentIdx >= 0) {
|
||||
loadZoneTextures(continentIdx);
|
||||
compositeZone(continentIdx);
|
||||
currentIdx = continentIdx;
|
||||
|
|
@ -942,17 +1040,25 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi
|
|||
float sx1 = imgMin.x + zuMax * displayW;
|
||||
float sy1 = imgMin.y + zvMax * displayH;
|
||||
|
||||
bool explored = exploredZones.count(zi) > 0;
|
||||
|
||||
// Check hover
|
||||
bool hovered = (mousePos.x >= sx0 && mousePos.x <= sx1 &&
|
||||
mousePos.y >= sy0 && mousePos.y <= sy1);
|
||||
|
||||
// Fog of war: darken unexplored zones
|
||||
if (!explored) {
|
||||
drawList->AddRectFilled(ImVec2(sx0, sy0), ImVec2(sx1, sy1),
|
||||
IM_COL32(0, 0, 0, 160));
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
hoveredZone = zi;
|
||||
drawList->AddRectFilled(ImVec2(sx0, sy0), ImVec2(sx1, sy1),
|
||||
IM_COL32(255, 255, 200, 40));
|
||||
drawList->AddRect(ImVec2(sx0, sy0), ImVec2(sx1, sy1),
|
||||
IM_COL32(255, 215, 0, 180), 0.0f, 0, 2.0f);
|
||||
} else {
|
||||
} else if (explored) {
|
||||
drawList->AddRect(ImVec2(sx0, sy0), ImVec2(sx1, sy1),
|
||||
IM_COL32(255, 255, 255, 30), 0.0f, 0, 1.0f);
|
||||
}
|
||||
|
|
@ -1022,11 +1128,11 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi
|
|||
// Help text
|
||||
const char* helpText;
|
||||
if (viewLevel == ViewLevel::ZONE) {
|
||||
helpText = "Right-click to zoom out | M or Escape to close";
|
||||
helpText = "Scroll out or right-click to zoom out | M or Escape to close";
|
||||
} else if (viewLevel == ViewLevel::WORLD) {
|
||||
helpText = "Select a continent | M or Escape to close";
|
||||
helpText = "Select a continent | Scroll in to zoom | M or Escape to close";
|
||||
} else {
|
||||
helpText = "Click a zone to zoom in | Right-click for World | M or Escape to close";
|
||||
helpText = "Click zone or scroll in to zoom | Scroll out / right-click for World | M or Escape to close";
|
||||
}
|
||||
ImVec2 textSize = ImGui::CalcTextSize(helpText);
|
||||
float textY = mapY + displayH + 8.0f;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue