Compare commits
No commits in common. "main" and "feature/zone-specific-resources-v2" have entirely different histories.
main
...
feature/zo
5 changed files with 5 additions and 528 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -17,22 +17,6 @@ All notable changes to `mod-gold-rush` will be documented in this file.
|
||||||
global `GoldRush.NodeEntries` pool exactly as before.
|
global `GoldRush.NodeEntries` pool exactly as before.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Zone pool parsing no longer truncates every zone to its first resource entry.
|
|
||||||
`GoldRush.ZonePool` separates zones with `"; "` (semicolon + space) but the
|
|
||||||
node/bonus entry IDs within a zone with a bare `";"`. `BuildSites()` split on
|
|
||||||
the bare `";"`, so each zone's entry list was cut down to its first ID — which
|
|
||||||
is always a herb. That is why events spawned a single wrong-zone herb and no
|
|
||||||
ore at all (reported in PR #8). The parser now splits on the two-character
|
|
||||||
`"; "` zone delimiter, so every zone keeps its full node and bonus pools.
|
|
||||||
- Herb/ore categorization no longer guesses from display-name keywords. The old
|
|
||||||
`SplitByCategory()` matched "lotus"/"clover"/"thorn"/"bloom" for herbs and
|
|
||||||
"vein" for ore, which only worked for the original Northrend default pool and
|
|
||||||
silently fell back to the unsplit pool for almost every v2 zone (Outland/Classic
|
|
||||||
ore uses "Deposit", not "Vein"; most herb names match no keyword). Replaced with
|
|
||||||
a static ID→category lookup table built from the DB-verified entry IDs in
|
|
||||||
`docs/zone-specific-resources-spec.md`, with the old keyword check retained only
|
|
||||||
as a fallback for entries not in the table. See
|
|
||||||
`docs/herb-ore-category-fix-spec.md`.
|
|
||||||
- Event start/end announcements no longer crash the server. `StartEvent()` and
|
- Event start/end announcements no longer crash the server. `StartEvent()` and
|
||||||
`EndEvent()` both used `ChatHandler(nullptr).SendWorldText(...)` to broadcast
|
`EndEvent()` both used `ChatHandler(nullptr).SendWorldText(...)` to broadcast
|
||||||
server-wide -- `ChatHandler::SendWorldText` assumes a real session and calls
|
server-wide -- `ChatHandler::SendWorldText` assumes a real session and calls
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
# Fix: herb/ore categorization breaks for almost every zone in the v2 pool
|
|
||||||
|
|
||||||
## The bug, confirmed by reading the code
|
|
||||||
|
|
||||||
`SplitByCategory()` (`GoldRush.cpp:648`) decides whether a resource entry
|
|
||||||
is a herb or ore purely by matching keywords in its display name:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool isHerb = ContainsWord(name, "lotus") || ContainsWord(name, "clover") || ContainsWord(name, "thorn") || ContainsWord(name, "bloom");
|
|
||||||
bool isOre = ContainsWord(name, "vein");
|
|
||||||
```
|
|
||||||
|
|
||||||
This was written against the *original 5-entry Northrend default pool*
|
|
||||||
(Goldclover→"clover", Icethorn→"thorn", Frost Lotus→"lotus", Titanium
|
|
||||||
**Vein**→"vein") and never updated when the v2 zone-resources feature (PR
|
|
||||||
#5) introduced ~70 new resource names across 57 zones.
|
|
||||||
|
|
||||||
**Confirmed failure case**: tested live in Hellfire Peninsula. Its
|
|
||||||
configured pool is Felweed, Dreaming Glory, Fel Iron **Deposit**,
|
|
||||||
Adamantite **Deposit**. None of these match any herb keyword, and neither
|
|
||||||
ore entry contains "vein" — most Outland/Classic ore uses "Deposit," not
|
|
||||||
"Vein." So both `herbEntries` and `oreEntries` come back empty, and
|
|
||||||
`SpawnHotspot()` (`GoldRush.cpp:998-1017`) falls back to
|
|
||||||
`fallbackEntries` (the whole site pool, unsplit) for every node. The
|
|
||||||
intended herb/ore alternation silently never happens for the large
|
|
||||||
majority of the 57 configured zones — only the handful whose names happen
|
|
||||||
to contain one of the four hardcoded herb words or "vein" categorize
|
|
||||||
correctly at all.
|
|
||||||
|
|
||||||
**Observed symptom**: the test run in Hellfire produced overwhelmingly
|
|
||||||
Felweed with no observed ore, despite the fallback pool containing ore
|
|
||||||
entries. Whether that's pure sampling variance or a second, separate
|
|
||||||
issue (see "Worth checking after this fix" below) isn't confirmed from
|
|
||||||
reading the code alone — don't assume the placement-bias theory without
|
|
||||||
testing after this fix ships.
|
|
||||||
|
|
||||||
## Fix: stop guessing category from the name, use a known lookup table
|
|
||||||
|
|
||||||
Every entry ID used across the 57-zone pool was already individually
|
|
||||||
verified against `acore_world.gameobject_template` when the pool was
|
|
||||||
built (`docs/zone-specific-resources-spec.md`). Rather than continue
|
|
||||||
patching an ever-growing, ever-fragile keyword list — the next zone added
|
|
||||||
will just as easily introduce another unmatched name — replace the
|
|
||||||
name-based guess with a static ID→category lookup built from data that's
|
|
||||||
already verified correct.
|
|
||||||
|
|
||||||
**Add a lookup table** (e.g. `static const std::unordered_map<uint32,
|
|
||||||
bool> IsHerbById` or similar, `true` = herb, `false` = ore), populated
|
|
||||||
from every entry in the tables below. Change `SplitByCategory` to check
|
|
||||||
this table first; **keep the existing name-keyword check as a fallback
|
|
||||||
only** for any entry not found in the table (defensive — covers the
|
|
||||||
original global-pool default entries and anything added by hand later
|
|
||||||
without an explicit table update).
|
|
||||||
|
|
||||||
### Herb entries (mark `true`)
|
|
||||||
```
|
|
||||||
1618, 3724, 1617, 3725, 1619, 3726, // Peacebloom, Silverleaf, Earthroot
|
|
||||||
1620, 3727, 1621, 3729, 2045, 1622, 3730, // Mageroyal, Briarthorn, Stranglekelp, Bruiseweed
|
|
||||||
1623, 1624, 2041, 2042, 2046, 2043, 2866, // Wild Steelbloom, Kingsblood, Liferoot, Fadeleaf, Goldthorn, Khadgar's Whisker, Firebloom
|
|
||||||
142140, 180165, 142141, 176642, // Purple Lotus, Arthas' Tears
|
|
||||||
142142, 176636, 180164, 142143, 183046, // Sungrass, Blindweed
|
|
||||||
142144, 142145, 176637, 176587, 176641, // Ghost Mushroom, Gromsblood, Plaguebloom
|
|
||||||
176583, 176638, 180167, 176584, 176639, // Golden Sansam, Dreamfoil
|
|
||||||
180168, 176586, 176640, 180166, 176588, // Mountain Silversage, Icecap
|
|
||||||
191303, 176589, // Firethorn, Black Lotus
|
|
||||||
181270, 183044, 181271, 183045, 181275, // Felweed, Dreaming Glory, Ragveil
|
|
||||||
183043, 181277, 181276, 181279, 181280, 181281, // Terocone, Flame Cap, Netherbloom, Nightmare Vine, Mana Thistle
|
|
||||||
189973, 190171, 190172, 190176, 190170, // Goldclover, Lichbloom, Icethorn, Frost Lotus, Talandra's Rose
|
|
||||||
191019, 190169 // Adder's Tongue, Tiger Lily
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ore entries (mark `false`)
|
|
||||||
```
|
|
||||||
1731, 2055, 3763, 103713, 181248, // Copper Vein
|
|
||||||
1732, 2054, 3764, 103711, 181249, // Tin Vein
|
|
||||||
1733, 105569, // Silver Vein
|
|
||||||
1735, // Iron Deposit
|
|
||||||
1734, 150080, 181109, // Gold Vein
|
|
||||||
2040, 150079, 176645, // Mithril Deposit
|
|
||||||
2047, 150081, 181108, // Truesilver Deposit
|
|
||||||
324, 150082, 176643, 175404, // Small/Rich Thorium Vein
|
|
||||||
165658, // Dark Iron Deposit
|
|
||||||
181555, 181556, 181569, 181570, 181557, // Fel Iron, Adamantite (+ Rich), Khorium
|
|
||||||
189978, 189979, 189980, 189981, 191133 // Cobalt/Saronite Deposit (+ Rich), Titanium Vein
|
|
||||||
```
|
|
||||||
|
|
||||||
(This is every entry that appears anywhere in `gold_rush.conf.dist`'s
|
|
||||||
`GoldRush.ZonePool` plus the module's own default global
|
|
||||||
`GoldRush.NodeEntries` — cross-check against both files while
|
|
||||||
implementing, in case an entry was missed here.)
|
|
||||||
|
|
||||||
## Worth checking after this fix — don't build blind for this part
|
|
||||||
|
|
||||||
Once real ID-based categorization is in, re-test Hellfire Peninsula (or
|
|
||||||
another Outland zone) with `.goldrush teststart` a few times. If ore
|
|
||||||
still rarely/never appears despite `oreEntries` now correctly populated,
|
|
||||||
look at the placement-validation loop (`GoldRush.cpp:1019` onward) —
|
|
||||||
specifically the `MaxVerticalDrift` cliff/rock-face rejection. Ore
|
|
||||||
deposits are commonly placed on steep terrain in the actual game world;
|
|
||||||
if placement attempts for ore entries are failing validation far more
|
|
||||||
often than herb entries, that's a second, separate bug worth its own fix
|
|
||||||
— but confirm it's actually happening before changing that logic, rather
|
|
||||||
than assuming.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Hellfire Peninsula (the confirmed failure case) should now show a mix
|
|
||||||
of Felweed/Dreaming Glory *and* Fel Iron/Adamantite, not all-herb.
|
|
||||||
- Spot-check one or two zones whose original names *did* match the old
|
|
||||||
keyword list (e.g. Un'Goro Crater — Firethorn contains "thorn", Black
|
|
||||||
Lotus contains "lotus") to confirm the table-based path didn't regress
|
|
||||||
anything that used to work by keyword-coincidence.
|
|
||||||
- Confirm entries not in either table (if any exist) still fall through
|
|
||||||
to the old keyword check rather than being silently dropped.
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# Bug: events spawning resources from the wrong zone entirely
|
|
||||||
|
|
||||||
## Observed, live-tested after PR #7 merged and rebuilt
|
|
||||||
|
|
||||||
`.goldrush teststart` in **Durotar** spawned **Mageroyal** almost
|
|
||||||
exclusively. Mageroyal is not in Durotar's configured pool at all —
|
|
||||||
Durotar's `GoldRush.ZonePool` entry is:
|
|
||||||
|
|
||||||
```
|
|
||||||
Durotar|1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
```
|
|
||||||
|
|
||||||
(Peacebloom, Silverleaf, Earthroot, Copper Vein — verified directly
|
|
||||||
against the live config file, not corrupted.) Mageroyal (1620/3727)
|
|
||||||
belongs to a different zone's list entirely (The Barrens). No ore
|
|
||||||
(Copper Vein) appeared either, despite being in Durotar's list and
|
|
||||||
containing "vein" — a keyword that should have matched even under the
|
|
||||||
*old*, pre-PR-#7 categorization logic, which is a strong hint this isn't
|
|
||||||
purely a categorization problem.
|
|
||||||
|
|
||||||
Same no-ore symptom was also observed in Zangarmarsh (Outland) before
|
|
||||||
this Durotar test — so it isn't zone-specific.
|
|
||||||
|
|
||||||
## Hypothesis — check this first, don't assume
|
|
||||||
|
|
||||||
The live config data is confirmed correct, and the herb that *did* spawn
|
|
||||||
is a real herb from the correct global category, just from the **wrong
|
|
||||||
zone's list**. That points at a scoping bug: somewhere in how
|
|
||||||
`herbEntries`/`oreEntries` get built, the code may now be filtering
|
|
||||||
against the *entire universe* of IDs the lookup table (from PR #7) knows
|
|
||||||
about as herbs, rather than filtering `_currentSite`'s own configured pool
|
|
||||||
by category. That would explain a wrong-zone herb appearing in Durotar,
|
|
||||||
and — if there's an analogous issue on the ore side, or the ore bucket
|
|
||||||
ends up empty/never selected due to how the fix restructured the
|
|
||||||
category-split logic — could also explain the missing ore.
|
|
||||||
|
|
||||||
**Please verify directly against the actual merged code** (I haven't
|
|
||||||
re-opened the PR 7 diff for this report) exactly how the herb/ore split
|
|
||||||
is being built post-fix:
|
|
||||||
- Is it `SplitByCategory(_currentSite.NodeEntries or siteNormalPool, ...)`
|
|
||||||
— filtering the *site's own* entries by category — or did the fix
|
|
||||||
change this to build from some broader/global set?
|
|
||||||
- If the lookup table itself is being used correctly for
|
|
||||||
categorization, but something else changed which *pool* it's applied
|
|
||||||
to, that's the actual regression to find and fix.
|
|
||||||
|
|
||||||
## Testing once fixed
|
|
||||||
|
|
||||||
- Durotar: only Peacebloom/Silverleaf/Earthroot/Copper Vein should
|
|
||||||
appear, nothing from any other zone.
|
|
||||||
- Confirm ore (Copper Vein) actually shows up in Durotar, and Fel
|
|
||||||
Iron/Adamantite show up in Zangarmarsh — both were absent in testing
|
|
||||||
so far.
|
|
||||||
- Spot-check one more zone on a different continent (e.g. Sholazar
|
|
||||||
Basin) to confirm it isn't pulling in entries from unrelated zones
|
|
||||||
either.
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
# Zone-specific resource nodes — implementation spec (v2: full zone coverage)
|
|
||||||
|
|
||||||
**This supersedes the original 4-zone version of this spec.** Same
|
|
||||||
underlying mechanism (see section 3, unchanged), but `GoldRush.ZonePool`
|
|
||||||
now covers essentially every valid open-world zone in the game instead of
|
|
||||||
just four, each with resources appropriate to that zone. Any hotspot not
|
|
||||||
explicitly listed here falls back to the existing global `NodeEntries`
|
|
||||||
pool, exactly as before.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Zone list validation — do this before using this spec, not after
|
|
||||||
|
|
||||||
The starting zone list for this spec came from a hand-written file that
|
|
||||||
included several zones that **do not exist in WotLK 3.3.5a** — Cataclysm
|
|
||||||
additions/renames (Twilight Highlands, Abyssal Depths, Shimmering Expanse,
|
|
||||||
Kelp'thar Forest, Ruins of Gilneas, the Northern/Southern Barrens split,
|
|
||||||
"Northern Stranglethorn" as a standalone zone, Uldum as a full zone rather
|
|
||||||
than a Tanaris subzone, "Ahn'Qiraj: The Fallen Kingdom" which doesn't match
|
|
||||||
any real zone name).
|
|
||||||
|
|
||||||
**Every zone name below was cross-checked against AzerothCore's own
|
|
||||||
published `AreaTable.dbc` reference for version 3.3.5a**
|
|
||||||
(https://www.azerothcore.org/wiki/areatable) — not memory, not assumption.
|
|
||||||
If this spec is ever extended with more zones later, verify the same way
|
|
||||||
before adding them; guessing zone names that don't exist in this exact
|
|
||||||
game version has already cost real time once in this project.
|
|
||||||
|
|
||||||
**Two zones were confirmed real but deliberately excluded** as impractical
|
|
||||||
for a shared open-world event:
|
|
||||||
- **Plaguelands: The Scarlet Enclave** — real, but phased, instanced, and
|
|
||||||
faction-separated (the Death Knight starting experience). An event here
|
|
||||||
would be invisible to almost every player.
|
|
||||||
- **Mount Hyjal** — raid-instance content in 3.3.5a, not an open leveling
|
|
||||||
zone (it didn't become open world until Cataclysm).
|
|
||||||
|
|
||||||
**Hrothgar's Landing** is real and included, but flagged as niche — it's a
|
|
||||||
small island gated behind Argent Tournament rank progression, so events
|
|
||||||
there will reach a much smaller audience than the rest of the list.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Resource universe — every entry below verified against live `acore_world.gameobject_template` (type=3)
|
|
||||||
|
|
||||||
### Classic herbs (Eastern Kingdoms / Kalimdor)
|
|
||||||
| Tier (~level) | Herb | Entries |
|
|
||||||
|---|---|---|
|
|
||||||
| 1-10 | Peacebloom | 1618, 3724 |
|
|
||||||
| 1-10 | Silverleaf | 1617, 3725 |
|
|
||||||
| 1-10 | Earthroot | 1619, 3726 |
|
|
||||||
| 10-20 | Mageroyal | 1620, 3727 |
|
|
||||||
| 15-25 | Briarthorn | 1621, 3729 |
|
|
||||||
| 15-25 | Stranglekelp (coastal only) | 2045 |
|
|
||||||
| 15-25 | Bruiseweed | 1622, 3730 |
|
|
||||||
| 20-30 | Wild Steelbloom | 1623 |
|
|
||||||
| 20-30 | Kingsblood | 1624 |
|
|
||||||
| 30-40 | Liferoot | 2041 |
|
|
||||||
| 30-40 | Fadeleaf | 2042 |
|
|
||||||
| 35-45 | Goldthorn | 2046 |
|
|
||||||
| 35-45 | Khadgar's Whisker | 2043 |
|
|
||||||
| 40-50 | Firebloom | 2866 |
|
|
||||||
| 44-54 | Purple Lotus | 142140, 180165 |
|
|
||||||
| 44-54 | Arthas' Tears | 142141, 176642 |
|
|
||||||
| 44-54 | Sungrass | 142142, 176636, 180164 |
|
|
||||||
| 44-54 | Blindweed | 142143, 183046 |
|
|
||||||
| 45-55 | Ghost Mushroom | 142144 |
|
|
||||||
| 45-55 | Gromsblood | 142145, 176637 |
|
|
||||||
| 45-55 | Plaguebloom | 176587, 176641 |
|
|
||||||
| 48-60 | Golden Sansam | 176583, 176638, 180167 |
|
|
||||||
| 48-60 | Dreamfoil | 176584, 176639, 180168 |
|
|
||||||
| 48-60 | Mountain Silversage | 176586, 176640, 180166 |
|
|
||||||
| 48-60 | Icecap | 176588 |
|
|
||||||
| 48-60 | Firethorn (Un'Goro only) | 191303 |
|
|
||||||
| 55+ | Black Lotus (rare/bonus) | 176589 |
|
|
||||||
|
|
||||||
### Classic ore
|
|
||||||
| Tier | Ore | Entries |
|
|
||||||
|---|---|---|
|
|
||||||
| 1-15 | Copper Vein | 1731, 2055, 3763, 103713, 181248 |
|
|
||||||
| 15-25 | Tin Vein | 1732, 2054, 3764, 103711, 181249 |
|
|
||||||
| 15-25 | Silver Vein | 1733, 105569 |
|
|
||||||
| 25-35 | Iron Deposit | 1735 |
|
|
||||||
| 35-45 | Gold Vein | 1734, 150080, 181109 |
|
|
||||||
| 35-45 | Mithril Deposit | 2040, 150079, 176645 |
|
|
||||||
| 45-55 | Truesilver Deposit | 2047, 150081, 181108 |
|
|
||||||
| 50-60 | Small Thorium Vein | 324, 150082, 176643 |
|
|
||||||
| 50-60 | Rich Thorium Vein (bonus) | 175404 |
|
|
||||||
| 45-55 | Dark Iron Deposit (Searing Gorge / Burning Steppes only) | 165658 |
|
|
||||||
|
|
||||||
**Note**: no "Rich" variant exists for Copper/Tin/Silver/Iron/Mithril in
|
|
||||||
this database — confirmed by direct query, not assumed. Only Thorium and
|
|
||||||
the Outland tiers below got Rich variants in the actual game design.
|
|
||||||
|
|
||||||
### Outland herbs/ore (all zones, ~58-70)
|
|
||||||
| Herb | Entries | Ore | Entries |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Felweed | 181270, 183044 | Fel Iron Deposit | 181555 |
|
|
||||||
| Dreaming Glory | 181271, 183045 | Adamantite Deposit | 181556 |
|
|
||||||
| Ragveil (Zangarmarsh-flavored) | 181275, 183043 | Rich Adamantite Deposit (bonus) | 181569, 181570 |
|
|
||||||
| Terocone (Nagrand/Blade's Edge-flavored) | 181277 | Khorium Vein (bonus) | 181557 |
|
|
||||||
| Flame Cap | 181276 | | |
|
|
||||||
| Netherbloom | 181279 | | |
|
|
||||||
| Nightmare Vine | 181280 | | |
|
|
||||||
| Mana Thistle | 181281 | | |
|
|
||||||
|
|
||||||
### Northrend herbs/ore (already verified in v1 of this spec)
|
|
||||||
Goldclover 189973 · Lichbloom 190171 · Icethorn 190172 · Frost Lotus 190176
|
|
||||||
· Talandra's Rose 190170 · Adder's Tongue 191019 · Tiger Lily 190169 ·
|
|
||||||
Titanium Vein 191133 · Cobalt Deposit 189978 · Rich Cobalt Deposit 189979 ·
|
|
||||||
Saronite Deposit 189980 · Rich Saronite Deposit 189981
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Full `GoldRush.ZonePool` replacement
|
|
||||||
|
|
||||||
One line per zone, `;`-separated, using the format
|
|
||||||
`ZoneName|NodeEntries|BonusEntries` (no `AreaLabel` segment — using bare
|
|
||||||
zone names only, since these came verbatim from the AreaTable.dbc dump and
|
|
||||||
are guaranteed to resolve; a guessed subzone name could silently drop a
|
|
||||||
zone from the pool if wrong).
|
|
||||||
|
|
||||||
```
|
|
||||||
GoldRush.ZonePool = Dun Morogh|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Elwynn Forest|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Tirisfal Glades|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Durotar|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Teldrassil|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Mulgore|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Azuremyst Isle|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Bloodmyst Isle|1618;3724;1617;3725;1619;3726;1731;2055;3763|; Loch Modan|1620;3727;1621;3729;1731;2055;1732;2054|; Westfall|1620;3727;1621;3729;1731;2055;1732;2054|; Silverpine Forest|1620;3727;1621;3729;1731;2055;1732;2054|; Darkshore|1620;3727;1621;3729;1731;2055;1732;2054|; The Barrens|1620;3727;1621;3729;1731;2055;1732;2054|; Redridge Mountains|1622;3730;1623;1732;2054;1733;105569|; Duskwood|1622;3730;1623;1732;2054;1733;105569|; Ashenvale|1622;3730;1621;3729;1732;2054;1733;105569|; Stranglethorn Vale|1622;3730;1623;2045;1732;2054;1733;105569|; Wetlands|1624;1735|; Hillsbrad Foothills|1624;1735|; Stonetalon Mountains|1624;1735|; Thousand Needles|1624;1735|; The Hinterlands|2041;2042;1735;1734;150080|; Arathi Highlands|2041;2042;1735;1734;150080|; Desolace|2041;2042;1735;1734;150080|; Western Plaguelands|2046;2043;1734;150080;2040;150079|; Swamp of Sorrows|2046;2043;1734;150080;2040;150079|; Dustwallow Marsh|2046;2043;1734;150080;2040;150079|; Badlands|2866;2040;150079;176645|; Searing Gorge|2866;165658;2040;150079;176645|; Feralas|2866;2040;150079;176645|; Blasted Lands|142140;180165;142141;176642;2047;150081;181108|; Burning Steppes|142140;180165;142141;176642;165658;2047;150081;181108|; Tanaris|142142;176636;180164;142143;183046;2047;150081;181108|; Azshara|142140;180165;142141;176642;2047;150081;181108|; Eastern Plaguelands|142144;176587;176641;324;150082;176643;175404|176589; Felwood|142145;176637;324;150082;176643;175404|176589; Silithus|176588;142145;176637;324;150082;176643;175404|176589; Un'Goro Crater|191303;324;150082;176643;175404|176589; Winterspring|142140;180165;142142;176636;180164;324;150082;176643;175404|176589; Isle of Quel'Danas|181270;183044;181271;183045;181279;181555;181556|181557; Hellfire Peninsula|181270;183044;181271;183045;181555;181556|181569;181570; Zangarmarsh|181270;183044;181275;183043;181555;181556|181569;181570; Terokkar Forest|181270;183044;181271;183045;181555;181556|181569;181570; Nagrand|181279;181277;181556|181557; Blade's Edge Mountains|181277;181280;181556|181557; Netherstorm|181280;181281;181276;181557|181557; Shadowmoon Valley|181281;181276;181557|181557; Borean Tundra|189973;191019;189978|189979; Howling Fjord|189973;191019;189978|189979; Dragonblight|190169;190170;189978;189980|189979;189981; Grizzly Hills|190169;190170;189978;189980|189979;189981; Zul'Drak|190171;190172;189980;191133|189981; Storm Peaks|190171;190172;189980;191133|189981; Icecrown|190176;190171;190172;189973;190170;191019;191133|189979;189981; Crystalsong Forest|190176;190171;190172;189973;190170;191019;191133|189979;189981; Sholazar Basin|190171;190172;189973;190170;191019;189978;189979;189980;189981;191133|189979;189981; Hrothgar's Landing|190176;190171;190172;191133|189979;189981
|
|
||||||
```
|
|
||||||
|
|
||||||
**This is long and easy to typo when hand-editing later.** Recommend
|
|
||||||
keeping a formatted, one-zone-per-line copy in this file (below) as the
|
|
||||||
source of truth, and only generating the single-line config value from it
|
|
||||||
mechanically — don't hand-edit the config's one-liner directly for future
|
|
||||||
changes.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>One zone per line (source of truth for future edits)</summary>
|
|
||||||
|
|
||||||
```
|
|
||||||
Dun Morogh | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Elwynn Forest | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Tirisfal Glades | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Durotar | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Teldrassil | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Mulgore | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Azuremyst Isle | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Bloodmyst Isle | 1618;3724;1617;3725;1619;3726;1731;2055;3763
|
|
||||||
Loch Modan | 1620;3727;1621;3729;1731;2055;1732;2054
|
|
||||||
Westfall | 1620;3727;1621;3729;1731;2055;1732;2054
|
|
||||||
Silverpine Forest | 1620;3727;1621;3729;1731;2055;1732;2054
|
|
||||||
Darkshore | 1620;3727;1621;3729;1731;2055;1732;2054
|
|
||||||
The Barrens | 1620;3727;1621;3729;1731;2055;1732;2054
|
|
||||||
Redridge Mountains | 1622;3730;1623;1732;2054;1733;105569
|
|
||||||
Duskwood | 1622;3730;1623;1732;2054;1733;105569
|
|
||||||
Ashenvale | 1622;3730;1621;3729;1732;2054;1733;105569
|
|
||||||
Stranglethorn Vale | 1622;3730;1623;2045;1732;2054;1733;105569
|
|
||||||
Wetlands | 1624;1735
|
|
||||||
Hillsbrad Foothills | 1624;1735
|
|
||||||
Stonetalon Mountains | 1624;1735
|
|
||||||
Thousand Needles | 1624;1735
|
|
||||||
The Hinterlands | 2041;2042;1735;1734;150080
|
|
||||||
Arathi Highlands | 2041;2042;1735;1734;150080
|
|
||||||
Desolace | 2041;2042;1735;1734;150080
|
|
||||||
Western Plaguelands | 2046;2043;1734;150080;2040;150079
|
|
||||||
Swamp of Sorrows | 2046;2043;1734;150080;2040;150079
|
|
||||||
Dustwallow Marsh | 2046;2043;1734;150080;2040;150079
|
|
||||||
Badlands | 2866;2040;150079;176645
|
|
||||||
Searing Gorge | 2866;165658;2040;150079;176645 (Dark Iron)
|
|
||||||
Feralas | 2866;2040;150079;176645
|
|
||||||
Blasted Lands | 142140;180165;142141;176642;2047;150081;181108
|
|
||||||
Burning Steppes | 142140;180165;142141;176642;165658;2047;150081;181108 (Dark Iron)
|
|
||||||
Tanaris | 142142;176636;180164;142143;183046;2047;150081;181108
|
|
||||||
Azshara | 142140;180165;142141;176642;2047;150081;181108
|
|
||||||
Eastern Plaguelands | 142144;176587;176641;324;150082;176643;175404 | bonus: 176589
|
|
||||||
Felwood | 142145;176637;324;150082;176643;175404 | bonus: 176589
|
|
||||||
Silithus | 176588;142145;176637;324;150082;176643;175404 | bonus: 176589
|
|
||||||
Un'Goro Crater | 191303;324;150082;176643;175404 | bonus: 176589
|
|
||||||
Winterspring | 142140;180165;142142;176636;180164;324;150082;176643;175404 | bonus: 176589
|
|
||||||
Isle of Quel'Danas | 181270;183044;181271;183045;181279;181555;181556 | bonus: 181557
|
|
||||||
Hellfire Peninsula | 181270;183044;181271;183045;181555;181556 | bonus: 181569;181570
|
|
||||||
Zangarmarsh | 181270;183044;181275;183043;181555;181556 | bonus: 181569;181570
|
|
||||||
Terokkar Forest | 181270;183044;181271;183045;181555;181556 | bonus: 181569;181570
|
|
||||||
Nagrand | 181279;181277;181556 | bonus: 181557
|
|
||||||
Blade's Edge Mountains | 181277;181280;181556 | bonus: 181557
|
|
||||||
Netherstorm | 181280;181281;181276;181557 | bonus: 181557
|
|
||||||
Shadowmoon Valley | 181281;181276;181557 | bonus: 181557
|
|
||||||
Borean Tundra | 189973;191019;189978 | bonus: 189979
|
|
||||||
Howling Fjord | 189973;191019;189978 | bonus: 189979
|
|
||||||
Dragonblight | 190169;190170;189978;189980 | bonus: 189979;189981
|
|
||||||
Grizzly Hills | 190169;190170;189978;189980 | bonus: 189979;189981
|
|
||||||
Zul'Drak | 190171;190172;189980;191133 | bonus: 189981
|
|
||||||
Storm Peaks | 190171;190172;189980;191133 | bonus: 189981
|
|
||||||
Icecrown | 190176;190171;190172;189973;190170;191019;191133 | bonus: 189979;189981
|
|
||||||
Crystalsong Forest | 190176;190171;190172;189973;190170;191019;191133 | bonus: 189979;189981
|
|
||||||
Sholazar Basin | 190171;190172;189973;190170;191019;189978;189979;189980;189981;191133 | bonus: 189979;189981
|
|
||||||
Hrothgar's Landing (niche) | 190176;190171;190172;191133 | bonus: 189979;189981
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Code mechanism (unchanged from v1 — already specced, just restating for completeness)
|
|
||||||
|
|
||||||
- `GoldRushSite` struct (`GoldRush.cpp:44`) needs `std::vector<uint32>
|
|
||||||
NodeEntries` and `std::vector<uint32> BonusEntries`, defaulting empty.
|
|
||||||
- `BuildSites()` (`GoldRush.cpp:479`) parses the 2nd and 3rd
|
|
||||||
`|`-delimited segments (in this all-zone-name version, `NodeEntries` is
|
|
||||||
segment 2 and `BonusEntries` is segment 3, since no `AreaLabel` segment
|
|
||||||
is used here — adjust the parser accordingly, or use an empty middle
|
|
||||||
segment as a placeholder if keeping the original 4-segment
|
|
||||||
`Zone|Area|NodeEntries|BonusEntries` format is preferred for consistency
|
|
||||||
with a future entry that does want an area label. Pick whichever's
|
|
||||||
cleaner; document the choice in code comments either way, since the two
|
|
||||||
format variants look easy to confuse later.
|
|
||||||
- `SpawnHotspot()` (`GoldRush.cpp:919`) uses `_currentSite.NodeEntries`
|
|
||||||
when non-empty, falling back to global `_nodeEntries` otherwise.
|
|
||||||
- Bonus mixing: small per-node chance (`GoldRush.BonusChancePercent`,
|
|
||||||
~10-15%) to pull from `_currentSite.BonusEntries` instead of the normal
|
|
||||||
pool for that one node.
|
|
||||||
- **Backward compatibility required**: a hotspot with no node/bonus
|
|
||||||
segments must still fall back to the global pool.
|
|
||||||
|
|
||||||
## 4. Testing
|
|
||||||
|
|
||||||
Given the scale (50+ zones instead of 4), don't try to manually
|
|
||||||
`.goldrush teststart` every single one before shipping. Reasonable
|
|
||||||
approach:
|
|
||||||
- Spot-check one zone per tier (a starting zone, a mid-level zone, a
|
|
||||||
high-level Classic zone, one Outland zone, one Northrend zone) to
|
|
||||||
confirm the parsing and per-zone resource selection actually works.
|
|
||||||
- Confirm the fallback path still works for a hotspot deliberately left
|
|
||||||
without node/bonus data.
|
|
||||||
- Trust the DB-verified entry IDs for the rest rather than clicking
|
|
||||||
through all fifty — the risk at this point is a config-parsing bug
|
|
||||||
(same for every zone), not a wrong item ID (individually verified above).
|
|
||||||
115
src/GoldRush.cpp
115
src/GoldRush.cpp
|
|
@ -24,7 +24,6 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -42,47 +41,6 @@ static constexpr float MaxVerticalDrift = 20.0f; // reject ground this far
|
||||||
// Mirrors INVALID_HEIGHT from GridTerrainData.h without taking a dependency on that header.
|
// Mirrors INVALID_HEIGHT from GridTerrainData.h without taking a dependency on that header.
|
||||||
static constexpr float InvalidHeightSentinel = -99999.0f;
|
static constexpr float InvalidHeightSentinel = -99999.0f;
|
||||||
|
|
||||||
// Static ID -> category lookup for herb/ore classification. Every entry ID used
|
|
||||||
// across the 57-zone GoldRush.ZonePool (plus the module's own default global
|
|
||||||
// GoldRush.NodeEntries) was individually verified against
|
|
||||||
// acore_world.gameobject_template when the pool was built
|
|
||||||
// (docs/zone-specific-resources-spec.md). Guessing the category from the display
|
|
||||||
// name (the old keyword-list approach) broke for almost every zone: Outland/Classic
|
|
||||||
// ore uses "Deposit" rather than "Vein", and most herb names don't contain any of
|
|
||||||
// the four hardcoded herb keywords. true = herb, false = ore.
|
|
||||||
static const std::unordered_map<uint32, bool> IsHerbById = {
|
|
||||||
// Classic herbs
|
|
||||||
{ 1618, true }, { 3724, true }, { 1617, true }, { 3725, true }, { 1619, true }, { 3726, true }, // Peacebloom, Silverleaf, Earthroot
|
|
||||||
{ 1620, true }, { 3727, true }, { 1621, true }, { 3729, true }, { 2045, true }, { 1622, true }, { 3730, true }, // Mageroyal, Briarthorn, Stranglekelp, Bruiseweed
|
|
||||||
{ 1623, true }, { 1624, true }, { 2041, true }, { 2042, true }, { 2046, true }, { 2043, true }, { 2866, true }, // Wild Steelbloom, Kingsblood, Liferoot, Fadeleaf, Goldthorn, Khadgar's Whisker, Firebloom
|
|
||||||
{ 142140, true }, { 180165, true }, { 142141, true }, { 176642, true }, // Purple Lotus, Arthas' Tears
|
|
||||||
{ 142142, true }, { 176636, true }, { 180164, true }, { 142143, true }, { 183046, true }, // Sungrass, Blindweed
|
|
||||||
{ 142144, true }, { 142145, true }, { 176637, true }, { 176587, true }, { 176641, true }, // Ghost Mushroom, Gromsblood, Plaguebloom
|
|
||||||
{ 176583, true }, { 176638, true }, { 180167, true }, { 176584, true }, { 176639, true }, // Golden Sansam, Dreamfoil
|
|
||||||
{ 180168, true }, { 176586, true }, { 176640, true }, { 180166, true }, { 176588, true }, // Mountain Silversage, Icecap
|
|
||||||
{ 191303, true }, { 176589, true }, // Firethorn, Black Lotus
|
|
||||||
// Outland herbs
|
|
||||||
{ 181270, true }, { 183044, true }, { 181271, true }, { 183045, true }, { 181275, true }, // Felweed, Dreaming Glory, Ragveil
|
|
||||||
{ 183043, true }, { 181277, true }, { 181276, true }, { 181279, true }, { 181280, true }, { 181281, true }, // Terocone, Flame Cap, Netherbloom, Nightmare Vine, Mana Thistle
|
|
||||||
// Northrend herbs
|
|
||||||
{ 189973, true }, { 190171, true }, { 190172, true }, { 190176, true }, { 190170, true }, // Goldclover, Lichbloom, Icethorn, Frost Lotus, Talandra's Rose
|
|
||||||
{ 191019, true }, { 190169, true }, // Adder's Tongue, Tiger Lily
|
|
||||||
// Classic ore
|
|
||||||
{ 1731, false }, { 2055, false }, { 3763, false }, { 103713, false }, { 181248, false }, // Copper Vein
|
|
||||||
{ 1732, false }, { 2054, false }, { 3764, false }, { 103711, false }, { 181249, false }, // Tin Vein
|
|
||||||
{ 1733, false }, { 105569, false }, // Silver Vein
|
|
||||||
{ 1735, false }, // Iron Deposit
|
|
||||||
{ 1734, false }, { 150080, false }, { 181109, false }, // Gold Vein
|
|
||||||
{ 2040, false }, { 150079, false }, { 176645, false }, // Mithril Deposit
|
|
||||||
{ 2047, false }, { 150081, false }, { 181108, false }, // Truesilver Deposit
|
|
||||||
{ 324, false }, { 150082, false }, { 176643, false }, { 175404, false }, // Small/Rich Thorium Vein
|
|
||||||
{ 165658, false }, // Dark Iron Deposit
|
|
||||||
// Outland ore
|
|
||||||
{ 181555, false }, { 181556, false }, { 181569, false }, { 181570, false }, { 181557, false }, // Fel Iron, Adamantite (+ Rich), Khorium
|
|
||||||
// Northrend ore
|
|
||||||
{ 189978, false }, { 189979, false }, { 189980, false }, { 189981, false }, { 191133, false }, // Cobalt/Saronite Deposit (+ Rich), Titanium Vein
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GoldRushSite
|
struct GoldRushSite
|
||||||
{
|
{
|
||||||
std::string ZoneLabel;
|
std::string ZoneLabel;
|
||||||
|
|
@ -196,33 +154,6 @@ static AreaTableEntry const* ResolveAreaEntry(std::string const& areaName)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The GoldRush.ZonePool value separates zones with "; " (semicolon + space) and
|
|
||||||
// the node/bonus entry IDs within a zone with a bare ";". Acore::Tokenize only
|
|
||||||
// splits on a single character, so splitting on ';' cut every zone's entry list
|
|
||||||
// down to its first ID -- the bug behind the wrong-zone/incomplete resource
|
|
||||||
// spawning reported after PR #7 (every zone's first entry is a herb, so ore
|
|
||||||
// never appeared and only one herb type spawned). Split on the two-character
|
|
||||||
// zone delimiter instead.
|
|
||||||
static std::vector<std::string_view> SplitZonePool(std::string_view zones)
|
|
||||||
{
|
|
||||||
std::vector<std::string_view> result;
|
|
||||||
size_t start = 0;
|
|
||||||
while (start < zones.size())
|
|
||||||
{
|
|
||||||
size_t end = zones.find("; ", start);
|
|
||||||
if (end == std::string_view::npos)
|
|
||||||
{
|
|
||||||
result.push_back(zones.substr(start));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push_back(zones.substr(start, end - start));
|
|
||||||
start = end + 2; // skip the "; " delimiter
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GoldRushManager
|
class GoldRushManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -556,29 +487,7 @@ private:
|
||||||
if (!IsEligibleAnchor(player))
|
if (!IsEligibleAnchor(player))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
GoldRushSite site = BuildSiteFromAnchor(player);
|
return BuildSiteFromAnchor(player);
|
||||||
|
|
||||||
// BuildSiteFromAnchor only knows the player's live zone/area IDs -- it has
|
|
||||||
// no access to the per-zone NodeEntries/BonusEntries parsed from
|
|
||||||
// GoldRush.ZonePool into _sites. SelectSiteForPlayer() tries a player
|
|
||||||
// anchor FIRST and only falls back to picking randomly from _sites when no
|
|
||||||
// eligible player is available, so without this lookup, the common case
|
|
||||||
// (a live player standing in a configured zone) silently lost its
|
|
||||||
// zone-specific resources and fell back to the global pool every time --
|
|
||||||
// exactly the bug reported after the v2 zone-resources feature shipped:
|
|
||||||
// events kept spawning the default Northrend-flavored global pool
|
|
||||||
// regardless of which zone they were actually anchored in.
|
|
||||||
for (GoldRushSite const& configured : _sites)
|
|
||||||
{
|
|
||||||
if (configured.ZoneId == site.ZoneId)
|
|
||||||
{
|
|
||||||
site.NodeEntries = configured.NodeEntries;
|
|
||||||
site.BonusEntries = configured.BonusEntries;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return site;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GoldRushSite> BuildSites(std::string const& zones) const
|
std::vector<GoldRushSite> BuildSites(std::string const& zones) const
|
||||||
|
|
@ -595,7 +504,7 @@ private:
|
||||||
// area labels, change this parser to expect 4 segments and adjust
|
// area labels, change this parser to expect 4 segments and adjust
|
||||||
// BuildLocationText/FormatLocationForAnnouncement accordingly.
|
// BuildLocationText/FormatLocationForAnnouncement accordingly.
|
||||||
std::vector<GoldRushSite> sites;
|
std::vector<GoldRushSite> sites;
|
||||||
for (std::string_view zoneToken : SplitZonePool(zones))
|
for (std::string_view zoneToken : Acore::Tokenize(zones, ';', true))
|
||||||
{
|
{
|
||||||
std::string token = Trim(std::string(zoneToken));
|
std::string token = Trim(std::string(zoneToken));
|
||||||
if (token.empty())
|
if (token.empty())
|
||||||
|
|
@ -726,23 +635,9 @@ private:
|
||||||
if (!goinfo)
|
if (!goinfo)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Prefer the verified ID -> category lookup table. The old name-keyword
|
std::string name = Normalize(goinfo->name);
|
||||||
// guess is kept only as a defensive fallback for entries not in the table
|
bool isHerb = ContainsWord(name, "lotus") || ContainsWord(name, "clover") || ContainsWord(name, "thorn") || ContainsWord(name, "bloom");
|
||||||
// (e.g. the original global-pool defaults or anything added by hand later
|
bool isOre = ContainsWord(name, "vein");
|
||||||
// without an explicit table update).
|
|
||||||
bool isHerb;
|
|
||||||
auto const it = IsHerbById.find(entry);
|
|
||||||
if (it != IsHerbById.end())
|
|
||||||
{
|
|
||||||
isHerb = it->second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string name = Normalize(goinfo->name);
|
|
||||||
isHerb = ContainsWord(name, "lotus") || ContainsWord(name, "clover") || ContainsWord(name, "thorn") || ContainsWord(name, "bloom");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOre = !isHerb;
|
|
||||||
|
|
||||||
if (herbs && isHerb)
|
if (herbs && isHerb)
|
||||||
result.push_back(entry);
|
result.push_back(entry);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue