diff --git a/conf/gold_rush.conf.dist b/conf/gold_rush.conf.dist index a95e86e..864d6f3 100644 --- a/conf/gold_rush.conf.dist +++ b/conf/gold_rush.conf.dist @@ -109,6 +109,14 @@ GoldRush.BotPulseSeconds = 30 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 # Description: Semicolon-separated list of temporary gameobject template entries. @@ -119,11 +127,13 @@ GoldRush.NodeEntries = 191133;190176;190171;190172;189973 # # GoldRush.ZonePool -# Description: Semicolon-separated list of eligible zones. -# Use Zone|ApproximateSubArea for a better announcement. -# Default: "Un'Goro Crater|Fire Plume Ridge; Winterspring|Frostfire Hot Springs; Eastern Plaguelands|Terrorweb Tunnel; Sholazar Basin|River's Heart" +# Description: Semicolon-separated list of eligible zones in the format +# ZoneName|NodeEntries|BonusEntries. Empty segments are allowed; +# 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 diff --git a/src/GoldRush.cpp b/src/GoldRush.cpp index 31ec8c2..b501420 100644 --- a/src/GoldRush.cpp +++ b/src/GoldRush.cpp @@ -45,6 +45,8 @@ struct GoldRushSite { std::string ZoneLabel; std::string AreaLabel; + std::vector NodeEntries; + std::vector BonusEntries; uint32 ZoneId = 0; uint32 AreaId = 0; uint32 MapId = 0; @@ -85,6 +87,7 @@ public: uint32 GraceMs = 15 * MINUTE * IN_MILLISECONDS; uint32 MinPlayersOnline = 1; float SpawnRadius = 25.0f; + uint32 BonusChancePercent = 15; std::string NodeEntries; std::string ZonePool; std::string Blacklist; @@ -181,6 +184,7 @@ public: _config.GraceMs = sConfigMgr->GetOption("GoldRush.GraceMinutes", 15) * MINUTE * IN_MILLISECONDS; _config.MinPlayersOnline = std::max(1, sConfigMgr->GetOption("GoldRush.MinPlayersOnline", 1)); _config.SpawnRadius = sConfigMgr->GetOption("GoldRush.SpawnRadiusYards", 25.0f); + _config.BonusChancePercent = std::min(100, sConfigMgr->GetOption("GoldRush.BonusChancePercent", 15)); _config.NodeEntries = sConfigMgr->GetOption("GoldRush.NodeEntries", "191133;190176;190171;190172;189973"); _config.ZonePool = sConfigMgr->GetOption("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("GoldRush.Blacklist", "Stormwind City; Orgrimmar; Dalaran"); @@ -478,7 +482,17 @@ private: std::vector 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 sites; for (std::string_view zoneToken : Acore::Tokenize(zones, ';', true)) { @@ -491,13 +505,14 @@ private: GoldRushSite site; site.ZoneLabel = parts.empty() ? token : Trim(std::string(parts[0])); 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()) continue; AreaTableEntry const* zoneArea = ResolveAreaEntry(site.ZoneLabel); - AreaTableEntry const* areaEntry = site.AreaLabel.empty() ? zoneArea : ResolveAreaEntry(site.AreaLabel); if (!zoneArea) continue; @@ -505,11 +520,8 @@ private: if (!rootZone) continue; - if (!site.AreaLabel.empty() && !areaEntry) - continue; - 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; if (!IsValidZoneArea(rootZone) || IsBlockedSite(site)) @@ -927,15 +939,20 @@ private: uint32 nodeCount = RandomValue(minNodes, maxNodes); nodeCount = std::max(1, nodeCount); - std::vector oreEntries = SplitByCategory(_nodeEntries, false); - std::vector herbEntries = SplitByCategory(_nodeEntries, true); - std::vector fallbackEntries = _nodeEntries; + // Use zone-specific node pool when the current site has entries configured; + // otherwise fall back to the global GoldRush.NodeEntries list exactly as before. + std::vector siteNormalPool = _currentSite.NodeEntries.empty() ? _nodeEntries : _currentSite.NodeEntries; + std::vector siteBonusPool = _currentSite.BonusEntries; + + std::vector oreEntries = SplitByCategory(siteNormalPool, false); + std::vector herbEntries = SplitByCategory(siteNormalPool, true); + std::vector fallbackEntries = siteNormalPool; bool useHerb = true; if (_config.VerboseLogging) { - LOG_INFO(GoldRushLogFilter, "Gold Rush spawning {} node(s) using {} ore entry(s), {} herb entry(s), and {} fallback entry(s).", - nodeCount, oreEntries.size(), herbEntries.size(), fallbackEntries.size()); + 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(), siteBonusPool.size()); } // Map::SummonGameObject feeds this straight into GameObject::SetRespawnTime(int32), @@ -961,7 +978,11 @@ private: if (!pool || pool->empty()) 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 // within line of sight of the anchor is skipped rather than left floating.