v1.6.3-unreleased: Sunstrider zone fixes, QuestieLearner icon preservation, migration, and Busted test suite
Code changes (13 files, +916/-171): - ZoneDB integration: GetZoneId() converts uiMapId→areaId via ZoneDB reverse lookup - MIN_CONFIDENCE_PINS reduced to 1 for Ascension (incomplete NPC DBs) - QuestieLearner icon preservation: objective Icon passed to RegisterObjectiveTooltip - InjectLearnedData zone migration: converts old uiMapId spawn keys to areaId - areaId passed through all LearnNPC call sites (OnMouseoverUnit, OnQuestDetail, OnQuestComplete, OnQuestAccepted, OnQuestTurnedIn, OnGossipShow) - Compat/Compat.lua: C_Map.GetPlayerMapPosition UiMapData support - Compat/HBD.lua: Sunstrider mapData aliases and fallback loading - Sunstrider arrow fix (QuestieArrow.lua), resolved pin rendering (QuestieMap.lua) - _MergeOverride helper for string/numeric key compatibility - Northshire Valley UiMapData registration Testing infrastructure: - .busted config pointing to tests/ directory - Tests/wow_api_mock.lua: WoW API mocks (ZoneDB, C_Map, QuestLogCache, etc.) - Tests/QuestieLearner_spec.lua: 12 tests covering coordinate scaling, spell cast learning, zone migration (NPC/objects), icon preservation, settings defaults, and LearnNPC spawn zone tracking - selene.toml + wow_classic.yml: linter configuration Documentation: - Makefile with test/lint/ci targets - sunstrider-coordinate-collection.md: coordinate data reference - sunstrider-pin-fix.md: root cause analysis and fix documentation
This commit is contained in:
+33
-12
@@ -169,32 +169,53 @@
|
||||
<h1>Questie-X Documentation</h1>
|
||||
<p class="subtitle">Complete history of changes, fixes, and additions.</p>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<code>Version: v1.6.1</code>
|
||||
<code>Version: v1.6.2 + Unreleased</code>
|
||||
<a href="index.html"
|
||||
style="background: var(--bg-tertiary); color: var(--accent-blue); text-decoration: none; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; border: 1px solid var(--border-color);">← Back to Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2 id="unreleased">[Unreleased] — Sunstrider Isle Arrow, Tooltip Guard, QuestData String Safety</h2>
|
||||
<h2 id="unreleased">[Unreleased] — Sunstrider Isle Arrow Distance, Map Pins, Tooltip Schema Fixes, QuestData String Safety</h2>
|
||||
<ul>
|
||||
<li><strong>[Fix — MapIconTooltip _GetLevelString Guard]</strong> Resolved <code>attempt to concatenate local 'minLevel' (a nil value)</code> crash in <code>MapIconTooltip.lua:494</code> (<code>_GetLevelString</code> function). The creature name "Uneasy Citizen" existed in <code>creatureLevels</code> as an empty table <code>{}</code> rather than the expected <code>[1]=minLevel, [2]=maxLevel, [3]=rank</code> tuple. Added an early-return guard at the top of <code>_GetLevelString</code>: if <code>creatureLevels[name]</code> is falsy, return the name unmodified.
|
||||
</li>
|
||||
<li><strong>[Fix — Tooltip NPC/Object Type Guard]</strong> Resolved a crash in <code>QuestieTooltips</code> when hovering over NPC or object tooltip keys (<code>m_<id></code>, <code>o_<id></code>) where <code>learnedNpc[10]</code> or <code>learnedObj[10]</code> was unexpectedly a string instead of a table.
|
||||
<li><strong>[Fix — MapIconTooltip _GetLevelString Guard]</strong> Resolved <code>attempt to concatenate local 'minLevel' (a nil value)</code> crash in <code>MapIconTooltip.lua:494</code> (<code>_GetLevelString</code> function). The creature name "Uneasy Citizen" existed in <code>creatureLevels</code> as an empty table <code>{}</code> rather than the expected <code>[1]=minLevel, [2]=maxLevel, [3]=rank</code> tuple. Added an early-return guard at the top of <code>_GetLevelString</code>: if <code>creatureLevels[name]</code> is falsy or not a table with a numeric level at index <code>[1]</code>, return the name unmodified.</li>
|
||||
<li><strong>[Fix — Tooltip Learned Data Schema Mismatch]</strong> Reworked learned NPC/object tooltip registration in <code>Modules/Tooltips/Tooltip.lua</code> to match the actual <code>QuestieLearner</code> storage format.
|
||||
<ul>
|
||||
<li><strong>Root Cause</strong>: <code>InsertMissingQuestIds</code> in the WotLKDB corrections files writes directly to <code>QuestieDB.questData[questId]</code> but <code>questData</code> is stored as a loadable Lua string on Ascension. When code later tried to index into that string as a table, it threw <code>attempt to index field 'questData' (a string value)</code>.</li>
|
||||
<li><strong>Fix</strong>: Added <code>if type(objList) ~= "table" then break end</code> guard in both <code>m_/NPC</code> and <code>o_/object</code> iteration paths in <code>Tooltip.lua</code> before iterating <code>learnedNpc[10]</code> / <code>learnedObj[10]</code>.</li>
|
||||
<li><strong>Player-Facing Symptom</strong>: Fixed the Stormwind mouseover crash reported on Bronzebeard while hovering city guards and other learned tooltip targets: <code>Questie-X\\Modules\\Tooltips\\Tooltip.lua:240: attempt to index local 'objList' (a number value)</code>.</li>
|
||||
<li><strong>Root Cause</strong>: <code>QuestieLearner:_AddToArray</code> stores flat arrays of quest IDs (<code>learnedNpc[10] = { questId1, questId2, ... }</code>, <code>learnedObj[2] = { questId1, questId2, ... }</code>), but the pushed tooltip code still iterated them as <code>{ questId -> objList }</code> maps. That caused crashes like <code>attempt to index local 'objList' (a number value)</code> when a quest ID number was treated like an objective-text array.</li>
|
||||
<li><strong>Fix</strong>: Replaced the legacy <code>for questId, objList in next, ...</code> / <code>objList[oIndex]</code> traversal with schema-correct lookup: iterate learned quest IDs via <code>ipairs</code>, fetch <code>QuestieLearner.data.quests[questId]</code>, then walk <code>qData[10]</code> objective slots and entries (<code>objEntry[2]</code>) to reconstruct tooltip text safely.</li>
|
||||
<li><strong>Scope</strong>: Applied to both learned NPC tooltips (<code>m_<id></code>) and learned object tooltips (<code>o_<id></code>). Object lookup now reads quest IDs from <code>learnedObj[2]</code> (quest starts) instead of the old <code>learnedObj[10]</code> path.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>[Fix — InsertMissingQuestIds String Guard]</strong> Added <code>if type(QuestieDB.questData) ~= "table" then return end</code> guard at the start of <code>InsertMissingQuestIds()</code> in both <code>tbcQuestFixes.lua</code> and <code>wotlkQuestFixes.lua</code>. Prevents the function from writing to <code>questData</code> while it is still an uncompiled string during early loader initialization.</li>
|
||||
<li><strong>[Fix — Sunstrider Isle Arrow / Zone Override]</strong> Resolved the quest arrow not appearing on Sunstrider Isle (Ascension's starting zone) when the world map is closed.
|
||||
<li><strong>[Fix — Sunstrider Isle Arrow Distance (Map Closed)]</strong> Resolved arrow distance showing ~1118 yards instead of ~37 yards on Sunstrider Isle when the world map is NOT open. Map open and zoomed out showed correct distance.
|
||||
<ul>
|
||||
<li><strong>Root Cause</strong>: <code>C_Map.GetBestMapForUnit("player")</code> returns <code>946</code> (ghost/loading map uiMapId) instead of <code>1241</code> (Sunstrider Isle's real uiMapId) when the world map is closed. <code>ZoneDB:GetAreaIdByUiMapId(946)</code> had no override, causing <code>GetCurrentZoneId()</code> to return <code>946</code> instead of <code>3430</code> (Sunstrider Isle's areaId). This broke target zone filtering in <code>_CollectObjective</code> and caused <code>HBD:GetWorldCoordinatesFromZone</code> to return <code>0,0</code> (no world coord data for map 946).</li>
|
||||
<li><strong>Fix — zoneDB.lua</strong>: Added <code>[946] = 3430</code> and <code>[1241] = 3430</code> to <code>UiMapIdOverrides</code> so <code>GetAreaIdByUiMapId</code> always resolves to the real Sunstrider Isle areaId regardless of which ghost or real uiMapId the game returns.</li>
|
||||
<li><strong>Fix — QuestieArrow.lua</strong>: Updated <code>UpdateNearestTargets</code> fallback chain to use <code>QuestiePlayer:GetCurrentUiMapId()</code> for player position. When that returns an invalid/ghost map (946/947/0), it falls back to a <code>ZoneDB</code> lookup via the actual zoneId. Ensures the arrow gets real world coordinates via <code>C_Map.GetPlayerMapPosition</code> + <code>HBD:GetWorldCoordinatesFromZone</code> regardless of map open/closed state.</li>
|
||||
<li><strong>Debug Output</strong>: Added per-frame debug output (respecting <code>debugArrow</code> profile setting) showing <code>frameShown</code>, <code>target.title</code>, player coordinates, and uiMapId values.</li>
|
||||
<li><strong>Root Cause (Player Position)</strong>: <code>HBD:GetPlayerWorldPosition()</code> returns a non-nil value on Sunstrider Isle, but those coords are Eastern Kingdoms world position (wrong), not Sunstrider's actual position. Because a non-nil value is returned, the fallback chain never fires. The arrow then calculates distance using wrong player coords vs correct target coords, giving a wildly incorrect distance.</li>
|
||||
<li><strong>Root Cause (Ghost Map Player Position)</strong>: <code>C_Map.GetPlayerMapPosition(946, "player")</code> where <code>946</code> is Ascension's ghost/cosmic map for Sunstrider Isle returns <code>(0, 0)</code> because the ghost map has no valid coordinate data. The correct map for player position on Sunstrider is <code>1941</code> (Eversong Woods parent), which shares the same world coordinate space and returns valid zone coords.</li>
|
||||
<li><strong>Root Cause (OnUpdate same-map check)</strong>: <code>OnUpdate</code> was using <code>_ResolveArrowUiMapId(_arrow_playerUiMapId)</code> (which resolves 1241→1941) for the player-side uiMapId, making it equal to <code>target.uiMapId</code> (also 1941). This caused the same-map branch to fire, which then called <code>C_Map.GetPlayerMapPosition(_arrow_playerUiMapId)</code> with 946, getting <code>(0, 0)</code> and computing wrong distance.</li>
|
||||
<li><strong>Fix — QuestieArrow.lua UpdateNearestTargets</strong>: Added Sunstrider detection (<code>zoneId == 3430</code>) <em>before</em> calling <code>HBD:GetPlayerWorldPosition()</code> to force the C_Map fallback path. The fallback now calls <code>C_Map.GetPlayerMapPosition(1241, "player")</code> to get Sunstrider map-space coords, then converts through <code>HBD:GetWorldCoordinatesFromZone(..., 1941)</code> using Eversong Woods bounds — which share Sunstrider's world coordinate space.</li>
|
||||
<li><strong>Fix — QuestieArrow.lua OnUpdate same-map branch</strong>: Changed <code>C_Map.GetPlayerMapPosition(1941, "player")</code> for player zone coords (not <code>_arrow_playerUiMapId</code> which is 946). Normalizes both <code>playerUiMapId</code> and <code>targetUiMapId</code> through <code>_ResolveArrowUiMapId()</code> before same-map comparison. Uses <code>zoneScale = 13.53</code> yards/zone-unit for distance. Declares <code>worldPlayerX/Y</code> before the if-else to prevent nil in debug prints.</li>
|
||||
<li><strong>Debug Output</strong>: Added debug prints showing UnitPosition vs HBD player coords comparison, raw vs resolved uiMapIds, branch selection, and per-frame distance calculation inputs.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>[Fix — Sunstrider Isle Map Pins Not Appearing]</strong> Resolved quest objective icons (map pins) not appearing on the world map when zoomed into Sunstrider Isle (uiMapId 1241), even though they appeared correctly when zoomed out to Eversong Woods (uiMapId 1941).
|
||||
<ul>
|
||||
<li><strong>Root Cause</strong>: <code>HBDPins:HandlePin</code> (<code>HereBeDragons-Pins-2.0:424</code>) has an early-return guard: <code>if not HBD.mapData[uiMapID] then return end</code>. When the player zooms into Sunstrider Isle, <code>uiMapID</code> is 1241, but <code>HBD.mapData[1241]</code> is nil — no mapData entry existed for Sunstrider's custom child map. The icon was silently dropped before any coordinate conversion occurred.</li>
|
||||
<li><strong>Fix — Compat/HBD.lua</strong>: Added <code>mapData[1241] = mapData[1941]</code> alias and <code>mapData[946] = mapData[1941]</code> alias. Sunstrider Isle (1241) and its ghost map (946) share Eversong Woods' (1941) world coordinate space for these conversions.</li>
|
||||
<li><strong>Fix — HBD fallback loading</strong>: Added a lazy fallback to the real HBD library's <code>mapData</code> for maps not present in Questie's compat table, so custom/private-server maps can still resolve world and zone coordinates when <code>QuestieCompat</code> lacks a local entry.</li>
|
||||
<li><strong>Fix — Modules/Map/QuestieMap.lua</strong>: Added <code>_ResolveMapUiMapId()</code> helper (1241→1941) applied across <code>FadeLogic</code>, <code>FindClosestStarter</code>, <code>GetNearestSpawn</code>, and <code>GetNearestQuestSpawn</code>. <code>DrawWorldIcon</code> and <code>DrawManualIcon</code> now also store and render Sunstrider map icons against the resolved parent map coordinate space.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>[Fix — Northshire Valley UiMapData Registration]</strong> Added explicit <code>QuestieCompat.UiMapData[1238]</code> for Northshire Valley so custom/private-server zone lookups have concrete geometry for the child map instead of relying on incomplete parent fallbacks.</li>
|
||||
<li><strong>[Fix — QuestieLearner Icon Preservation]</strong> Preserved the learned objective icon when registering with the tooltip system so nameplates can render the correct learned slay/loot/talk marker. Previously the icon was always nil on fresh registration.</li>
|
||||
<li><strong>[Notes — Failed Approaches Documented]</strong> The unreleased notes now explicitly capture the Sunstrider approaches that did <em>not</em> work:
|
||||
<ul>
|
||||
<li>Relying on <code>HBD:GetPlayerWorldPosition()</code> while the world map is closed on Sunstrider, which returned the wrong world space.</li>
|
||||
<li>Using ghost map <code>946</code> for <code>C_Map.GetPlayerMapPosition</code>, which returned invalid or misleading local coordinates.</li>
|
||||
<li>Treating <code>QuestieLearner</code> tooltip data as legacy <code>{ questId -> objList }</code> maps instead of the actual flat quest-id arrays.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>[Note]</strong> The Unreleased section reflects the current working tree, including documentation for local in-progress fixes that are not yet part of a pushed release.</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
+32
-1
@@ -210,7 +210,7 @@
|
||||
<img src="QuestieXlogo.png" alt="Questie-X Logo" width="400" />
|
||||
<p class="subtitle">A universal WoW quest-helper with a plugin architecture for any private server.</p>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<code>Version: v1.6.2</code>
|
||||
<code>Version: v1.6.2 + Unreleased</code>
|
||||
<a href="changelog.html"
|
||||
style="background: var(--bg-tertiary); color: var(--accent-green); text-decoration: none; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; border: 1px solid var(--border-color);">View
|
||||
Changelog</a>
|
||||
@@ -218,6 +218,37 @@
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<section id="unreleased-highlights">
|
||||
<h2>Current Unreleased Work</h2>
|
||||
<p>The current working tree includes in-progress fixes for Ascension custom-zone rendering, learned tooltip reconstruction, and arrow/map behavior on Sunstrider Isle. These notes document local work that may not yet be part of the latest pushed release.</p>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h4>Sunstrider Coordinate Normalization</h4>
|
||||
<p>Arrow and world-map code now normalize Sunstrider's child map <code>1241</code>, ghost map <code>946</code>, and parent Eversong map <code>1941</code> into a consistent coordinate path so distance, direction, and icon placement all use the same world space.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h4>Learned Tooltip Schema Fix</h4>
|
||||
<p><code>QuestieTooltips</code> now reconstructs learned objective text from <code>QuestieLearner.data.quests[questId][10]</code> instead of treating learner arrays like legacy <code>{ questId -> objList }</code> maps, preventing the Stormwind City Guard-style <code>objList</code>-as-number crash reported from Bronzebeard.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h4>Custom Map Data Aliases</h4>
|
||||
<p><code>Compat/HBD.lua</code> now aliases Sunstrider map data (<code>1241</code> and <code>946</code>) to Eversong (<code>1941</code>) and can fall back to the real HBD library's <code>mapData</code> for maps not present in Questie's local compat table.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h4>QuestieLearner Icon Preservation</h4>
|
||||
<p>Learned objectives now preserve the resolved quest-log icon during immediate tooltip registration so nameplates can render the correct learned slay/loot/talk marker instead of a missing icon state.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="important">
|
||||
<strong>Documentation scope:</strong> The landing page and changelog intentionally describe both the latest release and the current local unreleased work so collaborators can see what is being tested before the next push.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- QuestieLearner Engine -->
|
||||
<section id="questielearner">
|
||||
<h2>QuestieLearner: Autonomous Data Engine</h2>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# Sunstrider Isle Pin Fix — Coordinate Collection Guide
|
||||
|
||||
## Architecture
|
||||
|
||||
On Ascension, Sunstrider Isle (uiMapId 1241) shares Eversong Woods' (1941)
|
||||
coordinate space — it's a child map within Eversong. Pins for zone 3430
|
||||
(Eversong Woods) render on the Eversong map (uiMapId 1941) and appear on the
|
||||
Sunstrider sub-map (1241) via ZONE_REDIRECT visibility in HBD.lua.
|
||||
|
||||
### Key mappings
|
||||
- **areaId 3430** (Eversong Woods) → uiMapId 1941 (Eversong map)
|
||||
- **uiMapId 1241** (Sunstrider Isle) → areaId 3430 → pins redirected to 1941 via `_ResolveMapUiMapId`
|
||||
- **ZONE_REDIRECT**: 1241→1941, 946→1941 (cross-visibility)
|
||||
- **HBD bounds**: mapData[1241] uses Eversong's calibrated bounds for player position tracking
|
||||
- **QuestieLearner**: GetZoneId() returns areaId 3430; HighConfidity set to 1 kill
|
||||
|
||||
### Files modified
|
||||
- `Database/Zones/zoneDB.lua` — areaIdToUiMapId[3430] = 1941 (was 1241)
|
||||
- `Modules/Map/QuestieMap.lua` — _ResolveMapUiMapId redirects 1241→1941
|
||||
- `Modules/Arrow/QuestieArrow.lua` — _ResolveArrowUiMapId redirects 1241/946→1941
|
||||
- `Modules/QuestieLearner.lua` — GetZoneId() returns areaId via ZoneDB; InjectLearnedData migrates uiMapId keys; MIN_CONFIDENCE_PINS = 1
|
||||
- `Compat/HBD.lua` — ZONE_REDIRECT[1241]=1941, ASCENSION_ZONE_BOUNDS for mapData[1241]
|
||||
|
||||
## Diagnostic /run Commands
|
||||
|
||||
These must be run **in-game** after Questie has fully loaded (wait 5+ seconds
|
||||
after login). If output is empty, the DB may not be initialized yet.
|
||||
|
||||
### Check ZoneDB mappings
|
||||
```lua
|
||||
/run print("3430→uiMapId:", QuestieLoader:ImportModule("ZoneDB"):GetUiMapIdByAreaId(3430), " 1241→areaId:", QuestieLoader:ImportModule("ZoneDB"):GetAreaIdByUiMapId(1241))
|
||||
```
|
||||
Expected: `3430→uiMapId: 1941 1241→areaId: 3430`
|
||||
|
||||
### Check known NPC spawns for Sunstrider (zone 3430)
|
||||
```lua
|
||||
/run local ZoneDB=QuestieLoader:ImportModule("ZoneDB"); local ids={15271,15273,15274,15278,15279,15280,15281,15283,15284,15285,15287,15289,15291,15292,15294,15295,15297,15298,15301,15366,15367,15371,15372}; for _,id in ipairs(ids) do local n=QuestieDB:GetNPC(id); if n and n.spawns then for z,c in pairs(n.spawns) do if z==3430 or z=="3430" then for i,pt in ipairs(c) do print(id..":"..(n.name or "?").." zone="..z.." ["..i.."]="..string.format("%.2f,%.2f",pt[1],pt[2])) end end end end end
|
||||
```
|
||||
|
||||
### Check QuestieLearner overrides for zone 3430
|
||||
```lua
|
||||
/run local ov=QuestieDB and QuestieDB.npcDataOverrides; if ov then for id,d in pairs(ov) do if d[7] then for z,c in pairs(d[7]) do if z==3430 or z=="3430" then for i,pt in ipairs(c) do print("override npc="..id.." zone="..z.." ["..i.."]="..string.format("%.2f,%.2f",pt[1],pt[2])) end end end end end else print("npcDataOverrides not loaded") end
|
||||
```
|
||||
|
||||
### Check HBD ZONE_REDIRECT
|
||||
```lua
|
||||
/run local HBD=LibStub("HereBeDragonsQuestie-2.0"); print("ResolveZone(1241)=", HBD.ResolveZone and HBD.ResolveZone(1241) or "N/A", "ResolveZone(946)=", HBD.ResolveZone and HBD.ResolveZone(946) or "N/A")
|
||||
```
|
||||
Expected: `ResolveZone(1241)= 1941 ResolveZone(946)= 1941`
|
||||
|
||||
### Check HBD bounds for map 1241
|
||||
```lua
|
||||
/run local HBD=LibStub("HereBeDragonsQuestie-2.0"); local d=HBD.mapData[1241]; if d then print("1241 bounds: left="..d.left.." right="..d.right.." top="..d.top.." bottom="..d.bottom.." parentMapID="..(d.parentMapID or "nil")) else print("No mapData for 1241") end
|
||||
```
|
||||
Expected: `left=-2721.0066 right=-1120.9934 top=8433.9360 bottom=7367.2693 parentMapID=1941`
|
||||
|
||||
### Check LearnNPC zone tracking (run after killing a mob on Sunstrider)
|
||||
```lua
|
||||
/run local ld=Questie.dbLearner; if ld and ld.global and ld.global.npcs then local count=0; for id,d in pairs(ld.global.npcs) do if d[7] and (d[7][3430] or d[7]["3430"]) then count=count+1; print("learned npc="..id.." mc="..(d.mc or 0).." zone=3430") end end; if count==0 then print("No learned NPCs in zone 3430 yet") end else print("Learner data not available") end
|
||||
```
|
||||
|
||||
### Verify pin rendering pipeline
|
||||
```lua
|
||||
/run local ZoneDB=QuestieLoader:ImportModule("ZoneDB"); local uiMapId=ZoneDB:GetUiMapIdByAreaId(3430); print("Zone 3430 → uiMapId "..tostring(uiMapId).." (expected 1941)"); local HBD=LibStub("HereBeDragonsQuestie-2.0"); local wx,wy=HBD:GetWorldCoordinatesFromZone(0.38,0.21,uiMapId); print("World coords for (38%,21%) on map "..uiMapId..": "..string.format("%.1f, %.1f",wx or 0,wy or 0))
|
||||
```
|
||||
Expected: uiMapId 1941, world coords around (-2156, 8209)
|
||||
|
||||
## Testing checklist
|
||||
- [ ] Load addon on Ascension server
|
||||
- [ ] Create a Blood Elf character on Sunstrider Isle
|
||||
- [ ] Verify quest giver pins appear on both the Sunstrider minimap AND the Eversong world map
|
||||
- [ ] Verify clicking a quest giver pin shows quest info
|
||||
- [ ] Verify arrow (distance/direction) points correctly to quest targets
|
||||
- [ ] After visiting/killing NPCs, verify QuestieLearner creates pin overrides (mc≥1)
|
||||
- [ ] Verify pins do NOT appear in mountains or off-map
|
||||
- [ ] Verify Eversong Woods (zone 1941) quest givers NOT on Sunstrider show correctly on Eversong map
|
||||
|
||||
## Adding Townsfolk / NPC Data
|
||||
|
||||
There are three ways to add NPC spawn data so that pins appear for
|
||||
Sunstrider Isle NPCs. Choose the one that matches your data source.
|
||||
|
||||
### 1. AscensionDB plugin (numeric-key array)
|
||||
|
||||
The AscensionDB companion addon ships NPC data as a numeric-key array.
|
||||
Each entry is keyed by NPC ID and uses numeric indices matching
|
||||
`QuestieDB.npcKeys`. The spawns field is index **7** (see `npcKeys.spawns = 7`)
|
||||
and is itself a dict keyed by **areaId**.
|
||||
|
||||
```lua
|
||||
-- AscensionDB.npcData example for a Sunstrider NPC
|
||||
A.npcData = {
|
||||
-- [npcId] = { [1]=name, [4]=minLevel, [5]=maxLevel, [6]=rank, [7]=spawns, ... }
|
||||
[15273] = {
|
||||
"Arcane Wraith", -- [1] name
|
||||
nil, nil, -- [2] minLevelHealth, [3] maxLevelHealth
|
||||
1, 2, -- [4] minLevel, [5] maxLevel
|
||||
0, -- [6] rank
|
||||
{ -- [7] spawns ← keyed by areaId, NOT uiMapId
|
||||
[3430] = { -- 3430 = Eversong Woods areaId
|
||||
{38.4, 21.6},
|
||||
{39.2, 20.8},
|
||||
{40.0, 22.4},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This data is loaded by `QuestieDB:LoadAscensionNpcData()` which calls
|
||||
`_Asc_MergeInto(QuestieDB.npcDataOverrides, data)` — it writes each NPC
|
||||
entry directly into `npcDataOverrides[npcId]`.
|
||||
|
||||
### 2. WotLKDB / TBC corrections (string-key dict)
|
||||
|
||||
The built-in correction files (`wotlkNPCFixes.lua`, `tbcNPCFixes.lua`,
|
||||
`classicNPCFixes.lua`) use the **named-key** format via the `npcKeys`
|
||||
constants. This is the format you should use for patches submitted to
|
||||
Questie-X itself.
|
||||
|
||||
```lua
|
||||
-- In Database/Corrections/wotlkNPCFixes.lua or tbcNPCFixes.lua
|
||||
local npcKeys = QuestieDB.npcKeys
|
||||
|
||||
return {
|
||||
-- [npcId] = { [npcKeys.field] = value, ... }
|
||||
[15273] = {
|
||||
[npcKeys.spawns] = {
|
||||
[3430] = { -- areaId 3430 (Eversong Woods), NOT uiMapId 1241
|
||||
{38.4, 21.6},
|
||||
{39.2, 20.8},
|
||||
{40.0, 22.4},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
These corrections are merged into `QuestieDB.npcDataOverrides` by the
|
||||
correction loader before overrides are applied.
|
||||
|
||||
### 3. QuestieLearner runtime (automatic)
|
||||
|
||||
QuestieLearner learns NPC positions automatically as you play. When you
|
||||
kill or interact with an NPC on Sunstrider Isle, `LearnNPC` stores the
|
||||
spawn under **areaId 3430** (the return value of `GetZoneId()`, which
|
||||
uses `ZoneDB:GetAreaIdByUiMapId(1241) → 3430`). Learned data is written
|
||||
to `Questie.dbLearner.global.npcs[npcId]` as a numeric-key array
|
||||
(identical structure to AscensionDB) and injected into
|
||||
`npcDataOverrides` once the confidence threshold (`mc >= MIN_CONFIDENCE_PINS`)
|
||||
is met.
|
||||
|
||||
### CRITICAL RULE: spawns are keyed by areaId, NOT uiMapId
|
||||
|
||||
This bears repeating because it is the #1 source of Sunstrider bugs:
|
||||
|
||||
- **CORRECT**: `[3430] = { {38.4, 21.6}, ... }` — areaId for Eversong Woods
|
||||
- **WRONG**: `[1241] = { {38.4, 21.6}, ... }` — uiMapId for Sunstrider Isle
|
||||
|
||||
Questie's internal spawn tables use `areaId` as the key. The ZoneDB
|
||||
redirect (`uiMapId 1241 → areaId 3430`) ensures that even when the
|
||||
player is on the Sunstrider sub-map, the correct areaId is used. If you
|
||||
accidentally key spawns by uiMapId (1241), they will never be found by
|
||||
`GetNPC` and no pins will render.
|
||||
|
||||
### _MergeOverride fix (historical note)
|
||||
|
||||
Prior to the `_MergeOverride` helper (added as part of the Sunstrider
|
||||
pin fix), the `GetNPC` function only checked **string-keyed** override
|
||||
entries (`override["spawns"]`). AscensionDB and QuestieLearner store
|
||||
overrides with **numeric keys** (`override[7]`), so their spawn data
|
||||
was silently ignored. The `_MergeOverride` function now checks both
|
||||
formats:
|
||||
|
||||
```lua
|
||||
-- _MergeOverride checks both override formats:
|
||||
-- 1. override[stringKey] (string-keyed, e.g. from wotlkNPCFixes)
|
||||
-- 2. override[intKey] (numeric-keyed, e.g. from QuestieLearner / AscensionDB)
|
||||
-- 3. rawdata[intKey] (fallback to compiled DB)
|
||||
```
|
||||
|
||||
This means override data from **all three sources** is now visible to
|
||||
`GetNPC` for the first time. If you are debugging and overrides seem
|
||||
ignored, confirm `_MergeOverride` is being called (line 1923 in
|
||||
QuestieDB.lua as of this writing).
|
||||
@@ -0,0 +1,236 @@
|
||||
# Sunstrider Isle Pin Fix — Questie-X on Ascension
|
||||
|
||||
## Architecture (Current)
|
||||
|
||||
On Ascension, Sunstrider Isle (uiMapId 1241) shares Eversong Woods' (1941)
|
||||
coordinate space. The fix ensures correct cross-map pin visibility.
|
||||
|
||||
### Coordinate Flow
|
||||
```
|
||||
NPC spawn data: zone 3430 (Eversong) → GetUiMapIdByAreaId(3430) → uiMapId 1941
|
||||
→ pin rendered on Eversong map (1941) with Eversong coordinates
|
||||
→ ZONE_REDIRECT makes pin visible on Sunstrider (1241) too
|
||||
→ _ResolveMapUiMapId redirects 1241→1941 for consistency
|
||||
```
|
||||
|
||||
### Key Mappings
|
||||
| Lookup | From | To | Purpose |
|
||||
|--------|------|----|---------|
|
||||
| `GetUiMapIdByAreaId(3430)` | areaId 3430 | uiMapId 1941 | Pin placement on Eversong map |
|
||||
| `GetAreaIdByUiMapId(1241)` | uiMapId 1241 | areaId 3430 | Zone ID for spawn data keys |
|
||||
| `_ResolveMapUiMapId(1241)` | uiMapId 1241 | uiMapId 1941 | Normalize pin rendering |
|
||||
| `_ResolveArrowUiMapId(1241)` | uiMapId 1241 | uiMapId 1941 | Arrow math normalization |
|
||||
| `ZONE_REDIRECT[1241]` | uiMapId 1241 | uiMapId 1941 | Cross-visibility |
|
||||
| `ZONE_REDIRECT[946]` | uiMapId 946 | uiMapId 1941 | Cross-visibility (ghost map) |
|
||||
| HBD bounds `mapData[1241]` | — | Eversong's bounds | Player position tracking on Sunstrider |
|
||||
|
||||
### Why zone 3430 → uiMapId 1941 (not 1241)
|
||||
|
||||
Zone 3430 = Eversong Woods (the whole zone, not just Sunstrider).
|
||||
In the WotLKDB, NPC spawn coordinates under zone 3430 are Eversong-wide
|
||||
percentages (e.g., NPC 15278 at 38.02%, 21.01%). These render correctly on
|
||||
the Eversong map (1941). Mapping 3430→1241 would place Eversong-wide
|
||||
coordinates on the Sunstrider sub-map, producing wrong positions.
|
||||
|
||||
Pins from zone 3430 render on uiMapId 1941 (Eversong) and appear on uiMapId
|
||||
1241 (Sunstrider) via ZONE_REDIRECT visibility, which works because
|
||||
`ResolveZone(1241) == ResolveZone(1941) == 1941`.
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Database/Zones/zoneDB.lua
|
||||
- `areaIdToUiMapId[3430] = 1941` (was 1241)
|
||||
- `uiMapIdToAreaIdCache[1241] = 3430` (unchanged — Sunstrider map IS in Eversong zone)
|
||||
- `UiMapIdOverrides[1241] = 3430` (unchanged — reverse lookup)
|
||||
|
||||
### Modules/Map/QuestieMap.lua
|
||||
- `_ResolveMapUiMapId(1241, x, y)` → redirects to 1941
|
||||
- `_ResolveMapUiMapId(946, x, y)` → redirects to 1941
|
||||
- Pins from zone 3430 naturally go to uiMapId 1941 (no redirect needed for them)
|
||||
|
||||
### Modules/Arrow/QuestieArrow.lua
|
||||
- `_ResolveArrowUiMapId(1241)` → 1941
|
||||
- `_ResolveArrowUiMapId(946)` → 1941
|
||||
- Comment updated to match new approach
|
||||
|
||||
### Modules/QuestieLearner.lua
|
||||
- `GetZoneId()`: Returns areaId via `ZoneDB:GetAreaIdByUiMapId(uiMapId)` with fallback
|
||||
- `MIN_CONFIDENCE_PINS = 1` (was 2) — Ascension needs every data point
|
||||
- `InjectLearnedData()`: Migration converts uiMapId spawn keys to areaId (1241→3430)
|
||||
- All `LearnNPC` call sites now pass zoneId:
|
||||
- `OnMouseoverUnit`: passes areaId from `l10n:GetAreaIdByLocalName()`
|
||||
- `OnQuestDetail`: passes zoneId from `GetZoneId()`
|
||||
- `OnQuestComplete`: passes zoneId from `GetZoneId()`
|
||||
- `OnQuestAccepted`: passes `GetZoneId()`
|
||||
- `OnQuestTurnedIn`: passes `GetZoneId()`
|
||||
- `GOSSIP_SHOW` handler: passes `GetZoneId()`
|
||||
- Kill handler: passes `bestKill.zoneId` (already correct)
|
||||
|
||||
### Compat/HBD.lua
|
||||
- `ASCENSION_ZONE_BOUNDS[1241]` = Eversong's calibrated bounds for player position tracking
|
||||
- `ASCENSION_ZONE_BOUNDS[946]` = same
|
||||
- `ZONE_REDIRECT[1241]=1941`, `ZONE_REDIRECT[946]=1941` (visibility)
|
||||
- `ResolveZone()` for `isSameZoneSpace` checks
|
||||
|
||||
### Database/QuestieDB.lua
|
||||
- `_MergeOverride(data, key, override)`: Fixed numeric-vs-string key mismatch
|
||||
- **Bug**: Override sources (wotlkNPCFixes, AscensionDB, QuestieLearner) could store spawn
|
||||
zone keys as either numbers (`3430`) or strings (`"3430"`). When `_MergeOverride` merged
|
||||
spawns into the base NPC data, a string key like `"3430"` would create a *new* table entry
|
||||
alongside the existing numeric `3430` key, producing duplicate spawn entries that rendered
|
||||
pins twice or confused zone lookups.
|
||||
- **Fix**: `_MergeOverride` now normalises all zone keys to numeric before merging. Any
|
||||
string-keyed spawn entry (e.g. `{["3430"] = {{0.38,0.21}}}`) is converted to its numeric
|
||||
equivalent (`{3430 = {{0.38,0.21}}}`) before the merge loop runs, so both formats resolve
|
||||
to the same table slot.
|
||||
- This fix is applied **once** inside `_MergeOverride` — no changes needed in individual
|
||||
override sources.
|
||||
|
||||
### Modules/QuestieLearner.lua (zone tracking additions)
|
||||
- `OnQuestComplete`: Now captures `zoneId` via `GetZoneId()` and passes it as `spawnZoneId`
|
||||
to every `LearnNPC` call inside this handler.
|
||||
- All `LearnNPC` call sites now pass `spawnZoneId` — the area ID of the zone the player
|
||||
was in when the event fired. Previously only some handlers included zone data; now every
|
||||
path supplies it, giving `npcDataOverrides` consistent spawn-zone keys for learned NPCs.
|
||||
|
||||
---
|
||||
|
||||
## NPC Data Format & Override Pipeline
|
||||
|
||||
### Override Sources
|
||||
Three systems feed into `QuestieDB.npcDataOverrides`, each producing spawn data that
|
||||
Questie merges at load time:
|
||||
|
||||
| Source | When it runs | Key format | Typical content |
|
||||
|--------|-------------|------------|-----------------|
|
||||
| `wotlkNPCFixes` (Database/NPCs) | Addon load | numeric | Corrections for vanilla→WotLK data changes |
|
||||
| AscensionDB plugin | Addon load | numeric | Ascension-specific NPC additions & tweaks |
|
||||
| QuestieLearner | Runtime events | **was string** (now numeric via `_MergeOverride`) | Player-observed NPC spawns |
|
||||
|
||||
### Numeric-vs-String Key Issue
|
||||
Lua tables can have both `3430` (number) and `"3430"` (string) as separate keys.
|
||||
The base NPC data in `QuestieDB.npcs` uses **numeric** zone keys exclusively.
|
||||
If an override source stored spawns under `"3430"`, the merge would produce:
|
||||
|
||||
```lua
|
||||
spawns = {
|
||||
[3430] = {{0.38, 0.21}}, -- original
|
||||
["3430"]= {{0.38, 0.21}}, -- duplicate from string key
|
||||
}
|
||||
```
|
||||
|
||||
This caused double pins and zone-lookup failures. The `_MergeOverride` fix normalises
|
||||
all keys to numeric *before* merging, collapsing both entries into one.
|
||||
|
||||
### How _MergeOverride Resolves Both Formats
|
||||
```lua
|
||||
-- Inside _MergeOverride, before merging spawns (field index 7):
|
||||
if override[7] then
|
||||
local normalised = {}
|
||||
for zoneKey, coords in pairs(override[7]) do
|
||||
normalised[tonumber(zoneKey) or zoneKey] = coords
|
||||
end
|
||||
override[7] = normalised
|
||||
end
|
||||
-- Then proceed with the standard deep-merge loop
|
||||
```
|
||||
|
||||
This ensures every string key like `"3430"` is converted to `3430`, matching the
|
||||
numeric keys in the base data. The fix is centralised — each override source can
|
||||
store keys in whatever format is convenient.
|
||||
|
||||
### Adding Townsfolk Data to AscensionDB Plugin
|
||||
To add a townsfolk (non-combat NPC) to the AscensionDB plugin's override data:
|
||||
|
||||
```lua
|
||||
-- In AscensionDB/NPCs.lua (or equivalent), npcDataOverrides section:
|
||||
npcDataOverrides[<npcId>] = {
|
||||
-- Field layout follows QuestieDB NPC format:
|
||||
-- [1] name, [2] minLevel, [3] maxLevel, [4] friendly (0=hostile, 1=friendly)
|
||||
-- [5] spawnByZone or nil, [6] waypoints or nil,
|
||||
-- [7] spawns keyed by areaId
|
||||
[7] = {
|
||||
[3430] = { -- areaId for Eversong Woods (covers Sunstrider Isle)
|
||||
{0.38, 0.21}, -- {x%, y%} on the Eversong map
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Key points:
|
||||
- Use **numeric** areaId keys (`3430`, not `"3430"`). Even though `_MergeOverride`
|
||||
now handles both formats, numeric is canonical and avoids ambiguity.
|
||||
- Spawn coordinates are percentages (0–1 range) relative to the Eversong Woods map
|
||||
(uiMapId 1941), **not** the Sunstrider sub-map.
|
||||
- Townsfolk typically set field `[4] = 1` (friendly).
|
||||
- areaId `3430` covers both Eversong Woods and Sunstrider Isle — no separate entry
|
||||
for the sub-zone is needed because `ZONE_REDIRECT` handles cross-visibility.
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic /run Commands
|
||||
|
||||
Must be run **in-game** after Questie has fully loaded (5+ seconds after login).
|
||||
|
||||
### Check ZoneDB mappings
|
||||
```lua
|
||||
/run print("3430→uiMapId:", QuestieLoader:ImportModule("ZoneDB"):GetUiMapIdByAreaId(3430), " 1241→areaId:", QuestieLoader:ImportModule("ZoneDB"):GetAreaIdByUiMapId(1241))
|
||||
```
|
||||
Expected: `3430→uiMapId: 1941 1241→areaId: 3430`
|
||||
|
||||
### Check HBD ZONE_REDIRECT
|
||||
```lua
|
||||
/run local HBD=LibStub("HereBeDragonsQuestie-2.0"); print("ResolveZone(1241)=", HBD.ResolveZone and HBD.ResolveZone(1241) or "N/A", "ResolveZone(946)=", HBD.ResolveZone and HBD.ResolveZone(946) or "N/A")
|
||||
```
|
||||
Expected: `ResolveZone(1241)= 1941 ResolveZone(946)= 1941`
|
||||
|
||||
### Check known NPC spawns for Sunstrider zone (3430)
|
||||
```lua
|
||||
/run local ZoneDB=QuestieLoader:ImportModule("ZoneDB"); local ids={15271,15273,15274,15278,15279,15280,15281,15283,15284,15285,15287,15289,15291,15292,15294,15295,15297,15298,15301,15366,15367,15371,15372}; for _,id in ipairs(ids) do local n=QuestieDB:GetNPC(id); if n and n.spawns then for z,c in pairs(n.spawns) do if z==3430 or z=="3430" then for i,pt in ipairs(c) do print(id..":"..(n.name or "?").." zone="..z.." ["..i.."]="..string.format("%.2f,%.2f",pt[1],pt[2])) end end end end end
|
||||
```
|
||||
|
||||
### Check QuestieLearner overrides for zone 3430
|
||||
```lua
|
||||
/run local ov=QuestieDB and QuestieDB.npcDataOverrides; if ov then for id,d in pairs(ov) do if d[7] then for z,c in pairs(d[7]) do if z==3430 or z=="3430" then for i,pt in ipairs(c) do print("override npc="..id.." zone="..z.." ["..i.."]="..string.format("%.2f,%.2f",pt[1],pt[2])) end end end end end else print("npcDataOverrides not loaded") end
|
||||
```
|
||||
|
||||
### Check HBD bounds for map 1241
|
||||
```lua
|
||||
/run local HBD=LibStub("HereBeDragonsQuestie-2.0"); local d=HBD.mapData[1241]; if d then print("1241: left="..d.left.." right="..d.right.." top="..d.top.." bottom="..d.bottom.." parentMapID="..(d.parentMapID or "nil")) else print("No mapData for 1241") end
|
||||
```
|
||||
|
||||
### Check learned data (after visiting Sunstrider)
|
||||
```lua
|
||||
/run local ld=Questie.dbLearner; if ld and ld.global and ld.global.npcs then local count=0; for id,d in pairs(ld.global.npcs) do if d[7] and (d[7][3430] or d[7]["3430"]) then count=count+1; print("learned npc="..id.." mc="..(d.mc or 0).." zone=3430") end end; if count==0 then print("No learned NPCs in zone 3430 yet") end else print("Learner data not available") end
|
||||
```
|
||||
|
||||
### Verify pin rendering
|
||||
```lua
|
||||
/run local ZoneDB=QuestieLoader:ImportModule("ZoneDB"); local uiMapId=ZoneDB:GetUiMapIdByAreaId(3430); print("Zone 3430 → uiMapId "..tostring(uiMapId).." (expected 1941)"); local HBD=LibStub("HereBeDragonsQuestie-2.0"); local wx,wy=HBD:GetWorldCoordinatesFromZone(0.38,0.21,uiMapId); print("World coords for (38%,21%) on map "..uiMapId..": "..string.format("%.1f, %.1f",wx or 0,wy or 0))
|
||||
```
|
||||
|
||||
### Verify MIN_CONFIDENCE_PINS
|
||||
```lua
|
||||
/run print("minConfidencePins:", Questie.dbLearner.global.settings.minConfidencePins or "default(1)")
|
||||
```
|
||||
|
||||
### Reset all learned data (WARNING: deletes everything!)
|
||||
```lua
|
||||
/run Questie.dbLearner.global.npcs = {}; Questie.dbLearner.global.quests = {}; Questie.dbLearner.global.items = {}; Questie.dbLearner.global.objects = {}; ReloadUI()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
- [ ] Load addon on Ascension server
|
||||
- [ ] Create a Blood Elf character on Sunstrider Isle
|
||||
- [ ] Verify diagnostic: `GetUiMapIdByAreaId(3430)` returns 1941
|
||||
- [ ] Verify quest giver pins appear on BOTH Sunstrider minimap AND Eversong world map
|
||||
- [ ] Verify pins do NOT appear in mountains or off-map
|
||||
- [ ] Verify arrow (distance/direction) points correctly to quest targets
|
||||
- [ ] Kill 1 NPC on Sunstrider, check learned data shows zone=3430 (not 1241)
|
||||
- [ ] After 1+ kill, verify learned pin auto-appears at correct position
|
||||
- [ ] Verify Eversong Woods NPCs NOT on Sunstrider show correctly on Eversong map
|
||||
- [ ] Check no regressions on other zones
|
||||
Reference in New Issue
Block a user