A rare NPC scanner and map overlay addon for Project Ebonhold (WotLK 3.3.5a private server). Forked from _NPCScan 7.x (Saiket) and adapted for Ebonhold's GUID format and roguelite run structure.
- Nameplate scanning - polls
nameplate1..40every frame usingUnitName,UnitReaction, andUnitClassification - Target / mouseover detection - catches rares via
PLAYER_TARGET_CHANGEDandUPDATE_MOUSEOVER_UNIT - Name-based matching - GUID NPC ID extraction is disabled (Ebonhold GUIDs do not encode NPC ID)
- DisableCache = true by default - every session scans fresh; no persistent suppression of found NPCs
- Multi-alert queue - alerts stack; use the NavNext button to cycle through multiple finds
- Alert button - click to target the rare and auto-place a skull raid marker; drag to reposition
- Unified trigger gate - every detection path (target, mouseover, nameplate scan, NAME_PLATE_UNIT_ADDED, TestID cache fallback) consults a single
ShouldAlertfilter chain so wildlife/zone/classification/reaction coverage stays consistent;/esd misfirereports path + reject stage for diagnostics - Wildlife filter - built-in list of known false-positive wildlife names (Plainsstrider, Reef Shark, etc.); user-extendable via
/esd wildlife addwithout a code release; case-insensitive matching soadd giraffesuppressesGiraffeat runtime - Zone blacklist - suppress scanning in specific zones via right-click or slash command; passive paths skip blacklisted zones, manual target/mouseover still alerts
- Map overlay - EbonOverlay draws patrol paths on World Map and Minimap for tracked rares
- Discovery pins - gold map pin placed at the player's position each time a rare is detected, persisted across sessions
- Minimap button - drag to reposition (position saved); left-click to open options; right-click to toggle zone blacklist
- Rare database - sourced from the Questie-X-WotLKDB and Questie-X-EbonholdDB feeds via
tools/extract_npcscan_rare_tables.ps1
- Copy
EbonSearch/andEbonOverlay/intoInterface/AddOns/ - Reload UI:
/reload - Type
/esdto open options
| Command | Description |
|---|---|
/esd |
Open options panel |
/esd add <NpcID> <Name> |
Add a custom NPC to track |
/esd remove <NpcID or Name> |
Remove a custom NPC |
/esd cache |
List NPCs that are cached (unreachable this session) |
/esd clear |
Remove all custom NPCs and reset session state |
/esd zone blacklist add [zone] |
Blacklist current zone (or named zone) |
/esd zone blacklist remove [zone] |
Un-blacklist a zone |
/esd zone blacklist list |
List all blacklisted zones |
/esd wildlife add <name> |
Suppress a misfire by creature name (persists across sessions) |
/esd wildlife remove <name> |
Un-suppress a creature name |
/esd wildlife list |
List user-added wildlife suppression entries |
/esd debug overlays |
Dump last triangle's Det + UV values to chat (dev tool) |
/esd misfire |
Print last 10 classification hits from the nameplate scanner (dev tool) |
/npcscanis retained as a backward-compatible alias for/esd. It is not the primary command.
Build-time unit tests run with standard Lua 5.x (no WoW client needed):
lua tests/run_tests.luaSuites: test_overlay_math, test_detection, test_texture_geom, test_tracked_names, test_wildlife_blacklist, test_localization, test_version_sync. GitHub Actions (.github/workflows/tests.yml) runs these automatically on every push and pull request.
The version literal lives in seven places: ## Title [vX.Y.Z], ## Version, and ## X-Date in each of the two .toc files, plus the H1 of this README. All in-game version display (config-panel byline, watermark, minimap tooltip, slash-command output) reads from ## Version via GetAddOnMetadata at game time, so the seven literal lines are the only manual touchpoints per release.
Use the bump script — never hand-edit:
.\tools\bump-version.ps1 2.2.5 # writes the change
.\tools\bump-version.ps1 2.2.5 -DryRun # preview the diff, no writesThen:
lua tests\run_tests.lua # test_version_sync must pass
git diff # eyeball
git commit -am "chore(v2.2.5): bump"
git tag v2.2.5
git push --follow-tagstest_version_sync cross-checks all seven touchpoints; CI fails if any drifts. Adding a new file that carries the version literal? Add it to both tools/bump-version.ps1 and tests/test_version_sync.lua so the next bumper can't forget.
Interface/AddOns/
├── EbonSearch/
│ ├── EbonSearch.toc
│ ├── EbonSearch.lua
│ ├── EbonSearch.Button.lua
│ ├── EbonSearch.Config.lua
│ ├── EbonSearch.Config.Search.lua
│ ├── EbonSearch.MinimapButton.lua
│ ├── EbonSearch.Overlays.lua
│ ├── EbonSearch.TamableIDs.lua
│ ├── EbonSearch.WowheadRares.lua
│ ├── generated_npcscan_rare_tables.lua
│ ├── Locales/
│ └── Libs/
└── EbonOverlay/
├── EbonOverlay.toc
├── EbonOverlay.lua
├── EbonOverlay.Config.lua
├── EbonOverlay.Modules.lua
├── EbonOverlay.PathData.lua
├── EbonOverlay.ZoneData.lua
├── Modules/
├── Locales/
└── Libs/
The addon uses two detection methods running simultaneously:
Nameplate scanner (primary, Classic world rares)
Polls nameplate1..40 unit tokens every frame. When a nameplate appears that matches a rare name from the Ebonhold/Classic database, an alert fires immediately. Built this way for Ebonhold specifically, because the server's GUIDs don't encode NPC IDs, making the old ID-based approach unreliable.
Tooltip cache scan (secondary, Outland and Northrend rares)
The original _NPCScan detection method. Checks the WoW client's internal creature cache using a tooltip hyperlink query (TestID). Runs alongside the nameplate scanner and handles Outland and Northrend rares where the original Wowhead rare list is the source of truth. Unchanged from the upstream addon.
Both methods feed into the same alert pipeline - queue, toast button, skull marker, and discovery pin.
| Feature | Status |
|---|---|
Unit token nameplates (nameplate1..40) |
✅ |
| Target / mouseover events | ✅ |
| Name-based rare matching | ✅ |
| GUID NPC ID extraction | ❌ not supported on this core |
| WorldFrame:GetChildren() nameplates | ❌ replaced by unit tokens |
- _NPCScan 7.x - Saiket (original foundation)
- SilverDragon - Torhal (WotLK 3.3.5 patterns)
- RareScanner - Sariel (detection patterns)
- Rare database - PE-Questie / Xurkon (Ebonhold NPC data)
- Ebonhold adaptations - Serv (powerfulqa)
- Branded AddOns-list title: TOC
## Titlefor both addons now uses the standard WoW colour-code format - addon name in Ebonhold light blue (|cff66ccff), version badge in yellow (|cffFFFF00[v2.2.4]|r). Folder-matching short names (EbonSearch,EbonOverlay) replace the verboseEbonhold Search and Destroy/... (Overlay)titles for a cleaner AddOns panel entry. - TOC files carry a maintenance comment reminding you to keep the
[vX.Y.Z]suffix in## Titlesynced with## Versionon every bump.
- Unified
ShouldAlerttrigger gate: every detection path (target, mouseover, ScanNameplates, ScanTrackedNameplates, NAME_PLATE_UNIT_ADDED, TestID cache fallback) now consults a single filter chain instead of five hand-rolled ones. Closes the architectural cause of recurring false-positive churn across v2.1.x/v2.2.x. - Path E (TestID cache fallback) now filtered: previously fired with zero filtering whenever an active-scan NPC's name happened to be in the WoW client cache. Wildlife and zone blacklists now apply.
OnFoundsplit: gate runs beforeNPCDeactivate, so a filtered unit no longer silently drops from the active scan.- Case-insensitive wildlife matching:
NormalizeName(trim + collapse + lowercase) on the lookup side; verbatimWildlifeBlacklistpreserved so/esd wildlife listreads naturally; normalized mirror rebuilt atPLAYER_LOGIN. Fixes the foot-gun where/esd wildlife add giraffe(lowercase) didn't suppressGiraffeat runtime. /esd misfireextended: ring-buffer entries now record path tag (target,mouseover,scan,trackedscan,npua,cache) and reject stage (wildlife,classification, etc.) for diagnosing future false-positives.- Removed
GetNameplateTrackedMatch; all three callers now use the gate. - 24 new unit tests covering
NormalizeNamebehaviour, case-insensitive lookup, add/remove case-fold round-trip, andSynchronizemigration of mixed-case saved entries (163/163 passing).
- Refreshed rare table from Questie-X: extractor repointed from
Xurkon/PE-Questie(superseded) toXurkon/Questie-X-WotLKDB(TBC + WotLK coverage) andXurkon/Questie-X-EbonholdDB. Generated table grew from 328 -> 522 entries. - Tighter rank/zone filters: WotLK source narrowed to
rank in {2, 4}(Rare Elite + Rare per cmangos schema) with a curation whitelist for rank=1 entries (loaded from previous committed list viagit show HEAD:) so established community-rares survive while fresh rank=1 elites are dropped. Default-deny zone resolution drops every unmapped TBC/WotLK instance and sub-zone (Karazhan, Black Temple, ICC, Coilfang, etc.) that was leaking through asZone NNNN. - 261 new rares added: spot-checked - Mezzir the Howler, Bog Lurker, Marticar, Doomsayer Jurim, Icehorn, Arcturis, Skoll, Gondria, Mekthorg the Wild, Putridus the Ancient, etc.
- 67 entries removed: mostly questgivers (Rexxar, Misha, Nathanos, Keeper Remulos) and Naxxramas adds. Real rares lost to upstream rank=0 retagging (Ursius, Brumeran, Borelgore, Duskwing, Vagash) remain covered by
OptionsCharacterDefault.NPCs. - Famous Northrend rares (Loque'nahak, Time-Lost Proto-Drake, Vyragosa, Skoll) remain tracked via
EbonSearch.WowheadRares.luaand the legacy TestID cache scanner - unchanged. - Parser rewritten to handle the WotLK DB's
_d[id] = {...}prefix and multi-line entries via brace balancing. .gitignorecollapsed totools/tmp_*.luawildcard so future extractor downloads are auto-ignored.
- Wildlife blacklist bypass in nameplate scan paths:
GetNameplateTrackedMatchhad no wildlife check, soScanTrackedNameplates(OnUpdate) andNAME_PLATE_UNIT_ADDED(event fast path) bypassedWILDLIFE_BLACKLIST_BUILTINentirely. Any mob both blacklisted and present in the generated rare tables (e.g. Reef Shark, ID 12123) would fire on every nameplate appearance despite the v2.1.8 blacklist fix. - One unified wildlife guard inside
GetNameplateTrackedMatchcovers all three callers (ScanNameplates,ScanTrackedNameplates,NAME_PLATE_UNIT_ADDED). - 5 regression tests added (139/139 passing).
- User-editable wildlife blacklist:
/esd wildlife add <name>suppresses any misfire by creature name without a code release; entries persist across sessions inEbonSearchDB - Built-in wildlife entries (
WILDLIFE_BLACKLIST_BUILTIN) retained for confirmed server-wide misfires; user entries merge at detection time - Fixed ZoneBlacklist persistence: blacklisted zones were silently lost on every
/reloadand relog; bothZoneBlacklistandWildlifeBlacklistare now restored fromEbonSearchDBinSynchronize() - Added 9 unit tests for wildlife filter merge logic (134/134 passing)
- Suppressed
Rot Hide BruiserandVile Fin Shredderfalse-positive wildlife misfires - Suppressed
Reef Sharkfalse-positive wildlife misfire
- Barrens wildlife misfire fix:
ProcessUnitForRaresnow resolvesUnitName()beforeUnitClassification()and rejects known false-rare wildlife (Plainsstrider, Ornery Plainsstrider, Giraffe, Barris Giraffe) viaWILDLIFE_BLACKLIST_BUILTIN - Added
FilterWildlife = trueoption (default on); set tofalseto temporarily disable - Added
/esd misfiredev command: prints the last 10 non-blacklisted classification hits for diagnosing false positives
- Fixed locale parse error in
Locale-enUS.luacommand string; added localization regression test
- Bumped TOC Interface version; updated README
- Fixed blocky minimap overlay paths:
ApplyTransformnow hides triangles whose UV values exceed ±100 (degenerate extreme-zoom triangles) instead of clamping all UVs to[0,1]. Clamping was distorting every rotated triangle - Root cause confirmed via in-game debug: Det is healthy (0.24–0.96) on all normal triangles; crash-inducing values (e.g.
±28212) only appear at extreme zoom and are correctly rejected by the new guard - Added
/esd debug overlaysdev command: dumps lastApplyTransformsample (Det, all 8 UVs, max|UV|, hidden flag) to chat - Added 112/112 Lua unit tests (
tests/) and GitHub Actions CI
- Intermediate fix: reject triangles with
Det < 1e-5; reverted, threshold was too aggressive and killed legitimate path triangles
- Hotfix: Clamp minimap TexCoord values to
[0, 1]to prevent aSetTexCoordcrash caused by extreme UV values (e.g.-31242) produced by minimap zoom math
- Names everywhere (fixed ID prints in overlay alerts)
- Single-line alerts (no double-spam from overlay + scanner)
- Overlay pins + confirm message (
NpcName sighted - Recorded on Map) - WotLK 3.3.5a GUID debounce hardened:
WasRecentlyDetectednow keys by Name only (UnitGUIDunreliable on this core) NAME_PLATE_UNIT_ADDEDevent fast-path: instant alert on nameplate appearance, no wait for next OnUpdate tick- Dynamic target keybind (
EbonSearch_TargetButton); bind any key to target the last detected rare
- Initial fork from _NPCScan 7.x; renamed to EbonSearch / EbonOverlay
- Nameplate scanner (
nameplate1..40), multi-alert queue, zone blacklist, discovery pins, minimap button