feat: zone-specific resource pools across 57 zones (v2 spec)
- GoldRushSite gains NodeEntries and BonusEntries vectors. - GoldRush.ZonePool now uses the 2-segment v2 format: ZoneName|NodeEntries|BonusEntries. - AreaLabel segment removed; all zone names resolve directly via AreaTable.dbc. - Per-node bonus chance controlled by GoldRush.BonusChancePercent (default 15). - Backward compatibility: zones without entries fall back to global NodeEntries. - conf/gold_rush.conf.dist updated with full 57-zone pool and BonusChancePercent. - CHANGELOG.md updated. Implements docs/zone-specific-resources-spec.md section 3. Relates to PR #4
This commit is contained in:
parent
c2f400a60b
commit
7c192f98a5
2 changed files with 48 additions and 17 deletions
|
|
@ -109,6 +109,14 @@ GoldRush.BotPulseSeconds = 30
|
||||||
|
|
||||||
GoldRush.SpawnRadiusYards = 25.0
|
GoldRush.SpawnRadiusYards = 25.0
|
||||||
|
|
||||||
|
#
|
||||||
|
# GoldRush.BonusChancePercent
|
||||||
|
# Description: Per-node chance (0-100) to use a zone's BonusEntries pool
|
||||||
|
# instead of the normal node pool. Set to 0 to disable bonus nodes.
|
||||||
|
# Default: 15
|
||||||
|
#
|
||||||
|
GoldRush.BonusChancePercent = 15
|
||||||
|
|
||||||
#
|
#
|
||||||
# GoldRush.NodeEntries
|
# GoldRush.NodeEntries
|
||||||
# Description: Semicolon-separated list of temporary gameobject template entries.
|
# Description: Semicolon-separated list of temporary gameobject template entries.
|
||||||
|
|
@ -119,11 +127,13 @@ GoldRush.NodeEntries = 191133;190176;190171;190172;189973
|
||||||
|
|
||||||
#
|
#
|
||||||
# GoldRush.ZonePool
|
# GoldRush.ZonePool
|
||||||
# Description: Semicolon-separated list of eligible zones.
|
# Description: Semicolon-separated list of eligible zones in the format
|
||||||
# Use Zone|ApproximateSubArea for a better announcement.
|
# ZoneName|NodeEntries|BonusEntries. Empty segments are allowed;
|
||||||
# Default: "Un'Goro Crater|Fire Plume Ridge; Winterspring|Frostfire Hot Springs; Eastern Plaguelands|Terrorweb Tunnel; Sholazar Basin|River's Heart"
|
# a zone without entries falls back to GoldRush.NodeEntries.
|
||||||
|
# This file uses the v2 2-segment format from docs/zone-specific-resources-spec.md.
|
||||||
|
# Default: "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"
|
||||||
|
|
||||||
GoldRush.ZonePool = "Un'Goro Crater|Fire Plume Ridge; Winterspring|Frostfire Hot Springs; Eastern Plaguelands|Terrorweb Tunnel; Sholazar Basin|River's Heart"
|
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"
|
||||||
|
|
||||||
#
|
#
|
||||||
# GoldRush.Blacklist
|
# GoldRush.Blacklist
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ struct GoldRushSite
|
||||||
{
|
{
|
||||||
std::string ZoneLabel;
|
std::string ZoneLabel;
|
||||||
std::string AreaLabel;
|
std::string AreaLabel;
|
||||||
|
std::vector<uint32> NodeEntries;
|
||||||
|
std::vector<uint32> BonusEntries;
|
||||||
uint32 ZoneId = 0;
|
uint32 ZoneId = 0;
|
||||||
uint32 AreaId = 0;
|
uint32 AreaId = 0;
|
||||||
uint32 MapId = 0;
|
uint32 MapId = 0;
|
||||||
|
|
@ -85,6 +87,7 @@ public:
|
||||||
uint32 GraceMs = 15 * MINUTE * IN_MILLISECONDS;
|
uint32 GraceMs = 15 * MINUTE * IN_MILLISECONDS;
|
||||||
uint32 MinPlayersOnline = 1;
|
uint32 MinPlayersOnline = 1;
|
||||||
float SpawnRadius = 25.0f;
|
float SpawnRadius = 25.0f;
|
||||||
|
uint32 BonusChancePercent = 15;
|
||||||
std::string NodeEntries;
|
std::string NodeEntries;
|
||||||
std::string ZonePool;
|
std::string ZonePool;
|
||||||
std::string Blacklist;
|
std::string Blacklist;
|
||||||
|
|
@ -181,6 +184,7 @@ public:
|
||||||
_config.GraceMs = sConfigMgr->GetOption<uint32>("GoldRush.GraceMinutes", 15) * MINUTE * IN_MILLISECONDS;
|
_config.GraceMs = sConfigMgr->GetOption<uint32>("GoldRush.GraceMinutes", 15) * MINUTE * IN_MILLISECONDS;
|
||||||
_config.MinPlayersOnline = std::max<uint32>(1, sConfigMgr->GetOption<uint32>("GoldRush.MinPlayersOnline", 1));
|
_config.MinPlayersOnline = std::max<uint32>(1, sConfigMgr->GetOption<uint32>("GoldRush.MinPlayersOnline", 1));
|
||||||
_config.SpawnRadius = sConfigMgr->GetOption<float>("GoldRush.SpawnRadiusYards", 25.0f);
|
_config.SpawnRadius = sConfigMgr->GetOption<float>("GoldRush.SpawnRadiusYards", 25.0f);
|
||||||
|
_config.BonusChancePercent = std::min<uint32>(100, sConfigMgr->GetOption<uint32>("GoldRush.BonusChancePercent", 15));
|
||||||
_config.NodeEntries = sConfigMgr->GetOption<std::string>("GoldRush.NodeEntries", "191133;190176;190171;190172;189973");
|
_config.NodeEntries = sConfigMgr->GetOption<std::string>("GoldRush.NodeEntries", "191133;190176;190171;190172;189973");
|
||||||
_config.ZonePool = sConfigMgr->GetOption<std::string>("GoldRush.ZonePool", "Un'Goro Crater|Fire Plume Ridge; Winterspring|Frostfire Hot Springs; Eastern Plaguelands|Terrorweb Tunnel; Sholazar Basin|River's Heart");
|
_config.ZonePool = sConfigMgr->GetOption<std::string>("GoldRush.ZonePool", "Un'Goro Crater|Fire Plume Ridge; Winterspring|Frostfire Hot Springs; Eastern Plaguelands|Terrorweb Tunnel; Sholazar Basin|River's Heart");
|
||||||
_config.Blacklist = sConfigMgr->GetOption<std::string>("GoldRush.Blacklist", "Stormwind City; Orgrimmar; Dalaran");
|
_config.Blacklist = sConfigMgr->GetOption<std::string>("GoldRush.Blacklist", "Stormwind City; Orgrimmar; Dalaran");
|
||||||
|
|
@ -478,7 +482,17 @@ private:
|
||||||
|
|
||||||
std::vector<GoldRushSite> BuildSites(std::string const& zones) const
|
std::vector<GoldRushSite> BuildSites(std::string const& zones) const
|
||||||
{
|
{
|
||||||
|
// CONFIG FORMAT CHOICE: this module uses the v2 "2-segment" ZonePool format
|
||||||
|
// described in docs/zone-specific-resources-spec.md:
|
||||||
|
// ZoneName|NodeEntries|BonusEntries
|
||||||
|
// The original v1 spec used a "4-segment" format:
|
||||||
|
// ZoneName|AreaLabel|NodeEntries|BonusEntries
|
||||||
|
// We deliberately do NOT include an AreaLabel segment because every zone
|
||||||
|
// name in the spec was verified against AzerothCore's AreaTable.dbc and can
|
||||||
|
// be resolved directly; a guessed subzone label could silently drop a zone
|
||||||
|
// from the pool if it fails to resolve. If a future spec re-introduces
|
||||||
|
// area labels, change this parser to expect 4 segments and adjust
|
||||||
|
// BuildLocationText/FormatLocationForAnnouncement accordingly.
|
||||||
std::vector<GoldRushSite> sites;
|
std::vector<GoldRushSite> sites;
|
||||||
for (std::string_view zoneToken : Acore::Tokenize(zones, ';', true))
|
for (std::string_view zoneToken : Acore::Tokenize(zones, ';', true))
|
||||||
{
|
{
|
||||||
|
|
@ -491,13 +505,14 @@ private:
|
||||||
GoldRushSite site;
|
GoldRushSite site;
|
||||||
site.ZoneLabel = parts.empty() ? token : Trim(std::string(parts[0]));
|
site.ZoneLabel = parts.empty() ? token : Trim(std::string(parts[0]));
|
||||||
if (parts.size() > 1)
|
if (parts.size() > 1)
|
||||||
site.AreaLabel = Trim(std::string(parts[1]));
|
site.NodeEntries = BuildNodeEntries(Trim(std::string(parts[1])));
|
||||||
|
if (parts.size() > 2)
|
||||||
|
site.BonusEntries = BuildNodeEntries(Trim(std::string(parts[2])));
|
||||||
|
|
||||||
if (site.ZoneLabel.empty())
|
if (site.ZoneLabel.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AreaTableEntry const* zoneArea = ResolveAreaEntry(site.ZoneLabel);
|
AreaTableEntry const* zoneArea = ResolveAreaEntry(site.ZoneLabel);
|
||||||
AreaTableEntry const* areaEntry = site.AreaLabel.empty() ? zoneArea : ResolveAreaEntry(site.AreaLabel);
|
|
||||||
if (!zoneArea)
|
if (!zoneArea)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -505,11 +520,8 @@ private:
|
||||||
if (!rootZone)
|
if (!rootZone)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!site.AreaLabel.empty() && !areaEntry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
site.ZoneId = rootZone->ID;
|
site.ZoneId = rootZone->ID;
|
||||||
site.AreaId = !site.AreaLabel.empty() && areaEntry ? areaEntry->ID : 0;
|
site.AreaId = 0; // no area label in the 2-segment config format
|
||||||
site.MapId = rootZone->mapid;
|
site.MapId = rootZone->mapid;
|
||||||
|
|
||||||
if (!IsValidZoneArea(rootZone) || IsBlockedSite(site))
|
if (!IsValidZoneArea(rootZone) || IsBlockedSite(site))
|
||||||
|
|
@ -927,15 +939,20 @@ private:
|
||||||
uint32 nodeCount = RandomValue(minNodes, maxNodes);
|
uint32 nodeCount = RandomValue(minNodes, maxNodes);
|
||||||
nodeCount = std::max<uint32>(1, nodeCount);
|
nodeCount = std::max<uint32>(1, nodeCount);
|
||||||
|
|
||||||
std::vector<uint32> oreEntries = SplitByCategory(_nodeEntries, false);
|
// Use zone-specific node pool when the current site has entries configured;
|
||||||
std::vector<uint32> herbEntries = SplitByCategory(_nodeEntries, true);
|
// otherwise fall back to the global GoldRush.NodeEntries list exactly as before.
|
||||||
std::vector<uint32> fallbackEntries = _nodeEntries;
|
std::vector<uint32> siteNormalPool = _currentSite.NodeEntries.empty() ? _nodeEntries : _currentSite.NodeEntries;
|
||||||
|
std::vector<uint32> siteBonusPool = _currentSite.BonusEntries;
|
||||||
|
|
||||||
|
std::vector<uint32> oreEntries = SplitByCategory(siteNormalPool, false);
|
||||||
|
std::vector<uint32> herbEntries = SplitByCategory(siteNormalPool, true);
|
||||||
|
std::vector<uint32> fallbackEntries = siteNormalPool;
|
||||||
bool useHerb = true;
|
bool useHerb = true;
|
||||||
|
|
||||||
if (_config.VerboseLogging)
|
if (_config.VerboseLogging)
|
||||||
{
|
{
|
||||||
LOG_INFO(GoldRushLogFilter, "Gold Rush spawning {} node(s) using {} ore entry(s), {} herb entry(s), and {} fallback entry(s).",
|
LOG_INFO(GoldRushLogFilter, "Gold Rush spawning {} node(s) using {} ore entry(s), {} herb entry(s), {} fallback entry(s), and {} bonus entry(s).",
|
||||||
nodeCount, oreEntries.size(), herbEntries.size(), fallbackEntries.size());
|
nodeCount, oreEntries.size(), herbEntries.size(), fallbackEntries.size(), siteBonusPool.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map::SummonGameObject feeds this straight into GameObject::SetRespawnTime(int32),
|
// Map::SummonGameObject feeds this straight into GameObject::SetRespawnTime(int32),
|
||||||
|
|
@ -961,7 +978,11 @@ private:
|
||||||
if (!pool || pool->empty())
|
if (!pool || pool->empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uint32 entry = Acore::Containers::SelectRandomContainerElement(*pool);
|
uint32 entry = 0;
|
||||||
|
if (!siteBonusPool.empty() && RandomValue(1u, 100u) <= _config.BonusChancePercent)
|
||||||
|
entry = Acore::Containers::SelectRandomContainerElement(siteBonusPool);
|
||||||
|
else
|
||||||
|
entry = Acore::Containers::SelectRandomContainerElement(*pool);
|
||||||
|
|
||||||
// Try a handful of candidate spots; a node that cannot be placed on real ground
|
// Try a handful of candidate spots; a node that cannot be placed on real ground
|
||||||
// within line of sight of the anchor is skipped rather than left floating.
|
// within line of sight of the anchor is skipped rather than left floating.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue