mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: animation stutter, resolution crash, memory cap, spell tooltip hints, GO collision
- Animation stutter: skip playAnimation(Run) for the local player in the server movement callback — the player renderer state machine already manages it; resetting animTime on every movement packet caused visible stutter - Resolution crash: reorder swapchain recreation so old swapchain is only destroyed after confirming the new build succeeded; add null-swapchain guard in beginFrame to survive the retry window - Memory cap: reduce cache budget from 80% uncapped to 50% hard-capped at 16 GB to prevent excessive RAM use on high-memory systems - Spell tooltip: suppress "Drag to action bar / Double-click to cast" hints when the tooltip is shown from the action bar (showUsageHints=false) - M2 collision: add watermelon/melon/squash/gourd to foliage (no-collision); exclude chair/bench/stool/seat/throne from smallSolidProp so invisible chair bounding boxes no longer trap the player
This commit is contained in:
parent
8f2974b17c
commit
19eb7a1fb7
7 changed files with 60 additions and 30 deletions
|
|
@ -94,8 +94,8 @@ private:
|
||||||
VkDescriptorSet getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager);
|
VkDescriptorSet getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager);
|
||||||
const SpellInfo* getSpellInfo(uint32_t spellId) const;
|
const SpellInfo* getSpellInfo(uint32_t spellId) const;
|
||||||
|
|
||||||
// Tooltip rendering helper
|
// Tooltip rendering helper (showUsageHints=false when called from action bar)
|
||||||
void renderSpellTooltip(const SpellInfo* info, game::GameHandler& gameHandler);
|
void renderSpellTooltip(const SpellInfo* info, game::GameHandler& gameHandler, bool showUsageHints = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
||||||
|
|
@ -2553,13 +2553,19 @@ void Application::setupUICallbacks() {
|
||||||
// Don't override Death animation (1). The per-frame sync loop will return to
|
// Don't override Death animation (1). The per-frame sync loop will return to
|
||||||
// Stand when movement stops.
|
// Stand when movement stops.
|
||||||
if (durationMs > 0) {
|
if (durationMs > 0) {
|
||||||
uint32_t curAnimId = 0; float curT = 0.0f, curDur = 0.0f;
|
// Player animation is managed by the local renderer state machine —
|
||||||
auto* cr = renderer->getCharacterRenderer();
|
// don't reset it here or every server movement packet restarts the
|
||||||
bool gotState = cr->getAnimationState(instanceId, curAnimId, curT, curDur);
|
// run cycle from frame 0, causing visible stutter.
|
||||||
if (!gotState || curAnimId != 1 /*Death*/) {
|
if (!isPlayer) {
|
||||||
cr->playAnimation(instanceId, 5u, /*loop=*/true);
|
uint32_t curAnimId = 0; float curT = 0.0f, curDur = 0.0f;
|
||||||
|
auto* cr = renderer->getCharacterRenderer();
|
||||||
|
bool gotState = cr->getAnimationState(instanceId, curAnimId, curT, curDur);
|
||||||
|
// Only start Run if not already running and not in Death animation.
|
||||||
|
if (!gotState || (curAnimId != 1 /*Death*/ && curAnimId != 5u /*Run*/)) {
|
||||||
|
cr->playAnimation(instanceId, 5u, /*loop=*/true);
|
||||||
|
}
|
||||||
|
creatureWasMoving_[guid] = true;
|
||||||
}
|
}
|
||||||
if (!isPlayer) creatureWasMoving_[guid] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -8701,17 +8707,21 @@ void Application::updateQuestMarkers() {
|
||||||
int markerType = -1; // -1 = no marker
|
int markerType = -1; // -1 = no marker
|
||||||
|
|
||||||
using game::QuestGiverStatus;
|
using game::QuestGiverStatus;
|
||||||
|
float markerGrayscale = 0.0f; // 0 = colour, 1 = grey (trivial quests)
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case QuestGiverStatus::AVAILABLE:
|
case QuestGiverStatus::AVAILABLE:
|
||||||
|
markerType = 0; // Yellow !
|
||||||
|
break;
|
||||||
case QuestGiverStatus::AVAILABLE_LOW:
|
case QuestGiverStatus::AVAILABLE_LOW:
|
||||||
markerType = 0; // Available (yellow !)
|
markerType = 0; // Grey ! (same texture, desaturated in shader)
|
||||||
|
markerGrayscale = 1.0f;
|
||||||
break;
|
break;
|
||||||
case QuestGiverStatus::REWARD:
|
case QuestGiverStatus::REWARD:
|
||||||
case QuestGiverStatus::REWARD_REP:
|
case QuestGiverStatus::REWARD_REP:
|
||||||
markerType = 1; // Turn-in (yellow ?)
|
markerType = 1; // Yellow ?
|
||||||
break;
|
break;
|
||||||
case QuestGiverStatus::INCOMPLETE:
|
case QuestGiverStatus::INCOMPLETE:
|
||||||
markerType = 2; // Incomplete (grey ?)
|
markerType = 2; // Grey ?
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -8745,7 +8755,7 @@ void Application::updateQuestMarkers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the marker (renderer will handle positioning, bob, glow, etc.)
|
// Set the marker (renderer will handle positioning, bob, glow, etc.)
|
||||||
questMarkerRenderer->setMarker(guid, renderPos, markerType, boundingHeight);
|
questMarkerRenderer->setMarker(guid, renderPos, markerType, boundingHeight, markerGrayscale);
|
||||||
markersAdded++;
|
markersAdded++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,16 +109,16 @@ size_t MemoryMonitor::getAvailableRAM() const {
|
||||||
|
|
||||||
size_t MemoryMonitor::getRecommendedCacheBudget() const {
|
size_t MemoryMonitor::getRecommendedCacheBudget() const {
|
||||||
size_t available = getAvailableRAM();
|
size_t available = getAvailableRAM();
|
||||||
// Use 80% of available RAM for caches (very aggressive), but cap at 90% of total
|
// Use 50% of available RAM for caches, hard-capped at 16 GB.
|
||||||
size_t budget = available * 80 / 100;
|
static constexpr size_t kHardCapBytes = 16ull * 1024 * 1024 * 1024; // 16 GB
|
||||||
size_t maxBudget = totalRAM_ * 90 / 100;
|
size_t budget = available * 50 / 100;
|
||||||
return budget < maxBudget ? budget : maxBudget;
|
return budget < kHardCapBytes ? budget : kHardCapBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MemoryMonitor::isMemoryPressure() const {
|
bool MemoryMonitor::isMemoryPressure() const {
|
||||||
size_t available = getAvailableRAM();
|
size_t available = getAvailableRAM();
|
||||||
// Memory pressure if < 20% RAM available
|
// Memory pressure if < 10% RAM available
|
||||||
return available < (totalRAM_ * 20 / 100);
|
return available < (totalRAM_ * 10 / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ void AssetManager::setupFileCacheBudget() {
|
||||||
const size_t envMaxMB = parseEnvSizeMB("WOWEE_FILE_CACHE_MAX_MB");
|
const size_t envMaxMB = parseEnvSizeMB("WOWEE_FILE_CACHE_MAX_MB");
|
||||||
|
|
||||||
const size_t minBudgetBytes = 256ull * 1024ull * 1024ull;
|
const size_t minBudgetBytes = 256ull * 1024ull * 1024ull;
|
||||||
const size_t defaultMaxBudgetBytes = 32768ull * 1024ull * 1024ull;
|
const size_t defaultMaxBudgetBytes = 12288ull * 1024ull * 1024ull; // 12 GB max for file cache
|
||||||
const size_t maxBudgetBytes = (envMaxMB > 0)
|
const size_t maxBudgetBytes = (envMaxMB > 0)
|
||||||
? (envMaxMB * 1024ull * 1024ull)
|
? (envMaxMB * 1024ull * 1024ull)
|
||||||
: defaultMaxBudgetBytes;
|
: defaultMaxBudgetBytes;
|
||||||
|
|
|
||||||
|
|
@ -979,8 +979,16 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
||||||
(lowerName.find("monument") != std::string::npos) ||
|
(lowerName.find("monument") != std::string::npos) ||
|
||||||
(lowerName.find("sculpture") != std::string::npos);
|
(lowerName.find("sculpture") != std::string::npos);
|
||||||
gpuModel.collisionStatue = statueName;
|
gpuModel.collisionStatue = statueName;
|
||||||
|
// Sittable furniture: chairs/benches/stools cause players to get stuck against
|
||||||
|
// invisible bounding boxes; WMOs already handle room collision.
|
||||||
|
bool sittableFurnitureName =
|
||||||
|
(lowerName.find("chair") != std::string::npos) ||
|
||||||
|
(lowerName.find("bench") != std::string::npos) ||
|
||||||
|
(lowerName.find("stool") != std::string::npos) ||
|
||||||
|
(lowerName.find("seat") != std::string::npos) ||
|
||||||
|
(lowerName.find("throne") != std::string::npos);
|
||||||
bool smallSolidPropName =
|
bool smallSolidPropName =
|
||||||
statueName ||
|
(statueName && !sittableFurnitureName) ||
|
||||||
(lowerName.find("crate") != std::string::npos) ||
|
(lowerName.find("crate") != std::string::npos) ||
|
||||||
(lowerName.find("box") != std::string::npos) ||
|
(lowerName.find("box") != std::string::npos) ||
|
||||||
(lowerName.find("chest") != std::string::npos) ||
|
(lowerName.find("chest") != std::string::npos) ||
|
||||||
|
|
@ -1023,6 +1031,10 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
||||||
(lowerName.find("bamboo") != std::string::npos) ||
|
(lowerName.find("bamboo") != std::string::npos) ||
|
||||||
(lowerName.find("banana") != std::string::npos) ||
|
(lowerName.find("banana") != std::string::npos) ||
|
||||||
(lowerName.find("coconut") != std::string::npos) ||
|
(lowerName.find("coconut") != std::string::npos) ||
|
||||||
|
(lowerName.find("watermelon") != std::string::npos) ||
|
||||||
|
(lowerName.find("melon") != std::string::npos) ||
|
||||||
|
(lowerName.find("squash") != std::string::npos) ||
|
||||||
|
(lowerName.find("gourd") != std::string::npos) ||
|
||||||
(lowerName.find("canopy") != std::string::npos) ||
|
(lowerName.find("canopy") != std::string::npos) ||
|
||||||
(lowerName.find("hedge") != std::string::npos) ||
|
(lowerName.find("hedge") != std::string::npos) ||
|
||||||
(lowerName.find("cactus") != std::string::npos) ||
|
(lowerName.find("cactus") != std::string::npos) ||
|
||||||
|
|
|
||||||
|
|
@ -1051,14 +1051,21 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
||||||
|
|
||||||
auto swapRet = builder.build();
|
auto swapRet = builder.build();
|
||||||
|
|
||||||
if (oldSwapchain) {
|
if (!swapRet) {
|
||||||
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
// Destroy old swapchain now that we failed (it can't be used either)
|
||||||
|
if (oldSwapchain) {
|
||||||
|
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
||||||
|
swapchain = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
LOG_ERROR("Failed to recreate swapchain: ", swapRet.error().message());
|
||||||
|
// Keep swapchainDirty=true so the next frame retries
|
||||||
|
swapchainDirty = true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!swapRet) {
|
// Success — safe to retire the old swapchain
|
||||||
LOG_ERROR("Failed to recreate swapchain: ", swapRet.error().message());
|
if (oldSwapchain) {
|
||||||
swapchain = VK_NULL_HANDLE;
|
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto vkbSwap = swapRet.value();
|
auto vkbSwap = swapRet.value();
|
||||||
|
|
@ -1322,6 +1329,7 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
||||||
|
|
||||||
VkCommandBuffer VkContext::beginFrame(uint32_t& imageIndex) {
|
VkCommandBuffer VkContext::beginFrame(uint32_t& imageIndex) {
|
||||||
if (deviceLost_) return VK_NULL_HANDLE;
|
if (deviceLost_) return VK_NULL_HANDLE;
|
||||||
|
if (swapchain == VK_NULL_HANDLE) return VK_NULL_HANDLE; // Swapchain lost; recreate pending
|
||||||
|
|
||||||
auto& frame = frames[currentFrame];
|
auto& frame = frames[currentFrame];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ bool SpellbookScreen::renderSpellInfoTooltip(uint32_t spellId, game::GameHandler
|
||||||
if (!dbcLoadAttempted) loadSpellDBC(assetManager);
|
if (!dbcLoadAttempted) loadSpellDBC(assetManager);
|
||||||
const SpellInfo* info = getSpellInfo(spellId);
|
const SpellInfo* info = getSpellInfo(spellId);
|
||||||
if (!info) return false;
|
if (!info) return false;
|
||||||
renderSpellTooltip(info, gameHandler);
|
renderSpellTooltip(info, gameHandler, /*showUsageHints=*/false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -446,7 +446,7 @@ const SpellInfo* SpellbookScreen::getSpellInfo(uint32_t spellId) const {
|
||||||
return (it != spellData.end()) ? &it->second : nullptr;
|
return (it != spellData.end()) ? &it->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpellbookScreen::renderSpellTooltip(const SpellInfo* info, game::GameHandler& gameHandler) {
|
void SpellbookScreen::renderSpellTooltip(const SpellInfo* info, game::GameHandler& gameHandler, bool showUsageHints) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
ImGui::PushTextWrapPos(320.0f);
|
ImGui::PushTextWrapPos(320.0f);
|
||||||
|
|
||||||
|
|
@ -551,8 +551,8 @@ void SpellbookScreen::renderSpellTooltip(const SpellInfo* info, game::GameHandle
|
||||||
ImGui::TextWrapped("%s", info->description.c_str());
|
ImGui::TextWrapped("%s", info->description.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage hints
|
// Usage hints — only shown when browsing the spellbook, not on action bar hover
|
||||||
if (!info->isPassive()) {
|
if (!info->isPassive() && showUsageHints) {
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Drag to action bar");
|
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Drag to action bar");
|
||||||
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Double-click to cast");
|
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Double-click to cast");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue