mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
ui,game,pipeline: player nameplates always-on, level-up ring effect, vanilla tile fallback, warden null guard
- Nameplates: player names always rendered regardless of V-key toggle; separate cull distance 40u (players/target) vs 20u (NPCs); cyan name color for other players; fade alpha scales with cull distance - Level-up: add expanding golden ring burst (3 staggered waves, 420u max radius) + full-screen flash to renderDingEffect(); M2 LevelUp.m2 is still attempted as a bonus on top - Vanilla tile loading: add AssetManager::setBaseFallbackPath() so that when the primary manifest is an expansion-specific DBC-only subset (e.g. Data/expansions/vanilla/), world terrain files fall back to the base Data/ extraction; wired in Application::initialize() - Warden: map a null guard page at address 0x0 in the Unicorn emulator so NULL-pointer reads in the module don't crash with UC_ERR_MAP; execution continues past the NULL read for better diagnostics
This commit is contained in:
parent
3cdaf78369
commit
0ea8e55ad4
5 changed files with 100 additions and 11 deletions
|
|
@ -59,6 +59,15 @@ public:
|
||||||
*/
|
*/
|
||||||
void setExpansionDataPath(const std::string& path);
|
void setExpansionDataPath(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a base data path to fall back to when the primary manifest
|
||||||
|
* does not contain a requested file. Call this when the primary
|
||||||
|
* dataPath is an expansion-specific subset (e.g. Data/expansions/vanilla/)
|
||||||
|
* that only holds DBC overrides, not the full world asset set.
|
||||||
|
* @param basePath Path to the base extraction (Data/) that has a manifest.json
|
||||||
|
*/
|
||||||
|
void setBaseFallbackPath(const std::string& basePath);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a DBC file
|
* Load a DBC file
|
||||||
* @param name DBC file name (e.g., "Map.dbc")
|
* @param name DBC file name (e.g., "Map.dbc")
|
||||||
|
|
@ -144,6 +153,11 @@ private:
|
||||||
AssetManifest manifest_;
|
AssetManifest manifest_;
|
||||||
LooseFileReader looseReader_;
|
LooseFileReader looseReader_;
|
||||||
|
|
||||||
|
// Optional base-path fallback: used when manifest_ doesn't contain a file.
|
||||||
|
// Populated by setBaseFallbackPath(); ignored if baseFallbackDataPath_ is empty.
|
||||||
|
std::string baseFallbackDataPath_;
|
||||||
|
AssetManifest baseFallbackManifest_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve filesystem path: check override dir first, then base manifest.
|
* Resolve filesystem path: check override dir first, then base manifest.
|
||||||
* Returns empty string if not found.
|
* Returns empty string if not found.
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,11 @@ bool Application::initialize() {
|
||||||
if (std::filesystem::exists(expansionManifest)) {
|
if (std::filesystem::exists(expansionManifest)) {
|
||||||
assetPath = profile->dataPath;
|
assetPath = profile->dataPath;
|
||||||
LOG_INFO("Using expansion-specific asset path: ", assetPath);
|
LOG_INFO("Using expansion-specific asset path: ", assetPath);
|
||||||
|
// Register base Data/ as fallback so world terrain files are found
|
||||||
|
// even when the expansion path only contains DBC overrides.
|
||||||
|
if (assetPath != dataPath) {
|
||||||
|
assetManager->setBaseFallbackPath(dataPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,15 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map a null guard page at address 0 (read-only, zeroed) so that NULL-pointer
|
||||||
|
// dereferences in the module don't crash the emulator with UC_ERR_MAP.
|
||||||
|
// This allows execution to continue past NULL reads, making diagnostics easier.
|
||||||
|
err = uc_mem_map(uc_, 0x0, 0x1000, UC_PROT_READ);
|
||||||
|
if (err != UC_ERR_OK) {
|
||||||
|
// Non-fatal — just log it; the emulator will still function
|
||||||
|
std::cerr << "[WardenEmulator] Note: could not map null guard page: " << uc_strerror(err) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
// Add hooks for debugging and invalid memory access
|
// Add hooks for debugging and invalid memory access
|
||||||
uc_hook hh;
|
uc_hook hh;
|
||||||
uc_hook_add(uc_, &hh, UC_HOOK_MEM_INVALID, (void*)hookMemInvalid, this, 1, 0);
|
uc_hook_add(uc_, &hh, UC_HOOK_MEM_INVALID, (void*)hookMemInvalid, this, 1, 0);
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,31 @@ std::string AssetManager::resolveFile(const std::string& normalizedPath) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fall back to base manifest
|
// Primary manifest
|
||||||
return manifest_.resolveFilesystemPath(normalizedPath);
|
std::string primaryPath = manifest_.resolveFilesystemPath(normalizedPath);
|
||||||
|
if (!primaryPath.empty()) return primaryPath;
|
||||||
|
|
||||||
|
// If a base-path fallback is configured (expansion-specific primary that only
|
||||||
|
// holds DBC overrides), retry against the base extraction.
|
||||||
|
if (!baseFallbackDataPath_.empty()) {
|
||||||
|
return baseFallbackManifest_.resolveFilesystemPath(normalizedPath);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetManager::setBaseFallbackPath(const std::string& basePath) {
|
||||||
|
if (basePath.empty() || basePath == dataPath) return; // nothing to do
|
||||||
|
std::string manifestPath = basePath + "/manifest.json";
|
||||||
|
if (!std::filesystem::exists(manifestPath)) {
|
||||||
|
LOG_DEBUG("AssetManager: base fallback manifest not found at ", manifestPath,
|
||||||
|
" — fallback disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (baseFallbackManifest_.load(manifestPath)) {
|
||||||
|
baseFallbackDataPath_ = basePath;
|
||||||
|
LOG_INFO("AssetManager: base fallback path set to '", basePath,
|
||||||
|
"' (", baseFallbackManifest_.getEntryCount(), " files)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BLPImage AssetManager::loadTexture(const std::string& path) {
|
BLPImage AssetManager::loadTexture(const std::string& path) {
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderCastBar(gameHandler);
|
renderCastBar(gameHandler);
|
||||||
renderMirrorTimers(gameHandler);
|
renderMirrorTimers(gameHandler);
|
||||||
renderQuestObjectiveTracker(gameHandler);
|
renderQuestObjectiveTracker(gameHandler);
|
||||||
if (showNameplates_) renderNameplates(gameHandler);
|
renderNameplates(gameHandler); // player names always shown; NPC plates gated by showNameplates_
|
||||||
renderBattlegroundScore(gameHandler);
|
renderBattlegroundScore(gameHandler);
|
||||||
renderCombatText(gameHandler);
|
renderCombatText(gameHandler);
|
||||||
renderPartyFrames(gameHandler);
|
renderPartyFrames(gameHandler);
|
||||||
|
|
@ -4848,16 +4848,20 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
|
||||||
auto* unit = dynamic_cast<game::Unit*>(entityPtr.get());
|
auto* unit = dynamic_cast<game::Unit*>(entityPtr.get());
|
||||||
if (!unit || unit->getMaxHealth() == 0) continue;
|
if (!unit || unit->getMaxHealth() == 0) continue;
|
||||||
|
|
||||||
|
bool isPlayer = (entityPtr->getType() == game::ObjectType::PLAYER);
|
||||||
bool isTarget = (guid == targetGuid);
|
bool isTarget = (guid == targetGuid);
|
||||||
|
|
||||||
|
// Player nameplates are always shown; NPC nameplates respect the V-key toggle
|
||||||
|
if (!isPlayer && !showNameplates_) continue;
|
||||||
|
|
||||||
// Convert canonical WoW position → render space, raise to head height
|
// Convert canonical WoW position → render space, raise to head height
|
||||||
glm::vec3 renderPos = core::coords::canonicalToRender(
|
glm::vec3 renderPos = core::coords::canonicalToRender(
|
||||||
glm::vec3(unit->getX(), unit->getY(), unit->getZ()));
|
glm::vec3(unit->getX(), unit->getY(), unit->getZ()));
|
||||||
renderPos.z += 2.3f;
|
renderPos.z += 2.3f;
|
||||||
|
|
||||||
// Cull distance: target up to 40 units; others up to 20 units
|
// Cull distance: target or other players up to 40 units; NPC others up to 20 units
|
||||||
float dist = glm::length(renderPos - camPos);
|
float dist = glm::length(renderPos - camPos);
|
||||||
float cullDist = isTarget ? 40.0f : 20.0f;
|
float cullDist = (isTarget || isPlayer) ? 40.0f : 20.0f;
|
||||||
if (dist > cullDist) continue;
|
if (dist > cullDist) continue;
|
||||||
|
|
||||||
// Project to clip space
|
// Project to clip space
|
||||||
|
|
@ -4874,8 +4878,8 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
|
||||||
float sx = (ndc.x * 0.5f + 0.5f) * screenW;
|
float sx = (ndc.x * 0.5f + 0.5f) * screenW;
|
||||||
float sy = (ndc.y * 0.5f + 0.5f) * screenH;
|
float sy = (ndc.y * 0.5f + 0.5f) * screenH;
|
||||||
|
|
||||||
// Fade out in the last 5 units of range
|
// Fade out in the last 5 units of cull range
|
||||||
float alpha = dist < 35.0f ? 1.0f : 1.0f - (dist - 35.0f) / 5.0f;
|
float alpha = dist < (cullDist - 5.0f) ? 1.0f : 1.0f - (dist - (cullDist - 5.0f)) / 5.0f;
|
||||||
auto A = [&](int v) { return static_cast<int>(v * alpha); };
|
auto A = [&](int v) { return static_cast<int>(v * alpha); };
|
||||||
|
|
||||||
// Bar colour by hostility
|
// Bar colour by hostility
|
||||||
|
|
@ -4920,10 +4924,12 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
|
||||||
ImVec2 textSize = ImGui::CalcTextSize(labelBuf);
|
ImVec2 textSize = ImGui::CalcTextSize(labelBuf);
|
||||||
float nameX = sx - textSize.x * 0.5f;
|
float nameX = sx - textSize.x * 0.5f;
|
||||||
float nameY = sy - barH - 12.0f;
|
float nameY = sy - barH - 12.0f;
|
||||||
// Name color: hostile=red, non-hostile=yellow (WoW convention)
|
// Name color: other player=cyan, hostile=red, non-hostile=yellow (WoW convention)
|
||||||
ImU32 nameColor = unit->isHostile()
|
ImU32 nameColor = isPlayer
|
||||||
? IM_COL32(220, 80, 80, A(230))
|
? IM_COL32( 80, 200, 255, A(230)) // cyan — other players
|
||||||
: IM_COL32(240, 200, 100, A(230));
|
: unit->isHostile()
|
||||||
|
? IM_COL32(220, 80, 80, A(230)) // red — hostile NPC
|
||||||
|
: IM_COL32(240, 200, 100, A(230)); // yellow — friendly NPC
|
||||||
drawList->AddText(ImVec2(nameX + 1.0f, nameY + 1.0f), IM_COL32(0, 0, 0, A(160)), labelBuf);
|
drawList->AddText(ImVec2(nameX + 1.0f, nameY + 1.0f), IM_COL32(0, 0, 0, A(160)), labelBuf);
|
||||||
drawList->AddText(ImVec2(nameX, nameY), nameColor, labelBuf);
|
drawList->AddText(ImVec2(nameX, nameY), nameColor, labelBuf);
|
||||||
|
|
||||||
|
|
@ -10242,6 +10248,7 @@ void GameScreen::renderDingEffect() {
|
||||||
if (dingTimer_ < 0.0f) dingTimer_ = 0.0f;
|
if (dingTimer_ < 0.0f) dingTimer_ = 0.0f;
|
||||||
|
|
||||||
float alpha = dingTimer_ < 0.8f ? (dingTimer_ / 0.8f) : 1.0f; // fade out last 0.8s
|
float alpha = dingTimer_ < 0.8f ? (dingTimer_ / 0.8f) : 1.0f; // fade out last 0.8s
|
||||||
|
float elapsed = DING_DURATION - dingTimer_; // 0 → DING_DURATION
|
||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
float cx = io.DisplaySize.x * 0.5f;
|
float cx = io.DisplaySize.x * 0.5f;
|
||||||
|
|
@ -10249,6 +10256,37 @@ void GameScreen::renderDingEffect() {
|
||||||
|
|
||||||
ImDrawList* draw = ImGui::GetForegroundDrawList();
|
ImDrawList* draw = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
// ---- Golden radial ring burst (3 waves staggered by 0.45s) ----
|
||||||
|
{
|
||||||
|
constexpr float kMaxRadius = 420.0f;
|
||||||
|
constexpr float kRingWidth = 18.0f;
|
||||||
|
constexpr float kWaveLen = 1.4f; // each wave lasts 1.4s
|
||||||
|
constexpr int kNumWaves = 3;
|
||||||
|
constexpr float kStagger = 0.45f; // seconds between waves
|
||||||
|
|
||||||
|
for (int w = 0; w < kNumWaves; ++w) {
|
||||||
|
float waveElapsed = elapsed - w * kStagger;
|
||||||
|
if (waveElapsed <= 0.0f || waveElapsed >= kWaveLen) continue;
|
||||||
|
|
||||||
|
float t = waveElapsed / kWaveLen; // 0 → 1
|
||||||
|
float radius = t * kMaxRadius;
|
||||||
|
float ringAlpha = (1.0f - t) * alpha; // fades as it expands
|
||||||
|
|
||||||
|
ImU32 outerCol = IM_COL32(255, 215, 60, (int)(ringAlpha * 200));
|
||||||
|
ImU32 innerCol = IM_COL32(255, 255, 150, (int)(ringAlpha * 120));
|
||||||
|
|
||||||
|
draw->AddCircle(ImVec2(cx, cy), radius, outerCol, 64, kRingWidth);
|
||||||
|
draw->AddCircle(ImVec2(cx, cy), radius * 0.92f, innerCol, 64, kRingWidth * 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Full-screen golden flash on first frame ----
|
||||||
|
if (elapsed < 0.15f) {
|
||||||
|
float flashA = (1.0f - elapsed / 0.15f) * 0.45f;
|
||||||
|
draw->AddRectFilled(ImVec2(0, 0), io.DisplaySize,
|
||||||
|
IM_COL32(255, 200, 50, (int)(flashA * 255)));
|
||||||
|
}
|
||||||
|
|
||||||
// "LEVEL X!" text — visible for first 2.2s
|
// "LEVEL X!" text — visible for first 2.2s
|
||||||
if (dingTimer_ > 0.8f) {
|
if (dingTimer_ > 0.8f) {
|
||||||
ImFont* font = ImGui::GetFont();
|
ImFont* font = ImGui::GetFont();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue