feat: hold the scheduler on an empty server instead of polling into an ambush
Every failed scheduled start overwrote the rolled Min/MaxInterval with a hard-coded 5 minutes and never restored it, so on a server that sits empty the scheduler degenerated into a permanent 5-minute poll. The moment the first player logged in, an event fired within one retry window and anchored on their exact position, with a server-wide announcement -- login ambush rather than a scheduled world event. It also wrote a retry line to gold-rush.log every 5 minutes forever (Debug defaults to 1). StartEvent now counts eligible anchors when the schedule expires, holds without consuming the interval while below MinPlayersOnline, and inserts GraceMinutes once the world repopulates. The count runs only on schedule expiry, never per world tick. New config: GoldRush.RetryMinutes (5), GoldRush.GraceMinutes (15), GoldRush.MinPlayersOnline (1). GraceMinutes = 0 rolls a fresh interval instead. GM-forced starts pass scheduled=false and bypass both gates. Verified with clang -fsyntax-only against the live build's compile flags. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019vTNrJGsprcjdjjjDJD5ZM
This commit is contained in:
parent
6244e2bc65
commit
2857b0580e
3 changed files with 123 additions and 11 deletions
|
|
@ -80,6 +80,9 @@ public:
|
|||
uint32 MaxNodeCount = 25;
|
||||
uint32 BotsPerFaction = 6;
|
||||
uint32 BotPulseMs = 30 * IN_MILLISECONDS;
|
||||
uint32 RetryMs = 5 * MINUTE * IN_MILLISECONDS;
|
||||
uint32 GraceMs = 15 * MINUTE * IN_MILLISECONDS;
|
||||
uint32 MinPlayersOnline = 1;
|
||||
float SpawnRadius = 25.0f;
|
||||
std::string NodeEntries;
|
||||
std::string ZonePool;
|
||||
|
|
@ -173,6 +176,9 @@ public:
|
|||
_config.MaxNodeCount = sConfigMgr->GetOption<uint32>("GoldRush.MaxNodes", 25);
|
||||
_config.BotsPerFaction = sConfigMgr->GetOption<uint32>("GoldRush.BotsPerFaction", 6);
|
||||
_config.BotPulseMs = sConfigMgr->GetOption<uint32>("GoldRush.BotPulseSeconds", 30) * IN_MILLISECONDS;
|
||||
_config.RetryMs = std::max<uint32>(1, sConfigMgr->GetOption<uint32>("GoldRush.RetryMinutes", 5)) * 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.SpawnRadius = sConfigMgr->GetOption<float>("GoldRush.SpawnRadiusYards", 25.0f);
|
||||
_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|The River's Heart");
|
||||
|
|
@ -191,6 +197,7 @@ public:
|
|||
_spawnedNodes.clear();
|
||||
_routedBotIds.clear();
|
||||
_telemetry = {};
|
||||
_worldWasEmpty = false;
|
||||
_timeUntilNextEventMs = 0;
|
||||
_eventTimeRemainingMs = 0;
|
||||
_nextBotPulseMs = _config.BotPulseMs;
|
||||
|
|
@ -267,7 +274,7 @@ public:
|
|||
if (!site.ZoneId || !anchor || !anchor->IsInWorld() || !anchor->GetMap())
|
||||
return false;
|
||||
|
||||
return StartEvent(anchor, site);
|
||||
return StartEvent(anchor, site, false);
|
||||
}
|
||||
|
||||
bool ForceTestStart(Player* anchor)
|
||||
|
|
@ -724,11 +731,72 @@ private:
|
|||
return Acore::Containers::SelectRandomContainerElement(candidates);
|
||||
}
|
||||
|
||||
bool StartEvent(Player* anchorOverride = nullptr, GoldRushSite siteOverride = {})
|
||||
// Counts players (bots included) standing somewhere an event could legally anchor.
|
||||
// Only called when the schedule expires, never per world tick.
|
||||
uint32 CountEligibleAnchors() const
|
||||
{
|
||||
uint32 count = 0;
|
||||
std::shared_lock lock(*HashMapHolder<Player>::GetLock());
|
||||
|
||||
for (auto const& [guid, player] : ObjectAccessor::GetPlayers())
|
||||
{
|
||||
if (IsEligibleAnchor(player))
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool StartEvent(Player* anchorOverride = nullptr, GoldRushSite siteOverride = {}, bool scheduled = true)
|
||||
{
|
||||
if (_nodeEntries.empty())
|
||||
return false;
|
||||
|
||||
// On an empty (or near-empty) server the scheduler used to burn its rolled
|
||||
// interval down to a flat retry and hold there, so the first person to log in
|
||||
// got an event dropped on their head within one retry window. Hold the schedule
|
||||
// while nobody is around, then give the world a grace period once it repopulates.
|
||||
// GM-forced starts skip all of this.
|
||||
if (scheduled)
|
||||
{
|
||||
uint32 const eligible = CountEligibleAnchors();
|
||||
|
||||
if (eligible < _config.MinPlayersOnline)
|
||||
{
|
||||
if (!_worldWasEmpty)
|
||||
{
|
||||
_worldWasEmpty = true;
|
||||
|
||||
if (_config.Debug)
|
||||
{
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush is holding: {} eligible anchor(s) online, {} required. Rechecking every {} minute(s).",
|
||||
eligible, _config.MinPlayersOnline, _config.RetryMs / (MINUTE * IN_MILLISECONDS));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogVerbose("Gold Rush still holding; no eligible anchors online.");
|
||||
}
|
||||
|
||||
_timeUntilNextEventMs = _config.RetryMs;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_worldWasEmpty)
|
||||
{
|
||||
_worldWasEmpty = false;
|
||||
_timeUntilNextEventMs = _config.GraceMs ? _config.GraceMs : RollIntervalMs();
|
||||
|
||||
if (_config.Debug)
|
||||
{
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush sees {} eligible anchor(s) after an idle period; waiting {} minute(s) before the next event.",
|
||||
eligible, _timeUntilNextEventMs / (MINUTE * IN_MILLISECONDS));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!siteOverride.ZoneId)
|
||||
siteOverride = anchorOverride ? BuildEligibleSiteFromPlayer(anchorOverride) : SelectSiteForPlayer(anchorOverride);
|
||||
|
||||
|
|
@ -746,9 +814,11 @@ private:
|
|||
|
||||
if (!siteOverride.ZoneId)
|
||||
{
|
||||
_timeUntilNextEventMs = 5 * MINUTE * IN_MILLISECONDS;
|
||||
_worldWasEmpty = true;
|
||||
_timeUntilNextEventMs = _config.RetryMs;
|
||||
if (_config.Debug)
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush could not find any eligible world zone with a live player anchor; retrying in 5 minutes.");
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush could not find any eligible world zone with a live player anchor; retrying in {} minute(s).",
|
||||
_config.RetryMs / (MINUTE * IN_MILLISECONDS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -760,9 +830,11 @@ private:
|
|||
anchor = FindAnyEligibleWorldPlayer();
|
||||
if (!anchor || !anchor->GetMap())
|
||||
{
|
||||
_timeUntilNextEventMs = 5 * MINUTE * IN_MILLISECONDS;
|
||||
_worldWasEmpty = true;
|
||||
_timeUntilNextEventMs = _config.RetryMs;
|
||||
if (_config.Debug)
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush hotspot in {} could not find an anchor player; retrying in 5 minutes.", BuildLocationText(_currentSite));
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush hotspot in {} could not find an anchor player; retrying in {} minute(s).",
|
||||
BuildLocationText(_currentSite), _config.RetryMs / (MINUTE * IN_MILLISECONDS));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -770,10 +842,10 @@ private:
|
|||
GoldRushSite anchorSite = BuildEligibleSiteFromPlayer(anchor);
|
||||
if (!anchorSite.ZoneId)
|
||||
{
|
||||
_timeUntilNextEventMs = 5 * MINUTE * IN_MILLISECONDS;
|
||||
_timeUntilNextEventMs = _config.RetryMs;
|
||||
if (_config.Debug)
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush rejected anchor {} because the zone is no longer eligible; retrying in 5 minutes.",
|
||||
anchor->GetName());
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush rejected anchor {} because the zone is no longer eligible; retrying in {} minute(s).",
|
||||
anchor->GetName(), _config.RetryMs / (MINUTE * IN_MILLISECONDS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -783,9 +855,10 @@ private:
|
|||
|
||||
if (!SpawnHotspot(anchor))
|
||||
{
|
||||
_timeUntilNextEventMs = 5 * MINUTE * IN_MILLISECONDS;
|
||||
_timeUntilNextEventMs = _config.RetryMs;
|
||||
if (_config.Debug)
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush hotspot in {} could not spawn nodes; retrying in 5 minutes.", BuildLocationText(_currentSite));
|
||||
LOG_INFO(GoldRushLogFilter, "Gold Rush hotspot in {} could not spawn nodes; retrying in {} minute(s).",
|
||||
BuildLocationText(_currentSite), _config.RetryMs / (MINUTE * IN_MILLISECONDS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1105,6 +1178,7 @@ private:
|
|||
uint32 _timeUntilNextEventMs = 0;
|
||||
uint32 _eventTimeRemainingMs = 0;
|
||||
uint32 _nextBotPulseMs = 0;
|
||||
bool _worldWasEmpty = false;
|
||||
bool _sitesInitialized = false;
|
||||
bool _active = false;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue