Fix bot routing no-op, 1000x node lifetime, reload orphaning, floating spawns, and empty-server scheduling #1
3 changed files with 123 additions and 11 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -21,7 +21,17 @@ All notable changes to `mod-gold-rush` will be documented in this file.
|
|||
ground height, rejects cliffs and lower floors, requires line of sight to the anchor,
|
||||
and retries a few times before giving up on a node.
|
||||
|
||||
### Added
|
||||
- `GoldRush.RetryMinutes` (default 5), `GoldRush.GraceMinutes` (default 15) and
|
||||
`GoldRush.MinPlayersOnline` (default 1).
|
||||
|
||||
### Changed
|
||||
- The scheduler no longer collapses to a fixed retry poll on an empty server. Previously
|
||||
every failed attempt overwrote the rolled Min/MaxInterval with a hard-coded 5 minutes
|
||||
and stayed there, so the first player to log in got an event announced on top of them
|
||||
within one retry window. It now holds while the world is below `MinPlayersOnline`, and
|
||||
waits `GraceMinutes` once the world repopulates before running. GM-forced starts
|
||||
(`.goldrush start` / `teststart`) bypass both checks.
|
||||
- Node scatter uses a square-root radius so points spread evenly across the disc
|
||||
instead of bunching near the anchor.
|
||||
- `DespawnHotspotNodes` resolves the map once instead of per node, and warns if the
|
||||
|
|
|
|||
|
|
@ -46,6 +46,34 @@ GoldRush.MaxIntervalMinutes = 240
|
|||
|
||||
GoldRush.DurationMinutes = 20
|
||||
|
||||
#
|
||||
# GoldRush.RetryMinutes
|
||||
# Description: How long to wait before trying again when a scheduled event cannot
|
||||
# start (nobody online, no eligible zone, nowhere to place nodes).
|
||||
# The rolled Min/MaxInterval is not consumed while holding.
|
||||
# Default: 5
|
||||
|
||||
GoldRush.RetryMinutes = 5
|
||||
|
||||
#
|
||||
# GoldRush.GraceMinutes
|
||||
# Description: After the server has been sitting below MinPlayersOnline, wait this
|
||||
# long once it repopulates before running an event. Stops the first
|
||||
# person to log in from having a Gold Rush announced on top of them.
|
||||
# Set to 0 to roll a fresh Min/MaxInterval instead.
|
||||
# Default: 15
|
||||
|
||||
GoldRush.GraceMinutes = 15
|
||||
|
||||
#
|
||||
# GoldRush.MinPlayersOnline
|
||||
# Description: Minimum number of players standing in an event-eligible zone before
|
||||
# a scheduled event may run. Playerbots count toward this. GM-forced
|
||||
# starts ignore it.
|
||||
# Default: 1
|
||||
|
||||
GoldRush.MinPlayersOnline = 1
|
||||
|
||||
#
|
||||
# GoldRush.MinNodes
|
||||
# Description: Minimum number of temporary nodes to spawn in the hotspot
|
||||
|
|
|
|||
|
|
@ -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